From f7e878c1260acc87fa53331bd5dc4584791baefd Mon Sep 17 00:00:00 2001 From: PrivacyDevel <105459436+PrivacyDevel@users.noreply.github.com> Date: Tue, 30 May 2023 11:37:35 +0000 Subject: [PATCH 001/302] fixed bug that caused threads on user profiles to be hidden (#885) --- src/parser.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/parser.nim b/src/parser.nim index 5ec21e4..5b0d584 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -490,6 +490,10 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Timeline = if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) result.content.add tweet + elif entryId.startsWith("profile-conversation") or entryId.startsWith("homeConversation"): + let (thread, self) = parseGraphThread(e) + for tweet in thread.content: + result.content.add tweet elif entryId.startsWith("cursor-bottom"): result.bottom = e{"content", "value"}.getStr From 38985af6ed30f050201b15425cdac0dc2e286b6d Mon Sep 17 00:00:00 2001 From: PrivacyDevel <105459436+PrivacyDevel@users.noreply.github.com> Date: Tue, 30 May 2023 21:42:14 +0000 Subject: [PATCH 002/302] fixed bug that caused everybody to be displayed as verified (#890) --- src/parser.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.nim b/src/parser.nim index 5b0d584..38dbb24 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -33,7 +33,7 @@ proc parseGraphUser(js: JsonNode): User = result = parseUser(user{"legacy"}) if "is_blue_verified" in user: - result.verified = true + result.verified = user{"is_blue_verified"}.getBool() proc parseGraphList*(js: JsonNode): List = if js.isNull: return From dcf73354ff173c0407c62f84ea3bb90a130303c1 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 1 Jul 2023 22:07:37 +0200 Subject: [PATCH 003/302] Fix GraphQL user crash with invalid JSON --- src/experimental/parser/graphql.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 36014e3..0f08c4f 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -4,6 +4,9 @@ import user, ../types/[graphuser, graphlistmembers] from ../../types import User, Result, Query, QueryKind proc parseGraphUser*(json: string): User = + if json.len == 0 or json[0] != '{': + return + let raw = json.fromJson(GraphUser) if raw.data.user.result.reason.get("") == "Suspended": From 0bc3c153d9b38a3c02f321fb64a375fef6b97e8e Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 10 Jul 2023 11:25:34 +0200 Subject: [PATCH 004/302] Fix everything (#927) * Switch bearer token and endpoints, update parser * Enable user search, disable tweet search * Disable multi-user timelines for now * Fix parsing of pinned tombstone --- src/api.nim | 26 ++++----- src/consts.nim | 67 +++++++++-------------- src/experimental/parser/graphql.nim | 8 +-- src/experimental/types/graphuser.nim | 4 +- src/parser.nim | 82 ++++++++++++++-------------- src/routes/embed.nim | 12 ++-- src/routes/rss.nim | 52 +++++++++--------- src/routes/search.nim | 14 +++-- src/routes/timeline.nim | 43 ++++++--------- src/sass/tweet/thread.scss | 26 +++++++++ src/tokens.nim | 9 ++- src/types.nim | 5 +- src/views/rss.nimf | 43 ++++++++------- src/views/search.nim | 2 +- src/views/timeline.nim | 48 ++++++++-------- src/views/tweet.nim | 18 +++--- tests/test_card.py | 35 +++--------- tests/test_profile.py | 4 +- tests/test_search.py | 10 ++-- tests/test_tweet.py | 16 +++--- 20 files changed, 260 insertions(+), 264 deletions(-) diff --git a/src/api.nim b/src/api.nim index b23aa87..d99eb3d 100644 --- a/src/api.nim +++ b/src/api.nim @@ -7,20 +7,20 @@ import experimental/parser as newParser proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return let - variables = %*{"screen_name": username} - params = {"variables": $variables, "features": gqlFeatures} + variables = """{"screen_name": "$1"}""" % username + params = {"variables": variables, "features": gqlFeatures} js = await fetchRaw(graphUser ? params, Api.userScreenName) result = parseGraphUser(js) proc getGraphUserById*(id: string): Future[User] {.async.} = if id.len == 0 or id.any(c => not c.isDigit): return let - variables = %*{"userId": id} - params = {"variables": $variables, "features": gqlFeatures} + variables = """{"rest_id": "$1"}""" % id + params = {"variables": variables, "features": gqlFeatures} js = await fetchRaw(graphUserById ? params, Api.userRestId) result = parseGraphUser(js) -proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Timeline] {.async.} = +proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profile] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" @@ -40,7 +40,7 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = variables = listTweetsVariables % [id, cursor] params = {"variables": variables, "features": gqlFeatures} js = await fetch(graphListTweets ? params, Api.listTweets) - result = parseGraphTimeline(js, "list", after) + result = parseGraphTimeline(js, "list", after).tweets proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = let @@ -50,8 +50,8 @@ proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = proc getGraphList*(id: string): Future[List] {.async.} = let - variables = %*{"listId": id} - params = {"variables": $variables, "features": gqlFeatures} + variables = """{"listId": "$1"}""" % id + params = {"variables": variables, "features": gqlFeatures} result = parseGraphList(await fetch(graphListById ? params, Api.list)) proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} = @@ -72,7 +72,7 @@ proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} = if id.len == 0: return let - variables = tweetResultVariables % id + variables = """{"rest_id": "$1"}""" % id params = {"variables": variables, "features": gqlFeatures} js = await fetch(graphTweetResult ? params, Api.tweetResult) result = parseGraphTweetResult(js) @@ -95,10 +95,10 @@ proc getTweet*(id: string; after=""): Future[Conversation] {.async.} = if after.len > 0: result.replies = await getReplies(id, after) -proc getGraphSearch*(query: Query; after=""): Future[Result[Tweet]] {.async.} = +proc getGraphSearch*(query: Query; after=""): Future[Profile] {.async.} = let q = genQueryParam(query) if q.len == 0 or q == emptyQuery: - return Result[Tweet](query: query, beginning: true) + return Profile(tweets: Timeline(query: query, beginning: true)) var variables = %*{ @@ -112,8 +112,8 @@ proc getGraphSearch*(query: Query; after=""): Future[Result[Tweet]] {.async.} = if after.len > 0: variables["cursor"] = % after let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} - result = parseGraphSearch(await fetch(url, Api.search), after) - result.query = query + result = Profile(tweets: parseGraphSearch(await fetch(url, Api.search), after)) + result.tweets.query = query proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = if query.text.len == 0: diff --git a/src/consts.nim b/src/consts.nim index f22581f..184f9da 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -2,7 +2,7 @@ import uri, sequtils, strutils const - auth* = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" + auth* = "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" api = parseUri("https://api.twitter.com") activate* = $(api / "1.1/guest/activate.json") @@ -11,18 +11,18 @@ const userSearch* = api / "1.1/users/search.json" graphql = api / "graphql" - graphUser* = graphql / "pVrmNaXcxPjisIvKtLDMEA/UserByScreenName" - graphUserById* = graphql / "1YAM811Q8Ry4XyPpJclURQ/UserByRestId" - graphUserTweets* = graphql / "WzJjibAcDa-oCjCcLOotcg/UserTweets" - graphUserTweetsAndReplies* = graphql / "fn9oRltM1N4thkh5CVusPg/UserTweetsAndReplies" - graphUserMedia* = graphql / "qQoeS7szGavsi8-ehD2AWg/UserMedia" - graphTweet* = graphql / "miKSMGb2R1SewIJv2-ablQ/TweetDetail" - graphTweetResult* = graphql / "0kc0a_7TTr3dvweZlMslsQ/TweetResultByRestId" + graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" + graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" + graphUserTweets* = graphql / "3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2" + graphUserTweetsAndReplies* = graphql / "8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2" + graphUserMedia* = graphql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" + graphTweet* = graphql / "83h5UyHZ9wEKBVzALX8R_g/ConversationTimelineV2" + graphTweetResult* = graphql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" graphSearchTimeline* = graphql / "gkjsKepM6gl_HmFWoWKfgg/SearchTimeline" graphListById* = graphql / "iTpgCtbdxrsJfyx0cFjHqg/ListByRestId" graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug" graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers" - graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline" + graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" timelineParams* = { "include_profile_interstitial_type": "0", @@ -49,10 +49,13 @@ const }.toSeq gqlFeatures* = """{ + "android_graphql_skip_api_media_color_palette": false, "blue_business_profile_image_shape_enabled": false, + "creator_subscriptions_subscription_count_enabled": false, "creator_subscriptions_tweet_preview_api_enabled": true, "freedom_of_speech_not_reach_fetch_enabled": false, "graphql_is_translatable_rweb_tweet_is_translatable_enabled": false, + "hidden_profile_likes_enabled": false, "highlights_tweets_tab_ui_enabled": false, "interactive_text_enabled": false, "longform_notetweets_consumption_enabled": true, @@ -64,15 +67,25 @@ const "responsive_web_graphql_exclude_directive_enabled": true, "responsive_web_graphql_skip_user_profile_image_extensions_enabled": false, "responsive_web_graphql_timeline_navigation_enabled": false, + "responsive_web_media_download_video_enabled": false, "responsive_web_text_conversations_enabled": false, + "responsive_web_twitter_article_tweet_consumption_enabled": false, "responsive_web_twitter_blue_verified_badge_is_enabled": true, "rweb_lists_timeline_redesign_enabled": true, "spaces_2022_h2_clipping": true, "spaces_2022_h2_spaces_communities": true, "standardized_nudges_misinfo": false, + "subscriptions_verification_info_enabled": true, + "subscriptions_verification_info_reason_enabled": true, + "subscriptions_verification_info_verified_since_enabled": true, + "super_follow_badge_privacy_enabled": false, + "super_follow_exclusive_tweet_notifications_enabled": false, + "super_follow_tweet_api_enabled": false, + "super_follow_user_api_enabled": false, "tweet_awards_web_tipping_enabled": false, "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": false, "tweetypie_unmention_optimization_enabled": false, + "unified_cards_ad_metadata_container_dynamic_card_content_query_enabled": false, "verified_phone_label_enabled": false, "vibe_api_enabled": false, "view_counts_everywhere_api_enabled": false @@ -81,41 +94,15 @@ const tweetVariables* = """{ "focalTweetId": "$1", $2 - "withBirdwatchNotes": false, - "includePromotedContent": false, - "withDownvotePerspective": false, - "withReactionsMetadata": false, - "withReactionsPerspective": false, - "withVoice": false -}""" - - tweetResultVariables* = """{ - "tweetId": "$1", - "includePromotedContent": false, - "withDownvotePerspective": false, - "withReactionsMetadata": false, - "withReactionsPerspective": false, - "withVoice": false, - "withCommunity": false + "includeHasBirdwatchNotes": false }""" userTweetsVariables* = """{ - "userId": "$1", $2 - "count": 20, - "includePromotedContent": false, - "withDownvotePerspective": false, - "withReactionsMetadata": false, - "withReactionsPerspective": false, - "withVoice": false, - "withV2Timeline": true + "rest_id": "$1", $2 + "count": 20 }""" listTweetsVariables* = """{ - "listId": "$1", $2 - "count": 20, - "includePromotedContent": false, - "withDownvotePerspective": false, - "withReactionsMetadata": false, - "withReactionsPerspective": false, - "withVoice": false + "rest_id": "$1", $2 + "count": 20 }""" diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 0f08c4f..b9da7c4 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -9,12 +9,12 @@ proc parseGraphUser*(json: string): User = let raw = json.fromJson(GraphUser) - if raw.data.user.result.reason.get("") == "Suspended": + if raw.data.userResult.result.unavailableReason.get("") == "Suspended": return User(suspended: true) - result = toUser raw.data.user.result.legacy - result.id = raw.data.user.result.restId - result.verified = result.verified or raw.data.user.result.isBlueVerified + result = toUser raw.data.userResult.result.legacy + result.id = raw.data.userResult.result.restId + result.verified = result.verified or raw.data.userResult.result.isBlueVerified proc parseGraphListMembers*(json, cursor: string): Result[User] = result = Result[User]( diff --git a/src/experimental/types/graphuser.nim b/src/experimental/types/graphuser.nim index 478e7f3..c30eed9 100644 --- a/src/experimental/types/graphuser.nim +++ b/src/experimental/types/graphuser.nim @@ -3,7 +3,7 @@ import user type GraphUser* = object - data*: tuple[user: UserData] + data*: tuple[userResult: UserData] UserData* = object result*: UserResult @@ -12,4 +12,4 @@ type legacy*: RawUser restId*: string isBlueVerified*: bool - reason*: Option[string] + unavailableReason*: Option[string] diff --git a/src/parser.nim b/src/parser.nim index 38dbb24..7b178f3 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -29,7 +29,7 @@ proc parseUser(js: JsonNode; id=""): User = result.expandUserEntities(js) proc parseGraphUser(js: JsonNode): User = - let user = ? js{"user_results", "result"} + let user = ? js{"user_result", "result"} result = parseUser(user{"legacy"}) if "is_blue_verified" in user: @@ -262,6 +262,11 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.gif = some(parseGif(m)) else: discard + with url, m{"url"}: + if result.text.endsWith(url.getStr): + result.text.removeSuffix(url.getStr) + result.text = result.text.strip() + with jsWithheld, js{"withheld_in_countries"}: let withheldInCountries: seq[string] = if jsWithheld.kind != JArray: @[] @@ -294,16 +299,6 @@ proc finalizeTweet(global: GlobalObjects; id: string): Tweet = else: result.retweet = some Tweet() -proc parsePin(js: JsonNode; global: GlobalObjects): Tweet = - let pin = js{"pinEntry", "entry", "entryId"}.getStr - if pin.len == 0: return - - let id = pin.getId - if id notin global.tweets: return - - global.tweets[id].pinned = true - return finalizeTweet(global, id) - proc parseGlobalObjects(js: JsonNode): GlobalObjects = result = GlobalObjects() let @@ -314,7 +309,7 @@ proc parseGlobalObjects(js: JsonNode): GlobalObjects = result.users[k] = parseUser(v, k) for k, v in tweets: - var tweet = parseTweet(v, v{"card"}) + var tweet = parseTweet(v, v{"tweet_card"}) if tweet.user.id in result.users: tweet.user = result.users[tweet.user.id] result.tweets[k] = tweet @@ -324,11 +319,6 @@ proc parseInstructions[T](res: var Result[T]; global: GlobalObjects; js: JsonNod return for i in js: - when T is Tweet: - if res.beginning and i{"pinEntry"}.notNull: - with pin, parsePin(i, global): - res.content.add pin - with r, i{"replaceEntry", "entry"}: if "top" in r{"entryId"}.getStr: res.top = r.getCursor @@ -369,7 +359,7 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline = proc parsePhotoRail*(js: JsonNode): PhotoRail = for tweet in js: let - t = parseTweet(tweet, js{"card"}) + t = parseTweet(tweet, js{"tweet_card"}) url = if t.photos.len > 0: t.photos[0] elif t.video.isSome: get(t.video).thumb elif t.gif.isSome: get(t.gif).thumb @@ -387,13 +377,17 @@ proc parseGraphTweet(js: JsonNode): Tweet = of "TweetUnavailable": return Tweet() of "TweetTombstone": - return Tweet(text: js{"tombstone", "text"}.getTombstone) + with text, js{"tombstone", "richText"}: + return Tweet(text: text.getTombstone) + with text, js{"tombstone", "text"}: + return Tweet(text: text.getTombstone) + return Tweet() of "TweetPreviewDisplay": return Tweet(text: "You're unable to view this Tweet because it's only available to the Subscribers of the account owner.") of "TweetWithVisibilityResults": return parseGraphTweet(js{"tweet"}) - var jsCard = copy(js{"card", "legacy"}) + var jsCard = copy(js{"tweet_card", "legacy"}) if jsCard.kind != JNull: var values = newJObject() for val in jsCard["binding_values"]: @@ -401,6 +395,7 @@ proc parseGraphTweet(js: JsonNode): Tweet = jsCard["binding_values"] = values result = parseTweet(js{"legacy"}, jsCard) + result.id = js{"rest_id"}.getId result.user = parseGraphUser(js{"core"}) with noteTweet, js{"note_tweet", "note_tweet_results", "result"}: @@ -414,32 +409,31 @@ proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = for t in js{"content", "items"}: let entryId = t{"entryId"}.getStr if "cursor-showmore" in entryId: - let cursor = t{"item", "itemContent", "value"} + let cursor = t{"item", "content", "value"} result.thread.cursor = cursor.getStr result.thread.hasMore = true elif "tweet" in entryId: - let tweet = parseGraphTweet(t{"item", "itemContent", "tweet_results", "result"}) + let tweet = parseGraphTweet(t{"item", "content", "tweetResult", "result"}) result.thread.content.add tweet - if t{"item", "itemContent", "tweetDisplayType"}.getStr == "SelfThread": + if t{"item", "content", "tweetDisplayType"}.getStr == "SelfThread": result.self = true proc parseGraphTweetResult*(js: JsonNode): Tweet = - with tweet, js{"data", "tweetResult", "result"}: + with tweet, js{"data", "tweet_result", "result"}: result = parseGraphTweet(tweet) proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) - let instructions = ? js{"data", "threaded_conversation_with_injections", "instructions"} + let instructions = ? js{"data", "timeline_response", "instructions"} if instructions.len == 0: return for e in instructions[0]{"entries"}: let entryId = e{"entryId"}.getStr - # echo entryId if entryId.startsWith("tweet"): - with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: + with tweetResult, e{"content", "content", "tweetResult", "result"}: let tweet = parseGraphTweet(tweetResult) if not tweet.available: @@ -454,7 +448,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = let tweet = Tweet( id: parseBiggestInt(id), available: false, - text: e{"content", "itemContent", "tombstoneInfo", "richText"}.getTombstone + text: e{"content", "content", "tombstoneInfo", "richText"}.getTombstone ) if id == tweetId: @@ -468,34 +462,42 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = else: result.replies.content.add thread elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", "itemContent", "value"}.getStr + result.replies.bottom = e{"content", "content", "value"}.getStr -proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Timeline = - result = Timeline(beginning: after.len == 0) +proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = + result = Profile(tweets: Timeline(beginning: after.len == 0)) let instructions = - if root == "list": ? js{"data", "list", "tweets_timeline", "timeline", "instructions"} - else: ? js{"data", "user", "result", "timeline_v2", "timeline", "instructions"} + if root == "list": ? js{"data", "list", "timeline_response", "timeline", "instructions"} + else: ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} if instructions.len == 0: return for i in instructions: - if i{"type"}.getStr == "TimelineAddEntries": + if i{"__typename"}.getStr == "TimelineAddEntries": for e in i{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): - with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: + with tweetResult, e{"content", "content", "tweetResult", "result"}: let tweet = parseGraphTweet(tweetResult) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) - result.content.add tweet - elif entryId.startsWith("profile-conversation") or entryId.startsWith("homeConversation"): + result.tweets.content.add tweet + elif "-conversation-" in entryId or entryId.startsWith("homeConversation"): let (thread, self) = parseGraphThread(e) - for tweet in thread.content: - result.content.add tweet + result.tweets.content.add thread elif entryId.startsWith("cursor-bottom"): - result.bottom = e{"content", "value"}.getStr + result.tweets.bottom = e{"content", "value"}.getStr + if after.len == 0 and i{"__typename"}.getStr == "TimelinePinEntry": + with tweetResult, i{"entry", "content", "content", "tweetResult", "result"}: + let tweet = parseGraphTweet(tweetResult) + tweet.pinned = true + if not tweet.available and tweet.tombstone.len == 0: + let entryId = i{"entry", "entryId"}.getEntryId + if entryId.len > 0: + tweet.id = parseBiggestInt(entryId) + result.pinned = some tweet proc parseGraphSearch*(js: JsonNode; after=""): Timeline = result = Timeline(beginning: after.len == 0) diff --git a/src/routes/embed.nim b/src/routes/embed.nim index baaec68..994364b 100644 --- a/src/routes/embed.nim +++ b/src/routes/embed.nim @@ -10,22 +10,22 @@ export api, embed, vdom, tweet, general, router_utils proc createEmbedRouter*(cfg: Config) = router embed: get "/i/videos/tweet/@id": - let convo = await getTweet(@"id") - if convo == nil or convo.tweet == nil or convo.tweet.video.isNone: + let tweet = await getGraphTweetResult(@"id") + if tweet == nil or tweet.video.isNone: resp Http404 - resp renderVideoEmbed(convo.tweet, cfg, request) + resp renderVideoEmbed(tweet, cfg, request) get "/@user/status/@id/embed": let - convo = await getTweet(@"id") + tweet = await getGraphTweetResult(@"id") prefs = cookiePrefs() path = getPath() - if convo == nil or convo.tweet == nil: + if tweet == nil: resp Http404 - resp renderTweetEmbed(convo.tweet, path, prefs, cfg, request) + resp renderTweetEmbed(tweet, path, prefs, cfg, request) get "/embed/Tweet.html": let id = @"id" diff --git a/src/routes/rss.nim b/src/routes/rss.nim index 1323ed3..8eec399 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -27,14 +27,12 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async. else: var q = query q.fromUser = names - profile = Profile( - tweets: await getGraphSearch(q, after), - # this is kinda dumb - user: User( - username: name, - fullname: names.join(" | "), - userpic: "https://abs.twimg.com/sticky/default_profile_images/default_profile.png" - ) + profile = await getGraphSearch(q, after) + # this is kinda dumb + profile.user = User( + username: name, + fullname: names.join(" | "), + userpic: "https://abs.twimg.com/sticky/default_profile_images/default_profile.png" ) if profile.user.suspended: @@ -61,29 +59,29 @@ template respRss*(rss, page) = proc createRssRouter*(cfg: Config) = router rss: - get "/search/rss": - cond cfg.enableRss - if @"q".len > 200: - resp Http400, showError("Search input too long.", cfg) + # get "/search/rss": + # cond cfg.enableRss + # if @"q".len > 200: + # resp Http400, showError("Search input too long.", cfg) - let query = initQuery(params(request)) - if query.kind != tweets: - resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg) + # let query = initQuery(params(request)) + # if query.kind != tweets: + # resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg) - let - cursor = getCursor() - key = redisKey("search", $hash(genQueryUrl(query)), cursor) + # let + # cursor = getCursor() + # key = redisKey("search", $hash(genQueryUrl(query)), cursor) - var rss = await getCachedRss(key) - if rss.cursor.len > 0: - respRss(rss, "Search") + # var rss = await getCachedRss(key) + # if rss.cursor.len > 0: + # respRss(rss, "Search") - let tweets = await getGraphSearch(query, cursor) - rss.cursor = tweets.bottom - rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg) + # let tweets = await getGraphSearch(query, cursor) + # rss.cursor = tweets.bottom + # rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg) - await cacheRss(key, rss) - respRss(rss, "Search") + # await cacheRss(key, rss) + # respRss(rss, "Search") get "/@name/rss": cond cfg.enableRss @@ -112,7 +110,7 @@ proc createRssRouter*(cfg: Config) = case tab of "with_replies": getReplyQuery(name) of "media": getMediaQuery(name) - of "search": initQuery(params(request), name=name) + # of "search": initQuery(params(request), name=name) else: Query(fromUser: @[name]) let searchKey = if tab != "search": "" diff --git a/src/routes/search.nim b/src/routes/search.nim index 02c14e3..ed2c397 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -34,11 +34,15 @@ proc createSearchRouter*(cfg: Config) = users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) of tweets: - let - tweets = await getGraphSearch(query, getCursor()) - rss = "/search/rss?" & genQueryUrl(query) - resp renderMain(renderTweetSearch(tweets, prefs, getPath()), - request, cfg, prefs, title, rss=rss) + # let + # tweets = await getGraphSearch(query, getCursor()) + # rss = "/search/rss?" & genQueryUrl(query) + # resp renderMain(renderTweetSearch(tweets, prefs, getPath()), + # request, cfg, prefs, title, rss=rss) + var fakeTimeline = Timeline(beginning: true) + fakeTimeline.content.add Tweet(tombstone: "Tweet search is unavailable for now") + + resp renderMain(renderTweetSearch(fakeTimeline, prefs, getPath()), request, cfg, prefs, title) else: resp Http404, showError("Invalid search", cfg) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 331b8ae..4ac60d2 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -45,34 +45,24 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; after.setLen 0 let - timeline = - case query.kind - of posts: getGraphUserTweets(userId, TimelineKind.tweets, after) - of replies: getGraphUserTweets(userId, TimelineKind.replies, after) - of media: getGraphUserTweets(userId, TimelineKind.media, after) - else: getGraphSearch(query, after) - rail = skipIf(skipRail or query.kind == media, @[]): getCachedPhotoRail(name) - user = await getCachedUser(name) + user = getCachedUser(name) - var pinned: Option[Tweet] - if not skipPinned and user.pinnedTweet > 0 and - after.len == 0 and query.kind in {posts, replies}: - let tweet = await getCachedTweet(user.pinnedTweet) - if not tweet.isNil: - tweet.pinned = true - tweet.user = user - pinned = some tweet + result = + case query.kind + of posts: await getGraphUserTweets(userId, TimelineKind.tweets, after) + of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) + of media: await getGraphUserTweets(userId, TimelineKind.media, after) + else: Profile(tweets: Timeline(beginning: true, content: @[Chain(content: + @[Tweet(tombstone: "Tweet search is unavailable for now")] + )])) + # else: await getGraphSearch(query, after) - result = Profile( - user: user, - pinned: pinned, - tweets: await timeline, - photoRail: await rail - ) + result.user = await user + result.photoRail = await rail if result.user.protected or result.user.suspended: return @@ -83,8 +73,11 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; rss, after: string): Future[string] {.async.} = if query.fromUser.len != 1: let - timeline = await getGraphSearch(query, after) - html = renderTweetSearch(timeline, prefs, getPath()) + # timeline = await getGraphSearch(query, after) + timeline = Profile(tweets: Timeline(beginning: true, content: @[Chain(content: + @[Tweet(tombstone: "This features is unavailable for now")] + )])) + html = renderTweetSearch(timeline.tweets, prefs, getPath()) return renderMain(html, request, cfg, prefs, "Multi", rss=rss) var profile = await fetchProfile(after, query, skipPinned=prefs.hidePins) @@ -138,7 +131,7 @@ proc createTimelineRouter*(cfg: Config) = # used for the infinite scroll feature if @"scroll".len > 0: if query.fromUser.len != 1: - var timeline = await getGraphSearch(query, after) + var timeline = (await getGraphSearch(query, after)).tweets if timeline.content.len == 0: resp Http404 timeline.beginning = true resp $renderTweetSearch(timeline, prefs, getPath()) diff --git a/src/sass/tweet/thread.scss b/src/sass/tweet/thread.scss index 5fbad21..19fb3e0 100644 --- a/src/sass/tweet/thread.scss +++ b/src/sass/tweet/thread.scss @@ -110,3 +110,29 @@ margin-left: 58px; padding: 7px 0; } + +.timeline-item.thread.more-replies-thread { + padding: 0 0.75em; + + &::before { + top: 40px; + margin-bottom: 31px; + } + + .more-replies { + display: flex; + padding-top: unset !important; + margin-top: 8px; + + &::before { + display: inline-block; + position: relative; + top: -1px; + line-height: 0.4em; + } + + .more-replies-text { + display: inline; + } + } +} diff --git a/src/tokens.nim b/src/tokens.nim index 6ef81f5..6643de3 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -41,11 +41,10 @@ proc getPoolJson*(): JsonNode = let maxReqs = case api - of Api.timeline: 187 - of Api.listMembers, Api.listBySlug, Api.list, Api.listTweets, - Api.userTweets, Api.userTweetsAndReplies, Api.userMedia, - Api.userRestId, Api.userScreenName, - Api.tweetDetail, Api.tweetResult, Api.search: 500 + of Api.timeline: 180 + of Api.userTweets, Api.userTweetsAndReplies, Api.userRestId, + Api.userScreenName, Api.tweetDetail, Api.tweetResult, Api.search: 500 + of Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500 of Api.userSearch: 900 reqs = maxReqs - token.apis[api].remaining diff --git a/src/types.nim b/src/types.nim index 4dca5f0..e7f3303 100644 --- a/src/types.nim +++ b/src/types.nim @@ -222,7 +222,7 @@ type after*: Chain replies*: Result[Chain] - Timeline* = Result[Tweet] + Timeline* = Result[Chain] Profile* = object user*: User @@ -274,3 +274,6 @@ type proc contains*(thread: Chain; tweet: Tweet): bool = thread.content.anyIt(it.id == tweet.id) + +proc add*(timeline: var seq[Chain]; tweet: Tweet) = + timeline.add Chain(content: @[tweet]) diff --git a/src/views/rss.nimf b/src/views/rss.nimf index 96f6466..ce2518a 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -56,24 +56,29 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} #end if #end proc # -#proc renderRssTweets(tweets: seq[Tweet]; cfg: Config): string = +#proc renderRssTweets(tweets: seq[Chain]; cfg: Config; userId=""): string = #let urlPrefix = getUrlPrefix(cfg) #var links: seq[string] -#for t in tweets: -# let retweet = if t.retweet.isSome: t.user.username else: "" -# let tweet = if retweet.len > 0: t.retweet.get else: t -# let link = getLink(tweet) -# if link in links: continue -# end if -# links.add link - - ${getTitle(tweet, retweet)} - @${tweet.user.username} - - ${getRfc822Time(tweet)} - ${urlPrefix & link} - ${urlPrefix & link} - +#for c in tweets: +# for t in c.content: +# if userId.len > 0 and t.user.id != userId: continue +# end if +# +# let retweet = if t.retweet.isSome: t.user.username else: "" +# let tweet = if retweet.len > 0: t.retweet.get else: t +# let link = getLink(tweet) +# if link in links: continue +# end if +# links.add link + + ${getTitle(tweet, retweet)} + @${tweet.user.username} + + ${getRfc822Time(tweet)} + ${urlPrefix & link} + ${urlPrefix & link} + +# end for #end for #end proc # @@ -102,13 +107,13 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} 128 #if profile.tweets.content.len > 0: -${renderRssTweets(profile.tweets.content, cfg)} +${renderRssTweets(profile.tweets.content, cfg, userId=profile.user.id)} #end if #end proc # -#proc renderListRss*(tweets: seq[Tweet]; list: List; cfg: Config): string = +#proc renderListRss*(tweets: seq[Chain]; list: List; cfg: Config): string = #let link = &"{getUrlPrefix(cfg)}/i/lists/{list.id}" #result = "" @@ -125,7 +130,7 @@ ${renderRssTweets(tweets, cfg)} #end proc # -#proc renderSearchRss*(tweets: seq[Tweet]; name, param: string; cfg: Config): string = +#proc renderSearchRss*(tweets: seq[Chain]; name, param: string; cfg: Config): string = #let link = &"{getUrlPrefix(cfg)}/search" #let escName = xmltree.escape(name) #result = "" diff --git a/src/views/search.nim b/src/views/search.nim index 72c59f5..401e6da 100644 --- a/src/views/search.nim +++ b/src/views/search.nim @@ -88,7 +88,7 @@ proc renderSearchPanel*(query: Query): VNode = span(class="search-title"): text "Near" genInput("near", "", query.near, "Location...", autofocus=false) -proc renderTweetSearch*(results: Result[Tweet]; prefs: Prefs; path: string; +proc renderTweetSearch*(results: Timeline; prefs: Prefs; path: string; pinned=none(Tweet)): VNode = let query = results.query buildHtml(tdiv(class="timeline-container")): diff --git a/src/views/timeline.nim b/src/views/timeline.nim index 54cad7a..8ae888e 100644 --- a/src/views/timeline.nim +++ b/src/views/timeline.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, strformat, sequtils, algorithm, uri, options +import strutils, strformat, algorithm, uri, options import karax/[karaxdsl, vdom] import ".."/[types, query, formatters] @@ -43,20 +43,18 @@ proc renderThread(thread: seq[Tweet]; prefs: Prefs; path: string): VNode = buildHtml(tdiv(class="thread-line")): let sortedThread = thread.sortedByIt(it.id) for i, tweet in sortedThread: + # thread has a gap, display "more replies" link + if i > 0 and tweet.replyId != sortedThread[i - 1].id: + tdiv(class="timeline-item thread more-replies-thread"): + tdiv(class="more-replies"): + a(class="more-replies-text", href=getLink(tweet)): + text "more replies" + let show = i == thread.high and sortedThread[0].id != tweet.threadId let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: "" renderTweet(tweet, prefs, path, class=(header & "thread"), index=i, last=(i == thread.high), showThread=show) -proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet): seq[Tweet] = - result = @[it] - if it.retweet.isSome or it.replyId in threads: return - for t in tweets: - if t.id == result[0].replyId: - result.insert t - elif t.replyId == result[0].id: - result.add t - proc renderUser(user: User; prefs: Prefs): VNode = buildHtml(tdiv(class="timeline-item")): a(class="tweet-link", href=("/" & user.username)) @@ -89,7 +87,7 @@ proc renderTimelineUsers*(results: Result[User]; prefs: Prefs; path=""): VNode = else: renderNoMore() -proc renderTimelineTweets*(results: Result[Tweet]; prefs: Prefs; path: string; +proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string; pinned=none(Tweet)): VNode = buildHtml(tdiv(class="timeline")): if not results.beginning: @@ -105,26 +103,26 @@ proc renderTimelineTweets*(results: Result[Tweet]; prefs: Prefs; path: string; else: renderNoneFound() else: - var - threads: seq[int64] - retweets: seq[int64] + var retweets: seq[int64] - for tweet in results.content: - let rt = if tweet.retweet.isSome: get(tweet.retweet).id else: 0 + for thread in results.content: + if thread.content.len == 1: + let + tweet = thread.content[0] + retweetId = if tweet.retweet.isSome: get(tweet.retweet).id else: 0 - if tweet.id in threads or rt in retweets or tweet.id in retweets or - tweet.pinned and prefs.hidePins: continue + if retweetId in retweets or tweet.id in retweets or + tweet.pinned and prefs.hidePins: + continue - let thread = results.content.threadFilter(threads, tweet) - if thread.len < 2: var hasThread = tweet.hasThread - if rt != 0: - retweets &= rt + if retweetId != 0 and tweet.retweet.isSome: + retweets &= retweetId hasThread = get(tweet.retweet).hasThread renderTweet(tweet, prefs, path, showThread=hasThread) else: - renderThread(thread, prefs, path) - threads &= thread.mapIt(it.id) + renderThread(thread.content, prefs, path) - renderMore(results.query, results.bottom) + if results.bottom.len > 0: + renderMore(results.query, results.bottom) renderToTop() diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 3338b71..f47ae9a 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -14,15 +14,14 @@ proc renderMiniAvatar(user: User; prefs: Prefs): VNode = buildHtml(): img(class=(prefs.getAvatarClass & " mini"), src=url) -proc renderHeader(tweet: Tweet; retweet: string; prefs: Prefs): VNode = +proc renderHeader(tweet: Tweet; retweet: string; pinned: bool; prefs: Prefs): VNode = buildHtml(tdiv): - if retweet.len > 0: - tdiv(class="retweet-header"): - span: icon "retweet", retweet & " retweeted" - - if tweet.pinned: + if pinned: tdiv(class="pinned"): span: icon "pin", "Pinned Tweet" + elif retweet.len > 0: + tdiv(class="retweet-header"): + span: icon "retweet", retweet & " retweeted" tdiv(class="tweet-header"): a(class="tweet-avatar", href=("/" & tweet.user.username)): @@ -290,7 +289,10 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; if tweet.quote.isSome: renderQuote(tweet.quote.get(), prefs, path) - let fullTweet = tweet + let + fullTweet = tweet + pinned = tweet.pinned + var retweet: string var tweet = fullTweet if tweet.retweet.isSome: @@ -303,7 +305,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; tdiv(class="tweet-body"): var views = "" - renderHeader(tweet, retweet, prefs) + renderHeader(tweet, retweet, pinned, prefs) if not afterTweet and index == 0 and tweet.reply.len > 0 and (tweet.reply.len > 1 or tweet.reply[0] != tweet.user.username): diff --git a/tests/test_card.py b/tests/test_card.py index 696b9d5..f84ddca 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -16,7 +16,12 @@ card = [ ['FluentAI/status/1116417904831029248', 'Amazon’s Alexa isn’t just AI — thousands of humans are listening', 'One of the only ways to improve Alexa is to have human beings check it for errors', - 'theverge.com', True] + 'theverge.com', True], + + ['nim_lang/status/1082989146040340480', + 'Nim in 2018: A short recap', + 'There were several big news in the Nim world in 2018 – two new major releases, partnership with Status, and much more. But let us go chronologically.', + 'nim-lang.org', True] ] no_thumb = [ @@ -33,12 +38,7 @@ no_thumb = [ ['voidtarget/status/1133028231672582145', 'sinkingsugar/nimqt-example', 'A sample of a Qt app written using mostly nim. Contribute to sinkingsugar/nimqt-example development by creating an account on GitHub.', - 'github.com'], - - ['nim_lang/status/1082989146040340480', - 'Nim in 2018: A short recap', - 'Posted by u/miran1 - 36 votes and 46 comments', - 'reddit.com'] + 'github.com'] ] playable = [ @@ -53,17 +53,6 @@ playable = [ 'youtube.com'] ] -# promo = [ - # ['BangOlufsen/status/1145698701517754368', - # 'Upgrade your journey', '', - # 'www.bang-olufsen.com'], - - # ['BangOlufsen/status/1154934429900406784', - # 'Learn more about Beosound Shape', '', - # 'www.bang-olufsen.com'] -# ] - - class CardTest(BaseTestCase): @parameterized.expand(card) def test_card(self, tweet, title, description, destination, large): @@ -98,13 +87,3 @@ class CardTest(BaseTestCase): self.assert_element_visible('.card-overlay') if len(description) > 0: self.assert_text(description, c.description) - - # @parameterized.expand(promo) - # def test_card_promo(self, tweet, title, description, destination): - # self.open_nitter(tweet) - # c = Card(Conversation.main + " ") - # self.assert_text(title, c.title) - # self.assert_text(destination, c.destination) - # self.assert_element_visible('.video-overlay') - # if len(description) > 0: - # self.assert_text(description, c.description) diff --git a/tests/test_profile.py b/tests/test_profile.py index f9b5047..4c75ad2 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -66,8 +66,8 @@ class ProfileTest(BaseTestCase): self.assert_text(f'User "{username}" not found') def test_suspended(self): - self.open_nitter('user') - self.assert_text('User "user" has been suspended') + self.open_nitter('suspendme') + self.assert_text('User "suspendme" has been suspended') @parameterized.expand(banner_image) def test_banner_image(self, username, url): diff --git a/tests/test_search.py b/tests/test_search.py index 80ee36a..62c4640 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -2,8 +2,8 @@ from base import BaseTestCase from parameterized import parameterized -class SearchTest(BaseTestCase): - @parameterized.expand([['@mobile_test'], ['@mobile_test_2']]) - def test_username_search(self, username): - self.search_username(username) - self.assert_text(f'{username}') +#class SearchTest(BaseTestCase): + #@parameterized.expand([['@mobile_test'], ['@mobile_test_2']]) + #def test_username_search(self, username): + #self.search_username(username) + #self.assert_text(f'{username}') diff --git a/tests/test_tweet.py b/tests/test_tweet.py index 9209e70..e4231a4 100644 --- a/tests/test_tweet.py +++ b/tests/test_tweet.py @@ -74,9 +74,9 @@ retweet = [ [3, 'mobile_test_8', 'mobile test 8', 'jack', '@jack', 'twttr'] ] -reply = [ - ['mobile_test/with_replies', 15] -] +# reply = [ +# ['mobile_test/with_replies', 15] +# ] class TweetTest(BaseTestCase): @@ -137,8 +137,8 @@ class TweetTest(BaseTestCase): self.open_nitter(tweet) self.assert_text('Tweet not found', '.error-panel') - @parameterized.expand(reply) - def test_thread(self, tweet, num): - self.open_nitter(tweet) - thread = self.find_element(f'.timeline > div:nth-child({num})') - self.assertIn(thread.get_attribute('class'), 'thread-line') + # @parameterized.expand(reply) + # def test_thread(self, tweet, num): + # self.open_nitter(tweet) + # thread = self.find_element(f'.timeline > div:nth-child({num})') + # self.assertIn(thread.get_attribute('class'), 'thread-line') From b290f6fd29ac20717bad359fc55d32822d3e054d Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 12 Jul 2023 01:34:39 +0200 Subject: [PATCH 005/302] Optimize timeline data structure --- src/parser.nim | 2 +- src/routes/timeline.nim | 8 ++------ src/types.nim | 10 ++++++---- src/views/rss.nimf | 16 ++++++++-------- src/views/timeline.nim | 8 ++++---- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index 7b178f3..5dc96df 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -486,7 +486,7 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result.tweets.content.add tweet elif "-conversation-" in entryId or entryId.startsWith("homeConversation"): let (thread, self) = parseGraphThread(e) - result.tweets.content.add thread + result.tweets.content.add thread.content elif entryId.startsWith("cursor-bottom"): result.tweets.bottom = e{"content", "value"}.getStr if after.len == 0 and i{"__typename"}.getStr == "TimelinePinEntry": diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 4ac60d2..e62c9e0 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -56,9 +56,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; of posts: await getGraphUserTweets(userId, TimelineKind.tweets, after) of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) of media: await getGraphUserTweets(userId, TimelineKind.media, after) - else: Profile(tweets: Timeline(beginning: true, content: @[Chain(content: - @[Tweet(tombstone: "Tweet search is unavailable for now")] - )])) + else: Profile(tweets: Timeline(beginning: true, content: @[@[Tweet(tombstone: "Tweet search is unavailable for now")]])) # else: await getGraphSearch(query, after) result.user = await user @@ -74,9 +72,7 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; if query.fromUser.len != 1: let # timeline = await getGraphSearch(query, after) - timeline = Profile(tweets: Timeline(beginning: true, content: @[Chain(content: - @[Tweet(tombstone: "This features is unavailable for now")] - )])) + timeline = Profile(tweets: Timeline(beginning: true, content: @[@[Tweet(tombstone: "This features is unavailable for now")]])) html = renderTweetSearch(timeline.tweets, prefs, getPath()) return renderMain(html, request, cfg, prefs, "Multi", rss=rss) diff --git a/src/types.nim b/src/types.nim index e7f3303..f7d5f6b 100644 --- a/src/types.nim +++ b/src/types.nim @@ -205,6 +205,8 @@ type video*: Option[Video] photos*: seq[string] + Tweets* = seq[Tweet] + Result*[T] = object content*: seq[T] top*, bottom*: string @@ -212,7 +214,7 @@ type query*: Query Chain* = object - content*: seq[Tweet] + content*: Tweets hasMore*: bool cursor*: string @@ -222,7 +224,7 @@ type after*: Chain replies*: Result[Chain] - Timeline* = Result[Chain] + Timeline* = Result[Tweets] Profile* = object user*: User @@ -275,5 +277,5 @@ type proc contains*(thread: Chain; tweet: Tweet): bool = thread.content.anyIt(it.id == tweet.id) -proc add*(timeline: var seq[Chain]; tweet: Tweet) = - timeline.add Chain(content: @[tweet]) +proc add*(timeline: var seq[Tweets]; tweet: Tweet) = + timeline.add @[tweet] diff --git a/src/views/rss.nimf b/src/views/rss.nimf index ce2518a..036a7b9 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -56,16 +56,16 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} #end if #end proc # -#proc renderRssTweets(tweets: seq[Chain]; cfg: Config; userId=""): string = +#proc renderRssTweets(tweets: seq[Tweets]; cfg: Config; userId=""): string = #let urlPrefix = getUrlPrefix(cfg) #var links: seq[string] -#for c in tweets: -# for t in c.content: -# if userId.len > 0 and t.user.id != userId: continue +#for thread in tweets: +# for tweet in thread: +# if userId.len > 0 and tweet.user.id != userId: continue # end if # -# let retweet = if t.retweet.isSome: t.user.username else: "" -# let tweet = if retweet.len > 0: t.retweet.get else: t +# let retweet = if tweet.retweet.isSome: tweet.user.username else: "" +# let tweet = if retweet.len > 0: tweet.retweet.get else: tweet # let link = getLink(tweet) # if link in links: continue # end if @@ -113,7 +113,7 @@ ${renderRssTweets(profile.tweets.content, cfg, userId=profile.user.id)} #end proc # -#proc renderListRss*(tweets: seq[Chain]; list: List; cfg: Config): string = +#proc renderListRss*(tweets: seq[Tweets]; list: List; cfg: Config): string = #let link = &"{getUrlPrefix(cfg)}/i/lists/{list.id}" #result = "" @@ -130,7 +130,7 @@ ${renderRssTweets(tweets, cfg)} #end proc # -#proc renderSearchRss*(tweets: seq[Chain]; name, param: string; cfg: Config): string = +#proc renderSearchRss*(tweets: seq[Tweets]; name, param: string; cfg: Config): string = #let link = &"{getUrlPrefix(cfg)}/search" #let escName = xmltree.escape(name) #result = "" diff --git a/src/views/timeline.nim b/src/views/timeline.nim index 8ae888e..abeb6d3 100644 --- a/src/views/timeline.nim +++ b/src/views/timeline.nim @@ -39,7 +39,7 @@ proc renderNoneFound(): VNode = h2(class="timeline-none"): text "No items found" -proc renderThread(thread: seq[Tweet]; prefs: Prefs; path: string): VNode = +proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode = buildHtml(tdiv(class="thread-line")): let sortedThread = thread.sortedByIt(it.id) for i, tweet in sortedThread: @@ -106,9 +106,9 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string; var retweets: seq[int64] for thread in results.content: - if thread.content.len == 1: + if thread.len == 1: let - tweet = thread.content[0] + tweet = thread[0] retweetId = if tweet.retweet.isSome: get(tweet.retweet).id else: 0 if retweetId in retweets or tweet.id in retweets or @@ -121,7 +121,7 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string; hasThread = get(tweet.retweet).hasThread renderTweet(tweet, prefs, path, showThread=hasThread) else: - renderThread(thread.content, prefs, path) + renderThread(thread, prefs, path) if results.bottom.len > 0: renderMore(results.query, results.bottom) From 67203a431d9242e2f0d5fbdbfa86ba55f0a4cc54 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 12 Jul 2023 03:37:44 +0200 Subject: [PATCH 006/302] Add back search --- src/api.nim | 16 ++++++++++++++++ src/consts.nim | 1 + src/parser.nim | 34 ++++++++++++++++++++++++++++++++-- src/routes/rss.nim | 38 +++++++++++++++++++------------------- src/routes/search.nim | 14 +++++--------- src/routes/timeline.nim | 8 +++----- src/tokens.nim | 4 ++-- 7 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/api.nim b/src/api.nim index d99eb3d..60af68d 100644 --- a/src/api.nim +++ b/src/api.nim @@ -115,6 +115,22 @@ proc getGraphSearch*(query: Query; after=""): Future[Profile] {.async.} = result = Profile(tweets: parseGraphSearch(await fetch(url, Api.search), after)) result.tweets.query = query +proc getTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = + let q = genQueryParam(query) + if q.len == 0 or q == emptyQuery: + return Timeline(query: query, beginning: true) + + let url = tweetSearch ? genParams({ + "q": q, + "tweet_search_mode": "live", + "max_id": after + }) + + result = parseTweetSearch(await fetch(url, Api.search)) + result.query = query + if after.len == 0: + result.beginning = true + proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) diff --git a/src/consts.nim b/src/consts.nim index 184f9da..8dd1b14 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,6 +9,7 @@ const photoRail* = api / "1.1/statuses/media_timeline.json" userSearch* = api / "1.1/users/search.json" + tweetSearch* = api / "1.1/search/tweets.json" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" diff --git a/src/parser.nim b/src/parser.nim index 5dc96df..f298160 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -82,12 +82,16 @@ proc parseVideo(js: JsonNode): Video = result = Video( thumb: js{"media_url_https"}.getImageStr, views: js{"ext", "mediaStats", "r", "ok", "viewCount"}.getStr($js{"mediaStats", "viewCount"}.getInt), - available: js{"ext_media_availability", "status"}.getStr.toLowerAscii == "available", + available: true, title: js{"ext_alt_text"}.getStr, durationMs: js{"video_info", "duration_millis"}.getInt # playbackType: mp4 ) + with status, js{"ext_media_availability", "status"}: + if status.getStr.len > 0 and status.getStr.toLowerAscii != "available": + result.available = false + with title, js{"additional_media_info", "title"}: result.title = title.getStr @@ -219,7 +223,9 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = if result.hasThread and result.threadId == 0: result.threadId = js{"self_thread", "id_str"}.getId - if js{"is_quote_status"}.getBool: + if "retweeted_status" in js: + result.retweet = some Tweet() + elif js{"is_quote_status"}.getBool: result.quote = some Tweet(id: js{"quoted_status_id_str"}.getId) # legacy @@ -281,6 +287,30 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.text.removeSuffix(" Learn more.") result.available = false +proc parseLegacyTweet(js: JsonNode): Tweet = + result = parseTweet(js, js{"card"}) + if not result.isNil and result.available: + result.user = parseUser(js{"user"}) + + if result.quote.isSome: + result.quote = some parseLegacyTweet(js{"quoted_status"}) + +proc parseTweetSearch*(js: JsonNode): Timeline = + if js.kind == JNull or "statuses" notin js: + return Timeline(beginning: true) + + for tweet in js{"statuses"}: + let parsed = parseLegacyTweet(tweet) + + if parsed.retweet.isSome: + parsed.retweet = some parseLegacyTweet(tweet{"retweeted_status"}) + + result.content.add @[parsed] + + let cursor = js{"search_metadata", "next_results"}.getStr + if cursor.len > 0 and "max_id" in cursor: + result.bottom = cursor[cursor.find("=") + 1 .. cursor.find("&q=")] + proc finalizeTweet(global: GlobalObjects; id: string): Tweet = let intId = if id.len > 0: parseBiggestInt(id) else: 0 result = global.tweets.getOrDefault(id, Tweet(id: intId)) diff --git a/src/routes/rss.nim b/src/routes/rss.nim index 8eec399..d378396 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -27,7 +27,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async. else: var q = query q.fromUser = names - profile = await getGraphSearch(q, after) + profile.tweets = await getTweetSearch(q, after) # this is kinda dumb profile.user = User( username: name, @@ -59,29 +59,29 @@ template respRss*(rss, page) = proc createRssRouter*(cfg: Config) = router rss: - # get "/search/rss": - # cond cfg.enableRss - # if @"q".len > 200: - # resp Http400, showError("Search input too long.", cfg) + get "/search/rss": + cond cfg.enableRss + if @"q".len > 200: + resp Http400, showError("Search input too long.", cfg) - # let query = initQuery(params(request)) - # if query.kind != tweets: - # resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg) + let query = initQuery(params(request)) + if query.kind != tweets: + resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg) - # let - # cursor = getCursor() - # key = redisKey("search", $hash(genQueryUrl(query)), cursor) + let + cursor = getCursor() + key = redisKey("search", $hash(genQueryUrl(query)), cursor) - # var rss = await getCachedRss(key) - # if rss.cursor.len > 0: - # respRss(rss, "Search") + var rss = await getCachedRss(key) + if rss.cursor.len > 0: + respRss(rss, "Search") - # let tweets = await getGraphSearch(query, cursor) - # rss.cursor = tweets.bottom - # rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg) + let tweets = await getTweetSearch(query, cursor) + rss.cursor = tweets.bottom + rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg) - # await cacheRss(key, rss) - # respRss(rss, "Search") + await cacheRss(key, rss) + respRss(rss, "Search") get "/@name/rss": cond cfg.enableRss diff --git a/src/routes/search.nim b/src/routes/search.nim index ed2c397..c270df5 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -34,15 +34,11 @@ proc createSearchRouter*(cfg: Config) = users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) of tweets: - # let - # tweets = await getGraphSearch(query, getCursor()) - # rss = "/search/rss?" & genQueryUrl(query) - # resp renderMain(renderTweetSearch(tweets, prefs, getPath()), - # request, cfg, prefs, title, rss=rss) - var fakeTimeline = Timeline(beginning: true) - fakeTimeline.content.add Tweet(tombstone: "Tweet search is unavailable for now") - - resp renderMain(renderTweetSearch(fakeTimeline, prefs, getPath()), request, cfg, prefs, title) + let + tweets = await getTweetSearch(query, getCursor()) + rss = "/search/rss?" & genQueryUrl(query) + resp renderMain(renderTweetSearch(tweets, prefs, getPath()), + request, cfg, prefs, title, rss=rss) else: resp Http404, showError("Invalid search", cfg) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index e62c9e0..ef3d012 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -56,8 +56,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; of posts: await getGraphUserTweets(userId, TimelineKind.tweets, after) of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) of media: await getGraphUserTweets(userId, TimelineKind.media, after) - else: Profile(tweets: Timeline(beginning: true, content: @[@[Tweet(tombstone: "Tweet search is unavailable for now")]])) - # else: await getGraphSearch(query, after) + else: Profile(tweets: await getTweetSearch(query, after)) result.user = await user result.photoRail = await rail @@ -71,9 +70,8 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; rss, after: string): Future[string] {.async.} = if query.fromUser.len != 1: let - # timeline = await getGraphSearch(query, after) - timeline = Profile(tweets: Timeline(beginning: true, content: @[@[Tweet(tombstone: "This features is unavailable for now")]])) - html = renderTweetSearch(timeline.tweets, prefs, getPath()) + timeline = await getTweetSearch(query, after) + html = renderTweetSearch(timeline, prefs, getPath()) return renderMain(html, request, cfg, prefs, "Multi", rss=rss) var profile = await fetchProfile(after, query, skipPinned=prefs.hidePins) diff --git a/src/tokens.nim b/src/tokens.nim index 6643de3..531f557 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -41,9 +41,9 @@ proc getPoolJson*(): JsonNode = let maxReqs = case api - of Api.timeline: 180 + of Api.timeline, Api.search: 180 of Api.userTweets, Api.userTweetsAndReplies, Api.userRestId, - Api.userScreenName, Api.tweetDetail, Api.tweetResult, Api.search: 500 + Api.userScreenName, Api.tweetDetail, Api.tweetResult: 500 of Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500 of Api.userSearch: 900 reqs = maxReqs - token.apis[api].remaining From afbdbd293e30f614ee288731717868c6d618b55f Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 12 Jul 2023 03:47:37 +0200 Subject: [PATCH 007/302] Fix protected user photo rail crash --- src/parser.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/parser.nim b/src/parser.nim index f298160..b988cf7 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -387,6 +387,10 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline = result.top = cursor{"value"}.getStr proc parsePhotoRail*(js: JsonNode): PhotoRail = + with error, js{"error"}: + if error.getStr == "Not authorized.": + return + for tweet in js: let t = parseTweet(tweet, js{"tweet_card"}) From 4c4d5485a06c37977c54a9ae226bb36cd4dadeab Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Fri, 14 Jul 2023 18:11:56 +0200 Subject: [PATCH 008/302] Fix typo (#943) --- src/nitter.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nitter.nim b/src/nitter.nim index 627af75..25a569d 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -87,7 +87,7 @@ routes: error BadClientError: echo error.exc.name, ": ", error.exc.msg - resp Http500, showError("Network error occured, please try again.", cfg) + resp Http500, showError("Network error occurred, please try again.", cfg) error RateLimitError: const link = a("another instance", href = instancesUrl) From f881226b223f1650f7f1621991baa38513ddb61f Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 14 Jul 2023 21:35:37 +0200 Subject: [PATCH 009/302] Fix video embed --- src/views/embed.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/embed.nim b/src/views/embed.nim index a884cf3..ba49f45 100644 --- a/src/views/embed.nim +++ b/src/views/embed.nim @@ -11,7 +11,7 @@ const doctype = "\n" proc renderVideoEmbed*(tweet: Tweet; cfg: Config; req: Request): string = let thumb = get(tweet.video).thumb let vidUrl = getVideoEmbed(cfg, tweet.id) - let prefs = Prefs(hlsPlayback: true) + let prefs = Prefs(hlsPlayback: true, mp4Playback: true) let node = buildHtml(html(lang="en")): renderHead(prefs, cfg, req, video=vidUrl, images=(@[thumb])) From cc5841df308506356d329662d0f0c2ec4713a35c Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 21 Jul 2023 18:56:39 +0200 Subject: [PATCH 010/302] Use old timeline endpoint --- src/api.nim | 9 ++++++++- src/consts.nim | 14 ++++++++++++++ src/parser.nim | 36 +++++++++++++++++++++++++----------- src/routes/timeline.nim | 2 +- src/tokens.nim | 6 ++++-- src/types.nim | 1 + 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/api.nim b/src/api.nim index 60af68d..e8d0830 100644 --- a/src/api.nim +++ b/src/api.nim @@ -33,6 +33,13 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi js = await fetch(url ? params, apiId) result = parseGraphTimeline(js, "user", after) +proc getTimeline*(id: string; after=""; replies=false): Future[Profile] {.async.} = + if id.len == 0: return + let + ps = genParams({"userId": id, "include_tweet_replies": $replies}, after) + url = oldUserTweets / (id & ".json") ? ps + result = parseTimeline(await fetch(url, Api.timeline), after) + proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let @@ -155,7 +162,7 @@ proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = ps = genParams({"screen_name": name, "trim_user": "true"}, count="18", ext=false) url = photoRail ? ps - result = parsePhotoRail(await fetch(url, Api.timeline)) + result = parsePhotoRail(await fetch(url, Api.photoRail)) proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} = let client = newAsyncHttpClient(maxRedirects=0) diff --git a/src/consts.nim b/src/consts.nim index 8dd1b14..7ba09fb 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -11,6 +11,8 @@ const userSearch* = api / "1.1/users/search.json" tweetSearch* = api / "1.1/search/tweets.json" + oldUserTweets* = api / "2/timeline/profile" + graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" @@ -98,6 +100,18 @@ const "includeHasBirdwatchNotes": false }""" + oldUserTweetsVariables* = """{ + "userId": "$1", $2 + "count": 20, + "includePromotedContent": false, + "withDownvotePerspective": false, + "withReactionsMetadata": false, + "withReactionsPerspective": false, + "withVoice": false, + "withV2Timeline": true +} +""" + userTweetsVariables* = """{ "rest_id": "$1", $2 "count": 20 diff --git a/src/parser.nim b/src/parser.nim index b988cf7..193d77f 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -329,6 +329,16 @@ proc finalizeTweet(global: GlobalObjects; id: string): Tweet = else: result.retweet = some Tweet() +proc parsePin(js: JsonNode; global: GlobalObjects): Tweet = + let pin = js{"pinEntry", "entry", "entryId"}.getStr + if pin.len == 0: return + + let id = pin.getId + if id notin global.tweets: return + + global.tweets[id].pinned = true + return finalizeTweet(global, id) + proc parseGlobalObjects(js: JsonNode): GlobalObjects = result = GlobalObjects() let @@ -339,24 +349,28 @@ proc parseGlobalObjects(js: JsonNode): GlobalObjects = result.users[k] = parseUser(v, k) for k, v in tweets: - var tweet = parseTweet(v, v{"tweet_card"}) + var tweet = parseTweet(v, v{"card"}) if tweet.user.id in result.users: tweet.user = result.users[tweet.user.id] result.tweets[k] = tweet -proc parseInstructions[T](res: var Result[T]; global: GlobalObjects; js: JsonNode) = +proc parseInstructions(res: var Profile; global: GlobalObjects; js: JsonNode) = if js.kind != JArray or js.len == 0: return for i in js: + if res.tweets.beginning and i{"pinEntry"}.notNull: + with pin, parsePin(i, global): + res.pinned = some pin + with r, i{"replaceEntry", "entry"}: if "top" in r{"entryId"}.getStr: - res.top = r.getCursor + res.tweets.top = r.getCursor elif "bottom" in r{"entryId"}.getStr: - res.bottom = r.getCursor + res.tweets.bottom = r.getCursor -proc parseTimeline*(js: JsonNode; after=""): Timeline = - result = Timeline(beginning: after.len == 0) +proc parseTimeline*(js: JsonNode; after=""): Profile = + result = Profile(tweets: Timeline(beginning: after.len == 0)) let global = parseGlobalObjects(? js) let instructions = ? js{"timeline", "instructions"} @@ -374,17 +388,17 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline = if "tweet" in entry or entry.startsWith("sq-I-t") or "tombstone" in entry: let tweet = finalizeTweet(global, e.getEntryId) if not tweet.available: continue - result.content.add tweet + result.tweets.content.add tweet elif "cursor-top" in entry: - result.top = e.getCursor + result.tweets.top = e.getCursor elif "cursor-bottom" in entry: - result.bottom = e.getCursor + result.tweets.bottom = e.getCursor elif entry.startsWith("sq-cursor"): with cursor, e{"content", "operation", "cursor"}: if cursor{"cursorType"}.getStr == "Bottom": - result.bottom = cursor{"value"}.getStr + result.tweets.bottom = cursor{"value"}.getStr else: - result.top = cursor{"value"}.getStr + result.tweets.top = cursor{"value"}.getStr proc parsePhotoRail*(js: JsonNode): PhotoRail = with error, js{"error"}: diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index ef3d012..b574631 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -53,7 +53,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; result = case query.kind - of posts: await getGraphUserTweets(userId, TimelineKind.tweets, after) + of posts: await getTimeline(userId, after) of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) of media: await getGraphUserTweets(userId, TimelineKind.media, after) else: Profile(tweets: await getTweetSearch(query, after)) diff --git a/src/tokens.nim b/src/tokens.nim index 531f557..8a25257 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -41,8 +41,10 @@ proc getPoolJson*(): JsonNode = let maxReqs = case api - of Api.timeline, Api.search: 180 - of Api.userTweets, Api.userTweetsAndReplies, Api.userRestId, + of Api.photoRail, Api.search: 180 + of Api.timeline: 187 + of Api.userTweets: 300 + of Api.userTweetsAndReplies, Api.userRestId, Api.userScreenName, Api.tweetDetail, Api.tweetResult: 500 of Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500 of Api.userSearch: 900 diff --git a/src/types.nim b/src/types.nim index f7d5f6b..5db9ec3 100644 --- a/src/types.nim +++ b/src/types.nim @@ -18,6 +18,7 @@ type tweetDetail tweetResult timeline + photoRail search userSearch list From 50f821dbd8a7bbea75e9cdf2c4189e108b7b0541 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 22 Jul 2023 03:03:45 +0200 Subject: [PATCH 011/302] Use search instead of old timeline endpoint --- src/api.nim | 28 +++--- src/apiutils.nim | 2 +- src/consts.nim | 63 ++++++------- src/parser.nim | 194 ++++++++++++++++++++-------------------- src/routes/timeline.nim | 12 ++- tests/test_timeline.py | 8 +- tests/test_tweet.py | 36 ++++---- 7 files changed, 178 insertions(+), 165 deletions(-) diff --git a/src/api.nim b/src/api.nim index e8d0830..c7dc0e0 100644 --- a/src/api.nim +++ b/src/api.nim @@ -33,12 +33,12 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi js = await fetch(url ? params, apiId) result = parseGraphTimeline(js, "user", after) -proc getTimeline*(id: string; after=""; replies=false): Future[Profile] {.async.} = - if id.len == 0: return - let - ps = genParams({"userId": id, "include_tweet_replies": $replies}, after) - url = oldUserTweets / (id & ".json") ? ps - result = parseTimeline(await fetch(url, Api.timeline), after) +# proc getTimeline*(id: string; after=""; replies=false): Future[Profile] {.async.} = +# if id.len == 0: return +# let +# ps = genParams({"userId": id, "include_tweet_replies": $replies}, after) +# url = oldUserTweets / (id & ".json") ? ps +# result = parseTimeline(await fetch(url, Api.timeline), after) proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return @@ -123,20 +123,22 @@ proc getGraphSearch*(query: Query; after=""): Future[Profile] {.async.} = result.tweets.query = query proc getTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = - let q = genQueryParam(query) + var q = genQueryParam(query) + if q.len == 0 or q == emptyQuery: return Timeline(query: query, beginning: true) + if after.len > 0: + q &= " max_id:" & after + let url = tweetSearch ? genParams({ - "q": q, - "tweet_search_mode": "live", - "max_id": after + "q": q , + "modules": "status", + "result_type": "recent", }) - result = parseTweetSearch(await fetch(url, Api.search)) + result = parseTweetSearch(await fetch(url, Api.search), after) result.query = query - if after.len == 0: - result.beginning = true proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = if query.text.len == 0: diff --git a/src/apiutils.nim b/src/apiutils.nim index dbc6cca..c0c01d4 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -16,7 +16,7 @@ proc genParams*(pars: openArray[(string, string)] = @[]; cursor=""; for p in pars: result &= p if ext: - result &= ("ext", "mediaStats") + result &= ("ext", "mediaStats,isBlueVerified,isVerified,blue,blueVerified") result &= ("include_ext_alt_text", "1") result &= ("include_ext_media_availability", "1") if count.len > 0: diff --git a/src/consts.nim b/src/consts.nim index 7ba09fb..80a098f 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,9 +9,9 @@ const photoRail* = api / "1.1/statuses/media_timeline.json" userSearch* = api / "1.1/users/search.json" - tweetSearch* = api / "1.1/search/tweets.json" + tweetSearch* = api / "1.1/search/universal.json" - oldUserTweets* = api / "2/timeline/profile" + # oldUserTweets* = api / "2/timeline/profile" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" @@ -28,27 +28,28 @@ const graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" timelineParams* = { - "include_profile_interstitial_type": "0", - "include_blocking": "0", - "include_blocked_by": "0", - "include_followed_by": "0", - "include_want_retweets": "0", - "include_mute_edge": "0", - "include_can_dm": "0", - "include_can_media_tag": "1", - "include_ext_is_blue_verified": "1", - "skip_status": "1", - "cards_platform": "Web-12", - "include_cards": "1", - "include_composer_source": "0", - "include_reply_count": "1", + "cards_platform": "Web-13", "tweet_mode": "extended", - "include_entities": "1", - "include_user_entities": "1", - "include_ext_media_color": "0", + "ui_lang": "en-US", "send_error_codes": "1", "simple_quoted_tweet": "1", - "include_quote_count": "1" + "skip_status": "1", + "include_blocked_by": "0", + "include_blocking": "0", + "include_can_dm": "0", + "include_can_media_tag": "1", + "include_cards": "1", + "include_composer_source": "0", + "include_entities": "1", + "include_ext_is_blue_verified": "1", + "include_ext_media_color": "0", + "include_followed_by": "0", + "include_mute_edge": "0", + "include_profile_interstitial_type": "0", + "include_quote_count": "1", + "include_reply_count": "1", + "include_user_entities": "1", + "include_want_retweets": "0", }.toSeq gqlFeatures* = """{ @@ -100,17 +101,17 @@ const "includeHasBirdwatchNotes": false }""" - oldUserTweetsVariables* = """{ - "userId": "$1", $2 - "count": 20, - "includePromotedContent": false, - "withDownvotePerspective": false, - "withReactionsMetadata": false, - "withReactionsPerspective": false, - "withVoice": false, - "withV2Timeline": true -} -""" +# oldUserTweetsVariables* = """{ +# "userId": "$1", $2 +# "count": 20, +# "includePromotedContent": false, +# "withDownvotePerspective": false, +# "withReactionsMetadata": false, +# "withReactionsPerspective": false, +# "withVoice": false, +# "withV2Timeline": true +# } +# """ userTweetsVariables* = """{ "rest_id": "$1", $2 diff --git a/src/parser.nim b/src/parser.nim index 193d77f..991ca6e 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, options, tables, times, math +import strutils, options, times, math import packedjson, packedjson/deserialiser import types, parserutils, utils import experimental/parser/unifiedcard @@ -295,110 +295,112 @@ proc parseLegacyTweet(js: JsonNode): Tweet = if result.quote.isSome: result.quote = some parseLegacyTweet(js{"quoted_status"}) -proc parseTweetSearch*(js: JsonNode): Timeline = - if js.kind == JNull or "statuses" notin js: - return Timeline(beginning: true) +proc parseTweetSearch*(js: JsonNode; after=""): Timeline = + result.beginning = after.len == 0 - for tweet in js{"statuses"}: - let parsed = parseLegacyTweet(tweet) - - if parsed.retweet.isSome: - parsed.retweet = some parseLegacyTweet(tweet{"retweeted_status"}) - - result.content.add @[parsed] - - let cursor = js{"search_metadata", "next_results"}.getStr - if cursor.len > 0 and "max_id" in cursor: - result.bottom = cursor[cursor.find("=") + 1 .. cursor.find("&q=")] - -proc finalizeTweet(global: GlobalObjects; id: string): Tweet = - let intId = if id.len > 0: parseBiggestInt(id) else: 0 - result = global.tweets.getOrDefault(id, Tweet(id: intId)) - - if result.quote.isSome: - let quote = get(result.quote).id - if $quote in global.tweets: - result.quote = some global.tweets[$quote] - else: - result.quote = some Tweet() - - if result.retweet.isSome: - let rt = get(result.retweet).id - if $rt in global.tweets: - result.retweet = some finalizeTweet(global, $rt) - else: - result.retweet = some Tweet() - -proc parsePin(js: JsonNode; global: GlobalObjects): Tweet = - let pin = js{"pinEntry", "entry", "entryId"}.getStr - if pin.len == 0: return - - let id = pin.getId - if id notin global.tweets: return - - global.tweets[id].pinned = true - return finalizeTweet(global, id) - -proc parseGlobalObjects(js: JsonNode): GlobalObjects = - result = GlobalObjects() - let - tweets = ? js{"globalObjects", "tweets"} - users = ? js{"globalObjects", "users"} - - for k, v in users: - result.users[k] = parseUser(v, k) - - for k, v in tweets: - var tweet = parseTweet(v, v{"card"}) - if tweet.user.id in result.users: - tweet.user = result.users[tweet.user.id] - result.tweets[k] = tweet - -proc parseInstructions(res: var Profile; global: GlobalObjects; js: JsonNode) = - if js.kind != JArray or js.len == 0: + if js.kind == JNull or "modules" notin js or js{"modules"}.len == 0: return - for i in js: - if res.tweets.beginning and i{"pinEntry"}.notNull: - with pin, parsePin(i, global): - res.pinned = some pin + for item in js{"modules"}: + with tweet, item{"status", "data"}: + let parsed = parseLegacyTweet(tweet) - with r, i{"replaceEntry", "entry"}: - if "top" in r{"entryId"}.getStr: - res.tweets.top = r.getCursor - elif "bottom" in r{"entryId"}.getStr: - res.tweets.bottom = r.getCursor + if parsed.retweet.isSome: + parsed.retweet = some parseLegacyTweet(tweet{"retweeted_status"}) -proc parseTimeline*(js: JsonNode; after=""): Profile = - result = Profile(tweets: Timeline(beginning: after.len == 0)) - let global = parseGlobalObjects(? js) + result.content.add @[parsed] - let instructions = ? js{"timeline", "instructions"} - if instructions.len == 0: return + if result.content.len > 0: + result.bottom = $(result.content[^1][0].id - 1) - result.parseInstructions(global, instructions) +# proc finalizeTweet(global: GlobalObjects; id: string): Tweet = +# let intId = if id.len > 0: parseBiggestInt(id) else: 0 +# result = global.tweets.getOrDefault(id, Tweet(id: intId)) - var entries: JsonNode - for i in instructions: - if "addEntries" in i: - entries = i{"addEntries", "entries"} +# if result.quote.isSome: +# let quote = get(result.quote).id +# if $quote in global.tweets: +# result.quote = some global.tweets[$quote] +# else: +# result.quote = some Tweet() - for e in ? entries: - let entry = e{"entryId"}.getStr - if "tweet" in entry or entry.startsWith("sq-I-t") or "tombstone" in entry: - let tweet = finalizeTweet(global, e.getEntryId) - if not tweet.available: continue - result.tweets.content.add tweet - elif "cursor-top" in entry: - result.tweets.top = e.getCursor - elif "cursor-bottom" in entry: - result.tweets.bottom = e.getCursor - elif entry.startsWith("sq-cursor"): - with cursor, e{"content", "operation", "cursor"}: - if cursor{"cursorType"}.getStr == "Bottom": - result.tweets.bottom = cursor{"value"}.getStr - else: - result.tweets.top = cursor{"value"}.getStr +# if result.retweet.isSome: +# let rt = get(result.retweet).id +# if $rt in global.tweets: +# result.retweet = some finalizeTweet(global, $rt) +# else: +# result.retweet = some Tweet() + +# proc parsePin(js: JsonNode; global: GlobalObjects): Tweet = +# let pin = js{"pinEntry", "entry", "entryId"}.getStr +# if pin.len == 0: return + +# let id = pin.getId +# if id notin global.tweets: return + +# global.tweets[id].pinned = true +# return finalizeTweet(global, id) + +# proc parseGlobalObjects(js: JsonNode): GlobalObjects = +# result = GlobalObjects() +# let +# tweets = ? js{"globalObjects", "tweets"} +# users = ? js{"globalObjects", "users"} + +# for k, v in users: +# result.users[k] = parseUser(v, k) + +# for k, v in tweets: +# var tweet = parseTweet(v, v{"card"}) +# if tweet.user.id in result.users: +# tweet.user = result.users[tweet.user.id] +# result.tweets[k] = tweet + +# proc parseInstructions(res: var Profile; global: GlobalObjects; js: JsonNode) = +# if js.kind != JArray or js.len == 0: +# return + +# for i in js: +# if res.tweets.beginning and i{"pinEntry"}.notNull: +# with pin, parsePin(i, global): +# res.pinned = some pin + +# with r, i{"replaceEntry", "entry"}: +# if "top" in r{"entryId"}.getStr: +# res.tweets.top = r.getCursor +# elif "bottom" in r{"entryId"}.getStr: +# res.tweets.bottom = r.getCursor + +# proc parseTimeline*(js: JsonNode; after=""): Profile = +# result = Profile(tweets: Timeline(beginning: after.len == 0)) +# let global = parseGlobalObjects(? js) + +# let instructions = ? js{"timeline", "instructions"} +# if instructions.len == 0: return + +# result.parseInstructions(global, instructions) + +# var entries: JsonNode +# for i in instructions: +# if "addEntries" in i: +# entries = i{"addEntries", "entries"} + +# for e in ? entries: +# let entry = e{"entryId"}.getStr +# if "tweet" in entry or entry.startsWith("sq-I-t") or "tombstone" in entry: +# let tweet = finalizeTweet(global, e.getEntryId) +# if not tweet.available: continue +# result.tweets.content.add tweet +# elif "cursor-top" in entry: +# result.tweets.top = e.getCursor +# elif "cursor-bottom" in entry: +# result.tweets.bottom = e.getCursor +# elif entry.startsWith("sq-cursor"): +# with cursor, e{"content", "operation", "cursor"}: +# if cursor{"cursorType"}.getStr == "Bottom": +# result.tweets.bottom = cursor{"value"}.getStr +# else: +# result.tweets.top = cursor{"value"}.getStr proc parsePhotoRail*(js: JsonNode): PhotoRail = with error, js{"error"}: diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index b574631..8b8a23c 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -53,7 +53,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; result = case query.kind - of posts: await getTimeline(userId, after) + # of posts: await getTimeline(userId, after) of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) of media: await getGraphUserTweets(userId, TimelineKind.media, after) else: Profile(tweets: await getTweetSearch(query, after)) @@ -61,10 +61,18 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; result.user = await user result.photoRail = await rail + result.tweets.query = query + if result.user.protected or result.user.suspended: return - result.tweets.query = query + if not skipPinned and query.kind == posts and + result.user.pinnedTweet > 0 and after.len == 0: + let tweet = await getCachedTweet(result.user.pinnedTweet) + if not tweet.isNil: + tweet.pinned = true + tweet.user = result.user + result.pinned = some tweet proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; rss, after: string): Future[string] {.async.} = diff --git a/tests/test_timeline.py b/tests/test_timeline.py index dd78396..a630ae1 100644 --- a/tests/test_timeline.py +++ b/tests/test_timeline.py @@ -1,12 +1,12 @@ from base import BaseTestCase, Timeline from parameterized import parameterized -normal = [['mobile_test'], ['mobile_test_2']] +normal = [['jack'], ['elonmusk']] -after = [['mobile_test', 'HBaAgJPsqtGNhA0AAA%3D%3D'], - ['mobile_test_2', 'HBaAgJPsqtGNhA0AAA%3D%3D']] +after = [['jack', '1681686036294803456'], + ['elonmusk', '1681686036294803456']] -no_more = [['mobile_test_8?cursor=HBaAwJCsk%2F6%2FtgQAAA%3D%3D']] +no_more = [['mobile_test_8?cursor=1000']] empty = [['emptyuser'], ['mobile_test_10']] diff --git a/tests/test_tweet.py b/tests/test_tweet.py index e4231a4..7a3c4ed 100644 --- a/tests/test_tweet.py +++ b/tests/test_tweet.py @@ -80,16 +80,16 @@ retweet = [ class TweetTest(BaseTestCase): - @parameterized.expand(timeline) - def test_timeline(self, index, fullname, username, date, tid, text): - self.open_nitter(username) - tweet = get_timeline_tweet(index) - self.assert_exact_text(fullname, tweet.fullname) - self.assert_exact_text('@' + username, tweet.username) - self.assert_exact_text(date, tweet.date) - self.assert_text(text, tweet.text) - permalink = self.find_element(tweet.date + ' a') - self.assertIn(tid, permalink.get_attribute('href')) + # @parameterized.expand(timeline) + # def test_timeline(self, index, fullname, username, date, tid, text): + # self.open_nitter(username) + # tweet = get_timeline_tweet(index) + # self.assert_exact_text(fullname, tweet.fullname) + # self.assert_exact_text('@' + username, tweet.username) + # self.assert_exact_text(date, tweet.date) + # self.assert_text(text, tweet.text) + # permalink = self.find_element(tweet.date + ' a') + # self.assertIn(tid, permalink.get_attribute('href')) @parameterized.expand(status) def test_status(self, tid, fullname, username, date, text): @@ -123,14 +123,14 @@ class TweetTest(BaseTestCase): link = self.find_link_text(f'@{un}') self.assertIn(f'/{un}', link.get_property('href')) - @parameterized.expand(retweet) - def test_retweet(self, index, url, retweet_by, fullname, username, text): - self.open_nitter(url) - tweet = get_timeline_tweet(index) - self.assert_text(f'{retweet_by} retweeted', tweet.retweet) - self.assert_text(text, tweet.text) - self.assert_exact_text(fullname, tweet.fullname) - self.assert_exact_text(username, tweet.username) + # @parameterized.expand(retweet) + # def test_retweet(self, index, url, retweet_by, fullname, username, text): + # self.open_nitter(url) + # tweet = get_timeline_tweet(index) + # self.assert_text(f'{retweet_by} retweeted', tweet.retweet) + # self.assert_text(text, tweet.text) + # self.assert_exact_text(fullname, tweet.fullname) + # self.assert_exact_text(username, tweet.username) @parameterized.expand(invalid) def test_invalid_id(self, tweet): From 72d8f35cd1ec1205824711a41dab4b8d7a6b298a Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 22 Jul 2023 04:06:04 +0200 Subject: [PATCH 012/302] Search isn't rate limited --- src/tokens.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tokens.nim b/src/tokens.nim index 8a25257..b69786e 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -41,7 +41,8 @@ proc getPoolJson*(): JsonNode = let maxReqs = case api - of Api.photoRail, Api.search: 180 + of Api.search: 100000 + of Api.photoRail: 180 of Api.timeline: 187 of Api.userTweets: 300 of Api.userTweetsAndReplies, Api.userRestId, From 59a72831c749b2198cb83d1b7cee74a5d05da723 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 24 Jul 2023 04:26:32 +0200 Subject: [PATCH 013/302] Apply cached profile verified status to tweets --- src/routes/timeline.nim | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 8b8a23c..bf2a08e 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -66,13 +66,17 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; if result.user.protected or result.user.suspended: return - if not skipPinned and query.kind == posts and - result.user.pinnedTweet > 0 and after.len == 0: - let tweet = await getCachedTweet(result.user.pinnedTweet) - if not tweet.isNil: - tweet.pinned = true - tweet.user = result.user - result.pinned = some tweet + if query.kind == posts: + if result.user.verified: + for chain in result.tweets.content: + if chain[0].user.id == result.user.id: + chain[0].user.verified = true + if not skipPinned and result.user.pinnedTweet > 0 and after.len == 0: + let tweet = await getCachedTweet(result.user.pinnedTweet) + if not tweet.isNil: + tweet.pinned = true + tweet.user = result.user + result.pinned = some tweet proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; rss, after: string): Future[string] {.async.} = From 39192bf191dfc8c5645aa8101afd04474b899897 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 24 Jul 2023 10:18:50 +0200 Subject: [PATCH 014/302] Fix multi-timeline infinite scroll --- src/routes/timeline.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index bf2a08e..82dc45b 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -137,7 +137,7 @@ proc createTimelineRouter*(cfg: Config) = # used for the infinite scroll feature if @"scroll".len > 0: if query.fromUser.len != 1: - var timeline = (await getGraphSearch(query, after)).tweets + var timeline = await getTweetSearch(query, after) if timeline.content.len == 0: resp Http404 timeline.beginning = true resp $renderTweetSearch(timeline, prefs, getPath()) From 20b5cce5dc6437ffc06ea53e9efd884f2fc66abe Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 24 Jul 2023 10:37:25 +0200 Subject: [PATCH 015/302] Retry infinite scroll errors --- public/js/infiniteScroll.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/public/js/infiniteScroll.js b/public/js/infiniteScroll.js index 9939c03..be27e0c 100644 --- a/public/js/infiniteScroll.js +++ b/public/js/infiniteScroll.js @@ -5,7 +5,7 @@ function insertBeforeLast(node, elem) { } function getLoadMore(doc) { - return doc.querySelector('.show-more:not(.timeline-item)'); + return doc.querySelector(".show-more:not(.timeline-item)"); } function isDuplicate(item, itemClass) { @@ -15,18 +15,19 @@ function isDuplicate(item, itemClass) { return document.querySelector(itemClass + " .tweet-link[href='" + href + "']") != null; } -window.onload = function() { +window.onload = function () { const url = window.location.pathname; const isTweet = url.indexOf("/status/") !== -1; const containerClass = isTweet ? ".replies" : ".timeline"; - const itemClass = containerClass + ' > div:not(.top-ref)'; + const itemClass = containerClass + " > div:not(.top-ref)"; var html = document.querySelector("html"); var container = document.querySelector(containerClass); var loading = false; - window.addEventListener('scroll', function() { + function handleScroll(failed) { if (loading) return; + if (html.scrollTop + html.clientHeight >= html.scrollHeight - 3000) { loading = true; var loadMore = getLoadMore(document); @@ -35,13 +36,15 @@ window.onload = function() { loadMore.children[0].text = "Loading..."; var url = new URL(loadMore.children[0].href); - url.searchParams.append('scroll', 'true'); + url.searchParams.append("scroll", "true"); fetch(url.toString()).then(function (response) { + if (response.status === 404) throw "error"; + return response.text(); }).then(function (html) { var parser = new DOMParser(); - var doc = parser.parseFromString(html, 'text/html'); + var doc = parser.parseFromString(html, "text/html"); loadMore.remove(); for (var item of doc.querySelectorAll(itemClass)) { @@ -57,10 +60,18 @@ window.onload = function() { if (isTweet) container.appendChild(newLoadMore); else insertBeforeLast(container, newLoadMore); }).catch(function (err) { - console.warn('Something went wrong.', err); - loading = true; + console.warn("Something went wrong.", err); + if (failed > 3) { + loadMore.children[0].text = "Error"; + return; + } + + loading = false; + handleScroll((failed || 0) + 1); }); } - }); + } + + window.addEventListener("scroll", () => handleScroll()); }; // @license-end From 5725780c990cd55f00ed6558d052f0e5a148652a Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 6 Aug 2023 21:02:22 +0200 Subject: [PATCH 016/302] Bump Nim version in Docker image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c100394..138dc64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nimlang/nim:1.6.10-alpine-regular as nim +FROM nimlang/nim:2.0.0-alpine-regular as nim LABEL maintainer="setenforce@protonmail.com" RUN apk --no-cache add libsass-dev pcre From 624394430c0989d18c279153006c6a7e48f4dd03 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 8 Aug 2023 02:09:56 +0200 Subject: [PATCH 017/302] Use legacy timeline/user endpoint for Tweets tab --- src/api.nim | 10 ++++++++ src/apiutils.nim | 2 +- src/consts.nim | 23 +++++++----------- src/parser.nim | 52 +++++++++++++++++++++++++++++++++++++++-- src/parserutils.nim | 6 +++++ src/routes/timeline.nim | 17 +------------- src/tokens.nim | 8 +++---- src/types.nim | 1 + 8 files changed, 81 insertions(+), 38 deletions(-) diff --git a/src/api.nim b/src/api.nim index c7dc0e0..c313aa2 100644 --- a/src/api.nim +++ b/src/api.nim @@ -40,6 +40,16 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi # url = oldUserTweets / (id & ".json") ? ps # result = parseTimeline(await fetch(url, Api.timeline), after) +proc getUserTimeline*(id: string; after=""): Future[Profile] {.async.} = + var ps = genParams({"id": id}) + if after.len > 0: + ps.add ("down_cursor", after) + + let + url = legacyUserTweets ? ps + js = await fetch(url, Api.userTimeline) + result = parseUserTimeline(js, after) + proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let diff --git a/src/apiutils.nim b/src/apiutils.nim index c0c01d4..1da971a 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -16,8 +16,8 @@ proc genParams*(pars: openArray[(string, string)] = @[]; cursor=""; for p in pars: result &= p if ext: - result &= ("ext", "mediaStats,isBlueVerified,isVerified,blue,blueVerified") result &= ("include_ext_alt_text", "1") + result &= ("include_ext_media_stats", "1") result &= ("include_ext_media_availability", "1") if count.len > 0: result &= ("count", count) diff --git a/src/consts.nim b/src/consts.nim index 80a098f..a25f6ea 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -7,6 +7,7 @@ const api = parseUri("https://api.twitter.com") activate* = $(api / "1.1/guest/activate.json") + legacyUserTweets* = api / "1.1/timeline/user.json" photoRail* = api / "1.1/statuses/media_timeline.json" userSearch* = api / "1.1/users/search.json" tweetSearch* = api / "1.1/search/universal.json" @@ -28,28 +29,20 @@ const graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" timelineParams* = { - "cards_platform": "Web-13", - "tweet_mode": "extended", - "ui_lang": "en-US", - "send_error_codes": "1", - "simple_quoted_tweet": "1", - "skip_status": "1", - "include_blocked_by": "0", - "include_blocking": "0", - "include_can_dm": "0", "include_can_media_tag": "1", "include_cards": "1", - "include_composer_source": "0", "include_entities": "1", - "include_ext_is_blue_verified": "1", - "include_ext_media_color": "0", - "include_followed_by": "0", - "include_mute_edge": "0", "include_profile_interstitial_type": "0", "include_quote_count": "1", "include_reply_count": "1", "include_user_entities": "1", - "include_want_retweets": "0", + "include_ext_reply_count": "1", + "include_ext_is_blue_verified": "1", + "include_ext_media_color": "0", + "cards_platform": "Web-13", + "tweet_mode": "extended", + "send_error_codes": "1", + "simple_quoted_tweet": "1" }.toSeq gqlFeatures* = """{ diff --git a/src/parser.nim b/src/parser.nim index 991ca6e..c7d8bd1 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, options, times, math +import strutils, options, times, math, tables import packedjson, packedjson/deserialiser import types, parserutils, utils import experimental/parser/unifiedcard @@ -81,7 +81,7 @@ proc parseGif(js: JsonNode): Gif = proc parseVideo(js: JsonNode): Video = result = Video( thumb: js{"media_url_https"}.getImageStr, - views: js{"ext", "mediaStats", "r", "ok", "viewCount"}.getStr($js{"mediaStats", "viewCount"}.getInt), + views: getVideoViewCount(js), available: true, title: js{"ext_alt_text"}.getStr, durationMs: js{"video_info", "duration_millis"}.getInt @@ -313,6 +313,54 @@ proc parseTweetSearch*(js: JsonNode; after=""): Timeline = if result.content.len > 0: result.bottom = $(result.content[^1][0].id - 1) +proc parseUserTimelineTweet(tweet: JsonNode; users: TableRef[string, User]): Tweet = + result = parseTweet(tweet, tweet{"card"}) + + if result.isNil or not result.available: + return + + with user, tweet{"user"}: + let userId = user{"id_str"}.getStr + if user{"ext_is_blue_verified"}.getBool(false): + users[userId].verified = users[userId].verified or true + result.user = users[userId] + +proc parseUserTimeline*(js: JsonNode; after=""): Profile = + result = Profile(tweets: Timeline(beginning: after.len == 0)) + + if js.kind == JNull or "response" notin js or "twitter_objects" notin js: + return + + var users = newTable[string, User]() + for userId, user in js{"twitter_objects", "users"}: + users[userId] = parseUser(user) + + for entity in js{"response", "timeline"}: + let + tweetId = entity{"tweet", "id"}.getId + isPinned = entity{"tweet", "is_pinned"}.getBool(false) + + with tweet, js{"twitter_objects", "tweets", $tweetId}: + var parsed = parseUserTimelineTweet(tweet, users) + + if not parsed.isNil and parsed.available: + if parsed.quote.isSome: + parsed.quote = some parseUserTimelineTweet(tweet{"quoted_status"}, users) + + if parsed.retweet.isSome: + let retweet = parseUserTimelineTweet(tweet{"retweeted_status"}, users) + if retweet.quote.isSome: + retweet.quote = some parseUserTimelineTweet(tweet{"retweeted_status", "quoted_status"}, users) + parsed.retweet = some retweet + + if isPinned: + parsed.pinned = true + result.pinned = some parsed + else: + result.tweets.content.add parsed + + result.tweets.bottom = js{"response", "cursor", "bottom"}.getStr + # proc finalizeTweet(global: GlobalObjects; id: string): Tweet = # let intId = if id.len > 0: parseBiggestInt(id) else: 0 # result = global.tweets.getOrDefault(id, Tweet(id: intId)) diff --git a/src/parserutils.nim b/src/parserutils.nim index f28bd52..c65052e 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -148,6 +148,12 @@ proc getMp4Resolution*(url: string): int = # cannot determine resolution (e.g. m3u8/non-mp4 video) return 0 +proc getVideoViewCount*(js: JsonNode): string = + with stats, js{"ext_media_stats"}: + return stats{"view_count"}.getStr($stats{"viewCount"}.getInt) + + return $js{"mediaStats", "viewCount"}.getInt(0) + proc extractSlice(js: JsonNode): Slice[int] = result = js["indices"][0].getInt ..< js["indices"][1].getInt diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 82dc45b..8d02b68 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -53,7 +53,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; result = case query.kind - # of posts: await getTimeline(userId, after) + of posts: await getUserTimeline(userId, after) of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) of media: await getGraphUserTweets(userId, TimelineKind.media, after) else: Profile(tweets: await getTweetSearch(query, after)) @@ -63,21 +63,6 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; result.tweets.query = query - if result.user.protected or result.user.suspended: - return - - if query.kind == posts: - if result.user.verified: - for chain in result.tweets.content: - if chain[0].user.id == result.user.id: - chain[0].user.verified = true - if not skipPinned and result.user.pinnedTweet > 0 and after.len == 0: - let tweet = await getCachedTweet(result.user.pinnedTweet) - if not tweet.isNil: - tweet.pinned = true - tweet.user = result.user - result.pinned = some tweet - proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; rss, after: string): Future[string] {.async.} = if query.fromUser.len != 1: diff --git a/src/tokens.nim b/src/tokens.nim index b69786e..decf228 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -44,10 +44,10 @@ proc getPoolJson*(): JsonNode = of Api.search: 100000 of Api.photoRail: 180 of Api.timeline: 187 - of Api.userTweets: 300 + of Api.userTweets, Api.userTimeline: 300 of Api.userTweetsAndReplies, Api.userRestId, - Api.userScreenName, Api.tweetDetail, Api.tweetResult: 500 - of Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500 + Api.userScreenName, Api.tweetDetail, Api.tweetResult, + Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500 of Api.userSearch: 900 reqs = maxReqs - token.apis[api].remaining @@ -161,6 +161,6 @@ proc initTokenPool*(cfg: Config) {.async.} = enableLogging = cfg.enableDebug while true: - if tokenPool.countIt(not it.isLimited(Api.timeline)) < cfg.minTokens: + if tokenPool.countIt(not it.isLimited(Api.userTimeline)) < cfg.minTokens: await poolTokens(min(4, cfg.minTokens - tokenPool.len)) await sleepAsync(2000) diff --git a/src/types.nim b/src/types.nim index 5db9ec3..1a47d25 100644 --- a/src/types.nim +++ b/src/types.nim @@ -18,6 +18,7 @@ type tweetDetail tweetResult timeline + userTimeline photoRail search userSearch From 967f5e50f9c2ba4ac50dfb39fc559e104cedbc99 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 8 Aug 2023 02:15:32 +0200 Subject: [PATCH 018/302] Update and disable some tests --- tests/test_profile.py | 2 +- tests/test_timeline.py | 7 +++---- tests/test_tweet_media.py | 14 +++++++------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_profile.py b/tests/test_profile.py index 4c75ad2..38c5189 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -4,7 +4,7 @@ from parameterized import parameterized profiles = [ ['mobile_test', 'Test account', 'Test Account. test test Testing username with @mobile_test_2 and a #hashtag', - 'San Francisco, CA', 'example.com/foobar', 'Joined October 2009', '100'], + 'San Francisco, CA', 'example.com/foobar', 'Joined October 2009', '98'], ['mobile_test_2', 'mobile test 2', '', '', '', 'Joined January 2011', '13'] ] diff --git a/tests/test_timeline.py b/tests/test_timeline.py index a630ae1..90eaa28 100644 --- a/tests/test_timeline.py +++ b/tests/test_timeline.py @@ -13,10 +13,9 @@ empty = [['emptyuser'], ['mobile_test_10']] protected = [['mobile_test_7'], ['Empty_user']] photo_rail = [['mobile_test', [ - 'BzUnaDFCUAAmrjs', 'Bo0nDsYIYAIjqVn', 'Bos--KNIQAAA7Li', 'Boq1sDJIYAAxaoi', - 'BonISmPIEAAhP3G', 'BoQbwJAIUAA0QCY', 'BoQbRQxIIAA3FWD', 'Bn8Qh8iIIAABXrG', - 'Bn8QIG3IYAA0IGT', 'Bn8O3QeIUAAONai', 'Bn8NGViIAAATNG4', 'BkKovdrCUAAEz79', - 'BkKoe_oCIAASAqr', 'BkKoRLNCAAAYfDf', 'BkKndxoCQAE1vFt', 'BPEmIbYCMAE44dl' + 'Bo0nDsYIYAIjqVn', 'BoQbwJAIUAA0QCY', 'BoQbRQxIIAA3FWD', 'Bn8Qh8iIIAABXrG', + 'Bn8QIG3IYAA0IGT', 'Bn8O3QeIUAAONai', 'Bn8NGViIAAATNG4', 'BkKoRLNCAAAYfDf', + 'BkKndxoCQAE1vFt' ]]] diff --git a/tests/test_tweet_media.py b/tests/test_tweet_media.py index 233990e..7a00983 100644 --- a/tests/test_tweet_media.py +++ b/tests/test_tweet_media.py @@ -28,14 +28,14 @@ video_m3u8 = [ ] gallery = [ - ['mobile_test/status/451108446603980803', [ - ['BkKovdrCUAAEz79', 'BkKovdcCEAAfoBO'] - ]], + # ['mobile_test/status/451108446603980803', [ + # ['BkKovdrCUAAEz79', 'BkKovdcCEAAfoBO'] + # ]], - ['mobile_test/status/471539824713691137', [ - ['Bos--KNIQAAA7Li', 'Bos--FAIAAAWpah'], - ['Bos--IqIQAAav23'] - ]], + # ['mobile_test/status/471539824713691137', [ + # ['Bos--KNIQAAA7Li', 'Bos--FAIAAAWpah'], + # ['Bos--IqIQAAav23'] + # ]], ['mobile_test/status/469530783384743936', [ ['BoQbwJAIUAA0QCY', 'BoQbwN1IMAAuTiP'], From 54e6ce14ac48409c0552b96e1dadf674c1926c83 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 8 Aug 2023 02:35:43 +0200 Subject: [PATCH 019/302] Simplify photo rail test --- tests/test_timeline.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_timeline.py b/tests/test_timeline.py index 90eaa28..9261b44 100644 --- a/tests/test_timeline.py +++ b/tests/test_timeline.py @@ -12,11 +12,7 @@ empty = [['emptyuser'], ['mobile_test_10']] protected = [['mobile_test_7'], ['Empty_user']] -photo_rail = [['mobile_test', [ - 'Bo0nDsYIYAIjqVn', 'BoQbwJAIUAA0QCY', 'BoQbRQxIIAA3FWD', 'Bn8Qh8iIIAABXrG', - 'Bn8QIG3IYAA0IGT', 'Bn8O3QeIUAAONai', 'Bn8NGViIAAATNG4', 'BkKoRLNCAAAYfDf', - 'BkKndxoCQAE1vFt' -]]] +photo_rail = [['mobile_test', ['Bo0nDsYIYAIjqVn', 'BoQbwJAIUAA0QCY', 'BoQbRQxIIAA3FWD', 'Bn8Qh8iIIAABXrG']]] class TweetTest(BaseTestCase): From d7ca353a55ea3440a2ec1f09155951210a374cc7 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 8 Aug 2023 02:49:58 +0200 Subject: [PATCH 020/302] Disable photo rail test --- tests/test_timeline.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_timeline.py b/tests/test_timeline.py index 9261b44..b56d6ad 100644 --- a/tests/test_timeline.py +++ b/tests/test_timeline.py @@ -55,10 +55,10 @@ class TweetTest(BaseTestCase): self.assert_element_absent(Timeline.older) self.assert_element_absent(Timeline.end) - @parameterized.expand(photo_rail) - def test_photo_rail(self, username, images): - self.open_nitter(username) - self.assert_element_visible(Timeline.photo_rail) - for i, url in enumerate(images): - img = self.get_attribute(Timeline.photo_rail + f' a:nth-child({i + 1}) img', 'src') - self.assertIn(url, img) + #@parameterized.expand(photo_rail) + #def test_photo_rail(self, username, images): + #self.open_nitter(username) + #self.assert_element_visible(Timeline.photo_rail) + #for i, url in enumerate(images): + #img = self.get_attribute(Timeline.photo_rail + f' a:nth-child({i + 1}) img', 'src') + #self.assertIn(url, img) From 3572dd77719f549d18aa0872b04564ede1091ca3 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 19 Aug 2023 00:25:14 +0200 Subject: [PATCH 021/302] Replace tokens with guest accounts, swap endpoints --- nitter.nimble | 2 +- src/api.nim | 68 ++++---------- src/apiutils.nim | 54 +++++++---- src/consts.nim | 8 +- src/nitter.nim | 13 ++- src/parser.nim | 193 ++++------------------------------------ src/redis_cache.nim | 18 ++-- src/routes/rss.nim | 4 +- src/routes/search.nim | 4 +- src/routes/timeline.nim | 8 +- src/tokens.nim | 158 +++++++++++--------------------- src/types.nim | 11 ++- 12 files changed, 159 insertions(+), 382 deletions(-) diff --git a/nitter.nimble b/nitter.nimble index 7771b31..e6a1909 100644 --- a/nitter.nimble +++ b/nitter.nimble @@ -23,7 +23,7 @@ requires "https://github.com/zedeus/redis#d0a0e6f" requires "zippy#ca5989a" requires "flatty#e668085" requires "jsony#ea811be" - +requires "oauth#b8c163b" # Tasks diff --git a/src/api.nim b/src/api.nim index c313aa2..d6a4564 100644 --- a/src/api.nim +++ b/src/api.nim @@ -33,23 +33,6 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi js = await fetch(url ? params, apiId) result = parseGraphTimeline(js, "user", after) -# proc getTimeline*(id: string; after=""; replies=false): Future[Profile] {.async.} = -# if id.len == 0: return -# let -# ps = genParams({"userId": id, "include_tweet_replies": $replies}, after) -# url = oldUserTweets / (id & ".json") ? ps -# result = parseTimeline(await fetch(url, Api.timeline), after) - -proc getUserTimeline*(id: string; after=""): Future[Profile] {.async.} = - var ps = genParams({"id": id}) - if after.len > 0: - ps.add ("down_cursor", after) - - let - url = legacyUserTweets ? ps - js = await fetch(url, Api.userTimeline) - result = parseUserTimeline(js, after) - proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let @@ -112,10 +95,10 @@ proc getTweet*(id: string; after=""): Future[Conversation] {.async.} = if after.len > 0: result.replies = await getReplies(id, after) -proc getGraphSearch*(query: Query; after=""): Future[Profile] {.async.} = +proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = let q = genQueryParam(query) if q.len == 0 or q == emptyQuery: - return Profile(tweets: Timeline(query: query, beginning: true)) + return Timeline(query: query, beginning: true) var variables = %*{ @@ -129,44 +112,29 @@ proc getGraphSearch*(query: Query; after=""): Future[Profile] {.async.} = if after.len > 0: variables["cursor"] = % after let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} - result = Profile(tweets: parseGraphSearch(await fetch(url, Api.search), after)) - result.tweets.query = query - -proc getTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = - var q = genQueryParam(query) - - if q.len == 0 or q == emptyQuery: - return Timeline(query: query, beginning: true) - - if after.len > 0: - q &= " max_id:" & after - - let url = tweetSearch ? genParams({ - "q": q , - "modules": "status", - "result_type": "recent", - }) - - result = parseTweetSearch(await fetch(url, Api.search), after) + result = parseGraphSearch[Tweets](await fetch(url, Api.search), after) result.query = query -proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = +proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) - var url = userSearch ? { - "q": query.text, - "skip_status": "1", - "count": "20", - "page": page - } + var + variables = %*{ + "rawQuery": query.text, + "count": 20, + "product": "People", + "withDownvotePerspective": false, + "withReactionsMetadata": false, + "withReactionsPerspective": false + } + if after.len > 0: + variables["cursor"] = % after + result.beginning = false - result = parseUsers(await fetchRaw(url, Api.userSearch)) + let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} + result = parseGraphSearch[User](await fetch(url, Api.search), after) result.query = query - if page.len == 0: - result.bottom = "2" - elif page.allCharsInSet(Digits): - result.bottom = $(parseInt(page) + 1) proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return diff --git a/src/apiutils.nim b/src/apiutils.nim index 1da971a..d1ecfa3 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,6 +1,6 @@ # SPDX-License-Identifier: AGPL-3.0-only -import httpclient, asyncdispatch, options, strutils, uri -import jsony, packedjson, zippy +import httpclient, asyncdispatch, options, strutils, uri, times, math +import jsony, packedjson, zippy, oauth1 import types, tokens, consts, parserutils, http_pool import experimental/types/common @@ -29,12 +29,30 @@ proc genParams*(pars: openArray[(string, string)] = @[]; cursor=""; else: result &= ("cursor", cursor) -proc genHeaders*(token: Token = nil): HttpHeaders = +proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = + let + encodedUrl = url.replace(",", "%2C").replace("+", "%20") + params = OAuth1Parameters( + consumerKey: consumerKey, + signatureMethod: "HMAC-SHA1", + timestamp: $int(round(epochTime())), + nonce: "0", + isIncludeVersionToHeader: true, + token: oauthToken + ) + signature = getSignature(HttpGet, encodedUrl, "", params, consumerSecret, oauthTokenSecret) + + params.signature = percentEncode(signature) + + return getOauth1RequestHeader(params)["authorization"] + +proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = + let header = getOauthHeader(url, oauthToken, oauthTokenSecret) + result = newHttpHeaders({ "connection": "keep-alive", - "authorization": auth, + "authorization": header, "content-type": "application/json", - "x-guest-token": if token == nil: "" else: token.tok, "x-twitter-active-user": "yes", "authority": "api.twitter.com", "accept-encoding": "gzip", @@ -43,24 +61,24 @@ proc genHeaders*(token: Token = nil): HttpHeaders = "DNT": "1" }) -template updateToken() = +template updateAccount() = if resp.headers.hasKey(rlRemaining): let remaining = parseInt(resp.headers[rlRemaining]) reset = parseInt(resp.headers[rlReset]) - token.setRateLimit(api, remaining, reset) + account.setRateLimit(api, remaining, reset) template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() - var token = await getToken(api) - if token.tok.len == 0: + var account = await getGuestAccount(api) + if account.oauthToken.len == 0: raise rateLimitError() try: var resp: AsyncResponse - pool.use(genHeaders(token)): + pool.use(genHeaders($url, account.oauthToken, account.oauthSecret)): template getContent = resp = await c.get($url) result = await resp.body @@ -79,19 +97,19 @@ template fetchImpl(result, fetchBody) {.dirty.} = fetchBody - release(token, used=true) + release(account, used=true) if resp.status == $Http400: raise newException(InternalError, $url) except InternalError as e: raise e except BadClientError as e: - release(token, used=true) + release(account, used=true) raise e except Exception as e: - echo "error: ", e.name, ", msg: ", e.msg, ", token: ", token[], ", url: ", url + echo "error: ", e.name, ", msg: ", e.msg, ", accountId: ", account.id, ", url: ", url if "length" notin e.msg and "descriptor" notin e.msg: - release(token, invalid=true) + release(account, invalid=true) raise rateLimitError() proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = @@ -103,12 +121,12 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = echo resp.status, ": ", body, " --- url: ", url result = newJNull() - updateToken() + updateAccount() let error = result.getError if error in {invalidToken, badToken}: echo "fetch error: ", result.getError - release(token, invalid=true) + release(account, invalid=true) raise rateLimitError() proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = @@ -117,11 +135,11 @@ proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = echo resp.status, ": ", result, " --- url: ", url result.setLen(0) - updateToken() + updateAccount() if result.startsWith("{\"errors"): let errors = result.fromJson(Errors) if errors in {invalidToken, badToken}: echo "fetch error: ", errors - release(token, invalid=true) + release(account, invalid=true) raise rateLimitError() diff --git a/src/consts.nim b/src/consts.nim index a25f6ea..2cfd1ed 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -2,17 +2,13 @@ import uri, sequtils, strutils const - auth* = "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" + consumerKey* = "3nVuSoBZnx6U4vzUxf5w" + consumerSecret* = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys" api = parseUri("https://api.twitter.com") activate* = $(api / "1.1/guest/activate.json") - legacyUserTweets* = api / "1.1/timeline/user.json" photoRail* = api / "1.1/statuses/media_timeline.json" - userSearch* = api / "1.1/users/search.json" - tweetSearch* = api / "1.1/search/universal.json" - - # oldUserTweets* = api / "2/timeline/profile" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" diff --git a/src/nitter.nim b/src/nitter.nim index 25a569d..4a4ec13 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -3,6 +3,7 @@ import asyncdispatch, strformat, logging from net import Port from htmlgen import a from os import getEnv +from json import parseJson import jester @@ -15,8 +16,14 @@ import routes/[ const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances" const issuesUrl = "https://github.com/zedeus/nitter/issues" -let configPath = getEnv("NITTER_CONF_FILE", "./nitter.conf") -let (cfg, fullCfg) = getConfig(configPath) +let + configPath = getEnv("NITTER_CONF_FILE", "./nitter.conf") + (cfg, fullCfg) = getConfig(configPath) + + accountsPath = getEnv("NITTER_ACCOUNTS_FILE", "./guest_accounts.json") + accounts = parseJson(readFile(accountsPath)) + +initAccountPool(cfg, parseJson(readFile(accountsPath))) if not cfg.enableDebug: # Silence Jester's query warning @@ -38,8 +45,6 @@ waitFor initRedisPool(cfg) stdout.write &"Connected to Redis at {cfg.redisHost}:{cfg.redisPort}\n" stdout.flushFile -asyncCheck initTokenPool(cfg) - createUnsupportedRouter(cfg) createResolverRouter(cfg) createPrefRouter(cfg) diff --git a/src/parser.nim b/src/parser.nim index c7d8bd1..9262b28 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -29,7 +29,9 @@ proc parseUser(js: JsonNode; id=""): User = result.expandUserEntities(js) proc parseGraphUser(js: JsonNode): User = - let user = ? js{"user_result", "result"} + var user = js{"user_result", "result"} + if user.isNull: + user = ? js{"user_results", "result"} result = parseUser(user{"legacy"}) if "is_blue_verified" in user: @@ -287,169 +289,6 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.text.removeSuffix(" Learn more.") result.available = false -proc parseLegacyTweet(js: JsonNode): Tweet = - result = parseTweet(js, js{"card"}) - if not result.isNil and result.available: - result.user = parseUser(js{"user"}) - - if result.quote.isSome: - result.quote = some parseLegacyTweet(js{"quoted_status"}) - -proc parseTweetSearch*(js: JsonNode; after=""): Timeline = - result.beginning = after.len == 0 - - if js.kind == JNull or "modules" notin js or js{"modules"}.len == 0: - return - - for item in js{"modules"}: - with tweet, item{"status", "data"}: - let parsed = parseLegacyTweet(tweet) - - if parsed.retweet.isSome: - parsed.retweet = some parseLegacyTweet(tweet{"retweeted_status"}) - - result.content.add @[parsed] - - if result.content.len > 0: - result.bottom = $(result.content[^1][0].id - 1) - -proc parseUserTimelineTweet(tweet: JsonNode; users: TableRef[string, User]): Tweet = - result = parseTweet(tweet, tweet{"card"}) - - if result.isNil or not result.available: - return - - with user, tweet{"user"}: - let userId = user{"id_str"}.getStr - if user{"ext_is_blue_verified"}.getBool(false): - users[userId].verified = users[userId].verified or true - result.user = users[userId] - -proc parseUserTimeline*(js: JsonNode; after=""): Profile = - result = Profile(tweets: Timeline(beginning: after.len == 0)) - - if js.kind == JNull or "response" notin js or "twitter_objects" notin js: - return - - var users = newTable[string, User]() - for userId, user in js{"twitter_objects", "users"}: - users[userId] = parseUser(user) - - for entity in js{"response", "timeline"}: - let - tweetId = entity{"tweet", "id"}.getId - isPinned = entity{"tweet", "is_pinned"}.getBool(false) - - with tweet, js{"twitter_objects", "tweets", $tweetId}: - var parsed = parseUserTimelineTweet(tweet, users) - - if not parsed.isNil and parsed.available: - if parsed.quote.isSome: - parsed.quote = some parseUserTimelineTweet(tweet{"quoted_status"}, users) - - if parsed.retweet.isSome: - let retweet = parseUserTimelineTweet(tweet{"retweeted_status"}, users) - if retweet.quote.isSome: - retweet.quote = some parseUserTimelineTweet(tweet{"retweeted_status", "quoted_status"}, users) - parsed.retweet = some retweet - - if isPinned: - parsed.pinned = true - result.pinned = some parsed - else: - result.tweets.content.add parsed - - result.tweets.bottom = js{"response", "cursor", "bottom"}.getStr - -# proc finalizeTweet(global: GlobalObjects; id: string): Tweet = -# let intId = if id.len > 0: parseBiggestInt(id) else: 0 -# result = global.tweets.getOrDefault(id, Tweet(id: intId)) - -# if result.quote.isSome: -# let quote = get(result.quote).id -# if $quote in global.tweets: -# result.quote = some global.tweets[$quote] -# else: -# result.quote = some Tweet() - -# if result.retweet.isSome: -# let rt = get(result.retweet).id -# if $rt in global.tweets: -# result.retweet = some finalizeTweet(global, $rt) -# else: -# result.retweet = some Tweet() - -# proc parsePin(js: JsonNode; global: GlobalObjects): Tweet = -# let pin = js{"pinEntry", "entry", "entryId"}.getStr -# if pin.len == 0: return - -# let id = pin.getId -# if id notin global.tweets: return - -# global.tweets[id].pinned = true -# return finalizeTweet(global, id) - -# proc parseGlobalObjects(js: JsonNode): GlobalObjects = -# result = GlobalObjects() -# let -# tweets = ? js{"globalObjects", "tweets"} -# users = ? js{"globalObjects", "users"} - -# for k, v in users: -# result.users[k] = parseUser(v, k) - -# for k, v in tweets: -# var tweet = parseTweet(v, v{"card"}) -# if tweet.user.id in result.users: -# tweet.user = result.users[tweet.user.id] -# result.tweets[k] = tweet - -# proc parseInstructions(res: var Profile; global: GlobalObjects; js: JsonNode) = -# if js.kind != JArray or js.len == 0: -# return - -# for i in js: -# if res.tweets.beginning and i{"pinEntry"}.notNull: -# with pin, parsePin(i, global): -# res.pinned = some pin - -# with r, i{"replaceEntry", "entry"}: -# if "top" in r{"entryId"}.getStr: -# res.tweets.top = r.getCursor -# elif "bottom" in r{"entryId"}.getStr: -# res.tweets.bottom = r.getCursor - -# proc parseTimeline*(js: JsonNode; after=""): Profile = -# result = Profile(tweets: Timeline(beginning: after.len == 0)) -# let global = parseGlobalObjects(? js) - -# let instructions = ? js{"timeline", "instructions"} -# if instructions.len == 0: return - -# result.parseInstructions(global, instructions) - -# var entries: JsonNode -# for i in instructions: -# if "addEntries" in i: -# entries = i{"addEntries", "entries"} - -# for e in ? entries: -# let entry = e{"entryId"}.getStr -# if "tweet" in entry or entry.startsWith("sq-I-t") or "tombstone" in entry: -# let tweet = finalizeTweet(global, e.getEntryId) -# if not tweet.available: continue -# result.tweets.content.add tweet -# elif "cursor-top" in entry: -# result.tweets.top = e.getCursor -# elif "cursor-bottom" in entry: -# result.tweets.bottom = e.getCursor -# elif entry.startsWith("sq-cursor"): -# with cursor, e{"content", "operation", "cursor"}: -# if cursor{"cursorType"}.getStr == "Bottom": -# result.tweets.bottom = cursor{"value"}.getStr -# else: -# result.tweets.top = cursor{"value"}.getStr - proc parsePhotoRail*(js: JsonNode): PhotoRail = with error, js{"error"}: if error.getStr == "Not authorized.": @@ -597,8 +436,8 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = tweet.id = parseBiggestInt(entryId) result.pinned = some tweet -proc parseGraphSearch*(js: JsonNode; after=""): Timeline = - result = Timeline(beginning: after.len == 0) +proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = + result = Result[T](beginning: after.len == 0) let instructions = js{"data", "search_by_raw_query", "search_timeline", "timeline", "instructions"} if instructions.len == 0: @@ -607,15 +446,21 @@ proc parseGraphSearch*(js: JsonNode; after=""): Timeline = for instruction in instructions: let typ = instruction{"type"}.getStr if typ == "TimelineAddEntries": - for e in instructions[0]{"entries"}: + for e in instruction{"entries"}: let entryId = e{"entryId"}.getStr - if entryId.startsWith("tweet"): - with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetResult) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) - result.content.add tweet - elif entryId.startsWith("cursor-bottom"): + when T is Tweets: + if entryId.startsWith("tweet"): + with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: + let tweet = parseGraphTweet(tweetRes) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) + result.content.add tweet + elif T is User: + if entryId.startsWith("user"): + with userRes, e{"content", "itemContent"}: + result.content.add parseGraphUser(userRes) + + if entryId.startsWith("cursor-bottom"): result.bottom = e{"content", "value"}.getStr elif typ == "TimelineReplaceEntry": if instruction{"entry_id_to_replace"}.getStr.startsWith("cursor-bottom"): diff --git a/src/redis_cache.nim b/src/redis_cache.nim index 89161be..2387a42 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -147,15 +147,15 @@ proc getCachedUsername*(userId: string): Future[string] {.async.} = if result.len > 0 and user.id.len > 0: await all(cacheUserId(result, user.id), cache(user)) -proc getCachedTweet*(id: int64): Future[Tweet] {.async.} = - if id == 0: return - let tweet = await get(id.tweetKey) - if tweet != redisNil: - tweet.deserialize(Tweet) - else: - result = await getGraphTweetResult($id) - if not result.isNil: - await cache(result) +# proc getCachedTweet*(id: int64): Future[Tweet] {.async.} = +# if id == 0: return +# let tweet = await get(id.tweetKey) +# if tweet != redisNil: +# tweet.deserialize(Tweet) +# else: +# result = await getGraphTweetResult($id) +# if not result.isNil: +# await cache(result) proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return diff --git a/src/routes/rss.nim b/src/routes/rss.nim index d378396..6c77992 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -27,7 +27,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async. else: var q = query q.fromUser = names - profile.tweets = await getTweetSearch(q, after) + profile.tweets = await getGraphTweetSearch(q, after) # this is kinda dumb profile.user = User( username: name, @@ -76,7 +76,7 @@ proc createRssRouter*(cfg: Config) = if rss.cursor.len > 0: respRss(rss, "Search") - let tweets = await getTweetSearch(query, cursor) + let tweets = await getGraphTweetSearch(query, cursor) rss.cursor = tweets.bottom rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg) diff --git a/src/routes/search.nim b/src/routes/search.nim index c270df5..e9f991d 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -29,13 +29,13 @@ proc createSearchRouter*(cfg: Config) = redirect("/" & q) var users: Result[User] try: - users = await getUserSearch(query, getCursor()) + users = await getGraphUserSearch(query, getCursor()) except InternalError: users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) of tweets: let - tweets = await getTweetSearch(query, getCursor()) + tweets = await getGraphTweetSearch(query, getCursor()) rss = "/search/rss?" & genQueryUrl(query) resp renderMain(renderTweetSearch(tweets, prefs, getPath()), request, cfg, prefs, title, rss=rss) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 8d02b68..3568ab7 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -53,10 +53,10 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; result = case query.kind - of posts: await getUserTimeline(userId, after) + of posts: await getGraphUserTweets(userId, TimelineKind.tweets, after) of replies: await getGraphUserTweets(userId, TimelineKind.replies, after) of media: await getGraphUserTweets(userId, TimelineKind.media, after) - else: Profile(tweets: await getTweetSearch(query, after)) + else: Profile(tweets: await getGraphTweetSearch(query, after)) result.user = await user result.photoRail = await rail @@ -67,7 +67,7 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; rss, after: string): Future[string] {.async.} = if query.fromUser.len != 1: let - timeline = await getTweetSearch(query, after) + timeline = await getGraphTweetSearch(query, after) html = renderTweetSearch(timeline, prefs, getPath()) return renderMain(html, request, cfg, prefs, "Multi", rss=rss) @@ -122,7 +122,7 @@ proc createTimelineRouter*(cfg: Config) = # used for the infinite scroll feature if @"scroll".len > 0: if query.fromUser.len != 1: - var timeline = await getTweetSearch(query, after) + var timeline = await getGraphTweetSearch(query, after) if timeline.content.len == 0: resp Http404 timeline.beginning = true resp $renderTweetSearch(timeline, prefs, getPath()) diff --git a/src/tokens.nim b/src/tokens.nim index decf228..71a7abd 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -1,23 +1,16 @@ # SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, httpclient, times, sequtils, json, random -import strutils, tables -import types, consts +import asyncdispatch, times, json, random, strutils, tables +import types -const - maxConcurrentReqs = 5 # max requests at a time per token, to avoid race conditions - maxLastUse = 1.hours # if a token is unused for 60 minutes, it expires - maxAge = 2.hours + 55.minutes # tokens expire after 3 hours - failDelay = initDuration(minutes=30) +# max requests at a time per account to avoid race conditions +const maxConcurrentReqs = 5 var - tokenPool: seq[Token] - lastFailed: Time + accountPool: seq[GuestAccount] enableLogging = false -let headers = newHttpHeaders({"authorization": auth}) - template log(str) = - if enableLogging: echo "[tokens] ", str + if enableLogging: echo "[accounts] ", str proc getPoolJson*(): JsonNode = var @@ -26,141 +19,94 @@ proc getPoolJson*(): JsonNode = totalPending = 0 reqsPerApi: Table[string, int] - for token in tokenPool: - totalPending.inc(token.pending) - list[token.tok] = %*{ + for account in accountPool: + totalPending.inc(account.pending) + list[account.id] = %*{ "apis": newJObject(), - "pending": token.pending, - "init": $token.init, - "lastUse": $token.lastUse + "pending": account.pending, } - for api in token.apis.keys: - list[token.tok]["apis"][$api] = %token.apis[api] + for api in account.apis.keys: + list[account.id]["apis"][$api] = %account.apis[api].remaining let maxReqs = case api - of Api.search: 100000 + of Api.search: 50 of Api.photoRail: 180 - of Api.timeline: 187 - of Api.userTweets, Api.userTimeline: 300 - of Api.userTweetsAndReplies, Api.userRestId, - Api.userScreenName, Api.tweetDetail, Api.tweetResult, - Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500 - of Api.userSearch: 900 - reqs = maxReqs - token.apis[api].remaining + of Api.userTweets, Api.userTweetsAndReplies, Api.userMedia, + Api.userRestId, Api.userScreenName, + Api.tweetDetail, Api.tweetResult, + Api.list, Api.listTweets, Api.listMembers, Api.listBySlug: 500 + reqs = maxReqs - account.apis[api].remaining reqsPerApi[$api] = reqsPerApi.getOrDefault($api, 0) + reqs totalReqs.inc(reqs) return %*{ - "amount": tokenPool.len, + "amount": accountPool.len, "requests": totalReqs, "pending": totalPending, "apis": reqsPerApi, - "tokens": list + "accounts": list } proc rateLimitError*(): ref RateLimitError = newException(RateLimitError, "rate limited") -proc fetchToken(): Future[Token] {.async.} = - if getTime() - lastFailed < failDelay: - raise rateLimitError() - - let client = newAsyncHttpClient(headers=headers) - - try: - let - resp = await client.postContent(activate) - tokNode = parseJson(resp)["guest_token"] - tok = tokNode.getStr($(tokNode.getInt)) - time = getTime() - - return Token(tok: tok, init: time, lastUse: time) - except Exception as e: - echo "[tokens] fetching token failed: ", e.msg - if "Try again" notin e.msg: - echo "[tokens] fetching tokens paused, resuming in 30 minutes" - lastFailed = getTime() - finally: - client.close() - -proc expired(token: Token): bool = - let time = getTime() - token.init < time - maxAge or token.lastUse < time - maxLastUse - -proc isLimited(token: Token; api: Api): bool = - if token.isNil or token.expired: +proc isLimited(account: GuestAccount; api: Api): bool = + if account.isNil: return true - if api in token.apis: - let limit = token.apis[api] + if api in account.apis: + let limit = account.apis[api] return (limit.remaining <= 10 and limit.reset > epochTime().int) else: return false -proc isReady(token: Token; api: Api): bool = - not (token.isNil or token.pending > maxConcurrentReqs or token.isLimited(api)) +proc isReady(account: GuestAccount; api: Api): bool = + not (account.isNil or account.pending > maxConcurrentReqs or account.isLimited(api)) -proc release*(token: Token; used=false; invalid=false) = - if token.isNil: return - if invalid or token.expired: - if invalid: log "discarding invalid token" - elif token.expired: log "discarding expired token" +proc release*(account: GuestAccount; used=false; invalid=false) = + if account.isNil: return + if invalid: + log "discarding invalid account: " & account.id - let idx = tokenPool.find(token) - if idx > -1: tokenPool.delete(idx) + let idx = accountPool.find(account) + if idx > -1: accountPool.delete(idx) elif used: - dec token.pending - token.lastUse = getTime() + dec account.pending -proc getToken*(api: Api): Future[Token] {.async.} = - for i in 0 ..< tokenPool.len: +proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = + for i in 0 ..< accountPool.len: if result.isReady(api): break release(result) - result = tokenPool.sample() + result = accountPool.sample() - if not result.isReady(api): - release(result) - result = await fetchToken() - log "added new token to pool" - tokenPool.add result - - if not result.isNil: + if not result.isNil and result.isReady(api): inc result.pending else: + log "no accounts available for API: " & $api raise rateLimitError() -proc setRateLimit*(token: Token; api: Api; remaining, reset: int) = +proc setRateLimit*(account: GuestAccount; api: Api; remaining, reset: int) = # avoid undefined behavior in race conditions - if api in token.apis: - let limit = token.apis[api] + if api in account.apis: + let limit = account.apis[api] if limit.reset >= reset and limit.remaining < remaining: return + if limit.reset == reset and limit.remaining >= remaining: + account.apis[api].remaining = remaining + return - token.apis[api] = RateLimit(remaining: remaining, reset: reset) + account.apis[api] = RateLimit(remaining: remaining, reset: reset) -proc poolTokens*(amount: int) {.async.} = - var futs: seq[Future[Token]] - for i in 0 ..< amount: - futs.add fetchToken() - - for token in futs: - var newToken: Token - - try: newToken = await token - except: discard - - if not newToken.isNil: - log "added new token to pool" - tokenPool.add newToken - -proc initTokenPool*(cfg: Config) {.async.} = +proc initAccountPool*(cfg: Config; accounts: JsonNode) = enableLogging = cfg.enableDebug - while true: - if tokenPool.countIt(not it.isLimited(Api.userTimeline)) < cfg.minTokens: - await poolTokens(min(4, cfg.minTokens - tokenPool.len)) - await sleepAsync(2000) + for account in accounts: + accountPool.add GuestAccount( + id: account{"user", "id_str"}.getStr, + oauthToken: account{"oauth_token"}.getStr, + oauthSecret: account{"oauth_token_secret"}.getStr, + ) diff --git a/src/types.nim b/src/types.nim index 1a47d25..2a553dd 100644 --- a/src/types.nim +++ b/src/types.nim @@ -17,11 +17,8 @@ type Api* {.pure.} = enum tweetDetail tweetResult - timeline - userTimeline photoRail search - userSearch list listBySlug listMembers @@ -36,9 +33,11 @@ type remaining*: int reset*: int - Token* = ref object - tok*: string - init*: Time + GuestAccount* = ref object + id*: string + oauthToken*: string + oauthSecret*: string + # init*: Time lastUse*: Time pending*: int apis*: Table[Api, RateLimit] From bbd68e684071e8b26fca89587db48f12417a9bf0 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 19 Aug 2023 01:13:36 +0200 Subject: [PATCH 022/302] Filter out account limits that already reset --- src/tokens.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tokens.nim b/src/tokens.nim index 71a7abd..401dc05 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -19,6 +19,8 @@ proc getPoolJson*(): JsonNode = totalPending = 0 reqsPerApi: Table[string, int] + let now = epochTime() + for account in accountPool: totalPending.inc(account.pending) list[account.id] = %*{ @@ -27,6 +29,9 @@ proc getPoolJson*(): JsonNode = } for api in account.apis.keys: + if (now.int - account.apis[api].reset) / 60 > 15: + continue + list[account.id]["apis"][$api] = %account.apis[api].remaining let From 3d8858f0d86d09ce815bc78db417290557f23908 Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 20 Aug 2023 11:56:42 +0200 Subject: [PATCH 023/302] Track rate limits, reset after 24 hours --- src/apiutils.nim | 12 +++++++++++- src/tokens.nim | 26 ++++++++++++++++++++------ src/types.nim | 2 ++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index d1ecfa3..54e6777 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import httpclient, asyncdispatch, options, strutils, uri, times, math +import httpclient, asyncdispatch, options, strutils, uri, times, math, tables import jsony, packedjson, zippy, oauth1 import types, tokens, consts, parserutils, http_pool import experimental/types/common @@ -129,6 +129,16 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = release(account, invalid=true) raise rateLimitError() + if body.startsWith("{\"errors"): + let errors = body.fromJson(Errors) + if errors in {invalidToken, badToken}: + echo "fetch error: ", errors + release(account, invalid=true) + raise rateLimitError() + elif errors in {rateLimited}: + account.apis[api].limited = true + echo "rate limited, api: ", $api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id + proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = fetchImpl result: if not (result.startsWith('{') or result.startsWith('[')): diff --git a/src/tokens.nim b/src/tokens.nim index 401dc05..45aa895 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -3,7 +3,9 @@ import asyncdispatch, times, json, random, strutils, tables import types # max requests at a time per account to avoid race conditions -const maxConcurrentReqs = 5 +const + maxConcurrentReqs = 5 + dayInSeconds = 24 * 60 * 60 var accountPool: seq[GuestAccount] @@ -19,7 +21,7 @@ proc getPoolJson*(): JsonNode = totalPending = 0 reqsPerApi: Table[string, int] - let now = epochTime() + let now = epochTime().int for account in accountPool: totalPending.inc(account.pending) @@ -29,10 +31,17 @@ proc getPoolJson*(): JsonNode = } for api in account.apis.keys: - if (now.int - account.apis[api].reset) / 60 > 15: - continue + let obj = %*{} + if account.apis[api].limited: + obj["limited"] = %true - list[account.id]["apis"][$api] = %account.apis[api].remaining + if account.apis[api].reset > now.int: + obj["remaining"] = %account.apis[api].remaining + + list[account.id]["apis"][$api] = obj + + if "remaining" notin obj: + continue let maxReqs = @@ -65,7 +74,12 @@ proc isLimited(account: GuestAccount; api: Api): bool = if api in account.apis: let limit = account.apis[api] - return (limit.remaining <= 10 and limit.reset > epochTime().int) + + if limit.limited and (epochTime().int - limit.limitedAt) > dayInSeconds: + account.apis[api].limited = false + echo "account limit reset, api: ", api, ", id: ", account.id + + return limit.limited or (limit.remaining <= 10 and limit.reset > epochTime().int) else: return false diff --git a/src/types.nim b/src/types.nim index 2a553dd..33d0cda 100644 --- a/src/types.nim +++ b/src/types.nim @@ -32,6 +32,8 @@ type RateLimit* = object remaining*: int reset*: int + limited*: bool + limitedAt*: int GuestAccount* = ref object id*: string From e8b5cbef7b984c527675a4df8fe2c7b4d841b13c Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 20 Aug 2023 12:31:08 +0200 Subject: [PATCH 024/302] Add missing limitedAt assignment --- src/apiutils.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apiutils.nim b/src/apiutils.nim index 54e6777..6e333e7 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -137,6 +137,7 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = raise rateLimitError() elif errors in {rateLimited}: account.apis[api].limited = true + account.apis[api].limitedAt = epochTime().int echo "rate limited, api: ", $api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = From 51714b5ad2f8992972f43fd9cb2178aba2729f39 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 21 Aug 2023 11:25:27 +0200 Subject: [PATCH 025/302] Add guest accounts variable to GitHub action --- .github/workflows/run-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 140b6bf..37979cb 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -40,6 +40,9 @@ jobs: nimble md nimble scss - name: Run tests + env: + GUEST_ACCOUNTS: ${{ secrets.GUEST_ACCOUNTS }} run: | + echo $GUEST_ACCOUNTS > ./guest_accounts.json ./nitter & pytest -n4 tests From c3d9441370e9740a02834dbf1da849263a9075c3 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 21 Aug 2023 14:49:50 +0200 Subject: [PATCH 026/302] Unify some guest account logs --- src/apiutils.nim | 2 +- src/tokens.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 6e333e7..453b36a 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -138,7 +138,7 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = elif errors in {rateLimited}: account.apis[api].limited = true account.apis[api].limitedAt = epochTime().int - echo "rate limited, api: ", $api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id + echo "[accounts] rate limited, api: ", api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = fetchImpl result: diff --git a/src/tokens.nim b/src/tokens.nim index 45aa895..a44866f 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -77,7 +77,7 @@ proc isLimited(account: GuestAccount; api: Api): bool = if limit.limited and (epochTime().int - limit.limitedAt) > dayInSeconds: account.apis[api].limited = false - echo "account limit reset, api: ", api, ", id: ", account.id + log "resetting limit, api: ", api, ", id: ", account.id return limit.limited or (limit.remaining <= 10 and limit.reset > epochTime().int) else: From 12504bcffed59efe626b90a574b27857f7450515 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 21 Aug 2023 18:12:06 +0200 Subject: [PATCH 027/302] Fix compilation error --- src/tokens.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokens.nim b/src/tokens.nim index a44866f..d1f1e68 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -77,7 +77,7 @@ proc isLimited(account: GuestAccount; api: Api): bool = if limit.limited and (epochTime().int - limit.limitedAt) > dayInSeconds: account.apis[api].limited = false - log "resetting limit, api: ", api, ", id: ", account.id + log "resetting limit, api: " & $api & ", id: " & $account.id return limit.limited or (limit.remaining <= 10 and limit.reset > epochTime().int) else: From 30bdf3a14e900c653f1c277c5e0221cab846e1de Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 22 Aug 2023 01:32:09 +0200 Subject: [PATCH 028/302] Reduce max concurrent pending requests per account --- src/tokens.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tokens.nim b/src/tokens.nim index d1f1e68..f16a816 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -1,10 +1,10 @@ -# SPDX-License-Identifier: AGPL-3.0-only +#i hate begging for this too em SPDX-License-Identifier: AGPL-3.0-only import asyncdispatch, times, json, random, strutils, tables import types # max requests at a time per account to avoid race conditions const - maxConcurrentReqs = 5 + maxConcurrentReqs = 2 dayInSeconds = 24 * 60 * 60 var From 5c08e6a774f428b4193a33e0400729c279ded07c Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 22 Aug 2023 02:27:44 +0200 Subject: [PATCH 029/302] Fix compilation on older versions of Nim --- src/parserutils.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parserutils.nim b/src/parserutils.nim index c65052e..7cf696e 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -36,7 +36,8 @@ template with*(ident, value, body): untyped = template with*(ident; value: JsonNode; body): untyped = if true: let ident {.inject.} = value - if value.notNull: body + # value.notNull causes a compilation error for versions < 1.6.14 + if notNull(value): body template getCursor*(js: JsonNode): string = js{"content", "operation", "cursor", "value"}.getStr From 6e8744943f9c47cbc7150785a86fa05a18d7b98b Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 22 Aug 2023 03:43:18 +0200 Subject: [PATCH 030/302] Tweak /.tokens, add amount of limited accounts --- src/tokens.nim | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/tokens.nim b/src/tokens.nim index f16a816..5582a51 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -19,6 +19,7 @@ proc getPoolJson*(): JsonNode = list = newJObject() totalReqs = 0 totalPending = 0 + totalLimited = 0 reqsPerApi: Table[string, int] let now = epochTime().int @@ -31,34 +32,40 @@ proc getPoolJson*(): JsonNode = } for api in account.apis.keys: - let obj = %*{} - if account.apis[api].limited: - obj["limited"] = %true + let + apiStatus = account.apis[api] + obj = %*{} - if account.apis[api].reset > now.int: - obj["remaining"] = %account.apis[api].remaining + if apiStatus.limited: + obj["limited"] = %true + inc totalLimited + + if apiStatus.reset > now.int: + obj["remaining"] = %apiStatus.remaining + + if "remaining" notin obj and not apiStatus.limited: + continue list[account.id]["apis"][$api] = obj - if "remaining" notin obj: - continue - let maxReqs = case api of Api.search: 50 + of Api.tweetDetail: 150 of Api.photoRail: 180 of Api.userTweets, Api.userTweetsAndReplies, Api.userMedia, - Api.userRestId, Api.userScreenName, - Api.tweetDetail, Api.tweetResult, + Api.userRestId, Api.userScreenName, Api.tweetResult, Api.list, Api.listTweets, Api.listMembers, Api.listBySlug: 500 - reqs = maxReqs - account.apis[api].remaining + of Api.userSearch: 900 + reqs = maxReqs - apiStatus.remaining reqsPerApi[$api] = reqsPerApi.getOrDefault($api, 0) + reqs totalReqs.inc(reqs) return %*{ "amount": accountPool.len, + "limited": totalLimited, "requests": totalReqs, "pending": totalPending, "apis": reqsPerApi, From 8df5256c1dd3970bcbb71cfa7753c3b50d59287e Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 22 Aug 2023 03:44:11 +0200 Subject: [PATCH 031/302] Switch back to old user search endpoint --- src/api.nim | 28 ++++++++++++---------------- src/consts.nim | 2 ++ src/experimental/parser/user.nim | 2 +- src/experimental/types/user.nim | 1 + src/parser.nim | 26 ++++++++++---------------- src/routes/search.nim | 2 +- src/types.nim | 1 + 7 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/api.nim b/src/api.nim index d6a4564..4ac999c 100644 --- a/src/api.nim +++ b/src/api.nim @@ -112,29 +112,25 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = if after.len > 0: variables["cursor"] = % after let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} - result = parseGraphSearch[Tweets](await fetch(url, Api.search), after) + result = parseGraphSearch(await fetch(url, Api.search), after) result.query = query -proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} = +proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) - var - variables = %*{ - "rawQuery": query.text, - "count": 20, - "product": "People", - "withDownvotePerspective": false, - "withReactionsMetadata": false, - "withReactionsPerspective": false - } - if after.len > 0: - variables["cursor"] = % after - result.beginning = false + let + page = if page.len == 0: "1" else: page + url = userSearch ? genParams({"q": query.text, "skip_status": "1", "page": page}) + js = await fetchRaw(url, Api.userSearch) + + result = parseUsers(js) - let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} - result = parseGraphSearch[User](await fetch(url, Api.search), after) result.query = query + if page.len == 0: + result.bottom = "2" + elif page.allCharsInSet(Digits): + result.bottom = $(parseInt(page) + 1) proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return diff --git a/src/consts.nim b/src/consts.nim index 2cfd1ed..8bf6422 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,6 +9,7 @@ const activate* = $(api / "1.1/guest/activate.json") photoRail* = api / "1.1/statuses/media_timeline.json" + userSearch* = api / "1.1/users/search.json" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" @@ -34,6 +35,7 @@ const "include_user_entities": "1", "include_ext_reply_count": "1", "include_ext_is_blue_verified": "1", + #"include_ext_verified_type": "1", "include_ext_media_color": "0", "cards_platform": "Web-13", "tweet_mode": "extended", diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index b4d710f..5962a87 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -56,7 +56,7 @@ proc toUser*(raw: RawUser): User = tweets: raw.statusesCount, likes: raw.favouritesCount, media: raw.mediaCount, - verified: raw.verified, + verified: raw.verified or raw.extIsBlueVerified, protected: raw.protected, joinDate: parseTwitterDate(raw.createdAt), banner: getBanner(raw), diff --git a/src/experimental/types/user.nim b/src/experimental/types/user.nim index 1c8a5c3..39331a0 100644 --- a/src/experimental/types/user.nim +++ b/src/experimental/types/user.nim @@ -16,6 +16,7 @@ type statusesCount*: int mediaCount*: int verified*: bool + extIsBlueVerified*: bool protected*: bool profileLinkColor*: string profileBannerUrl*: string diff --git a/src/parser.nim b/src/parser.nim index 9262b28..03242c1 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, options, times, math, tables +import strutils, options, times, math import packedjson, packedjson/deserialiser import types, parserutils, utils import experimental/parser/unifiedcard @@ -436,8 +436,8 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = tweet.id = parseBiggestInt(entryId) result.pinned = some tweet -proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = - result = Result[T](beginning: after.len == 0) +proc parseGraphSearch*(js: JsonNode; after=""): Timeline = + result = Timeline(beginning: after.len == 0) let instructions = js{"data", "search_by_raw_query", "search_timeline", "timeline", "instructions"} if instructions.len == 0: @@ -448,19 +448,13 @@ proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = if typ == "TimelineAddEntries": for e in instruction{"entries"}: let entryId = e{"entryId"}.getStr - when T is Tweets: - if entryId.startsWith("tweet"): - with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetRes) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) - result.content.add tweet - elif T is User: - if entryId.startsWith("user"): - with userRes, e{"content", "itemContent"}: - result.content.add parseGraphUser(userRes) - - if entryId.startsWith("cursor-bottom"): + if entryId.startsWith("tweet"): + with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: + let tweet = parseGraphTweet(tweetRes) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) + result.content.add tweet + elif entryId.startsWith("cursor-bottom"): result.bottom = e{"content", "value"}.getStr elif typ == "TimelineReplaceEntry": if instruction{"entry_id_to_replace"}.getStr.startsWith("cursor-bottom"): diff --git a/src/routes/search.nim b/src/routes/search.nim index e9f991d..676229e 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -29,7 +29,7 @@ proc createSearchRouter*(cfg: Config) = redirect("/" & q) var users: Result[User] try: - users = await getGraphUserSearch(query, getCursor()) + users = await getUserSearch(query, getCursor()) except InternalError: users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) diff --git a/src/types.nim b/src/types.nim index 33d0cda..fcb24c0 100644 --- a/src/types.nim +++ b/src/types.nim @@ -19,6 +19,7 @@ type tweetResult photoRail search + userSearch list listBySlug listMembers From 45808361af63e848ce322a27190d3ed7aba0723a Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 22 Aug 2023 04:45:49 +0200 Subject: [PATCH 032/302] Fix tweetDetail stats --- src/tokens.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tokens.nim b/src/tokens.nim index 5582a51..a3ed78a 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -52,10 +52,10 @@ proc getPoolJson*(): JsonNode = maxReqs = case api of Api.search: 50 - of Api.tweetDetail: 150 of Api.photoRail: 180 of Api.userTweets, Api.userTweetsAndReplies, Api.userMedia, - Api.userRestId, Api.userScreenName, Api.tweetResult, + Api.userRestId, Api.userScreenName, + Api.tweetResult, Api.tweetDetail, Api.list, Api.listTweets, Api.listMembers, Api.listBySlug: 500 of Api.userSearch: 900 reqs = maxReqs - apiStatus.remaining From a3e11e3272ebdd6118cd818591530ac8be5ae491 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 23 Aug 2023 10:14:44 +0200 Subject: [PATCH 033/302] Switch to using typeahead for user search --- src/api.nim | 12 +++--------- src/consts.nim | 2 +- src/experimental/parser/user.nim | 7 +++++++ src/experimental/types/user.nim | 3 +++ src/routes/search.nim | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/api.nim b/src/api.nim index 4ac999c..a7efbd0 100644 --- a/src/api.nim +++ b/src/api.nim @@ -115,22 +115,16 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = result = parseGraphSearch(await fetch(url, Api.search), after) result.query = query -proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = +proc getUserSearch*(query: Query): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) let - page = if page.len == 0: "1" else: page - url = userSearch ? genParams({"q": query.text, "skip_status": "1", "page": page}) + url = userSearch ? genParams({"q": query.text, "result_type": "users"}) js = await fetchRaw(url, Api.userSearch) - result = parseUsers(js) - + result = parseTypeahead(js) result.query = query - if page.len == 0: - result.bottom = "2" - elif page.allCharsInSet(Digits): - result.bottom = $(parseInt(page) + 1) proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return diff --git a/src/consts.nim b/src/consts.nim index 8bf6422..1bb950e 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,7 +9,7 @@ const activate* = $(api / "1.1/guest/activate.json") photoRail* = api / "1.1/statuses/media_timeline.json" - userSearch* = api / "1.1/users/search.json" + userSearch* = api / "1.1/search/typeahead.json" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 5962a87..400e740 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -85,3 +85,10 @@ proc parseUsers*(json: string; after=""): Result[User] = let raw = json.fromJson(seq[RawUser]) for user in raw: result.content.add user.toUser + +proc parseTypeahead*(json: string): Result[User] = + result = Result[User](beginning: true) + + let raw = json.fromJson(Typeahead) + for user in raw.users: + result.content.add user.toUser diff --git a/src/experimental/types/user.nim b/src/experimental/types/user.nim index 39331a0..7d34f7b 100644 --- a/src/experimental/types/user.nim +++ b/src/experimental/types/user.nim @@ -42,3 +42,6 @@ type Color* = object red*, green*, blue*: int + + Typeahead* = object + users*: seq[RawUser] diff --git a/src/routes/search.nim b/src/routes/search.nim index 676229e..e0a888c 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -29,7 +29,7 @@ proc createSearchRouter*(cfg: Config) = redirect("/" & q) var users: Result[User] try: - users = await getUserSearch(query, getCursor()) + users = await getUserSearch(query) except InternalError: users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) From 88b005c9da726af3f0858339da60949b42278744 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 23 Aug 2023 19:31:40 +0200 Subject: [PATCH 034/302] Revert "Switch to using typeahead for user search" This reverts commit a3e11e3272ebdd6118cd818591530ac8be5ae491. --- src/api.nim | 12 +++++++++--- src/consts.nim | 2 +- src/experimental/parser/user.nim | 7 ------- src/experimental/types/user.nim | 3 --- src/routes/search.nim | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/api.nim b/src/api.nim index a7efbd0..4ac999c 100644 --- a/src/api.nim +++ b/src/api.nim @@ -115,16 +115,22 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = result = parseGraphSearch(await fetch(url, Api.search), after) result.query = query -proc getUserSearch*(query: Query): Future[Result[User]] {.async.} = +proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) let - url = userSearch ? genParams({"q": query.text, "result_type": "users"}) + page = if page.len == 0: "1" else: page + url = userSearch ? genParams({"q": query.text, "skip_status": "1", "page": page}) js = await fetchRaw(url, Api.userSearch) - result = parseTypeahead(js) + result = parseUsers(js) + result.query = query + if page.len == 0: + result.bottom = "2" + elif page.allCharsInSet(Digits): + result.bottom = $(parseInt(page) + 1) proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return diff --git a/src/consts.nim b/src/consts.nim index 1bb950e..8bf6422 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,7 +9,7 @@ const activate* = $(api / "1.1/guest/activate.json") photoRail* = api / "1.1/statuses/media_timeline.json" - userSearch* = api / "1.1/search/typeahead.json" + userSearch* = api / "1.1/users/search.json" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 400e740..5962a87 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -85,10 +85,3 @@ proc parseUsers*(json: string; after=""): Result[User] = let raw = json.fromJson(seq[RawUser]) for user in raw: result.content.add user.toUser - -proc parseTypeahead*(json: string): Result[User] = - result = Result[User](beginning: true) - - let raw = json.fromJson(Typeahead) - for user in raw.users: - result.content.add user.toUser diff --git a/src/experimental/types/user.nim b/src/experimental/types/user.nim index 7d34f7b..39331a0 100644 --- a/src/experimental/types/user.nim +++ b/src/experimental/types/user.nim @@ -42,6 +42,3 @@ type Color* = object red*, green*, blue*: int - - Typeahead* = object - users*: seq[RawUser] diff --git a/src/routes/search.nim b/src/routes/search.nim index e0a888c..676229e 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -29,7 +29,7 @@ proc createSearchRouter*(cfg: Config) = redirect("/" & q) var users: Result[User] try: - users = await getUserSearch(query) + users = await getUserSearch(query, getCursor()) except InternalError: users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) From ae9fa02bf5f8459e59f0f18120fee9f06404aef5 Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 25 Aug 2023 16:28:30 +0200 Subject: [PATCH 035/302] Switch to TweetDetail for tweets --- src/consts.nim | 10 +++++++--- src/parser.nim | 25 +++++++++++++++---------- src/tokens.nim | 3 ++- tests/test_card.py | 10 +++++----- tests/test_timeline.py | 2 +- tests/test_tweet_media.py | 2 +- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/consts.nim b/src/consts.nim index 8bf6422..96cea47 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -17,7 +17,7 @@ const graphUserTweets* = graphql / "3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2" graphUserTweetsAndReplies* = graphql / "8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2" graphUserMedia* = graphql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" - graphTweet* = graphql / "83h5UyHZ9wEKBVzALX8R_g/ConversationTimelineV2" + graphTweet* = graphql / "q94uRCEn65LZThakYcPT6g/TweetDetail" graphTweetResult* = graphql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" graphSearchTimeline* = graphql / "gkjsKepM6gl_HmFWoWKfgg/SearchTimeline" graphListById* = graphql / "iTpgCtbdxrsJfyx0cFjHqg/ListByRestId" @@ -89,8 +89,12 @@ const tweetVariables* = """{ "focalTweetId": "$1", $2 - "includeHasBirdwatchNotes": false -}""" + "includeHasBirdwatchNotes": false, + "includePromotedContent": false, + "withBirdwatchNotes": false, + "withVoice": false, + "withV2Timeline": true +}""".replace(" ", "").replace("\n", "") # oldUserTweetsVariables* = """{ # "userId": "$1", $2 diff --git a/src/parser.nim b/src/parser.nim index 03242c1..d5190a3 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -324,7 +324,7 @@ proc parseGraphTweet(js: JsonNode): Tweet = of "TweetWithVisibilityResults": return parseGraphTweet(js{"tweet"}) - var jsCard = copy(js{"tweet_card", "legacy"}) + var jsCard = copy(js{"card", "legacy"}) if jsCard.kind != JNull: var values = newJObject() for val in jsCard["binding_values"]: @@ -342,7 +342,6 @@ proc parseGraphTweet(js: JsonNode): Tweet = result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"})) proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = - let thread = js{"content", "items"} for t in js{"content", "items"}: let entryId = t{"entryId"}.getStr if "cursor-showmore" in entryId: @@ -350,11 +349,16 @@ proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = result.thread.cursor = cursor.getStr result.thread.hasMore = true elif "tweet" in entryId: - let tweet = parseGraphTweet(t{"item", "content", "tweetResult", "result"}) - result.thread.content.add tweet + let + isLegacy = t{"item"}.hasKey("itemContent") + (contentKey, resultKey) = if isLegacy: ("itemContent", "tweet_results") + else: ("content", "tweetResult") - if t{"item", "content", "tweetDisplayType"}.getStr == "SelfThread": - result.self = true + with content, t{"item", contentKey}: + result.thread.content.add parseGraphTweet(content{resultKey, "result"}) + + if content{"tweetDisplayType"}.getStr == "SelfThread": + result.self = true proc parseGraphTweetResult*(js: JsonNode): Tweet = with tweet, js{"data", "tweet_result", "result"}: @@ -363,14 +367,14 @@ proc parseGraphTweetResult*(js: JsonNode): Tweet = proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) - let instructions = ? js{"data", "timeline_response", "instructions"} + let instructions = ? js{"data", "threaded_conversation_with_injections_v2", "instructions"} if instructions.len == 0: return for e in instructions[0]{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): - with tweetResult, e{"content", "content", "tweetResult", "result"}: + with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: let tweet = parseGraphTweet(tweetResult) if not tweet.available: @@ -385,7 +389,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = let tweet = Tweet( id: parseBiggestInt(id), available: false, - text: e{"content", "content", "tombstoneInfo", "richText"}.getTombstone + text: e{"content", "itemContent", "tombstoneInfo", "richText"}.getTombstone ) if id == tweetId: @@ -397,9 +401,10 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = if self: result.after = thread else: + echo "adding thread: ", thread.content.len result.replies.content.add thread elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", "content", "value"}.getStr + result.replies.bottom = e{"content", "itemContent", "value"}.getStr proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) diff --git a/src/tokens.nim b/src/tokens.nim index a3ed78a..bb9696c 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -52,10 +52,11 @@ proc getPoolJson*(): JsonNode = maxReqs = case api of Api.search: 50 + of Api.tweetDetail: 150 of Api.photoRail: 180 of Api.userTweets, Api.userTweetsAndReplies, Api.userMedia, Api.userRestId, Api.userScreenName, - Api.tweetResult, Api.tweetDetail, + Api.tweetResult, Api.list, Api.listTweets, Api.listMembers, Api.listBySlug: 500 of Api.userSearch: 900 reqs = maxReqs - apiStatus.remaining diff --git a/tests/test_card.py b/tests/test_card.py index f84ddca..733bd40 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -13,11 +13,6 @@ card = [ 'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too) - obsplugin.nim', 'gist.github.com', True], - ['FluentAI/status/1116417904831029248', - 'Amazon’s Alexa isn’t just AI — thousands of humans are listening', - 'One of the only ways to improve Alexa is to have human beings check it for errors', - 'theverge.com', True], - ['nim_lang/status/1082989146040340480', 'Nim in 2018: A short recap', 'There were several big news in the Nim world in 2018 – two new major releases, partnership with Status, and much more. But let us go chronologically.', @@ -25,6 +20,11 @@ card = [ ] no_thumb = [ + ['FluentAI/status/1116417904831029248', + 'Amazon’s Alexa isn’t just AI — thousands of humans are listening', + 'One of the only ways to improve Alexa is to have human beings check it for errors', + 'theverge.com'], + ['Thom_Wolf/status/1122466524860702729', 'facebookresearch/fairseq', 'Facebook AI Research Sequence-to-Sequence Toolkit written in Python. - GitHub - facebookresearch/fairseq: Facebook AI Research Sequence-to-Sequence Toolkit written in Python.', diff --git a/tests/test_timeline.py b/tests/test_timeline.py index b56d6ad..919aa70 100644 --- a/tests/test_timeline.py +++ b/tests/test_timeline.py @@ -6,7 +6,7 @@ normal = [['jack'], ['elonmusk']] after = [['jack', '1681686036294803456'], ['elonmusk', '1681686036294803456']] -no_more = [['mobile_test_8?cursor=1000']] +no_more = [['mobile_test_8?cursor=DAABCgABF4YVAqN___kKAAICNn_4msIQAAgAAwAAAAIAAA']] empty = [['emptyuser'], ['mobile_test_10']] diff --git a/tests/test_tweet_media.py b/tests/test_tweet_media.py index 7a00983..f54cea7 100644 --- a/tests/test_tweet_media.py +++ b/tests/test_tweet_media.py @@ -14,7 +14,7 @@ poll = [ image = [ ['mobile_test/status/519364660823207936', 'BzUnaDFCUAAmrjs'], - ['mobile_test_2/status/324619691039543297', 'BIFH45vCUAAQecj'] + #['mobile_test_2/status/324619691039543297', 'BIFH45vCUAAQecj'] ] gif = [ From 03794a8d4a0eb13fccdf88d5f6633956daaf6564 Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 25 Aug 2023 16:32:33 +0200 Subject: [PATCH 036/302] Cleanup --- src/parser.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser.nim b/src/parser.nim index d5190a3..4087a79 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -401,7 +401,6 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = if self: result.after = thread else: - echo "adding thread: ", thread.content.len result.replies.content.add thread elif entryId.startsWith("cursor-bottom"): result.replies.bottom = e{"content", "itemContent", "value"}.getStr From 7630f57f17246ffb60d4f5472d17af5fc3c6fa9f Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 26 Aug 2023 05:16:38 +0200 Subject: [PATCH 037/302] Fix cards not being displayed --- src/parser.nim | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index 4087a79..914c038 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -4,7 +4,7 @@ import packedjson, packedjson/deserialiser import types, parserutils, utils import experimental/parser/unifiedcard -proc parseGraphTweet(js: JsonNode): Tweet +proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet proc parseUser(js: JsonNode; id=""): User = if js.isNull: return @@ -306,7 +306,7 @@ proc parsePhotoRail*(js: JsonNode): PhotoRail = if url.len == 0: continue result.add GalleryPhoto(url: url, tweetId: $t.id) -proc parseGraphTweet(js: JsonNode): Tweet = +proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet = if js.kind == JNull: return Tweet() @@ -322,9 +322,9 @@ proc parseGraphTweet(js: JsonNode): Tweet = of "TweetPreviewDisplay": return Tweet(text: "You're unable to view this Tweet because it's only available to the Subscribers of the account owner.") of "TweetWithVisibilityResults": - return parseGraphTweet(js{"tweet"}) + return parseGraphTweet(js{"tweet"}, isLegacy) - var jsCard = copy(js{"card", "legacy"}) + var jsCard = copy(js{if isLegacy: "card" else: "tweet_card", "legacy"}) if jsCard.kind != JNull: var values = newJObject() for val in jsCard["binding_values"]: @@ -339,7 +339,7 @@ proc parseGraphTweet(js: JsonNode): Tweet = result.expandNoteTweetEntities(noteTweet) if result.quote.isSome: - result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"})) + result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"}, isLegacy)) proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = for t in js{"content", "items"}: @@ -355,14 +355,14 @@ proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = else: ("content", "tweetResult") with content, t{"item", contentKey}: - result.thread.content.add parseGraphTweet(content{resultKey, "result"}) + result.thread.content.add parseGraphTweet(content{resultKey, "result"}, isLegacy) if content{"tweetDisplayType"}.getStr == "SelfThread": result.self = true proc parseGraphTweetResult*(js: JsonNode): Tweet = with tweet, js{"data", "tweet_result", "result"}: - result = parseGraphTweet(tweet) + result = parseGraphTweet(tweet, false) proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) @@ -375,7 +375,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetResult) + let tweet = parseGraphTweet(tweetResult, true) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) @@ -421,7 +421,7 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): with tweetResult, e{"content", "content", "tweetResult", "result"}: - let tweet = parseGraphTweet(tweetResult) + let tweet = parseGraphTweet(tweetResult, false) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) result.tweets.content.add tweet @@ -432,7 +432,7 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result.tweets.bottom = e{"content", "value"}.getStr if after.len == 0 and i{"__typename"}.getStr == "TimelinePinEntry": with tweetResult, i{"entry", "content", "content", "tweetResult", "result"}: - let tweet = parseGraphTweet(tweetResult) + let tweet = parseGraphTweet(tweetResult, false) tweet.pinned = true if not tweet.available and tweet.tombstone.len == 0: let entryId = i{"entry", "entryId"}.getEntryId @@ -454,7 +454,7 @@ proc parseGraphSearch*(js: JsonNode; after=""): Timeline = let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetRes) + let tweet = parseGraphTweet(tweetRes, true) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) result.content.add tweet From 4ccf350dc74416173cad16eb0a7dd4b49239fd2c Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 29 Aug 2023 23:45:18 +0200 Subject: [PATCH 038/302] Improve .tokens output --- src/tokens.nim | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/tokens.nim b/src/tokens.nim index bb9696c..a3af9bf 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -1,5 +1,5 @@ -#i hate begging for this too em SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, times, json, random, strutils, tables +#SPDX-License-Identifier: AGPL-3.0-only +import asyncdispatch, times, json, random, strutils, tables, sets import types # max requests at a time per account to avoid race conditions @@ -19,14 +19,16 @@ proc getPoolJson*(): JsonNode = list = newJObject() totalReqs = 0 totalPending = 0 - totalLimited = 0 + limited: HashSet[string] reqsPerApi: Table[string, int] let now = epochTime().int for account in accountPool: totalPending.inc(account.pending) - list[account.id] = %*{ + + var includeAccount = false + let accountJson = %*{ "apis": newJObject(), "pending": account.pending, } @@ -36,17 +38,18 @@ proc getPoolJson*(): JsonNode = apiStatus = account.apis[api] obj = %*{} - if apiStatus.limited: - obj["limited"] = %true - inc totalLimited - if apiStatus.reset > now.int: obj["remaining"] = %apiStatus.remaining if "remaining" notin obj and not apiStatus.limited: continue - list[account.id]["apis"][$api] = obj + if apiStatus.limited: + obj["limited"] = %true + limited.incl account.id + + accountJson{"apis", $api} = obj + includeAccount = true let maxReqs = @@ -64,9 +67,12 @@ proc getPoolJson*(): JsonNode = reqsPerApi[$api] = reqsPerApi.getOrDefault($api, 0) + reqs totalReqs.inc(reqs) + if includeAccount: + list[account.id] = accountJson + return %*{ "amount": accountPool.len, - "limited": totalLimited, + "limited": limited.card, "requests": totalReqs, "pending": totalPending, "apis": reqsPerApi, From 986b91ac733b36949bd9ac13c78409c4a98f83a0 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 29 Aug 2023 23:58:03 +0200 Subject: [PATCH 039/302] Handle ProtocolError and BadClientError equally --- src/http_pool.nim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/http_pool.nim b/src/http_pool.nim index b4e3cee..664e9a6 100644 --- a/src/http_pool.nim +++ b/src/http_pool.nim @@ -39,11 +39,8 @@ template use*(pool: HttpPool; heads: HttpHeaders; body: untyped): untyped = try: body - except ProtocolError: - # Twitter closed the connection, retry - body - except BadClientError: - # Twitter returned 503, we need a new client + except BadClientError, ProtocolError: + # Twitter returned 503 or closed the connection, we need a new client pool.release(c, true) badClient = false c = pool.acquire(heads) From 898b19b92f3121b2185ba3a59381be2ea8299f44 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 30 Aug 2023 03:04:22 +0200 Subject: [PATCH 040/302] Improve rate limit handling, minor refactor --- src/apiutils.nim | 67 ++++++++++++++++++++---------------------------- src/tokens.nim | 28 ++++++++++++-------- src/types.nim | 2 +- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 453b36a..37afded 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -61,13 +61,6 @@ proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = "DNT": "1" }) -template updateAccount() = - if resp.headers.hasKey(rlRemaining): - let - remaining = parseInt(resp.headers[rlRemaining]) - reset = parseInt(resp.headers[rlReset]) - account.setRateLimit(api, remaining, reset) - template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() @@ -89,28 +82,46 @@ template fetchImpl(result, fetchBody) {.dirty.} = badClient = true raise newException(BadClientError, "Bad client") + if resp.headers.hasKey(rlRemaining): + let + remaining = parseInt(resp.headers[rlRemaining]) + reset = parseInt(resp.headers[rlReset]) + account.setRateLimit(api, remaining, reset) + if result.len > 0: if resp.headers.getOrDefault("content-encoding") == "gzip": result = uncompress(result, dfGzip) - else: - echo "non-gzip body, url: ", url, ", body: ", result + + if result.startsWith("{\"errors"): + let errors = result.fromJson(Errors) + if errors in {expiredToken, badToken}: + echo "fetch error: ", errors + invalidate(account) + raise rateLimitError() + elif errors in {rateLimited}: + # rate limit hit, resets after 24 hours + setLimited(account, api) + raise rateLimitError() + elif result.startsWith("429 Too Many Requests"): + account.apis[api].remaining = 0 + # rate limit hit, resets after the 15 minute window + raise rateLimitError() fetchBody - release(account, used=true) - if resp.status == $Http400: raise newException(InternalError, $url) except InternalError as e: raise e except BadClientError as e: - release(account, used=true) + raise e + except OSError as e: raise e except Exception as e: echo "error: ", e.name, ", msg: ", e.msg, ", accountId: ", account.id, ", url: ", url - if "length" notin e.msg and "descriptor" notin e.msg: - release(account, invalid=true) raise rateLimitError() + finally: + release(account) proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = var body: string @@ -121,36 +132,14 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = echo resp.status, ": ", body, " --- url: ", url result = newJNull() - updateAccount() - let error = result.getError - if error in {invalidToken, badToken}: - echo "fetch error: ", result.getError - release(account, invalid=true) + if error in {expiredToken, badToken}: + echo "fetchBody error: ", error + invalidate(account) raise rateLimitError() - if body.startsWith("{\"errors"): - let errors = body.fromJson(Errors) - if errors in {invalidToken, badToken}: - echo "fetch error: ", errors - release(account, invalid=true) - raise rateLimitError() - elif errors in {rateLimited}: - account.apis[api].limited = true - account.apis[api].limitedAt = epochTime().int - echo "[accounts] rate limited, api: ", api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id - proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = fetchImpl result: if not (result.startsWith('{') or result.startsWith('[')): echo resp.status, ": ", result, " --- url: ", url result.setLen(0) - - updateAccount() - - if result.startsWith("{\"errors"): - let errors = result.fromJson(Errors) - if errors in {invalidToken, badToken}: - echo "fetch error: ", errors - release(account, invalid=true) - raise rateLimitError() diff --git a/src/tokens.nim b/src/tokens.nim index a3af9bf..c620bc7 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -11,7 +11,7 @@ var accountPool: seq[GuestAccount] enableLogging = false -template log(str) = +template log(str: varargs[string, `$`]) = if enableLogging: echo "[accounts] ", str proc getPoolJson*(): JsonNode = @@ -91,7 +91,7 @@ proc isLimited(account: GuestAccount; api: Api): bool = if limit.limited and (epochTime().int - limit.limitedAt) > dayInSeconds: account.apis[api].limited = false - log "resetting limit, api: " & $api & ", id: " & $account.id + log "resetting limit, api: ", api, ", id: ", account.id return limit.limited or (limit.remaining <= 10 and limit.reset > epochTime().int) else: @@ -100,15 +100,18 @@ proc isLimited(account: GuestAccount; api: Api): bool = proc isReady(account: GuestAccount; api: Api): bool = not (account.isNil or account.pending > maxConcurrentReqs or account.isLimited(api)) -proc release*(account: GuestAccount; used=false; invalid=false) = +proc invalidate*(account: var GuestAccount) = if account.isNil: return - if invalid: - log "discarding invalid account: " & account.id + log "invalidating expired account: ", account.id - let idx = accountPool.find(account) - if idx > -1: accountPool.delete(idx) - elif used: - dec account.pending + # TODO: This isn't sufficient, but it works for now + let idx = accountPool.find(account) + if idx > -1: accountPool.delete(idx) + account = nil + +proc release*(account: GuestAccount; invalid=false) = + if account.isNil: return + dec account.pending proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = for i in 0 ..< accountPool.len: @@ -119,9 +122,14 @@ proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = if not result.isNil and result.isReady(api): inc result.pending else: - log "no accounts available for API: " & $api + log "no accounts available for API: ", api raise rateLimitError() +proc setLimited*(account: GuestAccount; api: Api) = + account.apis[api].limited = true + account.apis[api].limitedAt = epochTime().int + log "rate limited, api: ", api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id + proc setRateLimit*(account: GuestAccount; api: Api; remaining, reset: int) = # avoid undefined behavior in race conditions if api in account.apis: diff --git a/src/types.nim b/src/types.nim index fcb24c0..8a7a66e 100644 --- a/src/types.nim +++ b/src/types.nim @@ -56,7 +56,7 @@ type userNotFound = 50 suspended = 63 rateLimited = 88 - invalidToken = 89 + expiredToken = 89 listIdOrSlug = 112 tweetNotFound = 144 tweetNotAuthorized = 179 From 37b58a5a7e52b897c565677016c8d7bf56494bc4 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 30 Aug 2023 03:43:49 +0200 Subject: [PATCH 041/302] Fix accounts logging --- src/tokens.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokens.nim b/src/tokens.nim index c620bc7..3628ef6 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -12,7 +12,7 @@ var enableLogging = false template log(str: varargs[string, `$`]) = - if enableLogging: echo "[accounts] ", str + if enableLogging: echo "[accounts] ", str.join("") proc getPoolJson*(): JsonNode = var From 282ce8b0e9088a0a215116ce234c0b46f1e0d55f Mon Sep 17 00:00:00 2001 From: Zed Date: Thu, 31 Aug 2023 01:29:54 +0200 Subject: [PATCH 042/302] Add 429 logging --- src/apiutils.nim | 1 + src/tokens.nim | 2 +- src/types.nim | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 37afded..e5b9be2 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -103,6 +103,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = setLimited(account, api) raise rateLimitError() elif result.startsWith("429 Too Many Requests"): + echo "[accounts] 429 error, API: ", api, ", account: ", account.id account.apis[api].remaining = 0 # rate limit hit, resets after the 15 minute window raise rateLimitError() diff --git a/src/tokens.nim b/src/tokens.nim index 3628ef6..a4ebe7f 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -109,7 +109,7 @@ proc invalidate*(account: var GuestAccount) = if idx > -1: accountPool.delete(idx) account = nil -proc release*(account: GuestAccount; invalid=false) = +proc release*(account: GuestAccount) = if account.isNil: return dec account.pending diff --git a/src/types.nim b/src/types.nim index 8a7a66e..4cacc4b 100644 --- a/src/types.nim +++ b/src/types.nim @@ -40,8 +40,6 @@ type id*: string oauthToken*: string oauthSecret*: string - # init*: Time - lastUse*: Time pending*: int apis*: Table[Api, RateLimit] From 82beb5da8c60a981f0ce61fc99c8f23dff3b3865 Mon Sep 17 00:00:00 2001 From: Zed Date: Thu, 31 Aug 2023 01:31:27 +0200 Subject: [PATCH 043/302] Add empty oauth token logging --- src/apiutils.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apiutils.nim b/src/apiutils.nim index e5b9be2..0b1db26 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -67,6 +67,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = var account = await getGuestAccount(api) if account.oauthToken.len == 0: + echo "[accounts] Empty oauth token, account: ", account.id raise rateLimitError() try: From 84dcf4907907fc59a60e7bf2a7db738789e3e868 Mon Sep 17 00:00:00 2001 From: Zed Date: Thu, 31 Aug 2023 05:06:47 +0200 Subject: [PATCH 044/302] Fix negative pending requests bug --- src/tokens.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tokens.nim b/src/tokens.nim index a4ebe7f..3e20597 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -116,7 +116,6 @@ proc release*(account: GuestAccount) = proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = for i in 0 ..< accountPool.len: if result.isReady(api): break - release(result) result = accountPool.sample() if not result.isNil and result.isReady(api): From b8fe212e941ebb27a39b0900cfe271d700738f22 Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 1 Sep 2023 21:37:34 +0200 Subject: [PATCH 045/302] Add media proxying error logging --- src/routes/media.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/media.nim b/src/routes/media.nim index e63a0f8..d335c97 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -37,6 +37,7 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = try: let res = await client.get(url) if res.status != "200 OK": + echo "[media] Proxying media failed, status: $1, url: $2, body: $3" % [res.status, url, await res.body] return Http404 let hashed = $hash(url) @@ -65,6 +66,7 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = await request.client.send(data) data.setLen 0 except HttpRequestError, ProtocolError, OSError: + echo "[media] Proxying media exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url] result = Http404 finally: client.close() From 4250245263b77bd2b0ecd9a31c0e4000d8e2e6f7 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 2 Sep 2023 07:28:56 +0200 Subject: [PATCH 046/302] Shorten media proxy error log --- src/routes/media.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/media.nim b/src/routes/media.nim index d335c97..eacd1f8 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -37,7 +37,7 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = try: let res = await client.get(url) if res.status != "200 OK": - echo "[media] Proxying media failed, status: $1, url: $2, body: $3" % [res.status, url, await res.body] + echo "[media] Proxying failed, status: $1, url: $2" % [res.status, url] return Http404 let hashed = $hash(url) @@ -66,7 +66,7 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = await request.client.send(data) data.setLen 0 except HttpRequestError, ProtocolError, OSError: - echo "[media] Proxying media exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url] + echo "[media] Proxying exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url] result = Http404 finally: client.close() From fcd74e8048362fcf8284871ee067099e8de28a89 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 2 Sep 2023 08:15:58 +0200 Subject: [PATCH 047/302] Retry rate limited requests with different account --- src/apiutils.nim | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 0b1db26..9ac101e 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -120,28 +120,38 @@ template fetchImpl(result, fetchBody) {.dirty.} = except OSError as e: raise e except Exception as e: - echo "error: ", e.name, ", msg: ", e.msg, ", accountId: ", account.id, ", url: ", url + let id = if account.isNil: "null" else: account.id + echo "error: ", e.name, ", msg: ", e.msg, ", accountId: ", id, ", url: ", url raise rateLimitError() finally: release(account) -proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = - var body: string - fetchImpl body: - if body.startsWith('{') or body.startsWith('['): - result = parseJson(body) - else: - echo resp.status, ": ", body, " --- url: ", url - result = newJNull() +template retry(bod) = + try: + bod + except RateLimitError: + echo "[accounts] Rate limited, retrying ", api, " request..." + bod - let error = result.getError - if error in {expiredToken, badToken}: - echo "fetchBody error: ", error - invalidate(account) - raise rateLimitError() +proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = + retry: + var body: string + fetchImpl body: + if body.startsWith('{') or body.startsWith('['): + result = parseJson(body) + else: + echo resp.status, ": ", body, " --- url: ", url + result = newJNull() + + let error = result.getError + if error in {expiredToken, badToken}: + echo "fetchBody error: ", error + invalidate(account) + raise rateLimitError() proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = - fetchImpl result: - if not (result.startsWith('{') or result.startsWith('[')): - echo resp.status, ": ", result, " --- url: ", url - result.setLen(0) + retry: + fetchImpl result: + if not (result.startsWith('{') or result.startsWith('[')): + echo resp.status, ": ", result, " --- url: ", url + result.setLen(0) From 14f9a092d832c0aaf7eb4900ab36a76c6653c364 Mon Sep 17 00:00:00 2001 From: Zed Date: Thu, 14 Sep 2023 23:35:41 +0000 Subject: [PATCH 048/302] Fix crash on missing quote tweet data crash --- src/parser.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/parser.nim b/src/parser.nim index 914c038..776f176 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -324,6 +324,9 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet = of "TweetWithVisibilityResults": return parseGraphTweet(js{"tweet"}, isLegacy) + if not js.hasKey("legacy"): + return Tweet() + var jsCard = copy(js{if isLegacy: "card" else: "tweet_card", "legacy"}) if jsCard.kind != JNull: var values = newJObject() From 7abcb489f4176532322669e97eb2503a78b2bbc2 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 18 Sep 2023 17:15:09 +0000 Subject: [PATCH 049/302] Increase photo rail cache ttl --- src/redis_cache.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis_cache.nim b/src/redis_cache.nim index 2387a42..a8b5ff8 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -85,7 +85,7 @@ proc cache*(data: List) {.async.} = await setEx(data.listKey, listCacheTime, compress(toFlatty(data))) proc cache*(data: PhotoRail; name: string) {.async.} = - await setEx("pr:" & toLower(name), baseCacheTime, compress(toFlatty(data))) + await setEx("pr:" & toLower(name), baseCacheTime * 2, compress(toFlatty(data))) proc cache*(data: User) {.async.} = if data.username.len == 0: return From 7d147899103bf1a21b291641fd4230a02ba48fab Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 18 Sep 2023 18:24:23 +0000 Subject: [PATCH 050/302] Improve guest accounts loading, add JSONL support --- .gitignore | 1 + src/experimental/parser/guestaccount.nim | 20 ++++++++++++++++++++ src/experimental/types/guestaccount.nim | 4 ++++ src/nitter.nim | 4 +--- src/tokens.nim | 23 +++++++++++++++-------- 5 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 src/experimental/parser/guestaccount.nim create mode 100644 src/experimental/types/guestaccount.nim diff --git a/.gitignore b/.gitignore index d43cc3f..ea520dc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ nitter /public/css/style.css /public/md/*.html nitter.conf +guest_accounts.json* dump.rdb diff --git a/src/experimental/parser/guestaccount.nim b/src/experimental/parser/guestaccount.nim new file mode 100644 index 0000000..4d8ff47 --- /dev/null +++ b/src/experimental/parser/guestaccount.nim @@ -0,0 +1,20 @@ +import jsony +import ../types/guestaccount +from ../../types import GuestAccount + +proc toGuestAccount(account: RawAccount): GuestAccount = + let id = account.oauthToken[0 ..< account.oauthToken.find('-')] + result = GuestAccount( + id: id, + oauthToken: account.oauthToken, + oauthSecret: account.oauthTokenSecret + ) + +proc parseGuestAccount*(raw: string): GuestAccount = + let rawAccount = raw.fromJson(RawAccount) + result = rawAccount.toGuestAccount + +proc parseGuestAccounts*(path: string): seq[GuestAccount] = + let rawAccounts = readFile(path).fromJson(seq[RawAccount]) + for account in rawAccounts: + result.add account.toGuestAccount diff --git a/src/experimental/types/guestaccount.nim b/src/experimental/types/guestaccount.nim new file mode 100644 index 0000000..244edb3 --- /dev/null +++ b/src/experimental/types/guestaccount.nim @@ -0,0 +1,4 @@ +type + RawAccount* = object + oauthToken*: string + oauthTokenSecret*: string diff --git a/src/nitter.nim b/src/nitter.nim index 4a4ec13..1b4862b 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -3,7 +3,6 @@ import asyncdispatch, strformat, logging from net import Port from htmlgen import a from os import getEnv -from json import parseJson import jester @@ -21,9 +20,8 @@ let (cfg, fullCfg) = getConfig(configPath) accountsPath = getEnv("NITTER_ACCOUNTS_FILE", "./guest_accounts.json") - accounts = parseJson(readFile(accountsPath)) -initAccountPool(cfg, parseJson(readFile(accountsPath))) +initAccountPool(cfg, accountsPath) if not cfg.enableDebug: # Silence Jester's query warning diff --git a/src/tokens.nim b/src/tokens.nim index 3e20597..b8a50e3 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -1,6 +1,7 @@ #SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, times, json, random, strutils, tables, sets +import asyncdispatch, times, json, random, strutils, tables, sets, os import types +import experimental/parser/guestaccount # max requests at a time per account to avoid race conditions const @@ -141,12 +142,18 @@ proc setRateLimit*(account: GuestAccount; api: Api; remaining, reset: int) = account.apis[api] = RateLimit(remaining: remaining, reset: reset) -proc initAccountPool*(cfg: Config; accounts: JsonNode) = +proc initAccountPool*(cfg: Config; path: string) = enableLogging = cfg.enableDebug - for account in accounts: - accountPool.add GuestAccount( - id: account{"user", "id_str"}.getStr, - oauthToken: account{"oauth_token"}.getStr, - oauthSecret: account{"oauth_token_secret"}.getStr, - ) + let jsonlPath = if path.endsWith(".json"): (path & 'l') else: path + + if fileExists(jsonlPath): + log "Parsing JSONL guest accounts file: ", jsonlPath + for line in jsonlPath.lines: + accountPool.add parseGuestAccount(line) + elif fileExists(path): + log "Parsing JSON guest accounts file: ", path + accountPool = parseGuestAccounts(path) + else: + echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests." + quit 1 From 537af7fd5e846e614315fbbff8741d3c7f0c74ba Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 19 Sep 2023 01:29:41 +0000 Subject: [PATCH 051/302] Improve Liberapay css for Firefox compatibility --- src/sass/navbar.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sass/navbar.scss b/src/sass/navbar.scss index cf9c80e..47a8765 100644 --- a/src/sass/navbar.scss +++ b/src/sass/navbar.scss @@ -70,8 +70,9 @@ nav { .lp { height: 14px; - margin-top: 2px; - display: block; + display: inline-block; + position: relative; + top: 2px; fill: var(--fg_nav); &:hover { From 735b30c2da336cc57be2b98c48a6f7e826fdaed0 Mon Sep 17 00:00:00 2001 From: LS <66217791+DrSocket@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:13:06 +0100 Subject: [PATCH 052/302] fix(nitter): add graphql user search (#1047) * fix(nitter): add graphql user search * fix(nitter): rm gitignore 2nd guest_accounts * fix(nitter): keep query from user search in result. remove personal mods * fix(nitter): removce useless line gitignore --- src/api.nim | 28 ++++++++++++++++------------ src/consts.nim | 3 +-- src/parser.nim | 24 +++++++++++++++--------- src/routes/search.nim | 2 +- src/tokens.nim | 1 - src/types.nim | 1 - 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/api.nim b/src/api.nim index 4ac999c..d6a4564 100644 --- a/src/api.nim +++ b/src/api.nim @@ -112,25 +112,29 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = if after.len > 0: variables["cursor"] = % after let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} - result = parseGraphSearch(await fetch(url, Api.search), after) + result = parseGraphSearch[Tweets](await fetch(url, Api.search), after) result.query = query -proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} = +proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) - let - page = if page.len == 0: "1" else: page - url = userSearch ? genParams({"q": query.text, "skip_status": "1", "page": page}) - js = await fetchRaw(url, Api.userSearch) - - result = parseUsers(js) + var + variables = %*{ + "rawQuery": query.text, + "count": 20, + "product": "People", + "withDownvotePerspective": false, + "withReactionsMetadata": false, + "withReactionsPerspective": false + } + if after.len > 0: + variables["cursor"] = % after + result.beginning = false + let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} + result = parseGraphSearch[User](await fetch(url, Api.search), after) result.query = query - if page.len == 0: - result.bottom = "2" - elif page.allCharsInSet(Digits): - result.bottom = $(parseInt(page) + 1) proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return diff --git a/src/consts.nim b/src/consts.nim index 96cea47..d3a3d80 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,7 +9,6 @@ const activate* = $(api / "1.1/guest/activate.json") photoRail* = api / "1.1/statuses/media_timeline.json" - userSearch* = api / "1.1/users/search.json" graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" @@ -35,7 +34,7 @@ const "include_user_entities": "1", "include_ext_reply_count": "1", "include_ext_is_blue_verified": "1", - #"include_ext_verified_type": "1", + # "include_ext_verified_type": "1", "include_ext_media_color": "0", "cards_platform": "Web-13", "tweet_mode": "extended", diff --git a/src/parser.nim b/src/parser.nim index 776f176..cebf6f1 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -443,8 +443,8 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = tweet.id = parseBiggestInt(entryId) result.pinned = some tweet -proc parseGraphSearch*(js: JsonNode; after=""): Timeline = - result = Timeline(beginning: after.len == 0) +proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = + result = Result[T](beginning: after.len == 0) let instructions = js{"data", "search_by_raw_query", "search_timeline", "timeline", "instructions"} if instructions.len == 0: @@ -455,13 +455,19 @@ proc parseGraphSearch*(js: JsonNode; after=""): Timeline = if typ == "TimelineAddEntries": for e in instruction{"entries"}: let entryId = e{"entryId"}.getStr - if entryId.startsWith("tweet"): - with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetRes, true) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) - result.content.add tweet - elif entryId.startsWith("cursor-bottom"): + when T is Tweets: + if entryId.startsWith("tweet"): + with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: + let tweet = parseGraphTweet(tweetRes) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) + result.content.add tweet + elif T is User: + if entryId.startsWith("user"): + with userRes, e{"content", "itemContent"}: + result.content.add parseGraphUser(userRes) + + if entryId.startsWith("cursor-bottom"): result.bottom = e{"content", "value"}.getStr elif typ == "TimelineReplaceEntry": if instruction{"entry_id_to_replace"}.getStr.startsWith("cursor-bottom"): diff --git a/src/routes/search.nim b/src/routes/search.nim index 676229e..e9f991d 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -29,7 +29,7 @@ proc createSearchRouter*(cfg: Config) = redirect("/" & q) var users: Result[User] try: - users = await getUserSearch(query, getCursor()) + users = await getGraphUserSearch(query, getCursor()) except InternalError: users = Result[User](beginning: true, query: query) resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title) diff --git a/src/tokens.nim b/src/tokens.nim index b8a50e3..ca74ddc 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -62,7 +62,6 @@ proc getPoolJson*(): JsonNode = Api.userRestId, Api.userScreenName, Api.tweetResult, Api.list, Api.listTweets, Api.listMembers, Api.listBySlug: 500 - of Api.userSearch: 900 reqs = maxReqs - apiStatus.remaining reqsPerApi[$api] = reqsPerApi.getOrDefault($api, 0) + reqs diff --git a/src/types.nim b/src/types.nim index 4cacc4b..3f5f8ac 100644 --- a/src/types.nim +++ b/src/types.nim @@ -19,7 +19,6 @@ type tweetResult photoRail search - userSearch list listBySlug listMembers From 32e3469e3a580464a79c6b2b6bfbaa6757bd8cfe Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 05:53:55 +0000 Subject: [PATCH 053/302] Fix multi-user timelines --- src/query.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query.nim b/src/query.nim index d128f6f..06e1da2 100644 --- a/src/query.nim +++ b/src/query.nim @@ -60,7 +60,7 @@ proc genQueryParam*(query: Query): string = param &= "OR " if query.fromUser.len > 0 and query.kind in {posts, media}: - param &= "filter:self_threads OR-filter:replies " + param &= "filter:self_threads OR -filter:replies " if "nativeretweets" notin query.excludes: param &= "include:nativeretweets " From edad09f4c934fb44da008256994ba40347b0f9c9 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 08:31:51 +0000 Subject: [PATCH 054/302] Update nimcrypto and jsony --- nitter.nimble | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nitter.nimble b/nitter.nimble index e6a1909..20aab81 100644 --- a/nitter.nimble +++ b/nitter.nimble @@ -14,7 +14,7 @@ requires "nim >= 1.4.8" requires "jester#baca3f" requires "karax#5cf360c" requires "sass#7dfdd03" -requires "nimcrypto#4014ef9" +requires "nimcrypto#a079df9" requires "markdown#158efe3" requires "packedjson#9e6fbb6" requires "supersnappy#6c94198" @@ -22,7 +22,7 @@ requires "redpool#8b7c1db" requires "https://github.com/zedeus/redis#d0a0e6f" requires "zippy#ca5989a" requires "flatty#e668085" -requires "jsony#ea811be" +requires "jsony#1de1f08" requires "oauth#b8c163b" # Tasks From 089275826cae70c30183e1bd21b49538e43bd4d9 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 11:32:21 +0000 Subject: [PATCH 055/302] Bump minimum Nim version --- config.nims | 7 +------ nitter.nimble | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/config.nims b/config.nims index b7e52d0..4a7af27 100644 --- a/config.nims +++ b/config.nims @@ -7,12 +7,7 @@ # disable annoying warnings warning("GcUnsafe2", off) +warning("HoleEnumConv", off) hint("XDeclaredButNotUsed", off) hint("XCannotRaiseY", off) hint("User", off) - -const - nimVersion = (major: NimMajor, minor: NimMinor, patch: NimPatch) - -when nimVersion >= (1, 6, 0): - warning("HoleEnumConv", off) diff --git a/nitter.nimble b/nitter.nimble index 20aab81..37f9229 100644 --- a/nitter.nimble +++ b/nitter.nimble @@ -10,7 +10,7 @@ bin = @["nitter"] # Dependencies -requires "nim >= 1.4.8" +requires "nim >= 1.6.10" requires "jester#baca3f" requires "karax#5cf360c" requires "sass#7dfdd03" From 412055864940a79d3203aa41568c6ed67c5bb5f8 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 12:04:32 +0000 Subject: [PATCH 056/302] Replace /.tokens with /.health and /.accounts --- nitter.example.conf | 2 +- src/apiutils.nim | 4 +- src/{tokens.nim => auth.nim} | 113 +++++++++++++++-------- src/experimental/parser/guestaccount.nim | 3 +- src/nitter.nim | 2 +- src/routes/debug.nim | 9 +- src/types.nim | 2 +- 7 files changed, 86 insertions(+), 49 deletions(-) rename src/{tokens.nim => auth.nim} (69%) diff --git a/nitter.example.conf b/nitter.example.conf index 0d4deb7..f0b4214 100644 --- a/nitter.example.conf +++ b/nitter.example.conf @@ -23,7 +23,7 @@ redisMaxConnections = 30 hmacKey = "secretkey" # random key for cryptographic signing of video urls base64Media = false # use base64 encoding for proxied media urls enableRSS = true # set this to false to disable RSS feeds -enableDebug = false # enable request logs and debug endpoints (/.tokens) +enableDebug = false # enable request logs and debug endpoints (/.accounts) proxy = "" # http/https url, SOCKS proxies are not supported proxyAuth = "" tokenCount = 10 diff --git a/src/apiutils.nim b/src/apiutils.nim index 9ac101e..1ff05eb 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only import httpclient, asyncdispatch, options, strutils, uri, times, math, tables import jsony, packedjson, zippy, oauth1 -import types, tokens, consts, parserutils, http_pool +import types, auth, consts, parserutils, http_pool import experimental/types/common const @@ -120,7 +120,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = except OSError as e: raise e except Exception as e: - let id = if account.isNil: "null" else: account.id + let id = if account.isNil: "null" else: $account.id echo "error: ", e.name, ", msg: ", e.msg, ", accountId: ", id, ", url: ", url raise rateLimitError() finally: diff --git a/src/tokens.nim b/src/auth.nim similarity index 69% rename from src/tokens.nim rename to src/auth.nim index ca74ddc..560fb84 100644 --- a/src/tokens.nim +++ b/src/auth.nim @@ -1,5 +1,5 @@ #SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, times, json, random, strutils, tables, sets, os +import asyncdispatch, times, json, random, strutils, tables, intsets, os import types import experimental/parser/guestaccount @@ -7,6 +7,21 @@ import experimental/parser/guestaccount const maxConcurrentReqs = 2 dayInSeconds = 24 * 60 * 60 + apiMaxReqs: Table[Api, int] = { + Api.search: 50, + Api.tweetDetail: 150, + Api.photoRail: 180, + Api.userTweets: 500, + Api.userTweetsAndReplies: 500, + Api.userMedia: 500, + Api.userRestId: 500, + Api.userScreenName: 500, + Api.tweetResult: 500, + Api.list: 500, + Api.listTweets: 500, + Api.listMembers: 500, + Api.listBySlug: 500 + }.toTable var accountPool: seq[GuestAccount] @@ -15,20 +30,64 @@ var template log(str: varargs[string, `$`]) = if enableLogging: echo "[accounts] ", str.join("") -proc getPoolJson*(): JsonNode = - var - list = newJObject() - totalReqs = 0 - totalPending = 0 - limited: HashSet[string] - reqsPerApi: Table[string, int] - +proc getAccountPoolHealth*(): JsonNode = let now = epochTime().int - for account in accountPool: - totalPending.inc(account.pending) + var + totalReqs = 0 + limited: IntSet + reqsPerApi: Table[string, int] + oldest = now + newest = 0 + average = 0 - var includeAccount = false + for account in accountPool: + # Twitter snowflake conversion + let created = ((account.id shr 22) + 1288834974657) div 1000 + + if created > newest: + newest = created + if created < oldest: + oldest = created + average.inc created + + for api in account.apis.keys: + let + apiStatus = account.apis[api] + reqs = apiMaxReqs[api] - apiStatus.remaining + + reqsPerApi.mgetOrPut($api, 0).inc reqs + totalReqs.inc reqs + + if apiStatus.limited: + limited.incl account.id + + if accountPool.len > 0: + average = average div accountPool.len + else: + oldest = 0 + average = 0 + + return %*{ + "accounts": %*{ + "total": accountPool.len, + "active": accountPool.len - limited.card, + "limited": limited.card, + "oldest": $fromUnix(oldest), + "newest": $fromUnix(newest), + "average": $fromUnix(average) + }, + "requests": %*{ + "total": totalReqs, + "apis": reqsPerApi + } + } + +proc getAccountPoolDebug*(): JsonNode = + let now = epochTime().int + var list = newJObject() + + for account in accountPool: let accountJson = %*{ "apis": newJObject(), "pending": account.pending, @@ -47,37 +106,11 @@ proc getPoolJson*(): JsonNode = if apiStatus.limited: obj["limited"] = %true - limited.incl account.id accountJson{"apis", $api} = obj - includeAccount = true + list[$account.id] = accountJson - let - maxReqs = - case api - of Api.search: 50 - of Api.tweetDetail: 150 - of Api.photoRail: 180 - of Api.userTweets, Api.userTweetsAndReplies, Api.userMedia, - Api.userRestId, Api.userScreenName, - Api.tweetResult, - Api.list, Api.listTweets, Api.listMembers, Api.listBySlug: 500 - reqs = maxReqs - apiStatus.remaining - - reqsPerApi[$api] = reqsPerApi.getOrDefault($api, 0) + reqs - totalReqs.inc(reqs) - - if includeAccount: - list[account.id] = accountJson - - return %*{ - "amount": accountPool.len, - "limited": limited.card, - "requests": totalReqs, - "pending": totalPending, - "apis": reqsPerApi, - "accounts": list - } + return %list proc rateLimitError*(): ref RateLimitError = newException(RateLimitError, "rate limited") diff --git a/src/experimental/parser/guestaccount.nim b/src/experimental/parser/guestaccount.nim index 4d8ff47..f7e6d34 100644 --- a/src/experimental/parser/guestaccount.nim +++ b/src/experimental/parser/guestaccount.nim @@ -1,3 +1,4 @@ +import std/strutils import jsony import ../types/guestaccount from ../../types import GuestAccount @@ -5,7 +6,7 @@ from ../../types import GuestAccount proc toGuestAccount(account: RawAccount): GuestAccount = let id = account.oauthToken[0 ..< account.oauthToken.find('-')] result = GuestAccount( - id: id, + id: parseBiggestInt(id), oauthToken: account.oauthToken, oauthSecret: account.oauthTokenSecret ) diff --git a/src/nitter.nim b/src/nitter.nim index 1b4862b..dfc1dfd 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -6,7 +6,7 @@ from os import getEnv import jester -import types, config, prefs, formatters, redis_cache, http_pool, tokens +import types, config, prefs, formatters, redis_cache, http_pool, auth import views/[general, about] import routes/[ preferences, timeline, status, media, search, rss, list, debug, diff --git a/src/routes/debug.nim b/src/routes/debug.nim index 192786e..895a285 100644 --- a/src/routes/debug.nim +++ b/src/routes/debug.nim @@ -1,10 +1,13 @@ # SPDX-License-Identifier: AGPL-3.0-only import jester import router_utils -import ".."/[tokens, types] +import ".."/[auth, types] proc createDebugRouter*(cfg: Config) = router debug: - get "/.tokens": + get "/.health": + respJson getAccountPoolHealth() + + get "/.accounts": cond cfg.enableDebug - respJson getPoolJson() + respJson getAccountPoolDebug() diff --git a/src/types.nim b/src/types.nim index 3f5f8ac..3b0d55c 100644 --- a/src/types.nim +++ b/src/types.nim @@ -36,7 +36,7 @@ type limitedAt*: int GuestAccount* = ref object - id*: string + id*: BiggestInt oauthToken*: string oauthSecret*: string pending*: int From b62d73dbd373f08af07c7a79efcd790d3bc1a49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20=28perso=29?= <4016501+unixfox@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:33:08 +0100 Subject: [PATCH 057/302] nim version min require + update dockerfile arm (#1053) --- Dockerfile.arm64 | 6 +++--- nitter.nimble | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 6cd6744..fbad812 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,7 +1,7 @@ -FROM alpine:3.17 as nim +FROM alpine:3.18 as nim LABEL maintainer="setenforce@protonmail.com" -RUN apk --no-cache add gcc git libc-dev libsass-dev "nim=1.6.8-r0" nimble pcre +RUN apk --no-cache add gcc git libc-dev libsass-dev "nim=1.6.14-r0" nimble pcre WORKDIR /src/nitter @@ -13,7 +13,7 @@ RUN nimble build -d:danger -d:lto -d:strip \ && nimble scss \ && nimble md -FROM alpine:3.17 +FROM alpine:3.18 WORKDIR /src/ RUN apk --no-cache add ca-certificates pcre openssl1.1-compat COPY --from=nim /src/nitter/nitter ./ diff --git a/nitter.nimble b/nitter.nimble index 7771b31..3a490a5 100644 --- a/nitter.nimble +++ b/nitter.nimble @@ -10,7 +10,7 @@ bin = @["nitter"] # Dependencies -requires "nim >= 1.4.8" +requires "nim >= 1.6.10" requires "jester#baca3f" requires "karax#5cf360c" requires "sass#7dfdd03" From b8103cf5010ea515c3b9722dbda19f374c6ebdaa Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 23:02:45 +0000 Subject: [PATCH 058/302] Fix compilation on Nim 1.6.x --- src/auth.nim | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 560fb84..97ee301 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -1,5 +1,5 @@ #SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, times, json, random, strutils, tables, intsets, os +import std/[asyncdispatch, times, json, random, strutils, tables, packedsets, os] import types import experimental/parser/guestaccount @@ -35,21 +35,21 @@ proc getAccountPoolHealth*(): JsonNode = var totalReqs = 0 - limited: IntSet + limited: PackedSet[BiggestInt] reqsPerApi: Table[string, int] - oldest = now - newest = 0 - average = 0 + oldest = now.int64 + newest = 0'i64 + average = 0'i64 for account in accountPool: # Twitter snowflake conversion - let created = ((account.id shr 22) + 1288834974657) div 1000 + let created = int64(((account.id shr 22) + 1288834974657) div 1000) if created > newest: newest = created if created < oldest: oldest = created - average.inc created + average += created for api in account.apis.keys: let From 60a82563da979f81c24eb51b7ae031f4086c03fd Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 23:46:24 +0000 Subject: [PATCH 059/302] Run tests on multiple Nim versions --- .github/workflows/run-tests.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 37979cb..0af20a3 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,6 +11,12 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + nim: + - "1.6.10" + - "1.6.x" + - "2.0.x" steps: - uses: actions/checkout@v3 with: @@ -28,7 +34,8 @@ jobs: cache: "pip" - uses: jiro4989/setup-nim-action@v1 with: - nim-version: "1.x" + nim-version: ${{ matrix.nim }} + repo-token: ${{ secrets.GITHUB_TOKEN }} - run: nimble build -d:release -Y - run: pip install seleniumbase - run: seleniumbase install chromedriver From b930a3d5bf4b0ce679b5086ae712b65f279f1a49 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 31 Oct 2023 23:54:11 +0000 Subject: [PATCH 060/302] Fix guest accounts CI setup --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0af20a3..3fbe64a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -50,6 +50,6 @@ jobs: env: GUEST_ACCOUNTS: ${{ secrets.GUEST_ACCOUNTS }} run: | - echo $GUEST_ACCOUNTS > ./guest_accounts.json + echo $GUEST_ACCOUNTS > ./guest_accounts.jsonl ./nitter & pytest -n4 tests From 33bad37128abd3987e823d32e2ef90ea91f8e4f5 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 01:24:51 +0000 Subject: [PATCH 061/302] Fix guest accounts CI setup attempt 2 --- .github/workflows/run-tests.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3fbe64a..12adb1f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -44,12 +44,11 @@ jobs: run: | sudo apt install libsass-dev -y cp nitter.example.conf nitter.conf + sed -i 's/enableDebug = false/enableDebug = true/g' nimble md nimble scss + echo "${{ env.GUEST_ACCOUNTS }}" > ./guest_accounts.jsonl - name: Run tests - env: - GUEST_ACCOUNTS: ${{ secrets.GUEST_ACCOUNTS }} run: | - echo $GUEST_ACCOUNTS > ./guest_accounts.jsonl ./nitter & pytest -n4 tests From 006b91c90391e972b8e1377df18f7f1da5718c87 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 04:04:45 +0000 Subject: [PATCH 062/302] Prevent annoying warnings on devel --- src/parser.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parser.nim b/src/parser.nim index cebf6f1..fcee13f 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -323,6 +323,8 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet = return Tweet(text: "You're unable to view this Tweet because it's only available to the Subscribers of the account owner.") of "TweetWithVisibilityResults": return parseGraphTweet(js{"tweet"}, isLegacy) + else: + discard if not js.hasKey("legacy"): return Tweet() From b0b335106d992acdca2d82da82fe9dee89044404 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 04:06:42 +0000 Subject: [PATCH 063/302] Fix missing CI file argument --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 12adb1f..f9df235 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -44,7 +44,7 @@ jobs: run: | sudo apt install libsass-dev -y cp nitter.example.conf nitter.conf - sed -i 's/enableDebug = false/enableDebug = true/g' + sed -i 's/enableDebug = false/enableDebug = true/g' nitter.conf nimble md nimble scss echo "${{ env.GUEST_ACCOUNTS }}" > ./guest_accounts.jsonl From 58e73a14c576b275dde9f9af8d4f31f0f6202255 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 04:13:22 +0000 Subject: [PATCH 064/302] Fix guest accounts CI setup attempt 3 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f9df235..948d284 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -47,7 +47,7 @@ jobs: sed -i 's/enableDebug = false/enableDebug = true/g' nitter.conf nimble md nimble scss - echo "${{ env.GUEST_ACCOUNTS }}" > ./guest_accounts.jsonl + echo "${{ vars.GUEST_ACCOUNTS }}" > ./guest_accounts.jsonl - name: Run tests run: | ./nitter & From 1d20bd01cb9db816e47b1911f9836c442f2726c9 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 04:16:26 +0000 Subject: [PATCH 065/302] Remove redundant "active" field from /.health --- src/auth.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/src/auth.nim b/src/auth.nim index 97ee301..8be435c 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -71,7 +71,6 @@ proc getAccountPoolHealth*(): JsonNode = return %*{ "accounts": %*{ "total": accountPool.len, - "active": accountPool.len - limited.card, "limited": limited.card, "oldest": $fromUnix(oldest), "newest": $fromUnix(newest), From 7b3fcdc622628febc306cf9c0d4a33c493d690db Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 04:19:10 +0000 Subject: [PATCH 066/302] Fix guest accounts CI setup attempt 4 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 948d284..76af329 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -47,7 +47,7 @@ jobs: sed -i 's/enableDebug = false/enableDebug = true/g' nitter.conf nimble md nimble scss - echo "${{ vars.GUEST_ACCOUNTS }}" > ./guest_accounts.jsonl + echo '${{ secrets.GUEST_ACCOUNTS }}' > ./guest_accounts.jsonl - name: Run tests run: | ./nitter & From 623424f5160b3e99e6e4b9675c8705f87fbf9d72 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 04:52:44 +0000 Subject: [PATCH 067/302] Fix outdated test --- tests/test_card.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_card.py b/tests/test_card.py index 733bd40..8da91a2 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -21,9 +21,9 @@ card = [ no_thumb = [ ['FluentAI/status/1116417904831029248', - 'Amazon’s Alexa isn’t just AI — thousands of humans are listening', - 'One of the only ways to improve Alexa is to have human beings check it for errors', - 'theverge.com'], + 'LinkedIn', + 'This link will take you to a page that’s not on LinkedIn', + 'lnkd.in'], ['Thom_Wolf/status/1122466524860702729', 'facebookresearch/fairseq', From e1838e093335fab02f36649bba7b65aa4420993f Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 05:09:21 +0000 Subject: [PATCH 068/302] Move CI workflow to buildjet --- .github/workflows/run-tests.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 76af329..ee28e33 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,24 +10,26 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: buildjet-2vcpu-ubuntu-2204 strategy: matrix: nim: - "1.6.10" - "1.6.x" - "2.0.x" + - "devel" steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Cache nimble id: cache-nimble - uses: actions/cache@v3 + uses: buildjet/cache@v3 with: path: ~/.nimble - key: nimble-${{ hashFiles('*.nimble') }} - restore-keys: "nimble-" + key: ${{ matrix.nim }}-nimble-${{ hashFiles('*.nimble') }} + restore-keys: | + ${{ matrix.nim }}-nimble- - uses: actions/setup-python@v4 with: python-version: "3.10" @@ -51,4 +53,4 @@ jobs: - name: Run tests run: | ./nitter & - pytest -n4 tests + pytest -n8 tests From 209f453b7998d0ac43950618d61f87efbf8c2b26 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 05:09:44 +0000 Subject: [PATCH 069/302] Purge expired accounts after parsing --- src/auth.nim | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 8be435c..8c901e2 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -1,5 +1,5 @@ #SPDX-License-Identifier: AGPL-3.0-only -import std/[asyncdispatch, times, json, random, strutils, tables, packedsets, os] +import std/[asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os] import types import experimental/parser/guestaccount @@ -30,6 +30,16 @@ var template log(str: varargs[string, `$`]) = if enableLogging: echo "[accounts] ", str.join("") +proc snowflakeToEpoch(flake: int64): int64 = + int64(((flake shr 22) + 1288834974657) div 1000) + +proc hasExpired(account: GuestAccount): bool = + let + created = snowflakeToEpoch(account.id) + now = epochTime().int64 + daysOld = int(now - created) div (24 * 60 * 60) + return daysOld > 30 + proc getAccountPoolHealth*(): JsonNode = let now = epochTime().int @@ -42,9 +52,7 @@ proc getAccountPoolHealth*(): JsonNode = average = 0'i64 for account in accountPool: - # Twitter snowflake conversion - let created = int64(((account.id shr 22) + 1288834974657) div 1000) - + let created = snowflakeToEpoch(account.id) if created > newest: newest = created if created < oldest: @@ -188,3 +196,10 @@ proc initAccountPool*(cfg: Config; path: string) = else: echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests." quit 1 + + let accountsPrePurge = accountPool.len + accountPool.keepItIf(not it.hasExpired) + + log "Successfully added ", accountPool.len, " valid accounts." + if accountsPrePurge > accountPool.len: + log "Purged ", accountsPrePurge - accountPool.len, " expired accounts." From d17583286a11586c6ff5cffc43bc997e525a578e Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 1 Nov 2023 05:44:08 +0000 Subject: [PATCH 070/302] Don't requests made before reset --- src/auth.nim | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 8c901e2..fed7df3 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -64,12 +64,16 @@ proc getAccountPoolHealth*(): JsonNode = apiStatus = account.apis[api] reqs = apiMaxReqs[api] - apiStatus.remaining - reqsPerApi.mgetOrPut($api, 0).inc reqs - totalReqs.inc reqs - if apiStatus.limited: limited.incl account.id + # no requests made with this account and endpoint since the limit reset + if apiStatus.reset < now: + continue + + reqsPerApi.mgetOrPut($api, 0).inc reqs + totalReqs.inc reqs + if accountPool.len > 0: average = average div accountPool.len else: From e0d9dd0f9c8175fe9f5bf5aa86c0f56ccbc970a9 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 4 Nov 2023 02:56:32 +0000 Subject: [PATCH 071/302] Fix #670 --- src/auth.nim | 4 ++-- src/sass/profile/card.scss | 2 +- src/types.nim | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index fed7df3..b288c50 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -37,7 +37,7 @@ proc hasExpired(account: GuestAccount): bool = let created = snowflakeToEpoch(account.id) now = epochTime().int64 - daysOld = int(now - created) div (24 * 60 * 60) + daysOld = int(now - created) div dayInSeconds return daysOld > 30 proc getAccountPoolHealth*(): JsonNode = @@ -45,7 +45,7 @@ proc getAccountPoolHealth*(): JsonNode = var totalReqs = 0 - limited: PackedSet[BiggestInt] + limited: PackedSet[int64] reqsPerApi: Table[string, int] oldest = now.int64 newest = 0'i64 diff --git a/src/sass/profile/card.scss b/src/sass/profile/card.scss index 85878e4..46a9679 100644 --- a/src/sass/profile/card.scss +++ b/src/sass/profile/card.scss @@ -115,7 +115,7 @@ } .profile-card-tabs-name { - @include breakable; + flex-shrink: 100; } .profile-card-avatar { diff --git a/src/types.nim b/src/types.nim index 3b0d55c..9ddf283 100644 --- a/src/types.nim +++ b/src/types.nim @@ -36,7 +36,7 @@ type limitedAt*: int GuestAccount* = ref object - id*: BiggestInt + id*: int64 oauthToken*: string oauthSecret*: string pending*: int @@ -164,7 +164,7 @@ type newsletterPublication = "newsletter_publication" hidden unknown - + Card* = object kind*: CardKind url*: string From 5e188647fc5ddcc38084127f1db32f17f07fe727 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 8 Nov 2023 14:53:35 +0000 Subject: [PATCH 072/302] Bump Nim in the ARM64 Dockerfile, add nitter user --- Dockerfile.arm64 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index fbad812..c82be8a 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,7 +1,7 @@ FROM alpine:3.18 as nim LABEL maintainer="setenforce@protonmail.com" -RUN apk --no-cache add gcc git libc-dev libsass-dev "nim=1.6.14-r0" nimble pcre +RUN apk --no-cache add libsass-dev pcre gcc git libc-dev "nim=1.6.16-r0" "nimble=0.13.1-r3" WORKDIR /src/nitter @@ -15,9 +15,11 @@ RUN nimble build -d:danger -d:lto -d:strip \ FROM alpine:3.18 WORKDIR /src/ -RUN apk --no-cache add ca-certificates pcre openssl1.1-compat +RUN apk --no-cache add pcre ca-certificates openssl1.1-compat COPY --from=nim /src/nitter/nitter ./ COPY --from=nim /src/nitter/nitter.example.conf ./nitter.conf COPY --from=nim /src/nitter/public ./public EXPOSE 8080 +RUN adduser -h /src/ -D -s /bin/sh nitter +USER nitter CMD ./nitter From eaedd2aee7be6bc3dd2dceee09dc93052d0046f4 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 8 Nov 2023 16:38:43 +0000 Subject: [PATCH 073/302] Fix ARM64 Dockerfile versions --- Dockerfile.arm64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index c82be8a..70024b2 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,7 +1,7 @@ FROM alpine:3.18 as nim LABEL maintainer="setenforce@protonmail.com" -RUN apk --no-cache add libsass-dev pcre gcc git libc-dev "nim=1.6.16-r0" "nimble=0.13.1-r3" +RUN apk --no-cache add libsass-dev pcre gcc git libc-dev "nim=1.6.14-r0" "nimble=0.13.1-r2" WORKDIR /src/nitter From c2819dab441b8ad8220b03dd0fe79f5a5d51b841 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 15 Nov 2023 10:40:21 +0000 Subject: [PATCH 074/302] Fix #1106 Closes #831 --- src/parserutils.nim | 14 ++++++++++++-- tests/test_quote.py | 2 +- tests/test_tweet.py | 11 ++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/parserutils.nim b/src/parserutils.nim index 7cf696e..6b8263f 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -1,9 +1,17 @@ # SPDX-License-Identifier: AGPL-3.0-only -import std/[strutils, times, macros, htmlgen, options, algorithm, re] +import std/[times, macros, htmlgen, options, algorithm, re] +import std/strutils except escape import std/unicode except strip +from xmltree import escape import packedjson import types, utils, formatters +const + unicodeOpen = "\uFFFA" + unicodeClose = "\uFFFB" + xmlOpen = escape("<") + xmlClose = escape(">") + let unRegex = re"(^|[^A-z0-9-_./?])@([A-z0-9_]{1,15})" unReplace = "$1@$2" @@ -304,7 +312,9 @@ proc expandTweetEntities*(tweet: Tweet; js: JsonNode) = proc expandNoteTweetEntities*(tweet: Tweet; js: JsonNode) = let entities = ? js{"entity_set"} - text = js{"text"}.getStr + text = js{"text"}.getStr.multiReplace(("<", unicodeOpen), (">", unicodeClose)) textSlice = 0..text.runeLen tweet.expandTextEntities(entities, text, textSlice) + + tweet.text = tweet.text.multiReplace((unicodeOpen, xmlOpen), (unicodeClose, xmlClose)) diff --git a/tests/test_quote.py b/tests/test_quote.py index 1b458ea..4921c21 100644 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -9,7 +9,7 @@ text = [ What are we doing wrong? reuters.com/article/us-norwa…"""], ['nim_lang/status/1491461266849808397#m', - 'Nim language', '@nim_lang', + 'Nim', '@nim_lang', """What's better than Nim 1.6.0? Nim 1.6.2 :) diff --git a/tests/test_tweet.py b/tests/test_tweet.py index 7a3c4ed..ac89782 100644 --- a/tests/test_tweet.py +++ b/tests/test_tweet.py @@ -35,7 +35,16 @@ multiline = [ CALM AND CLICHÉ - ON"""] + ON"""], + [1718660434457239868, 'WebDesignMuseum', + """ +Happy 32nd Birthday HTML tags! + +On October 29, 1991, the internet pioneer, Tim Berners-Lee, published a document entitled HTML Tags. + +The document contained a description of the first 18 HTML tags: , <nextid>, <a>, <isindex>, <plaintext>, <listing>, <p>, <h1>…<h6>, <address>, <hp1>, <hp2>…, <dl>, <dt>, <dd>, <ul>, <li>,<menu> and <dir>. The design of the first version of HTML language was influenced by the SGML universal markup language. + +#WebDesignHistory"""] ] link = [ From 06ab1ea2e7341a239447e0ca7d1e9c6246b896c6 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 15 Nov 2023 11:11:56 +0000 Subject: [PATCH 075/302] Enable disabled tests --- tests/test_tweet.py | 58 +++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/tests/test_tweet.py b/tests/test_tweet.py index ac89782..839e6c5 100644 --- a/tests/test_tweet.py +++ b/tests/test_tweet.py @@ -1,4 +1,4 @@ -from base import BaseTestCase, Tweet, get_timeline_tweet +from base import BaseTestCase, Tweet, Conversation, get_timeline_tweet from parameterized import parameterized # image = tweet + 'div.attachments.media-body > div > div > a > div > img' @@ -83,22 +83,18 @@ retweet = [ [3, 'mobile_test_8', 'mobile test 8', 'jack', '@jack', 'twttr'] ] -# reply = [ -# ['mobile_test/with_replies', 15] -# ] - class TweetTest(BaseTestCase): - # @parameterized.expand(timeline) - # def test_timeline(self, index, fullname, username, date, tid, text): - # self.open_nitter(username) - # tweet = get_timeline_tweet(index) - # self.assert_exact_text(fullname, tweet.fullname) - # self.assert_exact_text('@' + username, tweet.username) - # self.assert_exact_text(date, tweet.date) - # self.assert_text(text, tweet.text) - # permalink = self.find_element(tweet.date + ' a') - # self.assertIn(tid, permalink.get_attribute('href')) + @parameterized.expand(timeline) + def test_timeline(self, index, fullname, username, date, tid, text): + self.open_nitter(username) + tweet = get_timeline_tweet(index) + self.assert_exact_text(fullname, tweet.fullname) + self.assert_exact_text('@' + username, tweet.username) + self.assert_exact_text(date, tweet.date) + self.assert_text(text, tweet.text) + permalink = self.find_element(tweet.date + ' a') + self.assertIn(tid, permalink.get_attribute('href')) @parameterized.expand(status) def test_status(self, tid, fullname, username, date, text): @@ -112,18 +108,18 @@ class TweetTest(BaseTestCase): @parameterized.expand(multiline) def test_multiline_formatting(self, tid, username, text): self.open_nitter(f'{username}/status/{tid}') - self.assert_text(text.strip('\n'), '.main-tweet') + self.assert_text(text.strip('\n'), Conversation.main) @parameterized.expand(emoji) def test_emoji(self, tweet, text): self.open_nitter(tweet) - self.assert_text(text, '.main-tweet') + self.assert_text(text, Conversation.main) @parameterized.expand(link) def test_link(self, tweet, links): self.open_nitter(tweet) for link in links: - self.assert_text(link, '.main-tweet') + self.assert_text(link, Conversation.main) @parameterized.expand(username) def test_username(self, tweet, usernames): @@ -132,22 +128,22 @@ class TweetTest(BaseTestCase): link = self.find_link_text(f'@{un}') self.assertIn(f'/{un}', link.get_property('href')) - # @parameterized.expand(retweet) - # def test_retweet(self, index, url, retweet_by, fullname, username, text): - # self.open_nitter(url) - # tweet = get_timeline_tweet(index) - # self.assert_text(f'{retweet_by} retweeted', tweet.retweet) - # self.assert_text(text, tweet.text) - # self.assert_exact_text(fullname, tweet.fullname) - # self.assert_exact_text(username, tweet.username) + @parameterized.expand(retweet) + def test_retweet(self, index, url, retweet_by, fullname, username, text): + self.open_nitter(url) + tweet = get_timeline_tweet(index) + self.assert_text(f'{retweet_by} retweeted', tweet.retweet) + self.assert_text(text, tweet.text) + self.assert_exact_text(fullname, tweet.fullname) + self.assert_exact_text(username, tweet.username) @parameterized.expand(invalid) def test_invalid_id(self, tweet): self.open_nitter(tweet) self.assert_text('Tweet not found', '.error-panel') - # @parameterized.expand(reply) - # def test_thread(self, tweet, num): - # self.open_nitter(tweet) - # thread = self.find_element(f'.timeline > div:nth-child({num})') - # self.assertIn(thread.get_attribute('class'), 'thread-line') + #@parameterized.expand(reply) + #def test_thread(self, tweet, num): + #self.open_nitter(tweet) + #thread = self.find_element(f'.timeline > div:nth-child({num})') + #self.assertIn(thread.get_attribute('class'), 'thread-line') From 4dac9f0798b885130b01f9a5a5535c729d351467 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 25 Nov 2023 05:31:15 +0000 Subject: [PATCH 076/302] Add simple job_details card support --- src/experimental/parser/graphql.nim | 4 ++-- src/experimental/parser/unifiedcard.nim | 11 +++++++++ src/experimental/parser/user.nim | 7 +++++- src/experimental/types/graphuser.nim | 4 ++-- src/experimental/types/timeline.nim | 4 ++-- src/experimental/types/unifiedcard.nim | 32 ++++++++++++++++++++----- src/parser.nim | 4 ++-- src/parserutils.nim | 9 +++---- src/types.nim | 1 + src/utils.nim | 5 ++-- 10 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index b9da7c4..0e9a678 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -12,7 +12,7 @@ proc parseGraphUser*(json: string): User = if raw.data.userResult.result.unavailableReason.get("") == "Suspended": return User(suspended: true) - result = toUser raw.data.userResult.result.legacy + result = raw.data.userResult.result.legacy result.id = raw.data.userResult.result.restId result.verified = result.verified or raw.data.userResult.result.isBlueVerified @@ -30,7 +30,7 @@ proc parseGraphListMembers*(json, cursor: string): Result[User] = of TimelineTimelineItem: let userResult = entry.content.itemContent.userResults.result if userResult.restId.len > 0: - result.content.add toUser userResult.legacy + result.content.add userResult.legacy of TimelineTimelineCursor: if entry.content.cursorType == "Bottom": result.bottom = entry.content.value diff --git a/src/experimental/parser/unifiedcard.nim b/src/experimental/parser/unifiedcard.nim index c9af437..4a50e48 100644 --- a/src/experimental/parser/unifiedcard.nim +++ b/src/experimental/parser/unifiedcard.nim @@ -1,5 +1,6 @@ import std/[options, tables, strutils, strformat, sugar] import jsony +import user import ../types/unifiedcard from ../../types import Card, CardKind, Video from ../../utils import twimg, https @@ -27,6 +28,14 @@ proc parseMediaDetails(data: ComponentData; card: UnifiedCard; result: var Card) result.text = data.topicDetail.title result.dest = "Topic" +proc parseJobDetails(data: ComponentData; card: UnifiedCard; result: var Card) = + data.destination.parseDestination(card, result) + + result.kind = jobDetails + result.title = data.title + result.text = data.shortDescriptionText + result.dest = &"@{data.profileUser.username} · {data.location}" + proc parseAppDetails(data: ComponentData; card: UnifiedCard; result: var Card) = let app = card.appStoreData[data.appId][0] @@ -84,6 +93,8 @@ proc parseUnifiedCard*(json: string): Card = component.parseMedia(card, result) of buttonGroup: discard + of jobDetails: + component.data.parseJobDetails(card, result) of ComponentType.hidden: result.kind = CardKind.hidden of ComponentType.unknown: diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 5962a87..78f596e 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -68,6 +68,11 @@ proc toUser*(raw: RawUser): User = result.expandUserEntities(raw) +proc parseHook*(s: string; i: var int; v: var User) = + var u: RawUser + parseHook(s, i, u) + v = toUser u + proc parseUser*(json: string; username=""): User = handleErrors: case error.code @@ -75,7 +80,7 @@ proc parseUser*(json: string; username=""): User = of userNotFound: return else: echo "[error - parseUser]: ", error - result = toUser json.fromJson(RawUser) + result = json.fromJson(User) proc parseUsers*(json: string; after=""): Result[User] = result = Result[User](beginning: after.len == 0) diff --git a/src/experimental/types/graphuser.nim b/src/experimental/types/graphuser.nim index c30eed9..08100f9 100644 --- a/src/experimental/types/graphuser.nim +++ b/src/experimental/types/graphuser.nim @@ -1,5 +1,5 @@ import options -import user +from ../../types import User type GraphUser* = object @@ -9,7 +9,7 @@ type result*: UserResult UserResult = object - legacy*: RawUser + legacy*: User restId*: string isBlueVerified*: bool unavailableReason*: Option[string] diff --git a/src/experimental/types/timeline.nim b/src/experimental/types/timeline.nim index 28239ad..5ce6d9f 100644 --- a/src/experimental/types/timeline.nim +++ b/src/experimental/types/timeline.nim @@ -1,5 +1,5 @@ import std/tables -import user +from ../../types import User type Search* = object @@ -7,7 +7,7 @@ type timeline*: Timeline GlobalObjects = object - users*: Table[string, RawUser] + users*: Table[string, User] Timeline = object instructions*: seq[Instructions] diff --git a/src/experimental/types/unifiedcard.nim b/src/experimental/types/unifiedcard.nim index 6e83cad..e540a64 100644 --- a/src/experimental/types/unifiedcard.nim +++ b/src/experimental/types/unifiedcard.nim @@ -1,7 +1,10 @@ -import options, tables -from ../../types import VideoType, VideoVariant +import std/[options, tables, times] +import jsony +from ../../types import VideoType, VideoVariant, User type + Text* = distinct string + UnifiedCard* = object componentObjects*: Table[string, Component] destinationObjects*: Table[string, Destination] @@ -13,6 +16,7 @@ type media swipeableMedia buttonGroup + jobDetails appStoreDetails twitterListDetails communityDetails @@ -29,12 +33,15 @@ type appId*: string mediaId*: string destination*: string + location*: string title*: Text subtitle*: Text name*: Text memberCount*: int mediaList*: seq[MediaItem] topicDetail*: tuple[title: Text] + profileUser*: User + shortDescriptionText*: string MediaItem* = object id*: string @@ -69,12 +76,9 @@ type title*: Text category*: Text - Text = object - content: string - TypeField = Component | Destination | MediaEntity | AppStoreData -converter fromText*(text: Text): string = text.content +converter fromText*(text: Text): string = string(text) proc renameHook*(v: var TypeField; fieldName: var string) = if fieldName == "type": @@ -86,6 +90,7 @@ proc enumHook*(s: string; v: var ComponentType) = of "media": media of "swipeable_media": swipeableMedia of "button_group": buttonGroup + of "job_details": jobDetails of "app_store_details": appStoreDetails of "twitter_list_details": twitterListDetails of "community_details": communityDetails @@ -106,3 +111,18 @@ proc enumHook*(s: string; v: var MediaType) = of "photo": photo of "model3d": model3d else: echo "ERROR: Unknown enum value (MediaType): ", s; photo + +proc parseHook*(s: string; i: var int; v: var DateTime) = + var str: string + parseHook(s, i, str) + v = parse(str, "yyyy-MM-dd hh:mm:ss") + +proc parseHook*(s: string; i: var int; v: var Text) = + if s[i] == '"': + var str: string + parseHook(s, i, str) + v = Text(str) + else: + var t: tuple[content: string] + parseHook(s, i, t) + v = Text(t.content) diff --git a/src/parser.nim b/src/parser.nim index fcee13f..d7ba613 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -219,8 +219,6 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = ) ) - result.expandTweetEntities(js) - # fix for pinned threads if result.hasThread and result.threadId == 0: result.threadId = js{"self_thread", "id_str"}.getId @@ -254,6 +252,8 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = else: result.card = some parseCard(jsCard, js{"entities", "urls"}) + result.expandTweetEntities(js) + with jsMedia, js{"extended_entities", "media"}: for m in jsMedia: case m{"type"}.getStr diff --git a/src/parserutils.nim b/src/parserutils.nim index 6b8263f..00ea6f4 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -246,7 +246,7 @@ proc expandUserEntities*(user: var User; js: JsonNode) = .replacef(htRegex, htReplace) proc expandTextEntities(tweet: Tweet; entities: JsonNode; text: string; textSlice: Slice[int]; - replyTo=""; hasQuote=false) = + replyTo=""; hasRedundantLink=false) = let hasCard = tweet.card.isSome var replacements = newSeq[ReplaceSlice]() @@ -257,7 +257,7 @@ proc expandTextEntities(tweet: Tweet; entities: JsonNode; text: string; textSlic if urlStr.len == 0 or urlStr notin text: continue - replacements.extractUrls(u, textSlice.b, hideTwitter = hasQuote) + replacements.extractUrls(u, textSlice.b, hideTwitter = hasRedundantLink) if hasCard and u{"url"}.getStr == get(tweet.card).url: get(tweet.card).url = u{"expanded_url"}.getStr @@ -297,9 +297,10 @@ proc expandTextEntities(tweet: Tweet; entities: JsonNode; text: string; textSlic proc expandTweetEntities*(tweet: Tweet; js: JsonNode) = let entities = ? js{"entities"} - hasQuote = js{"is_quote_status"}.getBool textRange = js{"display_text_range"} textSlice = textRange{0}.getInt .. textRange{1}.getInt + hasQuote = js{"is_quote_status"}.getBool + hasJobCard = tweet.card.isSome and get(tweet.card).kind == jobDetails var replyTo = "" if tweet.replyId != 0: @@ -307,7 +308,7 @@ proc expandTweetEntities*(tweet: Tweet; js: JsonNode) = replyTo = reply.getStr tweet.reply.add replyTo - tweet.expandTextEntities(entities, tweet.text, textSlice, replyTo, hasQuote) + tweet.expandTextEntities(entities, tweet.text, textSlice, replyTo, hasQuote or hasJobCard) proc expandNoteTweetEntities*(tweet: Tweet; js: JsonNode) = let diff --git a/src/types.nim b/src/types.nim index 9ddf283..bc791b1 100644 --- a/src/types.nim +++ b/src/types.nim @@ -162,6 +162,7 @@ type imageDirectMessage = "image_direct_message" audiospace = "audiospace" newsletterPublication = "newsletter_publication" + jobDetails = "job_details" hidden unknown diff --git a/src/utils.nim b/src/utils.nim index 9002bbf..c96a6dd 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -16,7 +16,8 @@ const "twimg.com", "abs.twimg.com", "pbs.twimg.com", - "video.twimg.com" + "video.twimg.com", + "x.com" ] proc setHmacKey*(key: string) = @@ -57,4 +58,4 @@ proc isTwitterUrl*(uri: Uri): bool = uri.hostname in twitterDomains proc isTwitterUrl*(url: string): bool = - parseUri(url).hostname in twitterDomains + isTwitterUrl(parseUri(url)) From d6be08d093bcde38286268b789395cf7a99e67ed Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 25 Nov 2023 05:53:13 +0000 Subject: [PATCH 077/302] Fix jobDetails error on old Nim versions --- src/experimental/parser/unifiedcard.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/experimental/parser/unifiedcard.nim b/src/experimental/parser/unifiedcard.nim index 4a50e48..1f7b825 100644 --- a/src/experimental/parser/unifiedcard.nim +++ b/src/experimental/parser/unifiedcard.nim @@ -31,7 +31,7 @@ proc parseMediaDetails(data: ComponentData; card: UnifiedCard; result: var Card) proc parseJobDetails(data: ComponentData; card: UnifiedCard; result: var Card) = data.destination.parseDestination(card, result) - result.kind = jobDetails + result.kind = CardKind.jobDetails result.title = data.title result.text = data.shortDescriptionText result.dest = &"@{data.profileUser.username} · {data.location}" @@ -93,7 +93,7 @@ proc parseUnifiedCard*(json: string): Card = component.parseMedia(card, result) of buttonGroup: discard - of jobDetails: + of ComponentType.jobDetails: component.data.parseJobDetails(card, result) of ComponentType.hidden: result.kind = CardKind.hidden From f8254c2f0f3bfacb754d7ad69be9b55258c8337c Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 25 Nov 2023 10:06:12 +0000 Subject: [PATCH 078/302] Add support for business and gov verification Also improve icon rendering on Firefox --- src/consts.nim | 10 ++++------ src/experimental/parser/graphql.nim | 5 +++-- src/experimental/parser/unifiedcard.nim | 3 +-- src/experimental/parser/user.nim | 2 +- src/experimental/types/user.nim | 4 ++-- src/parser.nim | 6 +++--- src/redis_cache.nim | 1 + src/sass/include/_variables.scss | 2 ++ src/sass/index.scss | 21 ++++++++++++++++++--- src/sass/search.scss | 2 ++ src/types.nim | 12 ++++++++---- src/views/general.nim | 2 +- src/views/renderutils.nim | 17 ++++++++++++----- src/views/tweet.nim | 3 +-- 14 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/consts.nim b/src/consts.nim index d3a3d80..e1c35e6 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -29,12 +29,10 @@ const "include_cards": "1", "include_entities": "1", "include_profile_interstitial_type": "0", - "include_quote_count": "1", - "include_reply_count": "1", - "include_user_entities": "1", - "include_ext_reply_count": "1", - "include_ext_is_blue_verified": "1", - # "include_ext_verified_type": "1", + "include_quote_count": "0", + "include_reply_count": "0", + "include_user_entities": "0", + "include_ext_reply_count": "0", "include_ext_media_color": "0", "cards_platform": "Web-13", "tweet_mode": "extended", diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 0e9a678..c7f115f 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -1,7 +1,7 @@ import options import jsony import user, ../types/[graphuser, graphlistmembers] -from ../../types import User, Result, Query, QueryKind +from ../../types import User, VerifiedType, Result, Query, QueryKind proc parseGraphUser*(json: string): User = if json.len == 0 or json[0] != '{': @@ -14,7 +14,8 @@ proc parseGraphUser*(json: string): User = result = raw.data.userResult.result.legacy result.id = raw.data.userResult.result.restId - result.verified = result.verified or raw.data.userResult.result.isBlueVerified + if result.verifiedType == none and raw.data.userResult.result.isBlueVerified: + result.verifiedType = blue proc parseGraphListMembers*(json, cursor: string): Result[User] = result = Result[User]( diff --git a/src/experimental/parser/unifiedcard.nim b/src/experimental/parser/unifiedcard.nim index 1f7b825..a112974 100644 --- a/src/experimental/parser/unifiedcard.nim +++ b/src/experimental/parser/unifiedcard.nim @@ -1,7 +1,6 @@ import std/[options, tables, strutils, strformat, sugar] import jsony -import user -import ../types/unifiedcard +import user, ../types/unifiedcard from ../../types import Card, CardKind, Video from ../../utils import twimg, https diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 78f596e..07e0477 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -56,7 +56,7 @@ proc toUser*(raw: RawUser): User = tweets: raw.statusesCount, likes: raw.favouritesCount, media: raw.mediaCount, - verified: raw.verified or raw.extIsBlueVerified, + verifiedType: raw.verifiedType, protected: raw.protected, joinDate: parseTwitterDate(raw.createdAt), banner: getBanner(raw), diff --git a/src/experimental/types/user.nim b/src/experimental/types/user.nim index 39331a0..7dc0194 100644 --- a/src/experimental/types/user.nim +++ b/src/experimental/types/user.nim @@ -1,5 +1,6 @@ import options import common +from ../../types import VerifiedType type RawUser* = object @@ -15,8 +16,7 @@ type favouritesCount*: int statusesCount*: int mediaCount*: int - verified*: bool - extIsBlueVerified*: bool + verifiedType*: VerifiedType protected*: bool profileLinkColor*: string profileBannerUrl*: string diff --git a/src/parser.nim b/src/parser.nim index d7ba613..a7bf89d 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -21,7 +21,7 @@ proc parseUser(js: JsonNode; id=""): User = tweets: js{"statuses_count"}.getInt, likes: js{"favourites_count"}.getInt, media: js{"media_count"}.getInt, - verified: js{"verified"}.getBool or js{"ext_is_blue_verified"}.getBool, + verifiedType: parseEnum[VerifiedType](js{"verified_type"}.getStr("None")), protected: js{"protected"}.getBool, joinDate: js{"created_at"}.getTime ) @@ -34,8 +34,8 @@ proc parseGraphUser(js: JsonNode): User = user = ? js{"user_results", "result"} result = parseUser(user{"legacy"}) - if "is_blue_verified" in user: - result.verified = user{"is_blue_verified"}.getBool() + if result.verifiedType == none and user{"is_blue_verified"}.getBool(false): + result.verifiedType = blue proc parseGraphList*(js: JsonNode): List = if js.isNull: return diff --git a/src/redis_cache.nim b/src/redis_cache.nim index a8b5ff8..1d77cca 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -52,6 +52,7 @@ proc initRedisPool*(cfg: Config) {.async.} = await migrate("profileDates", "p:*") await migrate("profileStats", "p:*") await migrate("userType", "p:*") + await migrate("verifiedType", "p:*") pool.withAcquire(r): # optimize memory usage for user ID buckets diff --git a/src/sass/include/_variables.scss b/src/sass/include/_variables.scss index 0f81235..0c95ff6 100644 --- a/src/sass/include/_variables.scss +++ b/src/sass/include/_variables.scss @@ -28,6 +28,8 @@ $more_replies_dots: #AD433B; $error_red: #420A05; $verified_blue: #1DA1F2; +$verified_business: #FAC82B; +$verified_government: #C1B6A4; $icon_text: $fg_color; $tab: $fg_color; diff --git a/src/sass/index.scss b/src/sass/index.scss index 9e2e347..6cab48e 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -39,6 +39,8 @@ body { --error_red: #{$error_red}; --verified_blue: #{$verified_blue}; + --verified_business: #{$verified_business}; + --verified_government: #{$verified_government}; --icon_text: #{$icon_text}; --tab: #{$fg_color}; @@ -141,17 +143,30 @@ ul { .verified-icon { color: var(--icon_text); - background-color: var(--verified_blue); border-radius: 50%; flex-shrink: 0; margin: 2px 0 3px 3px; - padding-top: 2px; - height: 12px; + padding-top: 3px; + height: 11px; width: 14px; font-size: 8px; display: inline-block; text-align: center; vertical-align: middle; + + &.blue { + background-color: var(--verified_blue); + } + + &.business { + color: var(--bg_panel); + background-color: var(--verified_business); + } + + &.government { + color: var(--bg_panel); + background-color: var(--verified_government); + } } @media(max-width: 600px) { diff --git a/src/sass/search.scss b/src/sass/search.scss index 0311fb0..f70f7ea 100644 --- a/src/sass/search.scss +++ b/src/sass/search.scss @@ -14,6 +14,8 @@ button { margin: 0 2px 0 0; height: 23px; + display: flex; + align-items: center; } .pref-input { diff --git a/src/types.nim b/src/types.nim index bc791b1..ddbebdf 100644 --- a/src/types.nim +++ b/src/types.nim @@ -10,9 +10,7 @@ type BadClientError* = object of CatchableError TimelineKind* {.pure.} = enum - tweets - replies - media + tweets, replies, media Api* {.pure.} = enum tweetDetail @@ -63,6 +61,12 @@ type tweetUnavailable = 421 tweetCensored = 422 + VerifiedType* = enum + none = "None" + blue = "Blue" + business = "Business" + government = "Government" + User* = object id*: string username*: string @@ -78,7 +82,7 @@ type tweets*: int likes*: int media*: int - verified*: bool + verifiedType*: VerifiedType protected*: bool suspended*: bool joinDate*: DateTime diff --git a/src/views/general.nim b/src/views/general.nim index 5e96d02..87d30f2 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -52,7 +52,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; let opensearchUrl = getUrlPrefix(cfg) & "/opensearch" buildHtml(head): - link(rel="stylesheet", type="text/css", href="/css/style.css?v=18") + link(rel="stylesheet", type="text/css", href="/css/style.css?v=19") link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=2") if theme.len > 0: diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index 9dffdcb..451ddfb 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -23,6 +23,13 @@ proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode = if text.len > 0: text " " & text +template verifiedIcon*(user: User): untyped {.dirty.} = + if user.verifiedType != none: + let lower = ($user.verifiedType).toLowerAscii() + icon "ok", class=(&"verified-icon {lower}"), title=(&"Verified {lower} account") + else: + text "" + proc linkUser*(user: User, class=""): VNode = let isName = "username" notin class @@ -32,11 +39,11 @@ proc linkUser*(user: User, class=""): VNode = buildHtml(a(href=href, class=class, title=nameText)): text nameText - if isName and user.verified: - icon "ok", class="verified-icon", title="Verified account" - if isName and user.protected: - text " " - icon "lock", title="Protected account" + if isName: + verifiedIcon(user) + if user.protected: + text " " + icon "lock", title="Protected account" proc linkText*(text: string; class=""): VNode = let url = if "http" notin text: https & text else: text diff --git a/src/views/tweet.nim b/src/views/tweet.nim index f47ae9a..2fe4ac9 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -200,8 +200,7 @@ proc renderAttribution(user: User; prefs: Prefs): VNode = buildHtml(a(class="attribution", href=("/" & user.username))): renderMiniAvatar(user, prefs) strong: text user.fullname - if user.verified: - icon "ok", class="verified-icon", title="Verified account" + verifiedIcon(user) proc renderMediaTags(tags: seq[User]): VNode = buildHtml(tdiv(class="media-tag-block")): From a9740fec8b2d9d4469322cc66d3f3547e0b6ccdb Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 25 Nov 2023 10:11:57 +0000 Subject: [PATCH 079/302] Fix compilation with old Nim again --- src/experimental/parser/graphql.nim | 2 +- src/parser.nim | 2 +- src/views/renderutils.nim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index c7f115f..69837ab 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -14,7 +14,7 @@ proc parseGraphUser*(json: string): User = result = raw.data.userResult.result.legacy result.id = raw.data.userResult.result.restId - if result.verifiedType == none and raw.data.userResult.result.isBlueVerified: + if result.verifiedType == VerifiedType.none and raw.data.userResult.result.isBlueVerified: result.verifiedType = blue proc parseGraphListMembers*(json, cursor: string): Result[User] = diff --git a/src/parser.nim b/src/parser.nim index a7bf89d..f2547e4 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -34,7 +34,7 @@ proc parseGraphUser(js: JsonNode): User = user = ? js{"user_results", "result"} result = parseUser(user{"legacy"}) - if result.verifiedType == none and user{"is_blue_verified"}.getBool(false): + if result.verifiedType == VerifiedType.none and user{"is_blue_verified"}.getBool(false): result.verifiedType = blue proc parseGraphList*(js: JsonNode): List = diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index 451ddfb..f298fad 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -24,7 +24,7 @@ proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode = text " " & text template verifiedIcon*(user: User): untyped {.dirty.} = - if user.verifiedType != none: + if user.verifiedType != VerifiedType.none: let lower = ($user.verifiedType).toLowerAscii() icon "ok", class=(&"verified-icon {lower}"), title=(&"Verified {lower} account") else: From 583c858cdf3486451ed6a0627640844f27009dbe Mon Sep 17 00:00:00 2001 From: blankie <blankie@nixnetmail.com> Date: Sun, 3 Dec 2023 08:54:24 +0000 Subject: [PATCH 080/302] Fix search queries in user search RSS feeds (#1126) Fixes #992 --- src/parser.nim | 2 +- src/routes/rss.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index f2547e4..ec856a6 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -32,7 +32,7 @@ proc parseGraphUser(js: JsonNode): User = var user = js{"user_result", "result"} if user.isNull: user = ? js{"user_results", "result"} - result = parseUser(user{"legacy"}) + result = parseUser(user{"legacy"}, user{"rest_id"}.getStr) if result.verifiedType == VerifiedType.none and user{"is_blue_verified"}.getBool(false): result.verifiedType = blue diff --git a/src/routes/rss.nim b/src/routes/rss.nim index 6c77992..447f4ad 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -110,7 +110,7 @@ proc createRssRouter*(cfg: Config) = case tab of "with_replies": getReplyQuery(name) of "media": getMediaQuery(name) - # of "search": initQuery(params(request), name=name) + of "search": initQuery(params(request), name=name) else: Query(fromUser: @[name]) let searchKey = if tab != "search": "" From 52db03b73ad5f83f67c83ab197ae3b20a2523d39 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Fri, 12 Jan 2024 03:47:06 +0100 Subject: [PATCH 081/302] Fix broken video playback by forcing fmp4 --- src/utils.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils.nim b/src/utils.nim index c96a6dd..ede9aed 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -31,7 +31,9 @@ proc getHmac*(data: string): string = proc getVidUrl*(link: string): string = if link.len == 0: return - let sig = getHmac(link) + let + link = link.replace("cmaf", "fmp4") + sig = getHmac(link) if base64Media: &"/video/enc/{sig}/{encode(link, safe=true)}" else: From cdff5e9b1c1ff9d61d991f95c45d0588a0d90434 Mon Sep 17 00:00:00 2001 From: jackyzy823 <jackyzy823@gmail.com> Date: Thu, 22 Feb 2024 07:10:54 +0800 Subject: [PATCH 082/302] Fix for #1147, Proxy for audio URL and upgrade hls.js (#1178) * Revert "Fix broken video playback by forcing fmp4" This reverts commit 52db03b73ad5f83f67c83ab197ae3b20a2523d39. * Fix audio url in video m3u8 * Upgrade hls.js to 1.5.1 and use full version --- public/js/hls.light.min.js | 5 ----- public/js/hls.min.js | 5 +++++ src/formatters.nim | 2 ++ src/utils.nim | 4 +--- src/views/general.nim | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 public/js/hls.light.min.js create mode 100644 public/js/hls.min.js diff --git a/public/js/hls.light.min.js b/public/js/hls.light.min.js deleted file mode 100644 index 5a1fd1d..0000000 --- a/public/js/hls.light.min.js +++ /dev/null @@ -1,5 +0,0 @@ -// @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0 -// @source https://github.com/video-dev/hls.js -// @version v1.2.9 -"undefined"!=typeof window&&function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Hls=t():e.Hls=t()}(this,(()=>(()=>{var e={21:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>u});var i=function(){function e(e,t){this.subtle=void 0,this.aesIV=void 0,this.subtle=e,this.aesIV=t}return e.prototype.decrypt=function(e,t){return this.subtle.decrypt({name:"AES-CBC",iv:this.aesIV},t,e)},e}(),n=function(){function e(e,t){this.subtle=void 0,this.key=void 0,this.subtle=e,this.key=t}return e.prototype.expandKey=function(){return this.subtle.importKey("raw",this.key,{name:"AES-CBC"},!1,["encrypt","decrypt"])},e}(),a=r(145),s=function(){function e(){this.rcon=[0,1,2,4,8,16,32,64,128,27,54],this.subMix=[new Uint32Array(256),new Uint32Array(256),new Uint32Array(256),new Uint32Array(256)],this.invSubMix=[new Uint32Array(256),new Uint32Array(256),new Uint32Array(256),new Uint32Array(256)],this.sBox=new Uint32Array(256),this.invSBox=new Uint32Array(256),this.key=new Uint32Array(0),this.ksRows=0,this.keySize=0,this.keySchedule=void 0,this.invKeySchedule=void 0,this.initTable()}var t=e.prototype;return t.uint8ArrayToUint32Array_=function(e){for(var t=new DataView(e),r=new Uint32Array(4),i=0;i<4;i++)r[i]=t.getUint32(4*i);return r},t.initTable=function(){var e=this.sBox,t=this.invSBox,r=this.subMix,i=r[0],n=r[1],a=r[2],s=r[3],o=this.invSubMix,l=o[0],u=o[1],d=o[2],h=o[3],f=new Uint32Array(256),c=0,v=0,g=0;for(g=0;g<256;g++)f[g]=g<128?g<<1:g<<1^283;for(g=0;g<256;g++){var p=v^v<<1^v<<2^v<<3^v<<4;p=p>>>8^255&p^99,e[c]=p,t[p]=c;var m=f[c],y=f[m],E=f[y],T=257*f[p]^16843008*p;i[c]=T<<24|T>>>8,n[c]=T<<16|T>>>16,a[c]=T<<8|T>>>24,s[c]=T,T=16843009*E^65537*y^257*m^16843008*c,l[p]=T<<24|T>>>8,u[p]=T<<16|T>>>16,d[p]=T<<8|T>>>24,h[p]=T,c?(c=m^f[f[f[E^m]]],v^=f[f[v]]):c=v=1}},t.expandKey=function(e){for(var t=this.uint8ArrayToUint32Array_(e),r=!0,i=0;i<t.length&&r;)r=t[i]===this.key[i],i++;if(!r){this.key=t;var n=this.keySize=t.length;if(4!==n&&6!==n&&8!==n)throw new Error("Invalid aes key size="+n);var a,s,o,l,u=this.ksRows=4*(n+6+1),d=this.keySchedule=new Uint32Array(u),h=this.invKeySchedule=new Uint32Array(u),f=this.sBox,c=this.rcon,v=this.invSubMix,g=v[0],p=v[1],m=v[2],y=v[3];for(a=0;a<u;a++)a<n?o=d[a]=t[a]:(l=o,a%n==0?(l=f[(l=l<<8|l>>>24)>>>24]<<24|f[l>>>16&255]<<16|f[l>>>8&255]<<8|f[255&l],l^=c[a/n|0]<<24):n>6&&a%n==4&&(l=f[l>>>24]<<24|f[l>>>16&255]<<16|f[l>>>8&255]<<8|f[255&l]),d[a]=o=(d[a-n]^l)>>>0);for(s=0;s<u;s++)a=u-s,l=3&s?d[a]:d[a-4],h[s]=s<4||a<=4?l:g[f[l>>>24]]^p[f[l>>>16&255]]^m[f[l>>>8&255]]^y[f[255&l]],h[s]=h[s]>>>0}},t.networkToHostOrderSwap=function(e){return e<<24|(65280&e)<<8|(16711680&e)>>8|e>>>24},t.decrypt=function(e,t,r){for(var i,n,a,s,o,l,u,d,h,f,c,v,g,p,m=this.keySize+6,y=this.invKeySchedule,E=this.invSBox,T=this.invSubMix,S=T[0],b=T[1],L=T[2],A=T[3],D=this.uint8ArrayToUint32Array_(r),R=D[0],k=D[1],_=D[2],x=D[3],I=new Int32Array(e),w=new Int32Array(I.length),C=this.networkToHostOrderSwap;t<I.length;){for(h=C(I[t]),f=C(I[t+1]),c=C(I[t+2]),v=C(I[t+3]),o=h^y[0],l=v^y[1],u=c^y[2],d=f^y[3],g=4,p=1;p<m;p++)i=S[o>>>24]^b[l>>16&255]^L[u>>8&255]^A[255&d]^y[g],n=S[l>>>24]^b[u>>16&255]^L[d>>8&255]^A[255&o]^y[g+1],a=S[u>>>24]^b[d>>16&255]^L[o>>8&255]^A[255&l]^y[g+2],s=S[d>>>24]^b[o>>16&255]^L[l>>8&255]^A[255&u]^y[g+3],o=i,l=n,u=a,d=s,g+=4;i=E[o>>>24]<<24^E[l>>16&255]<<16^E[u>>8&255]<<8^E[255&d]^y[g],n=E[l>>>24]<<24^E[u>>16&255]<<16^E[d>>8&255]<<8^E[255&o]^y[g+1],a=E[u>>>24]<<24^E[d>>16&255]<<16^E[o>>8&255]<<8^E[255&l]^y[g+2],s=E[d>>>24]<<24^E[o>>16&255]<<16^E[l>>8&255]<<8^E[255&u]^y[g+3],w[t]=C(i^R),w[t+1]=C(s^k),w[t+2]=C(a^_),w[t+3]=C(n^x),R=h,k=f,_=c,x=v,t+=4}return w.buffer},e}(),o=r(93),l=r(63),u=function(){function e(e,t,r){var i=(void 0===r?{}:r).removePKCS7Padding,n=void 0===i||i;if(this.logEnabled=!0,this.observer=void 0,this.config=void 0,this.removePKCS7Padding=void 0,this.subtle=null,this.softwareDecrypter=null,this.key=null,this.fastAesKey=null,this.remainderData=null,this.currentIV=null,this.currentResult=null,this.observer=e,this.config=t,this.removePKCS7Padding=n,n)try{var a=self.crypto;a&&(this.subtle=a.subtle||a.webkitSubtle)}catch(e){}null===this.subtle&&(this.config.enableSoftwareAES=!0)}var t=e.prototype;return t.destroy=function(){this.observer=null},t.isSync=function(){return this.config.enableSoftwareAES},t.flush=function(){var e=this.currentResult;if(e){var t,r,i,n=new Uint8Array(e);return this.reset(),this.removePKCS7Padding?(i=(r=(t=n).byteLength)&&new DataView(t.buffer).getUint8(r-1))?(0,a.sliceUint8)(t,0,r-i):t:n}this.reset()},t.reset=function(){this.currentResult=null,this.currentIV=null,this.remainderData=null,this.softwareDecrypter&&(this.softwareDecrypter=null)},t.decrypt=function(e,t,r,i){if(this.config.enableSoftwareAES){this.softwareDecrypt(new Uint8Array(e),t,r);var n=this.flush();n&&i(n.buffer)}else this.webCryptoDecrypt(new Uint8Array(e),t,r).then(i)},t.softwareDecrypt=function(e,t,r){var i=this.currentIV,n=this.currentResult,o=this.remainderData;this.logOnce("JS AES decrypt"),o&&(e=(0,l.appendUint8Array)(o,e),this.remainderData=null);var u=this.getValidChunk(e);if(!u.length)return null;i&&(r=i);var d=this.softwareDecrypter;d||(d=this.softwareDecrypter=new s),d.expandKey(t);var h=n;return this.currentResult=d.decrypt(u.buffer,0,r),this.currentIV=(0,a.sliceUint8)(u,-16).buffer,h||null},t.webCryptoDecrypt=function(e,t,r){var a=this,s=this.subtle;return this.key===t&&this.fastAesKey||(this.key=t,this.fastAesKey=new n(s,t)),this.fastAesKey.expandKey().then((function(t){return s?new i(s,r).decrypt(e.buffer,t):Promise.reject(new Error("web crypto not initialized"))})).catch((function(i){return a.onWebCryptoError(i,e,t,r)}))},t.onWebCryptoError=function(e,t,r,i){return o.logger.warn("[decrypter.ts]: WebCrypto Error, disable WebCrypto API:",e),this.config.enableSoftwareAES=!0,this.logEnabled=!0,this.softwareDecrypt(t,r,i)},t.getValidChunk=function(e){var t=e,r=e.length-e.length%16;return r!==e.length&&(t=(0,a.sliceUint8)(e,0,r),this.remainderData=(0,a.sliceUint8)(e,r)),t},t.logOnce=function(e){this.logEnabled&&(o.logger.log("[decrypter.ts]: "+e),this.logEnabled=!1)},e}()},181:(e,t,r)=>{"use strict";r.r(t),r.d(t,{canParse:()=>l,decodeFrame:()=>c,getID3Data:()=>s,getID3Frames:()=>f,getTimeStamp:()=>u,isFooter:()=>a,isHeader:()=>n,isTimeStampFrame:()=>d,testables:()=>E,utf8ArrayToStr:()=>y});var i,n=function(e,t){return t+10<=e.length&&73===e[t]&&68===e[t+1]&&51===e[t+2]&&e[t+3]<255&&e[t+4]<255&&e[t+6]<128&&e[t+7]<128&&e[t+8]<128&&e[t+9]<128},a=function(e,t){return t+10<=e.length&&51===e[t]&&68===e[t+1]&&73===e[t+2]&&e[t+3]<255&&e[t+4]<255&&e[t+6]<128&&e[t+7]<128&&e[t+8]<128&&e[t+9]<128},s=function(e,t){for(var r=t,i=0;n(e,t);)i+=10,i+=o(e,t+6),a(e,t+10)&&(i+=10),t+=i;if(i>0)return e.subarray(r,r+i)},o=function(e,t){var r=0;return r=(127&e[t])<<21,r|=(127&e[t+1])<<14,(r|=(127&e[t+2])<<7)|127&e[t+3]},l=function(e,t){return n(e,t)&&o(e,t+6)+10<=e.length-t},u=function(e){for(var t=f(e),r=0;r<t.length;r++){var i=t[r];if(d(i))return m(i)}},d=function(e){return e&&"PRIV"===e.key&&"com.apple.streaming.transportStreamTimestamp"===e.info},h=function(e){var t=String.fromCharCode(e[0],e[1],e[2],e[3]),r=o(e,4);return{type:t,size:r,data:e.subarray(10,10+r)}},f=function(e){for(var t=0,r=[];n(e,t);){for(var i=o(e,t+6),s=(t+=10)+i;t+8<s;){var l=h(e.subarray(t)),u=c(l);u&&r.push(u),t+=l.size+10}a(e,t)&&(t+=10)}return r},c=function(e){return"PRIV"===e.type?v(e):"W"===e.type[0]?p(e):g(e)},v=function(e){if(!(e.size<2)){var t=y(e.data,!0),r=new Uint8Array(e.data.subarray(t.length+1));return{key:e.type,info:t,data:r.buffer}}},g=function(e){if(!(e.size<2)){if("TXXX"===e.type){var t=1,r=y(e.data.subarray(t),!0);t+=r.length+1;var i=y(e.data.subarray(t));return{key:e.type,info:r,data:i}}var n=y(e.data.subarray(1));return{key:e.type,data:n}}},p=function(e){if("WXXX"===e.type){if(e.size<2)return;var t=1,r=y(e.data.subarray(t),!0);t+=r.length+1;var i=y(e.data.subarray(t));return{key:e.type,info:r,data:i}}var n=y(e.data);return{key:e.type,data:n}},m=function(e){if(8===e.data.byteLength){var t=new Uint8Array(e.data),r=1&t[3],i=(t[4]<<23)+(t[5]<<15)+(t[6]<<7)+t[7];return i/=45,r&&(i+=47721858.84),Math.round(i)}},y=function(e,t){void 0===t&&(t=!1);var r=T();if(r){var i=r.decode(e);if(t){var n=i.indexOf("\0");return-1!==n?i.substring(0,n):i}return i.replace(/\0/g,"")}for(var a,s,o,l=e.length,u="",d=0;d<l;){if(0===(a=e[d++])&&t)return u;if(0!==a&&3!==a)switch(a>>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:u+=String.fromCharCode(a);break;case 12:case 13:s=e[d++],u+=String.fromCharCode((31&a)<<6|63&s);break;case 14:s=e[d++],o=e[d++],u+=String.fromCharCode((15&a)<<12|(63&s)<<6|(63&o)<<0)}}return u},E={decodeTextFrame:g};function T(){return i||void 0===self.TextDecoder||(i=new self.TextDecoder("utf-8")),i}},182:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>o});var i=r(764),n=r(851),a=r(93),s=r(729);function o(e){var t=new s.EventEmitter,r=function(t,r){e.postMessage({event:t,data:r})};t.on(n.Events.FRAG_DECRYPTED,r),t.on(n.Events.ERROR,r),e.addEventListener("message",(function(n){var s=n.data;switch(s.cmd){case"init":var o=JSON.parse(s.config);e.transmuxer=new i.default(t,s.typeSupported,o,s.vendor,s.id),(0,a.enableLogs)(o.debug,s.id),function(){var e=function(e){a.logger[e]=function(t){r("workerLog",{logType:e,message:t})}};for(var t in a.logger)e(t)}(),r("init",null);break;case"configure":e.transmuxer.configure(s.config);break;case"demux":var u=e.transmuxer.push(s.data,s.decryptdata,s.chunkMeta,s.state);(0,i.isPromise)(u)?u.then((function(t){l(e,t)})):l(e,u);break;case"flush":var h=s.chunkMeta,f=e.transmuxer.flush(h);(0,i.isPromise)(f)?f.then((function(t){d(e,t,h)})):d(e,f,h)}}))}function l(e,t){if(!((r=t.remuxResult).audio||r.video||r.text||r.id3||r.initSegment))return!1;var r,i=[],n=t.remuxResult,a=n.audio,s=n.video;return a&&u(i,a),s&&u(i,s),e.postMessage({event:"transmuxComplete",data:t},i),!0}function u(e,t){t.data1&&e.push(t.data1.buffer),t.data2&&e.push(t.data2.buffer)}function d(e,t,r){t.reduce((function(t,r){return l(e,r)||t}),!1)||e.postMessage({event:"transmuxComplete",data:t[0]}),e.postMessage({event:"flush",data:r})}},764:(e,t,r)=>{"use strict";r.r(t),r.d(t,{TransmuxConfig:()=>Ee,TransmuxState:()=>Te,default:()=>pe,isPromise:()=>ye});var i=r(851),n=r(973),a=r(21),s=r(965),o=r(181),l=r(856);function u(e,t){return void 0===e&&(e=""),void 0===t&&(t=9e4),{type:e,id:-1,pid:-1,inputTimeScale:t,sequenceNumber:-1,samples:[],dropped:0}}var d=r(63),h=r(145),f=function(){function e(){this._audioTrack=void 0,this._id3Track=void 0,this.frameIndex=0,this.cachedData=null,this.basePTS=null,this.initPTS=null,this.lastPTS=null}var t=e.prototype;return t.resetInitSegment=function(e,t,r,i){this._id3Track={type:"id3",id:3,pid:-1,inputTimeScale:9e4,sequenceNumber:0,samples:[],dropped:0}},t.resetTimeStamp=function(e){this.initPTS=e,this.resetContiguity()},t.resetContiguity=function(){this.basePTS=null,this.lastPTS=null,this.frameIndex=0},t.canParse=function(e,t){return!1},t.appendFrame=function(e,t,r){},t.demux=function(e,t){this.cachedData&&(e=(0,d.appendUint8Array)(this.cachedData,e),this.cachedData=null);var r,i=o.getID3Data(e,0),n=i?i.length:0,a=this._audioTrack,f=this._id3Track,v=i?o.getTimeStamp(i):void 0,g=e.length;for((null===this.basePTS||0===this.frameIndex&&(0,s.isFiniteNumber)(v))&&(this.basePTS=c(v,t,this.initPTS),this.lastPTS=this.basePTS),null===this.lastPTS&&(this.lastPTS=this.basePTS),i&&i.length>0&&f.samples.push({pts:this.lastPTS,dts:this.lastPTS,data:i,type:l.MetadataSchema.audioId3,duration:Number.POSITIVE_INFINITY});n<g;){if(this.canParse(e,n)){var p=this.appendFrame(a,e,n);p?(this.frameIndex++,this.lastPTS=p.sample.pts,r=n+=p.length):n=g}else o.canParse(e,n)?(i=o.getID3Data(e,n),f.samples.push({pts:this.lastPTS,dts:this.lastPTS,data:i,type:l.MetadataSchema.audioId3,duration:Number.POSITIVE_INFINITY}),r=n+=i.length):n++;if(n===g&&r!==g){var m=(0,h.sliceUint8)(e,r);this.cachedData?this.cachedData=(0,d.appendUint8Array)(this.cachedData,m):this.cachedData=m}}return{audioTrack:a,videoTrack:u(),id3Track:f,textTrack:u()}},t.demuxSampleAes=function(e,t,r){return Promise.reject(new Error("["+this+"] This demuxer does not support Sample-AES decryption"))},t.flush=function(e){var t=this.cachedData;return t&&(this.cachedData=null,this.demux(t,0)),{audioTrack:this._audioTrack,videoTrack:u(),id3Track:this._id3Track,textTrack:u()}},t.destroy=function(){},e}(),c=function(e,t,r){return(0,s.isFiniteNumber)(e)?90*e:9e4*t+(r||0)};const v=f;var g=r(93);function p(e,t){return 255===e[t]&&240==(246&e[t+1])}function m(e,t){return 1&e[t+1]?7:9}function y(e,t){return(3&e[t+3])<<11|e[t+4]<<3|(224&e[t+5])>>>5}function E(e,t){return t+1<e.length&&p(e,t)}function T(e,t){if(E(e,t)){var r=m(e,t);if(t+r>=e.length)return!1;var i=y(e,t);if(i<=r)return!1;var n=t+i;return n===e.length||E(e,n)}return!1}function S(e,t,r,a,s){if(!e.samplerate){var o=function(e,t,r,a){var s,o,l,u,d=navigator.userAgent.toLowerCase(),h=a,f=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];s=1+((192&t[r+2])>>>6);var c=(60&t[r+2])>>>2;if(!(c>f.length-1))return l=(1&t[r+2])<<2,l|=(192&t[r+3])>>>6,g.logger.log("manifest codec:"+a+", ADTS type:"+s+", samplingIndex:"+c),/firefox/i.test(d)?c>=6?(s=5,u=new Array(4),o=c-3):(s=2,u=new Array(2),o=c):-1!==d.indexOf("android")?(s=2,u=new Array(2),o=c):(s=5,u=new Array(4),a&&(-1!==a.indexOf("mp4a.40.29")||-1!==a.indexOf("mp4a.40.5"))||!a&&c>=6?o=c-3:((a&&-1!==a.indexOf("mp4a.40.2")&&(c>=6&&1===l||/vivaldi/i.test(d))||!a&&1===l)&&(s=2,u=new Array(2)),o=c)),u[0]=s<<3,u[0]|=(14&c)>>1,u[1]|=(1&c)<<7,u[1]|=l<<3,5===s&&(u[1]|=(14&o)>>1,u[2]=(1&o)<<7,u[2]|=8,u[3]=0),{config:u,samplerate:f[c],channelCount:l,codec:"mp4a.40."+s,manifestCodec:h};e.trigger(i.Events.ERROR,{type:n.ErrorTypes.MEDIA_ERROR,details:n.ErrorDetails.FRAG_PARSING_ERROR,fatal:!0,reason:"invalid ADTS sampling index:"+c})}(t,r,a,s);if(!o)return;e.config=o.config,e.samplerate=o.samplerate,e.channelCount=o.channelCount,e.codec=o.codec,e.manifestCodec=o.manifestCodec,g.logger.log("parsed codec:"+e.codec+", rate:"+o.samplerate+", channels:"+o.channelCount)}}function b(e){return 9216e4/e}function L(e,t,r,i,n){var a,s=i+n*b(e.samplerate),o=function(e,t){var r=m(e,t);if(t+r<=e.length){var i=y(e,t)-r;if(i>0)return{headerLength:r,frameLength:i}}}(t,r);if(o){var l=o.frameLength,u=o.headerLength,d=u+l,h=Math.max(0,r+d-t.length);h?(a=new Uint8Array(d-u)).set(t.subarray(r+u,t.length),0):a=t.subarray(r+u,r+d);var f={unit:a,pts:s};return h||e.samples.push(f),{sample:f,length:d,missing:h}}var c=t.length-r;return(a=new Uint8Array(c)).set(t.subarray(r,t.length),0),{sample:{unit:a,pts:s},length:c,missing:-1}}function A(e,t){return A=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},A(e,t)}const D=function(e){var t,r;function i(t,r){var i;return(i=e.call(this)||this).observer=void 0,i.config=void 0,i.observer=t,i.config=r,i}r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,A(t,r);var n=i.prototype;return n.resetInitSegment=function(t,r,i,n){e.prototype.resetInitSegment.call(this,t,r,i,n),this._audioTrack={container:"audio/adts",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"aac",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},i.probe=function(e){if(!e)return!1;for(var t=(o.getID3Data(e,0)||[]).length,r=e.length;t<r;t++)if(T(e,t))return g.logger.log("ADTS sync word found !"),!0;return!1},n.canParse=function(e,t){return function(e,t){return function(e,t){return t+5<e.length}(e,t)&&p(e,t)&&y(e,t)<=e.length-t}(e,t)},n.appendFrame=function(e,t,r){S(e,this.observer,t,r,e.manifestCodec);var i=L(e,t,r,this.basePTS,this.frameIndex);if(i&&0===i.missing)return i},i}(v);var R=/\/emsg[-/]ID3/i;const k=function(){function e(e,t){this.remainderData=null,this.timeOffset=0,this.config=void 0,this.videoTrack=void 0,this.audioTrack=void 0,this.id3Track=void 0,this.txtTrack=void 0,this.config=t}var t=e.prototype;return t.resetTimeStamp=function(){},t.resetInitSegment=function(e,t,r,i){var n=(0,d.parseInitSegment)(e),a=this.videoTrack=u("video",1),s=this.audioTrack=u("audio",1),o=this.txtTrack=u("text",1);if(this.id3Track=u("id3",1),this.timeOffset=0,n.video){var l=n.video,h=l.id,f=l.timescale,c=l.codec;a.id=h,a.timescale=o.timescale=f,a.codec=c}if(n.audio){var v=n.audio,g=v.id,p=v.timescale,m=v.codec;s.id=g,s.timescale=p,s.codec=m}o.id=d.RemuxerTrackIdConfig.text,a.sampleDuration=0,a.duration=s.duration=i},t.resetContiguity=function(){},e.probe=function(e){return e=e.length>16384?e.subarray(0,16384):e,(0,d.findBox)(e,["moof"]).length>0},t.demux=function(e,t){this.timeOffset=t;var r=e,i=this.videoTrack,n=this.txtTrack;if(this.config.progressive){this.remainderData&&(r=(0,d.appendUint8Array)(this.remainderData,e));var a=(0,d.segmentValidRange)(r);this.remainderData=a.remainder,i.samples=a.valid||new Uint8Array}else i.samples=r;var s=this.extractID3Track(i,t);return n.samples=(0,d.parseSamples)(t,i),{videoTrack:i,audioTrack:this.audioTrack,id3Track:s,textTrack:this.txtTrack}},t.flush=function(){var e=this.timeOffset,t=this.videoTrack,r=this.txtTrack;t.samples=this.remainderData||new Uint8Array,this.remainderData=null;var i=this.extractID3Track(t,this.timeOffset);return r.samples=(0,d.parseSamples)(e,t),{videoTrack:t,audioTrack:u(),id3Track:i,textTrack:u()}},t.extractID3Track=function(e,t){var r=this.id3Track;if(e.samples.length){var i=(0,d.findBox)(e.samples,["emsg"]);i&&i.forEach((function(e){var i=(0,d.parseEmsg)(e);if(R.test(i.schemeIdUri)){var n=(0,s.isFiniteNumber)(i.presentationTime)?i.presentationTime/i.timeScale:t+i.presentationTimeDelta/i.timeScale,a=4294967295===i.eventDuration?Number.POSITIVE_INFINITY:i.eventDuration/i.timeScale;a<=.001&&(a=Number.POSITIVE_INFINITY);var o=i.payload;r.samples.push({data:o,len:o.byteLength,dts:n,pts:n,type:l.MetadataSchema.emsg,duration:a})}}))}return r},t.demuxSampleAes=function(e,t,r){return Promise.reject(new Error("The MP4 demuxer does not support SAMPLE-AES decryption"))},t.destroy=function(){},e}();var _=null,x=[32,64,96,128,160,192,224,256,288,320,352,384,416,448,32,48,56,64,80,96,112,128,160,192,224,256,320,384,32,40,48,56,64,80,96,112,128,160,192,224,256,320,32,48,56,64,80,96,112,128,144,160,176,192,224,256,8,16,24,32,40,48,56,64,80,96,112,128,144,160],I=[44100,48e3,32e3,22050,24e3,16e3,11025,12e3,8e3],w=[[0,72,144,12],[0,0,0,0],[0,72,144,12],[0,144,144,12]],C=[0,1,1,4];function P(e,t,r,i,n){if(!(r+24>t.length)){var a=O(t,r);if(a&&r+a.frameLength<=t.length){var s=i+n*(9e4*a.samplesPerFrame/a.sampleRate),o={unit:t.subarray(r,r+a.frameLength),pts:s,dts:s};return e.config=[],e.channelCount=a.channelCount,e.samplerate=a.sampleRate,e.samples.push(o),{sample:o,length:a.frameLength,missing:0}}}}function O(e,t){var r=e[t+1]>>3&3,i=e[t+1]>>1&3,n=e[t+2]>>4&15,a=e[t+2]>>2&3;if(1!==r&&0!==n&&15!==n&&3!==a){var s=e[t+2]>>1&1,o=e[t+3]>>6,l=1e3*x[14*(3===r?3-i:3===i?3:4)+n-1],u=I[3*(3===r?0:2===r?1:2)+a],d=3===o?1:2,h=w[r][i],f=C[i],c=8*h*f,v=Math.floor(h*l/u+s)*f;if(null===_){var g=(navigator.userAgent||"").match(/Chrome\/(\d+)/i);_=g?parseInt(g[1]):0}return!!_&&_<=87&&2===i&&l>=224e3&&0===o&&(e[t+3]=128|e[t+3]),{sampleRate:u,channelCount:d,frameLength:v,samplesPerFrame:c}}}function F(e,t){return 255===e[t]&&224==(224&e[t+1])&&0!=(6&e[t+1])}function M(e,t){return t+1<e.length&&F(e,t)}function N(e,t){if(t+1<e.length&&F(e,t)){var r=O(e,t),i=4;null!=r&&r.frameLength&&(i=r.frameLength);var n=t+i;return n===e.length||M(e,n)}return!1}const U=function(){function e(e){this.data=void 0,this.bytesAvailable=void 0,this.word=void 0,this.bitsAvailable=void 0,this.data=e,this.bytesAvailable=e.byteLength,this.word=0,this.bitsAvailable=0}var t=e.prototype;return t.loadWord=function(){var e=this.data,t=this.bytesAvailable,r=e.byteLength-t,i=new Uint8Array(4),n=Math.min(4,t);if(0===n)throw new Error("no bytes available");i.set(e.subarray(r,r+n)),this.word=new DataView(i.buffer).getUint32(0),this.bitsAvailable=8*n,this.bytesAvailable-=n},t.skipBits=function(e){var t;this.bitsAvailable>e?(this.word<<=e,this.bitsAvailable-=e):(e-=this.bitsAvailable,e-=(t=e>>3)>>3,this.bytesAvailable-=t,this.loadWord(),this.word<<=e,this.bitsAvailable-=e)},t.readBits=function(e){var t=Math.min(this.bitsAvailable,e),r=this.word>>>32-t;return e>32&&g.logger.error("Cannot read more than 32 bits at a time"),this.bitsAvailable-=t,this.bitsAvailable>0?this.word<<=t:this.bytesAvailable>0&&this.loadWord(),(t=e-t)>0&&this.bitsAvailable?r<<t|this.readBits(t):r},t.skipLZ=function(){var e;for(e=0;e<this.bitsAvailable;++e)if(0!=(this.word&2147483648>>>e))return this.word<<=e,this.bitsAvailable-=e,e;return this.loadWord(),e+this.skipLZ()},t.skipUEG=function(){this.skipBits(1+this.skipLZ())},t.skipEG=function(){this.skipBits(1+this.skipLZ())},t.readUEG=function(){var e=this.skipLZ();return this.readBits(e+1)-1},t.readEG=function(){var e=this.readUEG();return 1&e?1+e>>>1:-1*(e>>>1)},t.readBoolean=function(){return 1===this.readBits(1)},t.readUByte=function(){return this.readBits(8)},t.readUShort=function(){return this.readBits(16)},t.readUInt=function(){return this.readBits(32)},t.skipScalingList=function(e){for(var t=8,r=8,i=0;i<e;i++)0!==r&&(r=(t+this.readEG()+256)%256),t=0===r?t:r},t.readSPS=function(){var e,t,r,i=0,n=0,a=0,s=0,o=this.readUByte.bind(this),l=this.readBits.bind(this),u=this.readUEG.bind(this),d=this.readBoolean.bind(this),h=this.skipBits.bind(this),f=this.skipEG.bind(this),c=this.skipUEG.bind(this),v=this.skipScalingList.bind(this);o();var g=o();if(l(5),h(3),o(),c(),100===g||110===g||122===g||244===g||44===g||83===g||86===g||118===g||128===g){var p=u();if(3===p&&h(1),c(),c(),h(1),d())for(t=3!==p?8:12,r=0;r<t;r++)d()&&v(r<6?16:64)}c();var m=u();if(0===m)u();else if(1===m)for(h(1),f(),f(),e=u(),r=0;r<e;r++)f();c(),h(1);var y=u(),E=u(),T=l(1);0===T&&h(1),h(1),d()&&(i=u(),n=u(),a=u(),s=u());var S=[1,1];if(d()&&d())switch(o()){case 1:S=[1,1];break;case 2:S=[12,11];break;case 3:S=[10,11];break;case 4:S=[16,11];break;case 5:S=[40,33];break;case 6:S=[24,11];break;case 7:S=[20,11];break;case 8:S=[32,11];break;case 9:S=[80,33];break;case 10:S=[18,11];break;case 11:S=[15,11];break;case 12:S=[64,33];break;case 13:S=[160,99];break;case 14:S=[4,3];break;case 15:S=[3,2];break;case 16:S=[2,1];break;case 255:S=[o()<<8|o(),o()<<8|o()]}return{width:Math.ceil(16*(y+1)-2*i-2*n),height:(2-T)*(E+1)*16-(T?2:4)*(a+s),pixelRatio:S}},t.readSliceType=function(){return this.readUByte(),this.readUEG(),this.readUEG()},e}(),B=function(){function e(e,t,r){this.keyData=void 0,this.decrypter=void 0,this.keyData=r,this.decrypter=new a.default(e,t,{removePKCS7Padding:!1})}var t=e.prototype;return t.decryptBuffer=function(e,t){this.decrypter.decrypt(e,this.keyData.key.buffer,this.keyData.iv.buffer,t)},t.decryptAacSample=function(e,t,r,i){var n=e[t].unit;if(!(n.length<=16)){var a=n.subarray(16,n.length-n.length%16),s=a.buffer.slice(a.byteOffset,a.byteOffset+a.length),o=this;this.decryptBuffer(s,(function(a){var s=new Uint8Array(a);n.set(s,16),i||o.decryptAacSamples(e,t+1,r)}))}},t.decryptAacSamples=function(e,t,r){for(;;t++){if(t>=e.length)return void r();if(!(e[t].unit.length<32)){var i=this.decrypter.isSync();if(this.decryptAacSample(e,t,r,i),!i)return}}},t.getAvcEncryptedData=function(e){for(var t=16*Math.floor((e.length-48)/160)+16,r=new Int8Array(t),i=0,n=32;n<e.length-16;n+=160,i+=16)r.set(e.subarray(n,n+16),i);return r},t.getAvcDecryptedUnit=function(e,t){for(var r=new Uint8Array(t),i=0,n=32;n<e.length-16;n+=160,i+=16)e.set(r.subarray(i,i+16),n);return e},t.decryptAvcSample=function(e,t,r,i,n,a){var s=(0,d.discardEPB)(n.data),o=this.getAvcEncryptedData(s),l=this;this.decryptBuffer(o.buffer,(function(o){n.data=l.getAvcDecryptedUnit(s,o),a||l.decryptAvcSamples(e,t,r+1,i)}))},t.decryptAvcSamples=function(e,t,r,i){if(e instanceof Uint8Array)throw new Error("Cannot decrypt samples of type Uint8Array");for(;;t++,r=0){if(t>=e.length)return void i();for(var n=e[t].units;!(r>=n.length);r++){var a=n[r];if(!(a.data.length<=48||1!==a.type&&5!==a.type)){var s=this.decrypter.isSync();if(this.decryptAvcSample(e,t,r,i,a,s),!s)return}}}},e}();function G(){return G=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},G.apply(this,arguments)}var V=188;function H(e,t,r,i){return{key:e,frame:!1,pts:t,dts:r,units:[],debug:i,length:0}}function j(e,t){return(31&e[t+10])<<8|e[t+11]}function K(e,t,r,i){var n={audio:-1,avc:-1,id3:-1,segmentCodec:"aac"},a=t+3+((15&e[t+1])<<8|e[t+2])-4;for(t+=12+((15&e[t+10])<<8|e[t+11]);t<a;){var s=(31&e[t+1])<<8|e[t+2];switch(e[t]){case 207:if(!i){g.logger.log("ADTS AAC with AES-128-CBC frame encryption found in unencrypted stream");break}case 15:-1===n.audio&&(n.audio=s);break;case 21:-1===n.id3&&(n.id3=s);break;case 219:if(!i){g.logger.log("H.264 with AES-128-CBC slice encryption found in unencrypted stream");break}case 27:-1===n.avc&&(n.avc=s);break;case 3:case 4:!0!==r.mpeg&&!0!==r.mp3?g.logger.log("MPEG audio found, not supported in this browser"):-1===n.audio&&(n.audio=s,n.segmentCodec="mp3");break;case 36:g.logger.warn("Unsupported HEVC stream type found")}t+=5+((15&e[t+3])<<8|e[t+4])}return n}function W(e){var t,r,i,n,a,s=0,o=e.data;if(!e||0===e.size)return null;for(;o[0].length<19&&o.length>1;){var l=new Uint8Array(o[0].length+o[1].length);l.set(o[0]),l.set(o[1],o[0].length),o[0]=l,o.splice(1,1)}if(1===((t=o[0])[0]<<16)+(t[1]<<8)+t[2]){if((r=(t[4]<<8)+t[5])&&r>e.size-6)return null;var u=t[7];192&u&&(n=536870912*(14&t[9])+4194304*(255&t[10])+16384*(254&t[11])+128*(255&t[12])+(254&t[13])/2,64&u?n-(a=536870912*(14&t[14])+4194304*(255&t[15])+16384*(254&t[16])+128*(255&t[17])+(254&t[18])/2)>54e5&&(g.logger.warn(Math.round((n-a)/9e4)+"s delta between PTS and DTS, align them"),n=a):a=n);var d=(i=t[8])+9;if(e.size<=d)return null;e.size-=d;for(var h=new Uint8Array(e.size),f=0,c=o.length;f<c;f++){var v=(t=o[f]).byteLength;if(d){if(d>v){d-=v;continue}t=t.subarray(d),v-=d,d=0}h.set(t,s),s+=v}return r&&(r-=i+3),{data:h,pts:n,dts:a,len:r}}return null}function Y(e,t){if(e.units.length&&e.frame){if(void 0===e.pts){var r=t.samples,i=r.length;if(!i)return void t.dropped++;var n=r[i-1];e.pts=n.pts,e.dts=n.dts}t.samples.push(e)}e.debug.length&&g.logger.log(e.pts+"/"+e.dts+":"+e.debug)}const q=function(){function e(e,t,r){this.observer=void 0,this.config=void 0,this.typeSupported=void 0,this.sampleAes=null,this.pmtParsed=!1,this.audioCodec=void 0,this.videoCodec=void 0,this._duration=0,this._pmtId=-1,this._avcTrack=void 0,this._audioTrack=void 0,this._id3Track=void 0,this._txtTrack=void 0,this.aacOverFlow=null,this.avcSample=null,this.remainderData=null,this.observer=e,this.config=t,this.typeSupported=r}e.probe=function(t){var r=e.syncOffset(t);return r>0&&g.logger.warn("MPEG2-TS detected but first sync word found @ offset "+r),-1!==r},e.syncOffset=function(e){for(var t=Math.min(940,e.length-376)+1,r=0;r<t;){if(71===e[r]&&71===e[r+V])return r;r++}return-1},e.createTrack=function(e,t){return{container:"video"===e||"audio"===e?"video/mp2t":void 0,type:e,id:d.RemuxerTrackIdConfig[e],pid:-1,inputTimeScale:9e4,sequenceNumber:0,samples:[],dropped:0,duration:"audio"===e?t:void 0}};var t=e.prototype;return t.resetInitSegment=function(t,r,i,n){this.pmtParsed=!1,this._pmtId=-1,this._avcTrack=e.createTrack("video"),this._audioTrack=e.createTrack("audio",n),this._id3Track=e.createTrack("id3"),this._txtTrack=e.createTrack("text"),this._audioTrack.segmentCodec="aac",this.aacOverFlow=null,this.avcSample=null,this.remainderData=null,this.audioCodec=r,this.videoCodec=i,this._duration=n},t.resetTimeStamp=function(){},t.resetContiguity=function(){var e=this._audioTrack,t=this._avcTrack,r=this._id3Track;e&&(e.pesData=null),t&&(t.pesData=null),r&&(r.pesData=null),this.aacOverFlow=null,this.avcSample=null,this.remainderData=null},t.demux=function(t,r,a,s){var o;void 0===a&&(a=!1),void 0===s&&(s=!1),a||(this.sampleAes=null);var l=this._avcTrack,u=this._audioTrack,h=this._id3Track,f=this._txtTrack,c=l.pid,v=l.pesData,p=u.pid,m=h.pid,y=u.pesData,E=h.pesData,T=null,S=this.pmtParsed,b=this._pmtId,L=t.length;if(this.remainderData&&(L=(t=(0,d.appendUint8Array)(this.remainderData,t)).length,this.remainderData=null),L<V&&!s)return this.remainderData=t,{audioTrack:u,videoTrack:l,id3Track:h,textTrack:f};var A=Math.max(0,e.syncOffset(t));(L-=(L-A)%V)<t.byteLength&&!s&&(this.remainderData=new Uint8Array(t.buffer,L,t.buffer.byteLength-L));for(var D=0,R=A;R<L;R+=V)if(71===t[R]){var k=!!(64&t[R+1]),_=((31&t[R+1])<<8)+t[R+2],x=void 0;if((48&t[R+3])>>4>1){if((x=R+5+t[R+4])===R+V)continue}else x=R+4;switch(_){case c:k&&(v&&(o=W(v))&&this.parseAVCPES(l,f,o,!1),v={data:[],size:0}),v&&(v.data.push(t.subarray(x,R+V)),v.size+=R+V-x);break;case p:if(k){if(y&&(o=W(y)))switch(u.segmentCodec){case"aac":this.parseAACPES(u,o);break;case"mp3":this.parseMPEGPES(u,o)}y={data:[],size:0}}y&&(y.data.push(t.subarray(x,R+V)),y.size+=R+V-x);break;case m:k&&(E&&(o=W(E))&&this.parseID3PES(h,o),E={data:[],size:0}),E&&(E.data.push(t.subarray(x,R+V)),E.size+=R+V-x);break;case 0:k&&(x+=t[x]+1),b=this._pmtId=j(t,x);break;case b:k&&(x+=t[x]+1);var I=K(t,x,this.typeSupported,a);(c=I.avc)>0&&(l.pid=c),(p=I.audio)>0&&(u.pid=p,u.segmentCodec=I.segmentCodec),(m=I.id3)>0&&(h.pid=m),null===T||S||(g.logger.log("unknown PID '"+T+"' in TS found"),T=null,R=A-188),S=this.pmtParsed=!0;break;case 17:case 8191:break;default:T=_}}else D++;D>0&&this.observer.emit(i.Events.ERROR,i.Events.ERROR,{type:n.ErrorTypes.MEDIA_ERROR,details:n.ErrorDetails.FRAG_PARSING_ERROR,fatal:!1,reason:"Found "+D+" TS packet/s that do not start with 0x47"}),l.pesData=v,u.pesData=y,h.pesData=E;var w={audioTrack:u,videoTrack:l,id3Track:h,textTrack:f};return s&&this.extractRemainingSamples(w),w},t.flush=function(){var e,t=this.remainderData;return this.remainderData=null,e=t?this.demux(t,-1,!1,!0):{videoTrack:this._avcTrack,audioTrack:this._audioTrack,id3Track:this._id3Track,textTrack:this._txtTrack},this.extractRemainingSamples(e),this.sampleAes?this.decrypt(e,this.sampleAes):e},t.extractRemainingSamples=function(e){var t,r=e.audioTrack,i=e.videoTrack,n=e.id3Track,a=e.textTrack,s=i.pesData,o=r.pesData,l=n.pesData;if(s&&(t=W(s))?(this.parseAVCPES(i,a,t,!0),i.pesData=null):i.pesData=s,o&&(t=W(o))){switch(r.segmentCodec){case"aac":this.parseAACPES(r,t);break;case"mp3":this.parseMPEGPES(r,t)}r.pesData=null}else null!=o&&o.size&&g.logger.log("last AAC PES packet truncated,might overlap between fragments"),r.pesData=o;l&&(t=W(l))?(this.parseID3PES(n,t),n.pesData=null):n.pesData=l},t.demuxSampleAes=function(e,t,r){var i=this.demux(e,r,!0,!this.config.progressive),n=this.sampleAes=new B(this.observer,this.config,t);return this.decrypt(i,n)},t.decrypt=function(e,t){return new Promise((function(r){var i=e.audioTrack,n=e.videoTrack;i.samples&&"aac"===i.segmentCodec?t.decryptAacSamples(i.samples,0,(function(){n.samples?t.decryptAvcSamples(n.samples,0,0,(function(){r(e)})):r(e)})):n.samples&&t.decryptAvcSamples(n.samples,0,0,(function(){r(e)}))}))},t.destroy=function(){this._duration=0},t.parseAVCPES=function(e,t,r,i){var n,a=this,s=this.parseAVCNALu(e,r.data),o=this.avcSample,l=!1;r.data=null,o&&s.length&&!e.audFound&&(Y(o,e),o=this.avcSample=H(!1,r.pts,r.dts,"")),s.forEach((function(i){switch(i.type){case 1:n=!0,o||(o=a.avcSample=H(!0,r.pts,r.dts,"")),o.frame=!0;var s=i.data;if(l&&s.length>4){var u=new U(s).readSliceType();2!==u&&4!==u&&7!==u&&9!==u||(o.key=!0)}break;case 5:n=!0,o||(o=a.avcSample=H(!0,r.pts,r.dts,"")),o.key=!0,o.frame=!0;break;case 6:n=!0,(0,d.parseSEIMessageFromNALu)(i.data,1,r.pts,t.samples);break;case 7:if(n=!0,l=!0,!e.sps){var h=new U(i.data).readSPS();e.width=h.width,e.height=h.height,e.pixelRatio=h.pixelRatio,e.sps=[i.data],e.duration=a._duration;for(var f=i.data.subarray(1,4),c="avc1.",v=0;v<3;v++){var g=f[v].toString(16);g.length<2&&(g="0"+g),c+=g}e.codec=c}break;case 8:n=!0,e.pps||(e.pps=[i.data]);break;case 9:n=!1,e.audFound=!0,o&&Y(o,e),o=a.avcSample=H(!1,r.pts,r.dts,"");break;case 12:n=!0;break;default:n=!1,o&&(o.debug+="unknown NAL "+i.type+" ")}o&&n&&o.units.push(i)})),i&&o&&(Y(o,e),this.avcSample=null)},t.getLastNalUnit=function(e){var t,r,i=this.avcSample;if(i&&0!==i.units.length||(i=e[e.length-1]),null!==(t=i)&&void 0!==t&&t.units){var n=i.units;r=n[n.length-1]}return r},t.parseAVCNALu=function(e,t){var r,i,n=t.byteLength,a=e.naluState||0,s=a,o=[],l=0,u=-1,d=0;for(-1===a&&(u=0,d=31&t[0],a=0,l=1);l<n;)if(r=t[l++],a)if(1!==a)if(r)if(1===r){if(u>=0){var h={data:t.subarray(u,l-a-1),type:d};o.push(h)}else{var f=this.getLastNalUnit(e.samples);if(f&&(s&&l<=4-s&&f.state&&(f.data=f.data.subarray(0,f.data.byteLength-s)),(i=l-a-1)>0)){var c=new Uint8Array(f.data.byteLength+i);c.set(f.data,0),c.set(t.subarray(0,i),f.data.byteLength),f.data=c,f.state=0}}l<n?(u=l,d=31&t[l],a=0):a=-1}else a=0;else a=3;else a=r?0:2;else a=r?0:1;if(u>=0&&a>=0){var v={data:t.subarray(u,n),type:d,state:a};o.push(v)}if(0===o.length){var g=this.getLastNalUnit(e.samples);if(g){var p=new Uint8Array(g.data.byteLength+t.byteLength);p.set(g.data,0),p.set(t,g.data.byteLength),g.data=p}}return e.naluState=a,o},t.parseAACPES=function(e,t){var r,a,s,o,l,u=0,d=this.aacOverFlow,h=t.data;if(d){this.aacOverFlow=null;var f=d.missing,c=d.sample.unit.byteLength;if(-1===f){var v=new Uint8Array(c+h.byteLength);v.set(d.sample.unit,0),v.set(h,c),h=v}else{var p=c-f;d.sample.unit.set(h.subarray(0,f),p),e.samples.push(d.sample),u=d.missing}}for(r=u,a=h.length;r<a-1&&!E(h,r);r++);if(r===u||(r<a-1?(s="AAC PES did not start with ADTS header,offset:"+r,o=!1):(s="no ADTS header found in AAC PES",o=!0),g.logger.warn("parsing error:"+s),this.observer.emit(i.Events.ERROR,i.Events.ERROR,{type:n.ErrorTypes.MEDIA_ERROR,details:n.ErrorDetails.FRAG_PARSING_ERROR,fatal:o,reason:s}),!o)){if(S(e,this.observer,h,r,this.audioCodec),void 0!==t.pts)l=t.pts;else{if(!d)return void g.logger.warn("[tsdemuxer]: AAC PES unknown PTS");var m=b(e.samplerate);l=d.sample.pts+m}for(var y,T=0;r<a;){if(r+=(y=L(e,h,r,l,T)).length,y.missing){this.aacOverFlow=y;break}for(T++;r<a-1&&!E(h,r);r++);}}},t.parseMPEGPES=function(e,t){var r=t.data,i=r.length,n=0,a=0,s=t.pts;if(void 0!==s)for(;a<i;)if(M(r,a)){var o=P(e,r,a,s,n);if(!o)break;a+=o.length,n++}else a++;else g.logger.warn("[tsdemuxer]: MPEG PES unknown PTS")},t.parseID3PES=function(e,t){if(void 0!==t.pts){var r=G({},t,{type:this._avcTrack?l.MetadataSchema.emsg:l.MetadataSchema.audioId3,duration:Number.POSITIVE_INFINITY});e.samples.push(r)}else g.logger.warn("[tsdemuxer]: ID3 PES unknown PTS")},e}();function z(e,t){return z=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},z(e,t)}const X=function(e){var t,r;function i(){return e.apply(this,arguments)||this}r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,z(t,r);var n=i.prototype;return n.resetInitSegment=function(t,r,i,n){e.prototype.resetInitSegment.call(this,t,r,i,n),this._audioTrack={container:"audio/mpeg",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"mp3",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},i.probe=function(e){if(!e)return!1;for(var t=(o.getID3Data(e,0)||[]).length,r=e.length;t<r;t++)if(N(e,t))return g.logger.log("MPEG Audio sync word found !"),!0;return!1},n.canParse=function(e,t){return function(e,t){return F(e,t)&&4<=e.length-t}(e,t)},n.appendFrame=function(e,t,r){if(null!==this.basePTS)return P(e,t,r,this.basePTS,this.frameIndex)},i}(v),Q=function(){function e(){}return e.getSilentFrame=function(e,t){if("mp4a.40.2"===e){if(1===t)return new Uint8Array([0,200,0,128,35,128]);if(2===t)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===t)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}},e}();var $=Math.pow(2,32)-1,Z=function(){function e(){}return e.init=function(){var t;for(t in e.types={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],".mp3":[],mvex:[],mvhd:[],pasp:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]},e.types)e.types.hasOwnProperty(t)&&(e.types[t]=[t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2),t.charCodeAt(3)]);var r=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),i=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]);e.HDLR_TYPES={video:r,audio:i};var n=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),a=new Uint8Array([0,0,0,0,0,0,0,0]);e.STTS=e.STSC=e.STCO=a,e.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),e.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0]),e.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),e.STSD=new Uint8Array([0,0,0,0,0,0,0,1]);var s=new Uint8Array([105,115,111,109]),o=new Uint8Array([97,118,99,49]),l=new Uint8Array([0,0,0,1]);e.FTYP=e.box(e.types.ftyp,s,l,s,o),e.DINF=e.box(e.types.dinf,e.box(e.types.dref,n))},e.box=function(e){for(var t=8,r=arguments.length,i=new Array(r>1?r-1:0),n=1;n<r;n++)i[n-1]=arguments[n];for(var a=i.length,s=a;a--;)t+=i[a].byteLength;var o=new Uint8Array(t);for(o[0]=t>>24&255,o[1]=t>>16&255,o[2]=t>>8&255,o[3]=255&t,o.set(e,4),a=0,t=8;a<s;a++)o.set(i[a],t),t+=i[a].byteLength;return o},e.hdlr=function(t){return e.box(e.types.hdlr,e.HDLR_TYPES[t])},e.mdat=function(t){return e.box(e.types.mdat,t)},e.mdhd=function(t,r){r*=t;var i=Math.floor(r/($+1)),n=Math.floor(r%($+1));return e.box(e.types.mdhd,new Uint8Array([1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,t>>24&255,t>>16&255,t>>8&255,255&t,i>>24,i>>16&255,i>>8&255,255&i,n>>24,n>>16&255,n>>8&255,255&n,85,196,0,0]))},e.mdia=function(t){return e.box(e.types.mdia,e.mdhd(t.timescale,t.duration),e.hdlr(t.type),e.minf(t))},e.mfhd=function(t){return e.box(e.types.mfhd,new Uint8Array([0,0,0,0,t>>24,t>>16&255,t>>8&255,255&t]))},e.minf=function(t){return"audio"===t.type?e.box(e.types.minf,e.box(e.types.smhd,e.SMHD),e.DINF,e.stbl(t)):e.box(e.types.minf,e.box(e.types.vmhd,e.VMHD),e.DINF,e.stbl(t))},e.moof=function(t,r,i){return e.box(e.types.moof,e.mfhd(t),e.traf(i,r))},e.moov=function(t){for(var r=t.length,i=[];r--;)i[r]=e.trak(t[r]);return e.box.apply(null,[e.types.moov,e.mvhd(t[0].timescale,t[0].duration)].concat(i).concat(e.mvex(t)))},e.mvex=function(t){for(var r=t.length,i=[];r--;)i[r]=e.trex(t[r]);return e.box.apply(null,[e.types.mvex].concat(i))},e.mvhd=function(t,r){r*=t;var i=Math.floor(r/($+1)),n=Math.floor(r%($+1)),a=new Uint8Array([1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,t>>24&255,t>>16&255,t>>8&255,255&t,i>>24,i>>16&255,i>>8&255,255&i,n>>24,n>>16&255,n>>8&255,255&n,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]);return e.box(e.types.mvhd,a)},e.sdtp=function(t){var r,i,n=t.samples||[],a=new Uint8Array(4+n.length);for(r=0;r<n.length;r++)i=n[r].flags,a[r+4]=i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy;return e.box(e.types.sdtp,a)},e.stbl=function(t){return e.box(e.types.stbl,e.stsd(t),e.box(e.types.stts,e.STTS),e.box(e.types.stsc,e.STSC),e.box(e.types.stsz,e.STSZ),e.box(e.types.stco,e.STCO))},e.avc1=function(t){var r,i,n,a=[],s=[];for(r=0;r<t.sps.length;r++)n=(i=t.sps[r]).byteLength,a.push(n>>>8&255),a.push(255&n),a=a.concat(Array.prototype.slice.call(i));for(r=0;r<t.pps.length;r++)n=(i=t.pps[r]).byteLength,s.push(n>>>8&255),s.push(255&n),s=s.concat(Array.prototype.slice.call(i));var o=e.box(e.types.avcC,new Uint8Array([1,a[3],a[4],a[5],255,224|t.sps.length].concat(a).concat([t.pps.length]).concat(s))),l=t.width,u=t.height,d=t.pixelRatio[0],h=t.pixelRatio[1];return e.box(e.types.avc1,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,l>>8&255,255&l,u>>8&255,255&u,0,72,0,0,0,72,0,0,0,0,0,0,0,1,18,100,97,105,108,121,109,111,116,105,111,110,47,104,108,115,46,106,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,17,17]),o,e.box(e.types.btrt,new Uint8Array([0,28,156,128,0,45,198,192,0,45,198,192])),e.box(e.types.pasp,new Uint8Array([d>>24,d>>16&255,d>>8&255,255&d,h>>24,h>>16&255,h>>8&255,255&h])))},e.esds=function(e){var t=e.config.length;return new Uint8Array([0,0,0,0,3,23+t,0,1,0,4,15+t,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([t]).concat(e.config).concat([6,1,2]))},e.mp4a=function(t){var r=t.samplerate;return e.box(e.types.mp4a,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t.channelCount,0,16,0,0,0,0,r>>8&255,255&r,0,0]),e.box(e.types.esds,e.esds(t)))},e.mp3=function(t){var r=t.samplerate;return e.box(e.types[".mp3"],new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t.channelCount,0,16,0,0,0,0,r>>8&255,255&r,0,0]))},e.stsd=function(t){return"audio"===t.type?"mp3"===t.segmentCodec&&"mp3"===t.codec?e.box(e.types.stsd,e.STSD,e.mp3(t)):e.box(e.types.stsd,e.STSD,e.mp4a(t)):e.box(e.types.stsd,e.STSD,e.avc1(t))},e.tkhd=function(t){var r=t.id,i=t.duration*t.timescale,n=t.width,a=t.height,s=Math.floor(i/($+1)),o=Math.floor(i%($+1));return e.box(e.types.tkhd,new Uint8Array([1,0,0,7,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,r>>24&255,r>>16&255,r>>8&255,255&r,0,0,0,0,s>>24,s>>16&255,s>>8&255,255&s,o>>24,o>>16&255,o>>8&255,255&o,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,n>>8&255,255&n,0,0,a>>8&255,255&a,0,0]))},e.traf=function(t,r){var i=e.sdtp(t),n=t.id,a=Math.floor(r/($+1)),s=Math.floor(r%($+1));return e.box(e.types.traf,e.box(e.types.tfhd,new Uint8Array([0,0,0,0,n>>24,n>>16&255,n>>8&255,255&n])),e.box(e.types.tfdt,new Uint8Array([1,0,0,0,a>>24,a>>16&255,a>>8&255,255&a,s>>24,s>>16&255,s>>8&255,255&s])),e.trun(t,i.length+16+20+8+16+8+8),i)},e.trak=function(t){return t.duration=t.duration||4294967295,e.box(e.types.trak,e.tkhd(t),e.mdia(t))},e.trex=function(t){var r=t.id;return e.box(e.types.trex,new Uint8Array([0,0,0,0,r>>24,r>>16&255,r>>8&255,255&r,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]))},e.trun=function(t,r){var i,n,a,s,o,l,u=t.samples||[],d=u.length,h=12+16*d,f=new Uint8Array(h);for(r+=8+h,f.set(["video"===t.type?1:0,0,15,1,d>>>24&255,d>>>16&255,d>>>8&255,255&d,r>>>24&255,r>>>16&255,r>>>8&255,255&r],0),i=0;i<d;i++)a=(n=u[i]).duration,s=n.size,o=n.flags,l=n.cts,f.set([a>>>24&255,a>>>16&255,a>>>8&255,255&a,s>>>24&255,s>>>16&255,s>>>8&255,255&s,o.isLeading<<2|o.dependsOn,o.isDependedOn<<6|o.hasRedundancy<<4|o.paddingValue<<1|o.isNonSync,61440&o.degradPrio,15&o.degradPrio,l>>>24&255,l>>>16&255,l>>>8&255,255&l],12+16*i);return e.box(e.types.trun,f)},e.initSegment=function(t){e.types||e.init();var r=e.moov(t),i=new Uint8Array(e.FTYP.byteLength+r.byteLength);return i.set(e.FTYP),i.set(r,e.FTYP.byteLength),i},e}();Z.types=void 0,Z.HDLR_TYPES=void 0,Z.STTS=void 0,Z.STSC=void 0,Z.STCO=void 0,Z.STSZ=void 0,Z.VMHD=void 0,Z.SMHD=void 0,Z.STSD=void 0,Z.FTYP=void 0,Z.DINF=void 0;const J=Z;var ee=r(308);function te(e,t){return void 0===t&&(t=!1),function(e,t,r,i){void 0===r&&(r=1),void 0===i&&(i=!1);var n=e*t*r;return i?Math.round(n):n}(e,1e3,1/9e4,t)}function re(){return re=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},re.apply(this,arguments)}var ie=null,ne=null,ae=function(){function e(e,t,r,i){if(void 0===i&&(i=""),this.observer=void 0,this.config=void 0,this.typeSupported=void 0,this.ISGenerated=!1,this._initPTS=void 0,this._initDTS=void 0,this.nextAvcDts=null,this.nextAudioPts=null,this.videoSampleDuration=null,this.isAudioContiguous=!1,this.isVideoContiguous=!1,this.observer=e,this.config=t,this.typeSupported=r,this.ISGenerated=!1,null===ie){var n=(navigator.userAgent||"").match(/Chrome\/(\d+)/i);ie=n?parseInt(n[1]):0}if(null===ne){var a=navigator.userAgent.match(/Safari\/(\d+)/i);ne=a?parseInt(a[1]):0}}var t=e.prototype;return t.destroy=function(){},t.resetTimeStamp=function(e){g.logger.log("[mp4-remuxer]: initPTS & initDTS reset"),this._initPTS=this._initDTS=e},t.resetNextTimestamp=function(){g.logger.log("[mp4-remuxer]: reset next timestamp"),this.isVideoContiguous=!1,this.isAudioContiguous=!1},t.resetInitSegment=function(){g.logger.log("[mp4-remuxer]: ISGenerated flag reset"),this.ISGenerated=!1},t.getVideoStartPts=function(e){var t=!1,r=e.reduce((function(e,r){var i=r.pts-e;return i<-4294967296?(t=!0,se(e,r.pts)):i>0?e:r.pts}),e[0].pts);return t&&g.logger.debug("PTS rollover detected"),r},t.remux=function(e,t,r,i,n,a,s,o){var l,u,d,h,f,c,v=n,p=n,m=e.pid>-1,y=t.pid>-1,E=t.samples.length,T=e.samples.length>0,S=s&&E>0||E>1;if((!m||T)&&(!y||S)||this.ISGenerated||s){this.ISGenerated||(d=this.generateIS(e,t,n));var b,L=this.isVideoContiguous,A=-1;if(S&&(A=function(e){for(var t=0;t<e.length;t++)if(e[t].key)return t;return-1}(t.samples),!L&&this.config.forceKeyFrameOnDiscontinuity))if(c=!0,A>0){g.logger.warn("[mp4-remuxer]: Dropped "+A+" out of "+E+" video samples due to a missing keyframe");var D=this.getVideoStartPts(t.samples);t.samples=t.samples.slice(A),t.dropped+=A,b=p+=(t.samples[0].pts-D)/t.inputTimeScale}else-1===A&&(g.logger.warn("[mp4-remuxer]: No keyframe found out of "+E+" video samples"),c=!1);if(this.ISGenerated){if(T&&S){var R=this.getVideoStartPts(t.samples),k=(se(e.samples[0].pts,R)-R)/t.inputTimeScale;v+=Math.max(0,k),p+=Math.max(0,-k)}if(T){if(e.samplerate||(g.logger.warn("[mp4-remuxer]: regenerate InitSegment as audio detected"),d=this.generateIS(e,t,n)),u=this.remuxAudio(e,v,this.isAudioContiguous,a,y||S||o===ee.PlaylistLevelType.AUDIO?p:void 0),S){var _=u?u.endPTS-u.startPTS:0;t.inputTimeScale||(g.logger.warn("[mp4-remuxer]: regenerate InitSegment as video detected"),d=this.generateIS(e,t,n)),l=this.remuxVideo(t,p,L,_)}}else S&&(l=this.remuxVideo(t,p,L,0));l&&(l.firstKeyFrame=A,l.independent=-1!==A,l.firstKeyFramePTS=b)}}return this.ISGenerated&&(r.samples.length&&(f=oe(r,n,this._initPTS,this._initDTS)),i.samples.length&&(h=le(i,n,this._initPTS))),{audio:u,video:l,initSegment:d,independent:c,text:h,id3:f}},t.generateIS=function(e,t,r){var i,n,a,o=e.samples,l=t.samples,u=this.typeSupported,d={},h=!(0,s.isFiniteNumber)(this._initPTS),f="audio/mp4";if(h&&(i=n=1/0),e.config&&o.length&&(e.timescale=e.samplerate,"mp3"===e.segmentCodec&&(u.mpeg?(f="audio/mpeg",e.codec=""):u.mp3&&(e.codec="mp3")),d.audio={id:"audio",container:f,codec:e.codec,initSegment:"mp3"===e.segmentCodec&&u.mpeg?new Uint8Array(0):J.initSegment([e]),metadata:{channelCount:e.channelCount}},h&&(a=e.inputTimeScale,i=n=o[0].pts-Math.round(a*r))),t.sps&&t.pps&&l.length&&(t.timescale=t.inputTimeScale,d.video={id:"main",container:"video/mp4",codec:t.codec,initSegment:J.initSegment([t]),metadata:{width:t.width,height:t.height}},h)){a=t.inputTimeScale;var c=this.getVideoStartPts(l),v=Math.round(a*r);n=Math.min(n,se(l[0].dts,c)-v),i=Math.min(i,c-v)}if(Object.keys(d).length)return this.ISGenerated=!0,h&&(this._initPTS=i,this._initDTS=n),{tracks:d,initPTS:i,timescale:a}},t.remuxVideo=function(e,t,r,a){var s,o,l=e.inputTimeScale,u=e.samples,d=[],h=u.length,f=this._initPTS,c=this.nextAvcDts,v=8,p=this.videoSampleDuration,m=Number.POSITIVE_INFINITY,y=Number.NEGATIVE_INFINITY,E=!1;r&&null!==c||(c=t*l-(u[0].pts-se(u[0].dts,u[0].pts)));for(var T=0;T<h;T++){var S=u[T];S.pts=se(S.pts-f,c),S.dts=se(S.dts-f,c),S.dts<u[T>0?T-1:T].dts&&(E=!0)}E&&u.sort((function(e,t){var r=e.dts-t.dts,i=e.pts-t.pts;return r||i})),s=u[0].dts;var b=u[u.length-1].dts-s,L=b?Math.round(b/(h-1)):p||e.inputTimeScale/30;if(r){var A=s-c,D=A>L,R=A<-1;if((D||R)&&(D?g.logger.warn("AVC: "+te(A,!0)+" ms ("+A+"dts) hole between fragments detected, filling it"):g.logger.warn("AVC: "+te(-A,!0)+" ms ("+A+"dts) overlapping between fragments detected"),!R||c>u[0].pts)){s=c;var k=u[0].pts-A;u[0].dts=s,u[0].pts=k,g.logger.log("Video: First PTS/DTS adjusted: "+te(k,!0)+"/"+te(s,!0)+", delta: "+te(A,!0)+" ms")}}s=Math.max(0,s);for(var _=0,x=0,I=0;I<h;I++){for(var w=u[I],C=w.units,P=C.length,O=0,F=0;F<P;F++)O+=C[F].data.length;x+=O,_+=P,w.length=O,w.dts=Math.max(w.dts,s),m=Math.min(w.pts,m),y=Math.max(w.pts,y)}o=u[h-1].dts;var M,N=x+4*_+8;try{M=new Uint8Array(N)}catch(e){return void this.observer.emit(i.Events.ERROR,i.Events.ERROR,{type:n.ErrorTypes.MUX_ERROR,details:n.ErrorDetails.REMUX_ALLOC_ERROR,fatal:!1,bytes:N,reason:"fail allocating video mdat "+N})}var U=new DataView(M.buffer);U.setUint32(0,N),M.set(J.types.mdat,4);for(var B=!1,G=Number.POSITIVE_INFINITY,V=Number.POSITIVE_INFINITY,H=Number.NEGATIVE_INFINITY,j=Number.NEGATIVE_INFINITY,K=0;K<h;K++){for(var W=u[K],Y=W.units,q=0,z=0,X=Y.length;z<X;z++){var Q=Y[z],$=Q.data,Z=Q.data.byteLength;U.setUint32(v,Z),v+=4,M.set($,v),v+=Z,q+=4+Z}var ee=void 0;if(K<h-1)p=u[K+1].dts-W.dts,ee=u[K+1].pts-W.pts;else{var ae=this.config,oe=K>0?W.dts-u[K-1].dts:L;if(ee=K>0?W.pts-u[K-1].pts:L,ae.stretchShortVideoTrack&&null!==this.nextAudioPts){var le=Math.floor(ae.maxBufferHole*l),de=(a?m+a*l:this.nextAudioPts)-W.pts;de>le?((p=de-oe)<0?p=oe:B=!0,g.logger.log("[mp4-remuxer]: It is approximately "+de/90+" ms to the next segment; using duration "+p/90+" ms for the last video frame.")):p=oe}else p=oe}var he=Math.round(W.pts-W.dts);G=Math.min(G,p),H=Math.max(H,p),V=Math.min(V,ee),j=Math.max(j,ee),d.push(new ue(W.key,p,q,he))}if(d.length)if(ie){if(ie<70){var fe=d[0].flags;fe.dependsOn=2,fe.isNonSync=0}}else if(ne&&j-V<H-G&&L/H<.025&&0===d[0].cts){g.logger.warn("Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.");for(var ce=s,ve=0,ge=d.length;ve<ge;ve++){var pe=ce+d[ve].duration,me=ce+d[ve].cts;if(ve<ge-1){var ye=pe+d[ve+1].cts;d[ve].duration=ye-me}else d[ve].duration=ve?d[ve-1].duration:L;d[ve].cts=0,ce=pe}}p=B||!p?L:p,this.nextAvcDts=c=o+p,this.videoSampleDuration=p,this.isVideoContiguous=!0;var Ee={data1:J.moof(e.sequenceNumber++,s,re({},e,{samples:d})),data2:M,startPTS:m/l,endPTS:(y+p)/l,startDTS:s/l,endDTS:c/l,type:"video",hasAudio:!1,hasVideo:!0,nb:d.length,dropped:e.dropped};return e.samples=[],e.dropped=0,Ee},t.remuxAudio=function(e,t,r,a,s){var o=e.inputTimeScale,l=o/(e.samplerate?e.samplerate:o),u="aac"===e.segmentCodec?1024:1152,d=u*l,h=this._initPTS,f="mp3"===e.segmentCodec&&this.typeSupported.mpeg,c=[],v=void 0!==s,p=e.samples,m=f?0:8,y=this.nextAudioPts||-1,E=t*o;if(this.isAudioContiguous=r=r||p.length&&y>0&&(a&&Math.abs(E-y)<9e3||Math.abs(se(p[0].pts-h,E)-y)<20*d),p.forEach((function(e){e.pts=se(e.pts-h,E)})),!r||y<0){if(p=p.filter((function(e){return e.pts>=0})),!p.length)return;y=0===s?0:a&&!v?Math.max(0,E):p[0].pts}if("aac"===e.segmentCodec)for(var T=this.config.maxAudioFramesDrift,S=0,b=y;S<p.length;S++){var L=p[S],A=L.pts,D=A-b,R=Math.abs(1e3*D/o);if(D<=-T*d&&v)0===S&&(g.logger.warn("Audio frame @ "+(A/o).toFixed(3)+"s overlaps nextAudioPts by "+Math.round(1e3*D/o)+" ms."),this.nextAudioPts=y=b=A);else if(D>=T*d&&R<1e4&&v){var k=Math.round(D/d);(b=A-k*d)<0&&(k--,b+=d),0===S&&(this.nextAudioPts=y=b),g.logger.warn("[mp4-remuxer]: Injecting "+k+" audio frame @ "+(b/o).toFixed(3)+"s due to "+Math.round(1e3*D/o)+" ms gap.");for(var _=0;_<k;_++){var x=Math.max(b,0),I=Q.getSilentFrame(e.manifestCodec||e.codec,e.channelCount);I||(g.logger.log("[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead."),I=L.unit.subarray()),p.splice(S,0,{unit:I,pts:x}),b+=d,S++}}L.pts=b,b+=d}for(var w,C=null,P=null,O=0,F=p.length;F--;)O+=p[F].unit.byteLength;for(var M=0,N=p.length;M<N;M++){var U=p[M],B=U.unit,G=U.pts;if(null!==P)c[M-1].duration=Math.round((G-P)/l);else{if(r&&"aac"===e.segmentCodec&&(G=y),C=G,!(O>0))return;O+=m;try{w=new Uint8Array(O)}catch(e){return void this.observer.emit(i.Events.ERROR,i.Events.ERROR,{type:n.ErrorTypes.MUX_ERROR,details:n.ErrorDetails.REMUX_ALLOC_ERROR,fatal:!1,bytes:O,reason:"fail allocating audio mdat "+O})}f||(new DataView(w.buffer).setUint32(0,O),w.set(J.types.mdat,4))}w.set(B,m);var V=B.byteLength;m+=V,c.push(new ue(!0,u,V,0)),P=G}var H=c.length;if(H){var j=c[c.length-1];this.nextAudioPts=y=P+l*j.duration;var K=f?new Uint8Array(0):J.moof(e.sequenceNumber++,C/l,re({},e,{samples:c}));e.samples=[];var W=C/o,Y=y/o,q={data1:K,data2:w,startPTS:W,endPTS:Y,startDTS:W,endDTS:Y,type:"audio",hasAudio:!0,hasVideo:!1,nb:H};return this.isAudioContiguous=!0,q}},t.remuxEmptyAudio=function(e,t,r,i){var n=e.inputTimeScale,a=n/(e.samplerate?e.samplerate:n),s=this.nextAudioPts,o=(null!==s?s:i.startDTS*n)+this._initDTS,l=i.endDTS*n+this._initDTS,u=1024*a,d=Math.ceil((l-o)/u),h=Q.getSilentFrame(e.manifestCodec||e.codec,e.channelCount);if(g.logger.warn("[mp4-remuxer]: remux empty Audio"),h){for(var f=[],c=0;c<d;c++){var v=o+c*u;f.push({unit:h,pts:v,dts:v})}return e.samples=f,this.remuxAudio(e,t,r,!1)}g.logger.trace("[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec")},e}();function se(e,t){var r;if(null===t)return e;for(r=t<e?-8589934592:8589934592;Math.abs(e-t)>4294967296;)e+=r;return e}function oe(e,t,r,i){var n=e.samples.length;if(n){for(var a=e.inputTimeScale,s=0;s<n;s++){var o=e.samples[s];o.pts=se(o.pts-r,t*a)/a,o.dts=se(o.dts-i,t*a)/a}var l=e.samples;return e.samples=[],{samples:l}}}function le(e,t,r){var i=e.samples.length;if(i){for(var n=e.inputTimeScale,a=0;a<i;a++){var s=e.samples[a];s.pts=se(s.pts-r,t*n)/n}e.samples.sort((function(e,t){return e.pts-t.pts}));var o=e.samples;return e.samples=[],{samples:o}}}var ue=function(e,t,r,i){this.size=void 0,this.duration=void 0,this.cts=void 0,this.flags=void 0,this.duration=t,this.size=r,this.cts=i,this.flags=new de(e)},de=function(e){this.isLeading=0,this.isDependedOn=0,this.hasRedundancy=0,this.degradPrio=0,this.dependsOn=1,this.isNonSync=1,this.dependsOn=e?2:1,this.isNonSync=e?0:1},he=r(923);function fe(e,t){var r=null==e?void 0:e.codec;return r&&r.length>4?r:"hvc1"===r||"hev1"===r?"hvc1.1.c.L120.90":"av01"===r?"av01.0.04M.08":"avc1"===r||t===he.ElementaryStreamTypes.VIDEO?"avc1.42e01e":"mp4a.40.5"}const ce=function(){function e(){this.emitInitSegment=!1,this.audioCodec=void 0,this.videoCodec=void 0,this.initData=void 0,this.initPTS=void 0,this.initTracks=void 0,this.lastEndTime=null}var t=e.prototype;return t.destroy=function(){},t.resetTimeStamp=function(e){this.initPTS=e,this.lastEndTime=null},t.resetNextTimestamp=function(){this.lastEndTime=null},t.resetInitSegment=function(e,t,r){this.audioCodec=t,this.videoCodec=r,this.generateInitSegment(e),this.emitInitSegment=!0},t.generateInitSegment=function(e){var t=this.audioCodec,r=this.videoCodec;if(!e||!e.byteLength)return this.initTracks=void 0,void(this.initData=void 0);var i=this.initData=(0,d.parseInitSegment)(e);t||(t=fe(i.audio,he.ElementaryStreamTypes.AUDIO)),r||(r=fe(i.video,he.ElementaryStreamTypes.VIDEO));var n={};i.audio&&i.video?n.audiovideo={container:"video/mp4",codec:t+","+r,initSegment:e,id:"main"}:i.audio?n.audio={container:"audio/mp4",codec:t,initSegment:e,id:"audio"}:i.video?n.video={container:"video/mp4",codec:r,initSegment:e,id:"main"}:g.logger.warn("[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes."),this.initTracks=n},t.remux=function(e,t,r,i,n){var a,o=this.initPTS,l=this.lastEndTime,u={audio:void 0,video:void 0,text:i,id3:r,initSegment:void 0};(0,s.isFiniteNumber)(l)||(l=this.lastEndTime=n||0);var h=t.samples;if(!h||!h.length)return u;var f={initPTS:void 0,timescale:1},c=this.initData;if(c&&c.length||(this.generateInitSegment(h),c=this.initData),!c||!c.length)return g.logger.warn("[passthrough-remuxer.ts]: Failed to generate initSegment."),u;this.emitInitSegment&&(f.tracks=this.initTracks,this.emitInitSegment=!1);var v=(0,d.getStartDTS)(c,h);(0,s.isFiniteNumber)(o)||(this.initPTS=f.initPTS=o=v-n);var p=(0,d.getDuration)(h,c),m=e?v-o:l,y=m+p;(0,d.offsetStartDTS)(c,h,o),p>0?this.lastEndTime=y:(g.logger.warn("Duration parsed from mp4 should be greater than zero"),this.resetNextTimestamp());var E=!!c.audio,T=!!c.video,S="";E&&(S+="audio"),T&&(S+="video");var b={data1:h,startPTS:m,startDTS:m,endPTS:y,endDTS:y,type:S,hasAudio:E,hasVideo:T,nb:1,dropped:0};u.audio="audio"===b.type?b:void 0,u.video="audio"!==b.type?b:void 0,u.initSegment=f;var L=null!=(a=this.initPTS)?a:0;return u.id3=oe(r,n,L,L),i.samples.length&&(u.text=le(i,n,L)),u},e}();var ve;try{ve=self.performance.now.bind(self.performance)}catch(e){g.logger.debug("Unable to use Performance API on this environment"),ve=self.Date.now}var ge=[{demux:q,remux:ae},{demux:k,remux:ce},{demux:D,remux:ae},{demux:X,remux:ae}],pe=function(){function e(e,t,r,i,n){this.observer=void 0,this.typeSupported=void 0,this.config=void 0,this.vendor=void 0,this.id=void 0,this.demuxer=void 0,this.remuxer=void 0,this.decrypter=void 0,this.probe=void 0,this.decryptionPromise=null,this.transmuxConfig=void 0,this.currentTransmuxState=void 0,this.observer=e,this.typeSupported=t,this.config=r,this.vendor=i,this.id=n}var t=e.prototype;return t.configure=function(e){this.transmuxConfig=e,this.decrypter&&this.decrypter.reset()},t.push=function(e,t,r,i){var n=this,a=r.transmuxing;a.executeStart=ve();var s=new Uint8Array(e),o=this.config,l=this.currentTransmuxState,u=this.transmuxConfig;i&&(this.currentTransmuxState=i);var d=i||l,h=d.contiguous,f=d.discontinuity,c=d.trackSwitch,v=d.accurateTimeOffset,g=d.timeOffset,p=d.initSegmentChange,m=u.audioCodec,y=u.videoCodec,E=u.defaultInitPts,T=u.duration,S=u.initSegmentData;(f||c||p)&&this.resetInitSegment(S,m,y,T),(f||p)&&this.resetInitialTimestamp(E),h||this.resetContiguity();var b=function(e,t){var r=null;return e.byteLength>0&&null!=t&&null!=t.key&&null!==t.iv&&null!=t.method&&(r=t),r}(s,t);if(b&&"AES-128"===b.method){var L=this.getDecrypter();if(!o.enableSoftwareAES)return this.decryptionPromise=L.webCryptoDecrypt(s,b.key.buffer,b.iv.buffer).then((function(e){var t=n.push(e,null,r);return n.decryptionPromise=null,t})),this.decryptionPromise;var A=L.softwareDecrypt(s,b.key.buffer,b.iv.buffer);if(!A)return a.executeEnd=ve(),me(r);s=new Uint8Array(A)}this.needsProbing(s,f,c)&&this.configureTransmuxer(s,u);var D=this.transmux(s,b,g,v,r),R=this.currentTransmuxState;return R.contiguous=!0,R.discontinuity=!1,R.trackSwitch=!1,a.executeEnd=ve(),D},t.flush=function(e){var t=this,r=e.transmuxing;r.executeStart=ve();var a=this.decrypter,s=this.currentTransmuxState,o=this.decryptionPromise;if(o)return o.then((function(){return t.flush(e)}));var l=[],u=s.timeOffset;if(a){var d=a.flush();d&&l.push(this.push(d,null,e))}var h=this.demuxer,f=this.remuxer;if(!h||!f)return this.observer.emit(i.Events.ERROR,i.Events.ERROR,{type:n.ErrorTypes.MEDIA_ERROR,details:n.ErrorDetails.FRAG_PARSING_ERROR,fatal:!0,reason:"no demux matching with content found"}),r.executeEnd=ve(),[me(e)];var c=h.flush(u);return ye(c)?c.then((function(r){return t.flushRemux(l,r,e),l})):(this.flushRemux(l,c,e),l)},t.flushRemux=function(e,t,r){var i=t.audioTrack,n=t.videoTrack,a=t.id3Track,s=t.textTrack,o=this.currentTransmuxState,l=o.accurateTimeOffset,u=o.timeOffset;g.logger.log("[transmuxer.ts]: Flushed fragment "+r.sn+(r.part>-1?" p: "+r.part:"")+" of level "+r.level);var d=this.remuxer.remux(i,n,a,s,u,l,!0,this.id);e.push({remuxResult:d,chunkMeta:r}),r.transmuxing.executeEnd=ve()},t.resetInitialTimestamp=function(e){var t=this.demuxer,r=this.remuxer;t&&r&&(t.resetTimeStamp(e),r.resetTimeStamp(e))},t.resetContiguity=function(){var e=this.demuxer,t=this.remuxer;e&&t&&(e.resetContiguity(),t.resetNextTimestamp())},t.resetInitSegment=function(e,t,r,i){var n=this.demuxer,a=this.remuxer;n&&a&&(n.resetInitSegment(e,t,r,i),a.resetInitSegment(e,t,r))},t.destroy=function(){this.demuxer&&(this.demuxer.destroy(),this.demuxer=void 0),this.remuxer&&(this.remuxer.destroy(),this.remuxer=void 0)},t.transmux=function(e,t,r,i,n){return t&&"SAMPLE-AES"===t.method?this.transmuxSampleAes(e,t,r,i,n):this.transmuxUnencrypted(e,r,i,n)},t.transmuxUnencrypted=function(e,t,r,i){var n=this.demuxer.demux(e,t,!1,!this.config.progressive),a=n.audioTrack,s=n.videoTrack,o=n.id3Track,l=n.textTrack;return{remuxResult:this.remuxer.remux(a,s,o,l,t,r,!1,this.id),chunkMeta:i}},t.transmuxSampleAes=function(e,t,r,i,n){var a=this;return this.demuxer.demuxSampleAes(e,t,r).then((function(e){return{remuxResult:a.remuxer.remux(e.audioTrack,e.videoTrack,e.id3Track,e.textTrack,r,i,!1,a.id),chunkMeta:n}}))},t.configureTransmuxer=function(e,t){for(var r,i=this.config,n=this.observer,a=this.typeSupported,s=this.vendor,o=t.audioCodec,l=t.defaultInitPts,u=t.duration,d=t.initSegmentData,h=t.videoCodec,f=0,c=ge.length;f<c;f++)if(ge[f].demux.probe(e)){r=ge[f];break}r||(g.logger.warn("Failed to find demuxer by probing frag, treating as mp4 passthrough"),r={demux:k,remux:ce});var v=this.demuxer,p=this.remuxer,m=r.remux,y=r.demux;p&&p instanceof m||(this.remuxer=new m(n,i,a,s)),v&&v instanceof y||(this.demuxer=new y(n,i,a),this.probe=y.probe),this.resetInitSegment(d,o,h,u),this.resetInitialTimestamp(l)},t.needsProbing=function(e,t,r){return!this.demuxer||!this.remuxer||t||r},t.getDecrypter=function(){var e=this.decrypter;return e||(e=this.decrypter=new a.default(this.observer,this.config)),e},e}(),me=function(e){return{remuxResult:{},chunkMeta:e}};function ye(e){return"then"in e&&e.then instanceof Function}var Ee=function(e,t,r,i,n){this.audioCodec=void 0,this.videoCodec=void 0,this.initSegmentData=void 0,this.duration=void 0,this.defaultInitPts=void 0,this.audioCodec=e,this.videoCodec=t,this.initSegmentData=r,this.duration=i,this.defaultInitPts=n},Te=function(e,t,r,i,n,a){this.discontinuity=void 0,this.contiguous=void 0,this.accurateTimeOffset=void 0,this.trackSwitch=void 0,this.timeOffset=void 0,this.initSegmentChange=void 0,this.discontinuity=e,this.contiguous=t,this.accurateTimeOffset=r,this.trackSwitch=i,this.timeOffset=n,this.initSegmentChange=a}},514:e=>{e.exports=void 0},973:(e,t,r)=>{"use strict";var i,n;r.r(t),r.d(t,{ErrorDetails:()=>n,ErrorTypes:()=>i}),function(e){e.NETWORK_ERROR="networkError",e.MEDIA_ERROR="mediaError",e.KEY_SYSTEM_ERROR="keySystemError",e.MUX_ERROR="muxError",e.OTHER_ERROR="otherError"}(i||(i={})),function(e){e.KEY_SYSTEM_NO_KEYS="keySystemNoKeys",e.KEY_SYSTEM_NO_ACCESS="keySystemNoAccess",e.KEY_SYSTEM_NO_SESSION="keySystemNoSession",e.KEY_SYSTEM_LICENSE_REQUEST_FAILED="keySystemLicenseRequestFailed",e.KEY_SYSTEM_NO_INIT_DATA="keySystemNoInitData",e.MANIFEST_LOAD_ERROR="manifestLoadError",e.MANIFEST_LOAD_TIMEOUT="manifestLoadTimeOut",e.MANIFEST_PARSING_ERROR="manifestParsingError",e.MANIFEST_INCOMPATIBLE_CODECS_ERROR="manifestIncompatibleCodecsError",e.LEVEL_EMPTY_ERROR="levelEmptyError",e.LEVEL_LOAD_ERROR="levelLoadError",e.LEVEL_LOAD_TIMEOUT="levelLoadTimeOut",e.LEVEL_SWITCH_ERROR="levelSwitchError",e.AUDIO_TRACK_LOAD_ERROR="audioTrackLoadError",e.AUDIO_TRACK_LOAD_TIMEOUT="audioTrackLoadTimeOut",e.SUBTITLE_LOAD_ERROR="subtitleTrackLoadError",e.SUBTITLE_TRACK_LOAD_TIMEOUT="subtitleTrackLoadTimeOut",e.FRAG_LOAD_ERROR="fragLoadError",e.FRAG_LOAD_TIMEOUT="fragLoadTimeOut",e.FRAG_DECRYPT_ERROR="fragDecryptError",e.FRAG_PARSING_ERROR="fragParsingError",e.REMUX_ALLOC_ERROR="remuxAllocError",e.KEY_LOAD_ERROR="keyLoadError",e.KEY_LOAD_TIMEOUT="keyLoadTimeOut",e.BUFFER_ADD_CODEC_ERROR="bufferAddCodecError",e.BUFFER_INCOMPATIBLE_CODECS_ERROR="bufferIncompatibleCodecsError",e.BUFFER_APPEND_ERROR="bufferAppendError",e.BUFFER_APPENDING_ERROR="bufferAppendingError",e.BUFFER_STALLED_ERROR="bufferStalledError",e.BUFFER_FULL_ERROR="bufferFullError",e.BUFFER_SEEK_OVER_HOLE="bufferSeekOverHole",e.BUFFER_NUDGE_ON_STALL="bufferNudgeOnStall",e.INTERNAL_EXCEPTION="internalException",e.INTERNAL_ABORTED="aborted",e.UNKNOWN="unknown"}(n||(n={}))},851:(e,t,r)=>{"use strict";var i;r.r(t),r.d(t,{Events:()=>i}),function(e){e.MEDIA_ATTACHING="hlsMediaAttaching",e.MEDIA_ATTACHED="hlsMediaAttached",e.MEDIA_DETACHING="hlsMediaDetaching",e.MEDIA_DETACHED="hlsMediaDetached",e.BUFFER_RESET="hlsBufferReset",e.BUFFER_CODECS="hlsBufferCodecs",e.BUFFER_CREATED="hlsBufferCreated",e.BUFFER_APPENDING="hlsBufferAppending",e.BUFFER_APPENDED="hlsBufferAppended",e.BUFFER_EOS="hlsBufferEos",e.BUFFER_FLUSHING="hlsBufferFlushing",e.BUFFER_FLUSHED="hlsBufferFlushed",e.MANIFEST_LOADING="hlsManifestLoading",e.MANIFEST_LOADED="hlsManifestLoaded",e.MANIFEST_PARSED="hlsManifestParsed",e.LEVEL_SWITCHING="hlsLevelSwitching",e.LEVEL_SWITCHED="hlsLevelSwitched",e.LEVEL_LOADING="hlsLevelLoading",e.LEVEL_LOADED="hlsLevelLoaded",e.LEVEL_UPDATED="hlsLevelUpdated",e.LEVEL_PTS_UPDATED="hlsLevelPtsUpdated",e.LEVELS_UPDATED="hlsLevelsUpdated",e.AUDIO_TRACKS_UPDATED="hlsAudioTracksUpdated",e.AUDIO_TRACK_SWITCHING="hlsAudioTrackSwitching",e.AUDIO_TRACK_SWITCHED="hlsAudioTrackSwitched",e.AUDIO_TRACK_LOADING="hlsAudioTrackLoading",e.AUDIO_TRACK_LOADED="hlsAudioTrackLoaded",e.SUBTITLE_TRACKS_UPDATED="hlsSubtitleTracksUpdated",e.SUBTITLE_TRACKS_CLEARED="hlsSubtitleTracksCleared",e.SUBTITLE_TRACK_SWITCH="hlsSubtitleTrackSwitch",e.SUBTITLE_TRACK_LOADING="hlsSubtitleTrackLoading",e.SUBTITLE_TRACK_LOADED="hlsSubtitleTrackLoaded",e.SUBTITLE_FRAG_PROCESSED="hlsSubtitleFragProcessed",e.CUES_PARSED="hlsCuesParsed",e.NON_NATIVE_TEXT_TRACKS_FOUND="hlsNonNativeTextTracksFound",e.INIT_PTS_FOUND="hlsInitPtsFound",e.FRAG_LOADING="hlsFragLoading",e.FRAG_LOAD_EMERGENCY_ABORTED="hlsFragLoadEmergencyAborted",e.FRAG_LOADED="hlsFragLoaded",e.FRAG_DECRYPTED="hlsFragDecrypted",e.FRAG_PARSING_INIT_SEGMENT="hlsFragParsingInitSegment",e.FRAG_PARSING_USERDATA="hlsFragParsingUserdata",e.FRAG_PARSING_METADATA="hlsFragParsingMetadata",e.FRAG_PARSED="hlsFragParsed",e.FRAG_BUFFERED="hlsFragBuffered",e.FRAG_CHANGED="hlsFragChanged",e.FPS_DROP="hlsFpsDrop",e.FPS_DROP_LEVEL_CAPPING="hlsFpsDropLevelCapping",e.ERROR="hlsError",e.DESTROYING="hlsDestroying",e.KEY_LOADING="hlsKeyLoading",e.KEY_LOADED="hlsKeyLoaded",e.LIVE_BACK_BUFFER_REACHED="hlsLiveBackBufferReached",e.BACK_BUFFER_REACHED="hlsBackBufferReached"}(i||(i={}))},392:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>Ht});var i,n=r(945),a=r(965),s=r(851),o=r(973),l=r(93),u=r(63),d=/^(\d+)x(\d+)$/,h=/\s*(.+?)\s*=((?:\".*?\")|.*?)(?:,|$)/g,f=function(){function e(t){for(var r in"string"==typeof t&&(t=e.parseAttrList(t)),t)t.hasOwnProperty(r)&&(this[r]=t[r])}var t=e.prototype;return t.decimalInteger=function(e){var t=parseInt(this[e],10);return t>Number.MAX_SAFE_INTEGER?1/0:t},t.hexadecimalInteger=function(e){if(this[e]){var t=(this[e]||"0x").slice(2);t=(1&t.length?"0":"")+t;for(var r=new Uint8Array(t.length/2),i=0;i<t.length/2;i++)r[i]=parseInt(t.slice(2*i,2*i+2),16);return r}return null},t.hexadecimalIntegerAsNumber=function(e){var t=parseInt(this[e],16);return t>Number.MAX_SAFE_INTEGER?1/0:t},t.decimalFloatingPoint=function(e){return parseFloat(this[e])},t.optionalFloat=function(e,t){var r=this[e];return r?parseFloat(r):t},t.enumeratedString=function(e){return this[e]},t.bool=function(e){return"YES"===this[e]},t.decimalResolution=function(e){var t=d.exec(this[e]);if(null!==t)return{width:parseInt(t[1],10),height:parseInt(t[2],10)}},e.parseAttrList=function(e){var t,r={};for(h.lastIndex=0;null!==(t=h.exec(e));){var i=t[2];0===i.indexOf('"')&&i.lastIndexOf('"')===i.length-1&&(i=i.slice(1,-1)),r[t[1]]=i}return r},e}();function c(){return c=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},c.apply(this,arguments)}function v(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}!function(e){e.ID="ID",e.CLASS="CLASS",e.START_DATE="START-DATE",e.DURATION="DURATION",e.END_DATE="END-DATE",e.END_ON_NEXT="END-ON-NEXT",e.PLANNED_DURATION="PLANNED-DURATION",e.SCTE35_OUT="SCTE35-OUT",e.SCTE35_IN="SCTE35-IN"}(i||(i={}));var g=function(){function e(e,t){if(this.attr=void 0,this._startDate=void 0,this._endDate=void 0,this._badValueForSameId=void 0,t){var r=t.attr;for(var n in r)if(Object.prototype.hasOwnProperty.call(e,n)&&e[n]!==r[n]){l.logger.warn('DATERANGE tag attribute: "'+n+'" does not match for tags with ID: "'+e.ID+'"'),this._badValueForSameId=n;break}e=c(new f({}),r,e)}if(this.attr=e,this._startDate=new Date(e[i.START_DATE]),i.END_DATE in this.attr){var s=new Date(this.attr[i.END_DATE]);(0,a.isFiniteNumber)(s.getTime())&&(this._endDate=s)}}var t,r;return t=e,(r=[{key:"id",get:function(){return this.attr.ID}},{key:"class",get:function(){return this.attr.CLASS}},{key:"startDate",get:function(){return this._startDate}},{key:"endDate",get:function(){if(this._endDate)return this._endDate;var e=this.duration;return null!==e?new Date(this._startDate.getTime()+1e3*e):null}},{key:"duration",get:function(){if(i.DURATION in this.attr){var e=this.attr.decimalFloatingPoint(i.DURATION);if((0,a.isFiniteNumber)(e))return e}else if(this._endDate)return(this._endDate.getTime()-this._startDate.getTime())/1e3;return null}},{key:"plannedDuration",get:function(){return i.PLANNED_DURATION in this.attr?this.attr.decimalFloatingPoint(i.PLANNED_DURATION):null}},{key:"endOnNext",get:function(){return this.attr.bool(i.END_ON_NEXT)}},{key:"isValid",get:function(){return!!this.id&&!this._badValueForSameId&&(0,a.isFiniteNumber)(this.startDate.getTime())&&(null===this.duration||this.duration>=0)&&(!this.endOnNext||!!this.class)}}])&&v(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}(),p=r(923);function m(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}var y=function(){function e(e){this.PTSKnown=!1,this.alignedSliding=!1,this.averagetargetduration=void 0,this.endCC=0,this.endSN=0,this.fragments=void 0,this.fragmentHint=void 0,this.partList=null,this.dateRanges=void 0,this.live=!0,this.ageHeader=0,this.advancedDateTime=void 0,this.updated=!0,this.advanced=!0,this.availabilityDelay=void 0,this.misses=0,this.needSidxRanges=!1,this.startCC=0,this.startSN=0,this.startTimeOffset=null,this.targetduration=0,this.totalduration=0,this.type=null,this.url=void 0,this.m3u8="",this.version=null,this.canBlockReload=!1,this.canSkipUntil=0,this.canSkipDateRanges=!1,this.skippedSegments=0,this.recentlyRemovedDateranges=void 0,this.partHoldBack=0,this.holdBack=0,this.partTarget=0,this.preloadHint=void 0,this.renditionReports=void 0,this.tuneInGoal=0,this.deltaUpdateFailed=void 0,this.driftStartTime=0,this.driftEndTime=0,this.driftStart=0,this.driftEnd=0,this.fragments=[],this.dateRanges={},this.url=e}var t,r;return e.prototype.reloaded=function(e){if(!e)return this.advanced=!0,void(this.updated=!0);var t=this.lastPartSn-e.lastPartSn,r=this.lastPartIndex-e.lastPartIndex;this.updated=this.endSN!==e.endSN||!!r||!!t,this.advanced=this.endSN>e.endSN||t>0||0===t&&r>0,this.updated||this.advanced?this.misses=Math.floor(.6*e.misses):this.misses=e.misses+1,this.availabilityDelay=e.availabilityDelay},t=e,(r=[{key:"hasProgramDateTime",get:function(){return!!this.fragments.length&&(0,a.isFiniteNumber)(this.fragments[this.fragments.length-1].programDateTime)}},{key:"levelTargetDuration",get:function(){return this.averagetargetduration||this.targetduration||10}},{key:"drift",get:function(){var e=this.driftEndTime-this.driftStartTime;return e>0?1e3*(this.driftEnd-this.driftStart)/e:1}},{key:"edge",get:function(){return this.partEnd||this.fragmentEnd}},{key:"partEnd",get:function(){var e;return null!==(e=this.partList)&&void 0!==e&&e.length?this.partList[this.partList.length-1].end:this.fragmentEnd}},{key:"fragmentEnd",get:function(){var e;return null!==(e=this.fragments)&&void 0!==e&&e.length?this.fragments[this.fragments.length-1].end:0}},{key:"age",get:function(){return this.advancedDateTime?Math.max(Date.now()-this.advancedDateTime,0)/1e3:0}},{key:"lastPartIndex",get:function(){var e;return null!==(e=this.partList)&&void 0!==e&&e.length?this.partList[this.partList.length-1].index:-1}},{key:"lastPartSn",get:function(){var e;return null!==(e=this.partList)&&void 0!==e&&e.length?this.partList[this.partList.length-1].fragment.sn:this.endSN}}])&&m(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}(),E=r(960),T={audio:{a3ds:!0,"ac-3":!0,"ac-4":!0,alac:!0,alaw:!0,dra1:!0,"dts+":!0,"dts-":!0,dtsc:!0,dtse:!0,dtsh:!0,"ec-3":!0,enca:!0,g719:!0,g726:!0,m4ae:!0,mha1:!0,mha2:!0,mhm1:!0,mhm2:!0,mlpa:!0,mp4a:!0,"raw ":!0,Opus:!0,opus:!0,samr:!0,sawb:!0,sawp:!0,sevc:!0,sqcp:!0,ssmv:!0,twos:!0,ulaw:!0},video:{avc1:!0,avc2:!0,avc3:!0,avc4:!0,avcp:!0,av01:!0,drac:!0,dva1:!0,dvav:!0,dvh1:!0,dvhe:!0,encv:!0,hev1:!0,hvc1:!0,mjp2:!0,mp4v:!0,mvc1:!0,mvc2:!0,mvc3:!0,mvc4:!0,resv:!0,rv60:!0,s263:!0,svc1:!0,svc2:!0,"vc-1":!0,vp08:!0,vp09:!0},text:{stpp:!0,wvtt:!0}};function S(e,t){return MediaSource.isTypeSupported((t||"video")+'/mp4;codecs="'+e+'"')}var b=/#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-SESSION-DATA:([^\r\n]*)[\r\n]+/g,L=/#EXT-X-MEDIA:(.*)/g,A=new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source,/(?!#) *(\S[\S ]*)/.source,/#EXT-X-BYTERANGE:*(.+)/.source,/#EXT-X-PROGRAM-DATE-TIME:(.+)/.source,/#.*/.source].join("|"),"g"),D=new RegExp([/#(EXTM3U)/.source,/#EXT-X-(DATERANGE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source,/#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source,/#EXT-X-(DISCONTINUITY|ENDLIST|GAP)/.source,/(#)([^:]*):(.*)/.source,/(#)(.*)(?:.*)\r?\n?/.source].join("|")),R=/\.(mp4|m4s|m4v|m4a)$/i,k=function(){function e(){}return e.findGroup=function(e,t){for(var r=0;r<e.length;r++){var i=e[r];if(i.id===t)return i}},e.convertAVC1ToAVCOTI=function(e){var t=e.split(".");if(t.length>2){var r=t.shift()+".";return(r+=parseInt(t.shift()).toString(16))+("000"+parseInt(t.shift()).toString(16)).slice(-4)}return e},e.resolve=function(e,t){return n.buildAbsoluteURL(t,e,{alwaysNormalize:!0})},e.parseMasterPlaylist=function(t,r){var i,n=[],a=[],s={},o=!1;for(b.lastIndex=0;null!=(i=b.exec(t));)if(i[1]){var l,u=new f(i[1]),d={attrs:u,bitrate:u.decimalInteger("AVERAGE-BANDWIDTH")||u.decimalInteger("BANDWIDTH"),name:u.NAME,url:e.resolve(i[2],r)},h=u.decimalResolution("RESOLUTION");h&&(d.width=h.width,d.height=h.height),_((u.CODECS||"").split(/[ ,]+/).filter((function(e){return e})),d),d.videoCodec&&-1!==d.videoCodec.indexOf("avc1")&&(d.videoCodec=e.convertAVC1ToAVCOTI(d.videoCodec)),null!==(l=d.unknownCodecs)&&void 0!==l&&l.length||a.push(d),n.push(d)}else if(i[3]){var c=new f(i[3]);c["DATA-ID"]&&(o=!0,s[c["DATA-ID"]]=c)}return{levels:a.length>0&&a.length<n.length?a:n,sessionData:o?s:null}},e.parseMasterPlaylistMedia=function(t,r,i,n){var a;void 0===n&&(n=[]);var s=[],o=0;for(L.lastIndex=0;null!==(a=L.exec(t));){var l=new f(a[1]);if(l.TYPE===i){var u={attrs:l,bitrate:0,id:o++,groupId:l["GROUP-ID"],instreamId:l["INSTREAM-ID"],name:l.NAME||l.LANGUAGE||"",type:i,default:l.bool("DEFAULT"),autoselect:l.bool("AUTOSELECT"),forced:l.bool("FORCED"),lang:l.LANGUAGE,url:l.URI?e.resolve(l.URI,r):""};if(n.length){var d=e.findGroup(n,u.groupId)||n[0];x(u,d,"audioCodec"),x(u,d,"textCodec")}s.push(u)}}return s},e.parseLevelPlaylist=function(e,t,r,i,s){var o,u,d,h=new y(t),c=h.fragments,v=null,m=0,T=0,S=0,b=0,L=null,k=new p.Fragment(i,t),_=-1,x=!1;for(A.lastIndex=0,h.m3u8=e;null!==(o=A.exec(e));){x&&(x=!1,(k=new p.Fragment(i,t)).start=S,k.sn=m,k.cc=b,k.level=r,v&&(k.initSegment=v,k.rawProgramDateTime=v.rawProgramDateTime,v.rawProgramDateTime=null));var C=o[1];if(C){k.duration=parseFloat(C);var P=(" "+o[2]).slice(1);k.title=P||null,k.tagList.push(P?["INF",C,P]:["INF",C])}else if(o[3])(0,a.isFiniteNumber)(k.duration)&&(k.start=S,d&&(k.levelkey=d),k.sn=m,k.level=r,k.cc=b,k.urlId=s,c.push(k),k.relurl=(" "+o[3]).slice(1),I(k,L),L=k,S+=k.duration,m++,T=0,x=!0);else if(o[4]){var O=(" "+o[4]).slice(1);L?k.setByteRange(O,L):k.setByteRange(O)}else if(o[5])k.rawProgramDateTime=(" "+o[5]).slice(1),k.tagList.push(["PROGRAM-DATE-TIME",k.rawProgramDateTime]),-1===_&&(_=c.length);else{if(!(o=o[0].match(D))){l.logger.warn("No matches on slow regex match for level playlist!");continue}for(u=1;u<o.length&&void 0===o[u];u++);var F=(" "+o[u]).slice(1),M=(" "+o[u+1]).slice(1),N=o[u+2]?(" "+o[u+2]).slice(1):"";switch(F){case"PLAYLIST-TYPE":h.type=M.toUpperCase();break;case"MEDIA-SEQUENCE":m=h.startSN=parseInt(M);break;case"SKIP":var U=new f(M),B=U.decimalInteger("SKIPPED-SEGMENTS");if((0,a.isFiniteNumber)(B)){h.skippedSegments=B;for(var G=B;G--;)c.unshift(null);m+=B}var V=U.enumeratedString("RECENTLY-REMOVED-DATERANGES");V&&(h.recentlyRemovedDateranges=V.split("\t"));break;case"TARGETDURATION":h.targetduration=parseFloat(M);break;case"VERSION":h.version=parseInt(M);break;case"EXTM3U":break;case"ENDLIST":h.live=!1;break;case"#":(M||N)&&k.tagList.push(N?[M,N]:[M]);break;case"DISCONTINUITY":b++,k.tagList.push(["DIS"]);break;case"GAP":k.tagList.push([F]);break;case"BITRATE":k.tagList.push([F,M]);break;case"DATERANGE":var H=new f(M),j=new g(H,h.dateRanges[H.ID]);j.isValid||h.skippedSegments?h.dateRanges[j.id]=j:l.logger.warn('Ignoring invalid DATERANGE tag: "'+M+'"'),k.tagList.push(["EXT-X-DATERANGE",M]);break;case"DISCONTINUITY-SEQUENCE":b=parseInt(M);break;case"KEY":var K,W=new f(M),Y=W.enumeratedString("METHOD"),q=W.URI,z=W.hexadecimalInteger("IV"),X=W.enumeratedString("KEYFORMATVERSIONS"),Q=W.enumeratedString("KEYID"),$=null!=(K=W.enumeratedString("KEYFORMAT"))?K:"identity";if(["com.apple.streamingkeydelivery","com.microsoft.playready","urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed","com.widevine"].indexOf($)>-1){l.logger.warn("Keyformat "+$+" is not supported from the manifest");continue}if("identity"!==$)continue;Y&&(d=E.LevelKey.fromURL(t,q),q&&["AES-128","SAMPLE-AES","SAMPLE-AES-CENC"].indexOf(Y)>=0&&(d.method=Y,d.keyFormat=$,Q&&(d.keyID=Q),X&&(d.keyFormatVersions=X),d.iv=z));break;case"START":var Z=new f(M).decimalFloatingPoint("TIME-OFFSET");(0,a.isFiniteNumber)(Z)&&(h.startTimeOffset=Z);break;case"MAP":var J=new f(M);if(k.duration){var ee=new p.Fragment(i,t);w(ee,J,r,d),v=ee,k.initSegment=v,v.rawProgramDateTime&&!k.rawProgramDateTime&&(k.rawProgramDateTime=v.rawProgramDateTime)}else w(k,J,r,d),v=k,x=!0;break;case"SERVER-CONTROL":var te=new f(M);h.canBlockReload=te.bool("CAN-BLOCK-RELOAD"),h.canSkipUntil=te.optionalFloat("CAN-SKIP-UNTIL",0),h.canSkipDateRanges=h.canSkipUntil>0&&te.bool("CAN-SKIP-DATERANGES"),h.partHoldBack=te.optionalFloat("PART-HOLD-BACK",0),h.holdBack=te.optionalFloat("HOLD-BACK",0);break;case"PART-INF":var re=new f(M);h.partTarget=re.decimalFloatingPoint("PART-TARGET");break;case"PART":var ie=h.partList;ie||(ie=h.partList=[]);var ne=T>0?ie[ie.length-1]:void 0,ae=T++,se=new p.Part(new f(M),k,t,ae,ne);ie.push(se),k.duration+=se.duration;break;case"PRELOAD-HINT":var oe=new f(M);h.preloadHint=oe;break;case"RENDITION-REPORT":var le=new f(M);h.renditionReports=h.renditionReports||[],h.renditionReports.push(le);break;default:l.logger.warn("line parsed but not handled: "+o)}}}L&&!L.relurl?(c.pop(),S-=L.duration,h.partList&&(h.fragmentHint=L)):h.partList&&(I(k,L),k.cc=b,h.fragmentHint=k);var ue=c.length,de=c[0],he=c[ue-1];if((S+=h.skippedSegments*h.targetduration)>0&&ue&&he){h.averagetargetduration=S/ue;var fe=he.sn;h.endSN="initSegment"!==fe?fe:0,de&&(h.startCC=de.cc,de.initSegment||h.fragments.every((function(e){return e.relurl&&(t=e.relurl,R.test(null!=(r=null===(i=n.parseURL(t))||void 0===i?void 0:i.path)?r:""));var t,r,i}))&&(l.logger.warn("MP4 fragments found but no init segment (probably no MAP, incomplete M3U8), trying to fetch SIDX"),(k=new p.Fragment(i,t)).relurl=he.relurl,k.level=r,k.sn="initSegment",de.initSegment=k,h.needSidxRanges=!0))}else h.endSN=0,h.startCC=0;return h.fragmentHint&&(S+=h.fragmentHint.duration),h.totalduration=S,h.endCC=b,_>0&&function(e,t){for(var r=e[t],i=t;i--;){var n=e[i];if(!n)return;n.programDateTime=r.programDateTime-1e3*n.duration,r=n}}(c,_),h},e}();function _(e,t){["video","audio","text"].forEach((function(r){var i=e.filter((function(e){return function(e,t){var r=T[t];return!!r&&!0===r[e.slice(0,4)]}(e,r)}));if(i.length){var n=i.filter((function(e){return 0===e.lastIndexOf("avc1",0)||0===e.lastIndexOf("mp4a",0)}));t[r+"Codec"]=n.length>0?n[0]:i[0],e=e.filter((function(e){return-1===i.indexOf(e)}))}})),t.unknownCodecs=e}function x(e,t,r){var i=t[r];i&&(e[r]=i)}function I(e,t){e.rawProgramDateTime?e.programDateTime=Date.parse(e.rawProgramDateTime):null!=t&&t.programDateTime&&(e.programDateTime=t.endProgramDateTime),(0,a.isFiniteNumber)(e.programDateTime)||(e.programDateTime=null,e.rawProgramDateTime=null)}function w(e,t,r,i){e.relurl=t.URI,t.BYTERANGE&&e.setByteRange(t.BYTERANGE),e.level=r,e.sn="initSegment",i&&(e.levelkey=i),e.initSegment=null}var C=r(308);function P(e,t){var r=e.url;return void 0!==r&&0!==r.indexOf("data:")||(r=t.url),r}const O=function(){function e(e){this.hls=void 0,this.loaders=Object.create(null),this.hls=e,this.registerListeners()}var t=e.prototype;return t.startLoad=function(e){},t.stopLoad=function(){this.destroyInternalLoaders()},t.registerListeners=function(){var e=this.hls;e.on(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),e.on(s.Events.LEVEL_LOADING,this.onLevelLoading,this),e.on(s.Events.AUDIO_TRACK_LOADING,this.onAudioTrackLoading,this),e.on(s.Events.SUBTITLE_TRACK_LOADING,this.onSubtitleTrackLoading,this)},t.unregisterListeners=function(){var e=this.hls;e.off(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),e.off(s.Events.LEVEL_LOADING,this.onLevelLoading,this),e.off(s.Events.AUDIO_TRACK_LOADING,this.onAudioTrackLoading,this),e.off(s.Events.SUBTITLE_TRACK_LOADING,this.onSubtitleTrackLoading,this)},t.createInternalLoader=function(e){var t=this.hls.config,r=t.pLoader,i=t.loader,n=new(r||i)(t);return e.loader=n,this.loaders[e.type]=n,n},t.getInternalLoader=function(e){return this.loaders[e.type]},t.resetInternalLoader=function(e){this.loaders[e]&&delete this.loaders[e]},t.destroyInternalLoaders=function(){for(var e in this.loaders){var t=this.loaders[e];t&&t.destroy(),this.resetInternalLoader(e)}},t.destroy=function(){this.unregisterListeners(),this.destroyInternalLoaders()},t.onManifestLoading=function(e,t){var r=t.url;this.load({id:null,groupId:null,level:0,responseType:"text",type:C.PlaylistContextType.MANIFEST,url:r,deliveryDirectives:null})},t.onLevelLoading=function(e,t){var r=t.id,i=t.level,n=t.url,a=t.deliveryDirectives;this.load({id:r,groupId:null,level:i,responseType:"text",type:C.PlaylistContextType.LEVEL,url:n,deliveryDirectives:a})},t.onAudioTrackLoading=function(e,t){var r=t.id,i=t.groupId,n=t.url,a=t.deliveryDirectives;this.load({id:r,groupId:i,level:null,responseType:"text",type:C.PlaylistContextType.AUDIO_TRACK,url:n,deliveryDirectives:a})},t.onSubtitleTrackLoading=function(e,t){var r=t.id,i=t.groupId,n=t.url,a=t.deliveryDirectives;this.load({id:r,groupId:i,level:null,responseType:"text",type:C.PlaylistContextType.SUBTITLE_TRACK,url:n,deliveryDirectives:a})},t.load=function(e){var t,r,i,n,a,s,o=this.hls.config,u=this.getInternalLoader(e);if(u){var d=u.context;if(d&&d.url===e.url)return void l.logger.trace("[playlist-loader]: playlist request ongoing");l.logger.log("[playlist-loader]: aborting previous loader for type: "+e.type),u.abort()}switch(e.type){case C.PlaylistContextType.MANIFEST:r=o.manifestLoadingMaxRetry,i=o.manifestLoadingTimeOut,n=o.manifestLoadingRetryDelay,a=o.manifestLoadingMaxRetryTimeout;break;case C.PlaylistContextType.LEVEL:case C.PlaylistContextType.AUDIO_TRACK:case C.PlaylistContextType.SUBTITLE_TRACK:r=0,i=o.levelLoadingTimeOut;break;default:r=o.levelLoadingMaxRetry,i=o.levelLoadingTimeOut,n=o.levelLoadingRetryDelay,a=o.levelLoadingMaxRetryTimeout}if(u=this.createInternalLoader(e),null!==(t=e.deliveryDirectives)&&void 0!==t&&t.part&&(e.type===C.PlaylistContextType.LEVEL&&null!==e.level?s=this.hls.levels[e.level].details:e.type===C.PlaylistContextType.AUDIO_TRACK&&null!==e.id?s=this.hls.audioTracks[e.id].details:e.type===C.PlaylistContextType.SUBTITLE_TRACK&&null!==e.id&&(s=this.hls.subtitleTracks[e.id].details),s)){var h=s.partTarget,f=s.targetduration;h&&f&&(i=Math.min(1e3*Math.max(3*h,.8*f),i))}var c={timeout:i,maxRetry:r,retryDelay:n,maxRetryDelay:a,highWaterMark:0},v={onSuccess:this.loadsuccess.bind(this),onError:this.loaderror.bind(this),onTimeout:this.loadtimeout.bind(this)};u.load(e,c,v)},t.loadsuccess=function(e,t,r,i){if(void 0===i&&(i=null),r.isSidxRequest)return this.handleSidxRequest(e,r),void this.handlePlaylistLoaded(e,t,r,i);this.resetInternalLoader(r.type);var n=e.data;0===n.indexOf("#EXTM3U")?(t.parsing.start=performance.now(),n.indexOf("#EXTINF:")>0||n.indexOf("#EXT-X-TARGETDURATION:")>0?this.handleTrackOrLevelPlaylist(e,t,r,i):this.handleMasterPlaylist(e,t,r,i)):this.handleManifestParsingError(e,r,"no EXTM3U delimiter",i)},t.loaderror=function(e,t,r){void 0===r&&(r=null),this.handleNetworkError(t,r,!1,e)},t.loadtimeout=function(e,t,r){void 0===r&&(r=null),this.handleNetworkError(t,r,!0)},t.handleMasterPlaylist=function(e,t,r,i){var n=this.hls,a=e.data,o=P(e,r),u=k.parseMasterPlaylist(a,o),d=u.levels,h=u.sessionData;if(d.length){var c=d.map((function(e){return{id:e.attrs.AUDIO,audioCodec:e.audioCodec}})),v=d.map((function(e){return{id:e.attrs.SUBTITLES,textCodec:e.textCodec}})),g=k.parseMasterPlaylistMedia(a,o,"AUDIO",c),p=k.parseMasterPlaylistMedia(a,o,"SUBTITLES",v),m=k.parseMasterPlaylistMedia(a,o,"CLOSED-CAPTIONS");g.length&&(g.some((function(e){return!e.url}))||!d[0].audioCodec||d[0].attrs.AUDIO||(l.logger.log("[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one"),g.unshift({type:"main",name:"main",default:!1,autoselect:!1,forced:!1,id:-1,attrs:new f({}),bitrate:0,url:""}))),n.trigger(s.Events.MANIFEST_LOADED,{levels:d,audioTracks:g,subtitles:p,captions:m,url:o,stats:t,networkDetails:i,sessionData:h})}else this.handleManifestParsingError(e,r,"no level found in manifest",i)},t.handleTrackOrLevelPlaylist=function(e,t,r,i){var n=this.hls,l=r.id,u=r.level,d=r.type,h=P(e,r),c=(0,a.isFiniteNumber)(l)?l:0,v=(0,a.isFiniteNumber)(u)?u:c,g=function(e){switch(e.type){case C.PlaylistContextType.AUDIO_TRACK:return C.PlaylistLevelType.AUDIO;case C.PlaylistContextType.SUBTITLE_TRACK:return C.PlaylistLevelType.SUBTITLE;default:return C.PlaylistLevelType.MAIN}}(r),p=k.parseLevelPlaylist(e.data,h,v,g,c);if(p.fragments.length){if(d===C.PlaylistContextType.MANIFEST){var m={attrs:new f({}),bitrate:0,details:p,name:"",url:h};n.trigger(s.Events.MANIFEST_LOADED,{levels:[m],audioTracks:[],url:h,stats:t,networkDetails:i,sessionData:null})}if(t.parsing.end=performance.now(),p.needSidxRanges){var y,E=null===(y=p.fragments[0].initSegment)||void 0===y?void 0:y.url;this.load({url:E,isSidxRequest:!0,type:d,level:u,levelDetails:p,id:l,groupId:null,rangeStart:0,rangeEnd:2048,responseType:"arraybuffer",deliveryDirectives:null})}else r.levelDetails=p,this.handlePlaylistLoaded(e,t,r,i)}else n.trigger(s.Events.ERROR,{type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.LEVEL_EMPTY_ERROR,fatal:!1,url:h,reason:"no fragments found in level",level:"number"==typeof r.level?r.level:void 0})},t.handleSidxRequest=function(e,t){var r=new Uint8Array(e.data),i=(0,u.findBox)(r,["sidx"])[0];if(i){var n=(0,u.parseSegmentIndex)(i);if(n){var a=n.references,s=t.levelDetails;a.forEach((function(e,t){var i=e.info,n=s.fragments[t];if(0===n.byteRange.length&&n.setByteRange(String(1+i.end-i.start)+"@"+String(i.start)),n.initSegment){var a=(0,u.findBox)(r,["moov"])[0],o=a?a.length:null;n.initSegment.setByteRange(String(o)+"@0")}}))}}},t.handleManifestParsingError=function(e,t,r,i){this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.MANIFEST_PARSING_ERROR,fatal:t.type===C.PlaylistContextType.MANIFEST,url:e.url,reason:r,response:e,context:t,networkDetails:i})},t.handleNetworkError=function(e,t,r,i){void 0===r&&(r=!1),l.logger.warn("[playlist-loader]: A network "+(r?"timeout":"error")+" occurred while loading "+e.type+" level: "+e.level+" id: "+e.id+' group-id: "'+e.groupId+'"');var n=o.ErrorDetails.UNKNOWN,a=!1,u=this.getInternalLoader(e);switch(e.type){case C.PlaylistContextType.MANIFEST:n=r?o.ErrorDetails.MANIFEST_LOAD_TIMEOUT:o.ErrorDetails.MANIFEST_LOAD_ERROR,a=!0;break;case C.PlaylistContextType.LEVEL:n=r?o.ErrorDetails.LEVEL_LOAD_TIMEOUT:o.ErrorDetails.LEVEL_LOAD_ERROR,a=!1;break;case C.PlaylistContextType.AUDIO_TRACK:n=r?o.ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:o.ErrorDetails.AUDIO_TRACK_LOAD_ERROR,a=!1;break;case C.PlaylistContextType.SUBTITLE_TRACK:n=r?o.ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:o.ErrorDetails.SUBTITLE_LOAD_ERROR,a=!1}u&&this.resetInternalLoader(e.type);var d={type:o.ErrorTypes.NETWORK_ERROR,details:n,fatal:a,url:e.url,loader:u,context:e,networkDetails:t};i&&(d.response=i),this.hls.trigger(s.Events.ERROR,d)},t.handlePlaylistLoaded=function(e,t,r,i){var n=r.type,a=r.level,o=r.id,l=r.groupId,u=r.loader,d=r.levelDetails,h=r.deliveryDirectives;if(null!=d&&d.targetduration){if(u)switch(d.live&&(u.getCacheAge&&(d.ageHeader=u.getCacheAge()||0),u.getCacheAge&&!isNaN(d.ageHeader)||(d.ageHeader=0)),n){case C.PlaylistContextType.MANIFEST:case C.PlaylistContextType.LEVEL:this.hls.trigger(s.Events.LEVEL_LOADED,{details:d,level:a||0,id:o||0,stats:t,networkDetails:i,deliveryDirectives:h});break;case C.PlaylistContextType.AUDIO_TRACK:this.hls.trigger(s.Events.AUDIO_TRACK_LOADED,{details:d,id:o||0,groupId:l||"",stats:t,networkDetails:i,deliveryDirectives:h});break;case C.PlaylistContextType.SUBTITLE_TRACK:this.hls.trigger(s.Events.SUBTITLE_TRACK_LOADED,{details:d,id:o||0,groupId:l||"",stats:t,networkDetails:i,deliveryDirectives:h})}}else this.handleManifestParsingError(e,r,"invalid target duration",i)},e}();var F=function(){function e(e){this.hls=void 0,this.loaders={},this.decryptkey=null,this.decrypturl=null,this.hls=e,this.registerListeners()}var t=e.prototype;return t.startLoad=function(e){},t.stopLoad=function(){this.destroyInternalLoaders()},t.registerListeners=function(){this.hls.on(s.Events.KEY_LOADING,this.onKeyLoading,this)},t.unregisterListeners=function(){this.hls.off(s.Events.KEY_LOADING,this.onKeyLoading)},t.destroyInternalLoaders=function(){for(var e in this.loaders){var t=this.loaders[e];t&&t.destroy()}this.loaders={}},t.destroy=function(){this.unregisterListeners(),this.destroyInternalLoaders()},t.onKeyLoading=function(e,t){var r=t.frag,i=r.type,n=this.loaders[i];if(r.decryptdata){var a=r.decryptdata.uri;if(a!==this.decrypturl||null===this.decryptkey){var o=this.hls.config;if(n&&(l.logger.warn("abort previous key loader for type:"+i),n.abort()),!a)return void l.logger.warn("key uri is falsy");var u=o.loader,d=r.loader=this.loaders[i]=new u(o);this.decrypturl=a,this.decryptkey=null;var h={url:a,frag:r,responseType:"arraybuffer"},f={timeout:o.fragLoadingTimeOut,maxRetry:0,retryDelay:o.fragLoadingRetryDelay,maxRetryDelay:o.fragLoadingMaxRetryTimeout,highWaterMark:0},c={onSuccess:this.loadsuccess.bind(this),onError:this.loaderror.bind(this),onTimeout:this.loadtimeout.bind(this)};d.load(h,f,c)}else this.decryptkey&&(r.decryptdata.key=this.decryptkey,this.hls.trigger(s.Events.KEY_LOADED,{frag:r}))}else l.logger.warn("Missing decryption data on fragment in onKeyLoading")},t.loadsuccess=function(e,t,r){var i=r.frag;i.decryptdata?(this.decryptkey=i.decryptdata.key=new Uint8Array(e.data),i.loader=null,delete this.loaders[i.type],this.hls.trigger(s.Events.KEY_LOADED,{frag:i})):l.logger.error("after key load, decryptdata unset")},t.loaderror=function(e,t){var r=t.frag,i=r.loader;i&&i.abort(),delete this.loaders[r.type],this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.KEY_LOAD_ERROR,fatal:!1,frag:r,response:e})},t.loadtimeout=function(e,t){var r=t.frag,i=r.loader;i&&i.abort(),delete this.loaders[r.type],this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.KEY_LOAD_TIMEOUT,fatal:!1,frag:r})},e}();function M(e,t){var r;try{r=new Event("addtrack")}catch(e){(r=document.createEvent("Event")).initEvent("addtrack",!1,!1)}r.track=e,t.dispatchEvent(r)}var N=r(181),U=r(856);function B(){return self.WebKitDataCue||self.VTTCue||self.TextTrackCue}var G=function(){var e=B();try{new e(0,Number.POSITIVE_INFINITY,"")}catch(e){return Number.MAX_VALUE}return Number.POSITIVE_INFINITY}();function V(e,t){return e.getTime()/1e3-t}const H=function(){function e(e){this.hls=void 0,this.id3Track=null,this.media=null,this.dateRangeCuesAppended={},this.hls=e,this._registerListeners()}var t=e.prototype;return t.destroy=function(){this._unregisterListeners(),this.id3Track=null,this.media=null,this.dateRangeCuesAppended={},this.hls=null},t._registerListeners=function(){var e=this.hls;e.on(s.Events.MEDIA_ATTACHED,this.onMediaAttached,this),e.on(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),e.on(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),e.on(s.Events.FRAG_PARSING_METADATA,this.onFragParsingMetadata,this),e.on(s.Events.BUFFER_FLUSHING,this.onBufferFlushing,this),e.on(s.Events.LEVEL_UPDATED,this.onLevelUpdated,this)},t._unregisterListeners=function(){var e=this.hls;e.off(s.Events.MEDIA_ATTACHED,this.onMediaAttached,this),e.off(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),e.off(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),e.off(s.Events.FRAG_PARSING_METADATA,this.onFragParsingMetadata,this),e.off(s.Events.BUFFER_FLUSHING,this.onBufferFlushing,this),e.off(s.Events.LEVEL_UPDATED,this.onLevelUpdated,this)},t.onMediaAttached=function(e,t){this.media=t.media},t.onMediaDetaching=function(){this.id3Track&&(function(e){var t=e.mode;if("disabled"===t&&(e.mode="hidden"),e.cues)for(var r=e.cues.length;r--;)e.removeCue(e.cues[r]);"disabled"===t&&(e.mode=t)}(this.id3Track),this.id3Track=null,this.media=null,this.dateRangeCuesAppended={})},t.onManifestLoading=function(){this.dateRangeCuesAppended={}},t.createTrack=function(e){var t=this.getID3Track(e.textTracks);return t.mode="hidden",t},t.getID3Track=function(e){if(this.media){for(var t=0;t<e.length;t++){var r=e[t];if("metadata"===r.kind&&"id3"===r.label)return M(r,this.media),r}return this.media.addTextTrack("metadata","id3")}},t.onFragParsingMetadata=function(e,t){if(this.media){var r=this.hls.config,i=r.enableEmsgMetadataCues,n=r.enableID3MetadataCues;if(i||n){t.frag;var a=t.samples;t.details,this.id3Track||(this.id3Track=this.createTrack(this.media));for(var s=B(),o=0;o<a.length;o++){var l=a[o].type;if((l!==U.MetadataSchema.emsg||i)&&n){var u=N.getID3Frames(a[o].data);if(u){var d=a[o].pts,h=d+a[o].duration;h>G&&(h=G),h-d<=0&&(h=d+.25);for(var f=0;f<u.length;f++){var c=u[f];if(!N.isTimeStampFrame(c)){this.updateId3CueEnds(d);var v=new s(d,h,"");v.value=c,l&&(v.type=l),this.id3Track.addCue(v)}}}}}}}},t.updateId3CueEnds=function(e){var t,r=null===(t=this.id3Track)||void 0===t?void 0:t.cues;if(r)for(var i=r.length;i--;){var n=r[i];n.startTime<e&&n.endTime===G&&(n.endTime=e)}},t.onBufferFlushing=function(e,t){var r=t.startOffset,i=t.endOffset,n=t.type,a=this.id3Track,s=this.hls;if(s){var o=s.config,l=o.enableEmsgMetadataCues,u=o.enableID3MetadataCues;a&&(l||u)&&function(e,t,r,i){var n=e.mode;if("disabled"===n&&(e.mode="hidden"),e.cues&&e.cues.length>0)for(var a=function(e,t,r){var i=[],n=function(e,t){if(t<e[0].startTime)return 0;var r=e.length-1;if(t>e[r].endTime)return-1;for(var i=0,n=r;i<=n;){var a=Math.floor((n+i)/2);if(t<e[a].startTime)n=a-1;else{if(!(t>e[a].startTime&&i<r))return a;i=a+1}}return e[i].startTime-t<t-e[n].startTime?i:n}(e,t);if(n>-1)for(var a=n,s=e.length;a<s;a++){var o=e[a];if(o.startTime>=t&&o.endTime<=r)i.push(o);else if(o.startTime>r)return i}return i}(e.cues,t,r),s=0;s<a.length;s++)i&&!i(a[s])||e.removeCue(a[s]);"disabled"===n&&(e.mode=n)}(a,r,i,"audio"===n?function(e){return e.type===U.MetadataSchema.audioId3&&u}:"video"===n?function(e){return e.type===U.MetadataSchema.emsg&&l}:function(e){return e.type===U.MetadataSchema.audioId3&&u||e.type===U.MetadataSchema.emsg&&l})}},t.onLevelUpdated=function(e,t){var r=this,n=t.details;if(this.media&&n.hasProgramDateTime&&this.hls.config.enableDateRangeMetadataCues){var s=this.dateRangeCuesAppended,o=this.id3Track,l=n.dateRanges,u=Object.keys(l);if(o)for(var d=Object.keys(s).filter((function(e){return!u.includes(e)})),h=function(e){var t=d[e];Object.keys(s[t].cues).forEach((function(e){o.removeCue(s[t].cues[e])})),delete s[t]},f=d.length;f--;)h(f);var c=n.fragments[n.fragments.length-1];if(0!==u.length&&(0,a.isFiniteNumber)(null==c?void 0:c.programDateTime)){this.id3Track||(this.id3Track=this.createTrack(this.media));for(var v=c.programDateTime/1e3-c.start,g=B(),p=function(e){var t=u[e],n=l[t],a=s[t],o=(null==a?void 0:a.cues)||{},d=(null==a?void 0:a.durationKnown)||!1,h=V(n.startDate,v),f=G,c=n.endDate;if(c)f=V(c,v),d=!0;else if(n.endOnNext&&!d){var p=u.reduce((function(e,t){var r=l[t];return r.class===n.class&&r.id!==t&&r.startDate>n.startDate&&e.push(r),e}),[]).sort((function(e,t){return e.startDate.getTime()-t.startDate.getTime()}))[0];p&&(f=V(p.startDate,v),d=!0)}for(var m,y=Object.keys(n.attr),E=0;E<y.length;E++){var T=y[E];if(T!==i.ID&&T!==i.CLASS&&T!==i.START_DATE&&T!==i.DURATION&&T!==i.END_DATE&&T!==i.END_ON_NEXT){var S=o[T];if(S)d&&!a.durationKnown&&(S.endTime=f);else{var b=n.attr[T];S=new g(h,f,""),T!==i.SCTE35_OUT&&T!==i.SCTE35_IN||(m=b,b=Uint8Array.from(m.replace(/^0x/,"").replace(/([\da-fA-F]{2}) ?/g,"0x$1 ").replace(/ +$/,"").split(" ")).buffer),S.value={key:T,data:b},S.type=U.MetadataSchema.dateRange,r.id3Track.addCue(S),o[T]=S}}}s[t]={cues:o,dateRange:n,durationKnown:d}},m=0;m<u.length;m++)p(m)}}},e}();function j(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}var K,W=function(){function e(e){var t=this;this.hls=void 0,this.config=void 0,this.media=null,this.levelDetails=null,this.currentTime=0,this.stallCount=0,this._latency=null,this.timeupdateHandler=function(){return t.timeupdate()},this.hls=e,this.config=e.config,this.registerListeners()}var t,r,i=e.prototype;return i.destroy=function(){this.unregisterListeners(),this.onMediaDetaching(),this.levelDetails=null,this.hls=this.timeupdateHandler=null},i.registerListeners=function(){this.hls.on(s.Events.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.on(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),this.hls.on(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.on(s.Events.LEVEL_UPDATED,this.onLevelUpdated,this),this.hls.on(s.Events.ERROR,this.onError,this)},i.unregisterListeners=function(){this.hls.off(s.Events.MEDIA_ATTACHED,this.onMediaAttached),this.hls.off(s.Events.MEDIA_DETACHING,this.onMediaDetaching),this.hls.off(s.Events.MANIFEST_LOADING,this.onManifestLoading),this.hls.off(s.Events.LEVEL_UPDATED,this.onLevelUpdated),this.hls.off(s.Events.ERROR,this.onError)},i.onMediaAttached=function(e,t){this.media=t.media,this.media.addEventListener("timeupdate",this.timeupdateHandler)},i.onMediaDetaching=function(){this.media&&(this.media.removeEventListener("timeupdate",this.timeupdateHandler),this.media=null)},i.onManifestLoading=function(){this.levelDetails=null,this._latency=null,this.stallCount=0},i.onLevelUpdated=function(e,t){var r=t.details;this.levelDetails=r,r.advanced&&this.timeupdate(),!r.live&&this.media&&this.media.removeEventListener("timeupdate",this.timeupdateHandler)},i.onError=function(e,t){t.details===o.ErrorDetails.BUFFER_STALLED_ERROR&&(this.stallCount++,l.logger.warn("[playback-rate-controller]: Stall detected, adjusting target latency"))},i.timeupdate=function(){var e=this.media,t=this.levelDetails;if(e&&t){this.currentTime=e.currentTime;var r=this.computeLatency();if(null!==r){this._latency=r;var i=this.config,n=i.lowLatencyMode,a=i.maxLiveSyncPlaybackRate;if(n&&1!==a){var s=this.targetLatency;if(null!==s){var o=r-s,l=o<Math.min(this.maxLatency,s+t.targetduration);if(t.live&&l&&o>.05&&this.forwardBufferLength>1){var u=Math.min(2,Math.max(1,a)),d=Math.round(2/(1+Math.exp(-.75*o-this.edgeStalled))*20)/20;e.playbackRate=Math.min(u,Math.max(1,d))}else 1!==e.playbackRate&&0!==e.playbackRate&&(e.playbackRate=1)}}}}},i.estimateLiveEdge=function(){var e=this.levelDetails;return null===e?null:e.edge+e.age},i.computeLatency=function(){var e=this.estimateLiveEdge();return null===e?null:e-this.currentTime},t=e,(r=[{key:"latency",get:function(){return this._latency||0}},{key:"maxLatency",get:function(){var e=this.config,t=this.levelDetails;return void 0!==e.liveMaxLatencyDuration?e.liveMaxLatencyDuration:t?e.liveMaxLatencyDurationCount*t.targetduration:0}},{key:"targetLatency",get:function(){var e=this.levelDetails;if(null===e)return null;var t=e.holdBack,r=e.partHoldBack,i=e.targetduration,n=this.config,a=n.liveSyncDuration,s=n.liveSyncDurationCount,o=n.lowLatencyMode,l=this.hls.userConfig,u=o&&r||t;(l.liveSyncDuration||l.liveSyncDurationCount||0===u)&&(u=void 0!==a?a:s*i);var d=i;return u+Math.min(1*this.stallCount,d)}},{key:"liveSyncPosition",get:function(){var e=this.estimateLiveEdge(),t=this.targetLatency,r=this.levelDetails;if(null===e||null===t||null===r)return null;var i=r.edge,n=e-t-this.edgeStalled,a=i-r.totalduration,s=i-(this.config.lowLatencyMode&&r.partTarget||r.targetduration);return Math.min(Math.max(a,n),s)}},{key:"drift",get:function(){var e=this.levelDetails;return null===e?1:e.drift}},{key:"edgeStalled",get:function(){var e=this.levelDetails;if(null===e)return 0;var t=3*(this.config.lowLatencyMode&&e.partTarget||e.targetduration);return Math.max(e.age-t,0)}},{key:"forwardBufferLength",get:function(){var e=this.media,t=this.levelDetails;if(!e||!t)return 0;var r=e.buffered.length;return(r?e.buffered.end(r-1):t.edge)-this.currentTime}}])&&j(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Y(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}!function(e){e.No="",e.Yes="YES",e.v2="v2"}(K||(K={}));var q=function(){function e(e,t,r){this.msn=void 0,this.part=void 0,this.skip=void 0,this.msn=e,this.part=t,this.skip=r}return e.prototype.addDirectives=function(e){var t=new self.URL(e);return void 0!==this.msn&&t.searchParams.set("_HLS_msn",this.msn.toString()),void 0!==this.part&&t.searchParams.set("_HLS_part",this.part.toString()),this.skip&&t.searchParams.set("_HLS_skip",this.skip),t.toString()},e}(),z=function(){function e(e){this.attrs=void 0,this.audioCodec=void 0,this.bitrate=void 0,this.codecSet=void 0,this.height=void 0,this.id=void 0,this.name=void 0,this.videoCodec=void 0,this.width=void 0,this.unknownCodecs=void 0,this.audioGroupIds=void 0,this.details=void 0,this.fragmentError=0,this.loadError=0,this.loaded=void 0,this.realBitrate=0,this.textGroupIds=void 0,this.url=void 0,this._urlId=0,this.url=[e.url],this.attrs=e.attrs,this.bitrate=e.bitrate,e.details&&(this.details=e.details),this.id=e.id||0,this.name=e.name,this.width=e.width||0,this.height=e.height||0,this.audioCodec=e.audioCodec,this.videoCodec=e.videoCodec,this.unknownCodecs=e.unknownCodecs,this.codecSet=[e.videoCodec,e.audioCodec].filter((function(e){return e})).join(",").replace(/\.[^.,]+/g,"")}var t,r;return t=e,(r=[{key:"maxBitrate",get:function(){return Math.max(this.realBitrate,this.bitrate)}},{key:"uri",get:function(){return this.url[this._urlId]||""}},{key:"urlId",get:function(){return this._urlId},set:function(e){var t=e%this.url.length;this._urlId!==t&&(this.details=void 0,this._urlId=t)}}])&&Y(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function X(){return X=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},X.apply(this,arguments)}function Q(e,t,r){switch(t){case"audio":e.audioGroupIds||(e.audioGroupIds=[]),e.audioGroupIds.push(r);break;case"text":e.textGroupIds||(e.textGroupIds=[]),e.textGroupIds.push(r)}}function $(e){var t={};e.forEach((function(e){var r=e.groupId||"";e.id=t[r]=t[r]||0,t[r]++}))}function Z(e,t){var r=t.startPTS;if((0,a.isFiniteNumber)(r)){var i,n=0;t.sn>e.sn?(n=r-e.start,i=e):(n=e.start-r,i=t),i.duration!==n&&(i.duration=n)}else t.sn>e.sn?e.cc===t.cc&&e.minEndPTS?t.start=e.start+(e.minEndPTS-e.start):t.start=e.start+e.duration:t.start=Math.max(e.start-t.duration,0)}function J(e,t,r,i,n,s){i-r<=0&&(l.logger.warn("Fragment should have a positive duration",t),i=r+t.duration,s=n+t.duration);var o=r,u=i,d=t.startPTS,h=t.endPTS;if((0,a.isFiniteNumber)(d)){var f=Math.abs(d-r);(0,a.isFiniteNumber)(t.deltaPTS)?t.deltaPTS=Math.max(f,t.deltaPTS):t.deltaPTS=f,o=Math.max(r,d),r=Math.min(r,d),n=Math.min(n,t.startDTS),u=Math.min(i,h),i=Math.max(i,h),s=Math.max(s,t.endDTS)}t.duration=i-r;var c=r-t.start;t.appendedPTS=i,t.start=t.startPTS=r,t.maxStartPTS=o,t.startDTS=n,t.endPTS=i,t.minEndPTS=u,t.endDTS=s;var v,g=t.sn;if(!e||g<e.startSN||g>e.endSN)return 0;var p=g-e.startSN,m=e.fragments;for(m[p]=t,v=p;v>0;v--)Z(m[v],m[v-1]);for(v=p;v<m.length-1;v++)Z(m[v],m[v+1]);return e.fragmentHint&&Z(m[m.length-1],e.fragmentHint),e.PTSKnown=e.alignedSliding=!0,c}function ee(e,t){var r=t.startSN+t.skippedSegments-e.startSN,i=e.fragments;r<0||r>=i.length||function(e,t){if(t){for(var r=e.fragments,i=e.skippedSegments;i<r.length;i++)r[i].start+=t;e.fragmentHint&&(e.fragmentHint.start+=t)}}(t,i[r].start)}var te=function(){function e(e,t){this.hls=void 0,this.timer=-1,this.canLoad=!1,this.retryCount=0,this.log=void 0,this.warn=void 0,this.log=l.logger.log.bind(l.logger,t+":"),this.warn=l.logger.warn.bind(l.logger,t+":"),this.hls=e}var t=e.prototype;return t.destroy=function(){this.clearTimer(),this.hls=this.log=this.warn=null},t.onError=function(e,t){t.fatal&&t.type===o.ErrorTypes.NETWORK_ERROR&&this.clearTimer()},t.clearTimer=function(){clearTimeout(this.timer),this.timer=-1},t.startLoad=function(){this.canLoad=!0,this.retryCount=0,this.loadPlaylist()},t.stopLoad=function(){this.canLoad=!1,this.clearTimer()},t.switchParams=function(e,t){var r=null==t?void 0:t.renditionReports;if(r)for(var i=0;i<r.length;i++){var n=r[i],s=""+n.URI;if(s===e.slice(-s.length)){var o=parseInt(n["LAST-MSN"]),l=parseInt(n["LAST-PART"]);if(t&&this.hls.config.lowLatencyMode){var u=Math.min(t.age-t.partTarget,t.targetduration);void 0!==l&&u>t.partTarget&&(l+=1)}if((0,a.isFiniteNumber)(o))return new q(o,(0,a.isFiniteNumber)(l)?l:void 0,K.No)}}},t.loadPlaylist=function(e){},t.shouldLoadTrack=function(e){return this.canLoad&&e&&!!e.url&&(!e.details||e.details.live)},t.playlistLoaded=function(e,t,r){var i=this,n=t.details,s=t.stats,o=s.loading.end?Math.max(0,self.performance.now()-s.loading.end):0;if(n.advancedDateTime=Date.now()-o,n.live||null!=r&&r.live){if(n.reloaded(r),r&&this.log("live playlist "+e+" "+(n.advanced?"REFRESHED "+n.lastPartSn+"-"+n.lastPartIndex:"MISSED")),r&&n.fragments.length>0&&function(e,t){for(var r=null,i=e.fragments,n=i.length-1;n>=0;n--){var s=i[n].initSegment;if(s){r=s;break}}e.fragmentHint&&delete e.fragmentHint.endPTS;var o,u,d,h,f,c=0;if(function(e,t,r){for(var i=t.skippedSegments,n=Math.max(e.startSN,t.startSN)-t.startSN,a=(e.fragmentHint?1:0)+(i?t.endSN:Math.min(e.endSN,t.endSN))-t.startSN,s=t.startSN-e.startSN,o=t.fragmentHint?t.fragments.concat(t.fragmentHint):t.fragments,l=e.fragmentHint?e.fragments.concat(e.fragmentHint):e.fragments,u=n;u<=a;u++){var d=l[s+u],h=o[u];i&&!h&&u<i&&(h=t.fragments[u]=d),d&&h&&r(d,h)}}(e,t,(function(e,i){e.relurl&&(c=e.cc-i.cc),(0,a.isFiniteNumber)(e.startPTS)&&(0,a.isFiniteNumber)(e.endPTS)&&(i.start=i.startPTS=e.startPTS,i.startDTS=e.startDTS,i.appendedPTS=e.appendedPTS,i.maxStartPTS=e.maxStartPTS,i.endPTS=e.endPTS,i.endDTS=e.endDTS,i.minEndPTS=e.minEndPTS,i.duration=e.endPTS-e.startPTS,i.duration&&(o=i),t.PTSKnown=t.alignedSliding=!0),i.elementaryStreams=e.elementaryStreams,i.loader=e.loader,i.stats=e.stats,i.urlId=e.urlId,e.initSegment&&(i.initSegment=e.initSegment,r=e.initSegment)})),r&&(t.fragmentHint?t.fragments.concat(t.fragmentHint):t.fragments).forEach((function(e){var t;e.initSegment&&e.initSegment.relurl!==(null===(t=r)||void 0===t?void 0:t.relurl)||(e.initSegment=r)})),t.skippedSegments)if(t.deltaUpdateFailed=t.fragments.some((function(e){return!e})),t.deltaUpdateFailed){l.logger.warn("[level-helper] Previous playlist missing segments skipped in delta playlist");for(var v=t.skippedSegments;v--;)t.fragments.shift();t.startSN=t.fragments[0].sn,t.startCC=t.fragments[0].cc}else t.canSkipDateRanges&&(t.dateRanges=(u=e.dateRanges,d=t.dateRanges,h=t.recentlyRemovedDateranges,f=X({},u),h&&h.forEach((function(e){delete f[e]})),Object.keys(d).forEach((function(e){var t=new g(d[e].attr,f[e]);t.isValid?f[e]=t:l.logger.warn('Ignoring invalid Playlist Delta Update DATERANGE tag: "'+JSON.stringify(d[e].attr)+'"')})),f));var p=t.fragments;if(c){l.logger.warn("discontinuity sliding from playlist, take drift into account");for(var m=0;m<p.length;m++)p[m].cc+=c}t.skippedSegments&&(t.startCC=t.fragments[0].cc),function(e,t,r){if(e&&t)for(var i=0,n=0,a=e.length;n<=a;n++){var s=e[n],o=t[n+i];s&&o&&s.index===o.index&&s.fragment.sn===o.fragment.sn?(l=s,(u=o).elementaryStreams=l.elementaryStreams,u.stats=l.stats):i--}var l,u}(e.partList,t.partList),o?J(t,o,o.startPTS,o.endPTS,o.startDTS,o.endDTS):ee(e,t),p.length&&(t.totalduration=t.edge-p[0].start),t.driftStartTime=e.driftStartTime,t.driftStart=e.driftStart;var y=t.advancedDateTime;if(t.advanced&&y){var E=t.edge;t.driftStart||(t.driftStartTime=y,t.driftStart=E),t.driftEndTime=y,t.driftEnd=E}else t.driftEndTime=e.driftEndTime,t.driftEnd=e.driftEnd,t.advancedDateTime=e.advancedDateTime}(r,n),!this.canLoad||!n.live)return;var u,d=void 0,h=void 0;if(n.canBlockReload&&n.endSN&&n.advanced){var f=this.hls.config.lowLatencyMode,c=n.lastPartSn,v=n.endSN,p=n.lastPartIndex,m=c===v;-1!==p?(d=m?v+1:c,h=m?f?0:p:p+1):d=v+1;var y=n.age,E=y+n.ageHeader,T=Math.min(E-n.partTarget,1.5*n.targetduration);if(T>0){if(r&&T>r.tuneInGoal)this.warn("CDN Tune-in goal increased from: "+r.tuneInGoal+" to: "+T+" with playlist age: "+n.age),T=0;else{var S=Math.floor(T/n.targetduration);d+=S,void 0!==h&&(h+=Math.round(T%n.targetduration/n.partTarget)),this.log("CDN Tune-in age: "+n.ageHeader+"s last advanced "+y.toFixed(2)+"s goal: "+T+" skip sn "+S+" to part "+h)}n.tuneInGoal=T}if(u=this.getDeliveryDirectives(n,t.deliveryDirectives,d,h),f||!m)return void this.loadPlaylist(u)}else u=this.getDeliveryDirectives(n,t.deliveryDirectives,d,h);var b=function(e,t){var r,i=1e3*e.levelTargetDuration,n=i/2,a=e.age,s=a>0&&a<3*i,o=t.loading.end-t.loading.start,l=e.availabilityDelay;if(!1===e.updated)if(s){var u=333*e.misses;r=Math.max(Math.min(n,2*o),u),e.availabilityDelay=(e.availabilityDelay||0)+r}else r=n;else s?(l=Math.min(l||i/2,a),e.availabilityDelay=l,r=l+i-a):r=i-o;return Math.round(r)}(n,s);void 0!==d&&n.canBlockReload&&(b-=n.partTarget||1),this.log("reload live playlist "+e+" in "+Math.round(b)+" ms"),this.timer=self.setTimeout((function(){return i.loadPlaylist(u)}),b)}else this.clearTimer()},t.getDeliveryDirectives=function(e,t,r,i){var n=function(e,t){var r=e.canSkipUntil,i=e.canSkipDateRanges,n=e.endSN;return r&&(void 0!==t?t-n:0)<r?i?K.v2:K.Yes:K.No}(e,r);return null!=t&&t.skip&&e.deltaUpdateFailed&&(r=t.msn,i=t.part,n=K.No),new q(r,i,n)},t.retryLoadingOrFail=function(e){var t,r=this,i=this.hls.config,n=this.retryCount<i.levelLoadingMaxRetry;if(n)if(this.retryCount++,e.details.indexOf("LoadTimeOut")>-1&&null!==(t=e.context)&&void 0!==t&&t.deliveryDirectives)this.warn("retry playlist loading #"+this.retryCount+' after "'+e.details+'"'),this.loadPlaylist();else{var a=Math.min(Math.pow(2,this.retryCount)*i.levelLoadingRetryDelay,i.levelLoadingMaxRetryTimeout);this.timer=self.setTimeout((function(){return r.loadPlaylist()}),a),this.warn("retry playlist loading #"+this.retryCount+" in "+a+' ms after "'+e.details+'"')}else this.warn('cannot recover from error "'+e.details+'"'),this.clearTimer(),e.fatal=!0;return n},e}();function re(){return re=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},re.apply(this,arguments)}function ie(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function ne(e,t){return ne=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},ne(e,t)}var ae,se=/chrome|firefox/.test(navigator.userAgent.toLowerCase()),oe=function(e){var t,r;function i(t){var r;return(r=e.call(this,t,"[level-controller]")||this)._levels=[],r._firstLevel=-1,r._startLevel=void 0,r.currentLevelIndex=-1,r.manualLevelIndex=-1,r.onParsedComplete=void 0,r._registerListeners(),r}r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,ne(t,r);var n,a,l=i.prototype;return l._registerListeners=function(){var e=this.hls;e.on(s.Events.MANIFEST_LOADED,this.onManifestLoaded,this),e.on(s.Events.LEVEL_LOADED,this.onLevelLoaded,this),e.on(s.Events.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),e.on(s.Events.FRAG_LOADED,this.onFragLoaded,this),e.on(s.Events.ERROR,this.onError,this)},l._unregisterListeners=function(){var e=this.hls;e.off(s.Events.MANIFEST_LOADED,this.onManifestLoaded,this),e.off(s.Events.LEVEL_LOADED,this.onLevelLoaded,this),e.off(s.Events.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),e.off(s.Events.FRAG_LOADED,this.onFragLoaded,this),e.off(s.Events.ERROR,this.onError,this)},l.destroy=function(){this._unregisterListeners(),this.manualLevelIndex=-1,this._levels.length=0,e.prototype.destroy.call(this)},l.startLoad=function(){this._levels.forEach((function(e){e.loadError=0})),e.prototype.startLoad.call(this)},l.onManifestLoaded=function(e,t){var r,i,n=[],a=[],l=[],u={},d=!1,h=!1,f=!1;if(t.levels.forEach((function(e){var t=e.attrs;d=d||!(!e.width||!e.height),h=h||!!e.videoCodec,f=f||!!e.audioCodec,se&&e.audioCodec&&-1!==e.audioCodec.indexOf("mp4a.40.34")&&(e.audioCodec=void 0);var r=e.bitrate+"-"+e.attrs.RESOLUTION+"-"+e.attrs.CODECS;(i=u[r])?i.url.push(e.url):(i=new z(e),u[r]=i,n.push(i)),t&&(t.AUDIO&&Q(i,"audio",t.AUDIO),t.SUBTITLES&&Q(i,"text",t.SUBTITLES))})),(d||h)&&f&&(n=n.filter((function(e){var t=e.videoCodec,r=e.width,i=e.height;return!!t||!(!r||!i)}))),n=n.filter((function(e){var t=e.audioCodec,r=e.videoCodec;return(!t||S(t,"audio"))&&(!r||S(r,"video"))})),t.audioTracks&&$(a=t.audioTracks.filter((function(e){return!e.audioCodec||S(e.audioCodec,"audio")}))),t.subtitles&&$(l=t.subtitles),n.length>0){r=n[0].bitrate,n.sort((function(e,t){return e.bitrate-t.bitrate})),this._levels=n;for(var c=0;c<n.length;c++)if(n[c].bitrate===r){this._firstLevel=c,this.log("manifest loaded, "+n.length+" level(s) found, first bitrate: "+r);break}var v=f&&!h,g={levels:n,audioTracks:a,subtitleTracks:l,firstLevel:this._firstLevel,stats:t.stats,audio:f,video:h,altAudio:!v&&a.some((function(e){return!!e.url}))};this.hls.trigger(s.Events.MANIFEST_PARSED,g),(this.hls.config.autoStartLoad||this.hls.forceStartLoad)&&this.hls.startLoad(this.hls.config.startPosition)}else this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,fatal:!0,url:t.url,reason:"no level with compatible codecs found in manifest"})},l.onError=function(t,r){var i;if(e.prototype.onError.call(this,t,r),!r.fatal){var n=r.context,a=this._levels[this.currentLevelIndex];if(n&&(n.type===C.PlaylistContextType.AUDIO_TRACK&&a.audioGroupIds&&n.groupId===a.audioGroupIds[a.urlId]||n.type===C.PlaylistContextType.SUBTITLE_TRACK&&a.textGroupIds&&n.groupId===a.textGroupIds[a.urlId]))this.redundantFailover(this.currentLevelIndex);else{var s,l=!1,u=!0;switch(r.details){case o.ErrorDetails.FRAG_LOAD_ERROR:case o.ErrorDetails.FRAG_LOAD_TIMEOUT:case o.ErrorDetails.KEY_LOAD_ERROR:case o.ErrorDetails.KEY_LOAD_TIMEOUT:if(r.frag){var d=r.frag.type===C.PlaylistLevelType.MAIN?r.frag.level:this.currentLevelIndex,h=this._levels[d];h?(h.fragmentError++,h.fragmentError>this.hls.config.fragLoadingMaxRetry&&(s=d)):s=d}break;case o.ErrorDetails.LEVEL_LOAD_ERROR:case o.ErrorDetails.LEVEL_LOAD_TIMEOUT:n&&(n.deliveryDirectives&&(u=!1),s=n.level),l=!0;break;case o.ErrorDetails.REMUX_ALLOC_ERROR:s=null!=(i=r.level)?i:this.currentLevelIndex,l=!0}void 0!==s&&this.recoverLevel(r,s,l,u)}}},l.recoverLevel=function(e,t,r,i){var n=e.details,a=this._levels[t];if(a.loadError++,r){if(!this.retryLoadingOrFail(e))return void(this.currentLevelIndex=-1);e.levelRetry=!0}if(i){var s=a.url.length;if(s>1&&a.loadError<s)e.levelRetry=!0,this.redundantFailover(t);else if(-1===this.manualLevelIndex){for(var o=-1,l=this._levels,u=l.length;u--;){var d=(u+this.currentLevelIndex)%l.length;if(d!==this.currentLevelIndex&&0===l[d].loadError){o=d;break}}o>-1&&this.currentLevelIndex!==o&&(this.warn(n+": switch to "+o),e.levelRetry=!0,this.hls.nextAutoLevel=o)}}},l.redundantFailover=function(e){var t=this._levels[e],r=t.url.length;if(r>1){var i=(t.urlId+1)%r;this.warn("Switching to redundant URL-id "+i),this._levels.forEach((function(e){e.urlId=i})),this.level=e}},l.onFragLoaded=function(e,t){var r=t.frag;if(void 0!==r&&r.type===C.PlaylistLevelType.MAIN){var i=this._levels[r.level];void 0!==i&&(i.fragmentError=0,i.loadError=0)}},l.onLevelLoaded=function(e,t){var r,i,n=t.level,a=t.details,s=this._levels[n];if(!s)return this.warn("Invalid level index "+n),void(null!==(i=t.deliveryDirectives)&&void 0!==i&&i.skip&&(a.deltaUpdateFailed=!0));n===this.currentLevelIndex?(0===s.fragmentError&&(s.loadError=0,this.retryCount=0),this.playlistLoaded(n,t,s.details)):null!==(r=t.deliveryDirectives)&&void 0!==r&&r.skip&&(a.deltaUpdateFailed=!0)},l.onAudioTrackSwitched=function(e,t){var r=this.hls.levels[this.currentLevelIndex];if(r&&r.audioGroupIds){for(var i=-1,n=this.hls.audioTracks[t.id].groupId,a=0;a<r.audioGroupIds.length;a++)if(r.audioGroupIds[a]===n){i=a;break}i!==r.urlId&&(r.urlId=i,this.startLoad())}},l.loadPlaylist=function(e){var t=this.currentLevelIndex,r=this._levels[t];if(this.canLoad&&r&&r.url.length>0){var i=r.urlId,n=r.url[i];if(e)try{n=e.addDirectives(n)}catch(e){this.warn("Could not construct new URL with HLS Delivery Directives: "+e)}this.log("Attempt loading level index "+t+(e?" at sn "+e.msn+" part "+e.part:"")+" with URL-id "+i+" "+n),this.clearTimer(),this.hls.trigger(s.Events.LEVEL_LOADING,{url:n,level:t,id:i,deliveryDirectives:e||null})}},l.removeLevel=function(e,t){var r=function(e,r){return r!==t},i=this._levels.filter((function(i,n){return n!==e||i.url.length>1&&void 0!==t&&(i.url=i.url.filter(r),i.audioGroupIds&&(i.audioGroupIds=i.audioGroupIds.filter(r)),i.textGroupIds&&(i.textGroupIds=i.textGroupIds.filter(r)),i.urlId=0,!0)})).map((function(e,t){var r=e.details;return null!=r&&r.fragments&&r.fragments.forEach((function(e){e.level=t})),e}));this._levels=i,this.hls.trigger(s.Events.LEVELS_UPDATED,{levels:i})},n=i,(a=[{key:"levels",get:function(){return 0===this._levels.length?null:this._levels}},{key:"level",get:function(){return this.currentLevelIndex},set:function(e){var t,r=this._levels;if(0!==r.length&&(this.currentLevelIndex!==e||null===(t=r[e])||void 0===t||!t.details)){if(e<0||e>=r.length){var i=e<0;if(this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.OTHER_ERROR,details:o.ErrorDetails.LEVEL_SWITCH_ERROR,level:e,fatal:i,reason:"invalid level idx"}),i)return;e=Math.min(e,r.length-1)}this.clearTimer();var n=this.currentLevelIndex,a=r[n],l=r[e];this.log("switching to level "+e+" from "+n),this.currentLevelIndex=e;var u=re({},l,{level:e,maxBitrate:l.maxBitrate,uri:l.uri,urlId:l.urlId});delete u._urlId,this.hls.trigger(s.Events.LEVEL_SWITCHING,u);var d=l.details;if(!d||d.live){var h=this.switchParams(l.uri,null==a?void 0:a.details);this.loadPlaylist(h)}}}},{key:"manualLevel",get:function(){return this.manualLevelIndex},set:function(e){this.manualLevelIndex=e,void 0===this._startLevel&&(this._startLevel=e),-1!==e&&(this.level=e)}},{key:"firstLevel",get:function(){return this._firstLevel},set:function(e){this._firstLevel=e}},{key:"startLevel",get:function(){if(void 0===this._startLevel){var e=this.hls.config.startLevel;return void 0!==e?e:this._firstLevel}return this._startLevel},set:function(e){this._startLevel=e}},{key:"nextLoadLevel",get:function(){return-1!==this.manualLevelIndex?this.manualLevelIndex:this.hls.nextAutoLevel},set:function(e){this.level=e,-1===this.manualLevelIndex&&(this.hls.nextAutoLevel=e)}}])&&ie(n.prototype,a),Object.defineProperty(n,"prototype",{writable:!1}),i}(te);!function(e){e.NOT_LOADED="NOT_LOADED",e.APPENDING="APPENDING",e.PARTIAL="PARTIAL",e.OK="OK"}(ae||(ae={}));var le=function(){function e(e){this.activeFragment=null,this.activeParts=null,this.fragments=Object.create(null),this.timeRanges=Object.create(null),this.bufferPadding=.2,this.hls=void 0,this.hls=e,this._registerListeners()}var t=e.prototype;return t._registerListeners=function(){var e=this.hls;e.on(s.Events.BUFFER_APPENDED,this.onBufferAppended,this),e.on(s.Events.FRAG_BUFFERED,this.onFragBuffered,this),e.on(s.Events.FRAG_LOADED,this.onFragLoaded,this)},t._unregisterListeners=function(){var e=this.hls;e.off(s.Events.BUFFER_APPENDED,this.onBufferAppended,this),e.off(s.Events.FRAG_BUFFERED,this.onFragBuffered,this),e.off(s.Events.FRAG_LOADED,this.onFragLoaded,this)},t.destroy=function(){this._unregisterListeners(),this.fragments=this.timeRanges=null},t.getAppendedFrag=function(e,t){if(t===C.PlaylistLevelType.MAIN){var r=this.activeFragment,i=this.activeParts;if(!r)return null;if(i)for(var n=i.length;n--;){var a=i[n],s=a?a.end:r.appendedPTS;if(a.start<=e&&void 0!==s&&e<=s)return n>9&&(this.activeParts=i.slice(n-9)),a}else if(r.start<=e&&void 0!==r.appendedPTS&&e<=r.appendedPTS)return r}return this.getBufferedFrag(e,t)},t.getBufferedFrag=function(e,t){for(var r=this.fragments,i=Object.keys(r),n=i.length;n--;){var a=r[i[n]];if((null==a?void 0:a.body.type)===t&&a.buffered){var s=a.body;if(s.start<=e&&e<=s.end)return s}}return null},t.detectEvictedFragments=function(e,t,r){var i=this;Object.keys(this.fragments).forEach((function(n){var a=i.fragments[n];if(a)if(a.buffered){var s=a.range[e];s&&s.time.some((function(e){var r=!i.isTimeBuffered(e.startPTS,e.endPTS,t);return r&&i.removeFragment(a.body),r}))}else a.body.type===r&&i.removeFragment(a.body)}))},t.detectPartialFragments=function(e){var t=this,r=this.timeRanges,i=e.frag,n=e.part;if(r&&"initSegment"!==i.sn){var a=de(i),s=this.fragments[a];s&&(Object.keys(r).forEach((function(e){var a=i.elementaryStreams[e];if(a){var o=r[e],l=null!==n||!0===a.partial;s.range[e]=t.getBufferedTimes(i,n,l,o)}})),s.loaded=null,Object.keys(s.range).length?s.buffered=!0:this.removeFragment(s.body))}},t.fragBuffered=function(e){var t=de(e),r=this.fragments[t];r&&(r.loaded=null,r.buffered=!0)},t.getBufferedTimes=function(e,t,r,i){for(var n={time:[],partial:r},a=t?t.start:e.start,s=t?t.end:e.end,o=e.minEndPTS||s,l=e.maxStartPTS||a,u=0;u<i.length;u++){var d=i.start(u)-this.bufferPadding,h=i.end(u)+this.bufferPadding;if(l>=d&&o<=h){n.time.push({startPTS:Math.max(a,i.start(u)),endPTS:Math.min(s,i.end(u))});break}if(a<h&&s>d)n.partial=!0,n.time.push({startPTS:Math.max(a,i.start(u)),endPTS:Math.min(s,i.end(u))});else if(s<=d)break}return n},t.getPartialFragment=function(e){var t,r,i,n=null,a=0,s=this.bufferPadding,o=this.fragments;return Object.keys(o).forEach((function(l){var u=o[l];u&&ue(u)&&(r=u.body.start-s,i=u.body.end+s,e>=r&&e<=i&&(t=Math.min(e-r,i-e),a<=t&&(n=u.body,a=t)))})),n},t.getState=function(e){var t=de(e),r=this.fragments[t];return r?r.buffered?ue(r)?ae.PARTIAL:ae.OK:ae.APPENDING:ae.NOT_LOADED},t.isTimeBuffered=function(e,t,r){for(var i,n,a=0;a<r.length;a++){if(i=r.start(a)-this.bufferPadding,n=r.end(a)+this.bufferPadding,e>=i&&t<=n)return!0;if(t<=i)return!1}return!1},t.onFragLoaded=function(e,t){var r=t.frag,i=t.part;if("initSegment"!==r.sn&&!r.bitrateTest&&!i){var n=de(r);this.fragments[n]={body:r,loaded:t,buffered:!1,range:Object.create(null)}}},t.onBufferAppended=function(e,t){var r=this,i=t.frag,n=t.part,a=t.timeRanges;if(i.type===C.PlaylistLevelType.MAIN)if(this.activeFragment=i,n){var s=this.activeParts;s||(this.activeParts=s=[]),s.push(n)}else this.activeParts=null;this.timeRanges=a,Object.keys(a).forEach((function(e){var t=a[e];if(r.detectEvictedFragments(e,t),!n)for(var s=0;s<t.length;s++)i.appendedPTS=Math.max(t.end(s),i.appendedPTS||0)}))},t.onFragBuffered=function(e,t){this.detectPartialFragments(t)},t.hasFragment=function(e){var t=de(e);return!!this.fragments[t]},t.removeFragmentsInRange=function(e,t,r){var i=this;Object.keys(this.fragments).forEach((function(n){var a=i.fragments[n];if(a&&a.buffered){var s=a.body;s.type===r&&s.start<t&&s.end>e&&i.removeFragment(s)}}))},t.removeFragment=function(e){var t=de(e);e.stats.loaded=0,e.clearElementaryStreamInfo(),delete this.fragments[t]},t.removeAllFragments=function(){this.fragments=Object.create(null),this.activeFragment=null,this.activeParts=null},e}();function ue(e){var t,r;return e.buffered&&((null===(t=e.range.video)||void 0===t?void 0:t.partial)||(null===(r=e.range.audio)||void 0===r?void 0:r.partial))}function de(e){return e.type+"_"+e.level+"_"+e.urlId+"_"+e.sn}var he=function(){function e(){this._boundTick=void 0,this._tickTimer=null,this._tickInterval=null,this._tickCallCount=0,this._boundTick=this.tick.bind(this)}var t=e.prototype;return t.destroy=function(){this.onHandlerDestroying(),this.onHandlerDestroyed()},t.onHandlerDestroying=function(){this.clearNextTick(),this.clearInterval()},t.onHandlerDestroyed=function(){},t.hasInterval=function(){return!!this._tickInterval},t.hasNextTick=function(){return!!this._tickTimer},t.setInterval=function(e){return!this._tickInterval&&(this._tickInterval=self.setInterval(this._boundTick,e),!0)},t.clearInterval=function(){return!!this._tickInterval&&(self.clearInterval(this._tickInterval),this._tickInterval=null,!0)},t.clearNextTick=function(){return!!this._tickTimer&&(self.clearTimeout(this._tickTimer),this._tickTimer=null,!0)},t.tick=function(){this._tickCallCount++,1===this._tickCallCount&&(this.doTick(),this._tickCallCount>1&&this.tickImmediate(),this._tickCallCount=0)},t.tickImmediate=function(){this.clearNextTick(),this._tickTimer=self.setTimeout(this._boundTick,0)},t.doTick=function(){},e}(),fe={length:0,start:function(){return 0},end:function(){return 0}},ce=function(){function e(){}return e.isBuffered=function(t,r){try{if(t)for(var i=e.getBuffered(t),n=0;n<i.length;n++)if(r>=i.start(n)&&r<=i.end(n))return!0}catch(e){}return!1},e.bufferInfo=function(t,r,i){try{if(t){var n,a=e.getBuffered(t),s=[];for(n=0;n<a.length;n++)s.push({start:a.start(n),end:a.end(n)});return this.bufferedInfo(s,r,i)}}catch(e){}return{len:0,start:r,end:r,nextStart:void 0}},e.bufferedInfo=function(e,t,r){t=Math.max(0,t),e.sort((function(e,t){return e.start-t.start||t.end-e.end}));var i=[];if(r)for(var n=0;n<e.length;n++){var a=i.length;if(a){var s=i[a-1].end;e[n].start-s<r?e[n].end>s&&(i[a-1].end=e[n].end):i.push(e[n])}else i.push(e[n])}else i=e;for(var o,l=0,u=t,d=t,h=0;h<i.length;h++){var f=i[h].start,c=i[h].end;if(t+r>=f&&t<c)u=f,l=(d=c)-t;else if(t+r<f){o=f;break}}return{len:l,start:u||0,end:d||0,nextStart:o}},e.getBuffered=function(e){try{return e.buffered}catch(e){return l.logger.log("failed to get media.buffered",e),fe}},e}(),ve=function(e,t,r,i,n,a){void 0===i&&(i=0),void 0===n&&(n=-1),void 0===a&&(a=!1),this.level=void 0,this.sn=void 0,this.part=void 0,this.id=void 0,this.size=void 0,this.partial=void 0,this.transmuxing={start:0,executeStart:0,executeEnd:0,end:0},this.buffering={audio:{start:0,executeStart:0,executeEnd:0,end:0},video:{start:0,executeStart:0,executeEnd:0,end:0},audiovideo:{start:0,executeStart:0,executeEnd:0,end:0}},this.level=e,this.sn=t,this.id=r,this.size=i,this.part=n,this.partial=a};function ge(e,t){if(e){var r=e.start+t;e.start=e.startPTS=r,e.endPTS=r+e.duration}}function pe(e,t){for(var r=t.fragments,i=0,n=r.length;i<n;i++)ge(r[i],e);t.fragmentHint&&ge(t.fragmentHint,e),t.alignedSliding=!0}const me=function(e,t){for(var r=0,i=e.length-1,n=null,a=null;r<=i;){var s=t(a=e[n=(r+i)/2|0]);if(s>0)r=n+1;else{if(!(s<0))return a;i=n-1}}return null};function ye(e,t,r){void 0===e&&(e=0),void 0===t&&(t=0);var i=Math.min(t,r.duration+(r.deltaPTS?r.deltaPTS:0));return r.start+r.duration-i<=e?1:r.start-i>e&&r.start?-1:0}function Ee(e,t,r){var i=1e3*Math.min(t,r.duration+(r.deltaPTS?r.deltaPTS:0));return(r.endProgramDateTime||0)-i>e}function Te(e){var t="function"==typeof Map?new Map:void 0;return Te=function(e){if(null===e||(r=e,-1===Function.toString.call(r).indexOf("[native code]")))return e;var r;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,i)}function i(){return Se(e,arguments,Ae(this).constructor)}return i.prototype=Object.create(e.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),Le(i,e)},Te(e)}function Se(e,t,r){return Se=be()?Reflect.construct.bind():function(e,t,r){var i=[null];i.push.apply(i,t);var n=new(Function.bind.apply(e,i));return r&&Le(n,r.prototype),n},Se.apply(null,arguments)}function be(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function Le(e,t){return Le=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},Le(e,t)}function Ae(e){return Ae=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},Ae(e)}var De=Math.pow(2,17),Re=function(){function e(e){this.config=void 0,this.loader=null,this.partLoadTimeout=-1,this.config=e}var t=e.prototype;return t.destroy=function(){this.loader&&(this.loader.destroy(),this.loader=null)},t.abort=function(){this.loader&&this.loader.abort()},t.load=function(e,t){var r=this,i=e.url;if(!i)return Promise.reject(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.FRAG_LOAD_ERROR,fatal:!1,frag:e,networkDetails:null},"Fragment does not have a "+(i?"part list":"url")));this.abort();var n=this.config,a=n.fLoader,s=n.loader;return new Promise((function(i,l){r.loader&&r.loader.destroy();var u=r.loader=e.loader=a?new a(n):new s(n),d=ke(e),h={timeout:n.fragLoadingTimeOut,maxRetry:0,retryDelay:0,maxRetryDelay:n.fragLoadingMaxRetryTimeout,highWaterMark:"initSegment"===e.sn?1/0:De};e.stats=u.stats,u.load(d,h,{onSuccess:function(t,n,a,s){r.resetLoader(e,u),i({frag:e,part:null,payload:t.data,networkDetails:s})},onError:function(t,i,n){r.resetLoader(e,u),l(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.FRAG_LOAD_ERROR,fatal:!1,frag:e,response:t,networkDetails:n}))},onAbort:function(t,i,n){r.resetLoader(e,u),l(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.INTERNAL_ABORTED,fatal:!1,frag:e,networkDetails:n}))},onTimeout:function(t,i,n){r.resetLoader(e,u),l(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.FRAG_LOAD_TIMEOUT,fatal:!1,frag:e,networkDetails:n}))},onProgress:function(r,i,n,a){t&&t({frag:e,part:null,payload:n,networkDetails:a})}})}))},t.loadPart=function(e,t,r){var i=this;this.abort();var n=this.config,a=n.fLoader,s=n.loader;return new Promise((function(l,u){i.loader&&i.loader.destroy();var d=i.loader=e.loader=a?new a(n):new s(n),h=ke(e,t),f={timeout:n.fragLoadingTimeOut,maxRetry:0,retryDelay:0,maxRetryDelay:n.fragLoadingMaxRetryTimeout,highWaterMark:De};t.stats=d.stats,d.load(h,f,{onSuccess:function(n,a,s,o){i.resetLoader(e,d),i.updateStatsFromPart(e,t);var u={frag:e,part:t,payload:n.data,networkDetails:o};r(u),l(u)},onError:function(r,n,a){i.resetLoader(e,d),u(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.FRAG_LOAD_ERROR,fatal:!1,frag:e,part:t,response:r,networkDetails:a}))},onAbort:function(r,n,a){e.stats.aborted=t.stats.aborted,i.resetLoader(e,d),u(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.INTERNAL_ABORTED,fatal:!1,frag:e,part:t,networkDetails:a}))},onTimeout:function(r,n,a){i.resetLoader(e,d),u(new _e({type:o.ErrorTypes.NETWORK_ERROR,details:o.ErrorDetails.FRAG_LOAD_TIMEOUT,fatal:!1,frag:e,part:t,networkDetails:a}))}})}))},t.updateStatsFromPart=function(e,t){var r=e.stats,i=t.stats,n=i.total;if(r.loaded+=i.loaded,n){var a=Math.round(e.duration/t.duration),s=Math.min(Math.round(r.loaded/n),a),o=(a-s)*Math.round(r.loaded/s);r.total=r.loaded+o}else r.total=Math.max(r.loaded,r.total);var l=r.loading,u=i.loading;l.start?l.first+=u.first-u.start:(l.start=u.start,l.first=u.first),l.end=u.end},t.resetLoader=function(e,t){e.loader=null,this.loader===t&&(self.clearTimeout(this.partLoadTimeout),this.loader=null),t.destroy()},e}();function ke(e,t){void 0===t&&(t=null);var r=t||e,i={frag:e,part:t,responseType:"arraybuffer",url:r.url,headers:{},rangeStart:0,rangeEnd:0},n=r.byteRangeStartOffset,s=r.byteRangeEndOffset;return(0,a.isFiniteNumber)(n)&&(0,a.isFiniteNumber)(s)&&(i.rangeStart=n,i.rangeEnd=s),i}var _e=function(e){var t,r;function i(t){for(var r,i=arguments.length,n=new Array(i>1?i-1:0),a=1;a<i;a++)n[a-1]=arguments[a];return(r=e.call.apply(e,[this].concat(n))||this).data=void 0,r.data=t,r}return r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,Le(t,r),i}(Te(Error)),xe=r(21);const Ie=function(e){for(var t="",r=e.length,i=0;i<r;i++)t+="["+e.start(i).toFixed(3)+","+e.end(i).toFixed(3)+"]";return t};function we(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function Ce(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function Pe(e,t){return Pe=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},Pe(e,t)}var Oe="STOPPED",Fe="IDLE",Me="KEY_LOADING",Ne="FRAG_LOADING",Ue="FRAG_LOADING_WAITING_RETRY",Be="PARSING",Ge="PARSED",Ve="ENDED",He="ERROR",je="WAITING_LEVEL",Ke=function(e){var t,r;function i(t,r,i){var n;return(n=e.call(this)||this).hls=void 0,n.fragPrevious=null,n.fragCurrent=null,n.fragmentTracker=void 0,n.transmuxer=null,n._state=Oe,n.media=null,n.mediaBuffer=null,n.config=void 0,n.bitrateTest=!1,n.lastCurrentTime=0,n.nextLoadPosition=0,n.startPosition=0,n.loadedmetadata=!1,n.fragLoadError=0,n.retryDate=0,n.levels=null,n.fragmentLoader=void 0,n.levelLastLoaded=null,n.startFragRequested=!1,n.decrypter=void 0,n.initPTS=[],n.onvseeking=null,n.onvended=null,n.logPrefix="",n.log=void 0,n.warn=void 0,n.logPrefix=i,n.log=l.logger.log.bind(l.logger,i+":"),n.warn=l.logger.warn.bind(l.logger,i+":"),n.hls=t,n.fragmentLoader=new Re(t.config),n.fragmentTracker=r,n.config=t.config,n.decrypter=new xe.default(t,t.config),t.on(s.Events.KEY_LOADED,n.onKeyLoaded,Ce(n)),t.on(s.Events.LEVEL_SWITCHING,n.onLevelSwitching,Ce(n)),n}r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,Pe(t,r);var n,d,h=i.prototype;return h.doTick=function(){this.onTickEnd()},h.onTickEnd=function(){},h.startLoad=function(e){},h.stopLoad=function(){this.fragmentLoader.abort();var e=this.fragCurrent;e&&this.fragmentTracker.removeFragment(e),this.resetTransmuxer(),this.fragCurrent=null,this.fragPrevious=null,this.clearInterval(),this.clearNextTick(),this.state=Oe},h._streamEnded=function(e,t){var r=this.fragCurrent,i=this.fragmentTracker;if(!t.live&&r&&this.media&&r.sn>=t.endSN&&!e.nextStart){var n=t.partList;if(null!=n&&n.length){var a=n[n.length-1];return ce.isBuffered(this.media,a.start+a.duration/2)}var s=i.getState(r);return s===ae.PARTIAL||s===ae.OK}return!1},h.onMediaAttached=function(e,t){var r=this.media=this.mediaBuffer=t.media;this.onvseeking=this.onMediaSeeking.bind(this),this.onvended=this.onMediaEnded.bind(this),r.addEventListener("seeking",this.onvseeking),r.addEventListener("ended",this.onvended);var i=this.config;this.levels&&i.autoStartLoad&&this.state===Oe&&this.startLoad(i.startPosition)},h.onMediaDetaching=function(){var e=this.media;null!=e&&e.ended&&(this.log("MSE detaching and video ended, reset startPosition"),this.startPosition=this.lastCurrentTime=0),e&&this.onvseeking&&this.onvended&&(e.removeEventListener("seeking",this.onvseeking),e.removeEventListener("ended",this.onvended),this.onvseeking=this.onvended=null),this.media=this.mediaBuffer=null,this.loadedmetadata=!1,this.fragmentTracker.removeAllFragments(),this.stopLoad()},h.onMediaSeeking=function(){var e=this.config,t=this.fragCurrent,r=this.media,i=this.mediaBuffer,n=this.state,s=r?r.currentTime:0,o=ce.bufferInfo(i||r,s,e.maxBufferHole);if(this.log("media seeking to "+((0,a.isFiniteNumber)(s)?s.toFixed(3):s)+", state: "+n),n===Ve)this.resetLoadingState();else if(t){var l=e.maxFragLookUpTolerance,u=t.start-l,d=t.start+t.duration+l;if(!o.len||d<o.start||u>o.end){var h=s>d;(s<u||h)&&(h&&t.loader&&(this.log("seeking outside of buffer while fragment load in progress, cancel fragment load"),t.loader.abort()),this.resetLoadingState())}}r&&(this.lastCurrentTime=s),this.loadedmetadata||o.len||(this.nextLoadPosition=this.startPosition=s),this.tickImmediate()},h.onMediaEnded=function(){this.startPosition=this.lastCurrentTime=0},h.onKeyLoaded=function(e,t){if(this.state===Me&&t.frag===this.fragCurrent&&this.levels){this.state=Fe;var r=this.levels[t.frag.level].details;r&&this.loadFragment(t.frag,r,t.frag.start)}},h.onLevelSwitching=function(e,t){this.fragLoadError=0},h.onHandlerDestroying=function(){this.stopLoad(),e.prototype.onHandlerDestroying.call(this)},h.onHandlerDestroyed=function(){this.state=Oe,this.hls.off(s.Events.KEY_LOADED,this.onKeyLoaded,this),this.hls.off(s.Events.LEVEL_SWITCHING,this.onLevelSwitching,this),this.fragmentLoader&&this.fragmentLoader.destroy(),this.decrypter&&this.decrypter.destroy(),this.hls=this.log=this.warn=this.decrypter=this.fragmentLoader=this.fragmentTracker=null,e.prototype.onHandlerDestroyed.call(this)},h.loadKey=function(e,t){this.log("Loading key for "+e.sn+" of ["+t.startSN+"-"+t.endSN+"], "+("[stream-controller]"===this.logPrefix?"level":"track")+" "+e.level),this.state=Me,this.fragCurrent=e,this.hls.trigger(s.Events.KEY_LOADING,{frag:e})},h.loadFragment=function(e,t,r){this._loadFragForPlayback(e,t,r)},h._loadFragForPlayback=function(e,t,r){var i=this;this._doFragLoad(e,t,r,(function(t){if(i.fragContextChanged(e))return i.warn("Fragment "+e.sn+(t.part?" p: "+t.part.index:"")+" of level "+e.level+" was dropped during download."),void i.fragmentTracker.removeFragment(e);e.stats.chunkCount++,i._handleFragmentLoadProgress(t)})).then((function(t){if(t){i.fragLoadError=0;var r=i.state;i.fragContextChanged(e)?(r===Ne||!i.fragCurrent&&r===Be)&&(i.fragmentTracker.removeFragment(e),i.state=Fe):("payload"in t&&(i.log("Loaded fragment "+e.sn+" of level "+e.level),i.hls.trigger(s.Events.FRAG_LOADED,t)),i._handleFragmentLoadComplete(t))}})).catch((function(t){i.state!==Oe&&i.state!==He&&(i.warn(t),i.resetFragmentLoading(e))}))},h.flushMainBuffer=function(e,t,r){if(void 0===r&&(r=null),e-t){var i={startOffset:e,endOffset:t,type:r};this.fragLoadError=0,this.hls.trigger(s.Events.BUFFER_FLUSHING,i)}},h._loadInitSegment=function(e){var t=this;this._doFragLoad(e).then((function(r){if(!r||t.fragContextChanged(e)||!t.levels)throw new Error("init load aborted");return r})).then((function(r){var i=t.hls,n=r.payload,a=e.decryptdata;if(n&&n.byteLength>0&&a&&a.key&&a.iv&&"AES-128"===a.method){var o=self.performance.now();return t.decrypter.webCryptoDecrypt(new Uint8Array(n),a.key.buffer,a.iv.buffer).then((function(t){var n=self.performance.now();return i.trigger(s.Events.FRAG_DECRYPTED,{frag:e,payload:t,stats:{tstart:o,tdecrypt:n}}),r.payload=t,r}))}return r})).then((function(r){var i=t.fragCurrent,n=t.hls,a=t.levels;if(!a)throw new Error("init load aborted, missing levels");a[e.level].details;var o=e.stats;t.state=Fe,t.fragLoadError=0,e.data=new Uint8Array(r.payload),o.parsing.start=o.buffering.start=self.performance.now(),o.parsing.end=o.buffering.end=self.performance.now(),r.frag===i&&n.trigger(s.Events.FRAG_BUFFERED,{stats:o,frag:i,part:null,id:e.type}),t.tick()})).catch((function(r){t.state!==Oe&&t.state!==He&&(t.warn(r),t.resetFragmentLoading(e))}))},h.fragContextChanged=function(e){var t=this.fragCurrent;return!e||!t||e.level!==t.level||e.sn!==t.sn||e.urlId!==t.urlId},h.fragBufferedComplete=function(e,t){var r,i,n=this.mediaBuffer?this.mediaBuffer:this.media;this.log("Buffered "+e.type+" sn: "+e.sn+(t?" part: "+t.index:"")+" of "+("[stream-controller]"===this.logPrefix?"level":"track")+" "+e.level+" "+(n?Ie(ce.getBuffered(n)):"(detached)")),this.state=Fe,n&&(!this.loadedmetadata&&e.type==C.PlaylistLevelType.MAIN&&n.buffered.length&&(null===(r=this.fragCurrent)||void 0===r?void 0:r.sn)===(null===(i=this.fragPrevious)||void 0===i?void 0:i.sn)&&(this.loadedmetadata=!0,this.seekToStartPos()),this.tick())},h.seekToStartPos=function(){},h._handleFragmentLoadComplete=function(e){var t=this.transmuxer;if(t){var r=e.frag,i=e.part,n=e.partsLoaded,a=!n||0===n.length||n.some((function(e){return!e})),s=new ve(r.level,r.sn,r.stats.chunkCount+1,0,i?i.index:-1,!a);t.flush(s)}},h._handleFragmentLoadProgress=function(e){},h._doFragLoad=function(e,t,r,i){var n=this;if(void 0===r&&(r=null),!this.levels)throw new Error("frag load aborted, missing levels");if(r=Math.max(e.start,r||0),this.config.lowLatencyMode&&t){var o=t.partList;if(o&&i){r>e.end&&t.fragmentHint&&(e=t.fragmentHint);var l=this.getNextPart(o,e,r);if(l>-1){var u=o[l];return this.log("Loading part sn: "+e.sn+" p: "+u.index+" cc: "+e.cc+" of playlist ["+t.startSN+"-"+t.endSN+"] parts [0-"+l+"-"+(o.length-1)+"] "+("[stream-controller]"===this.logPrefix?"level":"track")+": "+e.level+", target: "+parseFloat(r.toFixed(3))),this.nextLoadPosition=u.start+u.duration,this.state=Ne,this.hls.trigger(s.Events.FRAG_LOADING,{frag:e,part:o[l],targetBufferTime:r}),this.doFragPartsLoad(e,o,l,i).catch((function(e){return n.handleFragLoadError(e)}))}if(!e.url||this.loadedEndOfParts(o,r))return Promise.resolve(null)}}return this.log("Loading fragment "+e.sn+" cc: "+e.cc+" "+(t?"of ["+t.startSN+"-"+t.endSN+"] ":"")+("[stream-controller]"===this.logPrefix?"level":"track")+": "+e.level+", target: "+parseFloat(r.toFixed(3))),(0,a.isFiniteNumber)(e.sn)&&!this.bitrateTest&&(this.nextLoadPosition=e.start+e.duration),this.state=Ne,this.hls.trigger(s.Events.FRAG_LOADING,{frag:e,targetBufferTime:r}),this.fragmentLoader.load(e,i).catch((function(e){return n.handleFragLoadError(e)}))},h.doFragPartsLoad=function(e,t,r,i){var n=this;return new Promise((function(a,o){var l=[];!function r(u){var d=t[u];n.fragmentLoader.loadPart(e,d,i).then((function(i){l[d.index]=i;var o=i.part;n.hls.trigger(s.Events.FRAG_LOADED,i);var h=t[u+1];if(!h||h.fragment!==e)return a({frag:e,part:o,partsLoaded:l});r(u+1)})).catch(o)}(r)}))},h.handleFragLoadError=function(e){var t=e.data;return t&&t.details===o.ErrorDetails.INTERNAL_ABORTED?this.handleFragLoadAborted(t.frag,t.part):this.hls.trigger(s.Events.ERROR,t),null},h._handleTransmuxerFlush=function(e){var t=this.getCurrentContext(e);if(t&&this.state===Be){var r=t.frag,i=t.part,n=t.level,a=self.performance.now();r.stats.parsing.end=a,i&&(i.stats.parsing.end=a),this.updateLevelTiming(r,i,n,e.partial)}else this.fragCurrent||(this.state=Fe)},h.getCurrentContext=function(e){var t=this.levels,r=e.level,i=e.sn,n=e.part;if(!t||!t[r])return this.warn("Levels object was unset while buffering fragment "+i+" of level "+r+". The current chunk will not be buffered."),null;var a=t[r],s=n>-1?function(e,t,r){if(!e||!e.details)return null;var i=e.details.partList;if(i)for(var n=i.length;n--;){var a=i[n];if(a.index===r&&a.fragment.sn===t)return a}return null}(a,i,n):null,o=s?s.fragment:function(e,t,r){if(!e||!e.details)return null;var i=e.details,n=i.fragments[t-i.startSN];return n||((n=i.fragmentHint)&&n.sn===t?n:t<i.startSN&&r&&r.sn===t?r:null)}(a,i,this.fragCurrent);return o?{frag:o,part:s,level:a}:null},h.bufferFragmentData=function(e,t,r,i){if(e&&this.state===Be){var n=e.data1,a=e.data2,o=n;if(n&&a&&(o=(0,u.appendUint8Array)(n,a)),o&&o.length){var l={type:e.type,frag:t,part:r,chunkMeta:i,parent:t.type,data:o};this.hls.trigger(s.Events.BUFFER_APPENDING,l),e.dropped&&e.independent&&!r&&this.flushBufferGap(t)}}},h.flushBufferGap=function(e){var t=this.media;if(t)if(ce.isBuffered(t,t.currentTime)){var r=t.currentTime,i=ce.bufferInfo(t,r,0),n=e.duration,a=Math.min(2*this.config.maxFragLookUpTolerance,.25*n),s=Math.max(Math.min(e.start-a,i.end-a),r+a);e.start-s>a&&this.flushMainBuffer(s,e.start)}else this.flushMainBuffer(0,e.start)},h.getFwdBufferInfo=function(e,t){var r=this.config,i=this.getLoadPosition();if(!(0,a.isFiniteNumber)(i))return null;var n=ce.bufferInfo(e,i,r.maxBufferHole);if(0===n.len&&void 0!==n.nextStart){var s=this.fragmentTracker.getBufferedFrag(i,t);if(s&&n.nextStart<s.end)return ce.bufferInfo(e,i,Math.max(n.nextStart,r.maxBufferHole))}return n},h.getMaxBufferLength=function(e){var t,r=this.config;return t=e?Math.max(8*r.maxBufferSize/e,r.maxBufferLength):r.maxBufferLength,Math.min(t,r.maxMaxBufferLength)},h.reduceMaxBufferLength=function(e){var t=this.config,r=e||t.maxBufferLength;return t.maxMaxBufferLength>=r&&(t.maxMaxBufferLength/=2,this.warn("Reduce max buffer length to "+t.maxMaxBufferLength+"s"),!0)},h.getNextFragment=function(e,t){var r=t.fragments,i=r.length;if(!i)return null;var n,a=this.config,s=r[0].start;if(t.live){var o=a.initialLiveManifestSize;if(i<o)return this.warn("Not enough fragments to start playback (have: "+i+", need: "+o+")"),null;t.PTSKnown||this.startFragRequested||-1!==this.startPosition||(n=this.getInitialLiveFragment(t,r),this.startPosition=n?this.hls.liveSyncPosition||n.start:e)}else e<=s&&(n=r[0]);if(!n){var l=a.lowLatencyMode?t.partEnd:t.fragmentEnd;n=this.getFragmentAtPosition(e,l,t)}return this.mapToInitFragWhenRequired(n)},h.mapToInitFragWhenRequired=function(e){return null==e||!e.initSegment||null!=e&&e.initSegment.data||this.bitrateTest?e:e.initSegment},h.getNextPart=function(e,t,r){for(var i=-1,n=!1,a=!0,s=0,o=e.length;s<o;s++){var l=e[s];if(a=a&&!l.independent,i>-1&&r<l.start)break;var u=l.loaded;!u&&(n||l.independent||a)&&l.fragment===t&&(i=s),n=u}return i},h.loadedEndOfParts=function(e,t){var r=e[e.length-1];return r&&t>r.start&&r.loaded},h.getInitialLiveFragment=function(e,t){var r=this.fragPrevious,i=null;if(r){if(e.hasProgramDateTime&&(this.log("Live playlist, switching playlist, load frag with same PDT: "+r.programDateTime),i=function(e,t,r){if(null===t||!Array.isArray(e)||!e.length||!(0,a.isFiniteNumber)(t))return null;if(t<(e[0].programDateTime||0))return null;if(t>=(e[e.length-1].endProgramDateTime||0))return null;r=r||0;for(var i=0;i<e.length;++i){var n=e[i];if(Ee(t,r,n))return n}return null}(t,r.endProgramDateTime,this.config.maxFragLookUpTolerance)),!i){var n=r.sn+1;if(n>=e.startSN&&n<=e.endSN){var s=t[n-e.startSN];r.cc===s.cc&&(i=s,this.log("Live playlist, switching playlist, load frag with next SN: "+i.sn))}i||(i=function(e,t){return me(e,(function(e){return e.cc<t?1:e.cc>t?-1:0}))}(t,r.cc),i&&this.log("Live playlist, switching playlist, load frag with same CC: "+i.sn))}}else{var o=this.hls.liveSyncPosition;null!==o&&(i=this.getFragmentAtPosition(o,this.bitrateTest?e.fragmentEnd:e.edge,e))}return i},h.getFragmentAtPosition=function(e,t,r){var i,n=this.config,a=this.fragPrevious,s=r.fragments,o=r.endSN,l=r.fragmentHint,u=n.maxFragLookUpTolerance,d=!!(n.lowLatencyMode&&r.partList&&l);if(d&&l&&!this.bitrateTest&&(s=s.concat(l),o=l.sn),i=e<t?function(e,t,r,i){void 0===r&&(r=0),void 0===i&&(i=0);var n=null;return e?n=t[e.sn-t[0].sn+1]||null:0===r&&0===t[0].start&&(n=t[0]),n&&0===ye(r,i,n)?n:me(t,ye.bind(null,r,i))||n}(a,s,e,e>t-u?0:u):s[s.length-1],i){var h=i.sn-r.startSN;if(this.fragmentTracker.getState(i)===ae.OK&&(a=i),a&&i.sn===a.sn&&!d&&a&&i.level===a.level){var f=s[h+1];i.sn<o&&this.fragmentTracker.getState(f)!==ae.OK?(this.log("SN "+i.sn+" just loaded, load next one: "+f.sn),i=f):i=null}}return i},h.synchronizeToLiveEdge=function(e){var t=this.config,r=this.media;if(r){var i=this.hls.liveSyncPosition,n=r.currentTime,a=e.fragments[0].start,s=e.edge,o=n>=a-t.maxFragLookUpTolerance&&n<=s;if(null!==i&&r.duration>i&&(n<i||!o)){var l=void 0!==t.liveMaxLatencyDuration?t.liveMaxLatencyDuration:t.liveMaxLatencyDurationCount*e.targetduration;(!o&&r.readyState<4||n<s-l)&&(this.loadedmetadata||(this.nextLoadPosition=i),r.readyState&&(this.warn("Playback: "+n.toFixed(3)+" is located too far from the end of live sliding playlist: "+s+", reset currentTime to : "+i.toFixed(3)),r.currentTime=i))}}},h.alignPlaylists=function(e,t){var r=this.levels,i=this.levelLastLoaded,n=this.fragPrevious,s=null!==i?r[i]:null,o=e.fragments.length;if(!o)return this.warn("No fragments in live playlist"),0;var u=e.fragments[0].start,d=!t,h=e.alignedSliding&&(0,a.isFiniteNumber)(u);if(d||!h&&!u){!function(e,t,r){t&&(function(e,t,r){if(function(e,t,r){return!(!t.details||!(r.endCC>r.startCC||e&&e.cc<r.startCC))}(e,r,t)){var i=function(e,t,r){void 0===r&&(r=0);var i=e.fragments,n=t.fragments;if(n.length&&i.length){var a=function(e,t){for(var r=null,i=0,n=e.length;i<n;i++){var a=e[i];if(a&&a.cc===t){r=a;break}}return r}(i,n[0].cc);if(a&&(!a||a.startPTS))return a;l.logger.log("No frag in previous level to align on")}else l.logger.log("No fragments to align")}(r.details,t);i&&(0,a.isFiniteNumber)(i.start)&&(l.logger.log("Adjusting PTS using last level due to CC increase within current level "+t.url),pe(i.start,t))}}(e,r,t),!r.alignedSliding&&t.details&&function(e,t){if(t.fragments.length&&e.hasProgramDateTime&&t.hasProgramDateTime){var r=t.fragments[0].programDateTime,i=e.fragments[0].programDateTime,n=(i-r)/1e3+t.fragments[0].start;n&&(0,a.isFiniteNumber)(n)&&(l.logger.log("Adjusting PTS using programDateTime delta "+(i-r)+"ms, sliding:"+n.toFixed(3)+" "+e.url+" "),pe(n,e))}}(r,t.details),r.alignedSliding||!t.details||r.skippedSegments||ee(t.details,r))}(n,s,e);var f=e.fragments[0].start;return this.log("Live playlist sliding: "+f.toFixed(2)+" start-sn: "+(t?t.startSN:"na")+"->"+e.startSN+" prev-sn: "+(n?n.sn:"na")+" fragments: "+o),f}return u},h.waitForCdnTuneIn=function(e){return e.live&&e.canBlockReload&&e.partTarget&&e.tuneInGoal>Math.max(e.partHoldBack,3*e.partTarget)},h.setStartPosition=function(e,t){var r=this.startPosition;if(r<t&&(r=-1),-1===r||-1===this.lastCurrentTime){var i=e.startTimeOffset;(0,a.isFiniteNumber)(i)?(r=t+i,i<0&&(r+=e.totalduration),r=Math.min(Math.max(t,r),t+e.totalduration),this.log("Start time offset "+i+" found in playlist, adjust startPosition to "+r),this.startPosition=r):e.live?r=this.hls.liveSyncPosition||t:this.startPosition=r=0,this.lastCurrentTime=r}this.nextLoadPosition=r},h.getLoadPosition=function(){var e=this.media,t=0;return this.loadedmetadata&&e?t=e.currentTime:this.nextLoadPosition&&(t=this.nextLoadPosition),t},h.handleFragLoadAborted=function(e,t){this.transmuxer&&"initSegment"!==e.sn&&e.stats.aborted&&(this.warn("Fragment "+e.sn+(t?" part"+t.index:"")+" of level "+e.level+" was aborted"),this.resetFragmentLoading(e))},h.resetFragmentLoading=function(e){this.fragCurrent&&(this.fragContextChanged(e)||this.state===Ue)||(this.state=Fe)},h.onFragmentOrKeyLoadError=function(e,t){if(!t.fatal){var r=t.frag;if(r&&r.type===e){this.fragCurrent;var i=this.config;if(this.fragLoadError+1<=i.fragLoadingMaxRetry){this.loadedmetadata||(this.startFragRequested=!1,this.nextLoadPosition=this.startPosition);var n=Math.min(Math.pow(2,this.fragLoadError)*i.fragLoadingRetryDelay,i.fragLoadingMaxRetryTimeout);this.warn("Fragment "+r.sn+" of "+e+" "+r.level+" failed to load, retrying in "+n+"ms"),this.retryDate=self.performance.now()+n,this.fragLoadError++,this.state=Ue}else t.levelRetry?(e===C.PlaylistLevelType.AUDIO&&(this.fragCurrent=null),this.fragLoadError=0,this.state=Fe):(l.logger.error(t.details+" reaches max retry, redispatch as fatal ..."),t.fatal=!0,this.hls.stopLoad(),this.state=He)}}},h.afterBufferFlushed=function(e,t,r){if(e){var i=ce.getBuffered(e);this.fragmentTracker.detectEvictedFragments(t,i,r),this.state===Ve&&this.resetLoadingState()}},h.resetLoadingState=function(){this.fragCurrent=null,this.fragPrevious=null,this.state=Fe},h.resetStartWhenNotLoaded=function(e){if(!this.loadedmetadata){this.startFragRequested=!1;var t=this.levels?this.levels[e].details:null;null!=t&&t.live?(this.startPosition=-1,this.setStartPosition(t,0),this.resetLoadingState()):this.nextLoadPosition=this.startPosition}},h.updateLevelTiming=function(e,t,r,i){var n=this,a=r.details;Object.keys(e.elementaryStreams).reduce((function(t,o){var l=e.elementaryStreams[o];if(l){var u=l.endPTS-l.startPTS;if(u<=0)return n.warn("Could not parse fragment "+e.sn+" "+o+" duration reliably ("+u+")"),t||!1;var d=i?0:J(a,e,l.startPTS,l.endPTS,l.startDTS,l.endDTS);return n.hls.trigger(s.Events.LEVEL_PTS_UPDATED,{details:a,level:r,drift:d,type:o,frag:e,start:l.startPTS,end:l.endPTS}),!0}return t}),!1)||(this.warn("Found no media in fragment "+e.sn+" of level "+r.id+" resetting transmuxer to fallback to playlist timing"),this.resetTransmuxer()),this.state=Ge,this.hls.trigger(s.Events.FRAG_PARSED,{frag:e,part:t})},h.resetTransmuxer=function(){this.transmuxer&&(this.transmuxer.destroy(),this.transmuxer=null)},n=i,(d=[{key:"state",get:function(){return this._state},set:function(e){var t=this._state;t!==e&&(this._state=e,this.log(t+"->"+e))}}])&&we(n.prototype,d),Object.defineProperty(n,"prototype",{writable:!1}),i}(he);function We(){return self.MediaSource||self.WebKitMediaSource}function Ye(){return self.SourceBuffer||self.WebKitSourceBuffer}var qe=function(){var e=ENTRY_MODULE,t={},r=function r(i){var n=t[i];if(void 0!==n)return n.exports;var a=t[i]={exports:{}};return e[i].call(a.exports,a,a.exports,r),a.exports};r.m=e,r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i=r(ENTRY_MODULE);return i.default||i}.toString().split("ENTRY_MODULE"),ze="\\(\\s*(/\\*.*?\\*/)?\\s*.*?([\\.|\\-|\\+|\\w|/|@]+).*?\\)";function Xe(e){return(e+"").replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}function Qe(e,t,i){var n={};n[i]=[];var a=t.toString().replace(/^"[^"]+"/,"function"),s=a.match(/^function\s?\w*\(\w+,\s*\w+,\s*(\w+)\)/)||a.match(/^\(\w+,\s*\w+,\s*(\w+)\)\s?\=\s?\>/);if(!s)return n;for(var o,l=s[1],u=new RegExp("(\\\\n|\\W)"+Xe(l)+ze,"g");o=u.exec(a);)"dll-reference"!==o[3]&&n[i].push(o[3]);for(u=new RegExp("\\("+Xe(l)+'\\("(dll-reference\\s([\\.|\\-|\\+|\\w|/|@]+))"\\)\\)'+ze,"g");o=u.exec(a);)e[o[2]]||(n[i].push(o[1]),e[o[2]]=r(o[1]).m),n[o[2]]=n[o[2]]||[],n[o[2]].push(o[4]);for(var d,h=Object.keys(n),f=0;f<h.length;f++)for(var c=0;c<n[h[f]].length;c++)d=n[h[f]][c],isNaN(1*d)||(n[h[f]][c]=1*n[h[f]][c]);return n}function $e(e){return Object.keys(e).reduce((function(t,r){return t||e[r].length>0}),!1)}function Ze(e,t,r,i){var n=e[i].map((function(e){return'"'+e+'": '+t[i][e].toString().replace(/^"[^"]+"/,"function")})).join(",");return qe[0]+"{"+n+"}"+qe[1]+'"'+r+'"'+qe[2]}var Je=r(764),et=r(729),tt=We()||{isTypeSupported:function(){return!1}},rt=function(){function e(e,t,i,n){var a=this;this.hls=void 0,this.id=void 0,this.observer=void 0,this.frag=null,this.part=null,this.useWorker=void 0,this.worker=void 0,this.onwmsg=void 0,this.transmuxer=null,this.onTransmuxComplete=void 0,this.onFlush=void 0;var u=e.config;this.hls=e,this.id=t,this.useWorker=!!u.enableWorker,this.onTransmuxComplete=i,this.onFlush=n;var d=function(e,t){(t=t||{}).frag=a.frag,t.id=a.id,a.hls.trigger(e,t)};this.observer=new et.EventEmitter,this.observer.on(s.Events.FRAG_DECRYPTED,d),this.observer.on(s.Events.ERROR,d);var h={mp4:tt.isTypeSupported("video/mp4"),mpeg:tt.isTypeSupported("audio/mpeg"),mp3:tt.isTypeSupported('audio/mp4; codecs="mp3"')},f=navigator.vendor;if(this.useWorker&&"undefined"!=typeof Worker){var c;l.logger.log("demuxing in webworker");try{c=this.worker=function(e,t){t=t||{};var i={main:r.m},n=t.all?{main:Object.keys(i.main)}:function(e,t){for(var r={main:[t]},i={main:[]},n={main:{}};$e(r);)for(var a=Object.keys(r),s=0;s<a.length;s++){var o=a[s],l=r[o].pop();if(n[o]=n[o]||{},!n[o][l]&&e[o][l]){n[o][l]=!0,i[o]=i[o]||[],i[o].push(l);for(var u=Qe(e,e[o][l],o),d=Object.keys(u),h=0;h<d.length;h++)r[d[h]]=r[d[h]]||[],r[d[h]]=r[d[h]].concat(u[d[h]])}}return i}(i,e),a="";Object.keys(n).filter((function(e){return"main"!==e})).forEach((function(e){for(var t=0;n[e][t];)t++;n[e].push(t),i[e][t]="(function(module, exports, __webpack_require__) { module.exports = __webpack_require__; })",a=a+"var "+e+" = ("+Ze(n,i,t,modules)+")();\n"})),a=a+"new (("+Ze(n,i,e,"main")+")())(self);";var s=new window.Blob([a],{type:"text/javascript"}),o=(window.URL||window.webkitURL||window.mozURL||window.msURL).createObjectURL(s),l=new window.Worker(o);return l.objectURL=o,l}(182),this.onwmsg=this.onWorkerMessage.bind(this),c.addEventListener("message",this.onwmsg),c.onerror=function(e){a.useWorker=!1,l.logger.warn("Exception in webworker, fallback to inline"),a.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.OTHER_ERROR,details:o.ErrorDetails.INTERNAL_EXCEPTION,fatal:!1,event:"demuxerWorker",error:new Error(e.message+" ("+e.filename+":"+e.lineno+")")})},c.postMessage({cmd:"init",typeSupported:h,vendor:f,id:t,config:JSON.stringify(u)})}catch(e){l.logger.warn("Error in worker:",e),l.logger.error("Error while initializing DemuxerWorker, fallback to inline"),c&&self.URL.revokeObjectURL(c.objectURL),this.transmuxer=new Je.default(this.observer,h,u,f,t),this.worker=null}}else this.transmuxer=new Je.default(this.observer,h,u,f,t)}var t=e.prototype;return t.destroy=function(){var e=this.worker;if(e)e.removeEventListener("message",this.onwmsg),e.terminate(),this.worker=null,this.onwmsg=void 0;else{var t=this.transmuxer;t&&(t.destroy(),this.transmuxer=null)}var r=this.observer;r&&r.removeAllListeners(),this.frag=null,this.observer=null,this.hls=null},t.push=function(e,t,r,i,n,a,s,o,u,d){var h,f,c=this;u.transmuxing.start=self.performance.now();var v=this.transmuxer,g=this.worker,p=a?a.start:n.start,m=n.decryptdata,y=this.frag,E=!(y&&n.cc===y.cc),T=!(y&&u.level===y.level),S=y?u.sn-y.sn:-1,b=this.part?u.part-this.part.index:-1,L=0===S&&u.id>1&&u.id===(null==y?void 0:y.stats.chunkCount),A=!T&&(1===S||0===S&&(1===b||L&&b<=0)),D=self.performance.now();(T||S||0===n.stats.parsing.start)&&(n.stats.parsing.start=D),!a||!b&&A||(a.stats.parsing.start=D);var R=!(y&&(null===(h=n.initSegment)||void 0===h?void 0:h.url)===(null===(f=y.initSegment)||void 0===f?void 0:f.url)),k=new Je.TransmuxState(E,A,o,T,p,R);if(!A||E||R){l.logger.log("[transmuxer-interface, "+n.type+"]: Starting new transmux session for sn: "+u.sn+" p: "+u.part+" level: "+u.level+" id: "+u.id+"\n discontinuity: "+E+"\n trackSwitch: "+T+"\n contiguous: "+A+"\n accurateTimeOffset: "+o+"\n timeOffset: "+p+"\n initSegmentChange: "+R);var _=new Je.TransmuxConfig(r,i,t,s,d);this.configureTransmuxer(_)}if(this.frag=n,this.part=a,g)g.postMessage({cmd:"demux",data:e,decryptdata:m,chunkMeta:u,state:k},e instanceof ArrayBuffer?[e]:[]);else if(v){var x=v.push(e,m,u,k);(0,Je.isPromise)(x)?x.then((function(e){c.handleTransmuxComplete(e)})):this.handleTransmuxComplete(x)}},t.flush=function(e){var t=this;e.transmuxing.start=self.performance.now();var r=this.transmuxer,i=this.worker;if(i)i.postMessage({cmd:"flush",chunkMeta:e});else if(r){var n=r.flush(e);(0,Je.isPromise)(n)?n.then((function(r){t.handleFlushResult(r,e)})):this.handleFlushResult(n,e)}},t.handleFlushResult=function(e,t){var r=this;e.forEach((function(e){r.handleTransmuxComplete(e)})),this.onFlush(t)},t.onWorkerMessage=function(e){var t=e.data,r=this.hls;switch(t.event){case"init":self.URL.revokeObjectURL(this.worker.objectURL);break;case"transmuxComplete":this.handleTransmuxComplete(t.data);break;case"flush":this.onFlush(t.data);break;case"workerLog":l.logger[t.data.logType]&&l.logger[t.data.logType](t.data.message);break;default:t.data=t.data||{},t.data.frag=this.frag,t.data.id=this.id,r.trigger(t.event,t.data)}},t.configureTransmuxer=function(e){var t=this.worker,r=this.transmuxer;t?t.postMessage({cmd:"configure",config:e}):r&&r.configure(e)},t.handleTransmuxComplete=function(e){e.chunkMeta.transmuxing.end=self.performance.now(),this.onTransmuxComplete(e)},e}(),it=function(){function e(e,t,r,i){this.config=void 0,this.media=null,this.fragmentTracker=void 0,this.hls=void 0,this.nudgeRetry=0,this.stallReported=!1,this.stalled=null,this.moved=!1,this.seeking=!1,this.config=e,this.media=t,this.fragmentTracker=r,this.hls=i}var t=e.prototype;return t.destroy=function(){this.media=null,this.hls=this.fragmentTracker=null},t.poll=function(e,t){var r=this.config,i=this.media,n=this.stalled;if(null!==i){var a=i.currentTime,s=i.seeking,o=this.seeking&&!s,u=!this.seeking&&s;if(this.seeking=s,a===e){if((u||o)&&(this.stalled=null),!(i.paused&&!s||i.ended||0===i.playbackRate)&&ce.getBuffered(i).length){var d=ce.bufferInfo(i,a,0),h=d.len>0,f=d.nextStart||0;if(h||f){if(s){var c=d.len>2,v=!f||t&&t.start<=a||f-a>2&&!this.fragmentTracker.getPartialFragment(a);if(c||v)return;this.moved=!1}if(!this.moved&&null!==this.stalled){var g,p=Math.max(f,d.start||0)-a,m=this.hls.levels?this.hls.levels[this.hls.currentLevel]:null,y=(null==m||null===(g=m.details)||void 0===g?void 0:g.live)?2*m.details.targetduration:2;if(p>0&&p<=y)return void this._trySkipBufferHole(null)}var E=self.performance.now();if(null!==n){var T=E-n;if(s||!(T>=250)||(this._reportStall(d),this.media)){var S=ce.bufferInfo(i,a,r.maxBufferHole);this._tryFixBufferStall(S,T)}}else this.stalled=E}}}else if(this.moved=!0,null!==n){if(this.stallReported){var b=self.performance.now()-n;l.logger.warn("playback not stuck anymore @"+a+", after "+Math.round(b)+"ms"),this.stallReported=!1}this.stalled=null,this.nudgeRetry=0}}},t._tryFixBufferStall=function(e,t){var r=this.config,i=this.fragmentTracker,n=this.media;if(null!==n){var a=n.currentTime,s=i.getPartialFragment(a);if(s&&(this._trySkipBufferHole(s)||!this.media))return;e.len>r.maxBufferHole&&t>1e3*r.highBufferWatchdogPeriod&&(l.logger.warn("Trying to nudge playhead over buffer-hole"),this.stalled=null,this._tryNudgeBuffer())}},t._reportStall=function(e){var t=this.hls,r=this.media;!this.stallReported&&r&&(this.stallReported=!0,l.logger.warn("Playback stalling at @"+r.currentTime+" due to low buffer ("+JSON.stringify(e)+")"),t.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_STALLED_ERROR,fatal:!1,buffer:e.len}))},t._trySkipBufferHole=function(e){var t=this.config,r=this.hls,i=this.media;if(null===i)return 0;for(var n=i.currentTime,a=0,u=ce.getBuffered(i),d=0;d<u.length;d++){var h=u.start(d);if(n+t.maxBufferHole>=a&&n<h){var f=Math.max(h+.05,i.currentTime+.1);return l.logger.warn("skipping hole, adjusting currentTime from "+n+" to "+f),this.moved=!0,this.stalled=null,i.currentTime=f,e&&r.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_SEEK_OVER_HOLE,fatal:!1,reason:"fragment loaded with buffer holes, seeking from "+n+" to "+f,frag:e}),f}a=u.end(d)}return 0},t._tryNudgeBuffer=function(){var e=this.config,t=this.hls,r=this.media,i=this.nudgeRetry;if(null!==r){var n=r.currentTime;if(this.nudgeRetry++,i<e.nudgeMaxRetry){var a=n+(i+1)*e.nudgeOffset;l.logger.warn("Nudging 'currentTime' from "+n+" to "+a),r.currentTime=a,t.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_NUDGE_ON_STALL,fatal:!1})}else l.logger.error("Playhead still not moving while enough data buffered @"+n+" after "+e.nudgeMaxRetry+" nudges"),t.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_STALLED_ERROR,fatal:!0})}},e}();function nt(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function at(e,t){return at=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},at(e,t)}var st=function(e){var t,r;function i(t,r){var i;return(i=e.call(this,t,r,"[stream-controller]")||this).audioCodecSwap=!1,i.gapController=null,i.level=-1,i._forceStartLoad=!1,i.altAudio=!1,i.audioOnly=!1,i.fragPlaying=null,i.onvplaying=null,i.onvseeked=null,i.fragLastKbps=0,i.couldBacktrack=!1,i.backtrackFragment=null,i.audioCodecSwitch=!1,i.videoBuffer=null,i._registerListeners(),i}r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,at(t,r);var n,l,u=i.prototype;return u._registerListeners=function(){var e=this.hls;e.on(s.Events.MEDIA_ATTACHED,this.onMediaAttached,this),e.on(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),e.on(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),e.on(s.Events.MANIFEST_PARSED,this.onManifestParsed,this),e.on(s.Events.LEVEL_LOADING,this.onLevelLoading,this),e.on(s.Events.LEVEL_LOADED,this.onLevelLoaded,this),e.on(s.Events.FRAG_LOAD_EMERGENCY_ABORTED,this.onFragLoadEmergencyAborted,this),e.on(s.Events.ERROR,this.onError,this),e.on(s.Events.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),e.on(s.Events.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),e.on(s.Events.BUFFER_CREATED,this.onBufferCreated,this),e.on(s.Events.BUFFER_FLUSHED,this.onBufferFlushed,this),e.on(s.Events.LEVELS_UPDATED,this.onLevelsUpdated,this),e.on(s.Events.FRAG_BUFFERED,this.onFragBuffered,this)},u._unregisterListeners=function(){var e=this.hls;e.off(s.Events.MEDIA_ATTACHED,this.onMediaAttached,this),e.off(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),e.off(s.Events.MANIFEST_LOADING,this.onManifestLoading,this),e.off(s.Events.MANIFEST_PARSED,this.onManifestParsed,this),e.off(s.Events.LEVEL_LOADED,this.onLevelLoaded,this),e.off(s.Events.FRAG_LOAD_EMERGENCY_ABORTED,this.onFragLoadEmergencyAborted,this),e.off(s.Events.ERROR,this.onError,this),e.off(s.Events.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),e.off(s.Events.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),e.off(s.Events.BUFFER_CREATED,this.onBufferCreated,this),e.off(s.Events.BUFFER_FLUSHED,this.onBufferFlushed,this),e.off(s.Events.LEVELS_UPDATED,this.onLevelsUpdated,this),e.off(s.Events.FRAG_BUFFERED,this.onFragBuffered,this)},u.onHandlerDestroying=function(){this._unregisterListeners(),this.onMediaDetaching()},u.startLoad=function(e){if(this.levels){var t=this.lastCurrentTime,r=this.hls;if(this.stopLoad(),this.setInterval(100),this.level=-1,this.fragLoadError=0,!this.startFragRequested){var i=r.startLevel;-1===i&&(r.config.testBandwidth&&this.levels.length>1?(i=0,this.bitrateTest=!0):i=r.nextAutoLevel),this.level=r.nextLoadLevel=i,this.loadedmetadata=!1}t>0&&-1===e&&(this.log("Override startPosition with lastCurrentTime @"+t.toFixed(3)),e=t),this.state=Fe,this.nextLoadPosition=this.startPosition=this.lastCurrentTime=e,this.tick()}else this._forceStartLoad=!0,this.state=Oe},u.stopLoad=function(){this._forceStartLoad=!1,e.prototype.stopLoad.call(this)},u.doTick=function(){switch(this.state){case Fe:this.doTickIdle();break;case je:var e,t=this.levels,r=this.level,i=null==t||null===(e=t[r])||void 0===e?void 0:e.details;if(i&&(!i.live||this.levelLastLoaded===this.level)){if(this.waitForCdnTuneIn(i))break;this.state=Fe;break}break;case Ue:var n,a=self.performance.now(),s=this.retryDate;(!s||a>=s||null!==(n=this.media)&&void 0!==n&&n.seeking)&&(this.log("retryDate reached, switch back to IDLE state"),this.resetStartWhenNotLoaded(this.level),this.state=Fe)}this.onTickEnd()},u.onTickEnd=function(){e.prototype.onTickEnd.call(this),this.checkBuffer(),this.checkFragmentChanged()},u.doTickIdle=function(){var e,t,r=this.hls,i=this.levelLastLoaded,n=this.levels,a=this.media,o=r.config,l=r.nextLoadLevel;if(null!==i&&(a||!this.startFragRequested&&o.startFragPrefetch)&&(!this.altAudio||!this.audioOnly)&&n&&n[l]){var u=n[l];this.level=r.nextLoadLevel=l;var d=u.details;if(!d||this.state===je||d.live&&this.levelLastLoaded!==l)this.state=je;else{var h=this.getMainFwdBufferInfo();if(null!==h&&!(h.len>=this.getMaxBufferLength(u.maxBitrate))){if(this._streamEnded(h,d)){var f={};return this.altAudio&&(f.type="video"),this.hls.trigger(s.Events.BUFFER_EOS,f),void(this.state=Ve)}this.backtrackFragment&&this.backtrackFragment.start>h.end&&(this.backtrackFragment=null);var c=this.backtrackFragment?this.backtrackFragment.start:h.end,v=this.getNextFragment(c,d);if(this.couldBacktrack&&!this.fragPrevious&&v&&"initSegment"!==v.sn&&this.fragmentTracker.getState(v)!==ae.OK){var g,m=(null!=(g=this.backtrackFragment)?g:v).sn-d.startSN,y=d.fragments[m-1];y&&v.cc===y.cc&&(v=y,this.fragmentTracker.removeFragment(y))}else this.backtrackFragment&&h.len&&(this.backtrackFragment=null);if(v&&this.fragmentTracker.getState(v)===ae.OK&&this.nextLoadPosition>c){var E=this.audioOnly&&!this.altAudio?p.ElementaryStreamTypes.AUDIO:p.ElementaryStreamTypes.VIDEO;a&&this.afterBufferFlushed(a,E,C.PlaylistLevelType.MAIN),v=this.getNextFragment(this.nextLoadPosition,d)}v&&(!v.initSegment||v.initSegment.data||this.bitrateTest||(v=v.initSegment),"identity"!==(null===(e=v.decryptdata)||void 0===e?void 0:e.keyFormat)||null!==(t=v.decryptdata)&&void 0!==t&&t.key?this.loadFragment(v,d,c):this.loadKey(v,d))}}}},u.loadFragment=function(t,r,i){var n,a=this.fragmentTracker.getState(t);this.fragCurrent=t,a===ae.NOT_LOADED?"initSegment"===t.sn?this._loadInitSegment(t):this.bitrateTest?(this.log("Fragment "+t.sn+" of level "+t.level+" is being downloaded to test bitrate and will not be buffered"),this._loadBitrateTestFrag(t)):(this.startFragRequested=!0,e.prototype.loadFragment.call(this,t,r,i)):a===ae.APPENDING?this.reduceMaxBufferLength(t.duration)&&this.fragmentTracker.removeFragment(t):0===(null===(n=this.media)||void 0===n?void 0:n.buffered.length)&&this.fragmentTracker.removeAllFragments()},u.getAppendedFrag=function(e){var t=this.fragmentTracker.getAppendedFrag(e,C.PlaylistLevelType.MAIN);return t&&"fragment"in t?t.fragment:t},u.getBufferedFrag=function(e){return this.fragmentTracker.getBufferedFrag(e,C.PlaylistLevelType.MAIN)},u.followingBufferedFrag=function(e){return e?this.getBufferedFrag(e.end+.5):null},u.immediateLevelSwitch=function(){this.abortCurrentFrag(),this.flushMainBuffer(0,Number.POSITIVE_INFINITY)},u.nextLevelSwitch=function(){var e=this.levels,t=this.media;if(null!=t&&t.readyState){var r,i=this.getAppendedFrag(t.currentTime);if(i&&i.start>1&&this.flushMainBuffer(0,i.start-1),!t.paused&&e){var n=e[this.hls.nextLoadLevel],a=this.fragLastKbps;r=a&&this.fragCurrent?this.fragCurrent.duration*n.maxBitrate/(1e3*a)+1:0}else r=0;var s=this.getBufferedFrag(t.currentTime+r);if(s){var o=this.followingBufferedFrag(s);if(o){this.abortCurrentFrag();var l=o.maxStartPTS?o.maxStartPTS:o.start,u=o.duration,d=Math.max(s.end,l+Math.min(Math.max(u-this.config.maxFragLookUpTolerance,.5*u),.75*u));this.flushMainBuffer(d,Number.POSITIVE_INFINITY)}}}},u.abortCurrentFrag=function(){var e=this.fragCurrent;switch(this.fragCurrent=null,this.backtrackFragment=null,null!=e&&e.loader&&e.loader.abort(),this.state){case Me:case Ne:case Ue:case Be:case Ge:this.state=Fe}this.nextLoadPosition=this.getLoadPosition()},u.flushMainBuffer=function(t,r){e.prototype.flushMainBuffer.call(this,t,r,this.altAudio?"video":null)},u.onMediaAttached=function(t,r){e.prototype.onMediaAttached.call(this,t,r);var i=r.media;this.onvplaying=this.onMediaPlaying.bind(this),this.onvseeked=this.onMediaSeeked.bind(this),i.addEventListener("playing",this.onvplaying),i.addEventListener("seeked",this.onvseeked),this.gapController=new it(this.config,i,this.fragmentTracker,this.hls)},u.onMediaDetaching=function(){var t=this.media;t&&this.onvplaying&&this.onvseeked&&(t.removeEventListener("playing",this.onvplaying),t.removeEventListener("seeked",this.onvseeked),this.onvplaying=this.onvseeked=null,this.videoBuffer=null),this.fragPlaying=null,this.gapController&&(this.gapController.destroy(),this.gapController=null),e.prototype.onMediaDetaching.call(this)},u.onMediaPlaying=function(){this.tick()},u.onMediaSeeked=function(){var e=this.media,t=e?e.currentTime:null;(0,a.isFiniteNumber)(t)&&this.log("Media seeked to "+t.toFixed(3)),this.tick()},u.onManifestLoading=function(){this.log("Trigger BUFFER_RESET"),this.hls.trigger(s.Events.BUFFER_RESET,void 0),this.fragmentTracker.removeAllFragments(),this.couldBacktrack=!1,this.startPosition=this.lastCurrentTime=0,this.fragPlaying=null,this.backtrackFragment=null},u.onManifestParsed=function(e,t){var r,i,n,a=!1,s=!1;t.levels.forEach((function(e){(r=e.audioCodec)&&(-1!==r.indexOf("mp4a.40.2")&&(a=!0),-1!==r.indexOf("mp4a.40.5")&&(s=!0))})),this.audioCodecSwitch=a&&s&&!("function"==typeof(null==(n=Ye())||null===(i=n.prototype)||void 0===i?void 0:i.changeType)),this.audioCodecSwitch&&this.log("Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC"),this.levels=t.levels,this.startFragRequested=!1},u.onLevelLoading=function(e,t){var r=this.levels;if(r&&this.state===Fe){var i=r[t.level];(!i.details||i.details.live&&this.levelLastLoaded!==t.level||this.waitForCdnTuneIn(i.details))&&(this.state=je)}},u.onLevelLoaded=function(e,t){var r,i=this.levels,n=t.level,a=t.details,o=a.totalduration;if(i){this.log("Level "+n+" loaded ["+a.startSN+","+a.endSN+"], cc ["+a.startCC+", "+a.endCC+"] duration:"+o);var l=this.fragCurrent;!l||this.state!==Ne&&this.state!==Ue||l.level!==t.level&&l.loader&&(this.state=Fe,this.backtrackFragment=null,l.loader.abort());var u=i[n],d=0;if(a.live||null!==(r=u.details)&&void 0!==r&&r.live){if(a.fragments[0]||(a.deltaUpdateFailed=!0),a.deltaUpdateFailed)return;d=this.alignPlaylists(a,u.details)}if(u.details=a,this.levelLastLoaded=n,this.hls.trigger(s.Events.LEVEL_UPDATED,{details:a,level:n}),this.state===je){if(this.waitForCdnTuneIn(a))return;this.state=Fe}this.startFragRequested?a.live&&this.synchronizeToLiveEdge(a):this.setStartPosition(a,d),this.tick()}else this.warn("Levels were reset while loading level "+n)},u._handleFragmentLoadProgress=function(e){var t,r=e.frag,i=e.part,n=e.payload,a=this.levels;if(a){var s=a[r.level],o=s.details;if(o){var l=s.videoCodec,u=o.PTSKnown||!o.live,d=null===(t=r.initSegment)||void 0===t?void 0:t.data,h=this._getAudioCodec(s),f=this.transmuxer=this.transmuxer||new rt(this.hls,C.PlaylistLevelType.MAIN,this._handleTransmuxComplete.bind(this),this._handleTransmuxerFlush.bind(this)),c=i?i.index:-1,v=-1!==c,g=new ve(r.level,r.sn,r.stats.chunkCount,n.byteLength,c,v),p=this.initPTS[r.cc];f.push(n,d,h,l,r,i,o.totalduration,u,g,p)}else this.warn("Dropping fragment "+r.sn+" of level "+r.level+" after level details were reset")}else this.warn("Levels were reset while fragment load was in progress. Fragment "+r.sn+" of level "+r.level+" will not be buffered")},u.onAudioTrackSwitching=function(e,t){var r=this.altAudio,i=!!t.url,n=t.id;if(!i){if(this.mediaBuffer!==this.media){this.log("Switching on main audio, use media.buffered to schedule main fragment loading"),this.mediaBuffer=this.media;var a=this.fragCurrent;null!=a&&a.loader&&(this.log("Switching to main audio track, cancel main fragment load"),a.loader.abort()),this.resetTransmuxer(),this.resetLoadingState()}else this.audioOnly&&this.resetTransmuxer();var o=this.hls;r&&o.trigger(s.Events.BUFFER_FLUSHING,{startOffset:0,endOffset:Number.POSITIVE_INFINITY,type:"audio"}),o.trigger(s.Events.AUDIO_TRACK_SWITCHED,{id:n})}},u.onAudioTrackSwitched=function(e,t){var r=t.id,i=!!this.hls.audioTracks[r].url;if(i){var n=this.videoBuffer;n&&this.mediaBuffer!==n&&(this.log("Switching on alternate audio, use video.buffered to schedule main fragment loading"),this.mediaBuffer=n)}this.altAudio=i,this.tick()},u.onBufferCreated=function(e,t){var r,i,n=t.tracks,a=!1;for(var s in n){var o=n[s];if("main"===o.id){if(i=s,r=o,"video"===s){var l=n[s];l&&(this.videoBuffer=l.buffer)}}else a=!0}a&&r?(this.log("Alternate track found, use "+i+".buffered to schedule main fragment loading"),this.mediaBuffer=r.buffer):this.mediaBuffer=this.media},u.onFragBuffered=function(e,t){var r=t.frag,i=t.part;if(!r||r.type===C.PlaylistLevelType.MAIN){if(this.fragContextChanged(r))return this.warn("Fragment "+r.sn+(i?" p: "+i.index:"")+" of level "+r.level+" finished buffering, but was aborted. state: "+this.state),void(this.state===Ge&&(this.state=Fe));var n=i?i.stats:r.stats;this.fragLastKbps=Math.round(8*n.total/(n.buffering.end-n.loading.first)),"initSegment"!==r.sn&&(this.fragPrevious=r),this.fragBufferedComplete(r,i)}},u.onError=function(e,t){switch(t.details){case o.ErrorDetails.FRAG_LOAD_ERROR:case o.ErrorDetails.FRAG_LOAD_TIMEOUT:case o.ErrorDetails.KEY_LOAD_ERROR:case o.ErrorDetails.KEY_LOAD_TIMEOUT:this.onFragmentOrKeyLoadError(C.PlaylistLevelType.MAIN,t);break;case o.ErrorDetails.LEVEL_LOAD_ERROR:case o.ErrorDetails.LEVEL_LOAD_TIMEOUT:this.state!==He&&(t.fatal?(this.warn(""+t.details),this.state=He):t.levelRetry||this.state!==je||(this.state=Fe));break;case o.ErrorDetails.BUFFER_FULL_ERROR:if("main"===t.parent&&(this.state===Be||this.state===Ge)){var r=!0,i=this.getFwdBufferInfo(this.media,C.PlaylistLevelType.MAIN);i&&i.len>.5&&(r=!this.reduceMaxBufferLength(i.len)),r&&(this.warn("buffer full error also media.currentTime is not buffered, flush main"),this.immediateLevelSwitch()),this.resetLoadingState()}}},u.checkBuffer=function(){var e=this.media,t=this.gapController;if(e&&t&&e.readyState){if(this.loadedmetadata||!ce.getBuffered(e).length){var r=this.state!==Fe?this.fragCurrent:null;t.poll(this.lastCurrentTime,r)}this.lastCurrentTime=e.currentTime}},u.onFragLoadEmergencyAborted=function(){this.state=Fe,this.loadedmetadata||(this.startFragRequested=!1,this.nextLoadPosition=this.startPosition),this.tickImmediate()},u.onBufferFlushed=function(e,t){var r=t.type;if(r!==p.ElementaryStreamTypes.AUDIO||this.audioOnly&&!this.altAudio){var i=(r===p.ElementaryStreamTypes.VIDEO?this.videoBuffer:this.mediaBuffer)||this.media;this.afterBufferFlushed(i,r,C.PlaylistLevelType.MAIN)}},u.onLevelsUpdated=function(e,t){this.levels=t.levels},u.swapAudioCodec=function(){this.audioCodecSwap=!this.audioCodecSwap},u.seekToStartPos=function(){var e=this.media;if(e){var t=e.currentTime,r=this.startPosition;if(r>=0&&t<r){if(e.seeking)return void this.log("could not seek to "+r+", already seeking at "+t);var i=ce.getBuffered(e),n=(i.length?i.start(0):0)-r;n>0&&(n<this.config.maxBufferHole||n<this.config.maxFragLookUpTolerance)&&(this.log("adjusting start position by "+n+" to match buffer start"),r+=n,this.startPosition=r),this.log("seek to target start position "+r+" from current time "+t),e.currentTime=r}}},u._getAudioCodec=function(e){var t=this.config.defaultAudioCodec||e.audioCodec;return this.audioCodecSwap&&t&&(this.log("Swapping audio codec"),t=-1!==t.indexOf("mp4a.40.5")?"mp4a.40.2":"mp4a.40.5"),t},u._loadBitrateTestFrag=function(e){var t=this;e.bitrateTest=!0,this._doFragLoad(e).then((function(r){var i=t.hls;if(r&&!i.nextLoadLevel&&!t.fragContextChanged(e)){t.fragLoadError=0,t.state=Fe,t.startFragRequested=!1,t.bitrateTest=!1;var n=e.stats;n.parsing.start=n.parsing.end=n.buffering.start=n.buffering.end=self.performance.now(),i.trigger(s.Events.FRAG_LOADED,r),e.bitrateTest=!1}}))},u._handleTransmuxComplete=function(e){var t,r="main",i=this.hls,n=e.remuxResult,o=e.chunkMeta,l=this.getCurrentContext(o);if(!l)return this.warn("The loading context changed while buffering fragment "+o.sn+" of level "+o.level+". This chunk will not be buffered."),void this.resetStartWhenNotLoaded(o.level);var u=l.frag,d=l.part,h=l.level,f=n.video,c=n.text,v=n.id3,g=n.initSegment,m=h.details,y=this.altAudio?void 0:n.audio;if(!this.fragContextChanged(u)){if(this.state=Be,g){g.tracks&&(this._bufferInitSegment(h,g.tracks,u,o),i.trigger(s.Events.FRAG_PARSING_INIT_SEGMENT,{frag:u,id:r,tracks:g.tracks}));var E=g.initPTS,T=g.timescale;(0,a.isFiniteNumber)(E)&&(this.initPTS[u.cc]=E,i.trigger(s.Events.INIT_PTS_FOUND,{frag:u,id:r,initPTS:E,timescale:T}))}if(f&&!1!==n.independent){if(m){var S=f.startPTS,b=f.endPTS,L=f.startDTS,A=f.endDTS;if(d)d.elementaryStreams[f.type]={startPTS:S,endPTS:b,startDTS:L,endDTS:A};else if(f.firstKeyFrame&&f.independent&&(this.couldBacktrack=!0),f.dropped&&f.independent){var D=this.getMainFwdBufferInfo();if((D?D.end:this.getLoadPosition())+this.config.maxBufferHole<(f.firstKeyFramePTS?f.firstKeyFramePTS:S)-this.config.maxBufferHole)return void this.backtrack(u);u.setElementaryStreamInfo(f.type,u.start,b,u.start,A,!0)}u.setElementaryStreamInfo(f.type,S,b,L,A),this.backtrackFragment&&(this.backtrackFragment=u),this.bufferFragmentData(f,u,d,o)}}else if(!1===n.independent)return void this.backtrack(u);if(y){var R=y.startPTS,k=y.endPTS,_=y.startDTS,x=y.endDTS;d&&(d.elementaryStreams[p.ElementaryStreamTypes.AUDIO]={startPTS:R,endPTS:k,startDTS:_,endDTS:x}),u.setElementaryStreamInfo(p.ElementaryStreamTypes.AUDIO,R,k,_,x),this.bufferFragmentData(y,u,d,o)}if(m&&null!=v&&null!==(t=v.samples)&&void 0!==t&&t.length){var I={id:r,frag:u,details:m,samples:v.samples};i.trigger(s.Events.FRAG_PARSING_METADATA,I)}if(m&&c){var w={id:r,frag:u,details:m,samples:c.samples};i.trigger(s.Events.FRAG_PARSING_USERDATA,w)}}},u._bufferInitSegment=function(e,t,r,i){var n=this;if(this.state===Be){this.audioOnly=!!t.audio&&!t.video,this.altAudio&&!this.audioOnly&&delete t.audio;var a=t.audio,o=t.video,l=t.audiovideo;if(a){var u=e.audioCodec,d=navigator.userAgent.toLowerCase();this.audioCodecSwitch&&(u&&(u=-1!==u.indexOf("mp4a.40.5")?"mp4a.40.2":"mp4a.40.5"),1!==a.metadata.channelCount&&-1===d.indexOf("firefox")&&(u="mp4a.40.5")),-1!==d.indexOf("android")&&"audio/mpeg"!==a.container&&(u="mp4a.40.2",this.log("Android: force audio codec to "+u)),e.audioCodec&&e.audioCodec!==u&&this.log('Swapping manifest audio codec "'+e.audioCodec+'" for "'+u+'"'),a.levelCodec=u,a.id="main",this.log("Init audio buffer, container:"+a.container+", codecs[selected/level/parsed]=["+(u||"")+"/"+(e.audioCodec||"")+"/"+a.codec+"]")}o&&(o.levelCodec=e.videoCodec,o.id="main",this.log("Init video buffer, container:"+o.container+", codecs[level/parsed]=["+(e.videoCodec||"")+"/"+o.codec+"]")),l&&this.log("Init audiovideo buffer, container:"+l.container+", codecs[level/parsed]=["+(e.attrs.CODECS||"")+"/"+l.codec+"]"),this.hls.trigger(s.Events.BUFFER_CODECS,t),Object.keys(t).forEach((function(e){var a=t[e].initSegment;null!=a&&a.byteLength&&n.hls.trigger(s.Events.BUFFER_APPENDING,{type:e,data:a,frag:r,part:null,chunkMeta:i,parent:r.type})})),this.tick()}},u.getMainFwdBufferInfo=function(){return this.getFwdBufferInfo(this.mediaBuffer?this.mediaBuffer:this.media,C.PlaylistLevelType.MAIN)},u.backtrack=function(e){this.couldBacktrack=!0,this.backtrackFragment=e,this.resetTransmuxer(),this.flushBufferGap(e),this.fragmentTracker.removeFragment(e),this.fragPrevious=null,this.nextLoadPosition=e.start,this.state=Fe},u.checkFragmentChanged=function(){var e=this.media,t=null;if(e&&e.readyState>1&&!1===e.seeking){var r=e.currentTime;if(ce.isBuffered(e,r)?t=this.getAppendedFrag(r):ce.isBuffered(e,r+.1)&&(t=this.getAppendedFrag(r+.1)),t){this.backtrackFragment=null;var i=this.fragPlaying,n=t.level;i&&t.sn===i.sn&&i.level===n&&t.urlId===i.urlId||(this.hls.trigger(s.Events.FRAG_CHANGED,{frag:t}),i&&i.level===n||this.hls.trigger(s.Events.LEVEL_SWITCHED,{level:n}),this.fragPlaying=t)}}},n=i,(l=[{key:"nextLevel",get:function(){var e=this.nextBufferedFrag;return e?e.level:-1}},{key:"currentFrag",get:function(){var e=this.media;return e?this.fragPlaying||this.getAppendedFrag(e.currentTime):null}},{key:"currentProgramDateTime",get:function(){var e=this.media;if(e){var t=e.currentTime,r=this.currentFrag;if(r&&(0,a.isFiniteNumber)(t)&&(0,a.isFiniteNumber)(r.programDateTime)){var i=r.programDateTime+1e3*(t-r.start);return new Date(i)}}return null}},{key:"currentLevel",get:function(){var e=this.currentFrag;return e?e.level:-1}},{key:"nextBufferedFrag",get:function(){var e=this.currentFrag;return e?this.followingBufferedFrag(e):null}},{key:"forceStartLoad",get:function(){return this._forceStartLoad}}])&&nt(n.prototype,l),Object.defineProperty(n,"prototype",{writable:!1}),i}(Ke);const ot=function(){function e(e,t,r){void 0===t&&(t=0),void 0===r&&(r=0),this.halfLife=void 0,this.alpha_=void 0,this.estimate_=void 0,this.totalWeight_=void 0,this.halfLife=e,this.alpha_=e?Math.exp(Math.log(.5)/e):0,this.estimate_=t,this.totalWeight_=r}var t=e.prototype;return t.sample=function(e,t){var r=Math.pow(this.alpha_,e);this.estimate_=t*(1-r)+r*this.estimate_,this.totalWeight_+=e},t.getTotalWeight=function(){return this.totalWeight_},t.getEstimate=function(){if(this.alpha_){var e=1-Math.pow(this.alpha_,this.totalWeight_);if(e)return this.estimate_/e}return this.estimate_},e}(),lt=function(){function e(e,t,r){this.defaultEstimate_=void 0,this.minWeight_=void 0,this.minDelayMs_=void 0,this.slow_=void 0,this.fast_=void 0,this.defaultEstimate_=r,this.minWeight_=.001,this.minDelayMs_=50,this.slow_=new ot(e),this.fast_=new ot(t)}var t=e.prototype;return t.update=function(e,t){var r=this.slow_,i=this.fast_;this.slow_.halfLife!==e&&(this.slow_=new ot(e,r.getEstimate(),r.getTotalWeight())),this.fast_.halfLife!==t&&(this.fast_=new ot(t,i.getEstimate(),i.getTotalWeight()))},t.sample=function(e,t){var r=(e=Math.max(e,this.minDelayMs_))/1e3,i=8*t/r;this.fast_.sample(r,i),this.slow_.sample(r,i)},t.canEstimate=function(){var e=this.fast_;return e&&e.getTotalWeight()>=this.minWeight_},t.getEstimate=function(){return this.canEstimate()?Math.min(this.fast_.getEstimate(),this.slow_.getEstimate()):this.defaultEstimate_},t.destroy=function(){},e}();function ut(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}const dt=function(){function e(e){this.hls=void 0,this.lastLoadedFragLevel=0,this._nextAutoLevel=-1,this.timer=void 0,this.onCheck=this._abandonRulesCheck.bind(this),this.fragCurrent=null,this.partCurrent=null,this.bitrateTestDelay=0,this.bwEstimator=void 0,this.hls=e;var t=e.config;this.bwEstimator=new lt(t.abrEwmaSlowVoD,t.abrEwmaFastVoD,t.abrEwmaDefaultEstimate),this.registerListeners()}var t,r,i=e.prototype;return i.registerListeners=function(){var e=this.hls;e.on(s.Events.FRAG_LOADING,this.onFragLoading,this),e.on(s.Events.FRAG_LOADED,this.onFragLoaded,this),e.on(s.Events.FRAG_BUFFERED,this.onFragBuffered,this),e.on(s.Events.LEVEL_LOADED,this.onLevelLoaded,this),e.on(s.Events.ERROR,this.onError,this)},i.unregisterListeners=function(){var e=this.hls;e.off(s.Events.FRAG_LOADING,this.onFragLoading,this),e.off(s.Events.FRAG_LOADED,this.onFragLoaded,this),e.off(s.Events.FRAG_BUFFERED,this.onFragBuffered,this),e.off(s.Events.LEVEL_LOADED,this.onLevelLoaded,this),e.off(s.Events.ERROR,this.onError,this)},i.destroy=function(){this.unregisterListeners(),this.clearTimer(),this.hls=this.onCheck=null,this.fragCurrent=this.partCurrent=null},i.onFragLoading=function(e,t){var r,i=t.frag;i.type===C.PlaylistLevelType.MAIN&&(this.timer||(this.fragCurrent=i,this.partCurrent=null!=(r=t.part)?r:null,this.timer=self.setInterval(this.onCheck,100)))},i.onLevelLoaded=function(e,t){var r=this.hls.config;t.details.live?this.bwEstimator.update(r.abrEwmaSlowLive,r.abrEwmaFastLive):this.bwEstimator.update(r.abrEwmaSlowVoD,r.abrEwmaFastVoD)},i._abandonRulesCheck=function(){var e=this.fragCurrent,t=this.partCurrent,r=this.hls,i=r.autoLevelEnabled,n=(r.config,r.media);if(e&&n){var o=t?t.stats:e.stats,u=t?t.duration:e.duration;if(o.aborted||o.loaded&&o.loaded===o.total||0===e.level)return this.clearTimer(),void(this._nextAutoLevel=-1);if(i&&!n.paused&&n.playbackRate&&n.readyState){var d=r.mainForwardBufferInfo;if(null!==d){var h=performance.now()-o.loading.start,f=Math.abs(n.playbackRate);if(!(h<=500*u/f)){var c=o.loaded&&o.loading.first,v=this.bwEstimator.getEstimate(),g=r.levels,p=r.minAutoLevel,m=g[e.level],y=o.total||Math.max(o.loaded,Math.round(u*m.maxBitrate/8)),E=c?1e3*o.loaded/h:0,T=E?(y-o.loaded)/E:8*y/v,S=d.len/f;if(!(T<=S)){var b,L=Number.POSITIVE_INFINITY;for(b=e.level-1;b>p;b--){var A=g[b].maxBitrate;if((L=E?u*A/(6.4*E):u*A/v)<S)break}L>=T||(l.logger.warn("Fragment "+e.sn+(t?" part "+t.index:"")+" of level "+e.level+" is loading too slowly and will cause an underbuffer; aborting and switching to level "+b+"\n Current BW estimate: "+((0,a.isFiniteNumber)(v)?(v/1024).toFixed(3):"Unknown")+" Kb/s\n Estimated load time for current fragment: "+T.toFixed(3)+" s\n Estimated load time for the next fragment: "+L.toFixed(3)+" s\n Time to underbuffer: "+S.toFixed(3)+" s"),r.nextLoadLevel=b,c&&this.bwEstimator.sample(h,o.loaded),this.clearTimer(),e.loader&&(this.fragCurrent=this.partCurrent=null,e.loader.abort()),r.trigger(s.Events.FRAG_LOAD_EMERGENCY_ABORTED,{frag:e,part:t,stats:o}))}}}}}},i.onFragLoaded=function(e,t){var r=t.frag,i=t.part;if(r.type===C.PlaylistLevelType.MAIN&&(0,a.isFiniteNumber)(r.sn)){var n=i?i.stats:r.stats,o=i?i.duration:r.duration;if(this.clearTimer(),this.lastLoadedFragLevel=r.level,this._nextAutoLevel=-1,this.hls.config.abrMaxWithRealBitrate){var l=this.hls.levels[r.level],u=(l.loaded?l.loaded.bytes:0)+n.loaded,d=(l.loaded?l.loaded.duration:0)+o;l.loaded={bytes:u,duration:d},l.realBitrate=Math.round(8*u/d)}if(r.bitrateTest){var h={stats:n,frag:r,part:i,id:r.type};this.onFragBuffered(s.Events.FRAG_BUFFERED,h)}}},i.onFragBuffered=function(e,t){var r=t.frag,i=t.part,n=i?i.stats:r.stats;if(!n.aborted&&r.type===C.PlaylistLevelType.MAIN&&"initSegment"!==r.sn){var a=n.parsing.end-n.loading.start;this.bwEstimator.sample(a,n.loaded),n.bwEstimate=this.bwEstimator.getEstimate(),r.bitrateTest?this.bitrateTestDelay=a/1e3:this.bitrateTestDelay=0}},i.onError=function(e,t){switch(t.details){case o.ErrorDetails.FRAG_LOAD_ERROR:case o.ErrorDetails.FRAG_LOAD_TIMEOUT:this.clearTimer()}},i.clearTimer=function(){self.clearInterval(this.timer),this.timer=void 0},i.getNextABRAutoLevel=function(){var e=this.fragCurrent,t=this.partCurrent,r=this.hls,i=r.maxAutoLevel,n=r.config,a=r.minAutoLevel,s=r.media,o=t?t.duration:e?e.duration:0,u=(s&&s.currentTime,s&&0!==s.playbackRate?Math.abs(s.playbackRate):1),d=this.bwEstimator?this.bwEstimator.getEstimate():n.abrEwmaDefaultEstimate,h=r.mainForwardBufferInfo,f=(h?h.len:0)/u,c=this.findBestLevel(d,a,i,f,n.abrBandWidthFactor,n.abrBandWidthUpFactor);if(c>=0)return c;l.logger.trace((f?"rebuffering expected":"buffer is empty")+", finding optimal quality level");var v=o?Math.min(o,n.maxStarvationDelay):n.maxStarvationDelay,g=n.abrBandWidthFactor,p=n.abrBandWidthUpFactor;if(!f){var m=this.bitrateTestDelay;m&&(v=(o?Math.min(o,n.maxLoadingDelay):n.maxLoadingDelay)-m,l.logger.trace("bitrate test took "+Math.round(1e3*m)+"ms, set first fragment max fetchDuration to "+Math.round(1e3*v)+" ms"),g=p=1)}return c=this.findBestLevel(d,a,i,f+v,g,p),Math.max(c,0)},i.findBestLevel=function(e,t,r,i,n,s){for(var o,u=this.fragCurrent,d=this.partCurrent,h=this.lastLoadedFragLevel,f=this.hls.levels,c=f[h],v=!(null==c||null===(o=c.details)||void 0===o||!o.live),g=null==c?void 0:c.codecSet,p=d?d.duration:u?u.duration:0,m=r;m>=t;m--){var y=f[m];if(y&&(!g||y.codecSet===g)){var E,T=y.details,S=(d?null==T?void 0:T.partTarget:null==T?void 0:T.averagetargetduration)||p;E=m<=h?n*e:s*e;var b=f[m].maxBitrate,L=b*S/E;if(l.logger.trace("level/adjustedbw/bitrate/avgDuration/maxFetchDuration/fetchDuration: "+m+"/"+Math.round(E)+"/"+b+"/"+S+"/"+i+"/"+L),E>b&&(0===L||!(0,a.isFiniteNumber)(L)||v&&!this.bitrateTestDelay||L<i))return m}}return-1},t=e,(r=[{key:"nextAutoLevel",get:function(){var e=this._nextAutoLevel,t=this.bwEstimator;if(-1!==e&&!t.canEstimate())return e;var r=this.getNextABRAutoLevel();return-1!==e&&this.hls.levels[r].loadError?e:(-1!==e&&(r=Math.min(e,r)),r)},set:function(e){this._nextAutoLevel=e}}])&&ut(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();var ht=r(514),ft=r.n(ht),ct=function(){function e(e){this.buffers=void 0,this.queues={video:[],audio:[],audiovideo:[]},this.buffers=e}var t=e.prototype;return t.append=function(e,t){var r=this.queues[t];r.push(e),1===r.length&&this.buffers[t]&&this.executeNext(t)},t.insertAbort=function(e,t){this.queues[t].unshift(e),this.executeNext(t)},t.appendBlocker=function(e){var t,r=new Promise((function(e){t=e})),i={execute:t,onStart:function(){},onComplete:function(){},onError:function(){}};return this.append(i,e),r},t.executeNext=function(e){var t=this.buffers,r=this.queues,i=t[e],n=r[e];if(n.length){var a=n[0];try{a.execute()}catch(t){l.logger.warn("[buffer-operation-queue]: Unhandled exception executing the current operation"),a.onError(t),i&&i.updating||(n.shift(),this.executeNext(e))}}},t.shiftAndExecuteNext=function(e){this.queues[e].shift(),this.executeNext(e)},t.current=function(e){return this.queues[e][0]},e}(),vt=We(),gt=/([ha]vc.)(?:\.[^.,]+)+/,pt=function(){function e(e){var t=this;this.details=null,this._objectUrl=null,this.operationQueue=void 0,this.listeners=void 0,this.hls=void 0,this.bufferCodecEventsExpected=0,this._bufferCodecEventsTotal=0,this.media=null,this.mediaSource=null,this.appendError=0,this.tracks={},this.pendingTracks={},this.sourceBuffer=void 0,this._onMediaSourceOpen=function(){var e=t.hls,r=t.media,i=t.mediaSource;l.logger.log("[buffer-controller]: Media source opened"),r&&(t.updateMediaElementDuration(),e.trigger(s.Events.MEDIA_ATTACHED,{media:r})),i&&i.removeEventListener("sourceopen",t._onMediaSourceOpen),t.checkPendingTracks()},this._onMediaSourceClose=function(){l.logger.log("[buffer-controller]: Media source closed")},this._onMediaSourceEnded=function(){l.logger.log("[buffer-controller]: Media source ended")},this.hls=e,this._initSourceBuffer(),this.registerListeners()}var t=e.prototype;return t.hasSourceTypes=function(){return this.getSourceBufferTypes().length>0||Object.keys(this.pendingTracks).length>0},t.destroy=function(){this.unregisterListeners(),this.details=null},t.registerListeners=function(){var e=this.hls;e.on(s.Events.MEDIA_ATTACHING,this.onMediaAttaching,this),e.on(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),e.on(s.Events.MANIFEST_PARSED,this.onManifestParsed,this),e.on(s.Events.BUFFER_RESET,this.onBufferReset,this),e.on(s.Events.BUFFER_APPENDING,this.onBufferAppending,this),e.on(s.Events.BUFFER_CODECS,this.onBufferCodecs,this),e.on(s.Events.BUFFER_EOS,this.onBufferEos,this),e.on(s.Events.BUFFER_FLUSHING,this.onBufferFlushing,this),e.on(s.Events.LEVEL_UPDATED,this.onLevelUpdated,this),e.on(s.Events.FRAG_PARSED,this.onFragParsed,this),e.on(s.Events.FRAG_CHANGED,this.onFragChanged,this)},t.unregisterListeners=function(){var e=this.hls;e.off(s.Events.MEDIA_ATTACHING,this.onMediaAttaching,this),e.off(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this),e.off(s.Events.MANIFEST_PARSED,this.onManifestParsed,this),e.off(s.Events.BUFFER_RESET,this.onBufferReset,this),e.off(s.Events.BUFFER_APPENDING,this.onBufferAppending,this),e.off(s.Events.BUFFER_CODECS,this.onBufferCodecs,this),e.off(s.Events.BUFFER_EOS,this.onBufferEos,this),e.off(s.Events.BUFFER_FLUSHING,this.onBufferFlushing,this),e.off(s.Events.LEVEL_UPDATED,this.onLevelUpdated,this),e.off(s.Events.FRAG_PARSED,this.onFragParsed,this),e.off(s.Events.FRAG_CHANGED,this.onFragChanged,this)},t._initSourceBuffer=function(){this.sourceBuffer={},this.operationQueue=new ct(this.sourceBuffer),this.listeners={audio:[],video:[],audiovideo:[]}},t.onManifestParsed=function(e,t){var r=2;(t.audio&&!t.video||!t.altAudio)&&(r=1),this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=r,this.details=null,l.logger.log(this.bufferCodecEventsExpected+" bufferCodec event(s) expected")},t.onMediaAttaching=function(e,t){var r=this.media=t.media;if(r&&vt){var i=this.mediaSource=new vt;i.addEventListener("sourceopen",this._onMediaSourceOpen),i.addEventListener("sourceended",this._onMediaSourceEnded),i.addEventListener("sourceclose",this._onMediaSourceClose),r.src=self.URL.createObjectURL(i),this._objectUrl=r.src}},t.onMediaDetaching=function(){var e=this.media,t=this.mediaSource,r=this._objectUrl;if(t){if(l.logger.log("[buffer-controller]: media source detaching"),"open"===t.readyState)try{t.endOfStream()}catch(e){l.logger.warn("[buffer-controller]: onMediaDetaching: "+e.message+" while calling endOfStream")}this.onBufferReset(),t.removeEventListener("sourceopen",this._onMediaSourceOpen),t.removeEventListener("sourceended",this._onMediaSourceEnded),t.removeEventListener("sourceclose",this._onMediaSourceClose),e&&(r&&self.URL.revokeObjectURL(r),e.src===r?(e.removeAttribute("src"),e.load()):l.logger.warn("[buffer-controller]: media.src was changed by a third party - skip cleanup")),this.mediaSource=null,this.media=null,this._objectUrl=null,this.bufferCodecEventsExpected=this._bufferCodecEventsTotal,this.pendingTracks={},this.tracks={}}this.hls.trigger(s.Events.MEDIA_DETACHED,void 0)},t.onBufferReset=function(){var e=this;this.getSourceBufferTypes().forEach((function(t){var r=e.sourceBuffer[t];try{r&&(e.removeBufferListeners(t),e.mediaSource&&e.mediaSource.removeSourceBuffer(r),e.sourceBuffer[t]=void 0)}catch(e){l.logger.warn("[buffer-controller]: Failed to reset the "+t+" buffer",e)}})),this._initSourceBuffer()},t.onBufferCodecs=function(e,t){var r=this,i=this.getSourceBufferTypes().length;Object.keys(t).forEach((function(e){if(i){var n=r.tracks[e];if(n&&"function"==typeof n.buffer.changeType){var a=t[e],s=a.id,o=a.codec,u=a.levelCodec,d=a.container,h=a.metadata,f=(n.levelCodec||n.codec).replace(gt,"$1"),c=(u||o).replace(gt,"$1");if(f!==c){var v=d+";codecs="+(u||o);r.appendChangeType(e,v),l.logger.log("[buffer-controller]: switching codec "+f+" to "+c),r.tracks[e]={buffer:n.buffer,codec:o,container:d,levelCodec:u,metadata:h,id:s}}}}else r.pendingTracks[e]=t[e]})),i||(this.bufferCodecEventsExpected=Math.max(this.bufferCodecEventsExpected-1,0),this.mediaSource&&"open"===this.mediaSource.readyState&&this.checkPendingTracks())},t.appendChangeType=function(e,t){var r=this,i=this.operationQueue,n={execute:function(){var n=r.sourceBuffer[e];n&&(l.logger.log("[buffer-controller]: changing "+e+" sourceBuffer type to "+t),n.changeType(t)),i.shiftAndExecuteNext(e)},onStart:function(){},onComplete:function(){},onError:function(t){l.logger.warn("[buffer-controller]: Failed to change "+e+" SourceBuffer type",t)}};i.append(n,e)},t.onBufferAppending=function(e,t){var r=this,i=this.hls,n=this.operationQueue,a=this.tracks,u=t.data,d=t.type,h=t.frag,f=t.part,c=t.chunkMeta,v=c.buffering[d],g=self.performance.now();v.start=g;var p=h.stats.buffering,m=f?f.stats.buffering:null;0===p.start&&(p.start=g),m&&0===m.start&&(m.start=g);var y=a.audio,E="audio"===d&&1===c.id&&"audio/mpeg"===(null==y?void 0:y.container),T={execute:function(){if(v.executeStart=self.performance.now(),E){var e=r.sourceBuffer[d];if(e){var t=h.start-e.timestampOffset;Math.abs(t)>=.1&&(l.logger.log("[buffer-controller]: Updating audio SourceBuffer timestampOffset to "+h.start+" (delta: "+t+") sn: "+h.sn+")"),e.timestampOffset=h.start)}}r.appendExecutor(u,d)},onStart:function(){},onComplete:function(){var e=self.performance.now();v.executeEnd=v.end=e,0===p.first&&(p.first=e),m&&0===m.first&&(m.first=e);var t=r.sourceBuffer,i={};for(var n in t)i[n]=ce.getBuffered(t[n]);r.appendError=0,r.hls.trigger(s.Events.BUFFER_APPENDED,{type:d,frag:h,part:f,chunkMeta:c,parent:h.type,timeRanges:i})},onError:function(e){l.logger.error("[buffer-controller]: Error encountered while trying to append to the "+d+" SourceBuffer",e);var t={type:o.ErrorTypes.MEDIA_ERROR,parent:h.type,details:o.ErrorDetails.BUFFER_APPEND_ERROR,err:e,fatal:!1};e.code===DOMException.QUOTA_EXCEEDED_ERR?t.details=o.ErrorDetails.BUFFER_FULL_ERROR:(r.appendError++,t.details=o.ErrorDetails.BUFFER_APPEND_ERROR,r.appendError>i.config.appendErrorMaxRetry&&(l.logger.error("[buffer-controller]: Failed "+i.config.appendErrorMaxRetry+" times to append segment in sourceBuffer"),t.fatal=!0,i.stopLoad())),i.trigger(s.Events.ERROR,t)}};n.append(T,d)},t.onBufferFlushing=function(e,t){var r=this,i=this.operationQueue,n=function(e){return{execute:r.removeExecutor.bind(r,e,t.startOffset,t.endOffset),onStart:function(){},onComplete:function(){r.hls.trigger(s.Events.BUFFER_FLUSHED,{type:e})},onError:function(t){l.logger.warn("[buffer-controller]: Failed to remove from "+e+" SourceBuffer",t)}}};t.type?i.append(n(t.type),t.type):this.getSourceBufferTypes().forEach((function(e){i.append(n(e),e)}))},t.onFragParsed=function(e,t){var r=this,i=t.frag,n=t.part,a=[],o=n?n.elementaryStreams:i.elementaryStreams;o[p.ElementaryStreamTypes.AUDIOVIDEO]?a.push("audiovideo"):(o[p.ElementaryStreamTypes.AUDIO]&&a.push("audio"),o[p.ElementaryStreamTypes.VIDEO]&&a.push("video")),0===a.length&&l.logger.warn("Fragments must have at least one ElementaryStreamType set. type: "+i.type+" level: "+i.level+" sn: "+i.sn),this.blockBuffers((function(){var e=self.performance.now();i.stats.buffering.end=e,n&&(n.stats.buffering.end=e);var t=n?n.stats:i.stats;r.hls.trigger(s.Events.FRAG_BUFFERED,{frag:i,part:n,stats:t,id:i.type})}),a)},t.onFragChanged=function(e,t){this.flushBackBuffer()},t.onBufferEos=function(e,t){var r=this;this.getSourceBufferTypes().reduce((function(e,i){var n=r.sourceBuffer[i];return t.type&&t.type!==i||n&&!n.ended&&(n.ended=!0,l.logger.log("[buffer-controller]: "+i+" sourceBuffer now EOS")),e&&!(n&&!n.ended)}),!0)&&this.blockBuffers((function(){var e=r.mediaSource;e&&"open"===e.readyState&&e.endOfStream()}))},t.onLevelUpdated=function(e,t){var r=t.details;r.fragments.length&&(this.details=r,this.getSourceBufferTypes().length?this.blockBuffers(this.updateMediaElementDuration.bind(this)):this.updateMediaElementDuration())},t.flushBackBuffer=function(){var e=this.hls,t=this.details,r=this.media,i=this.sourceBuffer;if(r&&null!==t){var n=this.getSourceBufferTypes();if(n.length){var o=t.live&&null!==e.config.liveBackBufferLength?e.config.liveBackBufferLength:e.config.backBufferLength;if((0,a.isFiniteNumber)(o)&&!(o<0)){var l=r.currentTime,u=t.levelTargetDuration,d=Math.max(o,u),h=Math.floor(l/u)*u-d;n.forEach((function(r){var n=i[r];if(n){var a=ce.getBuffered(n);a.length>0&&h>a.start(0)&&(e.trigger(s.Events.BACK_BUFFER_REACHED,{bufferEnd:h}),t.live&&e.trigger(s.Events.LIVE_BACK_BUFFER_REACHED,{bufferEnd:h}),e.trigger(s.Events.BUFFER_FLUSHING,{startOffset:0,endOffset:h,type:r}))}}))}}}},t.updateMediaElementDuration=function(){if(this.details&&this.media&&this.mediaSource&&"open"===this.mediaSource.readyState){var e=this.details,t=this.hls,r=this.media,i=this.mediaSource,n=e.fragments[0].start+e.totalduration,s=r.duration,o=(0,a.isFiniteNumber)(i.duration)?i.duration:0;e.live&&t.config.liveDurationInfinity?(l.logger.log("[buffer-controller]: Media Source duration is set to Infinity"),i.duration=1/0,this.updateSeekableRange(e)):(n>o&&n>s||!(0,a.isFiniteNumber)(s))&&(l.logger.log("[buffer-controller]: Updating Media Source duration to "+n.toFixed(3)),i.duration=n)}},t.updateSeekableRange=function(e){var t=this.mediaSource,r=e.fragments;if(r.length&&e.live&&null!=t&&t.setLiveSeekableRange){var i=Math.max(0,r[0].start),n=Math.max(i,i+e.totalduration);t.setLiveSeekableRange(i,n)}},t.checkPendingTracks=function(){var e=this.bufferCodecEventsExpected,t=this.operationQueue,r=this.pendingTracks,i=Object.keys(r).length;if(i&&!e||2===i){this.createSourceBuffers(r),this.pendingTracks={};var n=this.getSourceBufferTypes();if(0===n.length)return void this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR,fatal:!0,reason:"could not create source buffer for media codec(s)"});n.forEach((function(e){t.executeNext(e)}))}},t.createSourceBuffers=function(e){var t=this.sourceBuffer,r=this.mediaSource;if(!r)throw Error("createSourceBuffers called when mediaSource was null");var i=0;for(var n in e)if(!t[n]){var a=e[n];if(!a)throw Error("source buffer exists for track "+n+", however track does not");var u=a.levelCodec||a.codec,d=a.container+";codecs="+u;l.logger.log("[buffer-controller]: creating sourceBuffer("+d+")");try{var h=t[n]=r.addSourceBuffer(d),f=n;this.addBufferListener(f,"updatestart",this._onSBUpdateStart),this.addBufferListener(f,"updateend",this._onSBUpdateEnd),this.addBufferListener(f,"error",this._onSBUpdateError),this.tracks[n]={buffer:h,codec:u,container:a.container,levelCodec:a.levelCodec,metadata:a.metadata,id:a.id},i++}catch(e){l.logger.error("[buffer-controller]: error while trying to add sourceBuffer: "+e.message),this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_ADD_CODEC_ERROR,fatal:!1,error:e,mimeType:d})}}i&&this.hls.trigger(s.Events.BUFFER_CREATED,{tracks:this.tracks})},t._onSBUpdateStart=function(e){this.operationQueue.current(e).onStart()},t._onSBUpdateEnd=function(e){var t=this.operationQueue;t.current(e).onComplete(),t.shiftAndExecuteNext(e)},t._onSBUpdateError=function(e,t){l.logger.error("[buffer-controller]: "+e+" SourceBuffer error",t),this.hls.trigger(s.Events.ERROR,{type:o.ErrorTypes.MEDIA_ERROR,details:o.ErrorDetails.BUFFER_APPENDING_ERROR,fatal:!1});var r=this.operationQueue.current(e);r&&r.onError(t)},t.removeExecutor=function(e,t,r){var i=this.media,n=this.mediaSource,s=this.operationQueue,o=this.sourceBuffer[e];if(!i||!n||!o)return l.logger.warn("[buffer-controller]: Attempting to remove from the "+e+" SourceBuffer, but it does not exist"),void s.shiftAndExecuteNext(e);var u=(0,a.isFiniteNumber)(i.duration)?i.duration:1/0,d=(0,a.isFiniteNumber)(n.duration)?n.duration:1/0,h=Math.max(0,t),f=Math.min(r,u,d);f>h?(l.logger.log("[buffer-controller]: Removing ["+h+","+f+"] from the "+e+" SourceBuffer"),o.remove(h,f)):s.shiftAndExecuteNext(e)},t.appendExecutor=function(e,t){var r=this.operationQueue,i=this.sourceBuffer[t];if(!i)return l.logger.warn("[buffer-controller]: Attempting to append to the "+t+" SourceBuffer, but it does not exist"),void r.shiftAndExecuteNext(t);i.ended=!1,i.appendBuffer(e)},t.blockBuffers=function(e,t){var r=this;if(void 0===t&&(t=this.getSourceBufferTypes()),!t.length)return l.logger.log("[buffer-controller]: Blocking operation requested, but no SourceBuffers exist"),void Promise.resolve().then(e);var i=this.operationQueue,n=t.map((function(e){return i.appendBlocker(e)}));Promise.all(n).then((function(){e(),t.forEach((function(e){var t=r.sourceBuffer[e];t&&t.updating||i.shiftAndExecuteNext(e)}))}))},t.getSourceBufferTypes=function(){return Object.keys(this.sourceBuffer)},t.addBufferListener=function(e,t,r){var i=this.sourceBuffer[e];if(i){var n=r.bind(this,e);this.listeners[e].push({event:t,listener:n}),i.addEventListener(t,n)}},t.removeBufferListeners=function(e){var t=this.sourceBuffer[e];t&&this.listeners[e].forEach((function(e){t.removeEventListener(e.event,e.listener)}))},e}();function mt(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}const yt=function(){function e(e){this.autoLevelCapping=void 0,this.firstLevel=void 0,this.media=void 0,this.restrictedLevels=void 0,this.timer=void 0,this.hls=void 0,this.streamController=void 0,this.clientRect=void 0,this.hls=e,this.autoLevelCapping=Number.POSITIVE_INFINITY,this.firstLevel=-1,this.media=null,this.restrictedLevels=[],this.timer=void 0,this.clientRect=null,this.registerListeners()}var t,r,i=e.prototype;return i.setStreamController=function(e){this.streamController=e},i.destroy=function(){this.unregisterListener(),this.hls.config.capLevelToPlayerSize&&this.stopCapping(),this.media=null,this.clientRect=null,this.hls=this.streamController=null},i.registerListeners=function(){var e=this.hls;e.on(s.Events.FPS_DROP_LEVEL_CAPPING,this.onFpsDropLevelCapping,this),e.on(s.Events.MEDIA_ATTACHING,this.onMediaAttaching,this),e.on(s.Events.MANIFEST_PARSED,this.onManifestParsed,this),e.on(s.Events.BUFFER_CODECS,this.onBufferCodecs,this),e.on(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this)},i.unregisterListener=function(){var e=this.hls;e.off(s.Events.FPS_DROP_LEVEL_CAPPING,this.onFpsDropLevelCapping,this),e.off(s.Events.MEDIA_ATTACHING,this.onMediaAttaching,this),e.off(s.Events.MANIFEST_PARSED,this.onManifestParsed,this),e.off(s.Events.BUFFER_CODECS,this.onBufferCodecs,this),e.off(s.Events.MEDIA_DETACHING,this.onMediaDetaching,this)},i.onFpsDropLevelCapping=function(t,r){e.isLevelAllowed(r.droppedLevel,this.restrictedLevels)&&this.restrictedLevels.push(r.droppedLevel)},i.onMediaAttaching=function(e,t){this.media=t.media instanceof HTMLVideoElement?t.media:null},i.onManifestParsed=function(e,t){var r=this.hls;this.restrictedLevels=[],this.firstLevel=t.firstLevel,r.config.capLevelToPlayerSize&&t.video&&this.startCapping()},i.onBufferCodecs=function(e,t){this.hls.config.capLevelToPlayerSize&&t.video&&this.startCapping()},i.onMediaDetaching=function(){this.stopCapping()},i.detectPlayerSize=function(){if(this.media&&this.mediaHeight>0&&this.mediaWidth>0){var e=this.hls.levels;if(e.length){var t=this.hls;t.autoLevelCapping=this.getMaxLevel(e.length-1),t.autoLevelCapping>this.autoLevelCapping&&this.streamController&&this.streamController.nextLevelSwitch(),this.autoLevelCapping=t.autoLevelCapping}}},i.getMaxLevel=function(t){var r=this,i=this.hls.levels;if(!i.length)return-1;var n=i.filter((function(i,n){return e.isLevelAllowed(n,r.restrictedLevels)&&n<=t}));return this.clientRect=null,e.getMaxLevelByMediaSize(n,this.mediaWidth,this.mediaHeight)},i.startCapping=function(){this.timer||(this.autoLevelCapping=Number.POSITIVE_INFINITY,this.hls.firstLevel=this.getMaxLevel(this.firstLevel),self.clearInterval(this.timer),this.timer=self.setInterval(this.detectPlayerSize.bind(this),1e3),this.detectPlayerSize())},i.stopCapping=function(){this.restrictedLevels=[],this.firstLevel=-1,this.autoLevelCapping=Number.POSITIVE_INFINITY,this.timer&&(self.clearInterval(this.timer),this.timer=void 0)},i.getDimensions=function(){if(this.clientRect)return this.clientRect;var e=this.media,t={width:0,height:0};if(e){var r=e.getBoundingClientRect();t.width=r.width,t.height=r.height,t.width||t.height||(t.width=r.right-r.left||e.width||0,t.height=r.bottom-r.top||e.height||0)}return this.clientRect=t,t},e.isLevelAllowed=function(e,t){return void 0===t&&(t=[]),-1===t.indexOf(e)},e.getMaxLevelByMediaSize=function(e,t,r){if(!e||!e.length)return-1;for(var i,n,a=e.length-1,s=0;s<e.length;s+=1){var o=e[s];if((o.width>=t||o.height>=r)&&(i=o,!(n=e[s+1])||i.width!==n.width||i.height!==n.height)){a=s;break}}return a},t=e,(r=[{key:"mediaWidth",get:function(){return this.getDimensions().width*this.contentScaleFactor}},{key:"mediaHeight",get:function(){return this.getDimensions().height*this.contentScaleFactor}},{key:"contentScaleFactor",get:function(){var e=1;if(!this.hls.config.ignoreDevicePixelRatio)try{e=self.devicePixelRatio}catch(e){}return e}}])&&mt(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}(),Et=function(){function e(e){this.hls=void 0,this.isVideoPlaybackQualityAvailable=!1,this.timer=void 0,this.media=null,this.lastTime=void 0,this.lastDroppedFrames=0,this.lastDecodedFrames=0,this.streamController=void 0,this.hls=e,this.registerListeners()}var t=e.prototype;return t.setStreamController=function(e){this.streamController=e},t.registerListeners=function(){this.hls.on(s.Events.MEDIA_ATTACHING,this.onMediaAttaching,this)},t.unregisterListeners=function(){this.hls.off(s.Events.MEDIA_ATTACHING,this.onMediaAttaching)},t.destroy=function(){this.timer&&clearInterval(this.timer),this.unregisterListeners(),this.isVideoPlaybackQualityAvailable=!1,this.media=null},t.onMediaAttaching=function(e,t){var r=this.hls.config;if(r.capLevelOnFPSDrop){var i=t.media instanceof self.HTMLVideoElement?t.media:null;this.media=i,i&&"function"==typeof i.getVideoPlaybackQuality&&(this.isVideoPlaybackQualityAvailable=!0),self.clearInterval(this.timer),this.timer=self.setInterval(this.checkFPSInterval.bind(this),r.fpsDroppedMonitoringPeriod)}},t.checkFPS=function(e,t,r){var i=performance.now();if(t){if(this.lastTime){var n=i-this.lastTime,a=r-this.lastDroppedFrames,o=t-this.lastDecodedFrames,u=1e3*a/n,d=this.hls;if(d.trigger(s.Events.FPS_DROP,{currentDropped:a,currentDecoded:o,totalDroppedFrames:r}),u>0&&a>d.config.fpsDroppedMonitoringThreshold*o){var h=d.currentLevel;l.logger.warn("drop FPS ratio greater than max allowed value for currentLevel: "+h),h>0&&(-1===d.autoLevelCapping||d.autoLevelCapping>=h)&&(h-=1,d.trigger(s.Events.FPS_DROP_LEVEL_CAPPING,{level:h,droppedLevel:d.currentLevel}),d.autoLevelCapping=h,this.streamController.nextLevelSwitch())}}this.lastTime=i,this.lastDroppedFrames=r,this.lastDecodedFrames=t}},t.checkFPSInterval=function(){var e=this.media;if(e)if(this.isVideoPlaybackQualityAvailable){var t=e.getVideoPlaybackQuality();this.checkFPS(e,t.totalVideoFrames,t.droppedVideoFrames)}else this.checkFPS(e,e.webkitDecodedFrameCount,e.webkitDroppedFrameCount)},e}();var Tt=r(408),St=/^age:\s*[\d.]+\s*$/m;const bt=function(){function e(e){this.xhrSetup=void 0,this.requestTimeout=void 0,this.retryTimeout=void 0,this.retryDelay=void 0,this.config=null,this.callbacks=null,this.context=void 0,this.loader=null,this.stats=void 0,this.xhrSetup=e?e.xhrSetup:null,this.stats=new Tt.LoadStats,this.retryDelay=0}var t=e.prototype;return t.destroy=function(){this.callbacks=null,this.abortInternal(),this.loader=null,this.config=null},t.abortInternal=function(){var e=this.loader;self.clearTimeout(this.requestTimeout),self.clearTimeout(this.retryTimeout),e&&(e.onreadystatechange=null,e.onprogress=null,4!==e.readyState&&(this.stats.aborted=!0,e.abort()))},t.abort=function(){var e;this.abortInternal(),null!==(e=this.callbacks)&&void 0!==e&&e.onAbort&&this.callbacks.onAbort(this.stats,this.context,this.loader)},t.load=function(e,t,r){if(this.stats.loading.start)throw new Error("Loader can only be used once.");this.stats.loading.start=self.performance.now(),this.context=e,this.config=t,this.callbacks=r,this.retryDelay=t.retryDelay,this.loadInternal()},t.loadInternal=function(){var e=this.config,t=this.context;if(e){var r=this.loader=new self.XMLHttpRequest,i=this.stats;i.loading.first=0,i.loaded=0;var n=this.xhrSetup;try{if(n)try{n(r,t.url)}catch(e){r.open("GET",t.url,!0),n(r,t.url)}r.readyState||r.open("GET",t.url,!0);var a=this.context.headers;if(a)for(var s in a)r.setRequestHeader(s,a[s])}catch(e){return void this.callbacks.onError({code:r.status,text:e.message},t,r)}t.rangeEnd&&r.setRequestHeader("Range","bytes="+t.rangeStart+"-"+(t.rangeEnd-1)),r.onreadystatechange=this.readystatechange.bind(this),r.onprogress=this.loadprogress.bind(this),r.responseType=t.responseType,self.clearTimeout(this.requestTimeout),this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this),e.timeout),r.send()}},t.readystatechange=function(){var e=this.context,t=this.loader,r=this.stats;if(e&&t){var i=t.readyState,n=this.config;if(!r.aborted&&i>=2)if(self.clearTimeout(this.requestTimeout),0===r.loading.first&&(r.loading.first=Math.max(self.performance.now(),r.loading.start)),4===i){t.onreadystatechange=null,t.onprogress=null;var a=t.status,s="arraybuffer"===t.responseType;if(a>=200&&a<300&&(s&&t.response||null!==t.responseText)){var o,u;if(r.loading.end=Math.max(self.performance.now(),r.loading.first),u=s?(o=t.response).byteLength:(o=t.responseText).length,r.loaded=r.total=u,!this.callbacks)return;var d=this.callbacks.onProgress;if(d&&d(r,e,o,t),!this.callbacks)return;var h={url:t.responseURL,data:o};this.callbacks.onSuccess(h,r,e,t)}else r.retry>=n.maxRetry||a>=400&&a<499?(l.logger.error(a+" while loading "+e.url),this.callbacks.onError({code:a,text:t.statusText},e,t)):(l.logger.warn(a+" while loading "+e.url+", retrying in "+this.retryDelay+"..."),this.abortInternal(),this.loader=null,self.clearTimeout(this.retryTimeout),this.retryTimeout=self.setTimeout(this.loadInternal.bind(this),this.retryDelay),this.retryDelay=Math.min(2*this.retryDelay,n.maxRetryDelay),r.retry++)}else self.clearTimeout(this.requestTimeout),this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this),n.timeout)}},t.loadtimeout=function(){l.logger.warn("timeout while loading "+this.context.url);var e=this.callbacks;e&&(this.abortInternal(),e.onTimeout(this.stats,this.context,this.loader))},t.loadprogress=function(e){var t=this.stats;t.loaded=e.loaded,e.lengthComputable&&(t.total=e.total)},t.getCacheAge=function(){var e=null;if(this.loader&&St.test(this.loader.getAllResponseHeaders())){var t=this.loader.getResponseHeader("age");e=t?parseFloat(t):null}return e},e}();var Lt=function(){function e(){this.chunks=[],this.dataLength=0}var t=e.prototype;return t.push=function(e){this.chunks.push(e),this.dataLength+=e.length},t.flush=function(){var e,t=this.chunks,r=this.dataLength;return t.length?(e=1===t.length?t[0]:function(e,t){for(var r=new Uint8Array(t),i=0,n=0;n<e.length;n++){var a=e[n];r.set(a,i),i+=a.length}return r}(t,r),this.reset(),e):new Uint8Array(0)},t.reset=function(){this.chunks.length=0,this.dataLength=0},e}();function At(e){var t="function"==typeof Map?new Map:void 0;return At=function(e){if(null===e||(r=e,-1===Function.toString.call(r).indexOf("[native code]")))return e;var r;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,i)}function i(){return Dt(e,arguments,_t(this).constructor)}return i.prototype=Object.create(e.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),kt(i,e)},At(e)}function Dt(e,t,r){return Dt=Rt()?Reflect.construct.bind():function(e,t,r){var i=[null];i.push.apply(i,t);var n=new(Function.bind.apply(e,i));return r&&kt(n,r.prototype),n},Dt.apply(null,arguments)}function Rt(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function kt(e,t){return kt=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},kt(e,t)}function _t(e){return _t=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},_t(e)}function xt(){return xt=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},xt.apply(this,arguments)}var It=function(){function e(e){this.fetchSetup=void 0,this.requestTimeout=void 0,this.request=void 0,this.response=void 0,this.controller=void 0,this.context=void 0,this.config=null,this.callbacks=null,this.stats=void 0,this.loader=null,this.fetchSetup=e.fetchSetup||wt,this.controller=new self.AbortController,this.stats=new Tt.LoadStats}var t=e.prototype;return t.destroy=function(){this.loader=this.callbacks=null,this.abortInternal()},t.abortInternal=function(){var e=this.response;e&&e.ok||(this.stats.aborted=!0,this.controller.abort())},t.abort=function(){var e;this.abortInternal(),null!==(e=this.callbacks)&&void 0!==e&&e.onAbort&&this.callbacks.onAbort(this.stats,this.context,this.response)},t.load=function(e,t,r){var i=this,n=this.stats;if(n.loading.start)throw new Error("Loader can only be used once.");n.loading.start=self.performance.now();var s=function(e,t){var r={method:"GET",mode:"cors",credentials:"same-origin",signal:t,headers:new self.Headers(xt({},e.headers))};return e.rangeEnd&&r.headers.set("Range","bytes="+e.rangeStart+"-"+String(e.rangeEnd-1)),r}(e,this.controller.signal),o=r.onProgress,l="arraybuffer"===e.responseType,u=l?"byteLength":"length";this.context=e,this.config=t,this.callbacks=r,this.request=this.fetchSetup(e,s),self.clearTimeout(this.requestTimeout),this.requestTimeout=self.setTimeout((function(){i.abortInternal(),r.onTimeout(n,e,i.response)}),t.timeout),self.fetch(this.request).then((function(r){if(i.response=i.loader=r,!r.ok){var s=r.status,u=r.statusText;throw new Ct(u||"fetch, bad network response",s,r)}return n.loading.first=Math.max(self.performance.now(),n.loading.start),n.total=parseInt(r.headers.get("Content-Length")||"0"),o&&(0,a.isFiniteNumber)(t.highWaterMark)?i.loadProgressively(r,n,e,t.highWaterMark,o):l?r.arrayBuffer():r.text()})).then((function(s){var l=i.response;self.clearTimeout(i.requestTimeout),n.loading.end=Math.max(self.performance.now(),n.loading.first);var d=s[u];d&&(n.loaded=n.total=d);var h={url:l.url,data:s};o&&!(0,a.isFiniteNumber)(t.highWaterMark)&&o(n,e,s,l),r.onSuccess(h,n,e,l)})).catch((function(t){if(self.clearTimeout(i.requestTimeout),!n.aborted){var a=t&&t.code||0,s=t?t.message:null;r.onError({code:a,text:s},e,t?t.details:null)}}))},t.getCacheAge=function(){var e=null;if(this.response){var t=this.response.headers.get("age");e=t?parseFloat(t):null}return e},t.loadProgressively=function(e,t,r,i,n){void 0===i&&(i=0);var a=new Lt,s=e.body.getReader();return function o(){return s.read().then((function(s){if(s.done)return a.dataLength&&n(t,r,a.flush(),e),Promise.resolve(new ArrayBuffer(0));var l=s.value,u=l.length;return t.loaded+=u,u<i||a.dataLength?(a.push(l),a.dataLength>=i&&n(t,r,a.flush(),e)):n(t,r,l,e),o()})).catch((function(){return Promise.reject()}))}()},e}();function wt(e,t){return new self.Request(e.url,t)}var Ct=function(e){var t,r;function i(t,r,i){var n;return(n=e.call(this,t)||this).code=void 0,n.details=void 0,n.code=r,n.details=i,n}return r=e,(t=i).prototype=Object.create(r.prototype),t.prototype.constructor=t,kt(t,r),i}(At(Error));const Pt=It;var Ot;!function(e){e.WIDEVINE="com.widevine.alpha",e.PLAYREADY="com.microsoft.playready"}(Ot||(Ot={}));var Ft="undefined"!=typeof self&&self.navigator&&self.navigator.requestMediaKeySystemAccess?self.navigator.requestMediaKeySystemAccess.bind(self.navigator):null;function Mt(){return Mt=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e},Mt.apply(this,arguments)}function Nt(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,i)}return r}function Ut(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?Nt(Object(r),!0).forEach((function(t){Bt(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):Nt(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function Bt(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var Gt=Ut(Ut({autoStartLoad:!0,startPosition:-1,defaultAudioCodec:void 0,debug:!1,capLevelOnFPSDrop:!1,capLevelToPlayerSize:!1,ignoreDevicePixelRatio:!1,initialLiveManifestSize:1,maxBufferLength:30,backBufferLength:1/0,maxBufferSize:6e7,maxBufferHole:.1,highBufferWatchdogPeriod:2,nudgeOffset:.1,nudgeMaxRetry:3,maxFragLookUpTolerance:.25,liveSyncDurationCount:3,liveMaxLatencyDurationCount:1/0,liveSyncDuration:void 0,liveMaxLatencyDuration:void 0,maxLiveSyncPlaybackRate:1,liveDurationInfinity:!1,liveBackBufferLength:null,maxMaxBufferLength:600,enableWorker:!0,enableSoftwareAES:!0,manifestLoadingTimeOut:1e4,manifestLoadingMaxRetry:1,manifestLoadingRetryDelay:1e3,manifestLoadingMaxRetryTimeout:64e3,startLevel:void 0,levelLoadingTimeOut:1e4,levelLoadingMaxRetry:4,levelLoadingRetryDelay:1e3,levelLoadingMaxRetryTimeout:64e3,fragLoadingTimeOut:2e4,fragLoadingMaxRetry:6,fragLoadingRetryDelay:1e3,fragLoadingMaxRetryTimeout:64e3,startFragPrefetch:!1,fpsDroppedMonitoringPeriod:5e3,fpsDroppedMonitoringThreshold:.2,appendErrorMaxRetry:3,loader:bt,fLoader:void 0,pLoader:void 0,xhrSetup:void 0,licenseXhrSetup:void 0,licenseResponseCallback:void 0,abrController:dt,bufferController:pt,capLevelController:yt,fpsController:Et,stretchShortVideoTrack:!1,maxAudioFramesDrift:1,forceKeyFrameOnDiscontinuity:!0,abrEwmaFastLive:3,abrEwmaSlowLive:9,abrEwmaFastVoD:3,abrEwmaSlowVoD:9,abrEwmaDefaultEstimate:5e5,abrBandWidthFactor:.95,abrBandWidthUpFactor:.7,abrMaxWithRealBitrate:!1,maxStarvationDelay:4,maxLoadingDelay:4,minAutoBitrate:0,emeEnabled:!1,widevineLicenseUrl:void 0,drmSystemOptions:{},requestMediaKeySystemAccessFunc:Ft,testBandwidth:!0,progressive:!1,lowLatencyMode:!0,cmcd:void 0,enableDateRangeMetadataCues:!0,enableEmsgMetadataCues:!0,enableID3MetadataCues:!0},{cueHandler:ft(),enableWebVTT:!1,enableIMSC1:!1,enableCEA708Captions:!1,captionsTextTrack1Label:"English",captionsTextTrack1LanguageCode:"en",captionsTextTrack2Label:"Spanish",captionsTextTrack2LanguageCode:"es",captionsTextTrack3Label:"Unknown CC",captionsTextTrack3LanguageCode:"",captionsTextTrack4Label:"Unknown CC",captionsTextTrack4LanguageCode:"",renderTextTracksNatively:!0}),{},{subtitleStreamController:void 0,subtitleTrackController:void 0,timelineController:void 0,audioStreamController:void 0,audioTrackController:void 0,emeController:void 0,cmcdController:void 0});function Vt(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}var Ht=function(){function e(t){void 0===t&&(t={}),this.config=void 0,this.userConfig=void 0,this.coreComponents=void 0,this.networkControllers=void 0,this._emitter=new et.EventEmitter,this._autoLevelCapping=void 0,this.abrController=void 0,this.bufferController=void 0,this.capLevelController=void 0,this.latencyController=void 0,this.levelController=void 0,this.streamController=void 0,this.audioTrackController=void 0,this.subtitleTrackController=void 0,this.emeController=void 0,this.cmcdController=void 0,this._media=null,this.url=null;var r=this.config=function(e,t){if((t.liveSyncDurationCount||t.liveMaxLatencyDurationCount)&&(t.liveSyncDuration||t.liveMaxLatencyDuration))throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");if(void 0!==t.liveMaxLatencyDurationCount&&(void 0===t.liveSyncDurationCount||t.liveMaxLatencyDurationCount<=t.liveSyncDurationCount))throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');if(void 0!==t.liveMaxLatencyDuration&&(void 0===t.liveSyncDuration||t.liveMaxLatencyDuration<=t.liveSyncDuration))throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');return Mt({},e,t)}(e.DefaultConfig,t);this.userConfig=t,(0,l.enableLogs)(r.debug,"Hls instance"),this._autoLevelCapping=-1,r.progressive&&function(e){var t=e.loader;t!==Pt&&t!==bt?(l.logger.log("[config]: Custom loader detected, cannot enable progressive streaming"),e.progressive=!1):function(){if(self.fetch&&self.AbortController&&self.ReadableStream&&self.Request)try{return new self.ReadableStream({}),!0}catch(e){}return!1}()&&(e.loader=Pt,e.progressive=!0,e.enableSoftwareAES=!0,l.logger.log("[config]: Progressive streaming enabled, using FetchLoader"))}(r);var i=r.abrController,n=r.bufferController,a=r.capLevelController,s=r.fpsController,o=this.abrController=new i(this),u=this.bufferController=new n(this),d=this.capLevelController=new a(this),h=new s(this),f=new O(this),c=new F(this),v=new H(this),g=this.levelController=new oe(this),p=new le(this),m=this.streamController=new st(this,p);d.setStreamController(m),h.setStreamController(m);var y=[f,c,g,m];this.networkControllers=y;var E=[o,u,d,h,v,p];this.audioTrackController=this.createController(r.audioTrackController,null,y),this.createController(r.audioStreamController,p,y),this.subtitleTrackController=this.createController(r.subtitleTrackController,null,y),this.createController(r.subtitleStreamController,p,y),this.createController(r.timelineController,null,E),this.emeController=this.createController(r.emeController,null,E),this.cmcdController=this.createController(r.cmcdController,null,E),this.latencyController=this.createController(W,null,E),this.coreComponents=E}e.isSupported=function(){return function(){var e=We();if(!e)return!1;var t=Ye(),r=e&&"function"==typeof e.isTypeSupported&&e.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"'),i=!t||t.prototype&&"function"==typeof t.prototype.appendBuffer&&"function"==typeof t.prototype.remove;return!!r&&!!i}()};var t,r,i,a=e.prototype;return a.createController=function(e,t,r){if(e){var i=t?new e(this,t):new e(this);return r&&r.push(i),i}return null},a.on=function(e,t,r){void 0===r&&(r=this),this._emitter.on(e,t,r)},a.once=function(e,t,r){void 0===r&&(r=this),this._emitter.once(e,t,r)},a.removeAllListeners=function(e){this._emitter.removeAllListeners(e)},a.off=function(e,t,r,i){void 0===r&&(r=this),this._emitter.off(e,t,r,i)},a.listeners=function(e){return this._emitter.listeners(e)},a.emit=function(e,t,r){return this._emitter.emit(e,t,r)},a.trigger=function(e,t){if(this.config.debug)return this.emit(e,e,t);try{return this.emit(e,e,t)}catch(t){l.logger.error("An internal error happened while handling event "+e+'. Error message: "'+t.message+'". Here is a stacktrace:',t),this.trigger(s.Events.ERROR,{type:o.ErrorTypes.OTHER_ERROR,details:o.ErrorDetails.INTERNAL_EXCEPTION,fatal:!1,event:e,error:t})}return!1},a.listenerCount=function(e){return this._emitter.listenerCount(e)},a.destroy=function(){l.logger.log("destroy"),this.trigger(s.Events.DESTROYING,void 0),this.detachMedia(),this.removeAllListeners(),this._autoLevelCapping=-1,this.url=null,this.networkControllers.forEach((function(e){return e.destroy()})),this.networkControllers.length=0,this.coreComponents.forEach((function(e){return e.destroy()})),this.coreComponents.length=0},a.attachMedia=function(e){l.logger.log("attachMedia"),this._media=e,this.trigger(s.Events.MEDIA_ATTACHING,{media:e})},a.detachMedia=function(){l.logger.log("detachMedia"),this.trigger(s.Events.MEDIA_DETACHING,void 0),this._media=null},a.loadSource=function(e){this.stopLoad();var t=this.media,r=this.url,i=this.url=n.buildAbsoluteURL(self.location.href,e,{alwaysNormalize:!0});l.logger.log("loadSource:"+i),t&&r&&r!==i&&this.bufferController.hasSourceTypes()&&(this.detachMedia(),this.attachMedia(t)),this.trigger(s.Events.MANIFEST_LOADING,{url:e})},a.startLoad=function(e){void 0===e&&(e=-1),l.logger.log("startLoad("+e+")"),this.networkControllers.forEach((function(t){t.startLoad(e)}))},a.stopLoad=function(){l.logger.log("stopLoad"),this.networkControllers.forEach((function(e){e.stopLoad()}))},a.swapAudioCodec=function(){l.logger.log("swapAudioCodec"),this.streamController.swapAudioCodec()},a.recoverMediaError=function(){l.logger.log("recoverMediaError");var e=this._media;this.detachMedia(),e&&this.attachMedia(e)},a.removeLevel=function(e,t){void 0===t&&(t=0),this.levelController.removeLevel(e,t)},t=e,i=[{key:"version",get:function(){return"1.2.9"}},{key:"Events",get:function(){return s.Events}},{key:"ErrorTypes",get:function(){return o.ErrorTypes}},{key:"ErrorDetails",get:function(){return o.ErrorDetails}},{key:"DefaultConfig",get:function(){return e.defaultConfig?e.defaultConfig:Gt},set:function(t){e.defaultConfig=t}}],(r=[{key:"levels",get:function(){return this.levelController.levels||[]}},{key:"currentLevel",get:function(){return this.streamController.currentLevel},set:function(e){l.logger.log("set currentLevel:"+e),this.loadLevel=e,this.abrController.clearTimer(),this.streamController.immediateLevelSwitch()}},{key:"nextLevel",get:function(){return this.streamController.nextLevel},set:function(e){l.logger.log("set nextLevel:"+e),this.levelController.manualLevel=e,this.streamController.nextLevelSwitch()}},{key:"loadLevel",get:function(){return this.levelController.level},set:function(e){l.logger.log("set loadLevel:"+e),this.levelController.manualLevel=e}},{key:"nextLoadLevel",get:function(){return this.levelController.nextLoadLevel},set:function(e){this.levelController.nextLoadLevel=e}},{key:"firstLevel",get:function(){return Math.max(this.levelController.firstLevel,this.minAutoLevel)},set:function(e){l.logger.log("set firstLevel:"+e),this.levelController.firstLevel=e}},{key:"startLevel",get:function(){return this.levelController.startLevel},set:function(e){l.logger.log("set startLevel:"+e),-1!==e&&(e=Math.max(e,this.minAutoLevel)),this.levelController.startLevel=e}},{key:"capLevelToPlayerSize",get:function(){return this.config.capLevelToPlayerSize},set:function(e){var t=!!e;t!==this.config.capLevelToPlayerSize&&(t?this.capLevelController.startCapping():(this.capLevelController.stopCapping(),this.autoLevelCapping=-1,this.streamController.nextLevelSwitch()),this.config.capLevelToPlayerSize=t)}},{key:"autoLevelCapping",get:function(){return this._autoLevelCapping},set:function(e){this._autoLevelCapping!==e&&(l.logger.log("set autoLevelCapping:"+e),this._autoLevelCapping=e)}},{key:"bandwidthEstimate",get:function(){var e=this.abrController.bwEstimator;return e?e.getEstimate():NaN}},{key:"autoLevelEnabled",get:function(){return-1===this.levelController.manualLevel}},{key:"manualLevel",get:function(){return this.levelController.manualLevel}},{key:"minAutoLevel",get:function(){var e=this.levels,t=this.config.minAutoBitrate;if(!e)return 0;for(var r=e.length,i=0;i<r;i++)if(e[i].maxBitrate>=t)return i;return 0}},{key:"maxAutoLevel",get:function(){var e=this.levels,t=this.autoLevelCapping;return-1===t&&e&&e.length?e.length-1:t}},{key:"nextAutoLevel",get:function(){return Math.min(Math.max(this.abrController.nextAutoLevel,this.minAutoLevel),this.maxAutoLevel)},set:function(e){this.abrController.nextAutoLevel=Math.max(this.minAutoLevel,e)}},{key:"playingDate",get:function(){return this.streamController.currentProgramDateTime}},{key:"mainForwardBufferInfo",get:function(){return this.streamController.getMainFwdBufferInfo()}},{key:"audioTracks",get:function(){var e=this.audioTrackController;return e?e.audioTracks:[]}},{key:"audioTrack",get:function(){var e=this.audioTrackController;return e?e.audioTrack:-1},set:function(e){var t=this.audioTrackController;t&&(t.audioTrack=e)}},{key:"subtitleTracks",get:function(){var e=this.subtitleTrackController;return e?e.subtitleTracks:[]}},{key:"subtitleTrack",get:function(){var e=this.subtitleTrackController;return e?e.subtitleTrack:-1},set:function(e){var t=this.subtitleTrackController;t&&(t.subtitleTrack=e)}},{key:"media",get:function(){return this._media}},{key:"subtitleDisplay",get:function(){var e=this.subtitleTrackController;return!!e&&e.subtitleDisplay},set:function(e){var t=this.subtitleTrackController;t&&(t.subtitleDisplay=e)}},{key:"lowLatencyMode",get:function(){return this.config.lowLatencyMode},set:function(e){this.config.lowLatencyMode=e}},{key:"liveSyncPosition",get:function(){return this.latencyController.liveSyncPosition}},{key:"latency",get:function(){return this.latencyController.latency}},{key:"maxLatency",get:function(){return this.latencyController.maxLatency}},{key:"targetLatency",get:function(){return this.latencyController.targetLatency}},{key:"drift",get:function(){return this.latencyController.drift}},{key:"forceStartLoad",get:function(){return this.streamController.forceStartLoad}}])&&Vt(t.prototype,r),i&&Vt(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();Ht.defaultConfig=void 0},923:(e,t,r)=>{"use strict";r.r(t),r.d(t,{BaseSegment:()=>c,ElementaryStreamTypes:()=>i,Fragment:()=>v,Part:()=>g});var i,n=r(965),a=r(945),s=r(93),o=r(960),l=r(408);function u(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,d(e,t)}function d(e,t){return d=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},d(e,t)}function h(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function f(e,t,r){return t&&h(e.prototype,t),r&&h(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}!function(e){e.AUDIO="audio",e.VIDEO="video",e.AUDIOVIDEO="audiovideo"}(i||(i={}));var c=function(){function e(e){var t;this._byteRange=null,this._url=null,this.baseurl=void 0,this.relurl=void 0,this.elementaryStreams=((t={})[i.AUDIO]=null,t[i.VIDEO]=null,t[i.AUDIOVIDEO]=null,t),this.baseurl=e}return e.prototype.setByteRange=function(e,t){var r=e.split("@",2),i=[];1===r.length?i[0]=t?t.byteRangeEndOffset:0:i[0]=parseInt(r[1]),i[1]=parseInt(r[0])+i[0],this._byteRange=i},f(e,[{key:"byteRange",get:function(){return this._byteRange?this._byteRange:[]}},{key:"byteRangeStartOffset",get:function(){return this.byteRange[0]}},{key:"byteRangeEndOffset",get:function(){return this.byteRange[1]}},{key:"url",get:function(){return!this._url&&this.baseurl&&this.relurl&&(this._url=(0,a.buildAbsoluteURL)(this.baseurl,this.relurl,{alwaysNormalize:!0})),this._url||""},set:function(e){this._url=e}}]),e}(),v=function(e){function t(t,r){var i;return(i=e.call(this,r)||this)._decryptdata=null,i.rawProgramDateTime=null,i.programDateTime=null,i.tagList=[],i.duration=0,i.sn=0,i.levelkey=void 0,i.type=void 0,i.loader=null,i.level=-1,i.cc=0,i.startPTS=void 0,i.endPTS=void 0,i.appendedPTS=void 0,i.startDTS=void 0,i.endDTS=void 0,i.start=0,i.deltaPTS=void 0,i.maxStartPTS=void 0,i.minEndPTS=void 0,i.stats=new l.LoadStats,i.urlId=0,i.data=void 0,i.bitrateTest=!1,i.title=null,i.initSegment=null,i.type=t,i}u(t,e);var r=t.prototype;return r.createInitializationVector=function(e){for(var t=new Uint8Array(16),r=12;r<16;r++)t[r]=e>>8*(15-r)&255;return t},r.setDecryptDataFromLevelKey=function(e,t){var r=e;return"AES-128"===(null==e?void 0:e.method)&&e.uri&&!e.iv&&((r=o.LevelKey.fromURI(e.uri)).method=e.method,r.iv=this.createInitializationVector(t),r.keyFormat="identity"),r},r.setElementaryStreamInfo=function(e,t,r,i,n,a){void 0===a&&(a=!1);var s=this.elementaryStreams,o=s[e];o?(o.startPTS=Math.min(o.startPTS,t),o.endPTS=Math.max(o.endPTS,r),o.startDTS=Math.min(o.startDTS,i),o.endDTS=Math.max(o.endDTS,n)):s[e]={startPTS:t,endPTS:r,startDTS:i,endDTS:n,partial:a}},r.clearElementaryStreamInfo=function(){var e=this.elementaryStreams;e[i.AUDIO]=null,e[i.VIDEO]=null,e[i.AUDIOVIDEO]=null},f(t,[{key:"decryptdata",get:function(){if(!this.levelkey&&!this._decryptdata)return null;if(!this._decryptdata&&this.levelkey){var e=this.sn;"number"!=typeof e&&(this.levelkey&&"AES-128"===this.levelkey.method&&!this.levelkey.iv&&s.logger.warn('missing IV for initialization segment with method="'+this.levelkey.method+'" - compliance issue'),e=0),this._decryptdata=this.setDecryptDataFromLevelKey(this.levelkey,e)}return this._decryptdata}},{key:"end",get:function(){return this.start+this.duration}},{key:"endProgramDateTime",get:function(){if(null===this.programDateTime)return null;if(!(0,n.isFiniteNumber)(this.programDateTime))return null;var e=(0,n.isFiniteNumber)(this.duration)?this.duration:0;return this.programDateTime+1e3*e}},{key:"encrypted",get:function(){var e;return!(null===(e=this.decryptdata)||void 0===e||!e.keyFormat||!this.decryptdata.uri)}}]),t}(c),g=function(e){function t(t,r,i,n,a){var s;(s=e.call(this,i)||this).fragOffset=0,s.duration=0,s.gap=!1,s.independent=!1,s.relurl=void 0,s.fragment=void 0,s.index=void 0,s.stats=new l.LoadStats,s.duration=t.decimalFloatingPoint("DURATION"),s.gap=t.bool("GAP"),s.independent=t.bool("INDEPENDENT"),s.relurl=t.enumeratedString("URI"),s.fragment=r,s.index=n;var o=t.enumeratedString("BYTERANGE");return o&&s.setByteRange(o,a),a&&(s.fragOffset=a.fragOffset+a.duration),s}return u(t,e),f(t,[{key:"start",get:function(){return this.fragment.start+this.fragOffset}},{key:"end",get:function(){return this.start+this.duration}},{key:"loaded",get:function(){var e=this.elementaryStreams;return!!(e.audio||e.video||e.audiovideo)}}]),t}(c)},960:(e,t,r)=>{"use strict";r.r(t),r.d(t,{LevelKey:()=>a});var i=r(945);function n(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}var a=function(){function e(e,t){this._uri=null,this.method=null,this.keyFormat=null,this.keyFormatVersions=null,this.keyID=null,this.key=null,this.iv=null,this._uri=t?(0,i.buildAbsoluteURL)(e,t,{alwaysNormalize:!0}):e}var t,r;return e.fromURL=function(t,r){return new e(t,r)},e.fromURI=function(t){return new e(t)},t=e,(r=[{key:"uri",get:function(){return this._uri}}])&&n(t.prototype,r),Object.defineProperty(t,"prototype",{writable:!1}),e}()},408:(e,t,r)=>{"use strict";r.r(t),r.d(t,{LoadStats:()=>i});var i=function(){this.aborted=!1,this.loaded=0,this.retry=0,this.total=0,this.chunkCount=0,this.bwEstimate=0,this.loading={start:0,first:0,end:0},this.parsing={start:0,end:0},this.buffering={start:0,first:0,end:0}}},965:(e,t,r)=>{"use strict";r.r(t),r.d(t,{MAX_SAFE_INTEGER:()=>n,isFiniteNumber:()=>i});var i=Number.isFinite||function(e){return"number"==typeof e&&isFinite(e)},n=Number.MAX_SAFE_INTEGER||9007199254740991},856:(e,t,r)=>{"use strict";var i;r.r(t),r.d(t,{MetadataSchema:()=>i}),function(e){e.audioId3="org.id3",e.dateRange="com.apple.quicktime.HLS",e.emsg="https://aomedia.org/emsg/ID3"}(i||(i={}))},308:(e,t,r)=>{"use strict";var i,n;r.r(t),r.d(t,{PlaylistContextType:()=>i,PlaylistLevelType:()=>n}),function(e){e.MANIFEST="manifest",e.LEVEL="level",e.AUDIO_TRACK="audioTrack",e.SUBTITLE_TRACK="subtitleTrack"}(i||(i={})),function(e){e.MAIN="main",e.AUDIO="audio",e.SUBTITLE="subtitle"}(n||(n={}))},93:(e,t,r)=>{"use strict";r.r(t),r.d(t,{enableLogs:()=>o,logger:()=>l});var i=function(){},n={trace:i,debug:i,log:i,warn:i,info:i,error:i},a=n;function s(e){var t=self.console[e];return t?t.bind(self.console,"["+e+"] >"):i}function o(e,t){if(self.console&&!0===e||"object"==typeof e){!function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),i=1;i<t;i++)r[i-1]=arguments[i];r.forEach((function(t){a[t]=e[t]?e[t].bind(e):s(t)}))}(e,"debug","log","info","warn","error");try{a.log('Debug logs enabled for "'+t+'"')}catch(e){a=n}}else a=n}var l=n},63:(e,t,r)=>{"use strict";r.r(t),r.d(t,{RemuxerTrackIdConfig:()=>l,appendUint8Array:()=>b,bin2str:()=>u,computeRawDurationFromSamples:()=>E,discardEPB:()=>R,findBox:()=>v,getDuration:()=>y,getStartDTS:()=>m,offsetStartDTS:()=>T,parseEmsg:()=>k,parseInitSegment:()=>p,parseSEIMessageFromNALu:()=>D,parseSamples:()=>L,parseSegmentIndex:()=>g,readSint32:()=>f,readUint16:()=>d,readUint32:()=>h,segmentValidRange:()=>S,writeUint32:()=>c});var i=r(145),n=r(923),a=r(181),s=Math.pow(2,32)-1,o=[].push,l={video:1,audio:2,id3:3,text:4};function u(e){return String.fromCharCode.apply(null,e)}function d(e,t){var r=e[t]<<8|e[t+1];return r<0?65536+r:r}function h(e,t){var r=f(e,t);return r<0?4294967296+r:r}function f(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]}function c(e,t,r){e[t]=r>>24,e[t+1]=r>>16&255,e[t+2]=r>>8&255,e[t+3]=255&r}function v(e,t){var r=[];if(!t.length)return r;for(var i=e.byteLength,n=0;n<i;){var a=h(e,n),s=a>1?n+a:i;if(u(e.subarray(n+4,n+8))===t[0])if(1===t.length)r.push(e.subarray(n+8,s));else{var l=v(e.subarray(n+8,s),t.slice(1));l.length&&o.apply(r,l)}n=s}return r}function g(e){var t=[],r=e[0],i=8,n=h(e,i);i+=4,i+=0===r?8:16,i+=2;var a=e.length+0,s=d(e,i);i+=2;for(var o=0;o<s;o++){var l=i,u=h(e,l);l+=4;var f=2147483647&u;if(1==(2147483648&u)>>>31)return console.warn("SIDX has hierarchical references (not supported)"),null;var c=h(e,l);l+=4,t.push({referenceSize:f,subsegmentDuration:c,info:{duration:c/n,start:a,end:a+f-1}}),a+=f,i=l+=4}return{earliestPresentationTime:0,timescale:n,version:r,referencesCount:s,references:t}}function p(e){for(var t=[],r=v(e,["moov","trak"]),i=0;i<r.length;i++){var a=r[i],s=v(a,["tkhd"])[0];if(s){var o=s[0],l=0===o?12:20,d=h(s,l),f=v(a,["mdia","mdhd"])[0];if(f){var c=h(f,l=0===(o=f[0])?12:20),g=v(a,["mdia","hdlr"])[0];if(g){var p=u(g.subarray(8,12)),m={soun:n.ElementaryStreamTypes.AUDIO,vide:n.ElementaryStreamTypes.VIDEO}[p];if(m){var y=v(a,["mdia","minf","stbl","stsd"])[0],E=void 0;y&&(E=u(y.subarray(12,16))),t[d]={timescale:c,type:m},t[m]={timescale:c,id:d,codec:E}}}}}}return v(e,["moov","mvex","trex"]).forEach((function(e){var r=h(e,4),i=t[r];i&&(i.default={duration:h(e,12),flags:h(e,20)})})),t}function m(e,t){return v(t,["moof","traf"]).reduce((function(t,r){var i=v(r,["tfdt"])[0],n=i[0],a=v(r,["tfhd"]).reduce((function(t,r){var a=h(r,4),s=e[a];if(s){var o=h(i,4);1===n&&(o*=Math.pow(2,32),o+=h(i,8));var l=o/(s.timescale||9e4);if(isFinite(l)&&(null===t||l<t))return l}return t}),null);return null!==a&&isFinite(a)&&(null===t||a<t)?a:t}),null)||0}function y(e,t){for(var r=0,i=0,a=0,s=v(e,["moof","traf"]),o=0;o<s.length;o++){var l=s[o],u=v(l,["tfhd"])[0],d=t[h(u,4)];if(d){var f=d.default,c=h(u,0)|(null==f?void 0:f.flags),p=null==f?void 0:f.duration;8&c&&(p=h(u,2&c?12:8));for(var m=d.timescale||9e4,y=v(l,["trun"]),T=0;T<y.length;T++)!(r=E(y[T]))&&p&&(r=p*h(y[T],4)),d.type===n.ElementaryStreamTypes.VIDEO?i+=r/m:d.type===n.ElementaryStreamTypes.AUDIO&&(a+=r/m)}}if(0===i&&0===a){for(var S=0,b=v(e,["sidx"]),L=0;L<b.length;L++){var A=g(b[L]);null!=A&&A.references&&(S+=A.references.reduce((function(e,t){return e+t.info.duration||0}),0))}return S}return i||a}function E(e){var t=h(e,0),r=8;1&t&&(r+=4),4&t&&(r+=4);for(var i=0,n=h(e,4),a=0;a<n;a++)256&t&&(i+=h(e,r),r+=4),512&t&&(r+=4),1024&t&&(r+=4),2048&t&&(r+=4);return i}function T(e,t,r){v(t,["moof","traf"]).forEach((function(t){v(t,["tfhd"]).forEach((function(i){var n=h(i,4),a=e[n];if(a){var o=a.timescale||9e4;v(t,["tfdt"]).forEach((function(e){var t=e[0],i=h(e,4);if(0===t)i-=r*o,c(e,4,i=Math.max(i,0));else{i*=Math.pow(2,32),i+=h(e,8),i-=r*o,i=Math.max(i,0);var n=Math.floor(i/(s+1)),a=Math.floor(i%(s+1));c(e,4,n),c(e,8,a)}}))}}))}))}function S(e){var t={valid:null,remainder:null},r=v(e,["moof"]);if(!r)return t;if(r.length<2)return t.remainder=e,t;var n=r[r.length-1];return t.valid=(0,i.sliceUint8)(e,0,n.byteOffset-8),t.remainder=(0,i.sliceUint8)(e,n.byteOffset-8),t}function b(e,t){var r=new Uint8Array(e.length+t.length);return r.set(e),r.set(t,e.length),r}function L(e,t){var r=[],i=t.samples,a=t.timescale,s=t.id,o=!1;return v(i,["moof"]).map((function(l){var u=l.byteOffset-8;v(l,["traf"]).map((function(l){var d=v(l,["tfdt"]).map((function(e){var t=e[0],r=h(e,4);return 1===t&&(r*=Math.pow(2,32),r+=h(e,8)),r/a}))[0];return void 0!==d&&(e=d),v(l,["tfhd"]).map((function(d){var c=h(d,4),g=16777215&h(d,0),p=0,m=0!=(16&g),y=0,E=0!=(32&g),T=8;c===s&&(0!=(1&g)&&(T+=8),0!=(2&g)&&(T+=4),0!=(8&g)&&(p=h(d,T),T+=4),m&&(y=h(d,T),T+=4),E&&(T+=4),"video"===t.type&&(o=function(e){if(!e)return!1;var t=e.indexOf("."),r=t<0?e:e.substring(0,t);return"hvc1"===r||"hev1"===r||"dvh1"===r||"dvhe"===r}(t.codec)),v(l,["trun"]).map((function(s){var l=s[0],d=16777215&h(s,0),c=0!=(1&d),v=0,g=0!=(4&d),m=0!=(256&d),E=0,T=0!=(512&d),S=0,b=0!=(1024&d),L=0!=(2048&d),R=0,k=h(s,4),_=8;c&&(v=h(s,_),_+=4),g&&(_+=4);for(var x=v+u,I=0;I<k;I++){if(m?(E=h(s,_),_+=4):E=p,T?(S=h(s,_),_+=4):S=y,b&&(_+=4),L&&(R=0===l?h(s,_):f(s,_),_+=4),t.type===n.ElementaryStreamTypes.VIDEO)for(var w=0;w<S;){var C=h(i,x);A(o,i[x+=4])&&D(i.subarray(x,x+C),o?2:1,e+R/a,r),x+=C,w+=C+4}e+=E/a}})))}))}))})),r}function A(e,t){if(e){var r=t>>1&63;return 39===r||40===r}return 6==(31&t)}function D(e,t,r,i){var n=R(e),s=0;s+=t;for(var o=0,l=0,u=!1,f=0;s<n.length;){o=0;do{if(s>=n.length)break;o+=f=n[s++]}while(255===f);l=0;do{if(s>=n.length)break;l+=f=n[s++]}while(255===f);var c=n.length-s;if(!u&&4===o&&s<n.length){if(u=!0,181===n[s++]){var v=d(n,s);if(s+=2,49===v){var g=h(n,s);if(s+=4,1195456820===g){var p=n[s++];if(3===p){var m=n[s++],y=64&m,E=y?2+3*(31&m):0,T=new Uint8Array(E);if(y){T[0]=m;for(var S=1;S<E;S++)T[S]=n[s++]}i.push({type:p,payloadType:o,pts:r,bytes:T})}}}}}else if(5===o&&l<c){if(u=!0,l>16){for(var b=[],L=0;L<16;L++){var A=n[s++].toString(16);b.push(1==A.length?"0"+A:A),3!==L&&5!==L&&7!==L&&9!==L||b.push("-")}for(var D=l-16,k=new Uint8Array(D),_=0;_<D;_++)k[_]=n[s++];i.push({payloadType:o,pts:r,uuid:b.join(""),userData:(0,a.utf8ArrayToStr)(k),userDataBytes:k})}}else if(l<c)s+=l;else if(l>c)break}}function R(e){for(var t=e.byteLength,r=[],i=1;i<t-2;)0===e[i]&&0===e[i+1]&&3===e[i+2]?(r.push(i+2),i+=2):i++;if(0===r.length)return e;var n=t-r.length,a=new Uint8Array(n),s=0;for(i=0;i<n;s++,i++)s===r[0]&&(s++,r.shift()),a[i]=e[s];return a}function k(e){var t=e[0],r="",i="",n=0,a=0,s=0,o=0,l=0,d=0;if(0===t){for(;"\0"!==u(e.subarray(d,d+1));)r+=u(e.subarray(d,d+1)),d+=1;for(r+=u(e.subarray(d,d+1)),d+=1;"\0"!==u(e.subarray(d,d+1));)i+=u(e.subarray(d,d+1)),d+=1;i+=u(e.subarray(d,d+1)),d+=1,n=h(e,12),a=h(e,16),o=h(e,20),l=h(e,24),d=28}else if(1===t){n=h(e,d+=4);var f=h(e,d+=4),c=h(e,d+=4);for(d+=4,s=Math.pow(2,32)*f+c,Number.isSafeInteger(s)||(s=Number.MAX_SAFE_INTEGER,console.warn("Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box")),o=h(e,d),l=h(e,d+=4),d+=4;"\0"!==u(e.subarray(d,d+1));)r+=u(e.subarray(d,d+1)),d+=1;for(r+=u(e.subarray(d,d+1)),d+=1;"\0"!==u(e.subarray(d,d+1));)i+=u(e.subarray(d,d+1)),d+=1;i+=u(e.subarray(d,d+1)),d+=1}return{schemeIdUri:r,value:i,timeScale:n,presentationTime:s,presentationTimeDelta:a,eventDuration:o,id:l,payload:e.subarray(d,e.byteLength)}}},145:(e,t,r)=>{"use strict";function i(e,t,r){return Uint8Array.prototype.slice?e.slice(t,r):new Uint8Array(Array.prototype.slice.call(e,t,r))}r.r(t),r.d(t,{sliceUint8:()=>i})},729:e=>{"use strict";var t=Object.prototype.hasOwnProperty,r="~";function i(){}function n(e,t,r){this.fn=e,this.context=t,this.once=r||!1}function a(e,t,i,a,s){if("function"!=typeof i)throw new TypeError("The listener must be a function");var o=new n(i,a||e,s),l=r?r+t:t;return e._events[l]?e._events[l].fn?e._events[l]=[e._events[l],o]:e._events[l].push(o):(e._events[l]=o,e._eventsCount++),e}function s(e,t){0==--e._eventsCount?e._events=new i:delete e._events[t]}function o(){this._events=new i,this._eventsCount=0}Object.create&&(i.prototype=Object.create(null),(new i).__proto__||(r=!1)),o.prototype.eventNames=function(){var e,i,n=[];if(0===this._eventsCount)return n;for(i in e=this._events)t.call(e,i)&&n.push(r?i.slice(1):i);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(e)):n},o.prototype.listeners=function(e){var t=r?r+e:e,i=this._events[t];if(!i)return[];if(i.fn)return[i.fn];for(var n=0,a=i.length,s=new Array(a);n<a;n++)s[n]=i[n].fn;return s},o.prototype.listenerCount=function(e){var t=r?r+e:e,i=this._events[t];return i?i.fn?1:i.length:0},o.prototype.emit=function(e,t,i,n,a,s){var o=r?r+e:e;if(!this._events[o])return!1;var l,u,d=this._events[o],h=arguments.length;if(d.fn){switch(d.once&&this.removeListener(e,d.fn,void 0,!0),h){case 1:return d.fn.call(d.context),!0;case 2:return d.fn.call(d.context,t),!0;case 3:return d.fn.call(d.context,t,i),!0;case 4:return d.fn.call(d.context,t,i,n),!0;case 5:return d.fn.call(d.context,t,i,n,a),!0;case 6:return d.fn.call(d.context,t,i,n,a,s),!0}for(u=1,l=new Array(h-1);u<h;u++)l[u-1]=arguments[u];d.fn.apply(d.context,l)}else{var f,c=d.length;for(u=0;u<c;u++)switch(d[u].once&&this.removeListener(e,d[u].fn,void 0,!0),h){case 1:d[u].fn.call(d[u].context);break;case 2:d[u].fn.call(d[u].context,t);break;case 3:d[u].fn.call(d[u].context,t,i);break;case 4:d[u].fn.call(d[u].context,t,i,n);break;default:if(!l)for(f=1,l=new Array(h-1);f<h;f++)l[f-1]=arguments[f];d[u].fn.apply(d[u].context,l)}}return!0},o.prototype.on=function(e,t,r){return a(this,e,t,r,!1)},o.prototype.once=function(e,t,r){return a(this,e,t,r,!0)},o.prototype.removeListener=function(e,t,i,n){var a=r?r+e:e;if(!this._events[a])return this;if(!t)return s(this,a),this;var o=this._events[a];if(o.fn)o.fn!==t||n&&!o.once||i&&o.context!==i||s(this,a);else{for(var l=0,u=[],d=o.length;l<d;l++)(o[l].fn!==t||n&&!o[l].once||i&&o[l].context!==i)&&u.push(o[l]);u.length?this._events[a]=1===u.length?u[0]:u:s(this,a)}return this},o.prototype.removeAllListeners=function(e){var t;return e?(t=r?r+e:e,this._events[t]&&s(this,t)):(this._events=new i,this._eventsCount=0),this},o.prototype.off=o.prototype.removeListener,o.prototype.addListener=o.prototype.on,o.prefixed=r,o.EventEmitter=o,e.exports=o},945:function(e){var t,r,i,n,a;t=/^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/,r=/^(?=([^\/?#]*))\1([^]*)$/,i=/(?:\/|^)\.(?=\/)/g,n=/(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g,a={buildAbsoluteURL:function(e,t,i){if(i=i||{},e=e.trim(),!(t=t.trim())){if(!i.alwaysNormalize)return e;var n=a.parseURL(e);if(!n)throw new Error("Error trying to parse base URL.");return n.path=a.normalizePath(n.path),a.buildURLFromParts(n)}var s=a.parseURL(t);if(!s)throw new Error("Error trying to parse relative URL.");if(s.scheme)return i.alwaysNormalize?(s.path=a.normalizePath(s.path),a.buildURLFromParts(s)):t;var o=a.parseURL(e);if(!o)throw new Error("Error trying to parse base URL.");if(!o.netLoc&&o.path&&"/"!==o.path[0]){var l=r.exec(o.path);o.netLoc=l[1],o.path=l[2]}o.netLoc&&!o.path&&(o.path="/");var u={scheme:o.scheme,netLoc:s.netLoc,path:null,params:s.params,query:s.query,fragment:s.fragment};if(!s.netLoc&&(u.netLoc=o.netLoc,"/"!==s.path[0]))if(s.path){var d=o.path,h=d.substring(0,d.lastIndexOf("/")+1)+s.path;u.path=a.normalizePath(h)}else u.path=o.path,s.params||(u.params=o.params,s.query||(u.query=o.query));return null===u.path&&(u.path=i.alwaysNormalize?a.normalizePath(s.path):s.path),a.buildURLFromParts(u)},parseURL:function(e){var r=t.exec(e);return r?{scheme:r[1]||"",netLoc:r[2]||"",path:r[3]||"",params:r[4]||"",query:r[5]||"",fragment:r[6]||""}:null},normalizePath:function(e){for(e=e.split("").reverse().join("").replace(i,"");e.length!==(e=e.replace(n,"")).length;);return e.split("").reverse().join("")},buildURLFromParts:function(e){return e.scheme+e.netLoc+e.path+e.params+e.query+e.fragment}},e.exports=a}},t={};function r(i){var n=t[i];if(void 0!==n)return n.exports;var a=t[i]={exports:{}};return e[i].call(a.exports,a,a.exports,r),a.exports}r.m=e,r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i=r(392);return i.default})())); -// @license-end diff --git a/public/js/hls.min.js b/public/js/hls.min.js new file mode 100644 index 0000000..b9098c5 --- /dev/null +++ b/public/js/hls.min.js @@ -0,0 +1,5 @@ +// @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0 +// @source https://github.com/video-dev/hls.js +// @version v1.5.1 +!function t(e){var r,i;r=this,i=function(){"use strict";function r(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,i)}return r}function i(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[e]?arguments[e]:{};e%2?r(Object(i),!0).forEach((function(e){var r,a,s;r=t,a=e,s=i[e],(a=n(a))in r?Object.defineProperty(r,a,{value:s,enumerable:!0,configurable:!0,writable:!0}):r[a]=s})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):r(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function n(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var r=t[Symbol.toPrimitive];if(void 0!==r){var i=r.call(t,e||"default");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}function a(t,e){for(var r=0;r<e.length;r++){var i=e[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,n(i.key),i)}}function s(t,e,r){return e&&a(t.prototype,e),r&&a(t,r),Object.defineProperty(t,"prototype",{writable:!1}),t}function o(){return o=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(t[i]=r[i])}return t},o.apply(this,arguments)}function l(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,h(t,e)}function u(t){return u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},u(t)}function h(t,e){return h=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},h(t,e)}function d(t,e,r){return d=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}()?Reflect.construct.bind():function(t,e,r){var i=[null];i.push.apply(i,e);var n=new(Function.bind.apply(t,i));return r&&h(n,r.prototype),n},d.apply(null,arguments)}function c(t){var e="function"==typeof Map?new Map:void 0;return c=function(t){if(null===t||!function(t){try{return-1!==Function.toString.call(t).indexOf("[native code]")}catch(e){return"function"==typeof t}}(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,r)}function r(){return d(t,arguments,u(this).constructor)}return r.prototype=Object.create(t.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),h(r,t)},c(t)}function f(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,i=new Array(e);r<e;r++)i[r]=t[r];return i}function g(t,e){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(r)return(r=r.call(t)).next.bind(r);if(Array.isArray(t)||(r=function(t,e){if(t){if("string"==typeof t)return f(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?f(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){r&&(t=r);var i=0;return function(){return i>=t.length?{done:!0}:{done:!1,value:t[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function v(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var m={exports:{}};!function(t,e){var r,i,n,a,s;r=/^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/,i=/^(?=([^\/?#]*))\1([^]*)$/,n=/(?:\/|^)\.(?=\/)/g,a=/(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g,s={buildAbsoluteURL:function(t,e,r){if(r=r||{},t=t.trim(),!(e=e.trim())){if(!r.alwaysNormalize)return t;var n=s.parseURL(t);if(!n)throw new Error("Error trying to parse base URL.");return n.path=s.normalizePath(n.path),s.buildURLFromParts(n)}var a=s.parseURL(e);if(!a)throw new Error("Error trying to parse relative URL.");if(a.scheme)return r.alwaysNormalize?(a.path=s.normalizePath(a.path),s.buildURLFromParts(a)):e;var o=s.parseURL(t);if(!o)throw new Error("Error trying to parse base URL.");if(!o.netLoc&&o.path&&"/"!==o.path[0]){var l=i.exec(o.path);o.netLoc=l[1],o.path=l[2]}o.netLoc&&!o.path&&(o.path="/");var u={scheme:o.scheme,netLoc:a.netLoc,path:null,params:a.params,query:a.query,fragment:a.fragment};if(!a.netLoc&&(u.netLoc=o.netLoc,"/"!==a.path[0]))if(a.path){var h=o.path,d=h.substring(0,h.lastIndexOf("/")+1)+a.path;u.path=s.normalizePath(d)}else u.path=o.path,a.params||(u.params=o.params,a.query||(u.query=o.query));return null===u.path&&(u.path=r.alwaysNormalize?s.normalizePath(a.path):a.path),s.buildURLFromParts(u)},parseURL:function(t){var e=r.exec(t);return e?{scheme:e[1]||"",netLoc:e[2]||"",path:e[3]||"",params:e[4]||"",query:e[5]||"",fragment:e[6]||""}:null},normalizePath:function(t){for(t=t.split("").reverse().join("").replace(n,"");t.length!==(t=t.replace(a,"")).length;);return t.split("").reverse().join("")},buildURLFromParts:function(t){return t.scheme+t.netLoc+t.path+t.params+t.query+t.fragment}},t.exports=s}(m);var p=m.exports,y=Number.isFinite||function(t){return"number"==typeof t&&isFinite(t)},E=Number.isSafeInteger||function(t){return"number"==typeof t&&Math.abs(t)<=T},T=Number.MAX_SAFE_INTEGER||9007199254740991,S=function(t){return t.MEDIA_ATTACHING="hlsMediaAttaching",t.MEDIA_ATTACHED="hlsMediaAttached",t.MEDIA_DETACHING="hlsMediaDetaching",t.MEDIA_DETACHED="hlsMediaDetached",t.BUFFER_RESET="hlsBufferReset",t.BUFFER_CODECS="hlsBufferCodecs",t.BUFFER_CREATED="hlsBufferCreated",t.BUFFER_APPENDING="hlsBufferAppending",t.BUFFER_APPENDED="hlsBufferAppended",t.BUFFER_EOS="hlsBufferEos",t.BUFFER_FLUSHING="hlsBufferFlushing",t.BUFFER_FLUSHED="hlsBufferFlushed",t.MANIFEST_LOADING="hlsManifestLoading",t.MANIFEST_LOADED="hlsManifestLoaded",t.MANIFEST_PARSED="hlsManifestParsed",t.LEVEL_SWITCHING="hlsLevelSwitching",t.LEVEL_SWITCHED="hlsLevelSwitched",t.LEVEL_LOADING="hlsLevelLoading",t.LEVEL_LOADED="hlsLevelLoaded",t.LEVEL_UPDATED="hlsLevelUpdated",t.LEVEL_PTS_UPDATED="hlsLevelPtsUpdated",t.LEVELS_UPDATED="hlsLevelsUpdated",t.AUDIO_TRACKS_UPDATED="hlsAudioTracksUpdated",t.AUDIO_TRACK_SWITCHING="hlsAudioTrackSwitching",t.AUDIO_TRACK_SWITCHED="hlsAudioTrackSwitched",t.AUDIO_TRACK_LOADING="hlsAudioTrackLoading",t.AUDIO_TRACK_LOADED="hlsAudioTrackLoaded",t.SUBTITLE_TRACKS_UPDATED="hlsSubtitleTracksUpdated",t.SUBTITLE_TRACKS_CLEARED="hlsSubtitleTracksCleared",t.SUBTITLE_TRACK_SWITCH="hlsSubtitleTrackSwitch",t.SUBTITLE_TRACK_LOADING="hlsSubtitleTrackLoading",t.SUBTITLE_TRACK_LOADED="hlsSubtitleTrackLoaded",t.SUBTITLE_FRAG_PROCESSED="hlsSubtitleFragProcessed",t.CUES_PARSED="hlsCuesParsed",t.NON_NATIVE_TEXT_TRACKS_FOUND="hlsNonNativeTextTracksFound",t.INIT_PTS_FOUND="hlsInitPtsFound",t.FRAG_LOADING="hlsFragLoading",t.FRAG_LOAD_EMERGENCY_ABORTED="hlsFragLoadEmergencyAborted",t.FRAG_LOADED="hlsFragLoaded",t.FRAG_DECRYPTED="hlsFragDecrypted",t.FRAG_PARSING_INIT_SEGMENT="hlsFragParsingInitSegment",t.FRAG_PARSING_USERDATA="hlsFragParsingUserdata",t.FRAG_PARSING_METADATA="hlsFragParsingMetadata",t.FRAG_PARSED="hlsFragParsed",t.FRAG_BUFFERED="hlsFragBuffered",t.FRAG_CHANGED="hlsFragChanged",t.FPS_DROP="hlsFpsDrop",t.FPS_DROP_LEVEL_CAPPING="hlsFpsDropLevelCapping",t.MAX_AUTO_LEVEL_UPDATED="hlsMaxAutoLevelUpdated",t.ERROR="hlsError",t.DESTROYING="hlsDestroying",t.KEY_LOADING="hlsKeyLoading",t.KEY_LOADED="hlsKeyLoaded",t.LIVE_BACK_BUFFER_REACHED="hlsLiveBackBufferReached",t.BACK_BUFFER_REACHED="hlsBackBufferReached",t.STEERING_MANIFEST_LOADED="hlsSteeringManifestLoaded",t}({}),L=function(t){return t.NETWORK_ERROR="networkError",t.MEDIA_ERROR="mediaError",t.KEY_SYSTEM_ERROR="keySystemError",t.MUX_ERROR="muxError",t.OTHER_ERROR="otherError",t}({}),A=function(t){return t.KEY_SYSTEM_NO_KEYS="keySystemNoKeys",t.KEY_SYSTEM_NO_ACCESS="keySystemNoAccess",t.KEY_SYSTEM_NO_SESSION="keySystemNoSession",t.KEY_SYSTEM_NO_CONFIGURED_LICENSE="keySystemNoConfiguredLicense",t.KEY_SYSTEM_LICENSE_REQUEST_FAILED="keySystemLicenseRequestFailed",t.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED="keySystemServerCertificateRequestFailed",t.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED="keySystemServerCertificateUpdateFailed",t.KEY_SYSTEM_SESSION_UPDATE_FAILED="keySystemSessionUpdateFailed",t.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED="keySystemStatusOutputRestricted",t.KEY_SYSTEM_STATUS_INTERNAL_ERROR="keySystemStatusInternalError",t.MANIFEST_LOAD_ERROR="manifestLoadError",t.MANIFEST_LOAD_TIMEOUT="manifestLoadTimeOut",t.MANIFEST_PARSING_ERROR="manifestParsingError",t.MANIFEST_INCOMPATIBLE_CODECS_ERROR="manifestIncompatibleCodecsError",t.LEVEL_EMPTY_ERROR="levelEmptyError",t.LEVEL_LOAD_ERROR="levelLoadError",t.LEVEL_LOAD_TIMEOUT="levelLoadTimeOut",t.LEVEL_PARSING_ERROR="levelParsingError",t.LEVEL_SWITCH_ERROR="levelSwitchError",t.AUDIO_TRACK_LOAD_ERROR="audioTrackLoadError",t.AUDIO_TRACK_LOAD_TIMEOUT="audioTrackLoadTimeOut",t.SUBTITLE_LOAD_ERROR="subtitleTrackLoadError",t.SUBTITLE_TRACK_LOAD_TIMEOUT="subtitleTrackLoadTimeOut",t.FRAG_LOAD_ERROR="fragLoadError",t.FRAG_LOAD_TIMEOUT="fragLoadTimeOut",t.FRAG_DECRYPT_ERROR="fragDecryptError",t.FRAG_PARSING_ERROR="fragParsingError",t.FRAG_GAP="fragGap",t.REMUX_ALLOC_ERROR="remuxAllocError",t.KEY_LOAD_ERROR="keyLoadError",t.KEY_LOAD_TIMEOUT="keyLoadTimeOut",t.BUFFER_ADD_CODEC_ERROR="bufferAddCodecError",t.BUFFER_INCOMPATIBLE_CODECS_ERROR="bufferIncompatibleCodecsError",t.BUFFER_APPEND_ERROR="bufferAppendError",t.BUFFER_APPENDING_ERROR="bufferAppendingError",t.BUFFER_STALLED_ERROR="bufferStalledError",t.BUFFER_FULL_ERROR="bufferFullError",t.BUFFER_SEEK_OVER_HOLE="bufferSeekOverHole",t.BUFFER_NUDGE_ON_STALL="bufferNudgeOnStall",t.INTERNAL_EXCEPTION="internalException",t.INTERNAL_ABORTED="aborted",t.UNKNOWN="unknown",t}({}),R=function(){},k={trace:R,debug:R,log:R,warn:R,info:R,error:R},b=k;function D(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i<e;i++)r[i-1]=arguments[i];r.forEach((function(e){b[e]=t[e]?t[e].bind(t):function(t){var e=self.console[t];return e?e.bind(self.console,"["+t+"] >"):R}(e)}))}function I(t,e){if("object"==typeof console&&!0===t||"object"==typeof t){D(t,"debug","log","info","warn","error");try{b.log('Debug logs enabled for "'+e+'" in hls.js version 1.5.1')}catch(t){b=k}}else b=k}var w=b,C=/^(\d+)x(\d+)$/,_=/(.+?)=(".*?"|.*?)(?:,|$)/g,x=function(){function t(e){"string"==typeof e&&(e=t.parseAttrList(e)),o(this,e)}var e=t.prototype;return e.decimalInteger=function(t){var e=parseInt(this[t],10);return e>Number.MAX_SAFE_INTEGER?1/0:e},e.hexadecimalInteger=function(t){if(this[t]){var e=(this[t]||"0x").slice(2);e=(1&e.length?"0":"")+e;for(var r=new Uint8Array(e.length/2),i=0;i<e.length/2;i++)r[i]=parseInt(e.slice(2*i,2*i+2),16);return r}return null},e.hexadecimalIntegerAsNumber=function(t){var e=parseInt(this[t],16);return e>Number.MAX_SAFE_INTEGER?1/0:e},e.decimalFloatingPoint=function(t){return parseFloat(this[t])},e.optionalFloat=function(t,e){var r=this[t];return r?parseFloat(r):e},e.enumeratedString=function(t){return this[t]},e.bool=function(t){return"YES"===this[t]},e.decimalResolution=function(t){var e=C.exec(this[t]);if(null!==e)return{width:parseInt(e[1],10),height:parseInt(e[2],10)}},t.parseAttrList=function(t){var e,r={};for(_.lastIndex=0;null!==(e=_.exec(t));){var i=e[2];0===i.indexOf('"')&&i.lastIndexOf('"')===i.length-1&&(i=i.slice(1,-1)),r[e[1].trim()]=i}return r},s(t,[{key:"clientAttrs",get:function(){return Object.keys(this).filter((function(t){return"X-"===t.substring(0,2)}))}}]),t}();function P(t){return"SCTE35-OUT"===t||"SCTE35-IN"===t}var F=function(){function t(t,e){if(this.attr=void 0,this._startDate=void 0,this._endDate=void 0,this._badValueForSameId=void 0,e){var r=e.attr;for(var i in r)if(Object.prototype.hasOwnProperty.call(t,i)&&t[i]!==r[i]){w.warn('DATERANGE tag attribute: "'+i+'" does not match for tags with ID: "'+t.ID+'"'),this._badValueForSameId=i;break}t=o(new x({}),r,t)}if(this.attr=t,this._startDate=new Date(t["START-DATE"]),"END-DATE"in this.attr){var n=new Date(this.attr["END-DATE"]);y(n.getTime())&&(this._endDate=n)}}return s(t,[{key:"id",get:function(){return this.attr.ID}},{key:"class",get:function(){return this.attr.CLASS}},{key:"startDate",get:function(){return this._startDate}},{key:"endDate",get:function(){if(this._endDate)return this._endDate;var t=this.duration;return null!==t?new Date(this._startDate.getTime()+1e3*t):null}},{key:"duration",get:function(){if("DURATION"in this.attr){var t=this.attr.decimalFloatingPoint("DURATION");if(y(t))return t}else if(this._endDate)return(this._endDate.getTime()-this._startDate.getTime())/1e3;return null}},{key:"plannedDuration",get:function(){return"PLANNED-DURATION"in this.attr?this.attr.decimalFloatingPoint("PLANNED-DURATION"):null}},{key:"endOnNext",get:function(){return this.attr.bool("END-ON-NEXT")}},{key:"isValid",get:function(){return!!this.id&&!this._badValueForSameId&&y(this.startDate.getTime())&&(null===this.duration||this.duration>=0)&&(!this.endOnNext||!!this.class)}}]),t}(),M=function(){this.aborted=!1,this.loaded=0,this.retry=0,this.total=0,this.chunkCount=0,this.bwEstimate=0,this.loading={start:0,first:0,end:0},this.parsing={start:0,end:0},this.buffering={start:0,first:0,end:0}},O="audio",N="video",U="audiovideo",B=function(){function t(t){var e;this._byteRange=null,this._url=null,this.baseurl=void 0,this.relurl=void 0,this.elementaryStreams=((e={})[O]=null,e[N]=null,e[U]=null,e),this.baseurl=t}return t.prototype.setByteRange=function(t,e){var r,i=t.split("@",2);r=1===i.length?(null==e?void 0:e.byteRangeEndOffset)||0:parseInt(i[1]),this._byteRange=[r,parseInt(i[0])+r]},s(t,[{key:"byteRange",get:function(){return this._byteRange?this._byteRange:[]}},{key:"byteRangeStartOffset",get:function(){return this.byteRange[0]}},{key:"byteRangeEndOffset",get:function(){return this.byteRange[1]}},{key:"url",get:function(){return!this._url&&this.baseurl&&this.relurl&&(this._url=p.buildAbsoluteURL(this.baseurl,this.relurl,{alwaysNormalize:!0})),this._url||""},set:function(t){this._url=t}}]),t}(),G=function(t){function e(e,r){var i;return(i=t.call(this,r)||this)._decryptdata=null,i.rawProgramDateTime=null,i.programDateTime=null,i.tagList=[],i.duration=0,i.sn=0,i.levelkeys=void 0,i.type=void 0,i.loader=null,i.keyLoader=null,i.level=-1,i.cc=0,i.startPTS=void 0,i.endPTS=void 0,i.startDTS=void 0,i.endDTS=void 0,i.start=0,i.deltaPTS=void 0,i.maxStartPTS=void 0,i.minEndPTS=void 0,i.stats=new M,i.data=void 0,i.bitrateTest=!1,i.title=null,i.initSegment=null,i.endList=void 0,i.gap=void 0,i.urlId=0,i.type=e,i}l(e,t);var r=e.prototype;return r.setKeyFormat=function(t){if(this.levelkeys){var e=this.levelkeys[t];e&&!this._decryptdata&&(this._decryptdata=e.getDecryptData(this.sn))}},r.abortRequests=function(){var t,e;null==(t=this.loader)||t.abort(),null==(e=this.keyLoader)||e.abort()},r.setElementaryStreamInfo=function(t,e,r,i,n,a){void 0===a&&(a=!1);var s=this.elementaryStreams,o=s[t];o?(o.startPTS=Math.min(o.startPTS,e),o.endPTS=Math.max(o.endPTS,r),o.startDTS=Math.min(o.startDTS,i),o.endDTS=Math.max(o.endDTS,n)):s[t]={startPTS:e,endPTS:r,startDTS:i,endDTS:n,partial:a}},r.clearElementaryStreamInfo=function(){var t=this.elementaryStreams;t[O]=null,t[N]=null,t[U]=null},s(e,[{key:"decryptdata",get:function(){if(!this.levelkeys&&!this._decryptdata)return null;if(!this._decryptdata&&this.levelkeys&&!this.levelkeys.NONE){var t=this.levelkeys.identity;if(t)this._decryptdata=t.getDecryptData(this.sn);else{var e=Object.keys(this.levelkeys);if(1===e.length)return this._decryptdata=this.levelkeys[e[0]].getDecryptData(this.sn)}}return this._decryptdata}},{key:"end",get:function(){return this.start+this.duration}},{key:"endProgramDateTime",get:function(){if(null===this.programDateTime)return null;if(!y(this.programDateTime))return null;var t=y(this.duration)?this.duration:0;return this.programDateTime+1e3*t}},{key:"encrypted",get:function(){var t;if(null!=(t=this._decryptdata)&&t.encrypted)return!0;if(this.levelkeys){var e=Object.keys(this.levelkeys),r=e.length;if(r>1||1===r&&this.levelkeys[e[0]].encrypted)return!0}return!1}}]),e}(B),K=function(t){function e(e,r,i,n,a){var s;(s=t.call(this,i)||this).fragOffset=0,s.duration=0,s.gap=!1,s.independent=!1,s.relurl=void 0,s.fragment=void 0,s.index=void 0,s.stats=new M,s.duration=e.decimalFloatingPoint("DURATION"),s.gap=e.bool("GAP"),s.independent=e.bool("INDEPENDENT"),s.relurl=e.enumeratedString("URI"),s.fragment=r,s.index=n;var o=e.enumeratedString("BYTERANGE");return o&&s.setByteRange(o,a),a&&(s.fragOffset=a.fragOffset+a.duration),s}return l(e,t),s(e,[{key:"start",get:function(){return this.fragment.start+this.fragOffset}},{key:"end",get:function(){return this.start+this.duration}},{key:"loaded",get:function(){var t=this.elementaryStreams;return!!(t.audio||t.video||t.audiovideo)}}]),e}(B),H=function(){function t(t){this.PTSKnown=!1,this.alignedSliding=!1,this.averagetargetduration=void 0,this.endCC=0,this.endSN=0,this.fragments=void 0,this.fragmentHint=void 0,this.partList=null,this.dateRanges=void 0,this.live=!0,this.ageHeader=0,this.advancedDateTime=void 0,this.updated=!0,this.advanced=!0,this.availabilityDelay=void 0,this.misses=0,this.startCC=0,this.startSN=0,this.startTimeOffset=null,this.targetduration=0,this.totalduration=0,this.type=null,this.url=void 0,this.m3u8="",this.version=null,this.canBlockReload=!1,this.canSkipUntil=0,this.canSkipDateRanges=!1,this.skippedSegments=0,this.recentlyRemovedDateranges=void 0,this.partHoldBack=0,this.holdBack=0,this.partTarget=0,this.preloadHint=void 0,this.renditionReports=void 0,this.tuneInGoal=0,this.deltaUpdateFailed=void 0,this.driftStartTime=0,this.driftEndTime=0,this.driftStart=0,this.driftEnd=0,this.encryptedFragments=void 0,this.playlistParsingError=null,this.variableList=null,this.hasVariableRefs=!1,this.fragments=[],this.encryptedFragments=[],this.dateRanges={},this.url=t}return t.prototype.reloaded=function(t){if(!t)return this.advanced=!0,void(this.updated=!0);var e=this.lastPartSn-t.lastPartSn,r=this.lastPartIndex-t.lastPartIndex;this.updated=this.endSN!==t.endSN||!!r||!!e||!this.live,this.advanced=this.endSN>t.endSN||e>0||0===e&&r>0,this.updated||this.advanced?this.misses=Math.floor(.6*t.misses):this.misses=t.misses+1,this.availabilityDelay=t.availabilityDelay},s(t,[{key:"hasProgramDateTime",get:function(){return!!this.fragments.length&&y(this.fragments[this.fragments.length-1].programDateTime)}},{key:"levelTargetDuration",get:function(){return this.averagetargetduration||this.targetduration||10}},{key:"drift",get:function(){var t=this.driftEndTime-this.driftStartTime;return t>0?1e3*(this.driftEnd-this.driftStart)/t:1}},{key:"edge",get:function(){return this.partEnd||this.fragmentEnd}},{key:"partEnd",get:function(){var t;return null!=(t=this.partList)&&t.length?this.partList[this.partList.length-1].end:this.fragmentEnd}},{key:"fragmentEnd",get:function(){var t;return null!=(t=this.fragments)&&t.length?this.fragments[this.fragments.length-1].end:0}},{key:"age",get:function(){return this.advancedDateTime?Math.max(Date.now()-this.advancedDateTime,0)/1e3:0}},{key:"lastPartIndex",get:function(){var t;return null!=(t=this.partList)&&t.length?this.partList[this.partList.length-1].index:-1}},{key:"lastPartSn",get:function(){var t;return null!=(t=this.partList)&&t.length?this.partList[this.partList.length-1].fragment.sn:this.endSN}}]),t}();function V(t){return Uint8Array.from(atob(t),(function(t){return t.charCodeAt(0)}))}function Y(t){var e,r,i=t.split(":"),n=null;if("data"===i[0]&&2===i.length){var a=i[1].split(";"),s=a[a.length-1].split(",");if(2===s.length){var o="base64"===s[0],l=s[1];o?(a.splice(-1,1),n=V(l)):(e=W(l).subarray(0,16),(r=new Uint8Array(16)).set(e,16-e.length),n=r)}}return n}function W(t){return Uint8Array.from(unescape(encodeURIComponent(t)),(function(t){return t.charCodeAt(0)}))}var j="undefined"!=typeof self?self:void 0,q={CLEARKEY:"org.w3.clearkey",FAIRPLAY:"com.apple.fps",PLAYREADY:"com.microsoft.playready",WIDEVINE:"com.widevine.alpha"},X="org.w3.clearkey",z="com.apple.streamingkeydelivery",Q="com.microsoft.playready",J="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";function $(t){switch(t){case z:return q.FAIRPLAY;case Q:return q.PLAYREADY;case J:return q.WIDEVINE;case X:return q.CLEARKEY}}var Z="edef8ba979d64acea3c827dcd51d21ed";function tt(t){switch(t){case q.FAIRPLAY:return z;case q.PLAYREADY:return Q;case q.WIDEVINE:return J;case q.CLEARKEY:return X}}function et(t){var e=t.drmSystems,r=t.widevineLicenseUrl,i=e?[q.FAIRPLAY,q.WIDEVINE,q.PLAYREADY,q.CLEARKEY].filter((function(t){return!!e[t]})):[];return!i[q.WIDEVINE]&&r&&i.push(q.WIDEVINE),i}var rt,it=null!=j&&null!=(rt=j.navigator)&&rt.requestMediaKeySystemAccess?self.navigator.requestMediaKeySystemAccess.bind(self.navigator):null;function nt(t,e,r){return Uint8Array.prototype.slice?t.slice(e,r):new Uint8Array(Array.prototype.slice.call(t,e,r))}var at,st=function(t,e){return e+10<=t.length&&73===t[e]&&68===t[e+1]&&51===t[e+2]&&t[e+3]<255&&t[e+4]<255&&t[e+6]<128&&t[e+7]<128&&t[e+8]<128&&t[e+9]<128},ot=function(t,e){return e+10<=t.length&&51===t[e]&&68===t[e+1]&&73===t[e+2]&&t[e+3]<255&&t[e+4]<255&&t[e+6]<128&&t[e+7]<128&&t[e+8]<128&&t[e+9]<128},lt=function(t,e){for(var r=e,i=0;st(t,e);)i+=10,i+=ut(t,e+6),ot(t,e+10)&&(i+=10),e+=i;if(i>0)return t.subarray(r,r+i)},ut=function(t,e){var r=0;return r=(127&t[e])<<21,r|=(127&t[e+1])<<14,r|=(127&t[e+2])<<7,r|=127&t[e+3]},ht=function(t,e){return st(t,e)&&ut(t,e+6)+10<=t.length-e},dt=function(t){for(var e=gt(t),r=0;r<e.length;r++){var i=e[r];if(ct(i))return Et(i)}},ct=function(t){return t&&"PRIV"===t.key&&"com.apple.streaming.transportStreamTimestamp"===t.info},ft=function(t){var e=String.fromCharCode(t[0],t[1],t[2],t[3]),r=ut(t,4);return{type:e,size:r,data:t.subarray(10,10+r)}},gt=function(t){for(var e=0,r=[];st(t,e);){for(var i=ut(t,e+6),n=(e+=10)+i;e+8<n;){var a=ft(t.subarray(e)),s=vt(a);s&&r.push(s),e+=a.size+10}ot(t,e)&&(e+=10)}return r},vt=function(t){return"PRIV"===t.type?mt(t):"W"===t.type[0]?yt(t):pt(t)},mt=function(t){if(!(t.size<2)){var e=Tt(t.data,!0),r=new Uint8Array(t.data.subarray(e.length+1));return{key:t.type,info:e,data:r.buffer}}},pt=function(t){if(!(t.size<2)){if("TXXX"===t.type){var e=1,r=Tt(t.data.subarray(e),!0);e+=r.length+1;var i=Tt(t.data.subarray(e));return{key:t.type,info:r,data:i}}var n=Tt(t.data.subarray(1));return{key:t.type,data:n}}},yt=function(t){if("WXXX"===t.type){if(t.size<2)return;var e=1,r=Tt(t.data.subarray(e),!0);e+=r.length+1;var i=Tt(t.data.subarray(e));return{key:t.type,info:r,data:i}}var n=Tt(t.data);return{key:t.type,data:n}},Et=function(t){if(8===t.data.byteLength){var e=new Uint8Array(t.data),r=1&e[3],i=(e[4]<<23)+(e[5]<<15)+(e[6]<<7)+e[7];return i/=45,r&&(i+=47721858.84),Math.round(i)}},Tt=function(t,e){void 0===e&&(e=!1);var r=St();if(r){var i=r.decode(t);if(e){var n=i.indexOf("\0");return-1!==n?i.substring(0,n):i}return i.replace(/\0/g,"")}for(var a,s,o,l=t.length,u="",h=0;h<l;){if(0===(a=t[h++])&&e)return u;if(0!==a&&3!==a)switch(a>>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:u+=String.fromCharCode(a);break;case 12:case 13:s=t[h++],u+=String.fromCharCode((31&a)<<6|63&s);break;case 14:s=t[h++],o=t[h++],u+=String.fromCharCode((15&a)<<12|(63&s)<<6|(63&o)<<0)}}return u};function St(){if(!navigator.userAgent.includes("PlayStation 4"))return at||void 0===self.TextDecoder||(at=new self.TextDecoder("utf-8")),at}var Lt=function(t){for(var e="",r=0;r<t.length;r++){var i=t[r].toString(16);i.length<2&&(i="0"+i),e+=i}return e},At=Math.pow(2,32)-1,Rt=[].push,kt={video:1,audio:2,id3:3,text:4};function bt(t){return String.fromCharCode.apply(null,t)}function Dt(t,e){var r=t[e]<<8|t[e+1];return r<0?65536+r:r}function It(t,e){var r=wt(t,e);return r<0?4294967296+r:r}function wt(t,e){return t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3]}function Ct(t,e,r){t[e]=r>>24,t[e+1]=r>>16&255,t[e+2]=r>>8&255,t[e+3]=255&r}function _t(t,e){var r=[];if(!e.length)return r;for(var i=t.byteLength,n=0;n<i;){var a=It(t,n),s=a>1?n+a:i;if(bt(t.subarray(n+4,n+8))===e[0])if(1===e.length)r.push(t.subarray(n+8,s));else{var o=_t(t.subarray(n+8,s),e.slice(1));o.length&&Rt.apply(r,o)}n=s}return r}function xt(t){var e=[],r=t[0],i=8,n=It(t,i);i+=4,i+=0===r?8:16,i+=2;var a=t.length+0,s=Dt(t,i);i+=2;for(var o=0;o<s;o++){var l=i,u=It(t,l);l+=4;var h=2147483647&u;if(1==(2147483648&u)>>>31)return w.warn("SIDX has hierarchical references (not supported)"),null;var d=It(t,l);l+=4,e.push({referenceSize:h,subsegmentDuration:d,info:{duration:d/n,start:a,end:a+h-1}}),a+=h,i=l+=4}return{earliestPresentationTime:0,timescale:n,version:r,referencesCount:s,references:e}}function Pt(t){for(var e=[],r=_t(t,["moov","trak"]),n=0;n<r.length;n++){var a=r[n],s=_t(a,["tkhd"])[0];if(s){var o=s[0],l=It(s,0===o?12:20),u=_t(a,["mdia","mdhd"])[0];if(u){var h=It(u,0===(o=u[0])?12:20),d=_t(a,["mdia","hdlr"])[0];if(d){var c=bt(d.subarray(8,12)),f={soun:O,vide:N}[c];if(f){var g=Ft(_t(a,["mdia","minf","stbl","stsd"])[0]);e[l]={timescale:h,type:f},e[f]=i({timescale:h,id:l},g)}}}}}return _t(t,["moov","mvex","trex"]).forEach((function(t){var r=It(t,4),i=e[r];i&&(i.default={duration:It(t,12),flags:It(t,20)})})),e}function Ft(t){var e=t.subarray(8),r=e.subarray(86),i=bt(e.subarray(4,8)),n=i,a="enca"===i||"encv"===i;if(a){var s=_t(e,[i])[0];_t(s.subarray("enca"===i?28:78),["sinf"]).forEach((function(t){var e=_t(t,["schm"])[0];if(e){var r=bt(e.subarray(4,8));if("cbcs"===r||"cenc"===r){var i=_t(t,["frma"])[0];i&&(n=bt(i))}}}))}switch(n){case"avc1":case"avc2":case"avc3":case"avc4":var o=_t(r,["avcC"])[0];n+="."+Ot(o[1])+Ot(o[2])+Ot(o[3]);break;case"mp4a":var l=_t(e,[i])[0],u=_t(l.subarray(28),["esds"])[0];if(u&&u.length>12){var h=4;if(3!==u[h++])break;h=Mt(u,h),h+=2;var d=u[h++];if(128&d&&(h+=2),64&d&&(h+=u[h++]),4!==u[h++])break;h=Mt(u,h);var c=u[h++];if(64!==c)break;if(n+="."+Ot(c),h+=12,5!==u[h++])break;h=Mt(u,h);var f=u[h++],g=(248&f)>>3;31===g&&(g+=1+((7&f)<<3)+((224&u[h])>>5)),n+="."+g}break;case"hvc1":case"hev1":var v=_t(r,["hvcC"])[0],m=v[1],p=["","A","B","C"][m>>6],y=31&m,E=It(v,2),T=(32&m)>>5?"H":"L",S=v[12],L=v.subarray(6,12);n+="."+p+y,n+="."+E.toString(16).toUpperCase(),n+="."+T+S;for(var A="",R=L.length;R--;){var k=L[R];(k||A)&&(A="."+k.toString(16).toUpperCase()+A)}n+=A;break;case"dvh1":case"dvhe":var b=_t(r,["dvcC"])[0],D=b[2]>>1&127,I=b[2]<<5&32|b[3]>>3&31;n+="."+Nt(D)+"."+Nt(I);break;case"vp09":var w=_t(r,["vpcC"])[0],C=w[4],_=w[5],x=w[6]>>4&15;n+="."+Nt(C)+"."+Nt(_)+"."+Nt(x);break;case"av01":var P=_t(r,["av1C"])[0],F=P[1]>>>5,M=31&P[1],O=P[2]>>>7?"H":"M",N=(64&P[2])>>6,U=(32&P[2])>>5,B=2===F&&N?U?12:10:N?10:8,G=(16&P[2])>>4,K=(8&P[2])>>3,H=(4&P[2])>>2,V=3&P[2];n+="."+F+"."+Nt(M)+O+"."+Nt(B)+"."+G+"."+K+H+V+"."+Nt(1)+"."+Nt(1)+"."+Nt(1)+".0"}return{codec:n,encrypted:a}}function Mt(t,e){for(var r=e+5;128&t[e++]&&e<r;);return e}function Ot(t){return("0"+t.toString(16).toUpperCase()).slice(-2)}function Nt(t){return(t<10?"0":"")+t}function Ut(t){var e=_t(t,["schm"])[0];if(e){var r=bt(e.subarray(4,8));if("cbcs"===r||"cenc"===r)return _t(t,["schi","tenc"])[0]}return w.error("[eme] missing 'schm' box"),null}function Bt(t){var e=It(t,0),r=8;1&e&&(r+=4),4&e&&(r+=4);for(var i=0,n=It(t,4),a=0;a<n;a++)256&e&&(i+=It(t,r),r+=4),512&e&&(r+=4),1024&e&&(r+=4),2048&e&&(r+=4);return i}function Gt(t,e){var r=new Uint8Array(t.length+e.length);return r.set(t),r.set(e,t.length),r}function Kt(t,e){var r=[],i=e.samples,n=e.timescale,a=e.id,s=!1;return _t(i,["moof"]).map((function(o){var l=o.byteOffset-8;_t(o,["traf"]).map((function(o){var u=_t(o,["tfdt"]).map((function(t){var e=t[0],r=It(t,4);return 1===e&&(r*=Math.pow(2,32),r+=It(t,8)),r/n}))[0];return void 0!==u&&(t=u),_t(o,["tfhd"]).map((function(u){var h=It(u,4),d=16777215&It(u,0),c=0,f=0!=(16&d),g=0,v=0!=(32&d),m=8;h===a&&(0!=(1&d)&&(m+=8),0!=(2&d)&&(m+=4),0!=(8&d)&&(c=It(u,m),m+=4),f&&(g=It(u,m),m+=4),v&&(m+=4),"video"===e.type&&(s=function(t){if(!t)return!1;var e=t.indexOf("."),r=e<0?t:t.substring(0,e);return"hvc1"===r||"hev1"===r||"dvh1"===r||"dvhe"===r}(e.codec)),_t(o,["trun"]).map((function(a){var o=a[0],u=16777215&It(a,0),h=0!=(1&u),d=0,f=0!=(4&u),v=0!=(256&u),m=0,p=0!=(512&u),y=0,E=0!=(1024&u),T=0!=(2048&u),S=0,L=It(a,4),A=8;h&&(d=It(a,A),A+=4),f&&(A+=4);for(var R=d+l,k=0;k<L;k++){if(v?(m=It(a,A),A+=4):m=c,p?(y=It(a,A),A+=4):y=g,E&&(A+=4),T&&(S=0===o?It(a,A):wt(a,A),A+=4),e.type===N)for(var b=0;b<y;){var D=It(i,R);Ht(s,i[R+=4])&&Vt(i.subarray(R,R+D),s?2:1,t+S/n,r),R+=D,b+=D+4}t+=m/n}})))}))}))})),r}function Ht(t,e){if(t){var r=e>>1&63;return 39===r||40===r}return 6==(31&e)}function Vt(t,e,r,i){var n=Yt(t),a=0;a+=e;for(var s=0,o=0,l=0;a<n.length;){s=0;do{if(a>=n.length)break;s+=l=n[a++]}while(255===l);o=0;do{if(a>=n.length)break;o+=l=n[a++]}while(255===l);var u=n.length-a,h=a;if(o<u)a+=o;else if(o>u){w.error("Malformed SEI payload. "+o+" is too small, only "+u+" bytes left to parse.");break}if(4===s){if(181===n[h++]){var d=Dt(n,h);if(h+=2,49===d){var c=It(n,h);if(h+=4,1195456820===c){var f=n[h++];if(3===f){var g=n[h++],v=64&g,m=v?2+3*(31&g):0,p=new Uint8Array(m);if(v){p[0]=g;for(var y=1;y<m;y++)p[y]=n[h++]}i.push({type:f,payloadType:s,pts:r,bytes:p})}}}}}else if(5===s&&o>16){for(var E=[],T=0;T<16;T++){var S=n[h++].toString(16);E.push(1==S.length?"0"+S:S),3!==T&&5!==T&&7!==T&&9!==T||E.push("-")}for(var L=o-16,A=new Uint8Array(L),R=0;R<L;R++)A[R]=n[h++];i.push({payloadType:s,pts:r,uuid:E.join(""),userData:Tt(A),userDataBytes:A})}}}function Yt(t){for(var e=t.byteLength,r=[],i=1;i<e-2;)0===t[i]&&0===t[i+1]&&3===t[i+2]?(r.push(i+2),i+=2):i++;if(0===r.length)return t;var n=e-r.length,a=new Uint8Array(n),s=0;for(i=0;i<n;s++,i++)s===r[0]&&(s++,r.shift()),a[i]=t[s];return a}function Wt(t,e,r){if(16!==t.byteLength)throw new RangeError("Invalid system id");var i,n,a;if(e){i=1,n=new Uint8Array(16*e.length);for(var s=0;s<e.length;s++){var o=e[s];if(16!==o.byteLength)throw new RangeError("Invalid key");n.set(o,16*s)}}else i=0,n=new Uint8Array;i>0?(a=new Uint8Array(4),e.length>0&&new DataView(a.buffer).setUint32(0,e.length,!1)):a=new Uint8Array;var l=new Uint8Array(4);return r&&r.byteLength>0&&new DataView(l.buffer).setUint32(0,r.byteLength,!1),function(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i<e;i++)r[i-1]=arguments[i];for(var n=r.length,a=8,s=n;s--;)a+=r[s].byteLength;var o=new Uint8Array(a);for(o[0]=a>>24&255,o[1]=a>>16&255,o[2]=a>>8&255,o[3]=255&a,o.set(t,4),s=0,a=8;s<n;s++)o.set(r[s],a),a+=r[s].byteLength;return o}([112,115,115,104],new Uint8Array([i,0,0,0]),t,a,n,l,r||new Uint8Array)}var jt={},qt=function(){function t(t,e,r,i,n){void 0===i&&(i=[1]),void 0===n&&(n=null),this.uri=void 0,this.method=void 0,this.keyFormat=void 0,this.keyFormatVersions=void 0,this.encrypted=void 0,this.isCommonEncryption=void 0,this.iv=null,this.key=null,this.keyId=null,this.pssh=null,this.method=t,this.uri=e,this.keyFormat=r,this.keyFormatVersions=i,this.iv=n,this.encrypted=!!t&&"NONE"!==t,this.isCommonEncryption=this.encrypted&&"AES-128"!==t}t.clearKeyUriToKeyIdMap=function(){jt={}};var e=t.prototype;return e.isSupported=function(){if(this.method){if("AES-128"===this.method||"NONE"===this.method)return!0;if("identity"===this.keyFormat)return"SAMPLE-AES"===this.method;switch(this.keyFormat){case z:case J:case Q:case X:return-1!==["ISO-23001-7","SAMPLE-AES","SAMPLE-AES-CENC","SAMPLE-AES-CTR"].indexOf(this.method)}}return!1},e.getDecryptData=function(e){if(!this.encrypted||!this.uri)return null;if("AES-128"===this.method&&this.uri&&!this.iv){"number"!=typeof e&&("AES-128"!==this.method||this.iv||w.warn('missing IV for initialization segment with method="'+this.method+'" - compliance issue'),e=0);var r=function(t){for(var e=new Uint8Array(16),r=12;r<16;r++)e[r]=t>>8*(15-r)&255;return e}(e);return new t(this.method,this.uri,"identity",this.keyFormatVersions,r)}var i=Y(this.uri);if(i)switch(this.keyFormat){case J:this.pssh=i,i.length>=22&&(this.keyId=i.subarray(i.length-22,i.length-6));break;case Q:var n=new Uint8Array([154,4,240,121,152,64,66,134,171,146,230,91,224,136,95,149]);this.pssh=Wt(n,null,i);var a=new Uint16Array(i.buffer,i.byteOffset,i.byteLength/2),s=String.fromCharCode.apply(null,Array.from(a)),o=s.substring(s.indexOf("<"),s.length),l=(new DOMParser).parseFromString(o,"text/xml").getElementsByTagName("KID")[0];if(l){var u=l.childNodes[0]?l.childNodes[0].nodeValue:l.getAttribute("VALUE");if(u){var h=V(u).subarray(0,16);!function(t){var e=function(t,e,r){var i=t[e];t[e]=t[r],t[r]=i};e(t,0,3),e(t,1,2),e(t,4,5),e(t,6,7)}(h),this.keyId=h}}break;default:var d=i.subarray(0,16);if(16!==d.length){var c=new Uint8Array(16);c.set(d,16-d.length),d=c}this.keyId=d}if(!this.keyId||16!==this.keyId.byteLength){var f=jt[this.uri];if(!f){var g=Object.keys(jt).length%Number.MAX_SAFE_INTEGER;f=new Uint8Array(16),new DataView(f.buffer,12,4).setUint32(0,g),jt[this.uri]=f}this.keyId=f}return this},t}(),Xt=/\{\$([a-zA-Z0-9-_]+)\}/g;function zt(t){return Xt.test(t)}function Qt(t,e,r){if(null!==t.variableList||t.hasVariableRefs)for(var i=r.length;i--;){var n=r[i],a=e[n];a&&(e[n]=Jt(t,a))}}function Jt(t,e){if(null!==t.variableList||t.hasVariableRefs){var r=t.variableList;return e.replace(Xt,(function(e){var i=e.substring(2,e.length-1),n=null==r?void 0:r[i];return void 0===n?(t.playlistParsingError||(t.playlistParsingError=new Error('Missing preceding EXT-X-DEFINE tag for Variable Reference: "'+i+'"')),e):n}))}return e}function $t(t,e,r){var i,n,a=t.variableList;if(a||(t.variableList=a={}),"QUERYPARAM"in e){i=e.QUERYPARAM;try{var s=new self.URL(r).searchParams;if(!s.has(i))throw new Error('"'+i+'" does not match any query parameter in URI: "'+r+'"');n=s.get(i)}catch(e){t.playlistParsingError||(t.playlistParsingError=new Error("EXT-X-DEFINE QUERYPARAM: "+e.message))}}else i=e.NAME,n=e.VALUE;i in a?t.playlistParsingError||(t.playlistParsingError=new Error('EXT-X-DEFINE duplicate Variable Name declarations: "'+i+'"')):a[i]=n||""}function Zt(t,e,r){var i=e.IMPORT;if(r&&i in r){var n=t.variableList;n||(t.variableList=n={}),n[i]=r[i]}else t.playlistParsingError||(t.playlistParsingError=new Error('EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "'+i+'"'))}function te(t){if(void 0===t&&(t=!0),"undefined"!=typeof self)return(t||!self.MediaSource)&&self.ManagedMediaSource||self.MediaSource||self.WebKitMediaSource}var ee={audio:{a3ds:1,"ac-3":.95,"ac-4":1,alac:.9,alaw:1,dra1:1,"dts+":1,"dts-":1,dtsc:1,dtse:1,dtsh:1,"ec-3":.9,enca:1,fLaC:.9,flac:.9,FLAC:.9,g719:1,g726:1,m4ae:1,mha1:1,mha2:1,mhm1:1,mhm2:1,mlpa:1,mp4a:1,"raw ":1,Opus:1,opus:1,samr:1,sawb:1,sawp:1,sevc:1,sqcp:1,ssmv:1,twos:1,ulaw:1},video:{avc1:1,avc2:1,avc3:1,avc4:1,avcp:1,av01:.8,drac:1,dva1:1,dvav:1,dvh1:.7,dvhe:.7,encv:1,hev1:.75,hvc1:.75,mjp2:1,mp4v:1,mvc1:1,mvc2:1,mvc3:1,mvc4:1,resv:1,rv60:1,s263:1,svc1:1,svc2:1,"vc-1":1,vp08:1,vp09:.9},text:{stpp:1,wvtt:1}};function re(t,e,r){return void 0===r&&(r=!0),!t.split(",").some((function(t){return!ie(t,e,r)}))}function ie(t,e,r){var i;void 0===r&&(r=!0);var n=te(r);return null!=(i=null==n?void 0:n.isTypeSupported(ne(t,e)))&&i}function ne(t,e){return e+'/mp4;codecs="'+t+'"'}function ae(t){if(t){var e=t.substring(0,4);return ee.video[e]}return 2}function se(t){return t.split(",").reduce((function(t,e){var r=ee.video[e];return r?(2*r+t)/(t?3:2):(ee.audio[e]+t)/(t?2:1)}),0)}var oe={},le=/flac|opus/i;function ue(t,e){return void 0===e&&(e=!0),t.replace(le,(function(t){return function(t,e){if(void 0===e&&(e=!0),oe[t])return oe[t];for(var r={flac:["flac","fLaC","FLAC"],opus:["opus","Opus"]}[t],i=0;i<r.length;i++)if(ie(r[i],"audio",e))return oe[t]=r[i],r[i];return t}(t.toLowerCase(),e)}))}function he(t,e){return t&&"mp4a"!==t?t:e}var de=/#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g,ce=/#EXT-X-MEDIA:(.*)/g,fe=/^#EXT(?:INF|-X-TARGETDURATION):/m,ge=new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source,/(?!#) *(\S[\S ]*)/.source,/#EXT-X-BYTERANGE:*(.+)/.source,/#EXT-X-PROGRAM-DATE-TIME:(.+)/.source,/#.*/.source].join("|"),"g"),ve=new RegExp([/#(EXTM3U)/.source,/#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source,/#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source,/#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS)/.source,/(#)([^:]*):(.*)/.source,/(#)(.*)(?:.*)\r?\n?/.source].join("|")),me=function(){function t(){}return t.findGroup=function(t,e){for(var r=0;r<t.length;r++){var i=t[r];if(i.id===e)return i}},t.resolve=function(t,e){return p.buildAbsoluteURL(e,t,{alwaysNormalize:!0})},t.isMediaPlaylist=function(t){return fe.test(t)},t.parseMasterPlaylist=function(e,r){var i,n={contentSteering:null,levels:[],playlistParsingError:null,sessionData:null,sessionKeys:null,startTimeOffset:null,variableList:null,hasVariableRefs:zt(e)},a=[];for(de.lastIndex=0;null!=(i=de.exec(e));)if(i[1]){var s,o=new x(i[1]);Qt(n,o,["CODECS","SUPPLEMENTAL-CODECS","ALLOWED-CPC","PATHWAY-ID","STABLE-VARIANT-ID","AUDIO","VIDEO","SUBTITLES","CLOSED-CAPTIONS","NAME"]);var l=Jt(n,i[2]),u={attrs:o,bitrate:o.decimalInteger("BANDWIDTH")||o.decimalInteger("AVERAGE-BANDWIDTH"),name:o.NAME,url:t.resolve(l,r)},h=o.decimalResolution("RESOLUTION");h&&(u.width=h.width,u.height=h.height),Ee(o.CODECS,u),null!=(s=u.unknownCodecs)&&s.length||a.push(u),n.levels.push(u)}else if(i[3]){var d=i[3],c=i[4];switch(d){case"SESSION-DATA":var f=new x(c);Qt(n,f,["DATA-ID","LANGUAGE","VALUE","URI"]);var g=f["DATA-ID"];g&&(null===n.sessionData&&(n.sessionData={}),n.sessionData[g]=f);break;case"SESSION-KEY":var v=pe(c,r,n);v.encrypted&&v.isSupported()?(null===n.sessionKeys&&(n.sessionKeys=[]),n.sessionKeys.push(v)):w.warn('[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "'+c+'"');break;case"DEFINE":var m=new x(c);Qt(n,m,["NAME","VALUE","QUERYPARAM"]),$t(n,m,r);break;case"CONTENT-STEERING":var p=new x(c);Qt(n,p,["SERVER-URI","PATHWAY-ID"]),n.contentSteering={uri:t.resolve(p["SERVER-URI"],r),pathwayId:p["PATHWAY-ID"]||"."};break;case"START":n.startTimeOffset=ye(c)}}var y=a.length>0&&a.length<n.levels.length;return n.levels=y?a:n.levels,0===n.levels.length&&(n.playlistParsingError=new Error("no levels found in manifest")),n},t.parseMasterPlaylistMedia=function(e,r,i){var n,a={},s=i.levels,o={AUDIO:s.map((function(t){return{id:t.attrs.AUDIO,audioCodec:t.audioCodec}})),SUBTITLES:s.map((function(t){return{id:t.attrs.SUBTITLES,textCodec:t.textCodec}})),"CLOSED-CAPTIONS":[]},l=0;for(ce.lastIndex=0;null!==(n=ce.exec(e));){var u=new x(n[1]),h=u.TYPE;if(h){var d=o[h],c=a[h]||[];a[h]=c,Qt(i,u,["URI","GROUP-ID","LANGUAGE","ASSOC-LANGUAGE","STABLE-RENDITION-ID","NAME","INSTREAM-ID","CHARACTERISTICS","CHANNELS"]);var f=u.LANGUAGE,g=u["ASSOC-LANGUAGE"],v=u.CHANNELS,m=u.CHARACTERISTICS,p=u["INSTREAM-ID"],y={attrs:u,bitrate:0,id:l++,groupId:u["GROUP-ID"]||"",name:u.NAME||f||"",type:h,default:u.bool("DEFAULT"),autoselect:u.bool("AUTOSELECT"),forced:u.bool("FORCED"),lang:f,url:u.URI?t.resolve(u.URI,r):""};if(g&&(y.assocLang=g),v&&(y.channels=v),m&&(y.characteristics=m),p&&(y.instreamId=p),null!=d&&d.length){var E=t.findGroup(d,y.groupId)||d[0];Te(y,E,"audioCodec"),Te(y,E,"textCodec")}c.push(y)}}return a},t.parseLevelPlaylist=function(t,e,r,i,n,a){var s,l,u,h=new H(e),d=h.fragments,c=null,f=0,g=0,v=0,m=0,p=null,E=new G(i,e),T=-1,S=!1,L=null;for(ge.lastIndex=0,h.m3u8=t,h.hasVariableRefs=zt(t);null!==(s=ge.exec(t));){S&&(S=!1,(E=new G(i,e)).start=v,E.sn=f,E.cc=m,E.level=r,c&&(E.initSegment=c,E.rawProgramDateTime=c.rawProgramDateTime,c.rawProgramDateTime=null,L&&(E.setByteRange(L),L=null)));var A=s[1];if(A){E.duration=parseFloat(A);var R=(" "+s[2]).slice(1);E.title=R||null,E.tagList.push(R?["INF",A,R]:["INF",A])}else if(s[3]){if(y(E.duration)){E.start=v,u&&Ae(E,u,h),E.sn=f,E.level=r,E.cc=m,d.push(E);var k=(" "+s[3]).slice(1);E.relurl=Jt(h,k),Se(E,p),p=E,v+=E.duration,f++,g=0,S=!0}}else if(s[4]){var b=(" "+s[4]).slice(1);p?E.setByteRange(b,p):E.setByteRange(b)}else if(s[5])E.rawProgramDateTime=(" "+s[5]).slice(1),E.tagList.push(["PROGRAM-DATE-TIME",E.rawProgramDateTime]),-1===T&&(T=d.length);else{if(!(s=s[0].match(ve))){w.warn("No matches on slow regex match for level playlist!");continue}for(l=1;l<s.length&&void 0===s[l];l++);var D=(" "+s[l]).slice(1),I=(" "+s[l+1]).slice(1),C=s[l+2]?(" "+s[l+2]).slice(1):"";switch(D){case"PLAYLIST-TYPE":h.type=I.toUpperCase();break;case"MEDIA-SEQUENCE":f=h.startSN=parseInt(I);break;case"SKIP":var _=new x(I);Qt(h,_,["RECENTLY-REMOVED-DATERANGES"]);var P=_.decimalInteger("SKIPPED-SEGMENTS");if(y(P)){h.skippedSegments=P;for(var M=P;M--;)d.unshift(null);f+=P}var O=_.enumeratedString("RECENTLY-REMOVED-DATERANGES");O&&(h.recentlyRemovedDateranges=O.split("\t"));break;case"TARGETDURATION":h.targetduration=Math.max(parseInt(I),1);break;case"VERSION":h.version=parseInt(I);break;case"INDEPENDENT-SEGMENTS":case"EXTM3U":break;case"ENDLIST":h.live=!1;break;case"#":(I||C)&&E.tagList.push(C?[I,C]:[I]);break;case"DISCONTINUITY":m++,E.tagList.push(["DIS"]);break;case"GAP":E.gap=!0,E.tagList.push([D]);break;case"BITRATE":E.tagList.push([D,I]);break;case"DATERANGE":var N=new x(I);Qt(h,N,["ID","CLASS","START-DATE","END-DATE","SCTE35-CMD","SCTE35-OUT","SCTE35-IN"]),Qt(h,N,N.clientAttrs);var U=new F(N,h.dateRanges[N.ID]);U.isValid||h.skippedSegments?h.dateRanges[U.id]=U:w.warn('Ignoring invalid DATERANGE tag: "'+I+'"'),E.tagList.push(["EXT-X-DATERANGE",I]);break;case"DEFINE":var B=new x(I);Qt(h,B,["NAME","VALUE","IMPORT","QUERYPARAM"]),"IMPORT"in B?Zt(h,B,a):$t(h,B,e);break;case"DISCONTINUITY-SEQUENCE":m=parseInt(I);break;case"KEY":var V=pe(I,e,h);if(V.isSupported()){if("NONE"===V.method){u=void 0;break}u||(u={}),u[V.keyFormat]&&(u=o({},u)),u[V.keyFormat]=V}else w.warn('[Keys] Ignoring invalid EXT-X-KEY tag: "'+I+'"');break;case"START":h.startTimeOffset=ye(I);break;case"MAP":var Y=new x(I);if(Qt(h,Y,["BYTERANGE","URI"]),E.duration){var W=new G(i,e);Le(W,Y,r,u),c=W,E.initSegment=c,c.rawProgramDateTime&&!E.rawProgramDateTime&&(E.rawProgramDateTime=c.rawProgramDateTime)}else{var j=E.byteRangeEndOffset;if(j){var q=E.byteRangeStartOffset;L=j-q+"@"+q}else L=null;Le(E,Y,r,u),c=E,S=!0}break;case"SERVER-CONTROL":var X=new x(I);h.canBlockReload=X.bool("CAN-BLOCK-RELOAD"),h.canSkipUntil=X.optionalFloat("CAN-SKIP-UNTIL",0),h.canSkipDateRanges=h.canSkipUntil>0&&X.bool("CAN-SKIP-DATERANGES"),h.partHoldBack=X.optionalFloat("PART-HOLD-BACK",0),h.holdBack=X.optionalFloat("HOLD-BACK",0);break;case"PART-INF":var z=new x(I);h.partTarget=z.decimalFloatingPoint("PART-TARGET");break;case"PART":var Q=h.partList;Q||(Q=h.partList=[]);var J=g>0?Q[Q.length-1]:void 0,$=g++,Z=new x(I);Qt(h,Z,["BYTERANGE","URI"]);var tt=new K(Z,E,e,$,J);Q.push(tt),E.duration+=tt.duration;break;case"PRELOAD-HINT":var et=new x(I);Qt(h,et,["URI"]),h.preloadHint=et;break;case"RENDITION-REPORT":var rt=new x(I);Qt(h,rt,["URI"]),h.renditionReports=h.renditionReports||[],h.renditionReports.push(rt);break;default:w.warn("line parsed but not handled: "+s)}}}p&&!p.relurl?(d.pop(),v-=p.duration,h.partList&&(h.fragmentHint=p)):h.partList&&(Se(E,p),E.cc=m,h.fragmentHint=E,u&&Ae(E,u,h));var it=d.length,nt=d[0],at=d[it-1];if((v+=h.skippedSegments*h.targetduration)>0&&it&&at){h.averagetargetduration=v/it;var st=at.sn;h.endSN="initSegment"!==st?st:0,h.live||(at.endList=!0),nt&&(h.startCC=nt.cc)}else h.endSN=0,h.startCC=0;return h.fragmentHint&&(v+=h.fragmentHint.duration),h.totalduration=v,h.endCC=m,T>0&&function(t,e){for(var r=t[e],i=e;i--;){var n=t[i];if(!n)return;n.programDateTime=r.programDateTime-1e3*n.duration,r=n}}(d,T),h},t}();function pe(t,e,r){var i,n,a=new x(t);Qt(r,a,["KEYFORMAT","KEYFORMATVERSIONS","URI","IV","URI"]);var s=null!=(i=a.METHOD)?i:"",o=a.URI,l=a.hexadecimalInteger("IV"),u=a.KEYFORMATVERSIONS,h=null!=(n=a.KEYFORMAT)?n:"identity";o&&a.IV&&!l&&w.error("Invalid IV: "+a.IV);var d=o?me.resolve(o,e):"",c=(u||"1").split("/").map(Number).filter(Number.isFinite);return new qt(s,d,h,c,l)}function ye(t){var e=new x(t).decimalFloatingPoint("TIME-OFFSET");return y(e)?e:null}function Ee(t,e){var r=(t||"").split(/[ ,]+/).filter((function(t){return t}));["video","audio","text"].forEach((function(t){var i=r.filter((function(e){return function(t,e){var r=ee[e];return!!r&&!!r[t.slice(0,4)]}(e,t)}));i.length&&(e[t+"Codec"]=i.join(","),r=r.filter((function(t){return-1===i.indexOf(t)})))})),e.unknownCodecs=r}function Te(t,e,r){var i=e[r];i&&(t[r]=i)}function Se(t,e){t.rawProgramDateTime?t.programDateTime=Date.parse(t.rawProgramDateTime):null!=e&&e.programDateTime&&(t.programDateTime=e.endProgramDateTime),y(t.programDateTime)||(t.programDateTime=null,t.rawProgramDateTime=null)}function Le(t,e,r,i){t.relurl=e.URI,e.BYTERANGE&&t.setByteRange(e.BYTERANGE),t.level=r,t.sn="initSegment",i&&(t.levelkeys=i),t.initSegment=null}function Ae(t,e,r){t.levelkeys=e;var i=r.encryptedFragments;i.length&&i[i.length-1].levelkeys===e||!Object.keys(e).some((function(t){return e[t].isCommonEncryption}))||i.push(t)}var Re="manifest",ke="level",be="audioTrack",De="subtitleTrack",Ie="main",we="audio",Ce="subtitle";function _e(t){switch(t.type){case be:return we;case De:return Ce;default:return Ie}}function xe(t,e){var r=t.url;return void 0!==r&&0!==r.indexOf("data:")||(r=e.url),r}var Pe=function(){function t(t){this.hls=void 0,this.loaders=Object.create(null),this.variableList=null,this.hls=t,this.registerListeners()}var e=t.prototype;return e.startLoad=function(t){},e.stopLoad=function(){this.destroyInternalLoaders()},e.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.AUDIO_TRACK_LOADING,this.onAudioTrackLoading,this),t.on(S.SUBTITLE_TRACK_LOADING,this.onSubtitleTrackLoading,this)},e.unregisterListeners=function(){var t=this.hls;t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_LOADING,this.onLevelLoading,this),t.off(S.AUDIO_TRACK_LOADING,this.onAudioTrackLoading,this),t.off(S.SUBTITLE_TRACK_LOADING,this.onSubtitleTrackLoading,this)},e.createInternalLoader=function(t){var e=this.hls.config,r=e.pLoader,i=e.loader,n=new(r||i)(e);return this.loaders[t.type]=n,n},e.getInternalLoader=function(t){return this.loaders[t.type]},e.resetInternalLoader=function(t){this.loaders[t]&&delete this.loaders[t]},e.destroyInternalLoaders=function(){for(var t in this.loaders){var e=this.loaders[t];e&&e.destroy(),this.resetInternalLoader(t)}},e.destroy=function(){this.variableList=null,this.unregisterListeners(),this.destroyInternalLoaders()},e.onManifestLoading=function(t,e){var r=e.url;this.variableList=null,this.load({id:null,level:0,responseType:"text",type:Re,url:r,deliveryDirectives:null})},e.onLevelLoading=function(t,e){var r=e.id,i=e.level,n=e.pathwayId,a=e.url,s=e.deliveryDirectives;this.load({id:r,level:i,pathwayId:n,responseType:"text",type:ke,url:a,deliveryDirectives:s})},e.onAudioTrackLoading=function(t,e){var r=e.id,i=e.groupId,n=e.url,a=e.deliveryDirectives;this.load({id:r,groupId:i,level:null,responseType:"text",type:be,url:n,deliveryDirectives:a})},e.onSubtitleTrackLoading=function(t,e){var r=e.id,i=e.groupId,n=e.url,a=e.deliveryDirectives;this.load({id:r,groupId:i,level:null,responseType:"text",type:De,url:n,deliveryDirectives:a})},e.load=function(t){var e,r,i,n=this,a=this.hls.config,s=this.getInternalLoader(t);if(s){var l=s.context;if(l&&l.url===t.url&&l.level===t.level)return void w.trace("[playlist-loader]: playlist request ongoing");w.log("[playlist-loader]: aborting previous loader for type: "+t.type),s.abort()}if(r=t.type===Re?a.manifestLoadPolicy.default:o({},a.playlistLoadPolicy.default,{timeoutRetry:null,errorRetry:null}),s=this.createInternalLoader(t),y(null==(e=t.deliveryDirectives)?void 0:e.part)&&(t.type===ke&&null!==t.level?i=this.hls.levels[t.level].details:t.type===be&&null!==t.id?i=this.hls.audioTracks[t.id].details:t.type===De&&null!==t.id&&(i=this.hls.subtitleTracks[t.id].details),i)){var u=i.partTarget,h=i.targetduration;if(u&&h){var d=1e3*Math.max(3*u,.8*h);r=o({},r,{maxTimeToFirstByteMs:Math.min(d,r.maxTimeToFirstByteMs),maxLoadTimeMs:Math.min(d,r.maxTimeToFirstByteMs)})}}var c=r.errorRetry||r.timeoutRetry||{},f={loadPolicy:r,timeout:r.maxLoadTimeMs,maxRetry:c.maxNumRetry||0,retryDelay:c.retryDelayMs||0,maxRetryDelay:c.maxRetryDelayMs||0},g={onSuccess:function(t,e,r,i){var a=n.getInternalLoader(r);n.resetInternalLoader(r.type);var s=t.data;0===s.indexOf("#EXTM3U")?(e.parsing.start=performance.now(),me.isMediaPlaylist(s)?n.handleTrackOrLevelPlaylist(t,e,r,i||null,a):n.handleMasterPlaylist(t,e,r,i)):n.handleManifestParsingError(t,r,new Error("no EXTM3U delimiter"),i||null,e)},onError:function(t,e,r,i){n.handleNetworkError(e,r,!1,t,i)},onTimeout:function(t,e,r){n.handleNetworkError(e,r,!0,void 0,t)}};s.load(t,f,g)},e.handleMasterPlaylist=function(t,e,r,i){var n=this.hls,a=t.data,s=xe(t,r),o=me.parseMasterPlaylist(a,s);if(o.playlistParsingError)this.handleManifestParsingError(t,r,o.playlistParsingError,i,e);else{var l=o.contentSteering,u=o.levels,h=o.sessionData,d=o.sessionKeys,c=o.startTimeOffset,f=o.variableList;this.variableList=f;var g=me.parseMasterPlaylistMedia(a,s,o),v=g.AUDIO,m=void 0===v?[]:v,p=g.SUBTITLES,y=g["CLOSED-CAPTIONS"];m.length&&(m.some((function(t){return!t.url}))||!u[0].audioCodec||u[0].attrs.AUDIO||(w.log("[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one"),m.unshift({type:"main",name:"main",groupId:"main",default:!1,autoselect:!1,forced:!1,id:-1,attrs:new x({}),bitrate:0,url:""}))),n.trigger(S.MANIFEST_LOADED,{levels:u,audioTracks:m,subtitles:p,captions:y,contentSteering:l,url:s,stats:e,networkDetails:i,sessionData:h,sessionKeys:d,startTimeOffset:c,variableList:f})}},e.handleTrackOrLevelPlaylist=function(t,e,r,i,n){var a=this.hls,s=r.id,o=r.level,l=r.type,u=xe(t,r),h=y(o)?o:y(s)?s:0,d=_e(r),c=me.parseLevelPlaylist(t.data,u,h,d,0,this.variableList);if(l===Re){var f={attrs:new x({}),bitrate:0,details:c,name:"",url:u};a.trigger(S.MANIFEST_LOADED,{levels:[f],audioTracks:[],url:u,stats:e,networkDetails:i,sessionData:null,sessionKeys:null,contentSteering:null,startTimeOffset:null,variableList:null})}e.parsing.end=performance.now(),r.levelDetails=c,this.handlePlaylistLoaded(c,t,e,r,i,n)},e.handleManifestParsingError=function(t,e,r,i,n){this.hls.trigger(S.ERROR,{type:L.NETWORK_ERROR,details:A.MANIFEST_PARSING_ERROR,fatal:e.type===Re,url:t.url,err:r,error:r,reason:r.message,response:t,context:e,networkDetails:i,stats:n})},e.handleNetworkError=function(t,e,r,n,a){void 0===r&&(r=!1);var s="A network "+(r?"timeout":"error"+(n?" (status "+n.code+")":""))+" occurred while loading "+t.type;t.type===ke?s+=": "+t.level+" id: "+t.id:t.type!==be&&t.type!==De||(s+=" id: "+t.id+' group-id: "'+t.groupId+'"');var o=new Error(s);w.warn("[playlist-loader]: "+s);var l=A.UNKNOWN,u=!1,h=this.getInternalLoader(t);switch(t.type){case Re:l=r?A.MANIFEST_LOAD_TIMEOUT:A.MANIFEST_LOAD_ERROR,u=!0;break;case ke:l=r?A.LEVEL_LOAD_TIMEOUT:A.LEVEL_LOAD_ERROR,u=!1;break;case be:l=r?A.AUDIO_TRACK_LOAD_TIMEOUT:A.AUDIO_TRACK_LOAD_ERROR,u=!1;break;case De:l=r?A.SUBTITLE_TRACK_LOAD_TIMEOUT:A.SUBTITLE_LOAD_ERROR,u=!1}h&&this.resetInternalLoader(t.type);var d={type:L.NETWORK_ERROR,details:l,fatal:u,url:t.url,loader:h,context:t,error:o,networkDetails:e,stats:a};if(n){var c=(null==e?void 0:e.url)||t.url;d.response=i({url:c,data:void 0},n)}this.hls.trigger(S.ERROR,d)},e.handlePlaylistLoaded=function(t,e,r,i,n,a){var s=this.hls,o=i.type,l=i.level,u=i.id,h=i.groupId,d=i.deliveryDirectives,c=xe(e,i),f=_e(i),g="number"==typeof i.level&&f===Ie?l:void 0;if(t.fragments.length){t.targetduration||(t.playlistParsingError=new Error("Missing Target Duration"));var v=t.playlistParsingError;if(v)s.trigger(S.ERROR,{type:L.NETWORK_ERROR,details:A.LEVEL_PARSING_ERROR,fatal:!1,url:c,error:v,reason:v.message,response:e,context:i,level:g,parent:f,networkDetails:n,stats:r});else switch(t.live&&a&&(a.getCacheAge&&(t.ageHeader=a.getCacheAge()||0),a.getCacheAge&&!isNaN(t.ageHeader)||(t.ageHeader=0)),o){case Re:case ke:s.trigger(S.LEVEL_LOADED,{details:t,level:g||0,id:u||0,stats:r,networkDetails:n,deliveryDirectives:d});break;case be:s.trigger(S.AUDIO_TRACK_LOADED,{details:t,id:u||0,groupId:h||"",stats:r,networkDetails:n,deliveryDirectives:d});break;case De:s.trigger(S.SUBTITLE_TRACK_LOADED,{details:t,id:u||0,groupId:h||"",stats:r,networkDetails:n,deliveryDirectives:d})}}else{var m=new Error("No Segments found in Playlist");s.trigger(S.ERROR,{type:L.NETWORK_ERROR,details:A.LEVEL_EMPTY_ERROR,fatal:!1,url:c,error:m,reason:m.message,response:e,context:i,level:g,parent:f,networkDetails:n,stats:r})}},t}();function Fe(t,e){var r;try{r=new Event("addtrack")}catch(t){(r=document.createEvent("Event")).initEvent("addtrack",!1,!1)}r.track=t,e.dispatchEvent(r)}function Me(t,e){var r=t.mode;if("disabled"===r&&(t.mode="hidden"),t.cues&&!t.cues.getCueById(e.id))try{if(t.addCue(e),!t.cues.getCueById(e.id))throw new Error("addCue is failed for: "+e)}catch(r){w.debug("[texttrack-utils]: "+r);try{var i=new self.TextTrackCue(e.startTime,e.endTime,e.text);i.id=e.id,t.addCue(i)}catch(t){w.debug("[texttrack-utils]: Legacy TextTrackCue fallback failed: "+t)}}"disabled"===r&&(t.mode=r)}function Oe(t){var e=t.mode;if("disabled"===e&&(t.mode="hidden"),t.cues)for(var r=t.cues.length;r--;)t.removeCue(t.cues[r]);"disabled"===e&&(t.mode=e)}function Ne(t,e,r,i){var n=t.mode;if("disabled"===n&&(t.mode="hidden"),t.cues&&t.cues.length>0)for(var a=function(t,e,r){var i=[],n=function(t,e){if(e<t[0].startTime)return 0;var r=t.length-1;if(e>t[r].endTime)return-1;for(var i=0,n=r;i<=n;){var a=Math.floor((n+i)/2);if(e<t[a].startTime)n=a-1;else{if(!(e>t[a].startTime&&i<r))return a;i=a+1}}return t[i].startTime-e<e-t[n].startTime?i:n}(t,e);if(n>-1)for(var a=n,s=t.length;a<s;a++){var o=t[a];if(o.startTime>=e&&o.endTime<=r)i.push(o);else if(o.startTime>r)return i}return i}(t.cues,e,r),s=0;s<a.length;s++)i&&!i(a[s])||t.removeCue(a[s]);"disabled"===n&&(t.mode=n)}function Ue(t){for(var e=[],r=0;r<t.length;r++){var i=t[r];"subtitles"!==i.kind&&"captions"!==i.kind||!i.label||e.push(t[r])}return e}var Be="org.id3",Ge="com.apple.quicktime.HLS",Ke="https://aomedia.org/emsg/ID3";function He(){if("undefined"!=typeof self)return self.VTTCue||self.TextTrackCue}function Ve(t,e,r,n,a){var s=new t(e,r,"");try{s.value=n,a&&(s.type=a)}catch(o){s=new t(e,r,JSON.stringify(a?i({type:a},n):n))}return s}var Ye=function(){var t=He();try{t&&new t(0,Number.POSITIVE_INFINITY,"")}catch(t){return Number.MAX_VALUE}return Number.POSITIVE_INFINITY}();function We(t,e){return t.getTime()/1e3-e}var je=function(){function t(t){this.hls=void 0,this.id3Track=null,this.media=null,this.dateRangeCuesAppended={},this.hls=t,this._registerListeners()}var e=t.prototype;return e.destroy=function(){this._unregisterListeners(),this.id3Track=null,this.media=null,this.dateRangeCuesAppended={},this.hls=null},e._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.FRAG_PARSING_METADATA,this.onFragParsingMetadata,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.LEVEL_UPDATED,this.onLevelUpdated,this)},e._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.FRAG_PARSING_METADATA,this.onFragParsingMetadata,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.LEVEL_UPDATED,this.onLevelUpdated,this)},e.onMediaAttached=function(t,e){this.media=e.media},e.onMediaDetaching=function(){this.id3Track&&(Oe(this.id3Track),this.id3Track=null,this.media=null,this.dateRangeCuesAppended={})},e.onManifestLoading=function(){this.dateRangeCuesAppended={}},e.createTrack=function(t){var e=this.getID3Track(t.textTracks);return e.mode="hidden",e},e.getID3Track=function(t){if(this.media){for(var e=0;e<t.length;e++){var r=t[e];if("metadata"===r.kind&&"id3"===r.label)return Fe(r,this.media),r}return this.media.addTextTrack("metadata","id3")}},e.onFragParsingMetadata=function(t,e){if(this.media){var r=this.hls.config,i=r.enableEmsgMetadataCues,n=r.enableID3MetadataCues;if(i||n){var a=e.samples;this.id3Track||(this.id3Track=this.createTrack(this.media));var s=He();if(s)for(var o=0;o<a.length;o++){var l=a[o].type;if((l!==Ke||i)&&n){var u=gt(a[o].data);if(u){var h=a[o].pts,d=h+a[o].duration;d>Ye&&(d=Ye),d-h<=0&&(d=h+.25);for(var c=0;c<u.length;c++){var f=u[c];if(!ct(f)){this.updateId3CueEnds(h,l);var g=Ve(s,h,d,f,l);g&&this.id3Track.addCue(g)}}}}}}}},e.updateId3CueEnds=function(t,e){var r,i=null==(r=this.id3Track)?void 0:r.cues;if(i)for(var n=i.length;n--;){var a=i[n];a.type===e&&a.startTime<t&&a.endTime===Ye&&(a.endTime=t)}},e.onBufferFlushing=function(t,e){var r=e.startOffset,i=e.endOffset,n=e.type,a=this.id3Track,s=this.hls;if(s){var o=s.config,l=o.enableEmsgMetadataCues,u=o.enableID3MetadataCues;a&&(l||u)&&Ne(a,r,i,"audio"===n?function(t){return t.type===Be&&u}:"video"===n?function(t){return t.type===Ke&&l}:function(t){return t.type===Be&&u||t.type===Ke&&l})}},e.onLevelUpdated=function(t,e){var r=this,i=e.details;if(this.media&&i.hasProgramDateTime&&this.hls.config.enableDateRangeMetadataCues){var n=this.dateRangeCuesAppended,a=this.id3Track,s=i.dateRanges,o=Object.keys(s);if(a)for(var l=Object.keys(n).filter((function(t){return!o.includes(t)})),u=function(){var t=l[h];Object.keys(n[t].cues).forEach((function(e){a.removeCue(n[t].cues[e])})),delete n[t]},h=l.length;h--;)u();var d=i.fragments[i.fragments.length-1];if(0!==o.length&&y(null==d?void 0:d.programDateTime)){this.id3Track||(this.id3Track=this.createTrack(this.media));for(var c=d.programDateTime/1e3-d.start,f=He(),g=function(){var t=o[v],e=s[t],i=We(e.startDate,c),a=n[t],l=(null==a?void 0:a.cues)||{},u=(null==a?void 0:a.durationKnown)||!1,h=Ye,d=e.endDate;if(d)h=We(d,c),u=!0;else if(e.endOnNext&&!u){var g=o.reduce((function(t,r){if(r!==e.id){var i=s[r];if(i.class===e.class&&i.startDate>e.startDate&&(!t||e.startDate<t.startDate))return i}return t}),null);g&&(h=We(g.startDate,c),u=!0)}for(var m,p,y=Object.keys(e.attr),E=0;E<y.length;E++){var T=y[E];if("ID"!==(p=T)&&"CLASS"!==p&&"START-DATE"!==p&&"DURATION"!==p&&"END-DATE"!==p&&"END-ON-NEXT"!==p){var S=l[T];if(S)u&&!a.durationKnown&&(S.endTime=h);else if(f){var L=e.attr[T];P(T)&&(m=L,L=Uint8Array.from(m.replace(/^0x/,"").replace(/([\da-fA-F]{2}) ?/g,"0x$1 ").replace(/ +$/,"").split(" ")).buffer);var A=Ve(f,i,h,{key:T,data:L},Ge);A&&(A.id=t,r.id3Track.addCue(A),l[T]=A)}}}n[t]={cues:l,dateRange:e,durationKnown:u}},v=0;v<o.length;v++)g()}}},t}(),qe=function(){function t(t){var e=this;this.hls=void 0,this.config=void 0,this.media=null,this.levelDetails=null,this.currentTime=0,this.stallCount=0,this._latency=null,this.timeupdateHandler=function(){return e.timeupdate()},this.hls=t,this.config=t.config,this.registerListeners()}var e=t.prototype;return e.destroy=function(){this.unregisterListeners(),this.onMediaDetaching(),this.levelDetails=null,this.hls=this.timeupdateHandler=null},e.registerListeners=function(){this.hls.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),this.hls.on(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.on(S.LEVEL_UPDATED,this.onLevelUpdated,this),this.hls.on(S.ERROR,this.onError,this)},e.unregisterListeners=function(){this.hls.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),this.hls.off(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.off(S.LEVEL_UPDATED,this.onLevelUpdated,this),this.hls.off(S.ERROR,this.onError,this)},e.onMediaAttached=function(t,e){this.media=e.media,this.media.addEventListener("timeupdate",this.timeupdateHandler)},e.onMediaDetaching=function(){this.media&&(this.media.removeEventListener("timeupdate",this.timeupdateHandler),this.media=null)},e.onManifestLoading=function(){this.levelDetails=null,this._latency=null,this.stallCount=0},e.onLevelUpdated=function(t,e){var r=e.details;this.levelDetails=r,r.advanced&&this.timeupdate(),!r.live&&this.media&&this.media.removeEventListener("timeupdate",this.timeupdateHandler)},e.onError=function(t,e){var r;e.details===A.BUFFER_STALLED_ERROR&&(this.stallCount++,null!=(r=this.levelDetails)&&r.live&&w.warn("[playback-rate-controller]: Stall detected, adjusting target latency"))},e.timeupdate=function(){var t=this.media,e=this.levelDetails;if(t&&e){this.currentTime=t.currentTime;var r=this.computeLatency();if(null!==r){this._latency=r;var i=this.config,n=i.lowLatencyMode,a=i.maxLiveSyncPlaybackRate;if(n&&1!==a&&e.live){var s=this.targetLatency;if(null!==s){var o=r-s;if(o<Math.min(this.maxLatency,s+e.targetduration)&&o>.05&&this.forwardBufferLength>1){var l=Math.min(2,Math.max(1,a)),u=Math.round(2/(1+Math.exp(-.75*o-this.edgeStalled))*20)/20;t.playbackRate=Math.min(l,Math.max(1,u))}else 1!==t.playbackRate&&0!==t.playbackRate&&(t.playbackRate=1)}}}}},e.estimateLiveEdge=function(){var t=this.levelDetails;return null===t?null:t.edge+t.age},e.computeLatency=function(){var t=this.estimateLiveEdge();return null===t?null:t-this.currentTime},s(t,[{key:"latency",get:function(){return this._latency||0}},{key:"maxLatency",get:function(){var t=this.config,e=this.levelDetails;return void 0!==t.liveMaxLatencyDuration?t.liveMaxLatencyDuration:e?t.liveMaxLatencyDurationCount*e.targetduration:0}},{key:"targetLatency",get:function(){var t=this.levelDetails;if(null===t)return null;var e=t.holdBack,r=t.partHoldBack,i=t.targetduration,n=this.config,a=n.liveSyncDuration,s=n.liveSyncDurationCount,o=n.lowLatencyMode,l=this.hls.userConfig,u=o&&r||e;(l.liveSyncDuration||l.liveSyncDurationCount||0===u)&&(u=void 0!==a?a:s*i);var h=i;return u+Math.min(1*this.stallCount,h)}},{key:"liveSyncPosition",get:function(){var t=this.estimateLiveEdge(),e=this.targetLatency,r=this.levelDetails;if(null===t||null===e||null===r)return null;var i=r.edge,n=t-e-this.edgeStalled,a=i-r.totalduration,s=i-(this.config.lowLatencyMode&&r.partTarget||r.targetduration);return Math.min(Math.max(a,n),s)}},{key:"drift",get:function(){var t=this.levelDetails;return null===t?1:t.drift}},{key:"edgeStalled",get:function(){var t=this.levelDetails;if(null===t)return 0;var e=3*(this.config.lowLatencyMode&&t.partTarget||t.targetduration);return Math.max(t.age-e,0)}},{key:"forwardBufferLength",get:function(){var t=this.media,e=this.levelDetails;if(!t||!e)return 0;var r=t.buffered.length;return(r?t.buffered.end(r-1):e.edge)-this.currentTime}}]),t}(),Xe=["NONE","TYPE-0","TYPE-1",null],ze=["SDR","PQ","HLG"],Qe="",Je="YES",$e="v2",Ze=function(){function t(t,e,r){this.msn=void 0,this.part=void 0,this.skip=void 0,this.msn=t,this.part=e,this.skip=r}return t.prototype.addDirectives=function(t){var e=new self.URL(t);return void 0!==this.msn&&e.searchParams.set("_HLS_msn",this.msn.toString()),void 0!==this.part&&e.searchParams.set("_HLS_part",this.part.toString()),this.skip&&e.searchParams.set("_HLS_skip",this.skip),e.href},t}(),tr=function(){function t(t){this._attrs=void 0,this.audioCodec=void 0,this.bitrate=void 0,this.codecSet=void 0,this.url=void 0,this.frameRate=void 0,this.height=void 0,this.id=void 0,this.name=void 0,this.videoCodec=void 0,this.width=void 0,this.details=void 0,this.fragmentError=0,this.loadError=0,this.loaded=void 0,this.realBitrate=0,this.supportedPromise=void 0,this.supportedResult=void 0,this._avgBitrate=0,this._audioGroups=void 0,this._subtitleGroups=void 0,this._urlId=0,this.url=[t.url],this._attrs=[t.attrs],this.bitrate=t.bitrate,t.details&&(this.details=t.details),this.id=t.id||0,this.name=t.name,this.width=t.width||0,this.height=t.height||0,this.frameRate=t.attrs.optionalFloat("FRAME-RATE",0),this._avgBitrate=t.attrs.decimalInteger("AVERAGE-BANDWIDTH"),this.audioCodec=t.audioCodec,this.videoCodec=t.videoCodec,this.codecSet=[t.videoCodec,t.audioCodec].filter((function(t){return!!t})).map((function(t){return t.substring(0,4)})).join(","),this.addGroupId("audio",t.attrs.AUDIO),this.addGroupId("text",t.attrs.SUBTITLES)}var e=t.prototype;return e.hasAudioGroup=function(t){return er(this._audioGroups,t)},e.hasSubtitleGroup=function(t){return er(this._subtitleGroups,t)},e.addGroupId=function(t,e){if(e)if("audio"===t){var r=this._audioGroups;r||(r=this._audioGroups=[]),-1===r.indexOf(e)&&r.push(e)}else if("text"===t){var i=this._subtitleGroups;i||(i=this._subtitleGroups=[]),-1===i.indexOf(e)&&i.push(e)}},e.addFallback=function(){},s(t,[{key:"maxBitrate",get:function(){return Math.max(this.realBitrate,this.bitrate)}},{key:"averageBitrate",get:function(){return this._avgBitrate||this.realBitrate||this.bitrate}},{key:"attrs",get:function(){return this._attrs[0]}},{key:"codecs",get:function(){return this.attrs.CODECS||""}},{key:"pathwayId",get:function(){return this.attrs["PATHWAY-ID"]||"."}},{key:"videoRange",get:function(){return this.attrs["VIDEO-RANGE"]||"SDR"}},{key:"score",get:function(){return this.attrs.optionalFloat("SCORE",0)}},{key:"uri",get:function(){return this.url[0]||""}},{key:"audioGroups",get:function(){return this._audioGroups}},{key:"subtitleGroups",get:function(){return this._subtitleGroups}},{key:"urlId",get:function(){return 0},set:function(t){}},{key:"audioGroupIds",get:function(){return this.audioGroups?[this.audioGroupId]:void 0}},{key:"textGroupIds",get:function(){return this.subtitleGroups?[this.textGroupId]:void 0}},{key:"audioGroupId",get:function(){var t;return null==(t=this.audioGroups)?void 0:t[0]}},{key:"textGroupId",get:function(){var t;return null==(t=this.subtitleGroups)?void 0:t[0]}}]),t}();function er(t,e){return!(!e||!t)&&-1!==t.indexOf(e)}function rr(t,e){var r=e.startPTS;if(y(r)){var i,n=0;e.sn>t.sn?(n=r-t.start,i=t):(n=t.start-r,i=e),i.duration!==n&&(i.duration=n)}else e.sn>t.sn?t.cc===e.cc&&t.minEndPTS?e.start=t.start+(t.minEndPTS-t.start):e.start=t.start+t.duration:e.start=Math.max(t.start-e.duration,0)}function ir(t,e,r,i,n,a){i-r<=0&&(w.warn("Fragment should have a positive duration",e),i=r+e.duration,a=n+e.duration);var s=r,o=i,l=e.startPTS,u=e.endPTS;if(y(l)){var h=Math.abs(l-r);y(e.deltaPTS)?e.deltaPTS=Math.max(h,e.deltaPTS):e.deltaPTS=h,s=Math.max(r,l),r=Math.min(r,l),n=Math.min(n,e.startDTS),o=Math.min(i,u),i=Math.max(i,u),a=Math.max(a,e.endDTS)}var d=r-e.start;0!==e.start&&(e.start=r),e.duration=i-e.start,e.startPTS=r,e.maxStartPTS=s,e.startDTS=n,e.endPTS=i,e.minEndPTS=o,e.endDTS=a;var c,f=e.sn;if(!t||f<t.startSN||f>t.endSN)return 0;var g=f-t.startSN,v=t.fragments;for(v[g]=e,c=g;c>0;c--)rr(v[c],v[c-1]);for(c=g;c<v.length-1;c++)rr(v[c],v[c+1]);return t.fragmentHint&&rr(v[v.length-1],t.fragmentHint),t.PTSKnown=t.alignedSliding=!0,d}function nr(t,e){for(var r=null,i=t.fragments,n=i.length-1;n>=0;n--){var a=i[n].initSegment;if(a){r=a;break}}t.fragmentHint&&delete t.fragmentHint.endPTS;var s,l,u,h,d,c=0;if(function(t,e,r){for(var i=e.skippedSegments,n=Math.max(t.startSN,e.startSN)-e.startSN,a=(t.fragmentHint?1:0)+(i?e.endSN:Math.min(t.endSN,e.endSN))-e.startSN,s=e.startSN-t.startSN,o=e.fragmentHint?e.fragments.concat(e.fragmentHint):e.fragments,l=t.fragmentHint?t.fragments.concat(t.fragmentHint):t.fragments,u=n;u<=a;u++){var h=l[s+u],d=o[u];i&&!d&&u<i&&(d=e.fragments[u]=h),h&&d&&r(h,d)}}(t,e,(function(t,i){t.relurl&&(c=t.cc-i.cc),y(t.startPTS)&&y(t.endPTS)&&(i.start=i.startPTS=t.startPTS,i.startDTS=t.startDTS,i.maxStartPTS=t.maxStartPTS,i.endPTS=t.endPTS,i.endDTS=t.endDTS,i.minEndPTS=t.minEndPTS,i.duration=t.endPTS-t.startPTS,i.duration&&(s=i),e.PTSKnown=e.alignedSliding=!0),i.elementaryStreams=t.elementaryStreams,i.loader=t.loader,i.stats=t.stats,t.initSegment&&(i.initSegment=t.initSegment,r=t.initSegment)})),r&&(e.fragmentHint?e.fragments.concat(e.fragmentHint):e.fragments).forEach((function(t){var e;!t||t.initSegment&&t.initSegment.relurl!==(null==(e=r)?void 0:e.relurl)||(t.initSegment=r)})),e.skippedSegments)if(e.deltaUpdateFailed=e.fragments.some((function(t){return!t})),e.deltaUpdateFailed){w.warn("[level-helper] Previous playlist missing segments skipped in delta playlist");for(var f=e.skippedSegments;f--;)e.fragments.shift();e.startSN=e.fragments[0].sn,e.startCC=e.fragments[0].cc}else e.canSkipDateRanges&&(e.dateRanges=(l=t.dateRanges,u=e.dateRanges,h=e.recentlyRemovedDateranges,d=o({},l),h&&h.forEach((function(t){delete d[t]})),Object.keys(u).forEach((function(t){var e=new F(u[t].attr,d[t]);e.isValid?d[t]=e:w.warn('Ignoring invalid Playlist Delta Update DATERANGE tag: "'+JSON.stringify(u[t].attr)+'"')})),d));var g=e.fragments;if(c){w.warn("discontinuity sliding from playlist, take drift into account");for(var v=0;v<g.length;v++)g[v].cc+=c}e.skippedSegments&&(e.startCC=e.fragments[0].cc),function(t,e,r){if(t&&e)for(var i=0,n=0,a=t.length;n<=a;n++){var s=t[n],o=e[n+i];s&&o&&s.index===o.index&&s.fragment.sn===o.fragment.sn?r(s,o):i--}}(t.partList,e.partList,(function(t,e){e.elementaryStreams=t.elementaryStreams,e.stats=t.stats})),s?ir(e,s,s.startPTS,s.endPTS,s.startDTS,s.endDTS):ar(t,e),g.length&&(e.totalduration=e.edge-g[0].start),e.driftStartTime=t.driftStartTime,e.driftStart=t.driftStart;var m=e.advancedDateTime;if(e.advanced&&m){var p=e.edge;e.driftStart||(e.driftStartTime=m,e.driftStart=p),e.driftEndTime=m,e.driftEnd=p}else e.driftEndTime=t.driftEndTime,e.driftEnd=t.driftEnd,e.advancedDateTime=t.advancedDateTime}function ar(t,e){var r=e.startSN+e.skippedSegments-t.startSN,i=t.fragments;r<0||r>=i.length||sr(e,i[r].start)}function sr(t,e){if(e){for(var r=t.fragments,i=t.skippedSegments;i<r.length;i++)r[i].start+=e;t.fragmentHint&&(t.fragmentHint.start+=e)}}function or(t,e,r){var i;return null!=t&&t.details?lr(null==(i=t.details)?void 0:i.partList,e,r):null}function lr(t,e,r){if(t)for(var i=t.length;i--;){var n=t[i];if(n.index===r&&n.fragment.sn===e)return n}return null}function ur(t){t.forEach((function(t,e){var r=t.details;null!=r&&r.fragments&&r.fragments.forEach((function(t){t.level=e}))}))}function hr(t){switch(t.details){case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_TIMEOUT:case A.LEVEL_LOAD_TIMEOUT:case A.MANIFEST_LOAD_TIMEOUT:return!0}return!1}function dr(t,e){var r=hr(e);return t.default[(r?"timeout":"error")+"Retry"]}function cr(t,e){var r="linear"===t.backoff?1:Math.pow(2,e);return Math.min(r*t.retryDelayMs,t.maxRetryDelayMs)}function fr(t){return i(i({},t),{errorRetry:null,timeoutRetry:null})}function gr(t,e,r,i){if(!t)return!1;var n=null==i?void 0:i.code,a=e<t.maxNumRetry&&(function(t){return 0===t&&!1===navigator.onLine||!!t&&(t<400||t>499)}(n)||!!r);return t.shouldRetry?t.shouldRetry(t,e,r,i,a):a}var vr=function(t,e){for(var r=0,i=t.length-1,n=null,a=null;r<=i;){var s=e(a=t[n=(r+i)/2|0]);if(s>0)r=n+1;else{if(!(s<0))return a;i=n-1}}return null};function mr(t,e,r,i){void 0===r&&(r=0),void 0===i&&(i=0);var n=null;if(t){n=e[t.sn-e[0].sn+1]||null;var a=t.endDTS-r;a>0&&a<15e-7&&(r+=15e-7)}else 0===r&&0===e[0].start&&(n=e[0]);if(n&&(!t||t.level===n.level)&&0===pr(r,i,n))return n;var s=vr(e,pr.bind(null,r,i));return!s||s===t&&n?n:s}function pr(t,e,r){if(void 0===t&&(t=0),void 0===e&&(e=0),r.start<=t&&r.start+r.duration>t)return 0;var i=Math.min(e,r.duration+(r.deltaPTS?r.deltaPTS:0));return r.start+r.duration-i<=t?1:r.start-i>t&&r.start?-1:0}function yr(t,e,r){var i=1e3*Math.min(e,r.duration+(r.deltaPTS?r.deltaPTS:0));return(r.endProgramDateTime||0)-i>t}var Er=0,Tr=2,Sr=3,Lr=5,Ar=0,Rr=1,kr=2,br=function(){function t(t){this.hls=void 0,this.playlistError=0,this.penalizedRenditions={},this.log=void 0,this.warn=void 0,this.error=void 0,this.hls=t,this.log=w.log.bind(w,"[info]:"),this.warn=w.warn.bind(w,"[warning]:"),this.error=w.error.bind(w,"[error]:"),this.registerListeners()}var e=t.prototype;return e.registerListeners=function(){var t=this.hls;t.on(S.ERROR,this.onError,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_UPDATED,this.onLevelUpdated,this)},e.unregisterListeners=function(){var t=this.hls;t&&(t.off(S.ERROR,this.onError,this),t.off(S.ERROR,this.onErrorOut,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_UPDATED,this.onLevelUpdated,this))},e.destroy=function(){this.unregisterListeners(),this.hls=null,this.penalizedRenditions={}},e.startLoad=function(t){},e.stopLoad=function(){this.playlistError=0},e.getVariantLevelIndex=function(t){return(null==t?void 0:t.type)===Ie?t.level:this.hls.loadLevel},e.onManifestLoading=function(){this.playlistError=0,this.penalizedRenditions={}},e.onLevelUpdated=function(){this.playlistError=0},e.onError=function(t,e){var r,i;if(!e.fatal){var n=this.hls,a=e.context;switch(e.details){case A.FRAG_LOAD_ERROR:case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_ERROR:case A.KEY_LOAD_TIMEOUT:return void(e.errorAction=this.getFragRetryOrSwitchAction(e));case A.FRAG_PARSING_ERROR:if(null!=(r=e.frag)&&r.gap)return void(e.errorAction={action:Er,flags:Ar});case A.FRAG_GAP:case A.FRAG_DECRYPT_ERROR:return e.errorAction=this.getFragRetryOrSwitchAction(e),void(e.errorAction.action=Tr);case A.LEVEL_EMPTY_ERROR:case A.LEVEL_PARSING_ERROR:var s,o,l=e.parent===Ie?e.level:n.loadLevel;return void(e.details===A.LEVEL_EMPTY_ERROR&&null!=(s=e.context)&&null!=(o=s.levelDetails)&&o.live?e.errorAction=this.getPlaylistRetryOrSwitchAction(e,l):(e.levelRetry=!1,e.errorAction=this.getLevelSwitchAction(e,l)));case A.LEVEL_LOAD_ERROR:case A.LEVEL_LOAD_TIMEOUT:return void("number"==typeof(null==a?void 0:a.level)&&(e.errorAction=this.getPlaylistRetryOrSwitchAction(e,a.level)));case A.AUDIO_TRACK_LOAD_ERROR:case A.AUDIO_TRACK_LOAD_TIMEOUT:case A.SUBTITLE_LOAD_ERROR:case A.SUBTITLE_TRACK_LOAD_TIMEOUT:if(a){var u=n.levels[n.loadLevel];if(u&&(a.type===be&&u.hasAudioGroup(a.groupId)||a.type===De&&u.hasSubtitleGroup(a.groupId)))return e.errorAction=this.getPlaylistRetryOrSwitchAction(e,n.loadLevel),e.errorAction.action=Tr,void(e.errorAction.flags=Rr)}return;case A.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:var h=n.levels[n.loadLevel],d=null==h?void 0:h.attrs["HDCP-LEVEL"];return void(d?e.errorAction={action:Tr,flags:kr,hdcpLevel:d}:this.keySystemError(e));case A.BUFFER_ADD_CODEC_ERROR:case A.REMUX_ALLOC_ERROR:case A.BUFFER_APPEND_ERROR:return void(e.errorAction=this.getLevelSwitchAction(e,null!=(i=e.level)?i:n.loadLevel));case A.INTERNAL_EXCEPTION:case A.BUFFER_APPENDING_ERROR:case A.BUFFER_FULL_ERROR:case A.LEVEL_SWITCH_ERROR:case A.BUFFER_STALLED_ERROR:case A.BUFFER_SEEK_OVER_HOLE:case A.BUFFER_NUDGE_ON_STALL:return void(e.errorAction={action:Er,flags:Ar})}e.type===L.KEY_SYSTEM_ERROR&&this.keySystemError(e)}},e.keySystemError=function(t){var e=this.getVariantLevelIndex(t.frag);t.levelRetry=!1,t.errorAction=this.getLevelSwitchAction(t,e)},e.getPlaylistRetryOrSwitchAction=function(t,e){var r=dr(this.hls.config.playlistLoadPolicy,t),i=this.playlistError++;if(gr(r,i,hr(t),t.response))return{action:Lr,flags:Ar,retryConfig:r,retryCount:i};var n=this.getLevelSwitchAction(t,e);return r&&(n.retryConfig=r,n.retryCount=i),n},e.getFragRetryOrSwitchAction=function(t){var e=this.hls,r=this.getVariantLevelIndex(t.frag),i=e.levels[r],n=e.config,a=n.fragLoadPolicy,s=n.keyLoadPolicy,o=dr(t.details.startsWith("key")?s:a,t),l=e.levels.reduce((function(t,e){return t+e.fragmentError}),0);if(i&&(t.details!==A.FRAG_GAP&&i.fragmentError++,gr(o,l,hr(t),t.response)))return{action:Lr,flags:Ar,retryConfig:o,retryCount:l};var u=this.getLevelSwitchAction(t,r);return o&&(u.retryConfig=o,u.retryCount=l),u},e.getLevelSwitchAction=function(t,e){var r=this.hls;null==e&&(e=r.loadLevel);var i=this.hls.levels[e];if(i){var n,a,s=t.details;i.loadError++,s===A.BUFFER_APPEND_ERROR&&i.fragmentError++;var o=-1,l=r.levels,u=r.loadLevel,h=r.minAutoLevel,d=r.maxAutoLevel;r.autoLevelEnabled||(r.loadLevel=-1);for(var c,f=null==(n=t.frag)?void 0:n.type,g=(f===we&&s===A.FRAG_PARSING_ERROR||"audio"===t.sourceBufferName&&(s===A.BUFFER_ADD_CODEC_ERROR||s===A.BUFFER_APPEND_ERROR))&&l.some((function(t){var e=t.audioCodec;return i.audioCodec!==e})),v="video"===t.sourceBufferName&&(s===A.BUFFER_ADD_CODEC_ERROR||s===A.BUFFER_APPEND_ERROR)&&l.some((function(t){var e=t.codecSet,r=t.audioCodec;return i.codecSet!==e&&i.audioCodec===r})),m=null!=(a=t.context)?a:{},p=m.type,y=m.groupId,E=function(){var e=(T+u)%l.length;if(e!==u&&e>=h&&e<=d&&0===l[e].loadError){var r,n,a=l[e];if(s===A.FRAG_GAP&&t.frag){var c=l[e].details;if(c){var m=mr(t.frag,c.fragments,t.frag.start);if(null!=m&&m.gap)return 0}}else{if(p===be&&a.hasAudioGroup(y)||p===De&&a.hasSubtitleGroup(y))return 0;if(f===we&&null!=(r=i.audioGroups)&&r.some((function(t){return a.hasAudioGroup(t)}))||f===Ce&&null!=(n=i.subtitleGroups)&&n.some((function(t){return a.hasSubtitleGroup(t)}))||g&&i.audioCodec===a.audioCodec||!g&&i.audioCodec!==a.audioCodec||v&&i.codecSet===a.codecSet)return 0}return o=e,1}},T=l.length;T--&&(0===(c=E())||1!==c););if(o>-1&&r.loadLevel!==o)return t.levelRetry=!0,this.playlistError=0,{action:Tr,flags:Ar,nextAutoLevel:o}}return{action:Tr,flags:Rr}},e.onErrorOut=function(t,e){var r;switch(null==(r=e.errorAction)?void 0:r.action){case Er:break;case Tr:this.sendAlternateToPenaltyBox(e),e.errorAction.resolved||e.details===A.FRAG_GAP?/MediaSource readyState: ended/.test(e.error.message)&&(this.warn('MediaSource ended after "'+e.sourceBufferName+'" sourceBuffer append error. Attempting to recover from media error.'),this.hls.recoverMediaError()):e.fatal=!0}e.fatal&&this.hls.stopLoad()},e.sendAlternateToPenaltyBox=function(t){var e=this.hls,r=t.errorAction;if(r){var i=r.flags,n=r.hdcpLevel,a=r.nextAutoLevel;switch(i){case Ar:this.switchLevel(t,a);break;case kr:n&&(e.maxHdcpLevel=Xe[Xe.indexOf(n)-1],r.resolved=!0),this.warn('Restricting playback to HDCP-LEVEL of "'+e.maxHdcpLevel+'" or lower')}r.resolved||this.switchLevel(t,a)}},e.switchLevel=function(t,e){void 0!==e&&t.errorAction&&(this.warn("switching to level "+e+" after "+t.details),this.hls.nextAutoLevel=e,t.errorAction.resolved=!0,this.hls.nextLoadLevel=this.hls.nextAutoLevel)},t}(),Dr=function(){function t(t,e){this.hls=void 0,this.timer=-1,this.requestScheduled=-1,this.canLoad=!1,this.log=void 0,this.warn=void 0,this.log=w.log.bind(w,e+":"),this.warn=w.warn.bind(w,e+":"),this.hls=t}var e=t.prototype;return e.destroy=function(){this.clearTimer(),this.hls=this.log=this.warn=null},e.clearTimer=function(){-1!==this.timer&&(self.clearTimeout(this.timer),this.timer=-1)},e.startLoad=function(){this.canLoad=!0,this.requestScheduled=-1,this.loadPlaylist()},e.stopLoad=function(){this.canLoad=!1,this.clearTimer()},e.switchParams=function(t,e){var r=null==e?void 0:e.renditionReports;if(r){for(var i=-1,n=0;n<r.length;n++){var a=r[n],s=void 0;try{s=new self.URL(a.URI,e.url).href}catch(t){w.warn("Could not construct new URL for Rendition Report: "+t),s=a.URI||""}if(s===t){i=n;break}s===t.substring(0,s.length)&&(i=n)}if(-1!==i){var o=r[i],l=parseInt(o["LAST-MSN"])||(null==e?void 0:e.lastPartSn),u=parseInt(o["LAST-PART"])||(null==e?void 0:e.lastPartIndex);if(this.hls.config.lowLatencyMode){var h=Math.min(e.age-e.partTarget,e.targetduration);u>=0&&h>e.partTarget&&(u+=1)}return new Ze(l,u>=0?u:void 0,Qe)}}},e.loadPlaylist=function(t){-1===this.requestScheduled&&(this.requestScheduled=self.performance.now())},e.shouldLoadPlaylist=function(t){return this.canLoad&&!!t&&!!t.url&&(!t.details||t.details.live)},e.shouldReloadPlaylist=function(t){return-1===this.timer&&-1===this.requestScheduled&&this.shouldLoadPlaylist(t)},e.playlistLoaded=function(t,e,r){var i=this,n=e.details,a=e.stats,s=self.performance.now(),o=a.loading.first?Math.max(0,s-a.loading.first):0;if(n.advancedDateTime=Date.now()-o,n.live||null!=r&&r.live){if(n.reloaded(r),r&&this.log("live playlist "+t+" "+(n.advanced?"REFRESHED "+n.lastPartSn+"-"+n.lastPartIndex:n.updated?"UPDATED":"MISSED")),r&&n.fragments.length>0&&nr(r,n),!this.canLoad||!n.live)return;var l,u=void 0,h=void 0;if(n.canBlockReload&&n.endSN&&n.advanced){var d=this.hls.config.lowLatencyMode,c=n.lastPartSn,f=n.endSN,g=n.lastPartIndex,v=c===f;-1!==g?(u=v?f+1:c,h=v?d?0:g:g+1):u=f+1;var m=n.age,p=m+n.ageHeader,y=Math.min(p-n.partTarget,1.5*n.targetduration);if(y>0){if(r&&y>r.tuneInGoal)this.warn("CDN Tune-in goal increased from: "+r.tuneInGoal+" to: "+y+" with playlist age: "+n.age),y=0;else{var E=Math.floor(y/n.targetduration);u+=E,void 0!==h&&(h+=Math.round(y%n.targetduration/n.partTarget)),this.log("CDN Tune-in age: "+n.ageHeader+"s last advanced "+m.toFixed(2)+"s goal: "+y+" skip sn "+E+" to part "+h)}n.tuneInGoal=y}if(l=this.getDeliveryDirectives(n,e.deliveryDirectives,u,h),d||!v)return void this.loadPlaylist(l)}else(n.canBlockReload||n.canSkipUntil)&&(l=this.getDeliveryDirectives(n,e.deliveryDirectives,u,h));var T=this.hls.mainForwardBufferInfo,S=T?T.end-T.len:0,L=function(t,e){void 0===e&&(e=1/0);var r=1e3*t.targetduration;if(t.updated){var i=t.fragments;if(i.length&&4*r>e){var n=1e3*i[i.length-1].duration;n<r&&(r=n)}}else r/=2;return Math.round(r)}(n,1e3*(n.edge-S));n.updated&&s>this.requestScheduled+L&&(this.requestScheduled=a.loading.start),void 0!==u&&n.canBlockReload?this.requestScheduled=a.loading.first+L-(1e3*n.partTarget||1e3):-1===this.requestScheduled||this.requestScheduled+L<s?this.requestScheduled=s:this.requestScheduled-s<=0&&(this.requestScheduled+=L);var A=this.requestScheduled-s;A=Math.max(0,A),this.log("reload live playlist "+t+" in "+Math.round(A)+" ms"),this.timer=self.setTimeout((function(){return i.loadPlaylist(l)}),A)}else this.clearTimer()},e.getDeliveryDirectives=function(t,e,r,i){var n=function(t,e){var r=t.canSkipUntil,i=t.canSkipDateRanges,n=t.endSN;return r&&(void 0!==e?e-n:0)<r?i?$e:Je:Qe}(t,r);return null!=e&&e.skip&&t.deltaUpdateFailed&&(r=e.msn,i=e.part,n=Qe),new Ze(r,i,n)},e.checkRetry=function(t){var e=this,r=t.details,i=hr(t),n=t.errorAction,a=n||{},s=a.action,o=a.retryCount,l=void 0===o?0:o,u=a.retryConfig,h=!!n&&!!u&&(s===Lr||!n.resolved&&s===Tr);if(h){var d;if(this.requestScheduled=-1,l>=u.maxNumRetry)return!1;if(i&&null!=(d=t.context)&&d.deliveryDirectives)this.warn("Retrying playlist loading "+(l+1)+"/"+u.maxNumRetry+' after "'+r+'" without delivery-directives'),this.loadPlaylist();else{var c=cr(u,l);this.timer=self.setTimeout((function(){return e.loadPlaylist()}),c),this.warn("Retrying playlist loading "+(l+1)+"/"+u.maxNumRetry+' after "'+r+'" in '+c+"ms")}t.levelRetry=!0,n.resolved=!0}return h},t}(),Ir=function(){function t(t,e,r){void 0===e&&(e=0),void 0===r&&(r=0),this.halfLife=void 0,this.alpha_=void 0,this.estimate_=void 0,this.totalWeight_=void 0,this.halfLife=t,this.alpha_=t?Math.exp(Math.log(.5)/t):0,this.estimate_=e,this.totalWeight_=r}var e=t.prototype;return e.sample=function(t,e){var r=Math.pow(this.alpha_,t);this.estimate_=e*(1-r)+r*this.estimate_,this.totalWeight_+=t},e.getTotalWeight=function(){return this.totalWeight_},e.getEstimate=function(){if(this.alpha_){var t=1-Math.pow(this.alpha_,this.totalWeight_);if(t)return this.estimate_/t}return this.estimate_},t}(),wr=function(){function t(t,e,r,i){void 0===i&&(i=100),this.defaultEstimate_=void 0,this.minWeight_=void 0,this.minDelayMs_=void 0,this.slow_=void 0,this.fast_=void 0,this.defaultTTFB_=void 0,this.ttfb_=void 0,this.defaultEstimate_=r,this.minWeight_=.001,this.minDelayMs_=50,this.slow_=new Ir(t),this.fast_=new Ir(e),this.defaultTTFB_=i,this.ttfb_=new Ir(t)}var e=t.prototype;return e.update=function(t,e){var r=this.slow_,i=this.fast_,n=this.ttfb_;r.halfLife!==t&&(this.slow_=new Ir(t,r.getEstimate(),r.getTotalWeight())),i.halfLife!==e&&(this.fast_=new Ir(e,i.getEstimate(),i.getTotalWeight())),n.halfLife!==t&&(this.ttfb_=new Ir(t,n.getEstimate(),n.getTotalWeight()))},e.sample=function(t,e){var r=(t=Math.max(t,this.minDelayMs_))/1e3,i=8*e/r;this.fast_.sample(r,i),this.slow_.sample(r,i)},e.sampleTTFB=function(t){var e=t/1e3,r=Math.sqrt(2)*Math.exp(-Math.pow(e,2)/2);this.ttfb_.sample(r,Math.max(t,5))},e.canEstimate=function(){return this.fast_.getTotalWeight()>=this.minWeight_},e.getEstimate=function(){return this.canEstimate()?Math.min(this.fast_.getEstimate(),this.slow_.getEstimate()):this.defaultEstimate_},e.getEstimateTTFB=function(){return this.ttfb_.getTotalWeight()>=this.minWeight_?this.ttfb_.getEstimate():this.defaultTTFB_},e.destroy=function(){},t}(),Cr={supported:!0,configurations:[],decodingInfoResults:[{supported:!0,powerEfficient:!0,smooth:!0}]},_r={};function xr(t,e,r){var n=t.videoCodec,a=t.audioCodec;if(!n||!a||!r)return Promise.resolve(Cr);var s={width:t.width,height:t.height,bitrate:Math.ceil(Math.max(.9*t.bitrate,t.averageBitrate)),framerate:t.frameRate||30},o=t.videoRange;"SDR"!==o&&(s.transferFunction=o.toLowerCase());var l=n.split(",").map((function(t){return{type:"media-source",video:i(i({},s),{},{contentType:ne(t,"video")})}}));return a&&t.audioGroups&&t.audioGroups.forEach((function(t){var r;t&&(null==(r=e.groups[t])||r.tracks.forEach((function(e){if(e.groupId===t){var r=e.channels||"",i=parseFloat(r);y(i)&&i>2&&l.push.apply(l,a.split(",").map((function(t){return{type:"media-source",audio:{contentType:ne(t,"audio"),channels:""+i}}})))}})))})),Promise.all(l.map((function(t){var e=function(t){var e=t.audio,r=t.video,i=r||e;if(i){var n=i.contentType.split('"')[1];if(r)return"r"+r.height+"x"+r.width+"f"+Math.ceil(r.framerate)+(r.transferFunction||"sd")+"_"+n+"_"+Math.ceil(r.bitrate/1e5);if(e)return"c"+e.channels+(e.spatialRendering?"s":"n")+"_"+n}return""}(t);return _r[e]||(_r[e]=r.decodingInfo(t))}))).then((function(t){return{supported:!t.some((function(t){return!t.supported})),configurations:l,decodingInfoResults:t}})).catch((function(t){return{supported:!1,configurations:l,decodingInfoResults:[],error:t}}))}function Pr(t,e){var r=!1,i=[];return t&&(r="SDR"!==t,i=[t]),e&&(i=e.allowedVideoRanges||ze.slice(0),i=(r=void 0!==e.preferHDR?e.preferHDR:function(){if("function"==typeof matchMedia){var t=matchMedia("(dynamic-range: high)"),e=matchMedia("bad query");if(t.media!==e.media)return!0===t.matches}return!1}())?i.filter((function(t){return"SDR"!==t})):["SDR"]),{preferHDR:r,allowedVideoRanges:i}}function Fr(t,e){w.log('[abr] start candidates with "'+t+'" ignored because '+e)}function Mr(t,e,r){if("attrs"in t){var i=e.indexOf(t);if(-1!==i)return i}for(var n=0;n<e.length;n++)if(Or(t,e[n],r))return n;return-1}function Or(t,e,r){var i=t.groupId,n=t.name,a=t.lang,s=t.assocLang,o=t.characteristics,l=t.default,u=t.forced;return(void 0===i||e.groupId===i)&&(void 0===n||e.name===n)&&(void 0===a||e.lang===a)&&(void 0===a||e.assocLang===s)&&(void 0===l||e.default===l)&&(void 0===u||e.forced===u)&&(void 0===o||function(t,e){void 0===e&&(e="");var r=t.split(","),i=e.split(",");return r.length===i.length&&!r.some((function(t){return-1===i.indexOf(t)}))}(o,e.characteristics))&&(void 0===r||r(t,e))}function Nr(t,e){var r=t.audioCodec,i=t.channels;return!(void 0!==r&&(e.audioCodec||"").substring(0,4)!==r.substring(0,4)||void 0!==i&&i!==(e.channels||"2"))}function Ur(t,e,r){for(var i=e;i;i--)if(r(t[i]))return i;for(var n=e+1;n<t.length;n++)if(r(t[n]))return n;return-1}var Br=function(){function t(t){var e=this;this.hls=void 0,this.lastLevelLoadSec=0,this.lastLoadedFragLevel=-1,this.firstSelection=-1,this._nextAutoLevel=-1,this.nextAutoLevelKey="",this.audioTracksByGroup=null,this.codecTiers=null,this.timer=-1,this.fragCurrent=null,this.partCurrent=null,this.bitrateTestDelay=0,this.bwEstimator=void 0,this._abandonRulesCheck=function(){var t=e.fragCurrent,r=e.partCurrent,i=e.hls,n=i.autoLevelEnabled,a=i.media;if(t&&a){var s=performance.now(),o=r?r.stats:t.stats,l=r?r.duration:t.duration,u=s-o.loading.start,h=i.minAutoLevel;if(o.aborted||o.loaded&&o.loaded===o.total||t.level<=h)return e.clearTimer(),void(e._nextAutoLevel=-1);if(n&&!a.paused&&a.playbackRate&&a.readyState){var d=i.mainForwardBufferInfo;if(null!==d){var c=e.bwEstimator.getEstimateTTFB(),f=Math.abs(a.playbackRate);if(!(u<=Math.max(c,l/(2*f)*1e3))){var g=d.len/f,v=o.loading.first?o.loading.first-o.loading.start:-1,m=o.loaded&&v>-1,p=e.getBwEstimate(),E=i.levels,T=E[t.level],L=o.total||Math.max(o.loaded,Math.round(l*T.maxBitrate/8)),A=m?u-v:u;A<1&&m&&(A=Math.min(u,8*o.loaded/p));var R=m?1e3*o.loaded/A:0,k=R?(L-o.loaded)/R:8*L/p+c/1e3;if(!(k<=g)){var b,D=R?8*R:p,I=Number.POSITIVE_INFINITY;for(b=t.level-1;b>h;b--){var C=E[b].maxBitrate;if((I=e.getTimeToLoadFrag(c/1e3,D,l*C,!E[b].details))<g)break}if(!(I>=k||I>10*l)){i.nextLoadLevel=i.nextAutoLevel=b,m?e.bwEstimator.sample(u-Math.min(c,v),o.loaded):e.bwEstimator.sampleTTFB(u);var _=E[b].bitrate;e.getBwEstimate()*e.hls.config.abrBandWidthUpFactor>_&&e.resetEstimator(_),e.clearTimer(),w.warn("[abr] Fragment "+t.sn+(r?" part "+r.index:"")+" of level "+t.level+" is loading too slowly;\n Time to underbuffer: "+g.toFixed(3)+" s\n Estimated load time for current fragment: "+k.toFixed(3)+" s\n Estimated load time for down switch fragment: "+I.toFixed(3)+" s\n TTFB estimate: "+(0|v)+" ms\n Current BW estimate: "+(y(p)?0|p:"Unknown")+" bps\n New BW estimate: "+(0|e.getBwEstimate())+" bps\n Switching to level "+b+" @ "+(0|_)+" bps"),i.trigger(S.FRAG_LOAD_EMERGENCY_ABORTED,{frag:t,part:r,stats:o})}}}}}}},this.hls=t,this.bwEstimator=this.initEstimator(),this.registerListeners()}var e=t.prototype;return e.resetEstimator=function(t){t&&(w.log("setting initial bwe to "+t),this.hls.config.abrEwmaDefaultEstimate=t),this.firstSelection=-1,this.bwEstimator=this.initEstimator()},e.initEstimator=function(){var t=this.hls.config;return new wr(t.abrEwmaSlowVoD,t.abrEwmaFastVoD,t.abrEwmaDefaultEstimate)},e.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.FRAG_LOADING,this.onFragLoading,this),t.on(S.FRAG_LOADED,this.onFragLoaded,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this),t.on(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.MAX_AUTO_LEVEL_UPDATED,this.onMaxAutoLevelUpdated,this),t.on(S.ERROR,this.onError,this)},e.unregisterListeners=function(){var t=this.hls;t&&(t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.FRAG_LOADING,this.onFragLoading,this),t.off(S.FRAG_LOADED,this.onFragLoaded,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this),t.off(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.MAX_AUTO_LEVEL_UPDATED,this.onMaxAutoLevelUpdated,this),t.off(S.ERROR,this.onError,this))},e.destroy=function(){this.unregisterListeners(),this.clearTimer(),this.hls=this._abandonRulesCheck=null,this.fragCurrent=this.partCurrent=null},e.onManifestLoading=function(t,e){this.lastLoadedFragLevel=-1,this.firstSelection=-1,this.lastLevelLoadSec=0,this.fragCurrent=this.partCurrent=null,this.onLevelsUpdated(),this.clearTimer()},e.onLevelsUpdated=function(){this.lastLoadedFragLevel>-1&&this.fragCurrent&&(this.lastLoadedFragLevel=this.fragCurrent.level),this._nextAutoLevel=-1,this.onMaxAutoLevelUpdated(),this.codecTiers=null,this.audioTracksByGroup=null},e.onMaxAutoLevelUpdated=function(){this.firstSelection=-1,this.nextAutoLevelKey=""},e.onFragLoading=function(t,e){var r,i=e.frag;this.ignoreFragment(i)||(i.bitrateTest||(this.fragCurrent=i,this.partCurrent=null!=(r=e.part)?r:null),this.clearTimer(),this.timer=self.setInterval(this._abandonRulesCheck,100))},e.onLevelSwitching=function(t,e){this.clearTimer()},e.onError=function(t,e){if(!e.fatal)switch(e.details){case A.BUFFER_ADD_CODEC_ERROR:case A.BUFFER_APPEND_ERROR:this.lastLoadedFragLevel=-1,this.firstSelection=-1;break;case A.FRAG_LOAD_TIMEOUT:var r=e.frag,i=this.fragCurrent,n=this.partCurrent;if(r&&i&&r.sn===i.sn&&r.level===i.level){var a=performance.now(),s=n?n.stats:r.stats,o=a-s.loading.start,l=s.loading.first?s.loading.first-s.loading.start:-1;if(s.loaded&&l>-1){var u=this.bwEstimator.getEstimateTTFB();this.bwEstimator.sample(o-Math.min(u,l),s.loaded)}else this.bwEstimator.sampleTTFB(o)}}},e.getTimeToLoadFrag=function(t,e,r,i){return t+r/e+(i?this.lastLevelLoadSec:0)},e.onLevelLoaded=function(t,e){var r=this.hls.config,i=e.stats.loading,n=i.end-i.start;y(n)&&(this.lastLevelLoadSec=n/1e3),e.details.live?this.bwEstimator.update(r.abrEwmaSlowLive,r.abrEwmaFastLive):this.bwEstimator.update(r.abrEwmaSlowVoD,r.abrEwmaFastVoD)},e.onFragLoaded=function(t,e){var r=e.frag,i=e.part,n=i?i.stats:r.stats;if(r.type===Ie&&this.bwEstimator.sampleTTFB(n.loading.first-n.loading.start),!this.ignoreFragment(r)){if(this.clearTimer(),r.level===this._nextAutoLevel&&(this._nextAutoLevel=-1),this.firstSelection=-1,this.hls.config.abrMaxWithRealBitrate){var a=i?i.duration:r.duration,s=this.hls.levels[r.level],o=(s.loaded?s.loaded.bytes:0)+n.loaded,l=(s.loaded?s.loaded.duration:0)+a;s.loaded={bytes:o,duration:l},s.realBitrate=Math.round(8*o/l)}if(r.bitrateTest){var u={stats:n,frag:r,part:i,id:r.type};this.onFragBuffered(S.FRAG_BUFFERED,u),r.bitrateTest=!1}else this.lastLoadedFragLevel=r.level}},e.onFragBuffered=function(t,e){var r=e.frag,i=e.part,n=null!=i&&i.stats.loaded?i.stats:r.stats;if(!n.aborted&&!this.ignoreFragment(r)){var a=n.parsing.end-n.loading.start-Math.min(n.loading.first-n.loading.start,this.bwEstimator.getEstimateTTFB());this.bwEstimator.sample(a,n.loaded),n.bwEstimate=this.getBwEstimate(),r.bitrateTest?this.bitrateTestDelay=a/1e3:this.bitrateTestDelay=0}},e.ignoreFragment=function(t){return t.type!==Ie||"initSegment"===t.sn},e.clearTimer=function(){this.timer>-1&&(self.clearInterval(this.timer),this.timer=-1)},e.getAutoLevelKey=function(){var t;return this.getBwEstimate()+"_"+(null==(t=this.hls.mainForwardBufferInfo)?void 0:t.len)},e.getNextABRAutoLevel=function(){var t=this.fragCurrent,e=this.partCurrent,r=this.hls,i=r.maxAutoLevel,n=r.config,a=r.minAutoLevel,s=r.media,o=e?e.duration:t?t.duration:0,l=s&&0!==s.playbackRate?Math.abs(s.playbackRate):1,u=this.getBwEstimate(),h=r.mainForwardBufferInfo,d=(h?h.len:0)/l,c=n.abrBandWidthFactor,f=n.abrBandWidthUpFactor;if(d){var g=this.findBestLevel(u,a,i,d,0,c,f);if(g>=0)return g}var v=o?Math.min(o,n.maxStarvationDelay):n.maxStarvationDelay;if(!d){var m=this.bitrateTestDelay;m&&(v=(o?Math.min(o,n.maxLoadingDelay):n.maxLoadingDelay)-m,w.info("[abr] bitrate test took "+Math.round(1e3*m)+"ms, set first fragment max fetchDuration to "+Math.round(1e3*v)+" ms"),c=f=1)}var p=this.findBestLevel(u,a,i,d,v,c,f);if(w.info("[abr] "+(d?"rebuffering expected":"buffer is empty")+", optimal quality level "+p),p>-1)return p;var y=r.levels[a],E=r.levels[r.loadLevel];return(null==y?void 0:y.bitrate)<(null==E?void 0:E.bitrate)?a:r.loadLevel},e.getBwEstimate=function(){return this.bwEstimator.canEstimate()?this.bwEstimator.getEstimate():this.hls.config.abrEwmaDefaultEstimate},e.findBestLevel=function(t,e,r,i,n,a,s){var o,l=this,u=i+n,h=this.lastLoadedFragLevel,d=-1===h?this.hls.firstLevel:h,c=this.fragCurrent,f=this.partCurrent,g=this.hls,v=g.levels,m=g.allAudioTracks,p=g.loadLevel,E=g.config;if(1===v.length)return 0;var T,S=v[d],L=!(null==S||null==(o=S.details)||!o.live),A=-1===p||-1===h,R="SDR",k=(null==S?void 0:S.frameRate)||0,b=E.audioPreference,D=E.videoPreference,I=this.audioTracksByGroup||(this.audioTracksByGroup=function(t){return t.reduce((function(t,e){var r=t.groups[e.groupId];r||(r=t.groups[e.groupId]={tracks:[],channels:{2:0},hasDefault:!1,hasAutoSelect:!1}),r.tracks.push(e);var i=e.channels||"2";return r.channels[i]=(r.channels[i]||0)+1,r.hasDefault=r.hasDefault||e.default,r.hasAutoSelect=r.hasAutoSelect||e.autoselect,r.hasDefault&&(t.hasDefaultAudio=!0),r.hasAutoSelect&&(t.hasAutoSelectAudio=!0),t}),{hasDefaultAudio:!1,hasAutoSelectAudio:!1,groups:{}})}(m));if(A){if(-1!==this.firstSelection)return this.firstSelection;var C=this.codecTiers||(this.codecTiers=function(t,e,r,i){return t.slice(r,i+1).reduce((function(t,r){if(!r.codecSet)return t;var i=r.audioGroups,n=t[r.codecSet];n||(t[r.codecSet]=n={minBitrate:1/0,minHeight:1/0,minFramerate:1/0,maxScore:0,videoRanges:{SDR:0},channels:{2:0},hasDefaultAudio:!i,fragmentError:0}),n.minBitrate=Math.min(n.minBitrate,r.bitrate);var a=Math.min(r.height,r.width);return n.minHeight=Math.min(n.minHeight,a),n.minFramerate=Math.min(n.minFramerate,r.frameRate),n.maxScore=Math.max(n.maxScore,r.score),n.fragmentError+=r.fragmentError,n.videoRanges[r.videoRange]=(n.videoRanges[r.videoRange]||0)+1,i&&i.forEach((function(t){if(t){var r=e.groups[t];n.hasDefaultAudio=n.hasDefaultAudio||e.hasDefaultAudio?r.hasDefault:r.hasAutoSelect||!e.hasDefaultAudio&&!e.hasAutoSelectAudio,Object.keys(r.channels).forEach((function(t){n.channels[t]=(n.channels[t]||0)+r.channels[t]}))}})),t}),{})}(v,I,e,r)),_=function(t,e,r,i,n){for(var a=Object.keys(t),s=null==i?void 0:i.channels,o=null==i?void 0:i.audioCodec,l=s&&2===parseInt(s),u=!0,h=!1,d=1/0,c=1/0,f=1/0,g=0,v=[],m=Pr(e,n),p=m.preferHDR,E=m.allowedVideoRanges,T=function(){var e=t[a[S]];u=e.channels[2]>0,d=Math.min(d,e.minHeight),c=Math.min(c,e.minFramerate),f=Math.min(f,e.minBitrate);var r=E.filter((function(t){return e.videoRanges[t]>0}));r.length>0&&(h=!0,v=r)},S=a.length;S--;)T();d=y(d)?d:0,c=y(c)?c:0;var L=Math.max(1080,d),A=Math.max(30,c);return f=y(f)?f:r,r=Math.max(f,r),h||(e=void 0,v=[]),{codecSet:a.reduce((function(e,i){var n=t[i];if(i===e)return e;if(n.minBitrate>r)return Fr(i,"min bitrate of "+n.minBitrate+" > current estimate of "+r),e;if(!n.hasDefaultAudio)return Fr(i,"no renditions with default or auto-select sound found"),e;if(o&&i.indexOf(o.substring(0,4))%5!=0)return Fr(i,'audio codec preference "'+o+'" not found'),e;if(s&&!l){if(!n.channels[s])return Fr(i,"no renditions with "+s+" channel sound found (channels options: "+Object.keys(n.channels)+")"),e}else if((!o||l)&&u&&0===n.channels[2])return Fr(i,"no renditions with stereo sound found"),e;return n.minHeight>L?(Fr(i,"min resolution of "+n.minHeight+" > maximum of "+L),e):n.minFramerate>A?(Fr(i,"min framerate of "+n.minFramerate+" > maximum of "+A),e):v.some((function(t){return n.videoRanges[t]>0}))?n.maxScore<g?(Fr(i,"max score of "+n.maxScore+" < selected max of "+g),e):e&&(se(i)>=se(e)||n.fragmentError>t[e].fragmentError)?e:(g=n.maxScore,i):(Fr(i,"no variants with VIDEO-RANGE of "+JSON.stringify(v)+" found"),e)}),void 0),videoRanges:v,preferHDR:p,minFramerate:c,minBitrate:f}}(C,R,t,b,D),x=_.codecSet,P=_.videoRanges,F=_.minFramerate,M=_.minBitrate,O=_.preferHDR;T=x,R=O?P[P.length-1]:P[0],k=F,t=Math.max(t,M),w.log("[abr] picked start tier "+JSON.stringify(_))}else T=null==S?void 0:S.codecSet,R=null==S?void 0:S.videoRange;for(var N,U=f?f.duration:c?c.duration:0,B=this.bwEstimator.getEstimateTTFB()/1e3,G=[],K=function(){var e,o,c=v[H],g=H>d;if(!c)return 0;if(E.useMediaCapabilities&&!c.supportedResult&&!c.supportedPromise){var m=navigator.mediaCapabilities;"function"==typeof(null==m?void 0:m.decodingInfo)&&function(t,e,r,i,n,a){var s=t.audioCodec?t.audioGroups:null,o=null==a?void 0:a.audioCodec,l=null==a?void 0:a.channels,u=l?parseInt(l):o?1/0:2,h=null;if(null!=s&&s.length)try{h=1===s.length&&s[0]?e.groups[s[0]].channels:s.reduce((function(t,r){if(r){var i=e.groups[r];if(!i)throw new Error("Audio track group "+r+" not found");Object.keys(i.channels).forEach((function(e){t[e]=(t[e]||0)+i.channels[e]}))}return t}),{2:0})}catch(t){return!0}return void 0!==t.videoCodec&&(t.width>1920&&t.height>1088||t.height>1920&&t.width>1088||t.frameRate>Math.max(i,30)||"SDR"!==t.videoRange&&t.videoRange!==r||t.bitrate>Math.max(n,8e6))||!!h&&y(u)&&Object.keys(h).some((function(t){return parseInt(t)>u}))}(c,I,R,k,t,b)?(c.supportedPromise=xr(c,I,m),c.supportedPromise.then((function(t){c.supportedResult=t;var e=l.hls.levels,r=e.indexOf(c);t.error?w.warn('[abr] MediaCapabilities decodingInfo error: "'+t.error+'" for level '+r+" "+JSON.stringify(t)):t.supported||(w.warn("[abr] Unsupported MediaCapabilities decodingInfo result for level "+r+" "+JSON.stringify(t)),r>-1&&e.length>1&&(w.log("[abr] Removing unsupported level "+r),l.hls.removeLevel(r)))}))):c.supportedResult=Cr}if(T&&c.codecSet!==T||R&&c.videoRange!==R||g&&k>c.frameRate||!g&&k>0&&k<c.frameRate||null==(e=c.supportedResult)||null==(o=e.decodingInfoResults)||!o[0].smooth)return G.push(H),0;var D,C=c.details,_=(f?null==C?void 0:C.partTarget:null==C?void 0:C.averagetargetduration)||U;D=g?s*t:a*t;var x=U&&i>=2*U&&0===n?v[H].averageBitrate:v[H].maxBitrate,P=l.getTimeToLoadFrag(B,D,x*_,void 0===C);if(D>=x&&(H===h||0===c.loadError&&0===c.fragmentError)&&(P<=B||!y(P)||L&&!l.bitrateTestDelay||P<u)){var F=l.forcedAutoLevel;return H===p||-1!==F&&F===p||(G.length&&w.trace("[abr] Skipped level(s) "+G.join(",")+" of "+r+' max with CODECS and VIDEO-RANGE:"'+v[G[0]].codecs+'" '+v[G[0]].videoRange+'; not compatible with "'+S.codecs+'" '+R),w.info("[abr] switch candidate:"+d+"->"+H+" adjustedbw("+Math.round(D)+")-bitrate="+Math.round(D-x)+" ttfb:"+B.toFixed(1)+" avgDuration:"+_.toFixed(1)+" maxFetchDuration:"+u.toFixed(1)+" fetchDuration:"+P.toFixed(1)+" firstSelection:"+A+" codecSet:"+T+" videoRange:"+R+" hls.loadLevel:"+p)),A&&(l.firstSelection=H),{v:H}}},H=r;H>=e;H--)if(0!==(N=K())&&N)return N.v;return-1},s(t,[{key:"firstAutoLevel",get:function(){var t=this.hls,e=t.maxAutoLevel,r=t.minAutoLevel,i=this.getBwEstimate(),n=this.hls.config.maxStarvationDelay,a=this.findBestLevel(i,r,e,0,n,1,1);if(a>-1)return a;var s=this.hls.firstLevel,o=Math.min(Math.max(s,r),e);return w.warn("[abr] Could not find best starting auto level. Defaulting to first in playlist "+s+" clamped to "+o),o}},{key:"forcedAutoLevel",get:function(){return this.nextAutoLevelKey?-1:this._nextAutoLevel}},{key:"nextAutoLevel",get:function(){var t=this.forcedAutoLevel,e=this.bwEstimator.canEstimate(),r=this.lastLoadedFragLevel>-1;if(!(-1===t||e&&r&&this.nextAutoLevelKey!==this.getAutoLevelKey()))return t;var i=e&&r?this.getNextABRAutoLevel():this.firstAutoLevel;if(-1!==t){var n=this.hls.levels;if(n.length>Math.max(t,i)&&n[t].loadError<=n[i].loadError)return t}return this._nextAutoLevel=i,this.nextAutoLevelKey=this.getAutoLevelKey(),i},set:function(t){var e=Math.max(this.hls.minAutoLevel,t);this._nextAutoLevel!=e&&(this.nextAutoLevelKey="",this._nextAutoLevel=e)}}]),t}(),Gr=function(){function t(){this._boundTick=void 0,this._tickTimer=null,this._tickInterval=null,this._tickCallCount=0,this._boundTick=this.tick.bind(this)}var e=t.prototype;return e.destroy=function(){this.onHandlerDestroying(),this.onHandlerDestroyed()},e.onHandlerDestroying=function(){this.clearNextTick(),this.clearInterval()},e.onHandlerDestroyed=function(){},e.hasInterval=function(){return!!this._tickInterval},e.hasNextTick=function(){return!!this._tickTimer},e.setInterval=function(t){return!this._tickInterval&&(this._tickCallCount=0,this._tickInterval=self.setInterval(this._boundTick,t),!0)},e.clearInterval=function(){return!!this._tickInterval&&(self.clearInterval(this._tickInterval),this._tickInterval=null,!0)},e.clearNextTick=function(){return!!this._tickTimer&&(self.clearTimeout(this._tickTimer),this._tickTimer=null,!0)},e.tick=function(){this._tickCallCount++,1===this._tickCallCount&&(this.doTick(),this._tickCallCount>1&&this.tickImmediate(),this._tickCallCount=0)},e.tickImmediate=function(){this.clearNextTick(),this._tickTimer=self.setTimeout(this._boundTick,0)},e.doTick=function(){},t}(),Kr="NOT_LOADED",Hr="APPENDING",Vr="PARTIAL",Yr="OK",Wr=function(){function t(t){this.activePartLists=Object.create(null),this.endListFragments=Object.create(null),this.fragments=Object.create(null),this.timeRanges=Object.create(null),this.bufferPadding=.2,this.hls=void 0,this.hasGaps=!1,this.hls=t,this._registerListeners()}var e=t.prototype;return e._registerListeners=function(){var t=this.hls;t.on(S.BUFFER_APPENDED,this.onBufferAppended,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this),t.on(S.FRAG_LOADED,this.onFragLoaded,this)},e._unregisterListeners=function(){var t=this.hls;t.off(S.BUFFER_APPENDED,this.onBufferAppended,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this),t.off(S.FRAG_LOADED,this.onFragLoaded,this)},e.destroy=function(){this._unregisterListeners(),this.fragments=this.activePartLists=this.endListFragments=this.timeRanges=null},e.getAppendedFrag=function(t,e){var r=this.activePartLists[e];if(r)for(var i=r.length;i--;){var n=r[i];if(!n)break;var a=n.end;if(n.start<=t&&null!==a&&t<=a)return n}return this.getBufferedFrag(t,e)},e.getBufferedFrag=function(t,e){for(var r=this.fragments,i=Object.keys(r),n=i.length;n--;){var a=r[i[n]];if((null==a?void 0:a.body.type)===e&&a.buffered){var s=a.body;if(s.start<=t&&t<=s.end)return s}}return null},e.detectEvictedFragments=function(t,e,r,i){var n=this;this.timeRanges&&(this.timeRanges[t]=e);var a=(null==i?void 0:i.fragment.sn)||-1;Object.keys(this.fragments).forEach((function(i){var s=n.fragments[i];if(s&&!(a>=s.body.sn))if(s.buffered||s.loaded){var o=s.range[t];o&&o.time.some((function(t){var r=!n.isTimeBuffered(t.startPTS,t.endPTS,e);return r&&n.removeFragment(s.body),r}))}else s.body.type===r&&n.removeFragment(s.body)}))},e.detectPartialFragments=function(t){var e=this,r=this.timeRanges,i=t.frag,n=t.part;if(r&&"initSegment"!==i.sn){var a=qr(i),s=this.fragments[a];if(!(!s||s.buffered&&i.gap)){var o=!i.relurl;Object.keys(r).forEach((function(t){var a=i.elementaryStreams[t];if(a){var l=r[t],u=o||!0===a.partial;s.range[t]=e.getBufferedTimes(i,n,u,l)}})),s.loaded=null,Object.keys(s.range).length?(s.buffered=!0,(s.body.endList=i.endList||s.body.endList)&&(this.endListFragments[s.body.type]=s),jr(s)||this.removeParts(i.sn-1,i.type)):this.removeFragment(s.body)}}},e.removeParts=function(t,e){var r=this.activePartLists[e];r&&(this.activePartLists[e]=r.filter((function(e){return e.fragment.sn>=t})))},e.fragBuffered=function(t,e){var r=qr(t),i=this.fragments[r];!i&&e&&(i=this.fragments[r]={body:t,appendedPTS:null,loaded:null,buffered:!1,range:Object.create(null)},t.gap&&(this.hasGaps=!0)),i&&(i.loaded=null,i.buffered=!0)},e.getBufferedTimes=function(t,e,r,i){for(var n={time:[],partial:r},a=t.start,s=t.end,o=t.minEndPTS||s,l=t.maxStartPTS||a,u=0;u<i.length;u++){var h=i.start(u)-this.bufferPadding,d=i.end(u)+this.bufferPadding;if(l>=h&&o<=d){n.time.push({startPTS:Math.max(a,i.start(u)),endPTS:Math.min(s,i.end(u))});break}if(a<d&&s>h){var c=Math.max(a,i.start(u)),f=Math.min(s,i.end(u));f>c&&(n.partial=!0,n.time.push({startPTS:c,endPTS:f}))}else if(s<=h)break}return n},e.getPartialFragment=function(t){var e,r,i,n=null,a=0,s=this.bufferPadding,o=this.fragments;return Object.keys(o).forEach((function(l){var u=o[l];u&&jr(u)&&(r=u.body.start-s,i=u.body.end+s,t>=r&&t<=i&&(e=Math.min(t-r,i-t),a<=e&&(n=u.body,a=e)))})),n},e.isEndListAppended=function(t){var e=this.endListFragments[t];return void 0!==e&&(e.buffered||jr(e))},e.getState=function(t){var e=qr(t),r=this.fragments[e];return r?r.buffered?jr(r)?Vr:Yr:Hr:Kr},e.isTimeBuffered=function(t,e,r){for(var i,n,a=0;a<r.length;a++){if(i=r.start(a)-this.bufferPadding,n=r.end(a)+this.bufferPadding,t>=i&&e<=n)return!0;if(e<=i)return!1}return!1},e.onFragLoaded=function(t,e){var r=e.frag,i=e.part;if("initSegment"!==r.sn&&!r.bitrateTest){var n=i?null:e,a=qr(r);this.fragments[a]={body:r,appendedPTS:null,loaded:n,buffered:!1,range:Object.create(null)}}},e.onBufferAppended=function(t,e){var r=this,i=e.frag,n=e.part,a=e.timeRanges;if("initSegment"!==i.sn){var s=i.type;if(n){var o=this.activePartLists[s];o||(this.activePartLists[s]=o=[]),o.push(n)}this.timeRanges=a,Object.keys(a).forEach((function(t){var e=a[t];r.detectEvictedFragments(t,e,s,n)}))}},e.onFragBuffered=function(t,e){this.detectPartialFragments(e)},e.hasFragment=function(t){var e=qr(t);return!!this.fragments[e]},e.hasParts=function(t){var e;return!(null==(e=this.activePartLists[t])||!e.length)},e.removeFragmentsInRange=function(t,e,r,i,n){var a=this;i&&!this.hasGaps||Object.keys(this.fragments).forEach((function(s){var o=a.fragments[s];if(o){var l=o.body;l.type!==r||i&&!l.gap||l.start<e&&l.end>t&&(o.buffered||n)&&a.removeFragment(l)}}))},e.removeFragment=function(t){var e=qr(t);t.stats.loaded=0,t.clearElementaryStreamInfo();var r=this.activePartLists[t.type];if(r){var i=t.sn;this.activePartLists[t.type]=r.filter((function(t){return t.fragment.sn!==i}))}delete this.fragments[e],t.endList&&delete this.endListFragments[t.type]},e.removeAllFragments=function(){this.fragments=Object.create(null),this.endListFragments=Object.create(null),this.activePartLists=Object.create(null),this.hasGaps=!1},t}();function jr(t){var e,r,i;return t.buffered&&(t.body.gap||(null==(e=t.range.video)?void 0:e.partial)||(null==(r=t.range.audio)?void 0:r.partial)||(null==(i=t.range.audiovideo)?void 0:i.partial))}function qr(t){return t.type+"_"+t.level+"_"+t.sn}var Xr={length:0,start:function(){return 0},end:function(){return 0}},zr=function(){function t(){}return t.isBuffered=function(e,r){try{if(e)for(var i=t.getBuffered(e),n=0;n<i.length;n++)if(r>=i.start(n)&&r<=i.end(n))return!0}catch(t){}return!1},t.bufferInfo=function(e,r,i){try{if(e){var n,a=t.getBuffered(e),s=[];for(n=0;n<a.length;n++)s.push({start:a.start(n),end:a.end(n)});return this.bufferedInfo(s,r,i)}}catch(t){}return{len:0,start:r,end:r,nextStart:void 0}},t.bufferedInfo=function(t,e,r){e=Math.max(0,e),t.sort((function(t,e){var r=t.start-e.start;return r||e.end-t.end}));var i=[];if(r)for(var n=0;n<t.length;n++){var a=i.length;if(a){var s=i[a-1].end;t[n].start-s<r?t[n].end>s&&(i[a-1].end=t[n].end):i.push(t[n])}else i.push(t[n])}else i=t;for(var o,l=0,u=e,h=e,d=0;d<i.length;d++){var c=i[d].start,f=i[d].end;if(e+r>=c&&e<f)u=c,l=(h=f)-e;else if(e+r<c){o=c;break}}return{len:l,start:u||0,end:h||0,nextStart:o}},t.getBuffered=function(t){try{return t.buffered}catch(t){return w.log("failed to get media.buffered",t),Xr}},t}(),Qr=function(t,e,r,i,n,a){void 0===i&&(i=0),void 0===n&&(n=-1),void 0===a&&(a=!1),this.level=void 0,this.sn=void 0,this.part=void 0,this.id=void 0,this.size=void 0,this.partial=void 0,this.transmuxing={start:0,executeStart:0,executeEnd:0,end:0},this.buffering={audio:{start:0,executeStart:0,executeEnd:0,end:0},video:{start:0,executeStart:0,executeEnd:0,end:0},audiovideo:{start:0,executeStart:0,executeEnd:0,end:0}},this.level=t,this.sn=e,this.id=r,this.size=i,this.part=n,this.partial=a};function Jr(t,e){for(var r=0,i=t.length;r<i;r++){var n;if((null==(n=t[r])?void 0:n.cc)===e)return t[r]}return null}function $r(t,e){if(t){var r=t.start+e;t.start=t.startPTS=r,t.endPTS=r+t.duration}}function Zr(t,e){for(var r=e.fragments,i=0,n=r.length;i<n;i++)$r(r[i],t);e.fragmentHint&&$r(e.fragmentHint,t),e.alignedSliding=!0}function ti(t,e,r){e&&(function(t,e,r){if(function(t,e,r){return!(!e||!(r.endCC>r.startCC||t&&t.cc<r.startCC))}(t,r,e)){var i=function(t,e){var r=t.fragments,i=e.fragments;if(i.length&&r.length){var n=Jr(r,i[0].cc);if(n&&(!n||n.startPTS))return n;w.log("No frag in previous level to align on")}else w.log("No fragments to align")}(r,e);i&&y(i.start)&&(w.log("Adjusting PTS using last level due to CC increase within current level "+e.url),Zr(i.start,e))}}(t,r,e),!r.alignedSliding&&e&&ei(r,e),r.alignedSliding||!e||r.skippedSegments||ar(e,r))}function ei(t,e){if(t.hasProgramDateTime&&e.hasProgramDateTime){var r=t.fragments,i=e.fragments;if(r.length&&i.length){var n,a,s=Math.min(e.endCC,t.endCC);e.startCC<s&&t.startCC<s&&(n=Jr(i,s),a=Jr(r,s)),n&&a||(a=Jr(r,(n=i[Math.floor(i.length/2)]).cc)||r[Math.floor(r.length/2)]);var o=n.programDateTime,l=a.programDateTime;o&&l&&Zr((l-o)/1e3-(a.start-n.start),t)}}}var ri=Math.pow(2,17),ii=function(){function t(t){this.config=void 0,this.loader=null,this.partLoadTimeout=-1,this.config=t}var e=t.prototype;return e.destroy=function(){this.loader&&(this.loader.destroy(),this.loader=null)},e.abort=function(){this.loader&&this.loader.abort()},e.load=function(t,e){var r=this,n=t.url;if(!n)return Promise.reject(new si({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_ERROR,fatal:!1,frag:t,error:new Error("Fragment does not have a "+(n?"part list":"url")),networkDetails:null}));this.abort();var a=this.config,s=a.fLoader,o=a.loader;return new Promise((function(l,u){if(r.loader&&r.loader.destroy(),t.gap){if(t.tagList.some((function(t){return"GAP"===t[0]})))return void u(ai(t));t.gap=!1}var h=r.loader=t.loader=s?new s(a):new o(a),d=ni(t),c=fr(a.fragLoadPolicy.default),f={loadPolicy:c,timeout:c.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0,highWaterMark:"initSegment"===t.sn?1/0:ri};t.stats=h.stats,h.load(d,f,{onSuccess:function(e,i,n,a){r.resetLoader(t,h);var s=e.data;n.resetIV&&t.decryptdata&&(t.decryptdata.iv=new Uint8Array(s.slice(0,16)),s=s.slice(16)),l({frag:t,part:null,payload:s,networkDetails:a})},onError:function(e,a,s,o){r.resetLoader(t,h),u(new si({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_ERROR,fatal:!1,frag:t,response:i({url:n,data:void 0},e),error:new Error("HTTP Error "+e.code+" "+e.text),networkDetails:s,stats:o}))},onAbort:function(e,i,n){r.resetLoader(t,h),u(new si({type:L.NETWORK_ERROR,details:A.INTERNAL_ABORTED,fatal:!1,frag:t,error:new Error("Aborted"),networkDetails:n,stats:e}))},onTimeout:function(e,i,n){r.resetLoader(t,h),u(new si({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_TIMEOUT,fatal:!1,frag:t,error:new Error("Timeout after "+f.timeout+"ms"),networkDetails:n,stats:e}))},onProgress:function(r,i,n,a){e&&e({frag:t,part:null,payload:n,networkDetails:a})}})}))},e.loadPart=function(t,e,r){var n=this;this.abort();var a=this.config,s=a.fLoader,o=a.loader;return new Promise((function(l,u){if(n.loader&&n.loader.destroy(),t.gap||e.gap)u(ai(t,e));else{var h=n.loader=t.loader=s?new s(a):new o(a),d=ni(t,e),c=fr(a.fragLoadPolicy.default),f={loadPolicy:c,timeout:c.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0,highWaterMark:ri};e.stats=h.stats,h.load(d,f,{onSuccess:function(i,a,s,o){n.resetLoader(t,h),n.updateStatsFromPart(t,e);var u={frag:t,part:e,payload:i.data,networkDetails:o};r(u),l(u)},onError:function(r,a,s,o){n.resetLoader(t,h),u(new si({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_ERROR,fatal:!1,frag:t,part:e,response:i({url:d.url,data:void 0},r),error:new Error("HTTP Error "+r.code+" "+r.text),networkDetails:s,stats:o}))},onAbort:function(r,i,a){t.stats.aborted=e.stats.aborted,n.resetLoader(t,h),u(new si({type:L.NETWORK_ERROR,details:A.INTERNAL_ABORTED,fatal:!1,frag:t,part:e,error:new Error("Aborted"),networkDetails:a,stats:r}))},onTimeout:function(r,i,a){n.resetLoader(t,h),u(new si({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_TIMEOUT,fatal:!1,frag:t,part:e,error:new Error("Timeout after "+f.timeout+"ms"),networkDetails:a,stats:r}))}})}}))},e.updateStatsFromPart=function(t,e){var r=t.stats,i=e.stats,n=i.total;if(r.loaded+=i.loaded,n){var a=Math.round(t.duration/e.duration),s=Math.min(Math.round(r.loaded/n),a),o=(a-s)*Math.round(r.loaded/s);r.total=r.loaded+o}else r.total=Math.max(r.loaded,r.total);var l=r.loading,u=i.loading;l.start?l.first+=u.first-u.start:(l.start=u.start,l.first=u.first),l.end=u.end},e.resetLoader=function(t,e){t.loader=null,this.loader===e&&(self.clearTimeout(this.partLoadTimeout),this.loader=null),e.destroy()},t}();function ni(t,e){void 0===e&&(e=null);var r=e||t,i={frag:t,part:e,responseType:"arraybuffer",url:r.url,headers:{},rangeStart:0,rangeEnd:0},n=r.byteRangeStartOffset,a=r.byteRangeEndOffset;if(y(n)&&y(a)){var s,o=n,l=a;if("initSegment"===t.sn&&"AES-128"===(null==(s=t.decryptdata)?void 0:s.method)){var u=a-n;u%16&&(l=a+(16-u%16)),0!==n&&(i.resetIV=!0,o=n-16)}i.rangeStart=o,i.rangeEnd=l}return i}function ai(t,e){var r=new Error("GAP "+(t.gap?"tag":"attribute")+" found"),i={type:L.MEDIA_ERROR,details:A.FRAG_GAP,fatal:!1,frag:t,error:r,networkDetails:null};return e&&(i.part=e),(e||t).stats.aborted=!0,new si(i)}var si=function(t){function e(e){var r;return(r=t.call(this,e.error.message)||this).data=void 0,r.data=e,r}return l(e,t),e}(c(Error)),oi=function(){function t(t,e){this.subtle=void 0,this.aesIV=void 0,this.subtle=t,this.aesIV=e}return t.prototype.decrypt=function(t,e){return this.subtle.decrypt({name:"AES-CBC",iv:this.aesIV},e,t)},t}(),li=function(){function t(t,e){this.subtle=void 0,this.key=void 0,this.subtle=t,this.key=e}return t.prototype.expandKey=function(){return this.subtle.importKey("raw",this.key,{name:"AES-CBC"},!1,["encrypt","decrypt"])},t}(),ui=function(){function t(){this.rcon=[0,1,2,4,8,16,32,64,128,27,54],this.subMix=[new Uint32Array(256),new Uint32Array(256),new Uint32Array(256),new Uint32Array(256)],this.invSubMix=[new Uint32Array(256),new Uint32Array(256),new Uint32Array(256),new Uint32Array(256)],this.sBox=new Uint32Array(256),this.invSBox=new Uint32Array(256),this.key=new Uint32Array(0),this.ksRows=0,this.keySize=0,this.keySchedule=void 0,this.invKeySchedule=void 0,this.initTable()}var e=t.prototype;return e.uint8ArrayToUint32Array_=function(t){for(var e=new DataView(t),r=new Uint32Array(4),i=0;i<4;i++)r[i]=e.getUint32(4*i);return r},e.initTable=function(){var t=this.sBox,e=this.invSBox,r=this.subMix,i=r[0],n=r[1],a=r[2],s=r[3],o=this.invSubMix,l=o[0],u=o[1],h=o[2],d=o[3],c=new Uint32Array(256),f=0,g=0,v=0;for(v=0;v<256;v++)c[v]=v<128?v<<1:v<<1^283;for(v=0;v<256;v++){var m=g^g<<1^g<<2^g<<3^g<<4;m=m>>>8^255&m^99,t[f]=m,e[m]=f;var p=c[f],y=c[p],E=c[y],T=257*c[m]^16843008*m;i[f]=T<<24|T>>>8,n[f]=T<<16|T>>>16,a[f]=T<<8|T>>>24,s[f]=T,T=16843009*E^65537*y^257*p^16843008*f,l[m]=T<<24|T>>>8,u[m]=T<<16|T>>>16,h[m]=T<<8|T>>>24,d[m]=T,f?(f=p^c[c[c[E^p]]],g^=c[c[g]]):f=g=1}},e.expandKey=function(t){for(var e=this.uint8ArrayToUint32Array_(t),r=!0,i=0;i<e.length&&r;)r=e[i]===this.key[i],i++;if(!r){this.key=e;var n=this.keySize=e.length;if(4!==n&&6!==n&&8!==n)throw new Error("Invalid aes key size="+n);var a,s,o,l,u=this.ksRows=4*(n+6+1),h=this.keySchedule=new Uint32Array(u),d=this.invKeySchedule=new Uint32Array(u),c=this.sBox,f=this.rcon,g=this.invSubMix,v=g[0],m=g[1],p=g[2],y=g[3];for(a=0;a<u;a++)a<n?o=h[a]=e[a]:(l=o,a%n==0?(l=c[(l=l<<8|l>>>24)>>>24]<<24|c[l>>>16&255]<<16|c[l>>>8&255]<<8|c[255&l],l^=f[a/n|0]<<24):n>6&&a%n==4&&(l=c[l>>>24]<<24|c[l>>>16&255]<<16|c[l>>>8&255]<<8|c[255&l]),h[a]=o=(h[a-n]^l)>>>0);for(s=0;s<u;s++)a=u-s,l=3&s?h[a]:h[a-4],d[s]=s<4||a<=4?l:v[c[l>>>24]]^m[c[l>>>16&255]]^p[c[l>>>8&255]]^y[c[255&l]],d[s]=d[s]>>>0}},e.networkToHostOrderSwap=function(t){return t<<24|(65280&t)<<8|(16711680&t)>>8|t>>>24},e.decrypt=function(t,e,r){for(var i,n,a,s,o,l,u,h,d,c,f,g,v,m,p=this.keySize+6,y=this.invKeySchedule,E=this.invSBox,T=this.invSubMix,S=T[0],L=T[1],A=T[2],R=T[3],k=this.uint8ArrayToUint32Array_(r),b=k[0],D=k[1],I=k[2],w=k[3],C=new Int32Array(t),_=new Int32Array(C.length),x=this.networkToHostOrderSwap;e<C.length;){for(d=x(C[e]),c=x(C[e+1]),f=x(C[e+2]),g=x(C[e+3]),o=d^y[0],l=g^y[1],u=f^y[2],h=c^y[3],v=4,m=1;m<p;m++)i=S[o>>>24]^L[l>>16&255]^A[u>>8&255]^R[255&h]^y[v],n=S[l>>>24]^L[u>>16&255]^A[h>>8&255]^R[255&o]^y[v+1],a=S[u>>>24]^L[h>>16&255]^A[o>>8&255]^R[255&l]^y[v+2],s=S[h>>>24]^L[o>>16&255]^A[l>>8&255]^R[255&u]^y[v+3],o=i,l=n,u=a,h=s,v+=4;i=E[o>>>24]<<24^E[l>>16&255]<<16^E[u>>8&255]<<8^E[255&h]^y[v],n=E[l>>>24]<<24^E[u>>16&255]<<16^E[h>>8&255]<<8^E[255&o]^y[v+1],a=E[u>>>24]<<24^E[h>>16&255]<<16^E[o>>8&255]<<8^E[255&l]^y[v+2],s=E[h>>>24]<<24^E[o>>16&255]<<16^E[l>>8&255]<<8^E[255&u]^y[v+3],_[e]=x(i^b),_[e+1]=x(s^D),_[e+2]=x(a^I),_[e+3]=x(n^w),b=d,D=c,I=f,w=g,e+=4}return _.buffer},t}(),hi=function(){function t(t,e){var r=(void 0===e?{}:e).removePKCS7Padding,i=void 0===r||r;if(this.logEnabled=!0,this.removePKCS7Padding=void 0,this.subtle=null,this.softwareDecrypter=null,this.key=null,this.fastAesKey=null,this.remainderData=null,this.currentIV=null,this.currentResult=null,this.useSoftware=void 0,this.useSoftware=t.enableSoftwareAES,this.removePKCS7Padding=i,i)try{var n=self.crypto;n&&(this.subtle=n.subtle||n.webkitSubtle)}catch(t){}null===this.subtle&&(this.useSoftware=!0)}var e=t.prototype;return e.destroy=function(){this.subtle=null,this.softwareDecrypter=null,this.key=null,this.fastAesKey=null,this.remainderData=null,this.currentIV=null,this.currentResult=null},e.isSync=function(){return this.useSoftware},e.flush=function(){var t=this.currentResult,e=this.remainderData;if(!t||e)return this.reset(),null;var r,i,n,a=new Uint8Array(t);return this.reset(),this.removePKCS7Padding?(i=(r=a).byteLength,(n=i&&new DataView(r.buffer).getUint8(i-1))?nt(r,0,i-n):r):a},e.reset=function(){this.currentResult=null,this.currentIV=null,this.remainderData=null,this.softwareDecrypter&&(this.softwareDecrypter=null)},e.decrypt=function(t,e,r){var i=this;return this.useSoftware?new Promise((function(n,a){i.softwareDecrypt(new Uint8Array(t),e,r);var s=i.flush();s?n(s.buffer):a(new Error("[softwareDecrypt] Failed to decrypt data"))})):this.webCryptoDecrypt(new Uint8Array(t),e,r)},e.softwareDecrypt=function(t,e,r){var i=this.currentIV,n=this.currentResult,a=this.remainderData;this.logOnce("JS AES decrypt"),a&&(t=Gt(a,t),this.remainderData=null);var s=this.getValidChunk(t);if(!s.length)return null;i&&(r=i);var o=this.softwareDecrypter;o||(o=this.softwareDecrypter=new ui),o.expandKey(e);var l=n;return this.currentResult=o.decrypt(s.buffer,0,r),this.currentIV=nt(s,-16).buffer,l||null},e.webCryptoDecrypt=function(t,e,r){var i=this,n=this.subtle;return this.key===e&&this.fastAesKey||(this.key=e,this.fastAesKey=new li(n,e)),this.fastAesKey.expandKey().then((function(e){return n?(i.logOnce("WebCrypto AES decrypt"),new oi(n,new Uint8Array(r)).decrypt(t.buffer,e)):Promise.reject(new Error("web crypto not initialized"))})).catch((function(n){return w.warn("[decrypter]: WebCrypto Error, disable WebCrypto API, "+n.name+": "+n.message),i.onWebCryptoError(t,e,r)}))},e.onWebCryptoError=function(t,e,r){this.useSoftware=!0,this.logEnabled=!0,this.softwareDecrypt(t,e,r);var i=this.flush();if(i)return i.buffer;throw new Error("WebCrypto and softwareDecrypt: failed to decrypt data")},e.getValidChunk=function(t){var e=t,r=t.length-t.length%16;return r!==t.length&&(e=nt(t,0,r),this.remainderData=nt(t,r)),e},e.logOnce=function(t){this.logEnabled&&(w.log("[decrypter]: "+t),this.logEnabled=!1)},t}(),di=function(t){for(var e="",r=t.length,i=0;i<r;i++)e+="["+t.start(i).toFixed(3)+"-"+t.end(i).toFixed(3)+"]";return e},ci="STOPPED",fi="IDLE",gi="KEY_LOADING",vi="FRAG_LOADING",mi="FRAG_LOADING_WAITING_RETRY",pi="WAITING_TRACK",yi="PARSING",Ei="PARSED",Ti="ENDED",Si="ERROR",Li="WAITING_INIT_PTS",Ai="WAITING_LEVEL",Ri=function(t){function e(e,r,i,n,a){var s;return(s=t.call(this)||this).hls=void 0,s.fragPrevious=null,s.fragCurrent=null,s.fragmentTracker=void 0,s.transmuxer=null,s._state=ci,s.playlistType=void 0,s.media=null,s.mediaBuffer=null,s.config=void 0,s.bitrateTest=!1,s.lastCurrentTime=0,s.nextLoadPosition=0,s.startPosition=0,s.startTimeOffset=null,s.loadedmetadata=!1,s.retryDate=0,s.levels=null,s.fragmentLoader=void 0,s.keyLoader=void 0,s.levelLastLoaded=null,s.startFragRequested=!1,s.decrypter=void 0,s.initPTS=[],s.onvseeking=null,s.onvended=null,s.logPrefix="",s.log=void 0,s.warn=void 0,s.playlistType=a,s.logPrefix=n,s.log=w.log.bind(w,n+":"),s.warn=w.warn.bind(w,n+":"),s.hls=e,s.fragmentLoader=new ii(e.config),s.keyLoader=i,s.fragmentTracker=r,s.config=e.config,s.decrypter=new hi(e.config),e.on(S.MANIFEST_LOADED,s.onManifestLoaded,function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(s)),s}l(e,t);var r=e.prototype;return r.doTick=function(){this.onTickEnd()},r.onTickEnd=function(){},r.startLoad=function(t){},r.stopLoad=function(){this.fragmentLoader.abort(),this.keyLoader.abort(this.playlistType);var t=this.fragCurrent;null!=t&&t.loader&&(t.abortRequests(),this.fragmentTracker.removeFragment(t)),this.resetTransmuxer(),this.fragCurrent=null,this.fragPrevious=null,this.clearInterval(),this.clearNextTick(),this.state=ci},r._streamEnded=function(t,e){if(e.live||t.nextStart||!t.end||!this.media)return!1;var r=e.partList;if(null!=r&&r.length){var i=r[r.length-1];return zr.isBuffered(this.media,i.start+i.duration/2)}var n=e.fragments[e.fragments.length-1].type;return this.fragmentTracker.isEndListAppended(n)},r.getLevelDetails=function(){var t;if(this.levels&&null!==this.levelLastLoaded)return null==(t=this.levelLastLoaded)?void 0:t.details},r.onMediaAttached=function(t,e){var r=this.media=this.mediaBuffer=e.media;this.onvseeking=this.onMediaSeeking.bind(this),this.onvended=this.onMediaEnded.bind(this),r.addEventListener("seeking",this.onvseeking),r.addEventListener("ended",this.onvended);var i=this.config;this.levels&&i.autoStartLoad&&this.state===ci&&this.startLoad(i.startPosition)},r.onMediaDetaching=function(){var t=this.media;null!=t&&t.ended&&(this.log("MSE detaching and video ended, reset startPosition"),this.startPosition=this.lastCurrentTime=0),t&&this.onvseeking&&this.onvended&&(t.removeEventListener("seeking",this.onvseeking),t.removeEventListener("ended",this.onvended),this.onvseeking=this.onvended=null),this.keyLoader&&this.keyLoader.detach(),this.media=this.mediaBuffer=null,this.loadedmetadata=!1,this.fragmentTracker.removeAllFragments(),this.stopLoad()},r.onMediaSeeking=function(){var t=this.config,e=this.fragCurrent,r=this.media,i=this.mediaBuffer,n=this.state,a=r?r.currentTime:0,s=zr.bufferInfo(i||r,a,t.maxBufferHole);if(this.log("media seeking to "+(y(a)?a.toFixed(3):a)+", state: "+n),this.state===Ti)this.resetLoadingState();else if(e){var o=t.maxFragLookUpTolerance,l=e.start-o,u=e.start+e.duration+o;if(!s.len||u<s.start||l>s.end){var h=a>u;(a<l||h)&&(h&&e.loader&&(this.log("seeking outside of buffer while fragment load in progress, cancel fragment load"),e.abortRequests(),this.resetLoadingState()),this.fragPrevious=null)}}r&&(this.fragmentTracker.removeFragmentsInRange(a,1/0,this.playlistType,!0),this.lastCurrentTime=a),this.loadedmetadata||s.len||(this.nextLoadPosition=this.startPosition=a),this.tickImmediate()},r.onMediaEnded=function(){this.startPosition=this.lastCurrentTime=0},r.onManifestLoaded=function(t,e){this.startTimeOffset=e.startTimeOffset,this.initPTS=[]},r.onHandlerDestroying=function(){this.hls.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),this.stopLoad(),t.prototype.onHandlerDestroying.call(this),this.hls=null},r.onHandlerDestroyed=function(){this.state=ci,this.fragmentLoader&&this.fragmentLoader.destroy(),this.keyLoader&&this.keyLoader.destroy(),this.decrypter&&this.decrypter.destroy(),this.hls=this.log=this.warn=this.decrypter=this.keyLoader=this.fragmentLoader=this.fragmentTracker=null,t.prototype.onHandlerDestroyed.call(this)},r.loadFragment=function(t,e,r){this._loadFragForPlayback(t,e,r)},r._loadFragForPlayback=function(t,e,r){var i=this;this._doFragLoad(t,e,r,(function(e){if(i.fragContextChanged(t))return i.warn("Fragment "+t.sn+(e.part?" p: "+e.part.index:"")+" of level "+t.level+" was dropped during download."),void i.fragmentTracker.removeFragment(t);t.stats.chunkCount++,i._handleFragmentLoadProgress(e)})).then((function(e){if(e){var r=i.state;i.fragContextChanged(t)?(r===vi||!i.fragCurrent&&r===yi)&&(i.fragmentTracker.removeFragment(t),i.state=fi):("payload"in e&&(i.log("Loaded fragment "+t.sn+" of level "+t.level),i.hls.trigger(S.FRAG_LOADED,e)),i._handleFragmentLoadComplete(e))}})).catch((function(e){i.state!==ci&&i.state!==Si&&(i.warn(e),i.resetFragmentLoading(t))}))},r.clearTrackerIfNeeded=function(t){var e,r=this.fragmentTracker;if(r.getState(t)===Hr){var i=t.type,n=this.getFwdBufferInfo(this.mediaBuffer,i),a=Math.max(t.duration,n?n.len:this.config.maxBufferLength);this.reduceMaxBufferLength(a)&&r.removeFragment(t)}else 0===(null==(e=this.mediaBuffer)?void 0:e.buffered.length)?r.removeAllFragments():r.hasParts(t.type)&&(r.detectPartialFragments({frag:t,part:null,stats:t.stats,id:t.type}),r.getState(t)===Vr&&r.removeFragment(t))},r.checkLiveUpdate=function(t){if(t.updated&&!t.live){var e=t.fragments[t.fragments.length-1];this.fragmentTracker.detectPartialFragments({frag:e,part:null,stats:e.stats,id:e.type})}t.fragments[0]||(t.deltaUpdateFailed=!0)},r.flushMainBuffer=function(t,e,r){if(void 0===r&&(r=null),t-e){var i={startOffset:t,endOffset:e,type:r};this.hls.trigger(S.BUFFER_FLUSHING,i)}},r._loadInitSegment=function(t,e){var r=this;this._doFragLoad(t,e).then((function(e){if(!e||r.fragContextChanged(t)||!r.levels)throw new Error("init load aborted");return e})).then((function(e){var i=r.hls,n=e.payload,a=t.decryptdata;if(n&&n.byteLength>0&&null!=a&&a.key&&a.iv&&"AES-128"===a.method){var s=self.performance.now();return r.decrypter.decrypt(new Uint8Array(n),a.key.buffer,a.iv.buffer).catch((function(e){throw i.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_DECRYPT_ERROR,fatal:!1,error:e,reason:e.message,frag:t}),e})).then((function(n){var a=self.performance.now();return i.trigger(S.FRAG_DECRYPTED,{frag:t,payload:n,stats:{tstart:s,tdecrypt:a}}),e.payload=n,r.completeInitSegmentLoad(e)}))}return r.completeInitSegmentLoad(e)})).catch((function(e){r.state!==ci&&r.state!==Si&&(r.warn(e),r.resetFragmentLoading(t))}))},r.completeInitSegmentLoad=function(t){if(!this.levels)throw new Error("init load aborted, missing levels");var e=t.frag.stats;this.state=fi,t.frag.data=new Uint8Array(t.payload),e.parsing.start=e.buffering.start=self.performance.now(),e.parsing.end=e.buffering.end=self.performance.now(),this.tick()},r.fragContextChanged=function(t){var e=this.fragCurrent;return!t||!e||t.sn!==e.sn||t.level!==e.level},r.fragBufferedComplete=function(t,e){var r,i,n,a,s=this.mediaBuffer?this.mediaBuffer:this.media;if(this.log("Buffered "+t.type+" sn: "+t.sn+(e?" part: "+e.index:"")+" of "+(this.playlistType===Ie?"level":"track")+" "+t.level+" (frag:["+(null!=(r=t.startPTS)?r:NaN).toFixed(3)+"-"+(null!=(i=t.endPTS)?i:NaN).toFixed(3)+"] > buffer:"+(s?di(zr.getBuffered(s)):"(detached)")+")"),"initSegment"!==t.sn){var o;if(t.type!==Ce){var l=t.elementaryStreams;if(!Object.keys(l).some((function(t){return!!l[t]})))return void(this.state=fi)}var u=null==(o=this.levels)?void 0:o[t.level];null!=u&&u.fragmentError&&(this.log("Resetting level fragment error count of "+u.fragmentError+" on frag buffered"),u.fragmentError=0)}this.state=fi,s&&(!this.loadedmetadata&&t.type==Ie&&s.buffered.length&&(null==(n=this.fragCurrent)?void 0:n.sn)===(null==(a=this.fragPrevious)?void 0:a.sn)&&(this.loadedmetadata=!0,this.seekToStartPos()),this.tick())},r.seekToStartPos=function(){},r._handleFragmentLoadComplete=function(t){var e=this.transmuxer;if(e){var r=t.frag,i=t.part,n=t.partsLoaded,a=!n||0===n.length||n.some((function(t){return!t})),s=new Qr(r.level,r.sn,r.stats.chunkCount+1,0,i?i.index:-1,!a);e.flush(s)}},r._handleFragmentLoadProgress=function(t){},r._doFragLoad=function(t,e,r,i){var n,a=this;void 0===r&&(r=null);var s=null==e?void 0:e.details;if(!this.levels||!s)throw new Error("frag load aborted, missing level"+(s?"":" detail")+"s");var o=null;if(!t.encrypted||null!=(n=t.decryptdata)&&n.key?!t.encrypted&&s.encryptedFragments.length&&this.keyLoader.loadClear(t,s.encryptedFragments):(this.log("Loading key for "+t.sn+" of ["+s.startSN+"-"+s.endSN+"], "+("[stream-controller]"===this.logPrefix?"level":"track")+" "+t.level),this.state=gi,this.fragCurrent=t,o=this.keyLoader.load(t).then((function(t){if(!a.fragContextChanged(t.frag))return a.hls.trigger(S.KEY_LOADED,t),a.state===gi&&(a.state=fi),t})),this.hls.trigger(S.KEY_LOADING,{frag:t}),null===this.fragCurrent&&(o=Promise.reject(new Error("frag load aborted, context changed in KEY_LOADING")))),r=Math.max(t.start,r||0),this.config.lowLatencyMode&&"initSegment"!==t.sn){var l=s.partList;if(l&&i){r>t.end&&s.fragmentHint&&(t=s.fragmentHint);var u=this.getNextPart(l,t,r);if(u>-1){var h,d=l[u];return this.log("Loading part sn: "+t.sn+" p: "+d.index+" cc: "+t.cc+" of playlist ["+s.startSN+"-"+s.endSN+"] parts [0-"+u+"-"+(l.length-1)+"] "+("[stream-controller]"===this.logPrefix?"level":"track")+": "+t.level+", target: "+parseFloat(r.toFixed(3))),this.nextLoadPosition=d.start+d.duration,this.state=vi,h=o?o.then((function(r){return!r||a.fragContextChanged(r.frag)?null:a.doFragPartsLoad(t,d,e,i)})).catch((function(t){return a.handleFragLoadError(t)})):this.doFragPartsLoad(t,d,e,i).catch((function(t){return a.handleFragLoadError(t)})),this.hls.trigger(S.FRAG_LOADING,{frag:t,part:d,targetBufferTime:r}),null===this.fragCurrent?Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING parts")):h}if(!t.url||this.loadedEndOfParts(l,r))return Promise.resolve(null)}}this.log("Loading fragment "+t.sn+" cc: "+t.cc+" "+(s?"of ["+s.startSN+"-"+s.endSN+"] ":"")+("[stream-controller]"===this.logPrefix?"level":"track")+": "+t.level+", target: "+parseFloat(r.toFixed(3))),y(t.sn)&&!this.bitrateTest&&(this.nextLoadPosition=t.start+t.duration),this.state=vi;var c,f=this.config.progressive;return c=f&&o?o.then((function(e){return!e||a.fragContextChanged(null==e?void 0:e.frag)?null:a.fragmentLoader.load(t,i)})).catch((function(t){return a.handleFragLoadError(t)})):Promise.all([this.fragmentLoader.load(t,f?i:void 0),o]).then((function(t){var e=t[0];return!f&&e&&i&&i(e),e})).catch((function(t){return a.handleFragLoadError(t)})),this.hls.trigger(S.FRAG_LOADING,{frag:t,targetBufferTime:r}),null===this.fragCurrent?Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING")):c},r.doFragPartsLoad=function(t,e,r,i){var n=this;return new Promise((function(a,s){var o,l=[],u=null==(o=r.details)?void 0:o.partList;!function e(o){n.fragmentLoader.loadPart(t,o,i).then((function(i){l[o.index]=i;var s=i.part;n.hls.trigger(S.FRAG_LOADED,i);var h=or(r,t.sn,o.index+1)||lr(u,t.sn,o.index+1);if(!h)return a({frag:t,part:s,partsLoaded:l});e(h)})).catch(s)}(e)}))},r.handleFragLoadError=function(t){if("data"in t){var e=t.data;t.data&&e.details===A.INTERNAL_ABORTED?this.handleFragLoadAborted(e.frag,e.part):this.hls.trigger(S.ERROR,e)}else this.hls.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.INTERNAL_EXCEPTION,err:t,error:t,fatal:!0});return null},r._handleTransmuxerFlush=function(t){var e=this.getCurrentContext(t);if(e&&this.state===yi){var r=e.frag,i=e.part,n=e.level,a=self.performance.now();r.stats.parsing.end=a,i&&(i.stats.parsing.end=a),this.updateLevelTiming(r,i,n,t.partial)}else this.fragCurrent||this.state===ci||this.state===Si||(this.state=fi)},r.getCurrentContext=function(t){var e=this.levels,r=this.fragCurrent,i=t.level,n=t.sn,a=t.part;if(null==e||!e[i])return this.warn("Levels object was unset while buffering fragment "+n+" of level "+i+". The current chunk will not be buffered."),null;var s=e[i],o=a>-1?or(s,n,a):null,l=o?o.fragment:function(t,e,r){if(null==t||!t.details)return null;var i=t.details,n=i.fragments[e-i.startSN];return n||((n=i.fragmentHint)&&n.sn===e?n:e<i.startSN&&r&&r.sn===e?r:null)}(s,n,r);return l?(r&&r!==l&&(l.stats=r.stats),{frag:l,part:o,level:s}):null},r.bufferFragmentData=function(t,e,r,i,n){var a;if(t&&this.state===yi){var s=t.data1,o=t.data2,l=s;if(s&&o&&(l=Gt(s,o)),null!=(a=l)&&a.length){var u={type:t.type,frag:e,part:r,chunkMeta:i,parent:e.type,data:l};if(this.hls.trigger(S.BUFFER_APPENDING,u),t.dropped&&t.independent&&!r){if(n)return;this.flushBufferGap(e)}}}},r.flushBufferGap=function(t){var e=this.media;if(e)if(zr.isBuffered(e,e.currentTime)){var r=e.currentTime,i=zr.bufferInfo(e,r,0),n=t.duration,a=Math.min(2*this.config.maxFragLookUpTolerance,.25*n),s=Math.max(Math.min(t.start-a,i.end-a),r+a);t.start-s>a&&this.flushMainBuffer(s,t.start)}else this.flushMainBuffer(0,t.start)},r.getFwdBufferInfo=function(t,e){var r=this.getLoadPosition();return y(r)?this.getFwdBufferInfoAtPos(t,r,e):null},r.getFwdBufferInfoAtPos=function(t,e,r){var i=this.config.maxBufferHole,n=zr.bufferInfo(t,e,i);if(0===n.len&&void 0!==n.nextStart){var a=this.fragmentTracker.getBufferedFrag(e,r);if(a&&n.nextStart<a.end)return zr.bufferInfo(t,e,Math.max(n.nextStart,i))}return n},r.getMaxBufferLength=function(t){var e,r=this.config;return e=t?Math.max(8*r.maxBufferSize/t,r.maxBufferLength):r.maxBufferLength,Math.min(e,r.maxMaxBufferLength)},r.reduceMaxBufferLength=function(t){var e=this.config,r=t||e.maxBufferLength;return e.maxMaxBufferLength>=r&&(e.maxMaxBufferLength/=2,this.warn("Reduce max buffer length to "+e.maxMaxBufferLength+"s"),!0)},r.getAppendedFrag=function(t,e){var r=this.fragmentTracker.getAppendedFrag(t,Ie);return r&&"fragment"in r?r.fragment:r},r.getNextFragment=function(t,e){var r=e.fragments,i=r.length;if(!i)return null;var n,a=this.config,s=r[0].start;if(e.live){var o=a.initialLiveManifestSize;if(i<o)return this.warn("Not enough fragments to start playback (have: "+i+", need: "+o+")"),null;(!e.PTSKnown&&!this.startFragRequested&&-1===this.startPosition||t<s)&&(n=this.getInitialLiveFragment(e,r),this.startPosition=this.nextLoadPosition=n?this.hls.liveSyncPosition||n.start:t)}else t<=s&&(n=r[0]);if(!n){var l=a.lowLatencyMode?e.partEnd:e.fragmentEnd;n=this.getFragmentAtPosition(t,l,e)}return this.mapToInitFragWhenRequired(n)},r.isLoopLoading=function(t,e){var r=this.fragmentTracker.getState(t);return(r===Yr||r===Vr&&!!t.gap)&&this.nextLoadPosition>e},r.getNextFragmentLoopLoading=function(t,e,r,i,n){var a=t.gap,s=this.getNextFragment(this.nextLoadPosition,e);if(null===s)return s;if(t=s,a&&t&&!t.gap&&r.nextStart){var o=this.getFwdBufferInfoAtPos(this.mediaBuffer?this.mediaBuffer:this.media,r.nextStart,i);if(null!==o&&r.len+o.len>=n)return this.log('buffer full after gaps in "'+i+'" playlist starting at sn: '+t.sn),null}return t},r.mapToInitFragWhenRequired=function(t){return null==t||!t.initSegment||null!=t&&t.initSegment.data||this.bitrateTest?t:t.initSegment},r.getNextPart=function(t,e,r){for(var i=-1,n=!1,a=!0,s=0,o=t.length;s<o;s++){var l=t[s];if(a=a&&!l.independent,i>-1&&r<l.start)break;var u=l.loaded;u?i=-1:(n||l.independent||a)&&l.fragment===e&&(i=s),n=u}return i},r.loadedEndOfParts=function(t,e){var r=t[t.length-1];return r&&e>r.start&&r.loaded},r.getInitialLiveFragment=function(t,e){var r=this.fragPrevious,i=null;if(r){if(t.hasProgramDateTime&&(this.log("Live playlist, switching playlist, load frag with same PDT: "+r.programDateTime),i=function(t,e,r){if(null===e||!Array.isArray(t)||!t.length||!y(e))return null;if(e<(t[0].programDateTime||0))return null;if(e>=(t[t.length-1].endProgramDateTime||0))return null;r=r||0;for(var i=0;i<t.length;++i){var n=t[i];if(yr(e,r,n))return n}return null}(e,r.endProgramDateTime,this.config.maxFragLookUpTolerance)),!i){var n=r.sn+1;if(n>=t.startSN&&n<=t.endSN){var a=e[n-t.startSN];r.cc===a.cc&&(i=a,this.log("Live playlist, switching playlist, load frag with next SN: "+i.sn))}i||(i=function(t,e){return vr(t,(function(t){return t.cc<e?1:t.cc>e?-1:0}))}(e,r.cc),i&&this.log("Live playlist, switching playlist, load frag with same CC: "+i.sn))}}else{var s=this.hls.liveSyncPosition;null!==s&&(i=this.getFragmentAtPosition(s,this.bitrateTest?t.fragmentEnd:t.edge,t))}return i},r.getFragmentAtPosition=function(t,e,r){var i,n=this.config,a=this.fragPrevious,s=r.fragments,o=r.endSN,l=r.fragmentHint,u=n.maxFragLookUpTolerance,h=r.partList,d=!!(n.lowLatencyMode&&null!=h&&h.length&&l);if(d&&l&&!this.bitrateTest&&(s=s.concat(l),o=l.sn),i=t<e?mr(a,s,t,t>e-u?0:u):s[s.length-1]){var c=i.sn-r.startSN,f=this.fragmentTracker.getState(i);if((f===Yr||f===Vr&&i.gap)&&(a=i),a&&i.sn===a.sn&&(!d||h[0].fragment.sn>i.sn)&&a&&i.level===a.level){var g=s[c+1];i=i.sn<o&&this.fragmentTracker.getState(g)!==Yr?g:null}}return i},r.synchronizeToLiveEdge=function(t){var e=this.config,r=this.media;if(r){var i=this.hls.liveSyncPosition,n=r.currentTime,a=t.fragments[0].start,s=t.edge,o=n>=a-e.maxFragLookUpTolerance&&n<=s;if(null!==i&&r.duration>i&&(n<i||!o)){var l=void 0!==e.liveMaxLatencyDuration?e.liveMaxLatencyDuration:e.liveMaxLatencyDurationCount*t.targetduration;(!o&&r.readyState<4||n<s-l)&&(this.loadedmetadata||(this.nextLoadPosition=i),r.readyState&&(this.warn("Playback: "+n.toFixed(3)+" is located too far from the end of live sliding playlist: "+s+", reset currentTime to : "+i.toFixed(3)),r.currentTime=i))}}},r.alignPlaylists=function(t,e,r){var i=t.fragments.length;if(!i)return this.warn("No fragments in live playlist"),0;var n=t.fragments[0].start,a=!e,s=t.alignedSliding&&y(n);if(a||!s&&!n){var o=this.fragPrevious;ti(o,r,t);var l=t.fragments[0].start;return this.log("Live playlist sliding: "+l.toFixed(2)+" start-sn: "+(e?e.startSN:"na")+"->"+t.startSN+" prev-sn: "+(o?o.sn:"na")+" fragments: "+i),l}return n},r.waitForCdnTuneIn=function(t){return t.live&&t.canBlockReload&&t.partTarget&&t.tuneInGoal>Math.max(t.partHoldBack,3*t.partTarget)},r.setStartPosition=function(t,e){var r=this.startPosition;if(r<e&&(r=-1),-1===r||-1===this.lastCurrentTime){var i=null!==this.startTimeOffset,n=i?this.startTimeOffset:t.startTimeOffset;null!==n&&y(n)?(r=e+n,n<0&&(r+=t.totalduration),r=Math.min(Math.max(e,r),e+t.totalduration),this.log("Start time offset "+n+" found in "+(i?"multivariant":"media")+" playlist, adjust startPosition to "+r),this.startPosition=r):t.live?r=this.hls.liveSyncPosition||e:this.startPosition=r=0,this.lastCurrentTime=r}this.nextLoadPosition=r},r.getLoadPosition=function(){var t=this.media,e=0;return this.loadedmetadata&&t?e=t.currentTime:this.nextLoadPosition&&(e=this.nextLoadPosition),e},r.handleFragLoadAborted=function(t,e){this.transmuxer&&"initSegment"!==t.sn&&t.stats.aborted&&(this.warn("Fragment "+t.sn+(e?" part "+e.index:"")+" of level "+t.level+" was aborted"),this.resetFragmentLoading(t))},r.resetFragmentLoading=function(t){this.fragCurrent&&(this.fragContextChanged(t)||this.state===mi)||(this.state=fi)},r.onFragmentOrKeyLoadError=function(t,e){if(e.chunkMeta&&!e.frag){var r=this.getCurrentContext(e.chunkMeta);r&&(e.frag=r.frag)}var i=e.frag;if(i&&i.type===t&&this.levels)if(this.fragContextChanged(i)){var n;this.warn("Frag load error must match current frag to retry "+i.url+" > "+(null==(n=this.fragCurrent)?void 0:n.url))}else{var a=e.details===A.FRAG_GAP;a&&this.fragmentTracker.fragBuffered(i,!0);var s=e.errorAction,o=s||{},l=o.action,u=o.retryCount,h=void 0===u?0:u,d=o.retryConfig;if(s&&l===Lr&&d){this.resetStartWhenNotLoaded(this.levelLastLoaded);var c=cr(d,h);this.warn("Fragment "+i.sn+" of "+t+" "+i.level+" errored with "+e.details+", retrying loading "+(h+1)+"/"+d.maxNumRetry+" in "+c+"ms"),s.resolved=!0,this.retryDate=self.performance.now()+c,this.state=mi}else if(d&&s){if(this.resetFragmentErrors(t),!(h<d.maxNumRetry))return void w.warn(e.details+" reached or exceeded max retry ("+h+")");a||l===Sr||(s.resolved=!0)}else(null==s?void 0:s.action)===Tr?this.state=Ai:this.state=Si;this.tickImmediate()}},r.reduceLengthAndFlushBuffer=function(t){if(this.state===yi||this.state===Ei){var e=t.parent,r=this.getFwdBufferInfo(this.mediaBuffer,e),i=r&&r.len>.5;i&&this.reduceMaxBufferLength(r.len);var n=!i;return n&&this.warn("Buffer full error while media.currentTime is not buffered, flush "+e+" buffer"),t.frag&&(this.fragmentTracker.removeFragment(t.frag),this.nextLoadPosition=t.frag.start),this.resetLoadingState(),n}return!1},r.resetFragmentErrors=function(t){t===we&&(this.fragCurrent=null),this.loadedmetadata||(this.startFragRequested=!1),this.state!==ci&&(this.state=fi)},r.afterBufferFlushed=function(t,e,r){if(t){var i=zr.getBuffered(t);this.fragmentTracker.detectEvictedFragments(e,i,r),this.state===Ti&&this.resetLoadingState()}},r.resetLoadingState=function(){this.log("Reset loading state"),this.fragCurrent=null,this.fragPrevious=null,this.state=fi},r.resetStartWhenNotLoaded=function(t){if(!this.loadedmetadata){this.startFragRequested=!1;var e=t?t.details:null;null!=e&&e.live?(this.startPosition=-1,this.setStartPosition(e,0),this.resetLoadingState()):this.nextLoadPosition=this.startPosition}},r.resetWhenMissingContext=function(t){this.warn("The loading context changed while buffering fragment "+t.sn+" of level "+t.level+". This chunk will not be buffered."),this.removeUnbufferedFrags(),this.resetStartWhenNotLoaded(this.levelLastLoaded),this.resetLoadingState()},r.removeUnbufferedFrags=function(t){void 0===t&&(t=0),this.fragmentTracker.removeFragmentsInRange(t,1/0,this.playlistType,!1,!0)},r.updateLevelTiming=function(t,e,r,i){var n,a=this,s=r.details;if(s){if(!Object.keys(t.elementaryStreams).reduce((function(e,n){var o=t.elementaryStreams[n];if(o){var l=o.endPTS-o.startPTS;if(l<=0)return a.warn("Could not parse fragment "+t.sn+" "+n+" duration reliably ("+l+")"),e||!1;var u=i?0:ir(s,t,o.startPTS,o.endPTS,o.startDTS,o.endDTS);return a.hls.trigger(S.LEVEL_PTS_UPDATED,{details:s,level:r,drift:u,type:n,frag:t,start:o.startPTS,end:o.endPTS}),!0}return e}),!1)&&null===(null==(n=this.transmuxer)?void 0:n.error)){var o=new Error("Found no media in fragment "+t.sn+" of level "+t.level+" resetting transmuxer to fallback to playlist timing");if(0===r.fragmentError&&(r.fragmentError++,t.gap=!0,this.fragmentTracker.removeFragment(t),this.fragmentTracker.fragBuffered(t,!0)),this.warn(o.message),this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,error:o,frag:t,reason:"Found no media in msn "+t.sn+' of level "'+r.url+'"'}),!this.hls)return;this.resetTransmuxer()}this.state=Ei,this.hls.trigger(S.FRAG_PARSED,{frag:t,part:e})}else this.warn("level.details undefined")},r.resetTransmuxer=function(){this.transmuxer&&(this.transmuxer.destroy(),this.transmuxer=null)},r.recoverWorkerError=function(t){"demuxerWorker"===t.event&&(this.fragmentTracker.removeAllFragments(),this.resetTransmuxer(),this.resetStartWhenNotLoaded(this.levelLastLoaded),this.resetLoadingState())},s(e,[{key:"state",get:function(){return this._state},set:function(t){var e=this._state;e!==t&&(this._state=t,this.log(e+"->"+t))}}]),e}(Gr),ki=function(){function t(){this.chunks=[],this.dataLength=0}var e=t.prototype;return e.push=function(t){this.chunks.push(t),this.dataLength+=t.length},e.flush=function(){var t,e=this.chunks,r=this.dataLength;return e.length?(t=1===e.length?e[0]:function(t,e){for(var r=new Uint8Array(e),i=0,n=0;n<t.length;n++){var a=t[n];r.set(a,i),i+=a.length}return r}(e,r),this.reset(),t):new Uint8Array(0)},e.reset=function(){this.chunks.length=0,this.dataLength=0},t}();function bi(t,e){return void 0===t&&(t=""),void 0===e&&(e=9e4),{type:t,id:-1,pid:-1,inputTimeScale:e,sequenceNumber:-1,samples:[],dropped:0}}var Di=function(){function t(){this._audioTrack=void 0,this._id3Track=void 0,this.frameIndex=0,this.cachedData=null,this.basePTS=null,this.initPTS=null,this.lastPTS=null}var e=t.prototype;return e.resetInitSegment=function(t,e,r,i){this._id3Track={type:"id3",id:3,pid:-1,inputTimeScale:9e4,sequenceNumber:0,samples:[],dropped:0}},e.resetTimeStamp=function(t){this.initPTS=t,this.resetContiguity()},e.resetContiguity=function(){this.basePTS=null,this.lastPTS=null,this.frameIndex=0},e.canParse=function(t,e){return!1},e.appendFrame=function(t,e,r){},e.demux=function(t,e){this.cachedData&&(t=Gt(this.cachedData,t),this.cachedData=null);var r,i=lt(t,0),n=i?i.length:0,a=this._audioTrack,s=this._id3Track,o=i?dt(i):void 0,l=t.length;for((null===this.basePTS||0===this.frameIndex&&y(o))&&(this.basePTS=Ii(o,e,this.initPTS),this.lastPTS=this.basePTS),null===this.lastPTS&&(this.lastPTS=this.basePTS),i&&i.length>0&&s.samples.push({pts:this.lastPTS,dts:this.lastPTS,data:i,type:Be,duration:Number.POSITIVE_INFINITY});n<l;){if(this.canParse(t,n)){var u=this.appendFrame(a,t,n);u?(this.frameIndex++,this.lastPTS=u.sample.pts,r=n+=u.length):n=l}else ht(t,n)?(i=lt(t,n),s.samples.push({pts:this.lastPTS,dts:this.lastPTS,data:i,type:Be,duration:Number.POSITIVE_INFINITY}),r=n+=i.length):n++;if(n===l&&r!==l){var h=nt(t,r);this.cachedData?this.cachedData=Gt(this.cachedData,h):this.cachedData=h}}return{audioTrack:a,videoTrack:bi(),id3Track:s,textTrack:bi()}},e.demuxSampleAes=function(t,e,r){return Promise.reject(new Error("["+this+"] This demuxer does not support Sample-AES decryption"))},e.flush=function(t){var e=this.cachedData;return e&&(this.cachedData=null,this.demux(e,0)),{audioTrack:this._audioTrack,videoTrack:bi(),id3Track:this._id3Track,textTrack:bi()}},e.destroy=function(){},t}(),Ii=function(t,e,r){return y(t)?90*t:9e4*e+(r?9e4*r.baseTime/r.timescale:0)};function wi(t,e){return 255===t[e]&&240==(246&t[e+1])}function Ci(t,e){return 1&t[e+1]?7:9}function _i(t,e){return(3&t[e+3])<<11|t[e+4]<<3|(224&t[e+5])>>>5}function xi(t,e){return e+1<t.length&&wi(t,e)}function Pi(t,e){if(xi(t,e)){var r=Ci(t,e);if(e+r>=t.length)return!1;var i=_i(t,e);if(i<=r)return!1;var n=e+i;return n===t.length||xi(t,n)}return!1}function Fi(t,e,r,i,n){if(!t.samplerate){var a=function(t,e,r,i){var n,a,s,o,l=navigator.userAgent.toLowerCase(),u=i,h=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];n=1+((192&e[r+2])>>>6);var d=(60&e[r+2])>>>2;if(!(d>h.length-1))return s=(1&e[r+2])<<2,s|=(192&e[r+3])>>>6,w.log("manifest codec:"+i+", ADTS type:"+n+", samplingIndex:"+d),/firefox/i.test(l)?d>=6?(n=5,o=new Array(4),a=d-3):(n=2,o=new Array(2),a=d):-1!==l.indexOf("android")?(n=2,o=new Array(2),a=d):(n=5,o=new Array(4),i&&(-1!==i.indexOf("mp4a.40.29")||-1!==i.indexOf("mp4a.40.5"))||!i&&d>=6?a=d-3:((i&&-1!==i.indexOf("mp4a.40.2")&&(d>=6&&1===s||/vivaldi/i.test(l))||!i&&1===s)&&(n=2,o=new Array(2)),a=d)),o[0]=n<<3,o[0]|=(14&d)>>1,o[1]|=(1&d)<<7,o[1]|=s<<3,5===n&&(o[1]|=(14&a)>>1,o[2]=(1&a)<<7,o[2]|=8,o[3]=0),{config:o,samplerate:h[d],channelCount:s,codec:"mp4a.40."+n,manifestCodec:u};var c=new Error("invalid ADTS sampling index:"+d);t.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!0,error:c,reason:c.message})}(e,r,i,n);if(!a)return;t.config=a.config,t.samplerate=a.samplerate,t.channelCount=a.channelCount,t.codec=a.codec,t.manifestCodec=a.manifestCodec,w.log("parsed codec:"+t.codec+", rate:"+a.samplerate+", channels:"+a.channelCount)}}function Mi(t){return 9216e4/t}function Oi(t,e,r,i,n){var a,s=i+n*Mi(t.samplerate),o=function(t,e){var r=Ci(t,e);if(e+r<=t.length){var i=_i(t,e)-r;if(i>0)return{headerLength:r,frameLength:i}}}(e,r);if(o){var l=o.frameLength,u=o.headerLength,h=u+l,d=Math.max(0,r+h-e.length);d?(a=new Uint8Array(h-u)).set(e.subarray(r+u,e.length),0):a=e.subarray(r+u,r+h);var c={unit:a,pts:s};return d||t.samples.push(c),{sample:c,length:h,missing:d}}var f=e.length-r;return(a=new Uint8Array(f)).set(e.subarray(r,e.length),0),{sample:{unit:a,pts:s},length:f,missing:-1}}var Ni=null,Ui=[32,64,96,128,160,192,224,256,288,320,352,384,416,448,32,48,56,64,80,96,112,128,160,192,224,256,320,384,32,40,48,56,64,80,96,112,128,160,192,224,256,320,32,48,56,64,80,96,112,128,144,160,176,192,224,256,8,16,24,32,40,48,56,64,80,96,112,128,144,160],Bi=[44100,48e3,32e3,22050,24e3,16e3,11025,12e3,8e3],Gi=[[0,72,144,12],[0,0,0,0],[0,72,144,12],[0,144,144,12]],Ki=[0,1,1,4];function Hi(t,e,r,i,n){if(!(r+24>e.length)){var a=Vi(e,r);if(a&&r+a.frameLength<=e.length){var s=i+n*(9e4*a.samplesPerFrame/a.sampleRate),o={unit:e.subarray(r,r+a.frameLength),pts:s,dts:s};return t.config=[],t.channelCount=a.channelCount,t.samplerate=a.sampleRate,t.samples.push(o),{sample:o,length:a.frameLength,missing:0}}}}function Vi(t,e){var r=t[e+1]>>3&3,i=t[e+1]>>1&3,n=t[e+2]>>4&15,a=t[e+2]>>2&3;if(1!==r&&0!==n&&15!==n&&3!==a){var s=t[e+2]>>1&1,o=t[e+3]>>6,l=1e3*Ui[14*(3===r?3-i:3===i?3:4)+n-1],u=Bi[3*(3===r?0:2===r?1:2)+a],h=3===o?1:2,d=Gi[r][i],c=Ki[i],f=8*d*c,g=Math.floor(d*l/u+s)*c;if(null===Ni){var v=(navigator.userAgent||"").match(/Chrome\/(\d+)/i);Ni=v?parseInt(v[1]):0}return!!Ni&&Ni<=87&&2===i&&l>=224e3&&0===o&&(t[e+3]=128|t[e+3]),{sampleRate:u,channelCount:h,frameLength:g,samplesPerFrame:f}}}function Yi(t,e){return 255===t[e]&&224==(224&t[e+1])&&0!=(6&t[e+1])}function Wi(t,e){return e+1<t.length&&Yi(t,e)}function ji(t,e){if(e+1<t.length&&Yi(t,e)){var r=Vi(t,e),i=4;null!=r&&r.frameLength&&(i=r.frameLength);var n=e+i;return n===t.length||Wi(t,n)}return!1}var qi=function(t){function e(e,r){var i;return(i=t.call(this)||this).observer=void 0,i.config=void 0,i.observer=e,i.config=r,i}l(e,t);var r=e.prototype;return r.resetInitSegment=function(e,r,i,n){t.prototype.resetInitSegment.call(this,e,r,i,n),this._audioTrack={container:"audio/adts",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"aac",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},e.probe=function(t){if(!t)return!1;var e=lt(t,0),r=(null==e?void 0:e.length)||0;if(ji(t,r))return!1;for(var i=t.length;r<i;r++)if(Pi(t,r))return w.log("ADTS sync word found !"),!0;return!1},r.canParse=function(t,e){return function(t,e){return function(t,e){return e+5<t.length}(t,e)&&wi(t,e)&&_i(t,e)<=t.length-e}(t,e)},r.appendFrame=function(t,e,r){Fi(t,this.observer,e,r,t.manifestCodec);var i=Oi(t,e,r,this.basePTS,this.frameIndex);if(i&&0===i.missing)return i},e}(Di),Xi=/\/emsg[-/]ID3/i,zi=function(){function t(t,e){this.remainderData=null,this.timeOffset=0,this.config=void 0,this.videoTrack=void 0,this.audioTrack=void 0,this.id3Track=void 0,this.txtTrack=void 0,this.config=e}var e=t.prototype;return e.resetTimeStamp=function(){},e.resetInitSegment=function(t,e,r,i){var n=this.videoTrack=bi("video",1),a=this.audioTrack=bi("audio",1),s=this.txtTrack=bi("text",1);if(this.id3Track=bi("id3",1),this.timeOffset=0,null!=t&&t.byteLength){var o=Pt(t);if(o.video){var l=o.video,u=l.id,h=l.timescale,d=l.codec;n.id=u,n.timescale=s.timescale=h,n.codec=d}if(o.audio){var c=o.audio,f=c.id,g=c.timescale,v=c.codec;a.id=f,a.timescale=g,a.codec=v}s.id=kt.text,n.sampleDuration=0,n.duration=a.duration=i}},e.resetContiguity=function(){this.remainderData=null},t.probe=function(t){return function(t){for(var e=t.byteLength,r=0;r<e;){var i=It(t,r);if(i>8&&109===t[r+4]&&111===t[r+5]&&111===t[r+6]&&102===t[r+7])return!0;r=i>1?r+i:e}return!1}(t)},e.demux=function(t,e){this.timeOffset=e;var r=t,i=this.videoTrack,n=this.txtTrack;if(this.config.progressive){this.remainderData&&(r=Gt(this.remainderData,t));var a=function(t){var e={valid:null,remainder:null},r=_t(t,["moof"]);if(r.length<2)return e.remainder=t,e;var i=r[r.length-1];return e.valid=nt(t,0,i.byteOffset-8),e.remainder=nt(t,i.byteOffset-8),e}(r);this.remainderData=a.remainder,i.samples=a.valid||new Uint8Array}else i.samples=r;var s=this.extractID3Track(i,e);return n.samples=Kt(e,i),{videoTrack:i,audioTrack:this.audioTrack,id3Track:s,textTrack:this.txtTrack}},e.flush=function(){var t=this.timeOffset,e=this.videoTrack,r=this.txtTrack;e.samples=this.remainderData||new Uint8Array,this.remainderData=null;var i=this.extractID3Track(e,this.timeOffset);return r.samples=Kt(t,e),{videoTrack:e,audioTrack:bi(),id3Track:i,textTrack:bi()}},e.extractID3Track=function(t,e){var r=this.id3Track;if(t.samples.length){var i=_t(t.samples,["emsg"]);i&&i.forEach((function(t){var i=function(t){var e=t[0],r="",i="",n=0,a=0,s=0,o=0,l=0,u=0;if(0===e){for(;"\0"!==bt(t.subarray(u,u+1));)r+=bt(t.subarray(u,u+1)),u+=1;for(r+=bt(t.subarray(u,u+1)),u+=1;"\0"!==bt(t.subarray(u,u+1));)i+=bt(t.subarray(u,u+1)),u+=1;i+=bt(t.subarray(u,u+1)),u+=1,n=It(t,12),a=It(t,16),o=It(t,20),l=It(t,24),u=28}else if(1===e){n=It(t,u+=4);var h=It(t,u+=4),d=It(t,u+=4);for(u+=4,s=Math.pow(2,32)*h+d,E(s)||(s=Number.MAX_SAFE_INTEGER,w.warn("Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box")),o=It(t,u),l=It(t,u+=4),u+=4;"\0"!==bt(t.subarray(u,u+1));)r+=bt(t.subarray(u,u+1)),u+=1;for(r+=bt(t.subarray(u,u+1)),u+=1;"\0"!==bt(t.subarray(u,u+1));)i+=bt(t.subarray(u,u+1)),u+=1;i+=bt(t.subarray(u,u+1)),u+=1}return{schemeIdUri:r,value:i,timeScale:n,presentationTime:s,presentationTimeDelta:a,eventDuration:o,id:l,payload:t.subarray(u,t.byteLength)}}(t);if(Xi.test(i.schemeIdUri)){var n=y(i.presentationTime)?i.presentationTime/i.timeScale:e+i.presentationTimeDelta/i.timeScale,a=4294967295===i.eventDuration?Number.POSITIVE_INFINITY:i.eventDuration/i.timeScale;a<=.001&&(a=Number.POSITIVE_INFINITY);var s=i.payload;r.samples.push({data:s,len:s.byteLength,dts:n,pts:n,type:Ke,duration:a})}}))}return r},e.demuxSampleAes=function(t,e,r){return Promise.reject(new Error("The MP4 demuxer does not support SAMPLE-AES decryption"))},e.destroy=function(){},t}(),Qi=function(t,e){var r=0,i=5;e+=i;for(var n=new Uint32Array(1),a=new Uint32Array(1),s=new Uint8Array(1);i>0;){s[0]=t[e];var o=Math.min(i,8),l=8-o;a[0]=4278190080>>>24+l<<l,n[0]=(s[0]&a[0])>>l,r=r?r<<o|n[0]:n[0],e+=1,i-=o}return r},Ji=function(t){function e(e){var r;return(r=t.call(this)||this).observer=void 0,r.observer=e,r}l(e,t);var r=e.prototype;return r.resetInitSegment=function(e,r,i,n){t.prototype.resetInitSegment.call(this,e,r,i,n),this._audioTrack={container:"audio/ac-3",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"ac3",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},r.canParse=function(t,e){return e+64<t.length},r.appendFrame=function(t,e,r){var i=$i(t,e,r,this.basePTS,this.frameIndex);if(-1!==i)return{sample:t.samples[t.samples.length-1],length:i,missing:0}},e.probe=function(t){if(!t)return!1;var e=lt(t,0);if(!e)return!1;var r=e.length;return 11===t[r]&&119===t[r+1]&&void 0!==dt(e)&&Qi(t,r)<16},e}(Di);function $i(t,e,r,i,n){if(r+8>e.length)return-1;if(11!==e[r]||119!==e[r+1])return-1;var a=e[r+4]>>6;if(a>=3)return-1;var s=[48e3,44100,32e3][a],o=63&e[r+4],l=2*[64,69,96,64,70,96,80,87,120,80,88,120,96,104,144,96,105,144,112,121,168,112,122,168,128,139,192,128,140,192,160,174,240,160,175,240,192,208,288,192,209,288,224,243,336,224,244,336,256,278,384,256,279,384,320,348,480,320,349,480,384,417,576,384,418,576,448,487,672,448,488,672,512,557,768,512,558,768,640,696,960,640,697,960,768,835,1152,768,836,1152,896,975,1344,896,976,1344,1024,1114,1536,1024,1115,1536,1152,1253,1728,1152,1254,1728,1280,1393,1920,1280,1394,1920][3*o+a];if(r+l>e.length)return-1;var u=e[r+6]>>5,h=0;2===u?h+=2:(1&u&&1!==u&&(h+=2),4&u&&(h+=2));var d=(e[r+6]<<8|e[r+7])>>12-h&1,c=[2,1,2,3,3,4,4,5][u]+d,f=e[r+5]>>3,g=7&e[r+5],v=new Uint8Array([a<<6|f<<1|g>>2,(3&g)<<6|u<<3|d<<2|o>>4,o<<4&224]),m=i+n*(1536/s*9e4),p=e.subarray(r,r+l);return t.config=v,t.channelCount=c,t.samplerate=s,t.samples.push({unit:p,pts:m}),l}var Zi=function(){function t(){this.VideoSample=null}var e=t.prototype;return e.createVideoSample=function(t,e,r,i){return{key:t,frame:!1,pts:e,dts:r,units:[],debug:i,length:0}},e.getLastNalUnit=function(t){var e,r,i=this.VideoSample;if(i&&0!==i.units.length||(i=t[t.length-1]),null!=(e=i)&&e.units){var n=i.units;r=n[n.length-1]}return r},e.pushAccessUnit=function(t,e){if(t.units.length&&t.frame){if(void 0===t.pts){var r=e.samples,i=r.length;if(!i)return void e.dropped++;var n=r[i-1];t.pts=n.pts,t.dts=n.dts}e.samples.push(t)}t.debug.length&&w.log(t.pts+"/"+t.dts+":"+t.debug)},t}(),tn=function(){function t(t){this.data=void 0,this.bytesAvailable=void 0,this.word=void 0,this.bitsAvailable=void 0,this.data=t,this.bytesAvailable=t.byteLength,this.word=0,this.bitsAvailable=0}var e=t.prototype;return e.loadWord=function(){var t=this.data,e=this.bytesAvailable,r=t.byteLength-e,i=new Uint8Array(4),n=Math.min(4,e);if(0===n)throw new Error("no bytes available");i.set(t.subarray(r,r+n)),this.word=new DataView(i.buffer).getUint32(0),this.bitsAvailable=8*n,this.bytesAvailable-=n},e.skipBits=function(t){var e;t=Math.min(t,8*this.bytesAvailable+this.bitsAvailable),this.bitsAvailable>t?(this.word<<=t,this.bitsAvailable-=t):(t-=this.bitsAvailable,t-=(e=t>>3)<<3,this.bytesAvailable-=e,this.loadWord(),this.word<<=t,this.bitsAvailable-=t)},e.readBits=function(t){var e=Math.min(this.bitsAvailable,t),r=this.word>>>32-e;if(t>32&&w.error("Cannot read more than 32 bits at a time"),this.bitsAvailable-=e,this.bitsAvailable>0)this.word<<=e;else{if(!(this.bytesAvailable>0))throw new Error("no bits available");this.loadWord()}return(e=t-e)>0&&this.bitsAvailable?r<<e|this.readBits(e):r},e.skipLZ=function(){var t;for(t=0;t<this.bitsAvailable;++t)if(0!=(this.word&2147483648>>>t))return this.word<<=t,this.bitsAvailable-=t,t;return this.loadWord(),t+this.skipLZ()},e.skipUEG=function(){this.skipBits(1+this.skipLZ())},e.skipEG=function(){this.skipBits(1+this.skipLZ())},e.readUEG=function(){var t=this.skipLZ();return this.readBits(t+1)-1},e.readEG=function(){var t=this.readUEG();return 1&t?1+t>>>1:-1*(t>>>1)},e.readBoolean=function(){return 1===this.readBits(1)},e.readUByte=function(){return this.readBits(8)},e.readUShort=function(){return this.readBits(16)},e.readUInt=function(){return this.readBits(32)},e.skipScalingList=function(t){for(var e=8,r=8,i=0;i<t;i++)0!==r&&(r=(e+this.readEG()+256)%256),e=0===r?e:r},e.readSPS=function(){var t,e,r,i=0,n=0,a=0,s=0,o=this.readUByte.bind(this),l=this.readBits.bind(this),u=this.readUEG.bind(this),h=this.readBoolean.bind(this),d=this.skipBits.bind(this),c=this.skipEG.bind(this),f=this.skipUEG.bind(this),g=this.skipScalingList.bind(this);o();var v=o();if(l(5),d(3),o(),f(),100===v||110===v||122===v||244===v||44===v||83===v||86===v||118===v||128===v){var m=u();if(3===m&&d(1),f(),f(),d(1),h())for(e=3!==m?8:12,r=0;r<e;r++)h()&&g(r<6?16:64)}f();var p=u();if(0===p)u();else if(1===p)for(d(1),c(),c(),t=u(),r=0;r<t;r++)c();f(),d(1);var y=u(),E=u(),T=l(1);0===T&&d(1),d(1),h()&&(i=u(),n=u(),a=u(),s=u());var S=[1,1];if(h()&&h())switch(o()){case 1:S=[1,1];break;case 2:S=[12,11];break;case 3:S=[10,11];break;case 4:S=[16,11];break;case 5:S=[40,33];break;case 6:S=[24,11];break;case 7:S=[20,11];break;case 8:S=[32,11];break;case 9:S=[80,33];break;case 10:S=[18,11];break;case 11:S=[15,11];break;case 12:S=[64,33];break;case 13:S=[160,99];break;case 14:S=[4,3];break;case 15:S=[3,2];break;case 16:S=[2,1];break;case 255:S=[o()<<8|o(),o()<<8|o()]}return{width:Math.ceil(16*(y+1)-2*i-2*n),height:(2-T)*(E+1)*16-(T?2:4)*(a+s),pixelRatio:S}},e.readSliceType=function(){return this.readUByte(),this.readUEG(),this.readUEG()},t}(),en=function(t){function e(){return t.apply(this,arguments)||this}l(e,t);var r=e.prototype;return r.parseAVCPES=function(t,e,r,i,n){var a,s=this,o=this.parseAVCNALu(t,r.data),l=this.VideoSample,u=!1;r.data=null,l&&o.length&&!t.audFound&&(this.pushAccessUnit(l,t),l=this.VideoSample=this.createVideoSample(!1,r.pts,r.dts,"")),o.forEach((function(i){var o;switch(i.type){case 1:var h=!1;a=!0;var d,c=i.data;if(u&&c.length>4){var f=new tn(c).readSliceType();2!==f&&4!==f&&7!==f&&9!==f||(h=!0)}h&&null!=(d=l)&&d.frame&&!l.key&&(s.pushAccessUnit(l,t),l=s.VideoSample=null),l||(l=s.VideoSample=s.createVideoSample(!0,r.pts,r.dts,"")),l.frame=!0,l.key=h;break;case 5:a=!0,null!=(o=l)&&o.frame&&!l.key&&(s.pushAccessUnit(l,t),l=s.VideoSample=null),l||(l=s.VideoSample=s.createVideoSample(!0,r.pts,r.dts,"")),l.key=!0,l.frame=!0;break;case 6:a=!0,Vt(i.data,1,r.pts,e.samples);break;case 7:var g,v;a=!0,u=!0;var m=i.data,p=new tn(m).readSPS();if(!t.sps||t.width!==p.width||t.height!==p.height||(null==(g=t.pixelRatio)?void 0:g[0])!==p.pixelRatio[0]||(null==(v=t.pixelRatio)?void 0:v[1])!==p.pixelRatio[1]){t.width=p.width,t.height=p.height,t.pixelRatio=p.pixelRatio,t.sps=[m],t.duration=n;for(var y=m.subarray(1,4),E="avc1.",T=0;T<3;T++){var S=y[T].toString(16);S.length<2&&(S="0"+S),E+=S}t.codec=E}break;case 8:a=!0,t.pps=[i.data];break;case 9:a=!0,t.audFound=!0,l&&s.pushAccessUnit(l,t),l=s.VideoSample=s.createVideoSample(!1,r.pts,r.dts,"");break;case 12:a=!0;break;default:a=!1,l&&(l.debug+="unknown NAL "+i.type+" ")}l&&a&&l.units.push(i)})),i&&l&&(this.pushAccessUnit(l,t),this.VideoSample=null)},r.parseAVCNALu=function(t,e){var r,i,n=e.byteLength,a=t.naluState||0,s=a,o=[],l=0,u=-1,h=0;for(-1===a&&(u=0,h=31&e[0],a=0,l=1);l<n;)if(r=e[l++],a)if(1!==a)if(r)if(1===r){if(i=l-a-1,u>=0){var d={data:e.subarray(u,i),type:h};o.push(d)}else{var c=this.getLastNalUnit(t.samples);c&&(s&&l<=4-s&&c.state&&(c.data=c.data.subarray(0,c.data.byteLength-s)),i>0&&(c.data=Gt(c.data,e.subarray(0,i)),c.state=0))}l<n?(u=l,h=31&e[l],a=0):a=-1}else a=0;else a=3;else a=r?0:2;else a=r?0:1;if(u>=0&&a>=0){var f={data:e.subarray(u,n),type:h,state:a};o.push(f)}if(0===o.length){var g=this.getLastNalUnit(t.samples);g&&(g.data=Gt(g.data,e))}return t.naluState=a,o},e}(Zi),rn=function(){function t(t,e,r){this.keyData=void 0,this.decrypter=void 0,this.keyData=r,this.decrypter=new hi(e,{removePKCS7Padding:!1})}var e=t.prototype;return e.decryptBuffer=function(t){return this.decrypter.decrypt(t,this.keyData.key.buffer,this.keyData.iv.buffer)},e.decryptAacSample=function(t,e,r){var i=this,n=t[e].unit;if(!(n.length<=16)){var a=n.subarray(16,n.length-n.length%16),s=a.buffer.slice(a.byteOffset,a.byteOffset+a.length);this.decryptBuffer(s).then((function(a){var s=new Uint8Array(a);n.set(s,16),i.decrypter.isSync()||i.decryptAacSamples(t,e+1,r)}))}},e.decryptAacSamples=function(t,e,r){for(;;e++){if(e>=t.length)return void r();if(!(t[e].unit.length<32||(this.decryptAacSample(t,e,r),this.decrypter.isSync())))return}},e.getAvcEncryptedData=function(t){for(var e=16*Math.floor((t.length-48)/160)+16,r=new Int8Array(e),i=0,n=32;n<t.length-16;n+=160,i+=16)r.set(t.subarray(n,n+16),i);return r},e.getAvcDecryptedUnit=function(t,e){for(var r=new Uint8Array(e),i=0,n=32;n<t.length-16;n+=160,i+=16)t.set(r.subarray(i,i+16),n);return t},e.decryptAvcSample=function(t,e,r,i,n){var a=this,s=Yt(n.data),o=this.getAvcEncryptedData(s);this.decryptBuffer(o.buffer).then((function(o){n.data=a.getAvcDecryptedUnit(s,o),a.decrypter.isSync()||a.decryptAvcSamples(t,e,r+1,i)}))},e.decryptAvcSamples=function(t,e,r,i){if(t instanceof Uint8Array)throw new Error("Cannot decrypt samples of type Uint8Array");for(;;e++,r=0){if(e>=t.length)return void i();for(var n=t[e].units;!(r>=n.length);r++){var a=n[r];if(!(a.data.length<=48||1!==a.type&&5!==a.type||(this.decryptAvcSample(t,e,r,i,a),this.decrypter.isSync())))return}}},t}(),nn=188,an=function(){function t(t,e,r){this.observer=void 0,this.config=void 0,this.typeSupported=void 0,this.sampleAes=null,this.pmtParsed=!1,this.audioCodec=void 0,this.videoCodec=void 0,this._duration=0,this._pmtId=-1,this._videoTrack=void 0,this._audioTrack=void 0,this._id3Track=void 0,this._txtTrack=void 0,this.aacOverFlow=null,this.remainderData=null,this.videoParser=void 0,this.observer=t,this.config=e,this.typeSupported=r,this.videoParser=new en}t.probe=function(e){var r=t.syncOffset(e);return r>0&&w.warn("MPEG2-TS detected but first sync word found @ offset "+r),-1!==r},t.syncOffset=function(t){for(var e=t.length,r=Math.min(940,e-nn)+1,i=0;i<r;){for(var n=!1,a=-1,s=0,o=i;o<e;o+=nn){if(71!==t[o]||e-o!==nn&&71!==t[o+nn]){if(s)return-1;break}if(s++,-1===a&&0!==(a=o)&&(r=Math.min(a+18612,t.length-nn)+1),n||(n=0===sn(t,o)),n&&s>1&&(0===a&&s>2||o+nn>r))return a}i++}return-1},t.createTrack=function(t,e){return{container:"video"===t||"audio"===t?"video/mp2t":void 0,type:t,id:kt[t],pid:-1,inputTimeScale:9e4,sequenceNumber:0,samples:[],dropped:0,duration:"audio"===t?e:void 0}};var e=t.prototype;return e.resetInitSegment=function(e,r,i,n){this.pmtParsed=!1,this._pmtId=-1,this._videoTrack=t.createTrack("video"),this._audioTrack=t.createTrack("audio",n),this._id3Track=t.createTrack("id3"),this._txtTrack=t.createTrack("text"),this._audioTrack.segmentCodec="aac",this.aacOverFlow=null,this.remainderData=null,this.audioCodec=r,this.videoCodec=i,this._duration=n},e.resetTimeStamp=function(){},e.resetContiguity=function(){var t=this._audioTrack,e=this._videoTrack,r=this._id3Track;t&&(t.pesData=null),e&&(e.pesData=null),r&&(r.pesData=null),this.aacOverFlow=null,this.remainderData=null},e.demux=function(e,r,i,n){var a;void 0===i&&(i=!1),void 0===n&&(n=!1),i||(this.sampleAes=null);var s=this._videoTrack,o=this._audioTrack,l=this._id3Track,u=this._txtTrack,h=s.pid,d=s.pesData,c=o.pid,f=l.pid,g=o.pesData,v=l.pesData,m=null,p=this.pmtParsed,y=this._pmtId,E=e.length;if(this.remainderData&&(E=(e=Gt(this.remainderData,e)).length,this.remainderData=null),E<nn&&!n)return this.remainderData=e,{audioTrack:o,videoTrack:s,id3Track:l,textTrack:u};var T=Math.max(0,t.syncOffset(e));(E-=(E-T)%nn)<e.byteLength&&!n&&(this.remainderData=new Uint8Array(e.buffer,E,e.buffer.byteLength-E));for(var R=0,k=T;k<E;k+=nn)if(71===e[k]){var b=!!(64&e[k+1]),D=sn(e,k),I=void 0;if((48&e[k+3])>>4>1){if((I=k+5+e[k+4])===k+nn)continue}else I=k+4;switch(D){case h:b&&(d&&(a=hn(d))&&this.videoParser.parseAVCPES(s,u,a,!1,this._duration),d={data:[],size:0}),d&&(d.data.push(e.subarray(I,k+nn)),d.size+=k+nn-I);break;case c:if(b){if(g&&(a=hn(g)))switch(o.segmentCodec){case"aac":this.parseAACPES(o,a);break;case"mp3":this.parseMPEGPES(o,a);break;case"ac3":this.parseAC3PES(o,a)}g={data:[],size:0}}g&&(g.data.push(e.subarray(I,k+nn)),g.size+=k+nn-I);break;case f:b&&(v&&(a=hn(v))&&this.parseID3PES(l,a),v={data:[],size:0}),v&&(v.data.push(e.subarray(I,k+nn)),v.size+=k+nn-I);break;case 0:b&&(I+=e[I]+1),y=this._pmtId=on(e,I);break;case y:b&&(I+=e[I]+1);var C=ln(e,I,this.typeSupported,i);(h=C.videoPid)>0&&(s.pid=h,s.segmentCodec=C.segmentVideoCodec),(c=C.audioPid)>0&&(o.pid=c,o.segmentCodec=C.segmentAudioCodec),(f=C.id3Pid)>0&&(l.pid=f),null===m||p||(w.warn("MPEG-TS PMT found at "+k+" after unknown PID '"+m+"'. Backtracking to sync byte @"+T+" to parse all TS packets."),m=null,k=T-188),p=this.pmtParsed=!0;break;case 17:case 8191:break;default:m=D}}else R++;if(R>0){var _=new Error("Found "+R+" TS packet/s that do not start with 0x47");this.observer.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,error:_,reason:_.message})}s.pesData=d,o.pesData=g,l.pesData=v;var x={audioTrack:o,videoTrack:s,id3Track:l,textTrack:u};return n&&this.extractRemainingSamples(x),x},e.flush=function(){var t,e=this.remainderData;return this.remainderData=null,t=e?this.demux(e,-1,!1,!0):{videoTrack:this._videoTrack,audioTrack:this._audioTrack,id3Track:this._id3Track,textTrack:this._txtTrack},this.extractRemainingSamples(t),this.sampleAes?this.decrypt(t,this.sampleAes):t},e.extractRemainingSamples=function(t){var e,r=t.audioTrack,i=t.videoTrack,n=t.id3Track,a=t.textTrack,s=i.pesData,o=r.pesData,l=n.pesData;if(s&&(e=hn(s))?(this.videoParser.parseAVCPES(i,a,e,!0,this._duration),i.pesData=null):i.pesData=s,o&&(e=hn(o))){switch(r.segmentCodec){case"aac":this.parseAACPES(r,e);break;case"mp3":this.parseMPEGPES(r,e);break;case"ac3":this.parseAC3PES(r,e)}r.pesData=null}else null!=o&&o.size&&w.log("last AAC PES packet truncated,might overlap between fragments"),r.pesData=o;l&&(e=hn(l))?(this.parseID3PES(n,e),n.pesData=null):n.pesData=l},e.demuxSampleAes=function(t,e,r){var i=this.demux(t,r,!0,!this.config.progressive),n=this.sampleAes=new rn(this.observer,this.config,e);return this.decrypt(i,n)},e.decrypt=function(t,e){return new Promise((function(r){var i=t.audioTrack,n=t.videoTrack;i.samples&&"aac"===i.segmentCodec?e.decryptAacSamples(i.samples,0,(function(){n.samples?e.decryptAvcSamples(n.samples,0,0,(function(){r(t)})):r(t)})):n.samples&&e.decryptAvcSamples(n.samples,0,0,(function(){r(t)}))}))},e.destroy=function(){this._duration=0},e.parseAACPES=function(t,e){var r,i,n,a=0,s=this.aacOverFlow,o=e.data;if(s){this.aacOverFlow=null;var l=s.missing,u=s.sample.unit.byteLength;if(-1===l)o=Gt(s.sample.unit,o);else{var h=u-l;s.sample.unit.set(o.subarray(0,l),h),t.samples.push(s.sample),a=s.missing}}for(r=a,i=o.length;r<i-1&&!xi(o,r);r++);if(r!==a){var d,c=r<i-1;d=c?"AAC PES did not start with ADTS header,offset:"+r:"No ADTS header found in AAC PES";var f=new Error(d);if(w.warn("parsing error: "+d),this.observer.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,levelRetry:c,error:f,reason:d}),!c)return}if(Fi(t,this.observer,o,r,this.audioCodec),void 0!==e.pts)n=e.pts;else{if(!s)return void w.warn("[tsdemuxer]: AAC PES unknown PTS");var g=Mi(t.samplerate);n=s.sample.pts+g}for(var v,m=0;r<i;){if(r+=(v=Oi(t,o,r,n,m)).length,v.missing){this.aacOverFlow=v;break}for(m++;r<i-1&&!xi(o,r);r++);}},e.parseMPEGPES=function(t,e){var r=e.data,i=r.length,n=0,a=0,s=e.pts;if(void 0!==s)for(;a<i;)if(Wi(r,a)){var o=Hi(t,r,a,s,n);if(!o)break;a+=o.length,n++}else a++;else w.warn("[tsdemuxer]: MPEG PES unknown PTS")},e.parseAC3PES=function(t,e){var r=e.data,i=e.pts;if(void 0!==i)for(var n,a=r.length,s=0,o=0;o<a&&(n=$i(t,r,o,i,s++))>0;)o+=n;else w.warn("[tsdemuxer]: AC3 PES unknown PTS")},e.parseID3PES=function(t,e){if(void 0!==e.pts){var r=o({},e,{type:this._videoTrack?Ke:Be,duration:Number.POSITIVE_INFINITY});t.samples.push(r)}else w.warn("[tsdemuxer]: ID3 PES unknown PTS")},t}();function sn(t,e){return((31&t[e+1])<<8)+t[e+2]}function on(t,e){return(31&t[e+10])<<8|t[e+11]}function ln(t,e,r,i){var n={audioPid:-1,videoPid:-1,id3Pid:-1,segmentVideoCodec:"avc",segmentAudioCodec:"aac"},a=e+3+((15&t[e+1])<<8|t[e+2])-4;for(e+=12+((15&t[e+10])<<8|t[e+11]);e<a;){var s=sn(t,e),o=(15&t[e+3])<<8|t[e+4];switch(t[e]){case 207:if(!i){un("ADTS AAC");break}case 15:-1===n.audioPid&&(n.audioPid=s);break;case 21:-1===n.id3Pid&&(n.id3Pid=s);break;case 219:if(!i){un("H.264");break}case 27:-1===n.videoPid&&(n.videoPid=s,n.segmentVideoCodec="avc");break;case 3:case 4:r.mpeg||r.mp3?-1===n.audioPid&&(n.audioPid=s,n.segmentAudioCodec="mp3"):w.log("MPEG audio found, not supported in this browser");break;case 193:if(!i){un("AC-3");break}case 129:r.ac3?-1===n.audioPid&&(n.audioPid=s,n.segmentAudioCodec="ac3"):w.log("AC-3 audio found, not supported in this browser");break;case 6:if(-1===n.audioPid&&o>0)for(var l=e+5,u=o;u>2;){106===t[l]&&(!0!==r.ac3?w.log("AC-3 audio found, not supported in this browser for now"):(n.audioPid=s,n.segmentAudioCodec="ac3"));var h=t[l+1]+2;l+=h,u-=h}break;case 194:case 135:w.warn("Unsupported EC-3 in M2TS found");break;case 36:w.warn("Unsupported HEVC in M2TS found")}e+=o+5}return n}function un(t){w.log(t+" with AES-128-CBC encryption found in unencrypted stream")}function hn(t){var e,r,i,n,a,s=0,o=t.data;if(!t||0===t.size)return null;for(;o[0].length<19&&o.length>1;)o[0]=Gt(o[0],o[1]),o.splice(1,1);if(1===((e=o[0])[0]<<16)+(e[1]<<8)+e[2]){if((r=(e[4]<<8)+e[5])&&r>t.size-6)return null;var l=e[7];192&l&&(n=536870912*(14&e[9])+4194304*(255&e[10])+16384*(254&e[11])+128*(255&e[12])+(254&e[13])/2,64&l?n-(a=536870912*(14&e[14])+4194304*(255&e[15])+16384*(254&e[16])+128*(255&e[17])+(254&e[18])/2)>54e5&&(w.warn(Math.round((n-a)/9e4)+"s delta between PTS and DTS, align them"),n=a):a=n);var u=(i=e[8])+9;if(t.size<=u)return null;t.size-=u;for(var h=new Uint8Array(t.size),d=0,c=o.length;d<c;d++){var f=(e=o[d]).byteLength;if(u){if(u>f){u-=f;continue}e=e.subarray(u),f-=u,u=0}h.set(e,s),s+=f}return r&&(r-=i+3),{data:h,pts:n,dts:a,len:r}}return null}var dn=function(t){function e(){return t.apply(this,arguments)||this}l(e,t);var r=e.prototype;return r.resetInitSegment=function(e,r,i,n){t.prototype.resetInitSegment.call(this,e,r,i,n),this._audioTrack={container:"audio/mpeg",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"mp3",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},e.probe=function(t){if(!t)return!1;var e=lt(t,0),r=(null==e?void 0:e.length)||0;if(e&&11===t[r]&&119===t[r+1]&&void 0!==dt(e)&&Qi(t,r)<=16)return!1;for(var i=t.length;r<i;r++)if(ji(t,r))return w.log("MPEG Audio sync word found !"),!0;return!1},r.canParse=function(t,e){return function(t,e){return Yi(t,e)&&4<=t.length-e}(t,e)},r.appendFrame=function(t,e,r){if(null!==this.basePTS)return Hi(t,e,r,this.basePTS,this.frameIndex)},e}(Di),cn=function(){function t(){}return t.getSilentFrame=function(t,e){if("mp4a.40.2"===t){if(1===e)return new Uint8Array([0,200,0,128,35,128]);if(2===e)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===e)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===e)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===e)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}},t}(),fn=Math.pow(2,32)-1,gn=function(){function t(){}return t.init=function(){var e;for(e in t.types={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],".mp3":[],dac3:[],"ac-3":[],mvex:[],mvhd:[],pasp:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]},t.types)t.types.hasOwnProperty(e)&&(t.types[e]=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]);var r=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),i=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]);t.HDLR_TYPES={video:r,audio:i};var n=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),a=new Uint8Array([0,0,0,0,0,0,0,0]);t.STTS=t.STSC=t.STCO=a,t.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),t.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0]),t.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),t.STSD=new Uint8Array([0,0,0,0,0,0,0,1]);var s=new Uint8Array([105,115,111,109]),o=new Uint8Array([97,118,99,49]),l=new Uint8Array([0,0,0,1]);t.FTYP=t.box(t.types.ftyp,s,l,s,o),t.DINF=t.box(t.types.dinf,t.box(t.types.dref,n))},t.box=function(t){for(var e=8,r=arguments.length,i=new Array(r>1?r-1:0),n=1;n<r;n++)i[n-1]=arguments[n];for(var a=i.length,s=a;a--;)e+=i[a].byteLength;var o=new Uint8Array(e);for(o[0]=e>>24&255,o[1]=e>>16&255,o[2]=e>>8&255,o[3]=255&e,o.set(t,4),a=0,e=8;a<s;a++)o.set(i[a],e),e+=i[a].byteLength;return o},t.hdlr=function(e){return t.box(t.types.hdlr,t.HDLR_TYPES[e])},t.mdat=function(e){return t.box(t.types.mdat,e)},t.mdhd=function(e,r){r*=e;var i=Math.floor(r/(fn+1)),n=Math.floor(r%(fn+1));return t.box(t.types.mdhd,new Uint8Array([1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,e>>24&255,e>>16&255,e>>8&255,255&e,i>>24,i>>16&255,i>>8&255,255&i,n>>24,n>>16&255,n>>8&255,255&n,85,196,0,0]))},t.mdia=function(e){return t.box(t.types.mdia,t.mdhd(e.timescale,e.duration),t.hdlr(e.type),t.minf(e))},t.mfhd=function(e){return t.box(t.types.mfhd,new Uint8Array([0,0,0,0,e>>24,e>>16&255,e>>8&255,255&e]))},t.minf=function(e){return"audio"===e.type?t.box(t.types.minf,t.box(t.types.smhd,t.SMHD),t.DINF,t.stbl(e)):t.box(t.types.minf,t.box(t.types.vmhd,t.VMHD),t.DINF,t.stbl(e))},t.moof=function(e,r,i){return t.box(t.types.moof,t.mfhd(e),t.traf(i,r))},t.moov=function(e){for(var r=e.length,i=[];r--;)i[r]=t.trak(e[r]);return t.box.apply(null,[t.types.moov,t.mvhd(e[0].timescale,e[0].duration)].concat(i).concat(t.mvex(e)))},t.mvex=function(e){for(var r=e.length,i=[];r--;)i[r]=t.trex(e[r]);return t.box.apply(null,[t.types.mvex].concat(i))},t.mvhd=function(e,r){r*=e;var i=Math.floor(r/(fn+1)),n=Math.floor(r%(fn+1)),a=new Uint8Array([1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,e>>24&255,e>>16&255,e>>8&255,255&e,i>>24,i>>16&255,i>>8&255,255&i,n>>24,n>>16&255,n>>8&255,255&n,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]);return t.box(t.types.mvhd,a)},t.sdtp=function(e){var r,i,n=e.samples||[],a=new Uint8Array(4+n.length);for(r=0;r<n.length;r++)i=n[r].flags,a[r+4]=i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy;return t.box(t.types.sdtp,a)},t.stbl=function(e){return t.box(t.types.stbl,t.stsd(e),t.box(t.types.stts,t.STTS),t.box(t.types.stsc,t.STSC),t.box(t.types.stsz,t.STSZ),t.box(t.types.stco,t.STCO))},t.avc1=function(e){var r,i,n,a=[],s=[];for(r=0;r<e.sps.length;r++)n=(i=e.sps[r]).byteLength,a.push(n>>>8&255),a.push(255&n),a=a.concat(Array.prototype.slice.call(i));for(r=0;r<e.pps.length;r++)n=(i=e.pps[r]).byteLength,s.push(n>>>8&255),s.push(255&n),s=s.concat(Array.prototype.slice.call(i));var o=t.box(t.types.avcC,new Uint8Array([1,a[3],a[4],a[5],255,224|e.sps.length].concat(a).concat([e.pps.length]).concat(s))),l=e.width,u=e.height,h=e.pixelRatio[0],d=e.pixelRatio[1];return t.box(t.types.avc1,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,l>>8&255,255&l,u>>8&255,255&u,0,72,0,0,0,72,0,0,0,0,0,0,0,1,18,100,97,105,108,121,109,111,116,105,111,110,47,104,108,115,46,106,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,17,17]),o,t.box(t.types.btrt,new Uint8Array([0,28,156,128,0,45,198,192,0,45,198,192])),t.box(t.types.pasp,new Uint8Array([h>>24,h>>16&255,h>>8&255,255&h,d>>24,d>>16&255,d>>8&255,255&d])))},t.esds=function(t){var e=t.config.length;return new Uint8Array([0,0,0,0,3,23+e,0,1,0,4,15+e,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([e]).concat(t.config).concat([6,1,2]))},t.audioStsd=function(t){var e=t.samplerate;return new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t.channelCount,0,16,0,0,0,0,e>>8&255,255&e,0,0])},t.mp4a=function(e){return t.box(t.types.mp4a,t.audioStsd(e),t.box(t.types.esds,t.esds(e)))},t.mp3=function(e){return t.box(t.types[".mp3"],t.audioStsd(e))},t.ac3=function(e){return t.box(t.types["ac-3"],t.audioStsd(e),t.box(t.types.dac3,e.config))},t.stsd=function(e){return"audio"===e.type?"mp3"===e.segmentCodec&&"mp3"===e.codec?t.box(t.types.stsd,t.STSD,t.mp3(e)):"ac3"===e.segmentCodec?t.box(t.types.stsd,t.STSD,t.ac3(e)):t.box(t.types.stsd,t.STSD,t.mp4a(e)):t.box(t.types.stsd,t.STSD,t.avc1(e))},t.tkhd=function(e){var r=e.id,i=e.duration*e.timescale,n=e.width,a=e.height,s=Math.floor(i/(fn+1)),o=Math.floor(i%(fn+1));return t.box(t.types.tkhd,new Uint8Array([1,0,0,7,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,r>>24&255,r>>16&255,r>>8&255,255&r,0,0,0,0,s>>24,s>>16&255,s>>8&255,255&s,o>>24,o>>16&255,o>>8&255,255&o,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,n>>8&255,255&n,0,0,a>>8&255,255&a,0,0]))},t.traf=function(e,r){var i=t.sdtp(e),n=e.id,a=Math.floor(r/(fn+1)),s=Math.floor(r%(fn+1));return t.box(t.types.traf,t.box(t.types.tfhd,new Uint8Array([0,0,0,0,n>>24,n>>16&255,n>>8&255,255&n])),t.box(t.types.tfdt,new Uint8Array([1,0,0,0,a>>24,a>>16&255,a>>8&255,255&a,s>>24,s>>16&255,s>>8&255,255&s])),t.trun(e,i.length+16+20+8+16+8+8),i)},t.trak=function(e){return e.duration=e.duration||4294967295,t.box(t.types.trak,t.tkhd(e),t.mdia(e))},t.trex=function(e){var r=e.id;return t.box(t.types.trex,new Uint8Array([0,0,0,0,r>>24,r>>16&255,r>>8&255,255&r,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]))},t.trun=function(e,r){var i,n,a,s,o,l,u=e.samples||[],h=u.length,d=12+16*h,c=new Uint8Array(d);for(r+=8+d,c.set(["video"===e.type?1:0,0,15,1,h>>>24&255,h>>>16&255,h>>>8&255,255&h,r>>>24&255,r>>>16&255,r>>>8&255,255&r],0),i=0;i<h;i++)a=(n=u[i]).duration,s=n.size,o=n.flags,l=n.cts,c.set([a>>>24&255,a>>>16&255,a>>>8&255,255&a,s>>>24&255,s>>>16&255,s>>>8&255,255&s,o.isLeading<<2|o.dependsOn,o.isDependedOn<<6|o.hasRedundancy<<4|o.paddingValue<<1|o.isNonSync,61440&o.degradPrio,15&o.degradPrio,l>>>24&255,l>>>16&255,l>>>8&255,255&l],12+16*i);return t.box(t.types.trun,c)},t.initSegment=function(e){t.types||t.init();var r=t.moov(e);return Gt(t.FTYP,r)},t}();gn.types=void 0,gn.HDLR_TYPES=void 0,gn.STTS=void 0,gn.STSC=void 0,gn.STCO=void 0,gn.STSZ=void 0,gn.VMHD=void 0,gn.SMHD=void 0,gn.STSD=void 0,gn.FTYP=void 0,gn.DINF=void 0;var vn=9e4;function mn(t,e,r,i){void 0===r&&(r=1),void 0===i&&(i=!1);var n=t*e*r;return i?Math.round(n):n}function pn(t,e){return void 0===e&&(e=!1),mn(t,1e3,1/vn,e)}var yn=null,En=null,Tn=function(){function t(t,e,r,i){if(this.observer=void 0,this.config=void 0,this.typeSupported=void 0,this.ISGenerated=!1,this._initPTS=null,this._initDTS=null,this.nextAvcDts=null,this.nextAudioPts=null,this.videoSampleDuration=null,this.isAudioContiguous=!1,this.isVideoContiguous=!1,this.videoTrackConfig=void 0,this.observer=t,this.config=e,this.typeSupported=r,this.ISGenerated=!1,null===yn){var n=(navigator.userAgent||"").match(/Chrome\/(\d+)/i);yn=n?parseInt(n[1]):0}if(null===En){var a=navigator.userAgent.match(/Safari\/(\d+)/i);En=a?parseInt(a[1]):0}}var e=t.prototype;return e.destroy=function(){this.config=this.videoTrackConfig=this._initPTS=this._initDTS=null},e.resetTimeStamp=function(t){w.log("[mp4-remuxer]: initPTS & initDTS reset"),this._initPTS=this._initDTS=t},e.resetNextTimestamp=function(){w.log("[mp4-remuxer]: reset next timestamp"),this.isVideoContiguous=!1,this.isAudioContiguous=!1},e.resetInitSegment=function(){w.log("[mp4-remuxer]: ISGenerated flag reset"),this.ISGenerated=!1,this.videoTrackConfig=void 0},e.getVideoStartPts=function(t){var e=!1,r=t.reduce((function(t,r){var i=r.pts-t;return i<-4294967296?(e=!0,Sn(t,r.pts)):i>0?t:r.pts}),t[0].pts);return e&&w.debug("PTS rollover detected"),r},e.remux=function(t,e,r,i,n,a,s,o){var l,u,h,d,c,f,g=n,v=n,m=t.pid>-1,p=e.pid>-1,y=e.samples.length,E=t.samples.length>0,T=s&&y>0||y>1;if((!m||E)&&(!p||T)||this.ISGenerated||s){if(this.ISGenerated){var S,L,A,R,k=this.videoTrackConfig;!k||e.width===k.width&&e.height===k.height&&(null==(S=e.pixelRatio)?void 0:S[0])===(null==(L=k.pixelRatio)?void 0:L[0])&&(null==(A=e.pixelRatio)?void 0:A[1])===(null==(R=k.pixelRatio)?void 0:R[1])||this.resetInitSegment()}else h=this.generateIS(t,e,n,a);var b,D=this.isVideoContiguous,I=-1;if(T&&(I=function(t){for(var e=0;e<t.length;e++)if(t[e].key)return e;return-1}(e.samples),!D&&this.config.forceKeyFrameOnDiscontinuity))if(f=!0,I>0){w.warn("[mp4-remuxer]: Dropped "+I+" out of "+y+" video samples due to a missing keyframe");var C=this.getVideoStartPts(e.samples);e.samples=e.samples.slice(I),e.dropped+=I,b=v+=(e.samples[0].pts-C)/e.inputTimeScale}else-1===I&&(w.warn("[mp4-remuxer]: No keyframe found out of "+y+" video samples"),f=!1);if(this.ISGenerated){if(E&&T){var _=this.getVideoStartPts(e.samples),x=(Sn(t.samples[0].pts,_)-_)/e.inputTimeScale;g+=Math.max(0,x),v+=Math.max(0,-x)}if(E){if(t.samplerate||(w.warn("[mp4-remuxer]: regenerate InitSegment as audio detected"),h=this.generateIS(t,e,n,a)),u=this.remuxAudio(t,g,this.isAudioContiguous,a,p||T||o===we?v:void 0),T){var P=u?u.endPTS-u.startPTS:0;e.inputTimeScale||(w.warn("[mp4-remuxer]: regenerate InitSegment as video detected"),h=this.generateIS(t,e,n,a)),l=this.remuxVideo(e,v,D,P)}}else T&&(l=this.remuxVideo(e,v,D,0));l&&(l.firstKeyFrame=I,l.independent=-1!==I,l.firstKeyFramePTS=b)}}return this.ISGenerated&&this._initPTS&&this._initDTS&&(r.samples.length&&(c=Ln(r,n,this._initPTS,this._initDTS)),i.samples.length&&(d=An(i,n,this._initPTS))),{audio:u,video:l,initSegment:h,independent:f,text:d,id3:c}},e.generateIS=function(t,e,r,i){var n,a,s,o=t.samples,l=e.samples,u=this.typeSupported,h={},d=this._initPTS,c=!d||i,f="audio/mp4";if(c&&(n=a=1/0),t.config&&o.length){switch(t.timescale=t.samplerate,t.segmentCodec){case"mp3":u.mpeg?(f="audio/mpeg",t.codec=""):u.mp3&&(t.codec="mp3");break;case"ac3":t.codec="ac-3"}h.audio={id:"audio",container:f,codec:t.codec,initSegment:"mp3"===t.segmentCodec&&u.mpeg?new Uint8Array(0):gn.initSegment([t]),metadata:{channelCount:t.channelCount}},c&&(s=t.inputTimeScale,d&&s===d.timescale?c=!1:n=a=o[0].pts-Math.round(s*r))}if(e.sps&&e.pps&&l.length){if(e.timescale=e.inputTimeScale,h.video={id:"main",container:"video/mp4",codec:e.codec,initSegment:gn.initSegment([e]),metadata:{width:e.width,height:e.height}},c)if(s=e.inputTimeScale,d&&s===d.timescale)c=!1;else{var g=this.getVideoStartPts(l),v=Math.round(s*r);a=Math.min(a,Sn(l[0].dts,g)-v),n=Math.min(n,g-v)}this.videoTrackConfig={width:e.width,height:e.height,pixelRatio:e.pixelRatio}}if(Object.keys(h).length)return this.ISGenerated=!0,c?(this._initPTS={baseTime:n,timescale:s},this._initDTS={baseTime:a,timescale:s}):n=s=void 0,{tracks:h,initPTS:n,timescale:s}},e.remuxVideo=function(t,e,r,i){var n,a,s=t.inputTimeScale,l=t.samples,u=[],h=l.length,d=this._initPTS,c=this.nextAvcDts,f=8,g=this.videoSampleDuration,v=Number.POSITIVE_INFINITY,m=Number.NEGATIVE_INFINITY,p=!1;if(!r||null===c){var y=e*s,E=l[0].pts-Sn(l[0].dts,l[0].pts);yn&&null!==c&&Math.abs(y-E-c)<15e3?r=!0:c=y-E}for(var T=d.baseTime*s/d.timescale,R=0;R<h;R++){var k=l[R];k.pts=Sn(k.pts-T,c),k.dts=Sn(k.dts-T,c),k.dts<l[R>0?R-1:R].dts&&(p=!0)}p&&l.sort((function(t,e){var r=t.dts-e.dts,i=t.pts-e.pts;return r||i})),n=l[0].dts;var b=(a=l[l.length-1].dts)-n,D=b?Math.round(b/(h-1)):g||t.inputTimeScale/30;if(r){var I=n-c,C=I>D,_=I<-1;if((C||_)&&(C?w.warn("AVC: "+pn(I,!0)+" ms ("+I+"dts) hole between fragments detected at "+e.toFixed(3)):w.warn("AVC: "+pn(-I,!0)+" ms ("+I+"dts) overlapping between fragments detected at "+e.toFixed(3)),!_||c>=l[0].pts||yn)){n=c;var x=l[0].pts-I;if(C)l[0].dts=n,l[0].pts=x;else for(var P=0;P<l.length&&!(l[P].dts>x);P++)l[P].dts-=I,l[P].pts-=I;w.log("Video: Initial PTS/DTS adjusted: "+pn(x,!0)+"/"+pn(n,!0)+", delta: "+pn(I,!0)+" ms")}}for(var F=0,M=0,O=n=Math.max(0,n),N=0;N<h;N++){for(var U=l[N],B=U.units,G=B.length,K=0,H=0;H<G;H++)K+=B[H].data.length;M+=K,F+=G,U.length=K,U.dts<O?(U.dts=O,O+=D/4|0||1):O=U.dts,v=Math.min(U.pts,v),m=Math.max(U.pts,m)}a=l[h-1].dts;var V,Y=M+4*F+8;try{V=new Uint8Array(Y)}catch(t){return void this.observer.emit(S.ERROR,S.ERROR,{type:L.MUX_ERROR,details:A.REMUX_ALLOC_ERROR,fatal:!1,error:t,bytes:Y,reason:"fail allocating video mdat "+Y})}var W=new DataView(V.buffer);W.setUint32(0,Y),V.set(gn.types.mdat,4);for(var j=!1,q=Number.POSITIVE_INFINITY,X=Number.POSITIVE_INFINITY,z=Number.NEGATIVE_INFINITY,Q=Number.NEGATIVE_INFINITY,J=0;J<h;J++){for(var $=l[J],Z=$.units,tt=0,et=0,rt=Z.length;et<rt;et++){var it=Z[et],nt=it.data,at=it.data.byteLength;W.setUint32(f,at),f+=4,V.set(nt,f),f+=at,tt+=4+at}var st=void 0;if(J<h-1)g=l[J+1].dts-$.dts,st=l[J+1].pts-$.pts;else{var ot=this.config,lt=J>0?$.dts-l[J-1].dts:D;if(st=J>0?$.pts-l[J-1].pts:D,ot.stretchShortVideoTrack&&null!==this.nextAudioPts){var ut=Math.floor(ot.maxBufferHole*s),ht=(i?v+i*s:this.nextAudioPts)-$.pts;ht>ut?((g=ht-lt)<0?g=lt:j=!0,w.log("[mp4-remuxer]: It is approximately "+ht/90+" ms to the next segment; using duration "+g/90+" ms for the last video frame.")):g=lt}else g=lt}var dt=Math.round($.pts-$.dts);q=Math.min(q,g),z=Math.max(z,g),X=Math.min(X,st),Q=Math.max(Q,st),u.push(new kn($.key,g,tt,dt))}if(u.length)if(yn){if(yn<70){var ct=u[0].flags;ct.dependsOn=2,ct.isNonSync=0}}else if(En&&Q-X<z-q&&D/z<.025&&0===u[0].cts){w.warn("Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.");for(var ft=n,gt=0,vt=u.length;gt<vt;gt++){var mt=ft+u[gt].duration,pt=ft+u[gt].cts;if(gt<vt-1){var yt=mt+u[gt+1].cts;u[gt].duration=yt-pt}else u[gt].duration=gt?u[gt-1].duration:D;u[gt].cts=0,ft=mt}}g=j||!g?D:g,this.nextAvcDts=c=a+g,this.videoSampleDuration=g,this.isVideoContiguous=!0;var Et={data1:gn.moof(t.sequenceNumber++,n,o({},t,{samples:u})),data2:V,startPTS:v/s,endPTS:(m+g)/s,startDTS:n/s,endDTS:c/s,type:"video",hasAudio:!1,hasVideo:!0,nb:u.length,dropped:t.dropped};return t.samples=[],t.dropped=0,Et},e.getSamplesPerFrame=function(t){switch(t.segmentCodec){case"mp3":return 1152;case"ac3":return 1536;default:return 1024}},e.remuxAudio=function(t,e,r,i,n){var a=t.inputTimeScale,s=a/(t.samplerate?t.samplerate:a),l=this.getSamplesPerFrame(t),u=l*s,h=this._initPTS,d="mp3"===t.segmentCodec&&this.typeSupported.mpeg,c=[],f=void 0!==n,g=t.samples,v=d?0:8,m=this.nextAudioPts||-1,p=e*a,y=h.baseTime*a/h.timescale;if(this.isAudioContiguous=r=r||g.length&&m>0&&(i&&Math.abs(p-m)<9e3||Math.abs(Sn(g[0].pts-y,p)-m)<20*u),g.forEach((function(t){t.pts=Sn(t.pts-y,p)})),!r||m<0){if(g=g.filter((function(t){return t.pts>=0})),!g.length)return;m=0===n?0:i&&!f?Math.max(0,p):g[0].pts}if("aac"===t.segmentCodec)for(var E=this.config.maxAudioFramesDrift,T=0,R=m;T<g.length;T++){var k=g[T],b=k.pts,D=b-R,I=Math.abs(1e3*D/a);if(D<=-E*u&&f)0===T&&(w.warn("Audio frame @ "+(b/a).toFixed(3)+"s overlaps nextAudioPts by "+Math.round(1e3*D/a)+" ms."),this.nextAudioPts=m=R=b);else if(D>=E*u&&I<1e4&&f){var C=Math.round(D/u);(R=b-C*u)<0&&(C--,R+=u),0===T&&(this.nextAudioPts=m=R),w.warn("[mp4-remuxer]: Injecting "+C+" audio frame @ "+(R/a).toFixed(3)+"s due to "+Math.round(1e3*D/a)+" ms gap.");for(var _=0;_<C;_++){var x=Math.max(R,0),P=cn.getSilentFrame(t.manifestCodec||t.codec,t.channelCount);P||(w.log("[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead."),P=k.unit.subarray()),g.splice(T,0,{unit:P,pts:x}),R+=u,T++}}k.pts=R,R+=u}for(var F,M=null,O=null,N=0,U=g.length;U--;)N+=g[U].unit.byteLength;for(var B=0,G=g.length;B<G;B++){var K=g[B],H=K.unit,V=K.pts;if(null!==O)c[B-1].duration=Math.round((V-O)/s);else{if(r&&"aac"===t.segmentCodec&&(V=m),M=V,!(N>0))return;N+=v;try{F=new Uint8Array(N)}catch(t){return void this.observer.emit(S.ERROR,S.ERROR,{type:L.MUX_ERROR,details:A.REMUX_ALLOC_ERROR,fatal:!1,error:t,bytes:N,reason:"fail allocating audio mdat "+N})}d||(new DataView(F.buffer).setUint32(0,N),F.set(gn.types.mdat,4))}F.set(H,v);var Y=H.byteLength;v+=Y,c.push(new kn(!0,l,Y,0)),O=V}var W=c.length;if(W){var j=c[c.length-1];this.nextAudioPts=m=O+s*j.duration;var q=d?new Uint8Array(0):gn.moof(t.sequenceNumber++,M/s,o({},t,{samples:c}));t.samples=[];var X=M/a,z=m/a,Q={data1:q,data2:F,startPTS:X,endPTS:z,startDTS:X,endDTS:z,type:"audio",hasAudio:!0,hasVideo:!1,nb:W};return this.isAudioContiguous=!0,Q}},e.remuxEmptyAudio=function(t,e,r,i){var n=t.inputTimeScale,a=n/(t.samplerate?t.samplerate:n),s=this.nextAudioPts,o=this._initDTS,l=9e4*o.baseTime/o.timescale,u=(null!==s?s:i.startDTS*n)+l,h=i.endDTS*n+l,d=1024*a,c=Math.ceil((h-u)/d),f=cn.getSilentFrame(t.manifestCodec||t.codec,t.channelCount);if(w.warn("[mp4-remuxer]: remux empty Audio"),f){for(var g=[],v=0;v<c;v++){var m=u+v*d;g.push({unit:f,pts:m,dts:m})}return t.samples=g,this.remuxAudio(t,e,r,!1)}w.trace("[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec")},t}();function Sn(t,e){var r;if(null===e)return t;for(r=e<t?-8589934592:8589934592;Math.abs(t-e)>4294967296;)t+=r;return t}function Ln(t,e,r,i){var n=t.samples.length;if(n){for(var a=t.inputTimeScale,s=0;s<n;s++){var o=t.samples[s];o.pts=Sn(o.pts-r.baseTime*a/r.timescale,e*a)/a,o.dts=Sn(o.dts-i.baseTime*a/i.timescale,e*a)/a}var l=t.samples;return t.samples=[],{samples:l}}}function An(t,e,r){var i=t.samples.length;if(i){for(var n=t.inputTimeScale,a=0;a<i;a++){var s=t.samples[a];s.pts=Sn(s.pts-r.baseTime*n/r.timescale,e*n)/n}t.samples.sort((function(t,e){return t.pts-e.pts}));var o=t.samples;return t.samples=[],{samples:o}}}var Rn,kn=function(t,e,r,i){this.size=void 0,this.duration=void 0,this.cts=void 0,this.flags=void 0,this.duration=e,this.size=r,this.cts=i,this.flags={isLeading:0,isDependedOn:0,hasRedundancy:0,degradPrio:0,dependsOn:t?2:1,isNonSync:t?0:1}},bn=function(){function t(){this.emitInitSegment=!1,this.audioCodec=void 0,this.videoCodec=void 0,this.initData=void 0,this.initPTS=null,this.initTracks=void 0,this.lastEndTime=null}var e=t.prototype;return e.destroy=function(){},e.resetTimeStamp=function(t){this.initPTS=t,this.lastEndTime=null},e.resetNextTimestamp=function(){this.lastEndTime=null},e.resetInitSegment=function(t,e,r,i){this.audioCodec=e,this.videoCodec=r,this.generateInitSegment(function(t,e){if(!t||!e)return t;var r=e.keyId;return r&&e.isCommonEncryption&&_t(t,["moov","trak"]).forEach((function(t){var e=_t(t,["mdia","minf","stbl","stsd"])[0].subarray(8),i=_t(e,["enca"]),n=i.length>0;n||(i=_t(e,["encv"])),i.forEach((function(t){_t(n?t.subarray(28):t.subarray(78),["sinf"]).forEach((function(t){var e=Ut(t);if(e){var i=e.subarray(8,24);i.some((function(t){return 0!==t}))||(w.log("[eme] Patching keyId in 'enc"+(n?"a":"v")+">sinf>>tenc' box: "+Lt(i)+" -> "+Lt(r)),e.set(r,8))}}))}))})),t}(t,i)),this.emitInitSegment=!0},e.generateInitSegment=function(t){var e=this.audioCodec,r=this.videoCodec;if(null==t||!t.byteLength)return this.initTracks=void 0,void(this.initData=void 0);var i=this.initData=Pt(t);i.audio&&(e=Dn(i.audio,O)),i.video&&(r=Dn(i.video,N));var n={};i.audio&&i.video?n.audiovideo={container:"video/mp4",codec:e+","+r,initSegment:t,id:"main"}:i.audio?n.audio={container:"audio/mp4",codec:e,initSegment:t,id:"audio"}:i.video?n.video={container:"video/mp4",codec:r,initSegment:t,id:"main"}:w.warn("[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes."),this.initTracks=n},e.remux=function(t,e,r,i,n,a){var s,o,l=this.initPTS,u=this.lastEndTime,h={audio:void 0,video:void 0,text:i,id3:r,initSegment:void 0};y(u)||(u=this.lastEndTime=n||0);var d=e.samples;if(null==d||!d.length)return h;var c={initPTS:void 0,timescale:1},f=this.initData;if(null!=(s=f)&&s.length||(this.generateInitSegment(d),f=this.initData),null==(o=f)||!o.length)return w.warn("[passthrough-remuxer.ts]: Failed to generate initSegment."),h;this.emitInitSegment&&(c.tracks=this.initTracks,this.emitInitSegment=!1);var g=function(t,e){for(var r=0,i=0,n=0,a=_t(t,["moof","traf"]),s=0;s<a.length;s++){var o=a[s],l=_t(o,["tfhd"])[0],u=e[It(l,4)];if(u){var h=u.default,d=It(l,0)|(null==h?void 0:h.flags),c=null==h?void 0:h.duration;8&d&&(c=It(l,2&d?12:8));for(var f=u.timescale||9e4,g=_t(o,["trun"]),v=0;v<g.length;v++)!(r=Bt(g[v]))&&c&&(r=c*It(g[v],4)),u.type===N?i+=r/f:u.type===O&&(n+=r/f)}}if(0===i&&0===n){for(var m=0,p=_t(t,["sidx"]),y=0;y<p.length;y++){var E=xt(p[y]);null!=E&&E.references&&(m+=E.references.reduce((function(t,e){return t+e.info.duration||0}),0))}return m}return i||n}(d,f),v=function(t,e){return _t(e,["moof","traf"]).reduce((function(e,r){var i=_t(r,["tfdt"])[0],n=i[0],a=_t(r,["tfhd"]).reduce((function(e,r){var a=It(r,4),s=t[a];if(s){var o=It(i,4);if(1===n){if(o===At)return w.warn("[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time"),e;o*=At+1,o+=It(i,8)}var l=o/(s.timescale||9e4);if(y(l)&&(null===e||l<e))return l}return e}),null);return null!==a&&y(a)&&(null===e||a<e)?a:e}),null)}(f,d),m=null===v?n:v;(function(t,e,r,i){if(null===t)return!0;var n=Math.max(i,1),a=e-t.baseTime/t.timescale;return Math.abs(a-r)>n}(l,m,n,g)||c.timescale!==l.timescale&&a)&&(c.initPTS=m-n,l&&1===l.timescale&&w.warn("Adjusting initPTS by "+(c.initPTS-l.baseTime)),this.initPTS=l={baseTime:c.initPTS,timescale:1});var p=t?m-l.baseTime/l.timescale:u,E=p+g;!function(t,e,r){_t(e,["moof","traf"]).forEach((function(e){_t(e,["tfhd"]).forEach((function(i){var n=It(i,4),a=t[n];if(a){var s=a.timescale||9e4;_t(e,["tfdt"]).forEach((function(t){var e=t[0],i=r*s;if(i){var n=It(t,4);if(0===e)n-=i,Ct(t,4,n=Math.max(n,0));else{n*=Math.pow(2,32),n+=It(t,8),n-=i,n=Math.max(n,0);var a=Math.floor(n/(At+1)),o=Math.floor(n%(At+1));Ct(t,4,a),Ct(t,8,o)}}}))}}))}))}(f,d,l.baseTime/l.timescale),g>0?this.lastEndTime=E:(w.warn("Duration parsed from mp4 should be greater than zero"),this.resetNextTimestamp());var T=!!f.audio,S=!!f.video,L="";T&&(L+="audio"),S&&(L+="video");var A={data1:d,startPTS:p,startDTS:p,endPTS:E,endDTS:E,type:L,hasAudio:T,hasVideo:S,nb:1,dropped:0};return h.audio="audio"===A.type?A:void 0,h.video="audio"!==A.type?A:void 0,h.initSegment=c,h.id3=Ln(r,n,l,l),i.samples.length&&(h.text=An(i,n,l)),h},t}();function Dn(t,e){var r=null==t?void 0:t.codec;if(r&&r.length>4)return r;if(e===O){if("ec-3"===r||"ac-3"===r||"alac"===r)return r;if("fLaC"===r||"Opus"===r)return ue(r,!1);var i="mp4a.40.5";return w.info('Parsed audio codec "'+r+'" or audio object type not handled. Using "'+i+'"'),i}return w.warn('Unhandled video codec "'+r+'"'),"hvc1"===r||"hev1"===r?"hvc1.1.6.L120.90":"av01"===r?"av01.0.04M.08":"avc1.42e01e"}try{Rn=self.performance.now.bind(self.performance)}catch(t){w.debug("Unable to use Performance API on this environment"),Rn=null==j?void 0:j.Date.now}var In=[{demux:zi,remux:bn},{demux:an,remux:Tn},{demux:qi,remux:Tn},{demux:dn,remux:Tn}];In.splice(2,0,{demux:Ji,remux:Tn});var wn=function(){function t(t,e,r,i,n){this.async=!1,this.observer=void 0,this.typeSupported=void 0,this.config=void 0,this.vendor=void 0,this.id=void 0,this.demuxer=void 0,this.remuxer=void 0,this.decrypter=void 0,this.probe=void 0,this.decryptionPromise=null,this.transmuxConfig=void 0,this.currentTransmuxState=void 0,this.observer=t,this.typeSupported=e,this.config=r,this.vendor=i,this.id=n}var e=t.prototype;return e.configure=function(t){this.transmuxConfig=t,this.decrypter&&this.decrypter.reset()},e.push=function(t,e,r,i){var n=this,a=r.transmuxing;a.executeStart=Rn();var s=new Uint8Array(t),o=this.currentTransmuxState,l=this.transmuxConfig;i&&(this.currentTransmuxState=i);var u=i||o,h=u.contiguous,d=u.discontinuity,c=u.trackSwitch,f=u.accurateTimeOffset,g=u.timeOffset,v=u.initSegmentChange,m=l.audioCodec,p=l.videoCodec,y=l.defaultInitPts,E=l.duration,T=l.initSegmentData,R=function(t,e){var r=null;return t.byteLength>0&&null!=(null==e?void 0:e.key)&&null!==e.iv&&null!=e.method&&(r=e),r}(s,e);if(R&&"AES-128"===R.method){var k=this.getDecrypter();if(!k.isSync())return this.decryptionPromise=k.webCryptoDecrypt(s,R.key.buffer,R.iv.buffer).then((function(t){var e=n.push(t,null,r);return n.decryptionPromise=null,e})),this.decryptionPromise;var b=k.softwareDecrypt(s,R.key.buffer,R.iv.buffer);if(r.part>-1&&(b=k.flush()),!b)return a.executeEnd=Rn(),Cn(r);s=new Uint8Array(b)}var D=this.needsProbing(d,c);if(D){var I=this.configureTransmuxer(s);if(I)return w.warn("[transmuxer] "+I.message),this.observer.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,error:I,reason:I.message}),a.executeEnd=Rn(),Cn(r)}(d||c||v||D)&&this.resetInitSegment(T,m,p,E,e),(d||v||D)&&this.resetInitialTimestamp(y),h||this.resetContiguity();var C=this.transmux(s,R,g,f,r),_=this.currentTransmuxState;return _.contiguous=!0,_.discontinuity=!1,_.trackSwitch=!1,a.executeEnd=Rn(),C},e.flush=function(t){var e=this,r=t.transmuxing;r.executeStart=Rn();var i=this.decrypter,n=this.currentTransmuxState,a=this.decryptionPromise;if(a)return a.then((function(){return e.flush(t)}));var s=[],o=n.timeOffset;if(i){var l=i.flush();l&&s.push(this.push(l,null,t))}var u=this.demuxer,h=this.remuxer;if(!u||!h)return r.executeEnd=Rn(),[Cn(t)];var d=u.flush(o);return _n(d)?d.then((function(r){return e.flushRemux(s,r,t),s})):(this.flushRemux(s,d,t),s)},e.flushRemux=function(t,e,r){var i=e.audioTrack,n=e.videoTrack,a=e.id3Track,s=e.textTrack,o=this.currentTransmuxState,l=o.accurateTimeOffset,u=o.timeOffset;w.log("[transmuxer.ts]: Flushed fragment "+r.sn+(r.part>-1?" p: "+r.part:"")+" of level "+r.level);var h=this.remuxer.remux(i,n,a,s,u,l,!0,this.id);t.push({remuxResult:h,chunkMeta:r}),r.transmuxing.executeEnd=Rn()},e.resetInitialTimestamp=function(t){var e=this.demuxer,r=this.remuxer;e&&r&&(e.resetTimeStamp(t),r.resetTimeStamp(t))},e.resetContiguity=function(){var t=this.demuxer,e=this.remuxer;t&&e&&(t.resetContiguity(),e.resetNextTimestamp())},e.resetInitSegment=function(t,e,r,i,n){var a=this.demuxer,s=this.remuxer;a&&s&&(a.resetInitSegment(t,e,r,i),s.resetInitSegment(t,e,r,n))},e.destroy=function(){this.demuxer&&(this.demuxer.destroy(),this.demuxer=void 0),this.remuxer&&(this.remuxer.destroy(),this.remuxer=void 0)},e.transmux=function(t,e,r,i,n){return e&&"SAMPLE-AES"===e.method?this.transmuxSampleAes(t,e,r,i,n):this.transmuxUnencrypted(t,r,i,n)},e.transmuxUnencrypted=function(t,e,r,i){var n=this.demuxer.demux(t,e,!1,!this.config.progressive),a=n.audioTrack,s=n.videoTrack,o=n.id3Track,l=n.textTrack;return{remuxResult:this.remuxer.remux(a,s,o,l,e,r,!1,this.id),chunkMeta:i}},e.transmuxSampleAes=function(t,e,r,i,n){var a=this;return this.demuxer.demuxSampleAes(t,e,r).then((function(t){return{remuxResult:a.remuxer.remux(t.audioTrack,t.videoTrack,t.id3Track,t.textTrack,r,i,!1,a.id),chunkMeta:n}}))},e.configureTransmuxer=function(t){for(var e,r=this.config,i=this.observer,n=this.typeSupported,a=this.vendor,s=0,o=In.length;s<o;s++){var l;if(null!=(l=In[s].demux)&&l.probe(t)){e=In[s];break}}if(!e)return new Error("Failed to find demuxer by probing fragment data");var u=this.demuxer,h=this.remuxer,d=e.remux,c=e.demux;h&&h instanceof d||(this.remuxer=new d(i,r,n,a)),u&&u instanceof c||(this.demuxer=new c(i,r,n),this.probe=c.probe)},e.needsProbing=function(t,e){return!this.demuxer||!this.remuxer||t||e},e.getDecrypter=function(){var t=this.decrypter;return t||(t=this.decrypter=new hi(this.config)),t},t}(),Cn=function(t){return{remuxResult:{},chunkMeta:t}};function _n(t){return"then"in t&&t.then instanceof Function}var xn=function(t,e,r,i,n){this.audioCodec=void 0,this.videoCodec=void 0,this.initSegmentData=void 0,this.duration=void 0,this.defaultInitPts=void 0,this.audioCodec=t,this.videoCodec=e,this.initSegmentData=r,this.duration=i,this.defaultInitPts=n||null},Pn=function(t,e,r,i,n,a){this.discontinuity=void 0,this.contiguous=void 0,this.accurateTimeOffset=void 0,this.trackSwitch=void 0,this.timeOffset=void 0,this.initSegmentChange=void 0,this.discontinuity=t,this.contiguous=e,this.accurateTimeOffset=r,this.trackSwitch=i,this.timeOffset=n,this.initSegmentChange=a},Fn={exports:{}};!function(t){var e=Object.prototype.hasOwnProperty,r="~";function i(){}function n(t,e,r){this.fn=t,this.context=e,this.once=r||!1}function a(t,e,i,a,s){if("function"!=typeof i)throw new TypeError("The listener must be a function");var o=new n(i,a||t,s),l=r?r+e:e;return t._events[l]?t._events[l].fn?t._events[l]=[t._events[l],o]:t._events[l].push(o):(t._events[l]=o,t._eventsCount++),t}function s(t,e){0==--t._eventsCount?t._events=new i:delete t._events[e]}function o(){this._events=new i,this._eventsCount=0}Object.create&&(i.prototype=Object.create(null),(new i).__proto__||(r=!1)),o.prototype.eventNames=function(){var t,i,n=[];if(0===this._eventsCount)return n;for(i in t=this._events)e.call(t,i)&&n.push(r?i.slice(1):i);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(t)):n},o.prototype.listeners=function(t){var e=r?r+t:t,i=this._events[e];if(!i)return[];if(i.fn)return[i.fn];for(var n=0,a=i.length,s=new Array(a);n<a;n++)s[n]=i[n].fn;return s},o.prototype.listenerCount=function(t){var e=r?r+t:t,i=this._events[e];return i?i.fn?1:i.length:0},o.prototype.emit=function(t,e,i,n,a,s){var o=r?r+t:t;if(!this._events[o])return!1;var l,u,h=this._events[o],d=arguments.length;if(h.fn){switch(h.once&&this.removeListener(t,h.fn,void 0,!0),d){case 1:return h.fn.call(h.context),!0;case 2:return h.fn.call(h.context,e),!0;case 3:return h.fn.call(h.context,e,i),!0;case 4:return h.fn.call(h.context,e,i,n),!0;case 5:return h.fn.call(h.context,e,i,n,a),!0;case 6:return h.fn.call(h.context,e,i,n,a,s),!0}for(u=1,l=new Array(d-1);u<d;u++)l[u-1]=arguments[u];h.fn.apply(h.context,l)}else{var c,f=h.length;for(u=0;u<f;u++)switch(h[u].once&&this.removeListener(t,h[u].fn,void 0,!0),d){case 1:h[u].fn.call(h[u].context);break;case 2:h[u].fn.call(h[u].context,e);break;case 3:h[u].fn.call(h[u].context,e,i);break;case 4:h[u].fn.call(h[u].context,e,i,n);break;default:if(!l)for(c=1,l=new Array(d-1);c<d;c++)l[c-1]=arguments[c];h[u].fn.apply(h[u].context,l)}}return!0},o.prototype.on=function(t,e,r){return a(this,t,e,r,!1)},o.prototype.once=function(t,e,r){return a(this,t,e,r,!0)},o.prototype.removeListener=function(t,e,i,n){var a=r?r+t:t;if(!this._events[a])return this;if(!e)return s(this,a),this;var o=this._events[a];if(o.fn)o.fn!==e||n&&!o.once||i&&o.context!==i||s(this,a);else{for(var l=0,u=[],h=o.length;l<h;l++)(o[l].fn!==e||n&&!o[l].once||i&&o[l].context!==i)&&u.push(o[l]);u.length?this._events[a]=1===u.length?u[0]:u:s(this,a)}return this},o.prototype.removeAllListeners=function(t){var e;return t?(e=r?r+t:t,this._events[e]&&s(this,e)):(this._events=new i,this._eventsCount=0),this},o.prototype.off=o.prototype.removeListener,o.prototype.addListener=o.prototype.on,o.prefixed=r,o.EventEmitter=o,t.exports=o}(Fn);var Mn=v(Fn.exports);function On(t,e){if(!((r=e.remuxResult).audio||r.video||r.text||r.id3||r.initSegment))return!1;var r,i=[],n=e.remuxResult,a=n.audio,s=n.video;return a&&Nn(i,a),s&&Nn(i,s),t.postMessage({event:"transmuxComplete",data:e},i),!0}function Nn(t,e){e.data1&&t.push(e.data1.buffer),e.data2&&t.push(e.data2.buffer)}function Un(t,e,r){e.reduce((function(e,r){return On(t,r)||e}),!1)||t.postMessage({event:"transmuxComplete",data:e[0]}),t.postMessage({event:"flush",data:r})}void 0!==e&&e&&function(t){var e=new Mn,r=function(e,r){t.postMessage({event:e,data:r})};e.on(S.FRAG_DECRYPTED,r),e.on(S.ERROR,r);var i=function(){var t=function(t){var e=function(e){r("workerLog",{logType:t,message:e})};w[t]=e};for(var e in w)t(e)};t.addEventListener("message",(function(n){var a=n.data;switch(a.cmd){case"init":var s=JSON.parse(a.config);t.transmuxer=new wn(e,a.typeSupported,s,a.vendor,a.id),I(s.debug,a.id),i(),r("init",null);break;case"configure":t.transmuxer.configure(a.config);break;case"demux":var o=t.transmuxer.push(a.data,a.decryptdata,a.chunkMeta,a.state);_n(o)?(t.transmuxer.async=!0,o.then((function(e){On(t,e)})).catch((function(t){r(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,chunkMeta:a.chunkMeta,fatal:!1,error:t,err:t,reason:"transmuxer-worker push error"})}))):(t.transmuxer.async=!1,On(t,o));break;case"flush":var l=a.chunkMeta,u=t.transmuxer.flush(l);_n(u)||t.transmuxer.async?(_n(u)||(u=Promise.resolve(u)),u.then((function(e){Un(t,e,l)})).catch((function(t){r(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,chunkMeta:a.chunkMeta,fatal:!1,error:t,err:t,reason:"transmuxer-worker flush error"})}))):Un(t,u,l)}}))}(self);var Bn=function(){function e(e,r,i,n){var a=this;this.error=null,this.hls=void 0,this.id=void 0,this.observer=void 0,this.frag=null,this.part=null,this.useWorker=void 0,this.workerContext=null,this.onwmsg=void 0,this.transmuxer=null,this.onTransmuxComplete=void 0,this.onFlush=void 0;var s=e.config;this.hls=e,this.id=r,this.useWorker=!!s.enableWorker,this.onTransmuxComplete=i,this.onFlush=n;var o=function(t,e){(e=e||{}).frag=a.frag,e.id=a.id,t===S.ERROR&&(a.error=e.error),a.hls.trigger(t,e)};this.observer=new Mn,this.observer.on(S.FRAG_DECRYPTED,o),this.observer.on(S.ERROR,o);var l,u,h,d,c=te(s.preferManagedMediaSource)||{isTypeSupported:function(){return!1}},f={mpeg:c.isTypeSupported("audio/mpeg"),mp3:c.isTypeSupported('audio/mp4; codecs="mp3"'),ac3:c.isTypeSupported('audio/mp4; codecs="ac-3"')},g=navigator.vendor;if(!this.useWorker||"undefined"==typeof Worker||(s.workerPath,0))this.transmuxer=new wn(this.observer,f,s,g,r);else try{s.workerPath?(w.log("loading Web Worker "+s.workerPath+' for "'+r+'"'),this.workerContext=(h=s.workerPath,d=new self.URL(h,self.location.href).href,{worker:new self.Worker(d),scriptURL:d})):(w.log('injecting Web Worker for "'+r+'"'),this.workerContext=(l=new self.Blob(["var exports={};var module={exports:exports};function define(f){f()};define.amd=true;("+t.toString()+")(true);"],{type:"text/javascript"}),u=self.URL.createObjectURL(l),{worker:new self.Worker(u),objectURL:u})),this.onwmsg=function(t){return a.onWorkerMessage(t)};var v=this.workerContext.worker;v.addEventListener("message",this.onwmsg),v.onerror=function(t){var e=new Error(t.message+" ("+t.filename+":"+t.lineno+")");s.enableWorker=!1,w.warn('Error in "'+r+'" Web Worker, fallback to inline'),a.hls.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.INTERNAL_EXCEPTION,fatal:!1,event:"demuxerWorker",error:e})},v.postMessage({cmd:"init",typeSupported:f,vendor:g,id:r,config:JSON.stringify(s)})}catch(t){w.warn('Error setting up "'+r+'" Web Worker, fallback to inline',t),this.resetWorker(),this.error=null,this.transmuxer=new wn(this.observer,f,s,g,r)}}var r=e.prototype;return r.resetWorker=function(){if(this.workerContext){var t=this.workerContext,e=t.worker,r=t.objectURL;r&&self.URL.revokeObjectURL(r),e.removeEventListener("message",this.onwmsg),e.onerror=null,e.terminate(),this.workerContext=null}},r.destroy=function(){if(this.workerContext)this.resetWorker(),this.onwmsg=void 0;else{var t=this.transmuxer;t&&(t.destroy(),this.transmuxer=null)}var e=this.observer;e&&e.removeAllListeners(),this.frag=null,this.observer=null,this.hls=null},r.push=function(t,e,r,i,n,a,s,o,l,u){var h,d,c=this;l.transmuxing.start=self.performance.now();var f=this.transmuxer,g=a?a.start:n.start,v=n.decryptdata,m=this.frag,p=!(m&&n.cc===m.cc),y=!(m&&l.level===m.level),E=m?l.sn-m.sn:-1,T=this.part?l.part-this.part.index:-1,S=0===E&&l.id>1&&l.id===(null==m?void 0:m.stats.chunkCount),L=!y&&(1===E||0===E&&(1===T||S&&T<=0)),A=self.performance.now();(y||E||0===n.stats.parsing.start)&&(n.stats.parsing.start=A),!a||!T&&L||(a.stats.parsing.start=A);var R=!(m&&(null==(h=n.initSegment)?void 0:h.url)===(null==(d=m.initSegment)?void 0:d.url)),k=new Pn(p,L,o,y,g,R);if(!L||p||R){w.log("[transmuxer-interface, "+n.type+"]: Starting new transmux session for sn: "+l.sn+" p: "+l.part+" level: "+l.level+" id: "+l.id+"\n discontinuity: "+p+"\n trackSwitch: "+y+"\n contiguous: "+L+"\n accurateTimeOffset: "+o+"\n timeOffset: "+g+"\n initSegmentChange: "+R);var b=new xn(r,i,e,s,u);this.configureTransmuxer(b)}if(this.frag=n,this.part=a,this.workerContext)this.workerContext.worker.postMessage({cmd:"demux",data:t,decryptdata:v,chunkMeta:l,state:k},t instanceof ArrayBuffer?[t]:[]);else if(f){var D=f.push(t,v,l,k);_n(D)?(f.async=!0,D.then((function(t){c.handleTransmuxComplete(t)})).catch((function(t){c.transmuxerError(t,l,"transmuxer-interface push error")}))):(f.async=!1,this.handleTransmuxComplete(D))}},r.flush=function(t){var e=this;t.transmuxing.start=self.performance.now();var r=this.transmuxer;if(this.workerContext)this.workerContext.worker.postMessage({cmd:"flush",chunkMeta:t});else if(r){var i=r.flush(t);_n(i)||r.async?(_n(i)||(i=Promise.resolve(i)),i.then((function(r){e.handleFlushResult(r,t)})).catch((function(r){e.transmuxerError(r,t,"transmuxer-interface flush error")}))):this.handleFlushResult(i,t)}},r.transmuxerError=function(t,e,r){this.hls&&(this.error=t,this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,chunkMeta:e,fatal:!1,error:t,err:t,reason:r}))},r.handleFlushResult=function(t,e){var r=this;t.forEach((function(t){r.handleTransmuxComplete(t)})),this.onFlush(e)},r.onWorkerMessage=function(t){var e=t.data,r=this.hls;switch(e.event){case"init":var i,n=null==(i=this.workerContext)?void 0:i.objectURL;n&&self.URL.revokeObjectURL(n);break;case"transmuxComplete":this.handleTransmuxComplete(e.data);break;case"flush":this.onFlush(e.data);break;case"workerLog":w[e.data.logType]&&w[e.data.logType](e.data.message);break;default:e.data=e.data||{},e.data.frag=this.frag,e.data.id=this.id,r.trigger(e.event,e.data)}},r.configureTransmuxer=function(t){var e=this.transmuxer;this.workerContext?this.workerContext.worker.postMessage({cmd:"configure",config:t}):e&&e.configure(t)},r.handleTransmuxComplete=function(t){t.chunkMeta.transmuxing.end=self.performance.now(),this.onTransmuxComplete(t)},e}();function Gn(t,e){if(t.length!==e.length)return!1;for(var r=0;r<t.length;r++)if(!Kn(t[r].attrs,e[r].attrs))return!1;return!0}function Kn(t,e,r){var i=t["STABLE-RENDITION-ID"];return i&&!r?i===e["STABLE-RENDITION-ID"]:!(r||["LANGUAGE","NAME","CHARACTERISTICS","AUTOSELECT","DEFAULT","FORCED","ASSOC-LANGUAGE"]).some((function(r){return t[r]!==e[r]}))}function Hn(t,e){return e.label.toLowerCase()===t.name.toLowerCase()&&(!e.language||e.language.toLowerCase()===(t.lang||"").toLowerCase())}var Vn=function(t){function e(e,r,i){var n;return(n=t.call(this,e,r,i,"[audio-stream-controller]",we)||this).videoBuffer=null,n.videoTrackCC=-1,n.waitingVideoCC=-1,n.bufferedTrack=null,n.switchingTrack=null,n.trackId=-1,n.waitingData=null,n.mainDetails=null,n.flushing=!1,n.bufferFlushed=!1,n.cachedTrackLoadedData=null,n._registerListeners(),n}l(e,t);var r=e.prototype;return r.onHandlerDestroying=function(){this._unregisterListeners(),t.prototype.onHandlerDestroying.call(this),this.mainDetails=null,this.bufferedTrack=null,this.switchingTrack=null},r._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.AUDIO_TRACKS_UPDATED,this.onAudioTracksUpdated,this),t.on(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.on(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.on(S.ERROR,this.onError,this),t.on(S.BUFFER_RESET,this.onBufferReset,this),t.on(S.BUFFER_CREATED,this.onBufferCreated,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.on(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.AUDIO_TRACKS_UPDATED,this.onAudioTracksUpdated,this),t.off(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.off(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.off(S.ERROR,this.onError,this),t.off(S.BUFFER_RESET,this.onBufferReset,this),t.off(S.BUFFER_CREATED,this.onBufferCreated,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.off(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this)},r.onInitPtsFound=function(t,e){var r=e.frag,i=e.id,n=e.initPTS,a=e.timescale;if("main"===i){var s=r.cc;this.initPTS[r.cc]={baseTime:n,timescale:a},this.log("InitPTS for cc: "+s+" found from main: "+n),this.videoTrackCC=s,this.state===Li&&this.tick()}},r.startLoad=function(t){if(!this.levels)return this.startPosition=t,void(this.state=ci);var e=this.lastCurrentTime;this.stopLoad(),this.setInterval(100),e>0&&-1===t?(this.log("Override startPosition with lastCurrentTime @"+e.toFixed(3)),t=e,this.state=fi):(this.loadedmetadata=!1,this.state=pi),this.nextLoadPosition=this.startPosition=this.lastCurrentTime=t,this.tick()},r.doTick=function(){switch(this.state){case fi:this.doTickIdle();break;case pi:var e,r=this.levels,i=this.trackId,n=null==r||null==(e=r[i])?void 0:e.details;if(n){if(this.waitForCdnTuneIn(n))break;this.state=Li}break;case mi:var a,s=performance.now(),o=this.retryDate;if(!o||s>=o||null!=(a=this.media)&&a.seeking){var l=this.levels,u=this.trackId;this.log("RetryDate reached, switch back to IDLE state"),this.resetStartWhenNotLoaded((null==l?void 0:l[u])||null),this.state=fi}break;case Li:var h=this.waitingData;if(h){var d=h.frag,c=h.part,f=h.cache,g=h.complete;if(void 0!==this.initPTS[d.cc]){this.waitingData=null,this.waitingVideoCC=-1,this.state=vi;var v={frag:d,part:c,payload:f.flush(),networkDetails:null};this._handleFragmentLoadProgress(v),g&&t.prototype._handleFragmentLoadComplete.call(this,v)}else if(this.videoTrackCC!==this.waitingVideoCC)this.log("Waiting fragment cc ("+d.cc+") cancelled because video is at cc "+this.videoTrackCC),this.clearWaitingFragment();else{var m=this.getLoadPosition(),p=zr.bufferInfo(this.mediaBuffer,m,this.config.maxBufferHole);pr(p.end,this.config.maxFragLookUpTolerance,d)<0&&(this.log("Waiting fragment cc ("+d.cc+") @ "+d.start+" cancelled because another fragment at "+p.end+" is needed"),this.clearWaitingFragment())}}else this.state=fi}this.onTickEnd()},r.clearWaitingFragment=function(){var t=this.waitingData;t&&(this.fragmentTracker.removeFragment(t.frag),this.waitingData=null,this.waitingVideoCC=-1,this.state=fi)},r.resetLoadingState=function(){this.clearWaitingFragment(),t.prototype.resetLoadingState.call(this)},r.onTickEnd=function(){var t=this.media;null!=t&&t.readyState&&(this.lastCurrentTime=t.currentTime)},r.doTickIdle=function(){var t=this.hls,e=this.levels,r=this.media,i=this.trackId,n=t.config;if((r||!this.startFragRequested&&n.startFragPrefetch)&&null!=e&&e[i]){var a=e[i],s=a.details;if(!s||s.live&&this.levelLastLoaded!==a||this.waitForCdnTuneIn(s))this.state=pi;else{var o=this.mediaBuffer?this.mediaBuffer:this.media;this.bufferFlushed&&o&&(this.bufferFlushed=!1,this.afterBufferFlushed(o,O,we));var l=this.getFwdBufferInfo(o,we);if(null!==l){var u=this.bufferedTrack,h=this.switchingTrack;if(!h&&this._streamEnded(l,s))return t.trigger(S.BUFFER_EOS,{type:"audio"}),void(this.state=Ti);var d=this.getFwdBufferInfo(this.videoBuffer?this.videoBuffer:this.media,Ie),c=l.len,f=this.getMaxBufferLength(null==d?void 0:d.len),g=s.fragments,v=g[0].start,m=this.flushing?this.getLoadPosition():l.end;if(h&&r){var p=this.getLoadPosition();u&&!Kn(h.attrs,u.attrs)&&(m=p),s.PTSKnown&&p<v&&(l.end>v||l.nextStart)&&(this.log("Alt audio track ahead of main track, seek to start of alt audio track"),r.currentTime=v+.05)}if(!(c>=f&&!h&&m<g[g.length-1].start)){var y=this.getNextFragment(m,s),E=!1;if(y&&this.isLoopLoading(y,m)&&(E=!!y.gap,y=this.getNextFragmentLoopLoading(y,s,l,Ie,f)),y){var T=d&&y.start>d.end+s.targetduration;if(T||(null==d||!d.len)&&l.len){var L=this.getAppendedFrag(y.start,Ie);if(null===L)return;if(E||(E=!!L.gap||!!T&&0===d.len),T&&!E||E&&l.nextStart&&l.nextStart<L.end)return}this.loadFragment(y,a,m)}else this.bufferFlushed=!0}}}}},r.getMaxBufferLength=function(e){var r=t.prototype.getMaxBufferLength.call(this);return e?Math.min(Math.max(r,e),this.config.maxMaxBufferLength):r},r.onMediaDetaching=function(){this.videoBuffer=null,this.bufferFlushed=this.flushing=!1,t.prototype.onMediaDetaching.call(this)},r.onAudioTracksUpdated=function(t,e){var r=e.audioTracks;this.resetTransmuxer(),this.levels=r.map((function(t){return new tr(t)}))},r.onAudioTrackSwitching=function(t,e){var r=!!e.url;this.trackId=e.id;var i=this.fragCurrent;i&&(i.abortRequests(),this.removeUnbufferedFrags(i.start)),this.resetLoadingState(),r?this.setInterval(100):this.resetTransmuxer(),r?(this.switchingTrack=e,this.state=fi,this.flushAudioIfNeeded(e)):(this.switchingTrack=null,this.bufferedTrack=e,this.state=ci),this.tick()},r.onManifestLoading=function(){this.fragmentTracker.removeAllFragments(),this.startPosition=this.lastCurrentTime=0,this.bufferFlushed=this.flushing=!1,this.levels=this.mainDetails=this.waitingData=this.bufferedTrack=this.cachedTrackLoadedData=this.switchingTrack=null,this.startFragRequested=!1,this.trackId=this.videoTrackCC=this.waitingVideoCC=-1},r.onLevelLoaded=function(t,e){this.mainDetails=e.details,null!==this.cachedTrackLoadedData&&(this.hls.trigger(S.AUDIO_TRACK_LOADED,this.cachedTrackLoadedData),this.cachedTrackLoadedData=null)},r.onAudioTrackLoaded=function(t,e){var r;if(null!=this.mainDetails){var i=this.levels,n=e.details,a=e.id;if(i){this.log("Audio track "+a+" loaded ["+n.startSN+","+n.endSN+"]"+(n.lastPartSn?"[part-"+n.lastPartSn+"-"+n.lastPartIndex+"]":"")+",duration:"+n.totalduration);var s=i[a],o=0;if(n.live||null!=(r=s.details)&&r.live){this.checkLiveUpdate(n);var l,u=this.mainDetails;if(n.deltaUpdateFailed||!u)return;!s.details&&n.hasProgramDateTime&&u.hasProgramDateTime?(ei(n,u),o=n.fragments[0].start):o=this.alignPlaylists(n,s.details,null==(l=this.levelLastLoaded)?void 0:l.details)}s.details=n,this.levelLastLoaded=s,this.startFragRequested||!this.mainDetails&&n.live||this.setStartPosition(s.details,o),this.state!==pi||this.waitForCdnTuneIn(n)||(this.state=fi),this.tick()}else this.warn("Audio tracks were reset while loading level "+a)}else this.cachedTrackLoadedData=e},r._handleFragmentLoadProgress=function(t){var e,r=t.frag,i=t.part,n=t.payload,a=this.config,s=this.trackId,o=this.levels;if(o){var l=o[s];if(l){var u=l.details;if(!u)return this.warn("Audio track details undefined on fragment load progress"),void this.removeUnbufferedFrags(r.start);var h=a.defaultAudioCodec||l.audioCodec||"mp4a.40.2",d=this.transmuxer;d||(d=this.transmuxer=new Bn(this.hls,we,this._handleTransmuxComplete.bind(this),this._handleTransmuxerFlush.bind(this)));var c=this.initPTS[r.cc],f=null==(e=r.initSegment)?void 0:e.data;if(void 0!==c){var g=i?i.index:-1,v=-1!==g,m=new Qr(r.level,r.sn,r.stats.chunkCount,n.byteLength,g,v);d.push(n,f,h,"",r,i,u.totalduration,!1,m,c)}else this.log("Unknown video PTS for cc "+r.cc+", waiting for video PTS before demuxing audio frag "+r.sn+" of ["+u.startSN+" ,"+u.endSN+"],track "+s),(this.waitingData=this.waitingData||{frag:r,part:i,cache:new ki,complete:!1}).cache.push(new Uint8Array(n)),this.waitingVideoCC=this.videoTrackCC,this.state=Li}else this.warn("Audio track is undefined on fragment load progress")}else this.warn("Audio tracks were reset while fragment load was in progress. Fragment "+r.sn+" of level "+r.level+" will not be buffered")},r._handleFragmentLoadComplete=function(e){this.waitingData?this.waitingData.complete=!0:t.prototype._handleFragmentLoadComplete.call(this,e)},r.onBufferReset=function(){this.mediaBuffer=this.videoBuffer=null,this.loadedmetadata=!1},r.onBufferCreated=function(t,e){var r=e.tracks.audio;r&&(this.mediaBuffer=r.buffer||null),e.tracks.video&&(this.videoBuffer=e.tracks.video.buffer||null)},r.onFragBuffered=function(t,e){var r=e.frag,n=e.part;if(r.type===we)if(this.fragContextChanged(r))this.warn("Fragment "+r.sn+(n?" p: "+n.index:"")+" of level "+r.level+" finished buffering, but was aborted. state: "+this.state+", audioSwitch: "+(this.switchingTrack?this.switchingTrack.name:"false"));else{if("initSegment"!==r.sn){this.fragPrevious=r;var a=this.switchingTrack;a&&(this.bufferedTrack=a,this.switchingTrack=null,this.hls.trigger(S.AUDIO_TRACK_SWITCHED,i({},a)))}this.fragBufferedComplete(r,n)}else if(!this.loadedmetadata&&r.type===Ie){var s=this.videoBuffer||this.media;s&&zr.getBuffered(s).length&&(this.loadedmetadata=!0)}},r.onError=function(e,r){var i;if(r.fatal)this.state=Si;else switch(r.details){case A.FRAG_GAP:case A.FRAG_PARSING_ERROR:case A.FRAG_DECRYPT_ERROR:case A.FRAG_LOAD_ERROR:case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_ERROR:case A.KEY_LOAD_TIMEOUT:this.onFragmentOrKeyLoadError(we,r);break;case A.AUDIO_TRACK_LOAD_ERROR:case A.AUDIO_TRACK_LOAD_TIMEOUT:case A.LEVEL_PARSING_ERROR:r.levelRetry||this.state!==pi||(null==(i=r.context)?void 0:i.type)!==be||(this.state=fi);break;case A.BUFFER_APPEND_ERROR:case A.BUFFER_FULL_ERROR:if(!r.parent||"audio"!==r.parent)return;if(r.details===A.BUFFER_APPEND_ERROR)return void this.resetLoadingState();this.reduceLengthAndFlushBuffer(r)&&(this.bufferedTrack=null,t.prototype.flushMainBuffer.call(this,0,Number.POSITIVE_INFINITY,"audio"));break;case A.INTERNAL_EXCEPTION:this.recoverWorkerError(r)}},r.onBufferFlushing=function(t,e){e.type!==N&&(this.flushing=!0)},r.onBufferFlushed=function(t,e){var r=e.type;if(r!==N){this.flushing=!1,this.bufferFlushed=!0,this.state===Ti&&(this.state=fi);var i=this.mediaBuffer||this.media;i&&(this.afterBufferFlushed(i,r,we),this.tick())}},r._handleTransmuxComplete=function(t){var e,r="audio",i=this.hls,n=t.remuxResult,a=t.chunkMeta,s=this.getCurrentContext(a);if(s){var l=s.frag,u=s.part,h=s.level,d=h.details,c=n.audio,f=n.text,g=n.id3,v=n.initSegment;if(!this.fragContextChanged(l)&&d){if(this.state=yi,this.switchingTrack&&c&&this.completeAudioSwitch(this.switchingTrack),null!=v&&v.tracks){var m=l.initSegment||l;this._bufferInitSegment(h,v.tracks,m,a),i.trigger(S.FRAG_PARSING_INIT_SEGMENT,{frag:m,id:r,tracks:v.tracks})}if(c){var p=c.startPTS,y=c.endPTS,E=c.startDTS,T=c.endDTS;u&&(u.elementaryStreams[O]={startPTS:p,endPTS:y,startDTS:E,endDTS:T}),l.setElementaryStreamInfo(O,p,y,E,T),this.bufferFragmentData(c,l,u,a)}if(null!=g&&null!=(e=g.samples)&&e.length){var L=o({id:r,frag:l,details:d},g);i.trigger(S.FRAG_PARSING_METADATA,L)}if(f){var A=o({id:r,frag:l,details:d},f);i.trigger(S.FRAG_PARSING_USERDATA,A)}}else this.fragmentTracker.removeFragment(l)}else this.resetWhenMissingContext(a)},r._bufferInitSegment=function(t,e,r,i){if(this.state===yi){e.video&&delete e.video;var n=e.audio;if(n){n.id="audio";var a=t.audioCodec;this.log("Init audio buffer, container:"+n.container+", codecs[level/parsed]=["+a+"/"+n.codec+"]"),a&&1===a.split(",").length&&(n.levelCodec=a),this.hls.trigger(S.BUFFER_CODECS,e);var s=n.initSegment;if(null!=s&&s.byteLength){var o={type:"audio",frag:r,part:null,chunkMeta:i,parent:r.type,data:s};this.hls.trigger(S.BUFFER_APPENDING,o)}this.tickImmediate()}}},r.loadFragment=function(e,r,i){var n,a=this.fragmentTracker.getState(e);if(this.fragCurrent=e,this.switchingTrack||a===Kr||a===Vr)if("initSegment"===e.sn)this._loadInitSegment(e,r);else if(null!=(n=r.details)&&n.live&&!this.initPTS[e.cc]){this.log("Waiting for video PTS in continuity counter "+e.cc+" of live stream before loading audio fragment "+e.sn+" of level "+this.trackId),this.state=Li;var s=this.mainDetails;s&&s.fragments[0].start!==r.details.fragments[0].start&&ei(r.details,s)}else this.startFragRequested=!0,t.prototype.loadFragment.call(this,e,r,i);else this.clearTrackerIfNeeded(e)},r.flushAudioIfNeeded=function(e){var r=this.media,i=this.bufferedTrack,n=null==i?void 0:i.attrs,a=e.attrs;r&&n&&(n.CHANNELS!==a.CHANNELS||i.name!==e.name||i.lang!==e.lang)&&(this.log("Switching audio track : flushing all audio"),t.prototype.flushMainBuffer.call(this,0,Number.POSITIVE_INFINITY,"audio"),this.bufferedTrack=null)},r.completeAudioSwitch=function(t){var e=this.hls;this.flushAudioIfNeeded(t),this.bufferedTrack=t,this.switchingTrack=null,e.trigger(S.AUDIO_TRACK_SWITCHED,i({},t))},e}(Ri),Yn=function(t){function e(e){var r;return(r=t.call(this,e,"[audio-track-controller]")||this).tracks=[],r.groupIds=null,r.tracksInGroup=[],r.trackId=-1,r.currentTrack=null,r.selectDefaultTrack=!0,r.registerListeners(),r}l(e,t);var r=e.prototype;return r.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.on(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.on(S.ERROR,this.onError,this)},r.unregisterListeners=function(){var t=this.hls;t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVEL_LOADING,this.onLevelLoading,this),t.off(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.off(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.off(S.ERROR,this.onError,this)},r.destroy=function(){this.unregisterListeners(),this.tracks.length=0,this.tracksInGroup.length=0,this.currentTrack=null,t.prototype.destroy.call(this)},r.onManifestLoading=function(){this.tracks=[],this.tracksInGroup=[],this.groupIds=null,this.currentTrack=null,this.trackId=-1,this.selectDefaultTrack=!0},r.onManifestParsed=function(t,e){this.tracks=e.audioTracks||[]},r.onAudioTrackLoaded=function(t,e){var r=e.id,i=e.groupId,n=e.details,a=this.tracksInGroup[r];if(a&&a.groupId===i){var s=a.details;a.details=e.details,this.log("Audio track "+r+' "'+a.name+'" lang:'+a.lang+" group:"+i+" loaded ["+n.startSN+"-"+n.endSN+"]"),r===this.trackId&&this.playlistLoaded(r,e,s)}else this.warn("Audio track with id:"+r+" and group:"+i+" not found in active group "+(null==a?void 0:a.groupId))},r.onLevelLoading=function(t,e){this.switchLevel(e.level)},r.onLevelSwitching=function(t,e){this.switchLevel(e.level)},r.switchLevel=function(t){var e=this.hls.levels[t];if(e){var r=e.audioGroups||null,i=this.groupIds,n=this.currentTrack;if(!r||(null==i?void 0:i.length)!==(null==r?void 0:r.length)||null!=r&&r.some((function(t){return-1===(null==i?void 0:i.indexOf(t))}))){this.groupIds=r,this.trackId=-1,this.currentTrack=null;var a=this.tracks.filter((function(t){return!r||-1!==r.indexOf(t.groupId)}));if(a.length)this.selectDefaultTrack&&!a.some((function(t){return t.default}))&&(this.selectDefaultTrack=!1),a.forEach((function(t,e){t.id=e}));else if(!n&&!this.tracksInGroup.length)return;this.tracksInGroup=a;var s=this.hls.config.audioPreference;if(!n&&s){var o=Mr(s,a,Nr);if(o>-1)n=a[o];else{var l=Mr(s,this.tracks);n=this.tracks[l]}}var u=this.findTrackId(n);-1===u&&n&&(u=this.findTrackId(null));var h={audioTracks:a};this.log("Updating audio tracks, "+a.length+" track(s) found in group(s): "+(null==r?void 0:r.join(","))),this.hls.trigger(S.AUDIO_TRACKS_UPDATED,h);var d=this.trackId;if(-1!==u&&-1===d)this.setAudioTrack(u);else if(a.length&&-1===d){var c,f=new Error("No audio track selected for current audio group-ID(s): "+(null==(c=this.groupIds)?void 0:c.join(","))+" track count: "+a.length);this.warn(f.message),this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.AUDIO_TRACK_LOAD_ERROR,fatal:!0,error:f})}}else this.shouldReloadPlaylist(n)&&this.setAudioTrack(this.trackId)}},r.onError=function(t,e){!e.fatal&&e.context&&(e.context.type!==be||e.context.id!==this.trackId||this.groupIds&&-1===this.groupIds.indexOf(e.context.groupId)||(this.requestScheduled=-1,this.checkRetry(e)))},r.setAudioOption=function(t){var e=this.hls;if(e.config.audioPreference=t,t){var r=this.allAudioTracks;if(this.selectDefaultTrack=!1,r.length){var i=this.currentTrack;if(i&&Or(t,i,Nr))return i;var n=Mr(t,this.tracksInGroup,Nr);if(n>-1){var a=this.tracksInGroup[n];return this.setAudioTrack(n),a}if(i){var s=e.loadLevel;-1===s&&(s=e.firstAutoLevel);var o=function(t,e,r,i,n){var a=e[i],s=e.reduce((function(t,e,r){var i=e.uri;return(t[i]||(t[i]=[])).push(r),t}),{})[a.uri];s.length>1&&(i=Math.max.apply(Math,s));var o=a.videoRange,l=a.frameRate,u=a.codecSet.substring(0,4),h=Ur(e,i,(function(e){if(e.videoRange!==o||e.frameRate!==l||e.codecSet.substring(0,4)!==u)return!1;var i=e.audioGroups,a=r.filter((function(t){return!i||-1!==i.indexOf(t.groupId)}));return Mr(t,a,n)>-1}));return h>-1?h:Ur(e,i,(function(e){var i=e.audioGroups,a=r.filter((function(t){return!i||-1!==i.indexOf(t.groupId)}));return Mr(t,a,n)>-1}))}(t,e.levels,r,s,Nr);if(-1===o)return null;e.nextLoadLevel=o}if(t.channels||t.audioCodec){var l=Mr(t,r);if(l>-1)return r[l]}}}return null},r.setAudioTrack=function(t){var e=this.tracksInGroup;if(t<0||t>=e.length)this.warn("Invalid audio track id: "+t);else{this.clearTimer(),this.selectDefaultTrack=!1;var r=this.currentTrack,n=e[t],a=n.details&&!n.details.live;if(!(t===this.trackId&&n===r&&a||(this.log("Switching to audio-track "+t+' "'+n.name+'" lang:'+n.lang+" group:"+n.groupId+" channels:"+n.channels),this.trackId=t,this.currentTrack=n,this.hls.trigger(S.AUDIO_TRACK_SWITCHING,i({},n)),a))){var s=this.switchParams(n.url,null==r?void 0:r.details);this.loadPlaylist(s)}}},r.findTrackId=function(t){for(var e=this.tracksInGroup,r=0;r<e.length;r++){var i=e[r];if((!this.selectDefaultTrack||i.default)&&(!t||Or(t,i,Nr)))return r}if(t){for(var n=t.name,a=t.lang,s=t.assocLang,o=t.characteristics,l=t.audioCodec,u=t.channels,h=0;h<e.length;h++)if(Or({name:n,lang:a,assocLang:s,characteristics:o,audioCodec:l,channels:u},e[h],Nr))return h;for(var d=0;d<e.length;d++){var c=e[d];if(Kn(t.attrs,c.attrs,["LANGUAGE","ASSOC-LANGUAGE","CHARACTERISTICS"]))return d}for(var f=0;f<e.length;f++){var g=e[f];if(Kn(t.attrs,g.attrs,["LANGUAGE"]))return f}}return-1},r.loadPlaylist=function(e){var r=this.currentTrack;if(this.shouldLoadPlaylist(r)&&r){t.prototype.loadPlaylist.call(this);var i=r.id,n=r.groupId,a=r.url;if(e)try{a=e.addDirectives(a)}catch(t){this.warn("Could not construct new URL with HLS Delivery Directives: "+t)}this.log("loading audio-track playlist "+i+' "'+r.name+'" lang:'+r.lang+" group:"+n),this.clearTimer(),this.hls.trigger(S.AUDIO_TRACK_LOADING,{url:a,id:i,groupId:n,deliveryDirectives:e||null})}},s(e,[{key:"allAudioTracks",get:function(){return this.tracks}},{key:"audioTracks",get:function(){return this.tracksInGroup}},{key:"audioTrack",get:function(){return this.trackId},set:function(t){this.selectDefaultTrack=!1,this.setAudioTrack(t)}}]),e}(Dr),Wn=function(t){function e(e,r,i){var n;return(n=t.call(this,e,r,i,"[subtitle-stream-controller]",Ce)||this).currentTrackId=-1,n.tracksBuffered=[],n.mainDetails=null,n._registerListeners(),n}l(e,t);var r=e.prototype;return r.onHandlerDestroying=function(){this._unregisterListeners(),t.prototype.onHandlerDestroying.call(this),this.mainDetails=null},r._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.ERROR,this.onError,this),t.on(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.on(S.SUBTITLE_TRACK_SWITCH,this.onSubtitleTrackSwitch,this),t.on(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.on(S.SUBTITLE_FRAG_PROCESSED,this.onSubtitleFragProcessed,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.ERROR,this.onError,this),t.off(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.off(S.SUBTITLE_TRACK_SWITCH,this.onSubtitleTrackSwitch,this),t.off(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.off(S.SUBTITLE_FRAG_PROCESSED,this.onSubtitleFragProcessed,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this)},r.startLoad=function(t){this.stopLoad(),this.state=fi,this.setInterval(500),this.nextLoadPosition=this.startPosition=this.lastCurrentTime=t,this.tick()},r.onManifestLoading=function(){this.mainDetails=null,this.fragmentTracker.removeAllFragments()},r.onMediaDetaching=function(){this.tracksBuffered=[],t.prototype.onMediaDetaching.call(this)},r.onLevelLoaded=function(t,e){this.mainDetails=e.details},r.onSubtitleFragProcessed=function(t,e){var r=e.frag,i=e.success;if(this.fragPrevious=r,this.state=fi,i){var n=this.tracksBuffered[this.currentTrackId];if(n){for(var a,s=r.start,o=0;o<n.length;o++)if(s>=n[o].start&&s<=n[o].end){a=n[o];break}var l=r.start+r.duration;a?a.end=l:(a={start:s,end:l},n.push(a)),this.fragmentTracker.fragBuffered(r),this.fragBufferedComplete(r,null)}}},r.onBufferFlushing=function(t,e){var r=e.startOffset,i=e.endOffset;if(0===r&&i!==Number.POSITIVE_INFINITY){var n=i-1;if(n<=0)return;e.endOffsetSubtitles=Math.max(0,n),this.tracksBuffered.forEach((function(t){for(var e=0;e<t.length;)if(t[e].end<=n)t.shift();else{if(!(t[e].start<n))break;t[e].start=n,e++}})),this.fragmentTracker.removeFragmentsInRange(r,n,Ce)}},r.onFragBuffered=function(t,e){var r;this.loadedmetadata||e.frag.type!==Ie||null!=(r=this.media)&&r.buffered.length&&(this.loadedmetadata=!0)},r.onError=function(t,e){var r=e.frag;(null==r?void 0:r.type)===Ce&&(this.fragCurrent&&this.fragCurrent.abortRequests(),this.state!==ci&&(this.state=fi))},r.onSubtitleTracksUpdated=function(t,e){var r=this,i=e.subtitleTracks;this.levels&&!Gn(this.levels,i)?(this.tracksBuffered=[],this.levels=i.map((function(t){var e=new tr(t);return r.tracksBuffered[e.id]=[],e})),this.fragmentTracker.removeFragmentsInRange(0,Number.POSITIVE_INFINITY,Ce),this.fragPrevious=null,this.mediaBuffer=null):this.levels=i.map((function(t){return new tr(t)}))},r.onSubtitleTrackSwitch=function(t,e){var r;if(this.currentTrackId=e.id,null!=(r=this.levels)&&r.length&&-1!==this.currentTrackId){var i=this.levels[this.currentTrackId];null!=i&&i.details?this.mediaBuffer=this.mediaBufferTimeRanges:this.mediaBuffer=null,i&&this.setInterval(500)}else this.clearInterval()},r.onSubtitleTrackLoaded=function(t,e){var r,i=this.currentTrackId,n=this.levels,a=e.details,s=e.id;if(n){var o=n[i];if(!(s>=n.length||s!==i)&&o){this.log("Subtitle track "+s+" loaded ["+a.startSN+","+a.endSN+"]"+(a.lastPartSn?"[part-"+a.lastPartSn+"-"+a.lastPartIndex+"]":"")+",duration:"+a.totalduration),this.mediaBuffer=this.mediaBufferTimeRanges;var l=0;if(a.live||null!=(r=o.details)&&r.live){var u=this.mainDetails;if(a.deltaUpdateFailed||!u)return;var h,d=u.fragments[0];o.details?0===(l=this.alignPlaylists(a,o.details,null==(h=this.levelLastLoaded)?void 0:h.details))&&d&&sr(a,l=d.start):a.hasProgramDateTime&&u.hasProgramDateTime?(ei(a,u),l=a.fragments[0].start):d&&sr(a,l=d.start)}o.details=a,this.levelLastLoaded=o,this.startFragRequested||!this.mainDetails&&a.live||this.setStartPosition(o.details,l),this.tick(),a.live&&!this.fragCurrent&&this.media&&this.state===fi&&(mr(null,a.fragments,this.media.currentTime,0)||(this.warn("Subtitle playlist not aligned with playback"),o.details=void 0))}}else this.warn("Subtitle tracks were reset while loading level "+s)},r._handleFragmentLoadComplete=function(t){var e=this,r=t.frag,i=t.payload,n=r.decryptdata,a=this.hls;if(!this.fragContextChanged(r)&&i&&i.byteLength>0&&null!=n&&n.key&&n.iv&&"AES-128"===n.method){var s=performance.now();this.decrypter.decrypt(new Uint8Array(i),n.key.buffer,n.iv.buffer).catch((function(t){throw a.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_DECRYPT_ERROR,fatal:!1,error:t,reason:t.message,frag:r}),t})).then((function(t){var e=performance.now();a.trigger(S.FRAG_DECRYPTED,{frag:r,payload:t,stats:{tstart:s,tdecrypt:e}})})).catch((function(t){e.warn(t.name+": "+t.message),e.state=fi}))}},r.doTick=function(){if(this.media){if(this.state===fi){var t=this.currentTrackId,e=this.levels,r=null==e?void 0:e[t];if(!r||!e.length||!r.details)return;var i=this.config,n=this.getLoadPosition(),a=zr.bufferedInfo(this.tracksBuffered[this.currentTrackId]||[],n,i.maxBufferHole),s=a.end,o=a.len,l=this.getFwdBufferInfo(this.media,Ie),u=r.details;if(o>this.getMaxBufferLength(null==l?void 0:l.len)+u.levelTargetDuration)return;var h=u.fragments,d=h.length,c=u.edge,f=null,g=this.fragPrevious;if(s<c){var v=i.maxFragLookUpTolerance,m=s>c-v?0:v;!(f=mr(g,h,Math.max(h[0].start,s),m))&&g&&g.start<h[0].start&&(f=h[0])}else f=h[d-1];if(!f)return;if("initSegment"!==(f=this.mapToInitFragWhenRequired(f)).sn){var p=h[f.sn-u.startSN-1];p&&p.cc===f.cc&&this.fragmentTracker.getState(p)===Kr&&(f=p)}this.fragmentTracker.getState(f)===Kr&&this.loadFragment(f,r,s)}}else this.state=fi},r.getMaxBufferLength=function(e){var r=t.prototype.getMaxBufferLength.call(this);return e?Math.max(r,e):r},r.loadFragment=function(e,r,i){this.fragCurrent=e,"initSegment"===e.sn?this._loadInitSegment(e,r):(this.startFragRequested=!0,t.prototype.loadFragment.call(this,e,r,i))},s(e,[{key:"mediaBufferTimeRanges",get:function(){return new jn(this.tracksBuffered[this.currentTrackId]||[])}}]),e}(Ri),jn=function(t){this.buffered=void 0;var e=function(e,r,i){if((r>>>=0)>i-1)throw new DOMException("Failed to execute '"+e+"' on 'TimeRanges': The index provided ("+r+") is greater than the maximum bound ("+i+")");return t[r][e]};this.buffered={get length(){return t.length},end:function(r){return e("end",r,t.length)},start:function(r){return e("start",r,t.length)}}},qn=function(t){function e(e){var r;return(r=t.call(this,e,"[subtitle-track-controller]")||this).media=null,r.tracks=[],r.groupIds=null,r.tracksInGroup=[],r.trackId=-1,r.currentTrack=null,r.selectDefaultTrack=!0,r.queuedDefaultTrack=-1,r.asyncPollTrackChange=function(){return r.pollTrackChange(0)},r.useTextTrackPolling=!1,r.subtitlePollingInterval=-1,r._subtitleDisplay=!0,r.onTextTracksChanged=function(){if(r.useTextTrackPolling||self.clearInterval(r.subtitlePollingInterval),r.media&&r.hls.config.renderTextTracksNatively){for(var t=null,e=Ue(r.media.textTracks),i=0;i<e.length;i++)if("hidden"===e[i].mode)t=e[i];else if("showing"===e[i].mode){t=e[i];break}var n=r.findTrackForTextTrack(t);r.subtitleTrack!==n&&r.setSubtitleTrack(n)}},r.registerListeners(),r}l(e,t);var r=e.prototype;return r.destroy=function(){this.unregisterListeners(),this.tracks.length=0,this.tracksInGroup.length=0,this.currentTrack=null,this.onTextTracksChanged=this.asyncPollTrackChange=null,t.prototype.destroy.call(this)},r.registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.on(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.on(S.ERROR,this.onError,this)},r.unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVEL_LOADING,this.onLevelLoading,this),t.off(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.off(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.off(S.ERROR,this.onError,this)},r.onMediaAttached=function(t,e){this.media=e.media,this.media&&(this.queuedDefaultTrack>-1&&(this.subtitleTrack=this.queuedDefaultTrack,this.queuedDefaultTrack=-1),this.useTextTrackPolling=!(this.media.textTracks&&"onchange"in this.media.textTracks),this.useTextTrackPolling?this.pollTrackChange(500):this.media.textTracks.addEventListener("change",this.asyncPollTrackChange))},r.pollTrackChange=function(t){self.clearInterval(this.subtitlePollingInterval),this.subtitlePollingInterval=self.setInterval(this.onTextTracksChanged,t)},r.onMediaDetaching=function(){this.media&&(self.clearInterval(this.subtitlePollingInterval),this.useTextTrackPolling||this.media.textTracks.removeEventListener("change",this.asyncPollTrackChange),this.trackId>-1&&(this.queuedDefaultTrack=this.trackId),Ue(this.media.textTracks).forEach((function(t){Oe(t)})),this.subtitleTrack=-1,this.media=null)},r.onManifestLoading=function(){this.tracks=[],this.groupIds=null,this.tracksInGroup=[],this.trackId=-1,this.currentTrack=null,this.selectDefaultTrack=!0},r.onManifestParsed=function(t,e){this.tracks=e.subtitleTracks},r.onSubtitleTrackLoaded=function(t,e){var r=e.id,i=e.groupId,n=e.details,a=this.tracksInGroup[r];if(a&&a.groupId===i){var s=a.details;a.details=e.details,this.log("Subtitle track "+r+' "'+a.name+'" lang:'+a.lang+" group:"+i+" loaded ["+n.startSN+"-"+n.endSN+"]"),r===this.trackId&&this.playlistLoaded(r,e,s)}else this.warn("Subtitle track with id:"+r+" and group:"+i+" not found in active group "+(null==a?void 0:a.groupId))},r.onLevelLoading=function(t,e){this.switchLevel(e.level)},r.onLevelSwitching=function(t,e){this.switchLevel(e.level)},r.switchLevel=function(t){var e=this.hls.levels[t];if(e){var r=e.subtitleGroups||null,i=this.groupIds,n=this.currentTrack;if(!r||(null==i?void 0:i.length)!==(null==r?void 0:r.length)||null!=r&&r.some((function(t){return-1===(null==i?void 0:i.indexOf(t))}))){this.groupIds=r,this.trackId=-1,this.currentTrack=null;var a=this.tracks.filter((function(t){return!r||-1!==r.indexOf(t.groupId)}));if(a.length)this.selectDefaultTrack&&!a.some((function(t){return t.default}))&&(this.selectDefaultTrack=!1),a.forEach((function(t,e){t.id=e}));else if(!n&&!this.tracksInGroup.length)return;this.tracksInGroup=a;var s=this.hls.config.subtitlePreference;if(!n&&s){this.selectDefaultTrack=!1;var o=Mr(s,a);if(o>-1)n=a[o];else{var l=Mr(s,this.tracks);n=this.tracks[l]}}var u=this.findTrackId(n);-1===u&&n&&(u=this.findTrackId(null));var h={subtitleTracks:a};this.log("Updating subtitle tracks, "+a.length+' track(s) found in "'+(null==r?void 0:r.join(","))+'" group-id'),this.hls.trigger(S.SUBTITLE_TRACKS_UPDATED,h),-1!==u&&-1===this.trackId&&this.setSubtitleTrack(u)}else this.shouldReloadPlaylist(n)&&this.setSubtitleTrack(this.trackId)}},r.findTrackId=function(t){for(var e=this.tracksInGroup,r=this.selectDefaultTrack,i=0;i<e.length;i++){var n=e[i];if((!r||n.default)&&(r||t)&&(!t||Or(n,t)))return i}if(t){for(var a=0;a<e.length;a++){var s=e[a];if(Kn(t.attrs,s.attrs,["LANGUAGE","ASSOC-LANGUAGE","CHARACTERISTICS"]))return a}for(var o=0;o<e.length;o++){var l=e[o];if(Kn(t.attrs,l.attrs,["LANGUAGE"]))return o}}return-1},r.findTrackForTextTrack=function(t){if(t)for(var e=this.tracksInGroup,r=0;r<e.length;r++)if(Hn(e[r],t))return r;return-1},r.onError=function(t,e){!e.fatal&&e.context&&(e.context.type!==De||e.context.id!==this.trackId||this.groupIds&&-1===this.groupIds.indexOf(e.context.groupId)||this.checkRetry(e))},r.setSubtitleOption=function(t){if(this.hls.config.subtitlePreference=t,t){var e=this.allSubtitleTracks;if(this.selectDefaultTrack=!1,e.length){var r=this.currentTrack;if(r&&Or(t,r))return r;var i=Mr(t,this.tracksInGroup);if(i>-1){var n=this.tracksInGroup[i];return this.setSubtitleTrack(i),n}if(r)return null;var a=Mr(t,e);if(a>-1)return e[a]}}return null},r.loadPlaylist=function(e){t.prototype.loadPlaylist.call(this);var r=this.currentTrack;if(this.shouldLoadPlaylist(r)&&r){var i=r.id,n=r.groupId,a=r.url;if(e)try{a=e.addDirectives(a)}catch(t){this.warn("Could not construct new URL with HLS Delivery Directives: "+t)}this.log("Loading subtitle playlist for id "+i),this.hls.trigger(S.SUBTITLE_TRACK_LOADING,{url:a,id:i,groupId:n,deliveryDirectives:e||null})}},r.toggleTrackModes=function(){var t=this.media;if(t){var e,r=Ue(t.textTracks),i=this.currentTrack;if(i&&((e=r.filter((function(t){return Hn(i,t)}))[0])||this.warn('Unable to find subtitle TextTrack with name "'+i.name+'" and language "'+i.lang+'"')),[].slice.call(r).forEach((function(t){"disabled"!==t.mode&&t!==e&&(t.mode="disabled")})),e){var n=this.subtitleDisplay?"showing":"hidden";e.mode!==n&&(e.mode=n)}}},r.setSubtitleTrack=function(t){var e=this.tracksInGroup;if(this.media)if(t<-1||t>=e.length||!y(t))this.warn("Invalid subtitle track id: "+t);else{this.clearTimer(),this.selectDefaultTrack=!1;var r=this.currentTrack,i=e[t]||null;if(this.trackId=t,this.currentTrack=i,this.toggleTrackModes(),i){var n=!!i.details&&!i.details.live;if(t!==this.trackId||i!==r||!n){this.log("Switching to subtitle-track "+t+(i?' "'+i.name+'" lang:'+i.lang+" group:"+i.groupId:""));var a=i.id,s=i.groupId,o=void 0===s?"":s,l=i.name,u=i.type,h=i.url;this.hls.trigger(S.SUBTITLE_TRACK_SWITCH,{id:a,groupId:o,name:l,type:u,url:h});var d=this.switchParams(i.url,null==r?void 0:r.details);this.loadPlaylist(d)}}else this.hls.trigger(S.SUBTITLE_TRACK_SWITCH,{id:t})}else this.queuedDefaultTrack=t},s(e,[{key:"subtitleDisplay",get:function(){return this._subtitleDisplay},set:function(t){this._subtitleDisplay=t,this.trackId>-1&&this.toggleTrackModes()}},{key:"allSubtitleTracks",get:function(){return this.tracks}},{key:"subtitleTracks",get:function(){return this.tracksInGroup}},{key:"subtitleTrack",get:function(){return this.trackId},set:function(t){this.selectDefaultTrack=!1,this.setSubtitleTrack(t)}}]),e}(Dr),Xn=function(){function t(t){this.buffers=void 0,this.queues={video:[],audio:[],audiovideo:[]},this.buffers=t}var e=t.prototype;return e.append=function(t,e,r){var i=this.queues[e];i.push(t),1!==i.length||r||this.executeNext(e)},e.insertAbort=function(t,e){this.queues[e].unshift(t),this.executeNext(e)},e.appendBlocker=function(t){var e,r=new Promise((function(t){e=t})),i={execute:e,onStart:function(){},onComplete:function(){},onError:function(){}};return this.append(i,t),r},e.executeNext=function(t){var e=this.queues[t];if(e.length){var r=e[0];try{r.execute()}catch(e){w.warn('[buffer-operation-queue]: Exception executing "'+t+'" SourceBuffer operation: '+e),r.onError(e);var i=this.buffers[t];null!=i&&i.updating||this.shiftAndExecuteNext(t)}}},e.shiftAndExecuteNext=function(t){this.queues[t].shift(),this.executeNext(t)},e.current=function(t){return this.queues[t][0]},t}(),zn=/(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/,Qn=function(){function t(t){var e=this;this.details=null,this._objectUrl=null,this.operationQueue=void 0,this.listeners=void 0,this.hls=void 0,this.bufferCodecEventsExpected=0,this._bufferCodecEventsTotal=0,this.media=null,this.mediaSource=null,this.lastMpegAudioChunk=null,this.appendSource=void 0,this.appendErrors={audio:0,video:0,audiovideo:0},this.tracks={},this.pendingTracks={},this.sourceBuffer=void 0,this.log=void 0,this.warn=void 0,this.error=void 0,this._onEndStreaming=function(t){e.hls&&e.hls.pauseBuffering()},this._onStartStreaming=function(t){e.hls&&e.hls.resumeBuffering()},this._onMediaSourceOpen=function(){var t=e.media,r=e.mediaSource;e.log("Media source opened"),t&&(t.removeEventListener("emptied",e._onMediaEmptied),e.updateMediaElementDuration(),e.hls.trigger(S.MEDIA_ATTACHED,{media:t,mediaSource:r})),r&&r.removeEventListener("sourceopen",e._onMediaSourceOpen),e.checkPendingTracks()},this._onMediaSourceClose=function(){e.log("Media source closed")},this._onMediaSourceEnded=function(){e.log("Media source ended")},this._onMediaEmptied=function(){var t=e.mediaSrc,r=e._objectUrl;t!==r&&w.error("Media element src was set while attaching MediaSource ("+r+" > "+t+")")},this.hls=t;var r="[buffer-controller]";this.appendSource=t.config.preferManagedMediaSource,this.log=w.log.bind(w,r),this.warn=w.warn.bind(w,r),this.error=w.error.bind(w,r),this._initSourceBuffer(),this.registerListeners()}var e=t.prototype;return e.hasSourceTypes=function(){return this.getSourceBufferTypes().length>0||Object.keys(this.pendingTracks).length>0},e.destroy=function(){this.unregisterListeners(),this.details=null,this.lastMpegAudioChunk=null,this.hls=null},e.registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.BUFFER_RESET,this.onBufferReset,this),t.on(S.BUFFER_APPENDING,this.onBufferAppending,this),t.on(S.BUFFER_CODECS,this.onBufferCodecs,this),t.on(S.BUFFER_EOS,this.onBufferEos,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.LEVEL_UPDATED,this.onLevelUpdated,this),t.on(S.FRAG_PARSED,this.onFragParsed,this),t.on(S.FRAG_CHANGED,this.onFragChanged,this)},e.unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.BUFFER_RESET,this.onBufferReset,this),t.off(S.BUFFER_APPENDING,this.onBufferAppending,this),t.off(S.BUFFER_CODECS,this.onBufferCodecs,this),t.off(S.BUFFER_EOS,this.onBufferEos,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.LEVEL_UPDATED,this.onLevelUpdated,this),t.off(S.FRAG_PARSED,this.onFragParsed,this),t.off(S.FRAG_CHANGED,this.onFragChanged,this)},e._initSourceBuffer=function(){this.sourceBuffer={},this.operationQueue=new Xn(this.sourceBuffer),this.listeners={audio:[],video:[],audiovideo:[]},this.appendErrors={audio:0,video:0,audiovideo:0},this.lastMpegAudioChunk=null},e.onManifestLoading=function(){this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=0,this.details=null},e.onManifestParsed=function(t,e){var r=2;(e.audio&&!e.video||!e.altAudio)&&(r=1),this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=r,this.log(this.bufferCodecEventsExpected+" bufferCodec event(s) expected")},e.onMediaAttaching=function(t,e){var r=this.media=e.media,i=te(this.appendSource);if(r&&i){var n,a=this.mediaSource=new i;this.log("created media source: "+(null==(n=a.constructor)?void 0:n.name)),a.addEventListener("sourceopen",this._onMediaSourceOpen),a.addEventListener("sourceended",this._onMediaSourceEnded),a.addEventListener("sourceclose",this._onMediaSourceClose),a.addEventListener("startstreaming",this._onStartStreaming),a.addEventListener("endstreaming",this._onEndStreaming);var s=this._objectUrl=self.URL.createObjectURL(a);if(this.appendSource)try{r.removeAttribute("src");var o=self.ManagedMediaSource;r.disableRemotePlayback=r.disableRemotePlayback||o&&a instanceof o,Jn(r),function(t,e){var r=self.document.createElement("source");r.type="video/mp4",r.src=e,t.appendChild(r)}(r,s),r.load()}catch(t){r.src=s}else r.src=s;r.addEventListener("emptied",this._onMediaEmptied)}},e.onMediaDetaching=function(){var t=this.media,e=this.mediaSource,r=this._objectUrl;if(e){if(this.log("media source detaching"),"open"===e.readyState)try{e.endOfStream()}catch(t){this.warn("onMediaDetaching: "+t.message+" while calling endOfStream")}this.onBufferReset(),e.removeEventListener("sourceopen",this._onMediaSourceOpen),e.removeEventListener("sourceended",this._onMediaSourceEnded),e.removeEventListener("sourceclose",this._onMediaSourceClose),e.removeEventListener("startstreaming",this._onStartStreaming),e.removeEventListener("endstreaming",this._onEndStreaming),t&&(t.removeEventListener("emptied",this._onMediaEmptied),r&&self.URL.revokeObjectURL(r),this.mediaSrc===r?(t.removeAttribute("src"),this.appendSource&&Jn(t),t.load()):this.warn("media|source.src was changed by a third party - skip cleanup")),this.mediaSource=null,this.media=null,this._objectUrl=null,this.bufferCodecEventsExpected=this._bufferCodecEventsTotal,this.pendingTracks={},this.tracks={}}this.hls.trigger(S.MEDIA_DETACHED,void 0)},e.onBufferReset=function(){var t=this;this.getSourceBufferTypes().forEach((function(e){t.resetBuffer(e)})),this._initSourceBuffer()},e.resetBuffer=function(t){var e=this.sourceBuffer[t];try{var r;e&&(this.removeBufferListeners(t),this.sourceBuffer[t]=void 0,null!=(r=this.mediaSource)&&r.sourceBuffers.length&&this.mediaSource.removeSourceBuffer(e))}catch(e){this.warn("onBufferReset "+t,e)}},e.onBufferCodecs=function(t,e){var r=this,i=this.getSourceBufferTypes().length,n=Object.keys(e);if(n.forEach((function(t){if(i){var n=r.tracks[t];if(n&&"function"==typeof n.buffer.changeType){var a,s=e[t],o=s.id,l=s.codec,u=s.levelCodec,h=s.container,d=s.metadata,c=he(n.codec,n.levelCodec),f=null==c?void 0:c.replace(zn,"$1"),g=he(l,u),v=null==(a=g)?void 0:a.replace(zn,"$1");if(g&&f!==v){"audio"===t.slice(0,5)&&(g=ue(g,r.hls.config.preferManagedMediaSource));var m=h+";codecs="+g;r.appendChangeType(t,m),r.log("switching codec "+c+" to "+g),r.tracks[t]={buffer:n.buffer,codec:l,container:h,levelCodec:u,metadata:d,id:o}}}}else r.pendingTracks[t]=e[t]})),!i){var a=Math.max(this.bufferCodecEventsExpected-1,0);this.bufferCodecEventsExpected!==a&&(this.log(a+" bufferCodec event(s) expected "+n.join(",")),this.bufferCodecEventsExpected=a),this.mediaSource&&"open"===this.mediaSource.readyState&&this.checkPendingTracks()}},e.appendChangeType=function(t,e){var r=this,i=this.operationQueue,n={execute:function(){var n=r.sourceBuffer[t];n&&(r.log("changing "+t+" sourceBuffer type to "+e),n.changeType(e)),i.shiftAndExecuteNext(t)},onStart:function(){},onComplete:function(){},onError:function(e){r.warn("Failed to change "+t+" SourceBuffer type",e)}};i.append(n,t,!!this.pendingTracks[t])},e.onBufferAppending=function(t,e){var r=this,i=this.hls,n=this.operationQueue,a=this.tracks,s=e.data,o=e.type,l=e.frag,u=e.part,h=e.chunkMeta,d=h.buffering[o],c=self.performance.now();d.start=c;var f=l.stats.buffering,g=u?u.stats.buffering:null;0===f.start&&(f.start=c),g&&0===g.start&&(g.start=c);var v=a.audio,m=!1;"audio"===o&&"audio/mpeg"===(null==v?void 0:v.container)&&(m=!this.lastMpegAudioChunk||1===h.id||this.lastMpegAudioChunk.sn!==h.sn,this.lastMpegAudioChunk=h);var p=l.start,y={execute:function(){if(d.executeStart=self.performance.now(),m){var t=r.sourceBuffer[o];if(t){var e=p-t.timestampOffset;Math.abs(e)>=.1&&(r.log("Updating audio SourceBuffer timestampOffset to "+p+" (delta: "+e+") sn: "+l.sn+")"),t.timestampOffset=p)}}r.appendExecutor(s,o)},onStart:function(){},onComplete:function(){var t=self.performance.now();d.executeEnd=d.end=t,0===f.first&&(f.first=t),g&&0===g.first&&(g.first=t);var e=r.sourceBuffer,i={};for(var n in e)i[n]=zr.getBuffered(e[n]);r.appendErrors[o]=0,"audio"===o||"video"===o?r.appendErrors.audiovideo=0:(r.appendErrors.audio=0,r.appendErrors.video=0),r.hls.trigger(S.BUFFER_APPENDED,{type:o,frag:l,part:u,chunkMeta:h,parent:l.type,timeRanges:i})},onError:function(t){var e={type:L.MEDIA_ERROR,parent:l.type,details:A.BUFFER_APPEND_ERROR,sourceBufferName:o,frag:l,part:u,chunkMeta:h,error:t,err:t,fatal:!1};if(t.code===DOMException.QUOTA_EXCEEDED_ERR)e.details=A.BUFFER_FULL_ERROR;else{var n=++r.appendErrors[o];e.details=A.BUFFER_APPEND_ERROR,r.warn("Failed "+n+"/"+i.config.appendErrorMaxRetry+' times to append segment in "'+o+'" sourceBuffer'),n>=i.config.appendErrorMaxRetry&&(e.fatal=!0)}i.trigger(S.ERROR,e)}};n.append(y,o,!!this.pendingTracks[o])},e.onBufferFlushing=function(t,e){var r=this,i=this.operationQueue,n=function(t){return{execute:r.removeExecutor.bind(r,t,e.startOffset,e.endOffset),onStart:function(){},onComplete:function(){r.hls.trigger(S.BUFFER_FLUSHED,{type:t})},onError:function(e){r.warn("Failed to remove from "+t+" SourceBuffer",e)}}};e.type?i.append(n(e.type),e.type):this.getSourceBufferTypes().forEach((function(t){i.append(n(t),t)}))},e.onFragParsed=function(t,e){var r=this,i=e.frag,n=e.part,a=[],s=n?n.elementaryStreams:i.elementaryStreams;s[U]?a.push("audiovideo"):(s[O]&&a.push("audio"),s[N]&&a.push("video")),0===a.length&&this.warn("Fragments must have at least one ElementaryStreamType set. type: "+i.type+" level: "+i.level+" sn: "+i.sn),this.blockBuffers((function(){var t=self.performance.now();i.stats.buffering.end=t,n&&(n.stats.buffering.end=t);var e=n?n.stats:i.stats;r.hls.trigger(S.FRAG_BUFFERED,{frag:i,part:n,stats:e,id:i.type})}),a)},e.onFragChanged=function(t,e){this.trimBuffers()},e.onBufferEos=function(t,e){var r=this;this.getSourceBufferTypes().reduce((function(t,i){var n=r.sourceBuffer[i];return!n||e.type&&e.type!==i||(n.ending=!0,n.ended||(n.ended=!0,r.log(i+" sourceBuffer now EOS"))),t&&!(n&&!n.ended)}),!0)&&(this.log("Queueing mediaSource.endOfStream()"),this.blockBuffers((function(){r.getSourceBufferTypes().forEach((function(t){var e=r.sourceBuffer[t];e&&(e.ending=!1)}));var t=r.mediaSource;t&&"open"===t.readyState?(r.log("Calling mediaSource.endOfStream()"),t.endOfStream()):t&&r.log("Could not call mediaSource.endOfStream(). mediaSource.readyState: "+t.readyState)})))},e.onLevelUpdated=function(t,e){var r=e.details;r.fragments.length&&(this.details=r,this.getSourceBufferTypes().length?this.blockBuffers(this.updateMediaElementDuration.bind(this)):this.updateMediaElementDuration())},e.trimBuffers=function(){var t=this.hls,e=this.details,r=this.media;if(r&&null!==e&&this.getSourceBufferTypes().length){var i=t.config,n=r.currentTime,a=e.levelTargetDuration,s=e.live&&null!==i.liveBackBufferLength?i.liveBackBufferLength:i.backBufferLength;if(y(s)&&s>0){var o=Math.max(s,a),l=Math.floor(n/a)*a-o;this.flushBackBuffer(n,a,l)}if(y(i.frontBufferFlushThreshold)&&i.frontBufferFlushThreshold>0){var u=Math.max(i.maxBufferLength,i.frontBufferFlushThreshold),h=Math.max(u,a),d=Math.floor(n/a)*a+h;this.flushFrontBuffer(n,a,d)}}},e.flushBackBuffer=function(t,e,r){var i=this,n=this.details,a=this.sourceBuffer;this.getSourceBufferTypes().forEach((function(s){var o=a[s];if(o){var l=zr.getBuffered(o);if(l.length>0&&r>l.start(0)){if(i.hls.trigger(S.BACK_BUFFER_REACHED,{bufferEnd:r}),null!=n&&n.live)i.hls.trigger(S.LIVE_BACK_BUFFER_REACHED,{bufferEnd:r});else if(o.ended&&l.end(l.length-1)-t<2*e)return void i.log("Cannot flush "+s+" back buffer while SourceBuffer is in ended state");i.hls.trigger(S.BUFFER_FLUSHING,{startOffset:0,endOffset:r,type:s})}}}))},e.flushFrontBuffer=function(t,e,r){var i=this,n=this.sourceBuffer;this.getSourceBufferTypes().forEach((function(a){var s=n[a];if(s){var o=zr.getBuffered(s),l=o.length;if(l<2)return;var u=o.start(l-1),h=o.end(l-1);if(r>u||t>=u&&t<=h)return;if(s.ended&&t-h<2*e)return void i.log("Cannot flush "+a+" front buffer while SourceBuffer is in ended state");i.hls.trigger(S.BUFFER_FLUSHING,{startOffset:u,endOffset:1/0,type:a})}}))},e.updateMediaElementDuration=function(){if(this.details&&this.media&&this.mediaSource&&"open"===this.mediaSource.readyState){var t=this.details,e=this.hls,r=this.media,i=this.mediaSource,n=t.fragments[0].start+t.totalduration,a=r.duration,s=y(i.duration)?i.duration:0;t.live&&e.config.liveDurationInfinity?(i.duration=1/0,this.updateSeekableRange(t)):(n>s&&n>a||!y(a))&&(this.log("Updating Media Source duration to "+n.toFixed(3)),i.duration=n)}},e.updateSeekableRange=function(t){var e=this.mediaSource,r=t.fragments;if(r.length&&t.live&&null!=e&&e.setLiveSeekableRange){var i=Math.max(0,r[0].start),n=Math.max(i,i+t.totalduration);this.log("Media Source duration is set to "+e.duration+". Setting seekable range to "+i+"-"+n+"."),e.setLiveSeekableRange(i,n)}},e.checkPendingTracks=function(){var t=this.bufferCodecEventsExpected,e=this.operationQueue,r=this.pendingTracks,i=Object.keys(r).length;if(i&&(!t||2===i||"audiovideo"in r)){this.createSourceBuffers(r),this.pendingTracks={};var n=this.getSourceBufferTypes();if(n.length)this.hls.trigger(S.BUFFER_CREATED,{tracks:this.tracks}),n.forEach((function(t){e.executeNext(t)}));else{var a=new Error("could not create source buffer for media codec(s)");this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_INCOMPATIBLE_CODECS_ERROR,fatal:!0,error:a,reason:a.message})}}},e.createSourceBuffers=function(t){var e=this,r=this.sourceBuffer,i=this.mediaSource;if(!i)throw Error("createSourceBuffers called when mediaSource was null");var n=function(n){if(!r[n]){var a=t[n];if(!a)throw Error("source buffer exists for track "+n+", however track does not");var s=a.levelCodec||a.codec;s&&"audio"===n.slice(0,5)&&(s=ue(s,e.hls.config.preferManagedMediaSource));var o=a.container+";codecs="+s;e.log("creating sourceBuffer("+o+")");try{var l=r[n]=i.addSourceBuffer(o),u=n;e.addBufferListener(u,"updatestart",e._onSBUpdateStart),e.addBufferListener(u,"updateend",e._onSBUpdateEnd),e.addBufferListener(u,"error",e._onSBUpdateError),e.addBufferListener(u,"bufferedchange",(function(t,r){var i=r.removedRanges;null!=i&&i.length&&e.hls.trigger(S.BUFFER_FLUSHED,{type:n})})),e.tracks[n]={buffer:l,codec:s,container:a.container,levelCodec:a.levelCodec,metadata:a.metadata,id:a.id}}catch(t){e.error("error while trying to add sourceBuffer: "+t.message),e.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_ADD_CODEC_ERROR,fatal:!1,error:t,sourceBufferName:n,mimeType:o})}}};for(var a in t)n(a)},e._onSBUpdateStart=function(t){this.operationQueue.current(t).onStart()},e._onSBUpdateEnd=function(t){var e;if("closed"!==(null==(e=this.mediaSource)?void 0:e.readyState)){var r=this.operationQueue;r.current(t).onComplete(),r.shiftAndExecuteNext(t)}else this.resetBuffer(t)},e._onSBUpdateError=function(t,e){var r,i=new Error(t+" SourceBuffer error. MediaSource readyState: "+(null==(r=this.mediaSource)?void 0:r.readyState));this.error(""+i,e),this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_APPENDING_ERROR,sourceBufferName:t,error:i,fatal:!1});var n=this.operationQueue.current(t);n&&n.onError(i)},e.removeExecutor=function(t,e,r){var i=this.media,n=this.mediaSource,a=this.operationQueue,s=this.sourceBuffer[t];if(!i||!n||!s)return this.warn("Attempting to remove from the "+t+" SourceBuffer, but it does not exist"),void a.shiftAndExecuteNext(t);var o=y(i.duration)?i.duration:1/0,l=y(n.duration)?n.duration:1/0,u=Math.max(0,e),h=Math.min(r,o,l);h>u&&(!s.ending||s.ended)?(s.ended=!1,this.log("Removing ["+u+","+h+"] from the "+t+" SourceBuffer"),s.remove(u,h)):a.shiftAndExecuteNext(t)},e.appendExecutor=function(t,e){var r=this.sourceBuffer[e];if(r)r.ended=!1,r.appendBuffer(t);else if(!this.pendingTracks[e])throw new Error("Attempting to append to the "+e+" SourceBuffer, but it does not exist")},e.blockBuffers=function(t,e){var r=this;if(void 0===e&&(e=this.getSourceBufferTypes()),!e.length)return this.log("Blocking operation requested, but no SourceBuffers exist"),void Promise.resolve().then(t);var i=this.operationQueue,n=e.map((function(t){return i.appendBlocker(t)}));Promise.all(n).then((function(){t(),e.forEach((function(t){var e=r.sourceBuffer[t];null!=e&&e.updating||i.shiftAndExecuteNext(t)}))}))},e.getSourceBufferTypes=function(){return Object.keys(this.sourceBuffer)},e.addBufferListener=function(t,e,r){var i=this.sourceBuffer[t];if(i){var n=r.bind(this,t);this.listeners[t].push({event:e,listener:n}),i.addEventListener(e,n)}},e.removeBufferListeners=function(t){var e=this.sourceBuffer[t];e&&this.listeners[t].forEach((function(t){e.removeEventListener(t.event,t.listener)}))},s(t,[{key:"mediaSrc",get:function(){var t,e=(null==(t=this.media)?void 0:t.firstChild)||this.media;return null==e?void 0:e.src}}]),t}();function Jn(t){var e=t.querySelectorAll("source");[].slice.call(e).forEach((function(e){t.removeChild(e)}))}var $n={42:225,92:233,94:237,95:243,96:250,123:231,124:247,125:209,126:241,127:9608,128:174,129:176,130:189,131:191,132:8482,133:162,134:163,135:9834,136:224,137:32,138:232,139:226,140:234,141:238,142:244,143:251,144:193,145:201,146:211,147:218,148:220,149:252,150:8216,151:161,152:42,153:8217,154:9473,155:169,156:8480,157:8226,158:8220,159:8221,160:192,161:194,162:199,163:200,164:202,165:203,166:235,167:206,168:207,169:239,170:212,171:217,172:249,173:219,174:171,175:187,176:195,177:227,178:205,179:204,180:236,181:210,182:242,183:213,184:245,185:123,186:125,187:92,188:94,189:95,190:124,191:8764,192:196,193:228,194:214,195:246,196:223,197:165,198:164,199:9475,200:197,201:229,202:216,203:248,204:9487,205:9491,206:9495,207:9499},Zn=function(t){var e=t;return $n.hasOwnProperty(t)&&(e=$n[t]),String.fromCharCode(e)},ta=15,ea=100,ra={17:1,18:3,21:5,22:7,23:9,16:11,19:12,20:14},ia={17:2,18:4,21:6,22:8,23:10,19:13,20:15},na={25:1,26:3,29:5,30:7,31:9,24:11,27:12,28:14},aa={25:2,26:4,29:6,30:8,31:10,27:13,28:15},sa=["white","green","blue","cyan","red","yellow","magenta","black","transparent"],oa=function(){function t(){this.time=null,this.verboseLevel=0}return t.prototype.log=function(t,e){if(this.verboseLevel>=t){var r="function"==typeof e?e():e;w.log(this.time+" ["+t+"] "+r)}},t}(),la=function(t){for(var e=[],r=0;r<t.length;r++)e.push(t[r].toString(16));return e},ua=function(){function t(){this.foreground="white",this.underline=!1,this.italics=!1,this.background="black",this.flash=!1}var e=t.prototype;return e.reset=function(){this.foreground="white",this.underline=!1,this.italics=!1,this.background="black",this.flash=!1},e.setStyles=function(t){for(var e=["foreground","underline","italics","background","flash"],r=0;r<e.length;r++){var i=e[r];t.hasOwnProperty(i)&&(this[i]=t[i])}},e.isDefault=function(){return"white"===this.foreground&&!this.underline&&!this.italics&&"black"===this.background&&!this.flash},e.equals=function(t){return this.foreground===t.foreground&&this.underline===t.underline&&this.italics===t.italics&&this.background===t.background&&this.flash===t.flash},e.copy=function(t){this.foreground=t.foreground,this.underline=t.underline,this.italics=t.italics,this.background=t.background,this.flash=t.flash},e.toString=function(){return"color="+this.foreground+", underline="+this.underline+", italics="+this.italics+", background="+this.background+", flash="+this.flash},t}(),ha=function(){function t(){this.uchar=" ",this.penState=new ua}var e=t.prototype;return e.reset=function(){this.uchar=" ",this.penState.reset()},e.setChar=function(t,e){this.uchar=t,this.penState.copy(e)},e.setPenState=function(t){this.penState.copy(t)},e.equals=function(t){return this.uchar===t.uchar&&this.penState.equals(t.penState)},e.copy=function(t){this.uchar=t.uchar,this.penState.copy(t.penState)},e.isEmpty=function(){return" "===this.uchar&&this.penState.isDefault()},t}(),da=function(){function t(t){this.chars=[],this.pos=0,this.currPenState=new ua,this.cueStartTime=null,this.logger=void 0;for(var e=0;e<ea;e++)this.chars.push(new ha);this.logger=t}var e=t.prototype;return e.equals=function(t){for(var e=0;e<ea;e++)if(!this.chars[e].equals(t.chars[e]))return!1;return!0},e.copy=function(t){for(var e=0;e<ea;e++)this.chars[e].copy(t.chars[e])},e.isEmpty=function(){for(var t=!0,e=0;e<ea;e++)if(!this.chars[e].isEmpty()){t=!1;break}return t},e.setCursor=function(t){this.pos!==t&&(this.pos=t),this.pos<0?(this.logger.log(3,"Negative cursor position "+this.pos),this.pos=0):this.pos>ea&&(this.logger.log(3,"Too large cursor position "+this.pos),this.pos=ea)},e.moveCursor=function(t){var e=this.pos+t;if(t>1)for(var r=this.pos+1;r<e+1;r++)this.chars[r].setPenState(this.currPenState);this.setCursor(e)},e.backSpace=function(){this.moveCursor(-1),this.chars[this.pos].setChar(" ",this.currPenState)},e.insertChar=function(t){var e=this;t>=144&&this.backSpace();var r=Zn(t);this.pos>=ea?this.logger.log(0,(function(){return"Cannot insert "+t.toString(16)+" ("+r+") at position "+e.pos+". Skipping it!"})):(this.chars[this.pos].setChar(r,this.currPenState),this.moveCursor(1))},e.clearFromPos=function(t){var e;for(e=t;e<ea;e++)this.chars[e].reset()},e.clear=function(){this.clearFromPos(0),this.pos=0,this.currPenState.reset()},e.clearToEndOfRow=function(){this.clearFromPos(this.pos)},e.getTextString=function(){for(var t=[],e=!0,r=0;r<ea;r++){var i=this.chars[r].uchar;" "!==i&&(e=!1),t.push(i)}return e?"":t.join("")},e.setPenStyles=function(t){this.currPenState.setStyles(t),this.chars[this.pos].setPenState(this.currPenState)},t}(),ca=function(){function t(t){this.rows=[],this.currRow=14,this.nrRollUpRows=null,this.lastOutputScreen=null,this.logger=void 0;for(var e=0;e<ta;e++)this.rows.push(new da(t));this.logger=t}var e=t.prototype;return e.reset=function(){for(var t=0;t<ta;t++)this.rows[t].clear();this.currRow=14},e.equals=function(t){for(var e=!0,r=0;r<ta;r++)if(!this.rows[r].equals(t.rows[r])){e=!1;break}return e},e.copy=function(t){for(var e=0;e<ta;e++)this.rows[e].copy(t.rows[e])},e.isEmpty=function(){for(var t=!0,e=0;e<ta;e++)if(!this.rows[e].isEmpty()){t=!1;break}return t},e.backSpace=function(){this.rows[this.currRow].backSpace()},e.clearToEndOfRow=function(){this.rows[this.currRow].clearToEndOfRow()},e.insertChar=function(t){this.rows[this.currRow].insertChar(t)},e.setPen=function(t){this.rows[this.currRow].setPenStyles(t)},e.moveCursor=function(t){this.rows[this.currRow].moveCursor(t)},e.setCursor=function(t){this.logger.log(2,"setCursor: "+t),this.rows[this.currRow].setCursor(t)},e.setPAC=function(t){this.logger.log(2,(function(){return"pacData = "+JSON.stringify(t)}));var e=t.row-1;if(this.nrRollUpRows&&e<this.nrRollUpRows-1&&(e=this.nrRollUpRows-1),this.nrRollUpRows&&this.currRow!==e){for(var r=0;r<ta;r++)this.rows[r].clear();var i=this.currRow+1-this.nrRollUpRows,n=this.lastOutputScreen;if(n){var a=n.rows[i].cueStartTime,s=this.logger.time;if(null!==a&&null!==s&&a<s)for(var o=0;o<this.nrRollUpRows;o++)this.rows[e-this.nrRollUpRows+o+1].copy(n.rows[i+o])}}this.currRow=e;var l=this.rows[this.currRow];if(null!==t.indent){var u=t.indent,h=Math.max(u-1,0);l.setCursor(t.indent),t.color=l.chars[h].penState.foreground}var d={foreground:t.color,underline:t.underline,italics:t.italics,background:"black",flash:!1};this.setPen(d)},e.setBkgData=function(t){this.logger.log(2,(function(){return"bkgData = "+JSON.stringify(t)})),this.backSpace(),this.setPen(t),this.insertChar(32)},e.setRollUpRows=function(t){this.nrRollUpRows=t},e.rollUp=function(){var t=this;if(null!==this.nrRollUpRows){this.logger.log(1,(function(){return t.getDisplayText()}));var e=this.currRow+1-this.nrRollUpRows,r=this.rows.splice(e,1)[0];r.clear(),this.rows.splice(this.currRow,0,r),this.logger.log(2,"Rolling up")}else this.logger.log(3,"roll_up but nrRollUpRows not set yet")},e.getDisplayText=function(t){t=t||!1;for(var e=[],r="",i=-1,n=0;n<ta;n++){var a=this.rows[n].getTextString();a&&(i=n+1,t?e.push("Row "+i+": '"+a+"'"):e.push(a.trim()))}return e.length>0&&(r=t?"["+e.join(" | ")+"]":e.join("\n")),r},e.getTextAndFormat=function(){return this.rows},t}(),fa=function(){function t(t,e,r){this.chNr=void 0,this.outputFilter=void 0,this.mode=void 0,this.verbose=void 0,this.displayedMemory=void 0,this.nonDisplayedMemory=void 0,this.lastOutputScreen=void 0,this.currRollUpRow=void 0,this.writeScreen=void 0,this.cueStartTime=void 0,this.logger=void 0,this.chNr=t,this.outputFilter=e,this.mode=null,this.verbose=0,this.displayedMemory=new ca(r),this.nonDisplayedMemory=new ca(r),this.lastOutputScreen=new ca(r),this.currRollUpRow=this.displayedMemory.rows[14],this.writeScreen=this.displayedMemory,this.mode=null,this.cueStartTime=null,this.logger=r}var e=t.prototype;return e.reset=function(){this.mode=null,this.displayedMemory.reset(),this.nonDisplayedMemory.reset(),this.lastOutputScreen.reset(),this.outputFilter.reset(),this.currRollUpRow=this.displayedMemory.rows[14],this.writeScreen=this.displayedMemory,this.mode=null,this.cueStartTime=null},e.getHandler=function(){return this.outputFilter},e.setHandler=function(t){this.outputFilter=t},e.setPAC=function(t){this.writeScreen.setPAC(t)},e.setBkgData=function(t){this.writeScreen.setBkgData(t)},e.setMode=function(t){t!==this.mode&&(this.mode=t,this.logger.log(2,(function(){return"MODE="+t})),"MODE_POP-ON"===this.mode?this.writeScreen=this.nonDisplayedMemory:(this.writeScreen=this.displayedMemory,this.writeScreen.reset()),"MODE_ROLL-UP"!==this.mode&&(this.displayedMemory.nrRollUpRows=null,this.nonDisplayedMemory.nrRollUpRows=null),this.mode=t)},e.insertChars=function(t){for(var e=this,r=0;r<t.length;r++)this.writeScreen.insertChar(t[r]);var i=this.writeScreen===this.displayedMemory?"DISP":"NON_DISP";this.logger.log(2,(function(){return i+": "+e.writeScreen.getDisplayText(!0)})),"MODE_PAINT-ON"!==this.mode&&"MODE_ROLL-UP"!==this.mode||(this.logger.log(1,(function(){return"DISPLAYED: "+e.displayedMemory.getDisplayText(!0)})),this.outputDataUpdate())},e.ccRCL=function(){this.logger.log(2,"RCL - Resume Caption Loading"),this.setMode("MODE_POP-ON")},e.ccBS=function(){this.logger.log(2,"BS - BackSpace"),"MODE_TEXT"!==this.mode&&(this.writeScreen.backSpace(),this.writeScreen===this.displayedMemory&&this.outputDataUpdate())},e.ccAOF=function(){},e.ccAON=function(){},e.ccDER=function(){this.logger.log(2,"DER- Delete to End of Row"),this.writeScreen.clearToEndOfRow(),this.outputDataUpdate()},e.ccRU=function(t){this.logger.log(2,"RU("+t+") - Roll Up"),this.writeScreen=this.displayedMemory,this.setMode("MODE_ROLL-UP"),this.writeScreen.setRollUpRows(t)},e.ccFON=function(){this.logger.log(2,"FON - Flash On"),this.writeScreen.setPen({flash:!0})},e.ccRDC=function(){this.logger.log(2,"RDC - Resume Direct Captioning"),this.setMode("MODE_PAINT-ON")},e.ccTR=function(){this.logger.log(2,"TR"),this.setMode("MODE_TEXT")},e.ccRTD=function(){this.logger.log(2,"RTD"),this.setMode("MODE_TEXT")},e.ccEDM=function(){this.logger.log(2,"EDM - Erase Displayed Memory"),this.displayedMemory.reset(),this.outputDataUpdate(!0)},e.ccCR=function(){this.logger.log(2,"CR - Carriage Return"),this.writeScreen.rollUp(),this.outputDataUpdate(!0)},e.ccENM=function(){this.logger.log(2,"ENM - Erase Non-displayed Memory"),this.nonDisplayedMemory.reset()},e.ccEOC=function(){var t=this;if(this.logger.log(2,"EOC - End Of Caption"),"MODE_POP-ON"===this.mode){var e=this.displayedMemory;this.displayedMemory=this.nonDisplayedMemory,this.nonDisplayedMemory=e,this.writeScreen=this.nonDisplayedMemory,this.logger.log(1,(function(){return"DISP: "+t.displayedMemory.getDisplayText()}))}this.outputDataUpdate(!0)},e.ccTO=function(t){this.logger.log(2,"TO("+t+") - Tab Offset"),this.writeScreen.moveCursor(t)},e.ccMIDROW=function(t){var e={flash:!1};if(e.underline=t%2==1,e.italics=t>=46,e.italics)e.foreground="white";else{var r=Math.floor(t/2)-16;e.foreground=["white","green","blue","cyan","red","yellow","magenta"][r]}this.logger.log(2,"MIDROW: "+JSON.stringify(e)),this.writeScreen.setPen(e)},e.outputDataUpdate=function(t){void 0===t&&(t=!1);var e=this.logger.time;null!==e&&this.outputFilter&&(null!==this.cueStartTime||this.displayedMemory.isEmpty()?this.displayedMemory.equals(this.lastOutputScreen)||(this.outputFilter.newCue(this.cueStartTime,e,this.lastOutputScreen),t&&this.outputFilter.dispatchCue&&this.outputFilter.dispatchCue(),this.cueStartTime=this.displayedMemory.isEmpty()?null:e):this.cueStartTime=e,this.lastOutputScreen.copy(this.displayedMemory))},e.cueSplitAtTime=function(t){this.outputFilter&&(this.displayedMemory.isEmpty()||(this.outputFilter.newCue&&this.outputFilter.newCue(this.cueStartTime,t,this.displayedMemory),this.cueStartTime=t))},t}(),ga=function(){function t(t,e,r){this.channels=void 0,this.currentChannel=0,this.cmdHistory={a:null,b:null},this.logger=void 0;var i=this.logger=new oa;this.channels=[null,new fa(t,e,i),new fa(t+1,r,i)]}var e=t.prototype;return e.getHandler=function(t){return this.channels[t].getHandler()},e.setHandler=function(t,e){this.channels[t].setHandler(e)},e.addData=function(t,e){var r,i,n,a=!1;this.logger.time=t;for(var s=0;s<e.length;s+=2)if(i=127&e[s],n=127&e[s+1],0!==i||0!==n){if(this.logger.log(3,"["+la([e[s],e[s+1]])+"] -> ("+la([i,n])+")"),(r=this.parseCmd(i,n))||(r=this.parseMidrow(i,n)),r||(r=this.parsePAC(i,n)),r||(r=this.parseBackgroundAttributes(i,n)),!r&&(a=this.parseChars(i,n))){var o=this.currentChannel;o&&o>0?this.channels[o].insertChars(a):this.logger.log(2,"No channel found yet. TEXT-MODE?")}r||a||this.logger.log(2,"Couldn't parse cleaned data "+la([i,n])+" orig: "+la([e[s],e[s+1]]))}},e.parseCmd=function(t,e){var r=this.cmdHistory;if(!((20===t||28===t||21===t||29===t)&&e>=32&&e<=47||(23===t||31===t)&&e>=33&&e<=35))return!1;if(ma(t,e,r))return va(null,null,r),this.logger.log(3,"Repeated command ("+la([t,e])+") is dropped"),!0;var i=20===t||21===t||23===t?1:2,n=this.channels[i];return 20===t||21===t||28===t||29===t?32===e?n.ccRCL():33===e?n.ccBS():34===e?n.ccAOF():35===e?n.ccAON():36===e?n.ccDER():37===e?n.ccRU(2):38===e?n.ccRU(3):39===e?n.ccRU(4):40===e?n.ccFON():41===e?n.ccRDC():42===e?n.ccTR():43===e?n.ccRTD():44===e?n.ccEDM():45===e?n.ccCR():46===e?n.ccENM():47===e&&n.ccEOC():n.ccTO(e-32),va(t,e,r),this.currentChannel=i,!0},e.parseMidrow=function(t,e){var r=0;if((17===t||25===t)&&e>=32&&e<=47){if((r=17===t?1:2)!==this.currentChannel)return this.logger.log(0,"Mismatch channel in midrow parsing"),!1;var i=this.channels[r];return!!i&&(i.ccMIDROW(e),this.logger.log(3,"MIDROW ("+la([t,e])+")"),!0)}return!1},e.parsePAC=function(t,e){var r,i=this.cmdHistory;if(!((t>=17&&t<=23||t>=25&&t<=31)&&e>=64&&e<=127||(16===t||24===t)&&e>=64&&e<=95))return!1;if(ma(t,e,i))return va(null,null,i),!0;var n=t<=23?1:2;r=e>=64&&e<=95?1===n?ra[t]:na[t]:1===n?ia[t]:aa[t];var a=this.channels[n];return!!a&&(a.setPAC(this.interpretPAC(r,e)),va(t,e,i),this.currentChannel=n,!0)},e.interpretPAC=function(t,e){var r,i={color:null,italics:!1,indent:null,underline:!1,row:t};return r=e>95?e-96:e-64,i.underline=1==(1&r),r<=13?i.color=["white","green","blue","cyan","red","yellow","magenta","white"][Math.floor(r/2)]:r<=15?(i.italics=!0,i.color="white"):i.indent=4*Math.floor((r-16)/2),i},e.parseChars=function(t,e){var r,i,n=null,a=null;if(t>=25?(r=2,a=t-8):(r=1,a=t),a>=17&&a<=19?(i=17===a?e+80:18===a?e+112:e+144,this.logger.log(2,"Special char '"+Zn(i)+"' in channel "+r),n=[i]):t>=32&&t<=127&&(n=0===e?[t]:[t,e]),n){var s=la(n);this.logger.log(3,"Char codes = "+s.join(",")),va(t,e,this.cmdHistory)}return n},e.parseBackgroundAttributes=function(t,e){var r;if(!((16===t||24===t)&&e>=32&&e<=47||(23===t||31===t)&&e>=45&&e<=47))return!1;var i={};16===t||24===t?(r=Math.floor((e-32)/2),i.background=sa[r],e%2==1&&(i.background=i.background+"_semi")):45===e?i.background="transparent":(i.foreground="black",47===e&&(i.underline=!0));var n=t<=23?1:2;return this.channels[n].setBkgData(i),va(t,e,this.cmdHistory),!0},e.reset=function(){for(var t=0;t<Object.keys(this.channels).length;t++){var e=this.channels[t];e&&e.reset()}this.cmdHistory={a:null,b:null}},e.cueSplitAtTime=function(t){for(var e=0;e<this.channels.length;e++){var r=this.channels[e];r&&r.cueSplitAtTime(t)}},t}();function va(t,e,r){r.a=t,r.b=e}function ma(t,e,r){return r.a===t&&r.b===e}var pa=function(){function t(t,e){this.timelineController=void 0,this.cueRanges=[],this.trackName=void 0,this.startTime=null,this.endTime=null,this.screen=null,this.timelineController=t,this.trackName=e}var e=t.prototype;return e.dispatchCue=function(){null!==this.startTime&&(this.timelineController.addCues(this.trackName,this.startTime,this.endTime,this.screen,this.cueRanges),this.startTime=null)},e.newCue=function(t,e,r){(null===this.startTime||this.startTime>t)&&(this.startTime=t),this.endTime=e,this.screen=r,this.timelineController.createCaptionsTrack(this.trackName)},e.reset=function(){this.cueRanges=[],this.startTime=null},t}(),ya=function(){if(null!=j&&j.VTTCue)return self.VTTCue;var t=["","lr","rl"],e=["start","middle","end","left","right"];function r(t,e){if("string"!=typeof e)return!1;if(!Array.isArray(t))return!1;var r=e.toLowerCase();return!!~t.indexOf(r)&&r}function i(t){return r(e,t)}function n(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i<e;i++)r[i-1]=arguments[i];for(var n=1;n<arguments.length;n++){var a=arguments[n];for(var s in a)t[s]=a[s]}return t}function a(e,a,s){var o=this,l={enumerable:!0};o.hasBeenReset=!1;var u="",h=!1,d=e,c=a,f=s,g=null,v="",m=!0,p="auto",y="start",E=50,T="middle",S=50,L="middle";Object.defineProperty(o,"id",n({},l,{get:function(){return u},set:function(t){u=""+t}})),Object.defineProperty(o,"pauseOnExit",n({},l,{get:function(){return h},set:function(t){h=!!t}})),Object.defineProperty(o,"startTime",n({},l,{get:function(){return d},set:function(t){if("number"!=typeof t)throw new TypeError("Start time must be set to a number.");d=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"endTime",n({},l,{get:function(){return c},set:function(t){if("number"!=typeof t)throw new TypeError("End time must be set to a number.");c=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"text",n({},l,{get:function(){return f},set:function(t){f=""+t,this.hasBeenReset=!0}})),Object.defineProperty(o,"region",n({},l,{get:function(){return g},set:function(t){g=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"vertical",n({},l,{get:function(){return v},set:function(e){var i=function(e){return r(t,e)}(e);if(!1===i)throw new SyntaxError("An invalid or illegal string was specified.");v=i,this.hasBeenReset=!0}})),Object.defineProperty(o,"snapToLines",n({},l,{get:function(){return m},set:function(t){m=!!t,this.hasBeenReset=!0}})),Object.defineProperty(o,"line",n({},l,{get:function(){return p},set:function(t){if("number"!=typeof t&&"auto"!==t)throw new SyntaxError("An invalid number or illegal string was specified.");p=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"lineAlign",n({},l,{get:function(){return y},set:function(t){var e=i(t);if(!e)throw new SyntaxError("An invalid or illegal string was specified.");y=e,this.hasBeenReset=!0}})),Object.defineProperty(o,"position",n({},l,{get:function(){return E},set:function(t){if(t<0||t>100)throw new Error("Position must be between 0 and 100.");E=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"positionAlign",n({},l,{get:function(){return T},set:function(t){var e=i(t);if(!e)throw new SyntaxError("An invalid or illegal string was specified.");T=e,this.hasBeenReset=!0}})),Object.defineProperty(o,"size",n({},l,{get:function(){return S},set:function(t){if(t<0||t>100)throw new Error("Size must be between 0 and 100.");S=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"align",n({},l,{get:function(){return L},set:function(t){var e=i(t);if(!e)throw new SyntaxError("An invalid or illegal string was specified.");L=e,this.hasBeenReset=!0}})),o.displayState=void 0}return a.prototype.getCueAsHTML=function(){return self.WebVTT.convertCueToDOMTree(self,this.text)},a}(),Ea=function(){function t(){}return t.prototype.decode=function(t,e){if(!t)return"";if("string"!=typeof t)throw new Error("Error - expected string data.");return decodeURIComponent(encodeURIComponent(t))},t}();function Ta(t){function e(t,e,r,i){return 3600*(0|t)+60*(0|e)+(0|r)+parseFloat(i||0)}var r=t.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/);return r?parseFloat(r[2])>59?e(r[2],r[3],0,r[4]):e(r[1],r[2],r[3],r[4]):null}var Sa=function(){function t(){this.values=Object.create(null)}var e=t.prototype;return e.set=function(t,e){this.get(t)||""===e||(this.values[t]=e)},e.get=function(t,e,r){return r?this.has(t)?this.values[t]:e[r]:this.has(t)?this.values[t]:e},e.has=function(t){return t in this.values},e.alt=function(t,e,r){for(var i=0;i<r.length;++i)if(e===r[i]){this.set(t,e);break}},e.integer=function(t,e){/^-?\d+$/.test(e)&&this.set(t,parseInt(e,10))},e.percent=function(t,e){if(/^([\d]{1,3})(\.[\d]*)?%$/.test(e)){var r=parseFloat(e);if(r>=0&&r<=100)return this.set(t,r),!0}return!1},t}();function La(t,e,r,i){var n=i?t.split(i):[t];for(var a in n)if("string"==typeof n[a]){var s=n[a].split(r);2===s.length&&e(s[0],s[1])}}var Aa=new ya(0,0,""),Ra="middle"===Aa.align?"middle":"center";function ka(t,e,r){var i=t;function n(){var e=Ta(t);if(null===e)throw new Error("Malformed timestamp: "+i);return t=t.replace(/^[^\sa-zA-Z-]+/,""),e}function a(){t=t.replace(/^\s+/,"")}if(a(),e.startTime=n(),a(),"--\x3e"!==t.slice(0,3))throw new Error("Malformed time stamp (time stamps must be separated by '--\x3e'): "+i);t=t.slice(3),a(),e.endTime=n(),a(),function(t,e){var i=new Sa;La(t,(function(t,e){var n;switch(t){case"region":for(var a=r.length-1;a>=0;a--)if(r[a].id===e){i.set(t,r[a].region);break}break;case"vertical":i.alt(t,e,["rl","lr"]);break;case"line":n=e.split(","),i.integer(t,n[0]),i.percent(t,n[0])&&i.set("snapToLines",!1),i.alt(t,n[0],["auto"]),2===n.length&&i.alt("lineAlign",n[1],["start",Ra,"end"]);break;case"position":n=e.split(","),i.percent(t,n[0]),2===n.length&&i.alt("positionAlign",n[1],["start",Ra,"end","line-left","line-right","auto"]);break;case"size":i.percent(t,e);break;case"align":i.alt(t,e,["start",Ra,"end","left","right"])}}),/:/,/\s/),e.region=i.get("region",null),e.vertical=i.get("vertical","");var n=i.get("line","auto");"auto"===n&&-1===Aa.line&&(n=-1),e.line=n,e.lineAlign=i.get("lineAlign","start"),e.snapToLines=i.get("snapToLines",!0),e.size=i.get("size",100),e.align=i.get("align",Ra);var a=i.get("position","auto");"auto"===a&&50===Aa.position&&(a="start"===e.align||"left"===e.align?0:"end"===e.align||"right"===e.align?100:50),e.position=a}(t,e)}function ba(t){return t.replace(/<br(?: \/)?>/gi,"\n")}var Da=function(){function t(){this.state="INITIAL",this.buffer="",this.decoder=new Ea,this.regionList=[],this.cue=null,this.oncue=void 0,this.onparsingerror=void 0,this.onflush=void 0}var e=t.prototype;return e.parse=function(t){var e=this;function r(){var t=e.buffer,r=0;for(t=ba(t);r<t.length&&"\r"!==t[r]&&"\n"!==t[r];)++r;var i=t.slice(0,r);return"\r"===t[r]&&++r,"\n"===t[r]&&++r,e.buffer=t.slice(r),i}t&&(e.buffer+=e.decoder.decode(t,{stream:!0}));try{var i="";if("INITIAL"===e.state){if(!/\r\n|\n/.test(e.buffer))return this;var n=(i=r()).match(/^()?WEBVTT([ \t].*)?$/);if(null==n||!n[0])throw new Error("Malformed WebVTT signature.");e.state="HEADER"}for(var a=!1;e.buffer;){if(!/\r\n|\n/.test(e.buffer))return this;switch(a?a=!1:i=r(),e.state){case"HEADER":/:/.test(i)?La(i,(function(t,e){}),/:/):i||(e.state="ID");continue;case"NOTE":i||(e.state="ID");continue;case"ID":if(/^NOTE($|[ \t])/.test(i)){e.state="NOTE";break}if(!i)continue;if(e.cue=new ya(0,0,""),e.state="CUE",-1===i.indexOf("--\x3e")){e.cue.id=i;continue}case"CUE":if(!e.cue){e.state="BADCUE";continue}try{ka(i,e.cue,e.regionList)}catch(t){e.cue=null,e.state="BADCUE";continue}e.state="CUETEXT";continue;case"CUETEXT":var s=-1!==i.indexOf("--\x3e");if(!i||s&&(a=!0)){e.oncue&&e.cue&&e.oncue(e.cue),e.cue=null,e.state="ID";continue}if(null===e.cue)continue;e.cue.text&&(e.cue.text+="\n"),e.cue.text+=i;continue;case"BADCUE":i||(e.state="ID")}}}catch(t){"CUETEXT"===e.state&&e.cue&&e.oncue&&e.oncue(e.cue),e.cue=null,e.state="INITIAL"===e.state?"BADWEBVTT":"BADCUE"}return this},e.flush=function(){var t=this;try{if((t.cue||"HEADER"===t.state)&&(t.buffer+="\n\n",t.parse()),"INITIAL"===t.state||"BADWEBVTT"===t.state)throw new Error("Malformed WebVTT signature.")}catch(e){t.onparsingerror&&t.onparsingerror(e)}return t.onflush&&t.onflush(),this},t}(),Ia=/\r\n|\n\r|\n|\r/g,wa=function(t,e,r){return void 0===r&&(r=0),t.slice(r,r+e.length)===e},Ca=function(t){for(var e=5381,r=t.length;r;)e=33*e^t.charCodeAt(--r);return(e>>>0).toString()};function _a(t,e,r){return Ca(t.toString())+Ca(e.toString())+Ca(r)}function xa(t,e,r,i,n,a,s){var o,l,u,h=new Da,d=Tt(new Uint8Array(t)).trim().replace(Ia,"\n").split("\n"),c=[],f=e?(o=e.baseTime,void 0===(l=e.timescale)&&(l=1),mn(o,vn,1/l)):0,g="00:00.000",v=0,m=0,p=!0;h.oncue=function(t){var a=r[i],s=r.ccOffset,o=(v-f)/9e4;if(null!=a&&a.new&&(void 0!==m?s=r.ccOffset=a.start:function(t,e,r){var i=t[e],n=t[i.prevCC];if(!n||!n.new&&i.new)return t.ccOffset=t.presentationOffset=i.start,void(i.new=!1);for(;null!=(a=n)&&a.new;){var a;t.ccOffset+=i.start-n.start,i.new=!1,n=t[(i=n).prevCC]}t.presentationOffset=r}(r,i,o)),o){if(!e)return void(u=new Error("Missing initPTS for VTT MPEGTS"));s=o-r.presentationOffset}var l=t.endTime-t.startTime,h=Sn(9e4*(t.startTime+s-m),9e4*n)/9e4;t.startTime=Math.max(h,0),t.endTime=Math.max(h+l,0);var d=t.text.trim();t.text=decodeURIComponent(encodeURIComponent(d)),t.id||(t.id=_a(t.startTime,t.endTime,d)),t.endTime>0&&c.push(t)},h.onparsingerror=function(t){u=t},h.onflush=function(){u?s(u):a(c)},d.forEach((function(t){if(p){if(wa(t,"X-TIMESTAMP-MAP=")){p=!1,t.slice(16).split(",").forEach((function(t){wa(t,"LOCAL:")?g=t.slice(6):wa(t,"MPEGTS:")&&(v=parseInt(t.slice(7)))}));try{m=function(t){var e=parseInt(t.slice(-3)),r=parseInt(t.slice(-6,-4)),i=parseInt(t.slice(-9,-7)),n=t.length>9?parseInt(t.substring(0,t.indexOf(":"))):0;if(!(y(e)&&y(r)&&y(i)&&y(n)))throw Error("Malformed X-TIMESTAMP-MAP: Local:"+t);return e+=1e3*r,(e+=6e4*i)+36e5*n}(g)/1e3}catch(t){u=t}return}""===t&&(p=!1)}h.parse(t+"\n")})),h.flush()}var Pa="stpp.ttml.im1t",Fa=/^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/,Ma=/^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/,Oa={left:"start",center:"center",right:"end",start:"start",end:"end"};function Na(t,e,r,i){var n=_t(new Uint8Array(t),["mdat"]);if(0!==n.length){var a,s,l,u,h=n.map((function(t){return Tt(t)})),d=(a=e.baseTime,s=1,void 0===(l=e.timescale)&&(l=1),void 0===u&&(u=!1),mn(a,s,1/l,u));try{h.forEach((function(t){return r(function(t,e){var r=(new DOMParser).parseFromString(t,"text/xml"),i=r.getElementsByTagName("tt")[0];if(!i)throw new Error("Invalid ttml");var n={frameRate:30,subFrameRate:1,frameRateMultiplier:0,tickRate:0},a=Object.keys(n).reduce((function(t,e){return t[e]=i.getAttribute("ttp:"+e)||n[e],t}),{}),s="preserve"!==i.getAttribute("xml:space"),l=Ba(Ua(i,"styling","style")),u=Ba(Ua(i,"layout","region")),h=Ua(i,"body","[begin]");return[].map.call(h,(function(t){var r=Ga(t,s);if(!r||!t.hasAttribute("begin"))return null;var i=Va(t.getAttribute("begin"),a),n=Va(t.getAttribute("dur"),a),h=Va(t.getAttribute("end"),a);if(null===i)throw Ha(t);if(null===h){if(null===n)throw Ha(t);h=i+n}var d=new ya(i-e,h-e,r);d.id=_a(d.startTime,d.endTime,d.text);var c=function(t,e,r){var i="http://www.w3.org/ns/ttml#styling",n=null,a=["displayAlign","textAlign","color","backgroundColor","fontSize","fontFamily"],s=null!=t&&t.hasAttribute("style")?t.getAttribute("style"):null;return s&&r.hasOwnProperty(s)&&(n=r[s]),a.reduce((function(r,a){var s=Ka(e,i,a)||Ka(t,i,a)||Ka(n,i,a);return s&&(r[a]=s),r}),{})}(u[t.getAttribute("region")],l[t.getAttribute("style")],l),f=c.textAlign;if(f){var g=Oa[f];g&&(d.lineAlign=g),d.align=f}return o(d,c),d})).filter((function(t){return null!==t}))}(t,d))}))}catch(t){i(t)}}else i(new Error("Could not parse IMSC1 mdat"))}function Ua(t,e,r){var i=t.getElementsByTagName(e)[0];return i?[].slice.call(i.querySelectorAll(r)):[]}function Ba(t){return t.reduce((function(t,e){var r=e.getAttribute("xml:id");return r&&(t[r]=e),t}),{})}function Ga(t,e){return[].slice.call(t.childNodes).reduce((function(t,r,i){var n;return"br"===r.nodeName&&i?t+"\n":null!=(n=r.childNodes)&&n.length?Ga(r,e):e?t+r.textContent.trim().replace(/\s+/g," "):t+r.textContent}),"")}function Ka(t,e,r){return t&&t.hasAttributeNS(e,r)?t.getAttributeNS(e,r):null}function Ha(t){return new Error("Could not parse ttml timestamp "+t)}function Va(t,e){if(!t)return null;var r=Ta(t);return null===r&&(Fa.test(t)?r=function(t,e){var r=Fa.exec(t),i=(0|r[4])+(0|r[5])/e.subFrameRate;return 3600*(0|r[1])+60*(0|r[2])+(0|r[3])+i/e.frameRate}(t,e):Ma.test(t)&&(r=function(t,e){var r=Ma.exec(t),i=Number(r[1]);switch(r[2]){case"h":return 3600*i;case"m":return 60*i;case"ms":return 1e3*i;case"f":return i/e.frameRate;case"t":return i/e.tickRate}return i}(t,e))),r}var Ya=function(){function t(t){this.hls=void 0,this.media=null,this.config=void 0,this.enabled=!0,this.Cues=void 0,this.textTracks=[],this.tracks=[],this.initPTS=[],this.unparsedVttFrags=[],this.captionsTracks={},this.nonNativeCaptionsTracks={},this.cea608Parser1=void 0,this.cea608Parser2=void 0,this.lastCc=-1,this.lastSn=-1,this.lastPartIndex=-1,this.prevCC=-1,this.vttCCs={ccOffset:0,presentationOffset:0,0:{start:0,prevCC:-1,new:!0}},this.captionsProperties=void 0,this.hls=t,this.config=t.config,this.Cues=t.config.cueHandler,this.captionsProperties={textTrack1:{label:this.config.captionsTextTrack1Label,languageCode:this.config.captionsTextTrack1LanguageCode},textTrack2:{label:this.config.captionsTextTrack2Label,languageCode:this.config.captionsTextTrack2LanguageCode},textTrack3:{label:this.config.captionsTextTrack3Label,languageCode:this.config.captionsTextTrack3LanguageCode},textTrack4:{label:this.config.captionsTextTrack4Label,languageCode:this.config.captionsTextTrack4LanguageCode}},t.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.on(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.on(S.FRAG_LOADING,this.onFragLoading,this),t.on(S.FRAG_LOADED,this.onFragLoaded,this),t.on(S.FRAG_PARSING_USERDATA,this.onFragParsingUserdata,this),t.on(S.FRAG_DECRYPTED,this.onFragDecrypted,this),t.on(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.on(S.SUBTITLE_TRACKS_CLEARED,this.onSubtitleTracksCleared,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this)}var e=t.prototype;return e.destroy=function(){var t=this.hls;t.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.off(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.off(S.FRAG_LOADING,this.onFragLoading,this),t.off(S.FRAG_LOADED,this.onFragLoaded,this),t.off(S.FRAG_PARSING_USERDATA,this.onFragParsingUserdata,this),t.off(S.FRAG_DECRYPTED,this.onFragDecrypted,this),t.off(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.off(S.SUBTITLE_TRACKS_CLEARED,this.onSubtitleTracksCleared,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),this.hls=this.config=null,this.cea608Parser1=this.cea608Parser2=void 0},e.initCea608Parsers=function(){if(this.config.enableCEA708Captions&&(!this.cea608Parser1||!this.cea608Parser2)){var t=new pa(this,"textTrack1"),e=new pa(this,"textTrack2"),r=new pa(this,"textTrack3"),i=new pa(this,"textTrack4");this.cea608Parser1=new ga(1,t,e),this.cea608Parser2=new ga(3,r,i)}},e.addCues=function(t,e,r,i,n){for(var a,s,o,l,u=!1,h=n.length;h--;){var d=n[h],c=(a=d[0],s=d[1],o=e,l=r,Math.min(s,l)-Math.max(a,o));if(c>=0&&(d[0]=Math.min(d[0],e),d[1]=Math.max(d[1],r),u=!0,c/(r-e)>.5))return}if(u||n.push([e,r]),this.config.renderTextTracksNatively){var f=this.captionsTracks[t];this.Cues.newCue(f,e,r,i)}else{var g=this.Cues.newCue(null,e,r,i);this.hls.trigger(S.CUES_PARSED,{type:"captions",cues:g,track:t})}},e.onInitPtsFound=function(t,e){var r=this,i=e.frag,n=e.id,a=e.initPTS,s=e.timescale,o=this.unparsedVttFrags;"main"===n&&(this.initPTS[i.cc]={baseTime:a,timescale:s}),o.length&&(this.unparsedVttFrags=[],o.forEach((function(t){r.onFragLoaded(S.FRAG_LOADED,t)})))},e.getExistingTrack=function(t,e){var r=this.media;if(r)for(var i=0;i<r.textTracks.length;i++){var n=r.textTracks[i];if(ja(n,{name:t,lang:e,attrs:{}}))return n}return null},e.createCaptionsTrack=function(t){this.config.renderTextTracksNatively?this.createNativeTrack(t):this.createNonNativeTrack(t)},e.createNativeTrack=function(t){if(!this.captionsTracks[t]){var e=this.captionsProperties,r=this.captionsTracks,i=this.media,n=e[t],a=n.label,s=n.languageCode,o=this.getExistingTrack(a,s);if(o)r[t]=o,Oe(r[t]),Fe(r[t],i);else{var l=this.createTextTrack("captions",a,s);l&&(l[t]=!0,r[t]=l)}}},e.createNonNativeTrack=function(t){if(!this.nonNativeCaptionsTracks[t]){var e=this.captionsProperties[t];if(e){var r={_id:t,label:e.label,kind:"captions",default:!!e.media&&!!e.media.default,closedCaptions:e.media};this.nonNativeCaptionsTracks[t]=r,this.hls.trigger(S.NON_NATIVE_TEXT_TRACKS_FOUND,{tracks:[r]})}}},e.createTextTrack=function(t,e,r){var i=this.media;if(i)return i.addTextTrack(t,e,r)},e.onMediaAttaching=function(t,e){this.media=e.media,this._cleanTracks()},e.onMediaDetaching=function(){var t=this.captionsTracks;Object.keys(t).forEach((function(e){Oe(t[e]),delete t[e]})),this.nonNativeCaptionsTracks={}},e.onManifestLoading=function(){this.lastCc=-1,this.lastSn=-1,this.lastPartIndex=-1,this.prevCC=-1,this.vttCCs={ccOffset:0,presentationOffset:0,0:{start:0,prevCC:-1,new:!0}},this._cleanTracks(),this.tracks=[],this.captionsTracks={},this.nonNativeCaptionsTracks={},this.textTracks=[],this.unparsedVttFrags=[],this.initPTS=[],this.cea608Parser1&&this.cea608Parser2&&(this.cea608Parser1.reset(),this.cea608Parser2.reset())},e._cleanTracks=function(){var t=this.media;if(t){var e=t.textTracks;if(e)for(var r=0;r<e.length;r++)Oe(e[r])}},e.onSubtitleTracksUpdated=function(t,e){var r=this,i=e.subtitleTracks||[],n=i.some((function(t){return t.textCodec===Pa}));if(this.config.enableWebVTT||n&&this.config.enableIMSC1){if(Gn(this.tracks,i))return void(this.tracks=i);if(this.textTracks=[],this.tracks=i,this.config.renderTextTracksNatively){var a=this.media,s=a?Ue(a.textTracks):null;if(this.tracks.forEach((function(t,e){var i;if(s){for(var n=null,a=0;a<s.length;a++)if(s[a]&&ja(s[a],t)){n=s[a],s[a]=null;break}n&&(i=n)}if(i)Oe(i);else{var o=Wa(t);(i=r.createTextTrack(o,t.name,t.lang))&&(i.mode="disabled")}i&&r.textTracks.push(i)})),null!=s&&s.length){var o=s.filter((function(t){return null!==t})).map((function(t){return t.label}));o.length&&w.warn("Media element contains unused subtitle tracks: "+o.join(", ")+". Replace media element for each source to clear TextTracks and captions menu.")}}else if(this.tracks.length){var l=this.tracks.map((function(t){return{label:t.name,kind:t.type.toLowerCase(),default:t.default,subtitleTrack:t}}));this.hls.trigger(S.NON_NATIVE_TEXT_TRACKS_FOUND,{tracks:l})}}},e.onManifestLoaded=function(t,e){var r=this;this.config.enableCEA708Captions&&e.captions&&e.captions.forEach((function(t){var e=/(?:CC|SERVICE)([1-4])/.exec(t.instreamId);if(e){var i="textTrack"+e[1],n=r.captionsProperties[i];n&&(n.label=t.name,t.lang&&(n.languageCode=t.lang),n.media=t)}}))},e.closedCaptionsForLevel=function(t){var e=this.hls.levels[t.level];return null==e?void 0:e.attrs["CLOSED-CAPTIONS"]},e.onFragLoading=function(t,e){this.initCea608Parsers();var r=this.cea608Parser1,i=this.cea608Parser2,n=this.lastCc,a=this.lastSn,s=this.lastPartIndex;if(this.enabled&&r&&i&&e.frag.type===Ie){var o,l,u=e.frag,h=u.cc,d=u.sn,c=null!=(o=null==e||null==(l=e.part)?void 0:l.index)?o:-1;d===a+1||d===a&&c===s+1||h===n||(r.reset(),i.reset()),this.lastCc=h,this.lastSn=d,this.lastPartIndex=c}},e.onFragLoaded=function(t,e){var r=e.frag,i=e.payload;if(r.type===Ce)if(i.byteLength){var n=r.decryptdata,a="stats"in e;if(null==n||!n.encrypted||a){var s=this.tracks[r.level],o=this.vttCCs;o[r.cc]||(o[r.cc]={start:r.start,prevCC:this.prevCC,new:!0},this.prevCC=r.cc),s&&s.textCodec===Pa?this._parseIMSC1(r,i):this._parseVTTs(e)}}else this.hls.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!1,frag:r,error:new Error("Empty subtitle payload")})},e._parseIMSC1=function(t,e){var r=this,i=this.hls;Na(e,this.initPTS[t.cc],(function(e){r._appendCues(e,t.level),i.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!0,frag:t})}),(function(e){w.log("Failed to parse IMSC1: "+e),i.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!1,frag:t,error:e})}))},e._parseVTTs=function(t){var e,r=this,i=t.frag,n=t.payload,a=this.initPTS,s=this.unparsedVttFrags,o=a.length-1;if(a[i.cc]||-1!==o){var l=this.hls;xa(null!=(e=i.initSegment)&&e.data?Gt(i.initSegment.data,new Uint8Array(n)):n,this.initPTS[i.cc],this.vttCCs,i.cc,i.start,(function(t){r._appendCues(t,i.level),l.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!0,frag:i})}),(function(e){var a="Missing initPTS for VTT MPEGTS"===e.message;a?s.push(t):r._fallbackToIMSC1(i,n),w.log("Failed to parse VTT cue: "+e),a&&o>i.cc||l.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!1,frag:i,error:e})}))}else s.push(t)},e._fallbackToIMSC1=function(t,e){var r=this,i=this.tracks[t.level];i.textCodec||Na(e,this.initPTS[t.cc],(function(){i.textCodec=Pa,r._parseIMSC1(t,e)}),(function(){i.textCodec="wvtt"}))},e._appendCues=function(t,e){var r=this.hls;if(this.config.renderTextTracksNatively){var i=this.textTracks[e];if(!i||"disabled"===i.mode)return;t.forEach((function(t){return Me(i,t)}))}else{var n=this.tracks[e];if(!n)return;var a=n.default?"default":"subtitles"+e;r.trigger(S.CUES_PARSED,{type:"subtitles",cues:t,track:a})}},e.onFragDecrypted=function(t,e){e.frag.type===Ce&&this.onFragLoaded(S.FRAG_LOADED,e)},e.onSubtitleTracksCleared=function(){this.tracks=[],this.captionsTracks={}},e.onFragParsingUserdata=function(t,e){this.initCea608Parsers();var r=this.cea608Parser1,i=this.cea608Parser2;if(this.enabled&&r&&i){var n=e.frag,a=e.samples;if(n.type!==Ie||"NONE"!==this.closedCaptionsForLevel(n))for(var s=0;s<a.length;s++){var o=a[s].bytes;if(o){var l=this.extractCea608Data(o);r.addData(a[s].pts,l[0]),i.addData(a[s].pts,l[1])}}}},e.onBufferFlushing=function(t,e){var r=e.startOffset,i=e.endOffset,n=e.endOffsetSubtitles,a=e.type,s=this.media;if(s&&!(s.currentTime<i)){if(!a||"video"===a){var o=this.captionsTracks;Object.keys(o).forEach((function(t){return Ne(o[t],r,i)}))}if(this.config.renderTextTracksNatively&&0===r&&void 0!==n){var l=this.textTracks;Object.keys(l).forEach((function(t){return Ne(l[t],r,n)}))}}},e.extractCea608Data=function(t){for(var e=[[],[]],r=31&t[0],i=2,n=0;n<r;n++){var a=t[i++],s=127&t[i++],o=127&t[i++];if((0!==s||0!==o)&&0!=(4&a)){var l=3&a;0!==l&&1!==l||(e[l].push(s),e[l].push(o))}}return e},t}();function Wa(t){return t.characteristics&&/transcribes-spoken-dialog/gi.test(t.characteristics)&&/describes-music-and-sound/gi.test(t.characteristics)?"captions":"subtitles"}function ja(t,e){return!!t&&t.kind===Wa(e)&&Hn(e,t)}var qa=function(){function t(t){this.hls=void 0,this.autoLevelCapping=void 0,this.firstLevel=void 0,this.media=void 0,this.restrictedLevels=void 0,this.timer=void 0,this.clientRect=void 0,this.streamController=void 0,this.hls=t,this.autoLevelCapping=Number.POSITIVE_INFINITY,this.firstLevel=-1,this.media=null,this.restrictedLevels=[],this.timer=void 0,this.clientRect=null,this.registerListeners()}var e=t.prototype;return e.setStreamController=function(t){this.streamController=t},e.destroy=function(){this.hls&&this.unregisterListener(),this.timer&&this.stopCapping(),this.media=null,this.clientRect=null,this.hls=this.streamController=null},e.registerListeners=function(){var t=this.hls;t.on(S.FPS_DROP_LEVEL_CAPPING,this.onFpsDropLevelCapping,this),t.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.BUFFER_CODECS,this.onBufferCodecs,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this)},e.unregisterListener=function(){var t=this.hls;t.off(S.FPS_DROP_LEVEL_CAPPING,this.onFpsDropLevelCapping,this),t.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.BUFFER_CODECS,this.onBufferCodecs,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this)},e.onFpsDropLevelCapping=function(t,e){var r=this.hls.levels[e.droppedLevel];this.isLevelAllowed(r)&&this.restrictedLevels.push({bitrate:r.bitrate,height:r.height,width:r.width})},e.onMediaAttaching=function(t,e){this.media=e.media instanceof HTMLVideoElement?e.media:null,this.clientRect=null,this.timer&&this.hls.levels.length&&this.detectPlayerSize()},e.onManifestParsed=function(t,e){var r=this.hls;this.restrictedLevels=[],this.firstLevel=e.firstLevel,r.config.capLevelToPlayerSize&&e.video&&this.startCapping()},e.onLevelsUpdated=function(t,e){this.timer&&y(this.autoLevelCapping)&&this.detectPlayerSize()},e.onBufferCodecs=function(t,e){this.hls.config.capLevelToPlayerSize&&e.video&&this.startCapping()},e.onMediaDetaching=function(){this.stopCapping()},e.detectPlayerSize=function(){if(this.media){if(this.mediaHeight<=0||this.mediaWidth<=0)return void(this.clientRect=null);var t=this.hls.levels;if(t.length){var e=this.hls,r=this.getMaxLevel(t.length-1);r!==this.autoLevelCapping&&w.log("Setting autoLevelCapping to "+r+": "+t[r].height+"p@"+t[r].bitrate+" for media "+this.mediaWidth+"x"+this.mediaHeight),e.autoLevelCapping=r,e.autoLevelCapping>this.autoLevelCapping&&this.streamController&&this.streamController.nextLevelSwitch(),this.autoLevelCapping=e.autoLevelCapping}}},e.getMaxLevel=function(e){var r=this,i=this.hls.levels;if(!i.length)return-1;var n=i.filter((function(t,i){return r.isLevelAllowed(t)&&i<=e}));return this.clientRect=null,t.getMaxLevelByMediaSize(n,this.mediaWidth,this.mediaHeight)},e.startCapping=function(){this.timer||(this.autoLevelCapping=Number.POSITIVE_INFINITY,self.clearInterval(this.timer),this.timer=self.setInterval(this.detectPlayerSize.bind(this),1e3),this.detectPlayerSize())},e.stopCapping=function(){this.restrictedLevels=[],this.firstLevel=-1,this.autoLevelCapping=Number.POSITIVE_INFINITY,this.timer&&(self.clearInterval(this.timer),this.timer=void 0)},e.getDimensions=function(){if(this.clientRect)return this.clientRect;var t=this.media,e={width:0,height:0};if(t){var r=t.getBoundingClientRect();e.width=r.width,e.height=r.height,e.width||e.height||(e.width=r.right-r.left||t.width||0,e.height=r.bottom-r.top||t.height||0)}return this.clientRect=e,e},e.isLevelAllowed=function(t){return!this.restrictedLevels.some((function(e){return t.bitrate===e.bitrate&&t.width===e.width&&t.height===e.height}))},t.getMaxLevelByMediaSize=function(t,e,r){if(null==t||!t.length)return-1;for(var i,n,a=t.length-1,s=Math.max(e,r),o=0;o<t.length;o+=1){var l=t[o];if((l.width>=s||l.height>=s)&&(i=l,!(n=t[o+1])||i.width!==n.width||i.height!==n.height)){a=o;break}}return a},s(t,[{key:"mediaWidth",get:function(){return this.getDimensions().width*this.contentScaleFactor}},{key:"mediaHeight",get:function(){return this.getDimensions().height*this.contentScaleFactor}},{key:"contentScaleFactor",get:function(){var t=1;if(!this.hls.config.ignoreDevicePixelRatio)try{t=self.devicePixelRatio}catch(t){}return t}}]),t}(),Xa=function(){function t(t){this.hls=void 0,this.isVideoPlaybackQualityAvailable=!1,this.timer=void 0,this.media=null,this.lastTime=void 0,this.lastDroppedFrames=0,this.lastDecodedFrames=0,this.streamController=void 0,this.hls=t,this.registerListeners()}var e=t.prototype;return e.setStreamController=function(t){this.streamController=t},e.registerListeners=function(){this.hls.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this)},e.unregisterListeners=function(){this.hls.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this)},e.destroy=function(){this.timer&&clearInterval(this.timer),this.unregisterListeners(),this.isVideoPlaybackQualityAvailable=!1,this.media=null},e.onMediaAttaching=function(t,e){var r=this.hls.config;if(r.capLevelOnFPSDrop){var i=e.media instanceof self.HTMLVideoElement?e.media:null;this.media=i,i&&"function"==typeof i.getVideoPlaybackQuality&&(this.isVideoPlaybackQualityAvailable=!0),self.clearInterval(this.timer),this.timer=self.setInterval(this.checkFPSInterval.bind(this),r.fpsDroppedMonitoringPeriod)}},e.checkFPS=function(t,e,r){var i=performance.now();if(e){if(this.lastTime){var n=i-this.lastTime,a=r-this.lastDroppedFrames,s=e-this.lastDecodedFrames,o=1e3*a/n,l=this.hls;if(l.trigger(S.FPS_DROP,{currentDropped:a,currentDecoded:s,totalDroppedFrames:r}),o>0&&a>l.config.fpsDroppedMonitoringThreshold*s){var u=l.currentLevel;w.warn("drop FPS ratio greater than max allowed value for currentLevel: "+u),u>0&&(-1===l.autoLevelCapping||l.autoLevelCapping>=u)&&(u-=1,l.trigger(S.FPS_DROP_LEVEL_CAPPING,{level:u,droppedLevel:l.currentLevel}),l.autoLevelCapping=u,this.streamController.nextLevelSwitch())}}this.lastTime=i,this.lastDroppedFrames=r,this.lastDecodedFrames=e}},e.checkFPSInterval=function(){var t=this.media;if(t)if(this.isVideoPlaybackQualityAvailable){var e=t.getVideoPlaybackQuality();this.checkFPS(t,e.totalVideoFrames,e.droppedVideoFrames)}else this.checkFPS(t,t.webkitDecodedFrameCount,t.webkitDroppedFrameCount)},t}(),za="[eme]",Qa=function(){function t(e){this.hls=void 0,this.config=void 0,this.media=null,this.keyFormatPromise=null,this.keySystemAccessPromises={},this._requestLicenseFailureCount=0,this.mediaKeySessions=[],this.keyIdToKeySessionPromise={},this.setMediaKeysQueue=t.CDMCleanupPromise?[t.CDMCleanupPromise]:[],this.onMediaEncrypted=this._onMediaEncrypted.bind(this),this.onWaitingForKey=this._onWaitingForKey.bind(this),this.debug=w.debug.bind(w,za),this.log=w.log.bind(w,za),this.warn=w.warn.bind(w,za),this.error=w.error.bind(w,za),this.hls=e,this.config=e.config,this.registerListeners()}var e=t.prototype;return e.destroy=function(){this.unregisterListeners(),this.onMediaDetached();var t=this.config;t.requestMediaKeySystemAccessFunc=null,t.licenseXhrSetup=t.licenseResponseCallback=void 0,t.drmSystems=t.drmSystemOptions={},this.hls=this.onMediaEncrypted=this.onWaitingForKey=this.keyIdToKeySessionPromise=null,this.config=null},e.registerListeners=function(){this.hls.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.on(S.MEDIA_DETACHED,this.onMediaDetached,this),this.hls.on(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.on(S.MANIFEST_LOADED,this.onManifestLoaded,this)},e.unregisterListeners=function(){this.hls.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.off(S.MEDIA_DETACHED,this.onMediaDetached,this),this.hls.off(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.off(S.MANIFEST_LOADED,this.onManifestLoaded,this)},e.getLicenseServerUrl=function(t){var e=this.config,r=e.drmSystems,i=e.widevineLicenseUrl,n=r[t];if(n)return n.licenseUrl;if(t===q.WIDEVINE&&i)return i;throw new Error('no license server URL configured for key-system "'+t+'"')},e.getServerCertificateUrl=function(t){var e=this.config.drmSystems[t];if(e)return e.serverCertificateUrl;this.log('No Server Certificate in config.drmSystems["'+t+'"]')},e.attemptKeySystemAccess=function(t){var e=this,r=this.hls.levels,i=function(t,e,r){return!!t&&r.indexOf(t)===e},n=r.map((function(t){return t.audioCodec})).filter(i),a=r.map((function(t){return t.videoCodec})).filter(i);return n.length+a.length===0&&a.push("avc1.42e01e"),new Promise((function(r,i){!function t(s){var o=s.shift();e.getMediaKeysPromise(o,n,a).then((function(t){return r({keySystem:o,mediaKeys:t})})).catch((function(e){s.length?t(s):i(e instanceof es?e:new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_ACCESS,error:e,fatal:!0},e.message))}))}(t)}))},e.requestMediaKeySystemAccess=function(t,e){var r=this.config.requestMediaKeySystemAccessFunc;if("function"!=typeof r){var i="Configured requestMediaKeySystemAccess is not a function "+r;return null===it&&"http:"===self.location.protocol&&(i="navigator.requestMediaKeySystemAccess is not available over insecure protocol "+location.protocol),Promise.reject(new Error(i))}return r(t,e)},e.getMediaKeysPromise=function(t,e,r){var i=this,n=function(t,e,r,i){var n;switch(t){case q.FAIRPLAY:n=["cenc","sinf"];break;case q.WIDEVINE:case q.PLAYREADY:n=["cenc"];break;case q.CLEARKEY:n=["cenc","keyids"];break;default:throw new Error("Unknown key-system: "+t)}return function(t,e,r,i){return[{initDataTypes:t,persistentState:i.persistentState||"optional",distinctiveIdentifier:i.distinctiveIdentifier||"optional",sessionTypes:i.sessionTypes||[i.sessionType||"temporary"],audioCapabilities:e.map((function(t){return{contentType:'audio/mp4; codecs="'+t+'"',robustness:i.audioRobustness||"",encryptionScheme:i.audioEncryptionScheme||null}})),videoCapabilities:r.map((function(t){return{contentType:'video/mp4; codecs="'+t+'"',robustness:i.videoRobustness||"",encryptionScheme:i.videoEncryptionScheme||null}}))}]}(n,e,r,i)}(t,e,r,this.config.drmSystemOptions),a=this.keySystemAccessPromises[t],s=null==a?void 0:a.keySystemAccess;if(!s){this.log('Requesting encrypted media "'+t+'" key-system access with config: '+JSON.stringify(n)),s=this.requestMediaKeySystemAccess(t,n);var o=this.keySystemAccessPromises[t]={keySystemAccess:s};return s.catch((function(e){i.log('Failed to obtain access to key-system "'+t+'": '+e)})),s.then((function(e){i.log('Access for key-system "'+e.keySystem+'" obtained');var r=i.fetchServerCertificate(t);return i.log('Create media-keys for "'+t+'"'),o.mediaKeys=e.createMediaKeys().then((function(e){return i.log('Media-keys created for "'+t+'"'),r.then((function(r){return r?i.setMediaKeysServerCertificate(e,t,r):e}))})),o.mediaKeys.catch((function(e){i.error('Failed to create media-keys for "'+t+'"}: '+e)})),o.mediaKeys}))}return s.then((function(){return a.mediaKeys}))},e.createMediaKeySessionContext=function(t){var e=t.decryptdata,r=t.keySystem,i=t.mediaKeys;this.log('Creating key-system session "'+r+'" keyId: '+Lt(e.keyId||[]));var n=i.createSession(),a={decryptdata:e,keySystem:r,mediaKeys:i,mediaKeysSession:n,keyStatus:"status-pending"};return this.mediaKeySessions.push(a),a},e.renewKeySession=function(t){var e=t.decryptdata;if(e.pssh){var r=this.createMediaKeySessionContext(t),i=this.getKeyIdString(e);this.keyIdToKeySessionPromise[i]=this.generateRequestWithPreferredKeySession(r,"cenc",e.pssh,"expired")}else this.warn("Could not renew expired session. Missing pssh initData.");this.removeSession(t)},e.getKeyIdString=function(t){if(!t)throw new Error("Could not read keyId of undefined decryptdata");if(null===t.keyId)throw new Error("keyId is null");return Lt(t.keyId)},e.updateKeySession=function(t,e){var r,i=t.mediaKeysSession;return this.log('Updating key-session "'+i.sessionId+'" for keyID '+Lt((null==(r=t.decryptdata)?void 0:r.keyId)||[])+"\n } (data length: "+(e?e.byteLength:e)+")"),i.update(e)},e.selectKeySystemFormat=function(t){var e=Object.keys(t.levelkeys||{});return this.keyFormatPromise||(this.log("Selecting key-system from fragment (sn: "+t.sn+" "+t.type+": "+t.level+") key formats "+e.join(", ")),this.keyFormatPromise=this.getKeyFormatPromise(e)),this.keyFormatPromise},e.getKeyFormatPromise=function(t){var e=this;return new Promise((function(r,i){var n=et(e.config),a=t.map($).filter((function(t){return!!t&&-1!==n.indexOf(t)}));return e.getKeySystemSelectionPromise(a).then((function(t){var e=t.keySystem,n=tt(e);n?r(n):i(new Error('Unable to find format for key-system "'+e+'"'))})).catch(i)}))},e.loadKey=function(t){var e=this,r=t.keyInfo.decryptdata,i=this.getKeyIdString(r),n="(keyId: "+i+' format: "'+r.keyFormat+'" method: '+r.method+" uri: "+r.uri+")";this.log("Starting session for key "+n);var a=this.keyIdToKeySessionPromise[i];return a||(a=this.keyIdToKeySessionPromise[i]=this.getKeySystemForKeyPromise(r).then((function(i){var a=i.keySystem,s=i.mediaKeys;return e.throwIfDestroyed(),e.log("Handle encrypted media sn: "+t.frag.sn+" "+t.frag.type+": "+t.frag.level+" using key "+n),e.attemptSetMediaKeys(a,s).then((function(){e.throwIfDestroyed();var t=e.createMediaKeySessionContext({keySystem:a,mediaKeys:s,decryptdata:r});return e.generateRequestWithPreferredKeySession(t,"cenc",r.pssh,"playlist-key")}))}))).catch((function(t){return e.handleError(t)})),a},e.throwIfDestroyed=function(t){if(!this.hls)throw new Error("invalid state")},e.handleError=function(t){this.hls&&(this.error(t.message),t instanceof es?this.hls.trigger(S.ERROR,t.data):this.hls.trigger(S.ERROR,{type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_KEYS,error:t,fatal:!0}))},e.getKeySystemForKeyPromise=function(t){var e=this.getKeyIdString(t),r=this.keyIdToKeySessionPromise[e];if(!r){var i=$(t.keyFormat),n=i?[i]:et(this.config);return this.attemptKeySystemAccess(n)}return r},e.getKeySystemSelectionPromise=function(t){if(t.length||(t=et(this.config)),0===t.length)throw new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_CONFIGURED_LICENSE,fatal:!0},"Missing key-system license configuration options "+JSON.stringify({drmSystems:this.config.drmSystems}));return this.attemptKeySystemAccess(t)},e._onMediaEncrypted=function(t){var e=this,r=t.initDataType,i=t.initData;if(this.debug('"'+t.type+'" event: init data type: "'+r+'"'),null!==i){var n,a;if("sinf"===r&&this.config.drmSystems[q.FAIRPLAY]){var s=bt(new Uint8Array(i));try{var o=V(JSON.parse(s).sinf),l=Ut(new Uint8Array(o));if(!l)return;n=l.subarray(8,24),a=q.FAIRPLAY}catch(t){return void this.warn('Failed to parse sinf "encrypted" event message initData')}}else{var u=function(t){if(!(t instanceof ArrayBuffer)||t.byteLength<32)return null;var e={version:0,systemId:"",kids:null,data:null},r=new DataView(t),i=r.getUint32(0);if(t.byteLength!==i&&i>44)return null;if(1886614376!==r.getUint32(4))return null;if(e.version=r.getUint32(8)>>>24,e.version>1)return null;e.systemId=Lt(new Uint8Array(t,12,16));var n=r.getUint32(28);if(0===e.version){if(i-32<n)return null;e.data=new Uint8Array(t,32,n)}else if(1===e.version){e.kids=[];for(var a=0;a<n;a++)e.kids.push(new Uint8Array(t,32+16*a,16))}return e}(i);if(null===u)return;0===u.version&&u.systemId===Z&&u.data&&(n=u.data.subarray(8,24)),a=function(t){if(t===Z)return q.WIDEVINE}(u.systemId)}if(a&&n){for(var h,d=Lt(n),c=this.keyIdToKeySessionPromise,f=this.mediaKeySessions,g=c[d],v=function(){var t=f[m],a=t.decryptdata;if(a.pssh||!a.keyId)return 0;var s=Lt(a.keyId);return d===s||-1!==a.uri.replace(/-/g,"").indexOf(d)?(g=c[s],delete c[s],a.pssh=new Uint8Array(i),a.keyId=n,g=c[d]=g.then((function(){return e.generateRequestWithPreferredKeySession(t,r,i,"encrypted-event-key-match")})),1):void 0},m=0;m<f.length&&(0===(h=v())||1!==h);m++);g||(g=c[d]=this.getKeySystemSelectionPromise([a]).then((function(t){var a,s=t.keySystem,o=t.mediaKeys;e.throwIfDestroyed();var l=new qt("ISO-23001-7",d,null!=(a=tt(s))?a:"");return l.pssh=new Uint8Array(i),l.keyId=n,e.attemptSetMediaKeys(s,o).then((function(){e.throwIfDestroyed();var t=e.createMediaKeySessionContext({decryptdata:l,keySystem:s,mediaKeys:o});return e.generateRequestWithPreferredKeySession(t,r,i,"encrypted-event-no-match")}))}))),g.catch((function(t){return e.handleError(t)}))}}},e._onWaitingForKey=function(t){this.log('"'+t.type+'" event')},e.attemptSetMediaKeys=function(t,e){var r=this,i=this.setMediaKeysQueue.slice();this.log('Setting media-keys for "'+t+'"');var n=Promise.all(i).then((function(){if(!r.media)throw new Error("Attempted to set mediaKeys without media element attached");return r.media.setMediaKeys(e)}));return this.setMediaKeysQueue.push(n),n.then((function(){r.log('Media-keys set for "'+t+'"'),i.push(n),r.setMediaKeysQueue=r.setMediaKeysQueue.filter((function(t){return-1===i.indexOf(t)}))}))},e.generateRequestWithPreferredKeySession=function(t,e,r,i){var n,a,s=this,o=null==(n=this.config.drmSystems)||null==(a=n[t.keySystem])?void 0:a.generateRequest;if(o)try{var l=o.call(this.hls,e,r,t);if(!l)throw new Error("Invalid response from configured generateRequest filter");e=l.initDataType,r=t.decryptdata.pssh=l.initData?new Uint8Array(l.initData):null}catch(t){var u;if(this.warn(t.message),null!=(u=this.hls)&&u.config.debug)throw t}if(null===r)return this.log('Skipping key-session request for "'+i+'" (no initData)'),Promise.resolve(t);var h=this.getKeyIdString(t.decryptdata);this.log('Generating key-session request for "'+i+'": '+h+" (init data type: "+e+" length: "+(r?r.byteLength:null)+")");var d=new Mn,c=t._onmessage=function(e){var r=t.mediaKeysSession;if(r){var i=e.messageType,n=e.message;s.log('"'+i+'" message event for session "'+r.sessionId+'" message size: '+n.byteLength),"license-request"===i||"license-renewal"===i?s.renewLicense(t,n).catch((function(t){s.handleError(t),d.emit("error",t)})):"license-release"===i?t.keySystem===q.FAIRPLAY&&(s.updateKeySession(t,W("acknowledged")),s.removeSession(t)):s.warn('unhandled media key message type "'+i+'"')}else d.emit("error",new Error("invalid state"))},f=t._onkeystatuseschange=function(e){if(t.mediaKeysSession){s.onKeyStatusChange(t);var r=t.keyStatus;d.emit("keyStatus",r),"expired"===r&&(s.warn(t.keySystem+" expired for key "+h),s.renewKeySession(t))}else d.emit("error",new Error("invalid state"))};t.mediaKeysSession.addEventListener("message",c),t.mediaKeysSession.addEventListener("keystatuseschange",f);var g=new Promise((function(t,e){d.on("error",e),d.on("keyStatus",(function(r){r.startsWith("usable")?t():"output-restricted"===r?e(new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED,fatal:!1},"HDCP level output restricted")):"internal-error"===r?e(new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_STATUS_INTERNAL_ERROR,fatal:!0},'key status changed to "'+r+'"')):"expired"===r?e(new Error("key expired while generating request")):s.warn('unhandled key status change "'+r+'"')}))}));return t.mediaKeysSession.generateRequest(e,r).then((function(){var e;s.log('Request generated for key-session "'+(null==(e=t.mediaKeysSession)?void 0:e.sessionId)+'" keyId: '+h)})).catch((function(t){throw new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_SESSION,error:t,fatal:!1},"Error generating key-session request: "+t)})).then((function(){return g})).catch((function(e){throw d.removeAllListeners(),s.removeSession(t),e})).then((function(){return d.removeAllListeners(),t}))},e.onKeyStatusChange=function(t){var e=this;t.mediaKeysSession.keyStatuses.forEach((function(r,i){e.log('key status change "'+r+'" for keyStatuses keyId: '+Lt("buffer"in i?new Uint8Array(i.buffer,i.byteOffset,i.byteLength):new Uint8Array(i))+" session keyId: "+Lt(new Uint8Array(t.decryptdata.keyId||[]))+" uri: "+t.decryptdata.uri),t.keyStatus=r}))},e.fetchServerCertificate=function(t){var e=this.config,r=new(0,e.loader)(e),n=this.getServerCertificateUrl(t);return n?(this.log('Fetching server certificate for "'+t+'"'),new Promise((function(a,s){var o={responseType:"arraybuffer",url:n},l=e.certLoadPolicy.default,u={loadPolicy:l,timeout:l.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0},h={onSuccess:function(t,e,r,i){a(t.data)},onError:function(e,r,a,l){s(new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,fatal:!0,networkDetails:a,response:i({url:o.url,data:void 0},e)},'"'+t+'" certificate request failed ('+n+"). Status: "+e.code+" ("+e.text+")"))},onTimeout:function(e,r,i){s(new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,fatal:!0,networkDetails:i,response:{url:o.url,data:void 0}},'"'+t+'" certificate request timed out ('+n+")"))},onAbort:function(t,e,r){s(new Error("aborted"))}};r.load(o,u,h)}))):Promise.resolve()},e.setMediaKeysServerCertificate=function(t,e,r){var i=this;return new Promise((function(n,a){t.setServerCertificate(r).then((function(a){i.log("setServerCertificate "+(a?"success":"not supported by CDM")+" ("+(null==r?void 0:r.byteLength)+') on "'+e+'"'),n(t)})).catch((function(t){a(new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED,error:t,fatal:!0},t.message))}))}))},e.renewLicense=function(t,e){var r=this;return this.requestLicense(t,new Uint8Array(e)).then((function(e){return r.updateKeySession(t,new Uint8Array(e)).catch((function(t){throw new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SESSION_UPDATE_FAILED,error:t,fatal:!0},t.message)}))}))},e.unpackPlayReadyKeyMessage=function(t,e){var r=String.fromCharCode.apply(null,new Uint16Array(e.buffer));if(!r.includes("PlayReadyKeyMessage"))return t.setRequestHeader("Content-Type","text/xml; charset=utf-8"),e;var i=(new DOMParser).parseFromString(r,"application/xml"),n=i.querySelectorAll("HttpHeader");if(n.length>0)for(var a,s=0,o=n.length;s<o;s++){var l,u,h=null==(l=(a=n[s]).querySelector("name"))?void 0:l.textContent,d=null==(u=a.querySelector("value"))?void 0:u.textContent;h&&d&&t.setRequestHeader(h,d)}var c=i.querySelector("Challenge"),f=null==c?void 0:c.textContent;if(!f)throw new Error("Cannot find <Challenge> in key message");return W(atob(f))},e.setupLicenseXHR=function(t,e,r,i){var n=this,a=this.config.licenseXhrSetup;return a?Promise.resolve().then((function(){if(!r.decryptdata)throw new Error("Key removed");return a.call(n.hls,t,e,r,i)})).catch((function(s){if(!r.decryptdata)throw s;return t.open("POST",e,!0),a.call(n.hls,t,e,r,i)})).then((function(r){return t.readyState||t.open("POST",e,!0),{xhr:t,licenseChallenge:r||i}})):(t.open("POST",e,!0),Promise.resolve({xhr:t,licenseChallenge:i}))},e.requestLicense=function(t,e){var r=this,i=this.config.keyLoadPolicy.default;return new Promise((function(n,a){var s=r.getLicenseServerUrl(t.keySystem);r.log("Sending license request to URL: "+s);var o=new XMLHttpRequest;o.responseType="arraybuffer",o.onreadystatechange=function(){if(!r.hls||!t.mediaKeysSession)return a(new Error("invalid state"));if(4===o.readyState)if(200===o.status){r._requestLicenseFailureCount=0;var l=o.response;r.log("License received "+(l instanceof ArrayBuffer?l.byteLength:l));var u=r.config.licenseResponseCallback;if(u)try{l=u.call(r.hls,o,s,t)}catch(t){r.error(t)}n(l)}else{var h=i.errorRetry,d=h?h.maxNumRetry:0;if(r._requestLicenseFailureCount++,r._requestLicenseFailureCount>d||o.status>=400&&o.status<500)a(new es({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_LICENSE_REQUEST_FAILED,fatal:!0,networkDetails:o,response:{url:s,data:void 0,code:o.status,text:o.statusText}},"License Request XHR failed ("+s+"). Status: "+o.status+" ("+o.statusText+")"));else{var c=d-r._requestLicenseFailureCount+1;r.warn("Retrying license request, "+c+" attempts left"),r.requestLicense(t,e).then(n,a)}}},t.licenseXhr&&t.licenseXhr.readyState!==XMLHttpRequest.DONE&&t.licenseXhr.abort(),t.licenseXhr=o,r.setupLicenseXHR(o,s,t,e).then((function(e){var i=e.xhr,n=e.licenseChallenge;t.keySystem==q.PLAYREADY&&(n=r.unpackPlayReadyKeyMessage(i,n)),i.send(n)}))}))},e.onMediaAttached=function(t,e){if(this.config.emeEnabled){var r=e.media;this.media=r,r.addEventListener("encrypted",this.onMediaEncrypted),r.addEventListener("waitingforkey",this.onWaitingForKey)}},e.onMediaDetached=function(){var e=this,r=this.media,i=this.mediaKeySessions;r&&(r.removeEventListener("encrypted",this.onMediaEncrypted),r.removeEventListener("waitingforkey",this.onWaitingForKey),this.media=null),this._requestLicenseFailureCount=0,this.setMediaKeysQueue=[],this.mediaKeySessions=[],this.keyIdToKeySessionPromise={},qt.clearKeyUriToKeyIdMap();var n=i.length;t.CDMCleanupPromise=Promise.all(i.map((function(t){return e.removeSession(t)})).concat(null==r?void 0:r.setMediaKeys(null).catch((function(t){e.log("Could not clear media keys: "+t)})))).then((function(){n&&(e.log("finished closing key sessions and clearing media keys"),i.length=0)})).catch((function(t){e.log("Could not close sessions and clear media keys: "+t)}))},e.onManifestLoading=function(){this.keyFormatPromise=null},e.onManifestLoaded=function(t,e){var r=e.sessionKeys;if(r&&this.config.emeEnabled&&!this.keyFormatPromise){var i=r.reduce((function(t,e){return-1===t.indexOf(e.keyFormat)&&t.push(e.keyFormat),t}),[]);this.log("Selecting key-system from session-keys "+i.join(", ")),this.keyFormatPromise=this.getKeyFormatPromise(i)}},e.removeSession=function(t){var e=this,r=t.mediaKeysSession,i=t.licenseXhr;if(r){this.log("Remove licenses and keys and close session "+r.sessionId),t._onmessage&&(r.removeEventListener("message",t._onmessage),t._onmessage=void 0),t._onkeystatuseschange&&(r.removeEventListener("keystatuseschange",t._onkeystatuseschange),t._onkeystatuseschange=void 0),i&&i.readyState!==XMLHttpRequest.DONE&&i.abort(),t.mediaKeysSession=t.decryptdata=t.licenseXhr=void 0;var n=this.mediaKeySessions.indexOf(t);return n>-1&&this.mediaKeySessions.splice(n,1),r.remove().catch((function(t){e.log("Could not remove session: "+t)})).then((function(){return r.close()})).catch((function(t){e.log("Could not close session: "+t)}))}},t}();Qa.CDMCleanupPromise=void 0;var Ja,$a,Za,ts,es=function(t){function e(e,r){var i;return(i=t.call(this,r)||this).data=void 0,e.error||(e.error=new Error(r)),i.data=e,e.err=e.error,i}return l(e,t),e}(c(Error));!function(t){t.MANIFEST="m",t.AUDIO="a",t.VIDEO="v",t.MUXED="av",t.INIT="i",t.CAPTION="c",t.TIMED_TEXT="tt",t.KEY="k",t.OTHER="o"}(Ja||(Ja={})),function(t){t.DASH="d",t.HLS="h",t.SMOOTH="s",t.OTHER="o"}($a||($a={})),function(t){t.OBJECT="CMCD-Object",t.REQUEST="CMCD-Request",t.SESSION="CMCD-Session",t.STATUS="CMCD-Status"}(Za||(Za={}));var rs=((ts={})[Za.OBJECT]=["br","d","ot","tb"],ts[Za.REQUEST]=["bl","dl","mtp","nor","nrr","su"],ts[Za.SESSION]=["cid","pr","sf","sid","st","v"],ts[Za.STATUS]=["bs","rtp"],ts),is=function t(e,r){this.value=void 0,this.params=void 0,Array.isArray(e)&&(e=e.map((function(e){return e instanceof t?e:new t(e)}))),this.value=e,this.params=r},ns=function(t){this.description=void 0,this.description=t},as="Dict";function ss(t,e,r,i){return new Error("failed to "+t+' "'+(n=e,(Array.isArray(n)?JSON.stringify(n):n instanceof Map?"Map{}":n instanceof Set?"Set{}":"object"==typeof n?JSON.stringify(n):String(n))+'" as ')+r,{cause:i});var n}var os="Bare Item",ls="Boolean",us="Byte Sequence",hs="Decimal",ds="Integer",cs=/[\x00-\x1f\x7f]+/,fs="Token",gs="Key";function vs(t,e,r){return ss("serialize",t,e,r)}function ms(t){if(!1===ArrayBuffer.isView(t))throw vs(t,us);return":"+(e=t,btoa(String.fromCharCode.apply(String,e))+":");var e}function ps(t){if(function(t){return t<-999999999999999||999999999999999<t}(t))throw vs(t,ds);return t.toString()}function ys(t,e){if(t<0)return-ys(-t,e);var r=Math.pow(10,e);if(Math.abs(t*r%1-.5)<Number.EPSILON){var i=Math.floor(t*r);return(i%2==0?i:i+1)/r}return Math.round(t*r)/r}function Es(t){var e=ys(t,3);if(Math.floor(Math.abs(e)).toString().length>12)throw vs(t,hs);var r=e.toString();return r.includes(".")?r:r+".0"}var Ts="String";function Ss(t){var e,r=(e=t).description||e.toString().slice(7,-1);if(!1===/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(r))throw vs(r,fs);return r}function Ls(t){switch(typeof t){case"number":if(!y(t))throw vs(t,os);return Number.isInteger(t)?ps(t):Es(t);case"string":return function(t){if(cs.test(t))throw vs(t,Ts);return'"'+t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'}(t);case"symbol":return Ss(t);case"boolean":return function(t){if("boolean"!=typeof t)throw vs(t,ls);return t?"?1":"?0"}(t);case"object":if(t instanceof Date)return function(t){return"@"+ps(t.getTime()/1e3)}(t);if(t instanceof Uint8Array)return ms(t);if(t instanceof ns)return Ss(t);default:throw vs(t,os)}}function As(t){if(!1===/^[a-z*][a-z0-9\-_.*]*$/.test(t))throw vs(t,gs);return t}function Rs(t){return null==t?"":Object.entries(t).map((function(t){var e=t[0],r=t[1];return!0===r?";"+As(e):";"+As(e)+"="+Ls(r)})).join("")}function ks(t){return t instanceof is?""+Ls(t.value)+Rs(t.params):Ls(t)}function bs(t,e){var r;if(void 0===e&&(e={whitespace:!0}),"object"!=typeof t)throw vs(t,as);var i=t instanceof Map?t.entries():Object.entries(t),n=null!=(r=e)&&r.whitespace?" ":"";return Array.from(i).map((function(t){var e=t[0],r=t[1];r instanceof is==0&&(r=new is(r));var i,n=As(e);return!0===r.value?n+=Rs(r.params):(n+="=",Array.isArray(r.value)?n+="("+(i=r).value.map(ks).join(" ")+")"+Rs(i.params):n+=ks(r)),n})).join(","+n)}var Ds=function(t){return"ot"===t||"sf"===t||"st"===t},Is=function(t){return"number"==typeof t?y(t):null!=t&&""!==t&&!1!==t},ws=function(t){return Math.round(t)},Cs=function(t){return 100*ws(t/100)},_s={br:ws,d:ws,bl:Cs,dl:Cs,mtp:Cs,nor:function(t,e){return null!=e&&e.baseUrl&&(t=function(t,e){var r=new URL(t),i=new URL(e);if(r.origin!==i.origin)return t;for(var n=r.pathname.split("/").slice(1),a=i.pathname.split("/").slice(1,-1);n[0]===a[0];)n.shift(),a.shift();for(;a.length;)a.shift(),n.unshift("..");return n.join("/")}(t,e.baseUrl)),encodeURIComponent(t)},rtp:Cs,tb:ws};function xs(t,e){return void 0===e&&(e={}),t?function(t,e){return bs(t,e)}(function(t,e){var r={};if(null==t||"object"!=typeof t)return r;var i=Object.keys(t).sort(),n=o({},_s,null==e?void 0:e.formatters),a=null==e?void 0:e.filter;return i.forEach((function(i){if(null==a||!a(i)){var s=t[i],o=n[i];o&&(s=o(s,e)),"v"===i&&1===s||"pr"==i&&1===s||Is(s)&&(Ds(i)&&"string"==typeof s&&(s=new ns(s)),r[i]=s)}})),r}(t,e),o({whitespace:!1},e)):""}function Ps(t,e,r){return o(t,function(t,e){var r;if(void 0===e&&(e={}),!t)return{};var i=Object.entries(t),n=Object.entries(rs).concat(Object.entries((null==(r=e)?void 0:r.customHeaderMap)||{})),a=i.reduce((function(t,e){var r,i=e[0],a=e[1],s=(null==(r=n.find((function(t){return t[1].includes(i)})))?void 0:r[0])||Za.REQUEST;return null!=t[s]||(t[s]={}),t[s][i]=a,t}),{});return Object.entries(a).reduce((function(t,r){var i=r[0],n=r[1];return t[i]=xs(n,e),t}),{})}(e,r))}var Fs="CMCD",Ms=/CMCD=[^&#]+/;function Os(t,e,r){var i=function(t,e){if(void 0===e&&(e={}),!t)return"";var r=xs(t,e);return Fs+"="+encodeURIComponent(r)}(e,r);if(!i)return t;if(Ms.test(t))return t.replace(Ms,i);var n=t.includes("?")?"&":"?";return""+t+n+i}var Ns=function(){function t(t){var e=this;this.hls=void 0,this.config=void 0,this.media=void 0,this.sid=void 0,this.cid=void 0,this.useHeaders=!1,this.includeKeys=void 0,this.initialized=!1,this.starved=!1,this.buffering=!0,this.audioBuffer=void 0,this.videoBuffer=void 0,this.onWaiting=function(){e.initialized&&(e.starved=!0),e.buffering=!0},this.onPlaying=function(){e.initialized||(e.initialized=!0),e.buffering=!1},this.applyPlaylistData=function(t){try{e.apply(t,{ot:Ja.MANIFEST,su:!e.initialized})}catch(t){w.warn("Could not generate manifest CMCD data.",t)}},this.applyFragmentData=function(t){try{var r=t.frag,i=e.hls.levels[r.level],n=e.getObjectType(r),a={d:1e3*r.duration,ot:n};n!==Ja.VIDEO&&n!==Ja.AUDIO&&n!=Ja.MUXED||(a.br=i.bitrate/1e3,a.tb=e.getTopBandwidth(n)/1e3,a.bl=e.getBufferLength(n)),e.apply(t,a)}catch(t){w.warn("Could not generate segment CMCD data.",t)}},this.hls=t;var r=this.config=t.config,i=r.cmcd;null!=i&&(r.pLoader=this.createPlaylistLoader(),r.fLoader=this.createFragmentLoader(),this.sid=i.sessionId||function(){try{return crypto.randomUUID()}catch(i){try{var t=URL.createObjectURL(new Blob),e=t.toString();return URL.revokeObjectURL(t),e.slice(e.lastIndexOf("/")+1)}catch(t){var r=(new Date).getTime();return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(t){var e=(r+16*Math.random())%16|0;return r=Math.floor(r/16),("x"==t?e:3&e|8).toString(16)}))}}}(),this.cid=i.contentId,this.useHeaders=!0===i.useHeaders,this.includeKeys=i.includeKeys,this.registerListeners())}var e=t.prototype;return e.registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHED,this.onMediaDetached,this),t.on(S.BUFFER_CREATED,this.onBufferCreated,this)},e.unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHED,this.onMediaDetached,this),t.off(S.BUFFER_CREATED,this.onBufferCreated,this)},e.destroy=function(){this.unregisterListeners(),this.onMediaDetached(),this.hls=this.config=this.audioBuffer=this.videoBuffer=null,this.onWaiting=this.onPlaying=null},e.onMediaAttached=function(t,e){this.media=e.media,this.media.addEventListener("waiting",this.onWaiting),this.media.addEventListener("playing",this.onPlaying)},e.onMediaDetached=function(){this.media&&(this.media.removeEventListener("waiting",this.onWaiting),this.media.removeEventListener("playing",this.onPlaying),this.media=null)},e.onBufferCreated=function(t,e){var r,i;this.audioBuffer=null==(r=e.tracks.audio)?void 0:r.buffer,this.videoBuffer=null==(i=e.tracks.video)?void 0:i.buffer},e.createData=function(){var t;return{v:1,sf:$a.HLS,sid:this.sid,cid:this.cid,pr:null==(t=this.media)?void 0:t.playbackRate,mtp:this.hls.bandwidthEstimate/1e3}},e.apply=function(t,e){void 0===e&&(e={}),o(e,this.createData());var r=e.ot===Ja.INIT||e.ot===Ja.VIDEO||e.ot===Ja.MUXED;this.starved&&r&&(e.bs=!0,e.su=!0,this.starved=!1),null==e.su&&(e.su=this.buffering);var i=this.includeKeys;i&&(e=Object.keys(e).reduce((function(t,r){return i.includes(r)&&(t[r]=e[r]),t}),{})),this.useHeaders?(t.headers||(t.headers={}),Ps(t.headers,e)):t.url=Os(t.url,e)},e.getObjectType=function(t){var e=t.type;return"subtitle"===e?Ja.TIMED_TEXT:"initSegment"===t.sn?Ja.INIT:"audio"===e?Ja.AUDIO:"main"===e?this.hls.audioTracks.length?Ja.VIDEO:Ja.MUXED:void 0},e.getTopBandwidth=function(t){var e,r=0,i=this.hls;if(t===Ja.AUDIO)e=i.audioTracks;else{var n=i.maxAutoLevel,a=n>-1?n+1:i.levels.length;e=i.levels.slice(0,a)}for(var s,o=g(e);!(s=o()).done;){var l=s.value;l.bitrate>r&&(r=l.bitrate)}return r>0?r:NaN},e.getBufferLength=function(t){var e=this.hls.media,r=t===Ja.AUDIO?this.audioBuffer:this.videoBuffer;return r&&e?1e3*zr.bufferInfo(r,e.currentTime,this.config.maxBufferHole).len:NaN},e.createPlaylistLoader=function(){var t=this.config.pLoader,e=this.applyPlaylistData,r=t||this.config.loader;return function(){function t(t){this.loader=void 0,this.loader=new r(t)}var i=t.prototype;return i.destroy=function(){this.loader.destroy()},i.abort=function(){this.loader.abort()},i.load=function(t,r,i){e(t),this.loader.load(t,r,i)},s(t,[{key:"stats",get:function(){return this.loader.stats}},{key:"context",get:function(){return this.loader.context}}]),t}()},e.createFragmentLoader=function(){var t=this.config.fLoader,e=this.applyFragmentData,r=t||this.config.loader;return function(){function t(t){this.loader=void 0,this.loader=new r(t)}var i=t.prototype;return i.destroy=function(){this.loader.destroy()},i.abort=function(){this.loader.abort()},i.load=function(t,r,i){e(t),this.loader.load(t,r,i)},s(t,[{key:"stats",get:function(){return this.loader.stats}},{key:"context",get:function(){return this.loader.context}}]),t}()},t}(),Us=function(){function t(t){this.hls=void 0,this.log=void 0,this.loader=null,this.uri=null,this.pathwayId=".",this.pathwayPriority=null,this.timeToLoad=300,this.reloadTimer=-1,this.updated=0,this.started=!1,this.enabled=!0,this.levels=null,this.audioTracks=null,this.subtitleTracks=null,this.penalizedPathways={},this.hls=t,this.log=w.log.bind(w,"[content-steering]:"),this.registerListeners()}var e=t.prototype;return e.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.ERROR,this.onError,this)},e.unregisterListeners=function(){var t=this.hls;t&&(t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.ERROR,this.onError,this))},e.startLoad=function(){if(this.started=!0,this.clearTimeout(),this.enabled&&this.uri){if(this.updated){var t=1e3*this.timeToLoad-(performance.now()-this.updated);if(t>0)return void this.scheduleRefresh(this.uri,t)}this.loadSteeringManifest(this.uri)}},e.stopLoad=function(){this.started=!1,this.loader&&(this.loader.destroy(),this.loader=null),this.clearTimeout()},e.clearTimeout=function(){-1!==this.reloadTimer&&(self.clearTimeout(this.reloadTimer),this.reloadTimer=-1)},e.destroy=function(){this.unregisterListeners(),this.stopLoad(),this.hls=null,this.levels=this.audioTracks=this.subtitleTracks=null},e.removeLevel=function(t){var e=this.levels;e&&(this.levels=e.filter((function(e){return e!==t})))},e.onManifestLoading=function(){this.stopLoad(),this.enabled=!0,this.timeToLoad=300,this.updated=0,this.uri=null,this.pathwayId=".",this.levels=this.audioTracks=this.subtitleTracks=null},e.onManifestLoaded=function(t,e){var r=e.contentSteering;null!==r&&(this.pathwayId=r.pathwayId,this.uri=r.uri,this.started&&this.startLoad())},e.onManifestParsed=function(t,e){this.audioTracks=e.audioTracks,this.subtitleTracks=e.subtitleTracks},e.onError=function(t,e){var r=e.errorAction;if((null==r?void 0:r.action)===Tr&&r.flags===Rr){var i=this.levels,n=this.pathwayPriority,a=this.pathwayId;if(e.context){var s=e.context,o=s.groupId,l=s.pathwayId,u=s.type;o&&i?a=this.getPathwayForGroupId(o,u,a):l&&(a=l)}a in this.penalizedPathways||(this.penalizedPathways[a]=performance.now()),!n&&i&&(n=i.reduce((function(t,e){return-1===t.indexOf(e.pathwayId)&&t.push(e.pathwayId),t}),[])),n&&n.length>1&&(this.updatePathwayPriority(n),r.resolved=this.pathwayId!==a),r.resolved||w.warn("Could not resolve "+e.details+' ("'+e.error.message+'") with content-steering for Pathway: '+a+" levels: "+(i?i.length:i)+" priorities: "+JSON.stringify(n)+" penalized: "+JSON.stringify(this.penalizedPathways))}},e.filterParsedLevels=function(t){this.levels=t;var e=this.getLevelsForPathway(this.pathwayId);if(0===e.length){var r=t[0].pathwayId;this.log("No levels found in Pathway "+this.pathwayId+'. Setting initial Pathway to "'+r+'"'),e=this.getLevelsForPathway(r),this.pathwayId=r}return e.length!==t.length?(this.log("Found "+e.length+"/"+t.length+' levels in Pathway "'+this.pathwayId+'"'),e):t},e.getLevelsForPathway=function(t){return null===this.levels?[]:this.levels.filter((function(e){return t===e.pathwayId}))},e.updatePathwayPriority=function(t){var e;this.pathwayPriority=t;var r=this.penalizedPathways,i=performance.now();Object.keys(r).forEach((function(t){i-r[t]>3e5&&delete r[t]}));for(var n=0;n<t.length;n++){var a=t[n];if(!(a in r)){if(a===this.pathwayId)return;var s=this.hls.nextLoadLevel,o=this.hls.levels[s];if((e=this.getLevelsForPathway(a)).length>0){this.log('Setting Pathway to "'+a+'"'),this.pathwayId=a,ur(e),this.hls.trigger(S.LEVELS_UPDATED,{levels:e});var l=this.hls.levels[s];o&&l&&this.levels&&(l.attrs["STABLE-VARIANT-ID"]!==o.attrs["STABLE-VARIANT-ID"]&&l.bitrate!==o.bitrate&&this.log("Unstable Pathways change from bitrate "+o.bitrate+" to "+l.bitrate),this.hls.nextLoadLevel=s);break}}}},e.getPathwayForGroupId=function(t,e,r){for(var i=this.getLevelsForPathway(r).concat(this.levels||[]),n=0;n<i.length;n++)if(e===be&&i[n].hasAudioGroup(t)||e===De&&i[n].hasSubtitleGroup(t))return i[n].pathwayId;return r},e.clonePathways=function(t){var e=this,r=this.levels;if(r){var i={},n={};t.forEach((function(t){var a=t.ID,s=t["BASE-ID"],o=t["URI-REPLACEMENT"];if(!r.some((function(t){return t.pathwayId===a}))){var l=e.getLevelsForPathway(s).map((function(t){var e=new x(t.attrs);e["PATHWAY-ID"]=a;var r=e.AUDIO&&e.AUDIO+"_clone_"+a,s=e.SUBTITLES&&e.SUBTITLES+"_clone_"+a;r&&(i[e.AUDIO]=r,e.AUDIO=r),s&&(n[e.SUBTITLES]=s,e.SUBTITLES=s);var l=Gs(t.uri,e["STABLE-VARIANT-ID"],"PER-VARIANT-URIS",o),u=new tr({attrs:e,audioCodec:t.audioCodec,bitrate:t.bitrate,height:t.height,name:t.name,url:l,videoCodec:t.videoCodec,width:t.width});if(t.audioGroups)for(var h=1;h<t.audioGroups.length;h++)u.addGroupId("audio",t.audioGroups[h]+"_clone_"+a);if(t.subtitleGroups)for(var d=1;d<t.subtitleGroups.length;d++)u.addGroupId("text",t.subtitleGroups[d]+"_clone_"+a);return u}));r.push.apply(r,l),Bs(e.audioTracks,i,o,a),Bs(e.subtitleTracks,n,o,a)}}))}},e.loadSteeringManifest=function(t){var e,r=this,i=this.hls.config,n=i.loader;this.loader&&this.loader.destroy(),this.loader=new n(i);try{e=new self.URL(t)}catch(e){return this.enabled=!1,void this.log("Failed to parse Steering Manifest URI: "+t)}if("data:"!==e.protocol){var a=0|(this.hls.bandwidthEstimate||i.abrEwmaDefaultEstimate);e.searchParams.set("_HLS_pathway",this.pathwayId),e.searchParams.set("_HLS_throughput",""+a)}var s={responseType:"json",url:e.href},o=i.steeringManifestLoadPolicy.default,l=o.errorRetry||o.timeoutRetry||{},u={loadPolicy:o,timeout:o.maxLoadTimeMs,maxRetry:l.maxNumRetry||0,retryDelay:l.retryDelayMs||0,maxRetryDelay:l.maxRetryDelayMs||0},h={onSuccess:function(t,i,n,a){r.log('Loaded steering manifest: "'+e+'"');var s=t.data;if(1===s.VERSION){r.updated=performance.now(),r.timeToLoad=s.TTL;var o=s["RELOAD-URI"],l=s["PATHWAY-CLONES"],u=s["PATHWAY-PRIORITY"];if(o)try{r.uri=new self.URL(o,e).href}catch(t){return r.enabled=!1,void r.log("Failed to parse Steering Manifest RELOAD-URI: "+o)}r.scheduleRefresh(r.uri||n.url),l&&r.clonePathways(l);var h={steeringManifest:s,url:e.toString()};r.hls.trigger(S.STEERING_MANIFEST_LOADED,h),u&&r.updatePathwayPriority(u)}else r.log("Steering VERSION "+s.VERSION+" not supported!")},onError:function(t,e,i,n){if(r.log("Error loading steering manifest: "+t.code+" "+t.text+" ("+e.url+")"),r.stopLoad(),410===t.code)return r.enabled=!1,void r.log("Steering manifest "+e.url+" no longer available");var a=1e3*r.timeToLoad;if(429!==t.code)r.scheduleRefresh(r.uri||e.url,a);else{var s=r.loader;if("function"==typeof(null==s?void 0:s.getResponseHeader)){var o=s.getResponseHeader("Retry-After");o&&(a=1e3*parseFloat(o))}r.log("Steering manifest "+e.url+" rate limited")}},onTimeout:function(t,e,i){r.log("Timeout loading steering manifest ("+e.url+")"),r.scheduleRefresh(r.uri||e.url)}};this.log("Requesting steering manifest: "+e),this.loader.load(s,u,h)},e.scheduleRefresh=function(t,e){var r=this;void 0===e&&(e=1e3*this.timeToLoad),this.clearTimeout(),this.reloadTimer=self.setTimeout((function(){var e,i=null==(e=r.hls)?void 0:e.media;!i||i.ended?r.scheduleRefresh(t,1e3*r.timeToLoad):r.loadSteeringManifest(t)}),e)},t}();function Bs(t,e,r,i){t&&Object.keys(e).forEach((function(n){var a=t.filter((function(t){return t.groupId===n})).map((function(t){var a=o({},t);return a.details=void 0,a.attrs=new x(a.attrs),a.url=a.attrs.URI=Gs(t.url,t.attrs["STABLE-RENDITION-ID"],"PER-RENDITION-URIS",r),a.groupId=a.attrs["GROUP-ID"]=e[n],a.attrs["PATHWAY-ID"]=i,a}));t.push.apply(t,a)}))}function Gs(t,e,r,i){var n,a=i.HOST,s=i.PARAMS,o=i[r];e&&(n=null==o?void 0:o[e])&&(t=n);var l=new self.URL(t);return a&&!n&&(l.host=a),s&&Object.keys(s).sort().forEach((function(t){t&&l.searchParams.set(t,s[t])})),l.href}var Ks=/^age:\s*[\d.]+\s*$/im,Hs=function(){function t(t){this.xhrSetup=void 0,this.requestTimeout=void 0,this.retryTimeout=void 0,this.retryDelay=void 0,this.config=null,this.callbacks=null,this.context=null,this.loader=null,this.stats=void 0,this.xhrSetup=t&&t.xhrSetup||null,this.stats=new M,this.retryDelay=0}var e=t.prototype;return e.destroy=function(){this.callbacks=null,this.abortInternal(),this.loader=null,this.config=null,this.context=null,this.xhrSetup=null,this.stats=null},e.abortInternal=function(){var t=this.loader;self.clearTimeout(this.requestTimeout),self.clearTimeout(this.retryTimeout),t&&(t.onreadystatechange=null,t.onprogress=null,4!==t.readyState&&(this.stats.aborted=!0,t.abort()))},e.abort=function(){var t;this.abortInternal(),null!=(t=this.callbacks)&&t.onAbort&&this.callbacks.onAbort(this.stats,this.context,this.loader)},e.load=function(t,e,r){if(this.stats.loading.start)throw new Error("Loader can only be used once.");this.stats.loading.start=self.performance.now(),this.context=t,this.config=e,this.callbacks=r,this.loadInternal()},e.loadInternal=function(){var t=this,e=this.config,r=this.context;if(e&&r){var i=this.loader=new self.XMLHttpRequest,n=this.stats;n.loading.first=0,n.loaded=0,n.aborted=!1;var a=this.xhrSetup;a?Promise.resolve().then((function(){if(!t.stats.aborted)return a(i,r.url)})).catch((function(t){return i.open("GET",r.url,!0),a(i,r.url)})).then((function(){t.stats.aborted||t.openAndSendXhr(i,r,e)})).catch((function(e){t.callbacks.onError({code:i.status,text:e.message},r,i,n)})):this.openAndSendXhr(i,r,e)}},e.openAndSendXhr=function(t,e,r){t.readyState||t.open("GET",e.url,!0);var i=e.headers,n=r.loadPolicy,a=n.maxTimeToFirstByteMs,s=n.maxLoadTimeMs;if(i)for(var o in i)t.setRequestHeader(o,i[o]);e.rangeEnd&&t.setRequestHeader("Range","bytes="+e.rangeStart+"-"+(e.rangeEnd-1)),t.onreadystatechange=this.readystatechange.bind(this),t.onprogress=this.loadprogress.bind(this),t.responseType=e.responseType,self.clearTimeout(this.requestTimeout),r.timeout=a&&y(a)?a:s,this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this),r.timeout),t.send()},e.readystatechange=function(){var t=this.context,e=this.loader,r=this.stats;if(t&&e){var i=e.readyState,n=this.config;if(!r.aborted&&i>=2&&(0===r.loading.first&&(r.loading.first=Math.max(self.performance.now(),r.loading.start),n.timeout!==n.loadPolicy.maxLoadTimeMs&&(self.clearTimeout(this.requestTimeout),n.timeout=n.loadPolicy.maxLoadTimeMs,this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this),n.loadPolicy.maxLoadTimeMs-(r.loading.first-r.loading.start)))),4===i)){self.clearTimeout(this.requestTimeout),e.onreadystatechange=null,e.onprogress=null;var a=e.status,s="text"!==e.responseType;if(a>=200&&a<300&&(s&&e.response||null!==e.responseText)){r.loading.end=Math.max(self.performance.now(),r.loading.first);var o=s?e.response:e.responseText,l="arraybuffer"===e.responseType?o.byteLength:o.length;if(r.loaded=r.total=l,r.bwEstimate=8e3*r.total/(r.loading.end-r.loading.first),!this.callbacks)return;var u=this.callbacks.onProgress;if(u&&u(r,t,o,e),!this.callbacks)return;var h={url:e.responseURL,data:o,code:a};this.callbacks.onSuccess(h,r,t,e)}else{var d=n.loadPolicy.errorRetry;gr(d,r.retry,!1,{url:t.url,data:void 0,code:a})?this.retry(d):(w.error(a+" while loading "+t.url),this.callbacks.onError({code:a,text:e.statusText},t,e,r))}}}},e.loadtimeout=function(){var t,e=null==(t=this.config)?void 0:t.loadPolicy.timeoutRetry;if(gr(e,this.stats.retry,!0))this.retry(e);else{var r;w.warn("timeout while loading "+(null==(r=this.context)?void 0:r.url));var i=this.callbacks;i&&(this.abortInternal(),i.onTimeout(this.stats,this.context,this.loader))}},e.retry=function(t){var e=this.context,r=this.stats;this.retryDelay=cr(t,r.retry),r.retry++,w.warn((status?"HTTP Status "+status:"Timeout")+" while loading "+(null==e?void 0:e.url)+", retrying "+r.retry+"/"+t.maxNumRetry+" in "+this.retryDelay+"ms"),this.abortInternal(),this.loader=null,self.clearTimeout(this.retryTimeout),this.retryTimeout=self.setTimeout(this.loadInternal.bind(this),this.retryDelay)},e.loadprogress=function(t){var e=this.stats;e.loaded=t.loaded,t.lengthComputable&&(e.total=t.total)},e.getCacheAge=function(){var t=null;if(this.loader&&Ks.test(this.loader.getAllResponseHeaders())){var e=this.loader.getResponseHeader("age");t=e?parseFloat(e):null}return t},e.getResponseHeader=function(t){return this.loader&&new RegExp("^"+t+":\\s*[\\d.]+\\s*$","im").test(this.loader.getAllResponseHeaders())?this.loader.getResponseHeader(t):null},t}(),Vs=/(\d+)-(\d+)\/(\d+)/,Ys=function(){function t(t){this.fetchSetup=void 0,this.requestTimeout=void 0,this.request=null,this.response=null,this.controller=void 0,this.context=null,this.config=null,this.callbacks=null,this.stats=void 0,this.loader=null,this.fetchSetup=t.fetchSetup||Ws,this.controller=new self.AbortController,this.stats=new M}var e=t.prototype;return e.destroy=function(){this.loader=this.callbacks=this.context=this.config=this.request=null,this.abortInternal(),this.response=null,this.fetchSetup=this.controller=this.stats=null},e.abortInternal=function(){this.controller&&!this.stats.loading.end&&(this.stats.aborted=!0,this.controller.abort())},e.abort=function(){var t;this.abortInternal(),null!=(t=this.callbacks)&&t.onAbort&&this.callbacks.onAbort(this.stats,this.context,this.response)},e.load=function(t,e,r){var i=this,n=this.stats;if(n.loading.start)throw new Error("Loader can only be used once.");n.loading.start=self.performance.now();var a=function(t,e){var r={method:"GET",mode:"cors",credentials:"same-origin",signal:e,headers:new self.Headers(o({},t.headers))};return t.rangeEnd&&r.headers.set("Range","bytes="+t.rangeStart+"-"+String(t.rangeEnd-1)),r}(t,this.controller.signal),s=r.onProgress,l="arraybuffer"===t.responseType,u=l?"byteLength":"length",h=e.loadPolicy,d=h.maxTimeToFirstByteMs,c=h.maxLoadTimeMs;this.context=t,this.config=e,this.callbacks=r,this.request=this.fetchSetup(t,a),self.clearTimeout(this.requestTimeout),e.timeout=d&&y(d)?d:c,this.requestTimeout=self.setTimeout((function(){i.abortInternal(),r.onTimeout(n,t,i.response)}),e.timeout),self.fetch(this.request).then((function(a){i.response=i.loader=a;var o=Math.max(self.performance.now(),n.loading.start);if(self.clearTimeout(i.requestTimeout),e.timeout=c,i.requestTimeout=self.setTimeout((function(){i.abortInternal(),r.onTimeout(n,t,i.response)}),c-(o-n.loading.start)),!a.ok){var u=a.status,h=a.statusText;throw new qs(h||"fetch, bad network response",u,a)}return n.loading.first=o,n.total=function(t){var e=t.get("Content-Range");if(e){var r=function(t){var e=Vs.exec(t);if(e)return parseInt(e[2])-parseInt(e[1])+1}(e);if(y(r))return r}var i=t.get("Content-Length");if(i)return parseInt(i)}(a.headers)||n.total,s&&y(e.highWaterMark)?i.loadProgressively(a,n,t,e.highWaterMark,s):l?a.arrayBuffer():"json"===t.responseType?a.json():a.text()})).then((function(a){var o=i.response;if(!o)throw new Error("loader destroyed");self.clearTimeout(i.requestTimeout),n.loading.end=Math.max(self.performance.now(),n.loading.first);var l=a[u];l&&(n.loaded=n.total=l);var h={url:o.url,data:a,code:o.status};s&&!y(e.highWaterMark)&&s(n,t,a,o),r.onSuccess(h,n,t,o)})).catch((function(e){if(self.clearTimeout(i.requestTimeout),!n.aborted){var a=e&&e.code||0,s=e?e.message:null;r.onError({code:a,text:s},t,e?e.details:null,n)}}))},e.getCacheAge=function(){var t=null;if(this.response){var e=this.response.headers.get("age");t=e?parseFloat(e):null}return t},e.getResponseHeader=function(t){return this.response?this.response.headers.get(t):null},e.loadProgressively=function(t,e,r,i,n){void 0===i&&(i=0);var a=new ki,s=t.body.getReader();return function o(){return s.read().then((function(s){if(s.done)return a.dataLength&&n(e,r,a.flush(),t),Promise.resolve(new ArrayBuffer(0));var l=s.value,u=l.length;return e.loaded+=u,u<i||a.dataLength?(a.push(l),a.dataLength>=i&&n(e,r,a.flush(),t)):n(e,r,l,t),o()})).catch((function(){return Promise.reject()}))}()},t}();function Ws(t,e){return new self.Request(t.url,e)}var js,qs=function(t){function e(e,r,i){var n;return(n=t.call(this,e)||this).code=void 0,n.details=void 0,n.code=r,n.details=i,n}return l(e,t),e}(c(Error)),Xs=/\s/,zs=i(i({autoStartLoad:!0,startPosition:-1,defaultAudioCodec:void 0,debug:!1,capLevelOnFPSDrop:!1,capLevelToPlayerSize:!1,ignoreDevicePixelRatio:!1,preferManagedMediaSource:!0,initialLiveManifestSize:1,maxBufferLength:30,backBufferLength:1/0,frontBufferFlushThreshold:1/0,maxBufferSize:6e7,maxBufferHole:.1,highBufferWatchdogPeriod:2,nudgeOffset:.1,nudgeMaxRetry:3,maxFragLookUpTolerance:.25,liveSyncDurationCount:3,liveMaxLatencyDurationCount:1/0,liveSyncDuration:void 0,liveMaxLatencyDuration:void 0,maxLiveSyncPlaybackRate:1,liveDurationInfinity:!1,liveBackBufferLength:null,maxMaxBufferLength:600,enableWorker:!0,workerPath:null,enableSoftwareAES:!0,startLevel:void 0,startFragPrefetch:!1,fpsDroppedMonitoringPeriod:5e3,fpsDroppedMonitoringThreshold:.2,appendErrorMaxRetry:3,loader:Hs,fLoader:void 0,pLoader:void 0,xhrSetup:void 0,licenseXhrSetup:void 0,licenseResponseCallback:void 0,abrController:Br,bufferController:Qn,capLevelController:qa,errorController:br,fpsController:Xa,stretchShortVideoTrack:!1,maxAudioFramesDrift:1,forceKeyFrameOnDiscontinuity:!0,abrEwmaFastLive:3,abrEwmaSlowLive:9,abrEwmaFastVoD:3,abrEwmaSlowVoD:9,abrEwmaDefaultEstimate:5e5,abrEwmaDefaultEstimateMax:5e6,abrBandWidthFactor:.95,abrBandWidthUpFactor:.7,abrMaxWithRealBitrate:!1,maxStarvationDelay:4,maxLoadingDelay:4,minAutoBitrate:0,emeEnabled:!1,widevineLicenseUrl:void 0,drmSystems:{},drmSystemOptions:{},requestMediaKeySystemAccessFunc:it,testBandwidth:!0,progressive:!1,lowLatencyMode:!0,cmcd:void 0,enableDateRangeMetadataCues:!0,enableEmsgMetadataCues:!0,enableID3MetadataCues:!0,useMediaCapabilities:!0,certLoadPolicy:{default:{maxTimeToFirstByteMs:8e3,maxLoadTimeMs:2e4,timeoutRetry:null,errorRetry:null}},keyLoadPolicy:{default:{maxTimeToFirstByteMs:8e3,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:1,retryDelayMs:1e3,maxRetryDelayMs:2e4,backoff:"linear"},errorRetry:{maxNumRetry:8,retryDelayMs:1e3,maxRetryDelayMs:2e4,backoff:"linear"}}},manifestLoadPolicy:{default:{maxTimeToFirstByteMs:1/0,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:2,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:1,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},playlistLoadPolicy:{default:{maxTimeToFirstByteMs:1e4,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:2,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:2,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},fragLoadPolicy:{default:{maxTimeToFirstByteMs:1e4,maxLoadTimeMs:12e4,timeoutRetry:{maxNumRetry:4,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:6,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},steeringManifestLoadPolicy:{default:{maxTimeToFirstByteMs:1e4,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:2,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:1,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},manifestLoadingTimeOut:1e4,manifestLoadingMaxRetry:1,manifestLoadingRetryDelay:1e3,manifestLoadingMaxRetryTimeout:64e3,levelLoadingTimeOut:1e4,levelLoadingMaxRetry:4,levelLoadingRetryDelay:1e3,levelLoadingMaxRetryTimeout:64e3,fragLoadingTimeOut:2e4,fragLoadingMaxRetry:6,fragLoadingRetryDelay:1e3,fragLoadingMaxRetryTimeout:64e3},{cueHandler:{newCue:function(t,e,r,i){for(var n,a,s,o,l,u=[],h=self.VTTCue||self.TextTrackCue,d=0;d<i.rows.length;d++)if(s=!0,o=0,l="",!(n=i.rows[d]).isEmpty()){for(var c,f=0;f<n.chars.length;f++)Xs.test(n.chars[f].uchar)&&s?o++:(l+=n.chars[f].uchar,s=!1);n.cueStartTime=e,e===r&&(r+=1e-4),o>=16?o--:o++;var g=ba(l.trim()),v=_a(e,r,g);null!=t&&null!=(c=t.cues)&&c.getCueById(v)||((a=new h(e,r,g)).id=v,a.line=d+1,a.align="left",a.position=10+Math.min(80,10*Math.floor(8*o/32)),u.push(a))}return t&&u.length&&(u.sort((function(t,e){return"auto"===t.line||"auto"===e.line?0:t.line>8&&e.line>8?e.line-t.line:t.line-e.line})),u.forEach((function(e){return Me(t,e)}))),u}},enableWebVTT:!0,enableIMSC1:!0,enableCEA708Captions:!0,captionsTextTrack1Label:"English",captionsTextTrack1LanguageCode:"en",captionsTextTrack2Label:"Spanish",captionsTextTrack2LanguageCode:"es",captionsTextTrack3Label:"Unknown CC",captionsTextTrack3LanguageCode:"",captionsTextTrack4Label:"Unknown CC",captionsTextTrack4LanguageCode:"",renderTextTracksNatively:!0}),{},{subtitleStreamController:Wn,subtitleTrackController:qn,timelineController:Ya,audioStreamController:Vn,audioTrackController:Yn,emeController:Qa,cmcdController:Ns,contentSteeringController:Us});function Qs(t){return t&&"object"==typeof t?Array.isArray(t)?t.map(Qs):Object.keys(t).reduce((function(e,r){return e[r]=Qs(t[r]),e}),{}):t}function Js(t){var e=t.loader;e!==Ys&&e!==Hs?(w.log("[config]: Custom loader detected, cannot enable progressive streaming"),t.progressive=!1):function(){if(self.fetch&&self.AbortController&&self.ReadableStream&&self.Request)try{return new self.ReadableStream({}),!0}catch(t){}return!1}()&&(t.loader=Ys,t.progressive=!0,t.enableSoftwareAES=!0,w.log("[config]: Progressive streaming enabled, using FetchLoader"))}var $s=function(t){function e(e,r){var i;return(i=t.call(this,e,"[level-controller]")||this)._levels=[],i._firstLevel=-1,i._maxAutoLevel=-1,i._startLevel=void 0,i.currentLevel=null,i.currentLevelIndex=-1,i.manualLevelIndex=-1,i.steering=void 0,i.onParsedComplete=void 0,i.steering=r,i._registerListeners(),i}l(e,t);var r=e.prototype;return r._registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this),t.on(S.ERROR,this.onError,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this),t.off(S.ERROR,this.onError,this)},r.destroy=function(){this._unregisterListeners(),this.steering=null,this.resetLevels(),t.prototype.destroy.call(this)},r.stopLoad=function(){this._levels.forEach((function(t){t.loadError=0,t.fragmentError=0})),t.prototype.stopLoad.call(this)},r.resetLevels=function(){this._startLevel=void 0,this.manualLevelIndex=-1,this.currentLevelIndex=-1,this.currentLevel=null,this._levels=[],this._maxAutoLevel=-1},r.onManifestLoading=function(t,e){this.resetLevels()},r.onManifestLoaded=function(t,e){var r=this.hls.config.preferManagedMediaSource,i=[],n={},a={},s=!1,o=!1,l=!1;e.levels.forEach((function(t){var e,u,h=t.attrs,d=t.audioCodec,c=t.videoCodec;-1!==(null==(e=d)?void 0:e.indexOf("mp4a.40.34"))&&(js||(js=/chrome|firefox/i.test(navigator.userAgent)),js&&(t.audioCodec=d=void 0)),d&&(t.audioCodec=d=ue(d,r)),0===(null==(u=c)?void 0:u.indexOf("avc1"))&&(c=t.videoCodec=function(t){var e=t.split(".");if(e.length>2){var r=e.shift()+".";return(r+=parseInt(e.shift()).toString(16))+("000"+parseInt(e.shift()).toString(16)).slice(-4)}return t}(c));var f=t.width,g=t.height,v=t.unknownCodecs;if(s||(s=!(!f||!g)),o||(o=!!c),l||(l=!!d),!(null!=v&&v.length||d&&!re(d,"audio",r)||c&&!re(c,"video",r))){var m=h.CODECS,p=h["FRAME-RATE"],y=h["HDCP-LEVEL"],E=h["PATHWAY-ID"],T=h.RESOLUTION,S=h["VIDEO-RANGE"],L=(E||".")+"-"+t.bitrate+"-"+T+"-"+p+"-"+m+"-"+S+"-"+y;if(n[L])if(n[L].uri===t.url||t.attrs["PATHWAY-ID"])n[L].addGroupId("audio",h.AUDIO),n[L].addGroupId("text",h.SUBTITLES);else{var A=a[L]+=1;t.attrs["PATHWAY-ID"]=new Array(A+1).join(".");var R=new tr(t);n[L]=R,i.push(R)}else{var k=new tr(t);n[L]=k,a[L]=1,i.push(k)}}})),this.filterAndSortMediaOptions(i,e,s,o,l)},r.filterAndSortMediaOptions=function(t,e,r,i,n){var a=this,s=[],o=[],l=t;if((r||i)&&n&&(l=l.filter((function(t){var e,r=t.videoCodec,i=t.videoRange,n=t.width,a=t.height;return(!!r||!(!n||!a))&&!!(e=i)&&ze.indexOf(e)>-1}))),0!==l.length){if(e.audioTracks){var u=this.hls.config.preferManagedMediaSource;Zs(s=e.audioTracks.filter((function(t){return!t.audioCodec||re(t.audioCodec,"audio",u)})))}e.subtitles&&Zs(o=e.subtitles);var h=l.slice(0);l.sort((function(t,e){if(t.attrs["HDCP-LEVEL"]!==e.attrs["HDCP-LEVEL"])return(t.attrs["HDCP-LEVEL"]||"")>(e.attrs["HDCP-LEVEL"]||"")?1:-1;if(r&&t.height!==e.height)return t.height-e.height;if(t.frameRate!==e.frameRate)return t.frameRate-e.frameRate;if(t.videoRange!==e.videoRange)return ze.indexOf(t.videoRange)-ze.indexOf(e.videoRange);if(t.videoCodec!==e.videoCodec){var i=ae(t.videoCodec),n=ae(e.videoCodec);if(i!==n)return n-i}if(t.uri===e.uri&&t.codecSet!==e.codecSet){var a=se(t.codecSet),s=se(e.codecSet);if(a!==s)return s-a}return t.bitrate!==e.bitrate?t.bitrate-e.bitrate:0}));var d=h[0];if(this.steering&&(l=this.steering.filterParsedLevels(l)).length!==h.length)for(var c=0;c<h.length;c++)if(h[c].pathwayId===l[0].pathwayId){d=h[c];break}this._levels=l;for(var f=0;f<l.length;f++)if(l[f]===d){var g;this._firstLevel=f;var v=d.bitrate,m=this.hls.bandwidthEstimate;if(this.log("manifest loaded, "+l.length+" level(s) found, first bitrate: "+v),void 0===(null==(g=this.hls.userConfig)?void 0:g.abrEwmaDefaultEstimate)){var p=Math.min(v,this.hls.config.abrEwmaDefaultEstimateMax);p>m&&m===zs.abrEwmaDefaultEstimate&&(this.hls.bandwidthEstimate=p)}break}var y=n&&!i,E={levels:l,audioTracks:s,subtitleTracks:o,sessionData:e.sessionData,sessionKeys:e.sessionKeys,firstLevel:this._firstLevel,stats:e.stats,audio:n,video:i,altAudio:!y&&s.some((function(t){return!!t.url}))};this.hls.trigger(S.MANIFEST_PARSED,E),(this.hls.config.autoStartLoad||this.hls.forceStartLoad)&&this.hls.startLoad(this.hls.config.startPosition)}else Promise.resolve().then((function(){if(a.hls){e.levels.length&&a.warn("One or more CODECS in variant not supported: "+JSON.stringify(e.levels[0].attrs));var t=new Error("no level with compatible codecs found in manifest");a.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.MANIFEST_INCOMPATIBLE_CODECS_ERROR,fatal:!0,url:e.url,error:t,reason:t.message})}}))},r.onError=function(t,e){!e.fatal&&e.context&&e.context.type===ke&&e.context.level===this.level&&this.checkRetry(e)},r.onFragBuffered=function(t,e){var r=e.frag;if(void 0!==r&&r.type===Ie){var i=r.elementaryStreams;if(!Object.keys(i).some((function(t){return!!i[t]})))return;var n=this._levels[r.level];null!=n&&n.loadError&&(this.log("Resetting level error count of "+n.loadError+" on frag buffered"),n.loadError=0)}},r.onLevelLoaded=function(t,e){var r,i,n=e.level,a=e.details,s=this._levels[n];if(!s)return this.warn("Invalid level index "+n),void(null!=(i=e.deliveryDirectives)&&i.skip&&(a.deltaUpdateFailed=!0));n===this.currentLevelIndex?(0===s.fragmentError&&(s.loadError=0),this.playlistLoaded(n,e,s.details)):null!=(r=e.deliveryDirectives)&&r.skip&&(a.deltaUpdateFailed=!0)},r.loadPlaylist=function(e){t.prototype.loadPlaylist.call(this);var r=this.currentLevelIndex,i=this.currentLevel;if(i&&this.shouldLoadPlaylist(i)){var n=i.uri;if(e)try{n=e.addDirectives(n)}catch(t){this.warn("Could not construct new URL with HLS Delivery Directives: "+t)}var a=i.attrs["PATHWAY-ID"];this.log("Loading level index "+r+(void 0!==(null==e?void 0:e.msn)?" at sn "+e.msn+" part "+e.part:"")+" with"+(a?" Pathway "+a:"")+" "+n),this.clearTimer(),this.hls.trigger(S.LEVEL_LOADING,{url:n,level:r,pathwayId:i.attrs["PATHWAY-ID"],id:0,deliveryDirectives:e||null})}},r.removeLevel=function(t){var e,r=this,i=this._levels.filter((function(e,i){return i!==t||(r.steering&&r.steering.removeLevel(e),e===r.currentLevel&&(r.currentLevel=null,r.currentLevelIndex=-1,e.details&&e.details.fragments.forEach((function(t){return t.level=-1}))),!1)}));ur(i),this._levels=i,this.currentLevelIndex>-1&&null!=(e=this.currentLevel)&&e.details&&(this.currentLevelIndex=this.currentLevel.details.fragments[0].level),this.hls.trigger(S.LEVELS_UPDATED,{levels:i})},r.onLevelsUpdated=function(t,e){var r=e.levels;this._levels=r},r.checkMaxAutoUpdated=function(){var t=this.hls,e=t.autoLevelCapping,r=t.maxAutoLevel,i=t.maxHdcpLevel;this._maxAutoLevel!==r&&(this._maxAutoLevel=r,this.hls.trigger(S.MAX_AUTO_LEVEL_UPDATED,{autoLevelCapping:e,levels:this.levels,maxAutoLevel:r,minAutoLevel:this.hls.minAutoLevel,maxHdcpLevel:i}))},s(e,[{key:"levels",get:function(){return 0===this._levels.length?null:this._levels}},{key:"level",get:function(){return this.currentLevelIndex},set:function(t){var e=this._levels;if(0!==e.length){if(t<0||t>=e.length){var r=new Error("invalid level idx"),i=t<0;if(this.hls.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.LEVEL_SWITCH_ERROR,level:t,fatal:i,error:r,reason:r.message}),i)return;t=Math.min(t,e.length-1)}var n=this.currentLevelIndex,a=this.currentLevel,s=a?a.attrs["PATHWAY-ID"]:void 0,o=e[t],l=o.attrs["PATHWAY-ID"];if(this.currentLevelIndex=t,this.currentLevel=o,n!==t||!o.details||!a||s!==l){this.log("Switching to level "+t+" ("+(o.height?o.height+"p ":"")+(o.videoRange?o.videoRange+" ":"")+(o.codecSet?o.codecSet+" ":"")+"@"+o.bitrate+")"+(l?" with Pathway "+l:"")+" from level "+n+(s?" with Pathway "+s:""));var u={level:t,attrs:o.attrs,details:o.details,bitrate:o.bitrate,averageBitrate:o.averageBitrate,maxBitrate:o.maxBitrate,realBitrate:o.realBitrate,width:o.width,height:o.height,codecSet:o.codecSet,audioCodec:o.audioCodec,videoCodec:o.videoCodec,audioGroups:o.audioGroups,subtitleGroups:o.subtitleGroups,loaded:o.loaded,loadError:o.loadError,fragmentError:o.fragmentError,name:o.name,id:o.id,uri:o.uri,url:o.url,urlId:0,audioGroupIds:o.audioGroupIds,textGroupIds:o.textGroupIds};this.hls.trigger(S.LEVEL_SWITCHING,u);var h=o.details;if(!h||h.live){var d=this.switchParams(o.uri,null==a?void 0:a.details);this.loadPlaylist(d)}}}}},{key:"manualLevel",get:function(){return this.manualLevelIndex},set:function(t){this.manualLevelIndex=t,void 0===this._startLevel&&(this._startLevel=t),-1!==t&&(this.level=t)}},{key:"firstLevel",get:function(){return this._firstLevel},set:function(t){this._firstLevel=t}},{key:"startLevel",get:function(){if(void 0===this._startLevel){var t=this.hls.config.startLevel;return void 0!==t?t:this.hls.firstAutoLevel}return this._startLevel},set:function(t){this._startLevel=t}},{key:"nextLoadLevel",get:function(){return-1!==this.manualLevelIndex?this.manualLevelIndex:this.hls.nextAutoLevel},set:function(t){this.level=t,-1===this.manualLevelIndex&&(this.hls.nextAutoLevel=t)}}]),e}(Dr);function Zs(t){var e={};t.forEach((function(t){var r=t.groupId||"";t.id=e[r]=e[r]||0,e[r]++}))}var to=function(){function t(t){this.config=void 0,this.keyUriToKeyInfo={},this.emeController=null,this.config=t}var e=t.prototype;return e.abort=function(t){for(var e in this.keyUriToKeyInfo){var r=this.keyUriToKeyInfo[e].loader;if(r){var i;if(t&&t!==(null==(i=r.context)?void 0:i.frag.type))return;r.abort()}}},e.detach=function(){for(var t in this.keyUriToKeyInfo){var e=this.keyUriToKeyInfo[t];(e.mediaKeySessionContext||e.decryptdata.isCommonEncryption)&&delete this.keyUriToKeyInfo[t]}},e.destroy=function(){for(var t in this.detach(),this.keyUriToKeyInfo){var e=this.keyUriToKeyInfo[t].loader;e&&e.destroy()}this.keyUriToKeyInfo={}},e.createKeyLoadError=function(t,e,r,i,n){return void 0===e&&(e=A.KEY_LOAD_ERROR),new si({type:L.NETWORK_ERROR,details:e,fatal:!1,frag:t,response:n,error:r,networkDetails:i})},e.loadClear=function(t,e){var r=this;if(this.emeController&&this.config.emeEnabled)for(var i=t.sn,n=t.cc,a=function(){var t=e[s];if(n<=t.cc&&("initSegment"===i||"initSegment"===t.sn||i<t.sn))return r.emeController.selectKeySystemFormat(t).then((function(e){t.setKeyFormat(e)})),1},s=0;s<e.length&&!a();s++);},e.load=function(t){var e=this;return!t.decryptdata&&t.encrypted&&this.emeController?this.emeController.selectKeySystemFormat(t).then((function(r){return e.loadInternal(t,r)})):this.loadInternal(t)},e.loadInternal=function(t,e){var r,i;e&&t.setKeyFormat(e);var n=t.decryptdata;if(!n){var a=new Error(e?"Expected frag.decryptdata to be defined after setting format "+e:"Missing decryption data on fragment in onKeyLoading");return Promise.reject(this.createKeyLoadError(t,A.KEY_LOAD_ERROR,a))}var s=n.uri;if(!s)return Promise.reject(this.createKeyLoadError(t,A.KEY_LOAD_ERROR,new Error('Invalid key URI: "'+s+'"')));var o,l=this.keyUriToKeyInfo[s];if(null!=(r=l)&&r.decryptdata.key)return n.key=l.decryptdata.key,Promise.resolve({frag:t,keyInfo:l});if(null!=(i=l)&&i.keyLoadPromise)switch(null==(o=l.mediaKeySessionContext)?void 0:o.keyStatus){case void 0:case"status-pending":case"usable":case"usable-in-future":return l.keyLoadPromise.then((function(e){return n.key=e.keyInfo.decryptdata.key,{frag:t,keyInfo:l}}))}switch(l=this.keyUriToKeyInfo[s]={decryptdata:n,keyLoadPromise:null,loader:null,mediaKeySessionContext:null},n.method){case"ISO-23001-7":case"SAMPLE-AES":case"SAMPLE-AES-CENC":case"SAMPLE-AES-CTR":return"identity"===n.keyFormat?this.loadKeyHTTP(l,t):this.loadKeyEME(l,t);case"AES-128":return this.loadKeyHTTP(l,t);default:return Promise.reject(this.createKeyLoadError(t,A.KEY_LOAD_ERROR,new Error('Key supplied with unsupported METHOD: "'+n.method+'"')))}},e.loadKeyEME=function(t,e){var r={frag:e,keyInfo:t};if(this.emeController&&this.config.emeEnabled){var i=this.emeController.loadKey(r);if(i)return(t.keyLoadPromise=i.then((function(e){return t.mediaKeySessionContext=e,r}))).catch((function(e){throw t.keyLoadPromise=null,e}))}return Promise.resolve(r)},e.loadKeyHTTP=function(t,e){var r=this,n=this.config,a=new(0,n.loader)(n);return e.keyLoader=t.loader=a,t.keyLoadPromise=new Promise((function(s,o){var l={keyInfo:t,frag:e,responseType:"arraybuffer",url:t.decryptdata.uri},u=n.keyLoadPolicy.default,h={loadPolicy:u,timeout:u.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0},d={onSuccess:function(t,e,i,n){var a=i.frag,l=i.keyInfo,u=i.url;if(!a.decryptdata||l!==r.keyUriToKeyInfo[u])return o(r.createKeyLoadError(a,A.KEY_LOAD_ERROR,new Error("after key load, decryptdata unset or changed"),n));l.decryptdata.key=a.decryptdata.key=new Uint8Array(t.data),a.keyLoader=null,l.loader=null,s({frag:a,keyInfo:l})},onError:function(t,n,a,s){r.resetLoader(n),o(r.createKeyLoadError(e,A.KEY_LOAD_ERROR,new Error("HTTP Error "+t.code+" loading key "+t.text),a,i({url:l.url,data:void 0},t)))},onTimeout:function(t,i,n){r.resetLoader(i),o(r.createKeyLoadError(e,A.KEY_LOAD_TIMEOUT,new Error("key loading timed out"),n))},onAbort:function(t,i,n){r.resetLoader(i),o(r.createKeyLoadError(e,A.INTERNAL_ABORTED,new Error("key loading aborted"),n))}};a.load(l,h,d)}))},e.resetLoader=function(t){var e=t.frag,r=t.keyInfo,i=t.url,n=r.loader;e.keyLoader===n&&(e.keyLoader=null,r.loader=null),delete this.keyUriToKeyInfo[i],n&&n.destroy()},t}();function eo(){return self.SourceBuffer||self.WebKitSourceBuffer}function ro(){if(!te())return!1;var t=eo();return!t||t.prototype&&"function"==typeof t.prototype.appendBuffer&&"function"==typeof t.prototype.remove}var io=function(){function t(t,e,r,i){this.config=void 0,this.media=null,this.fragmentTracker=void 0,this.hls=void 0,this.nudgeRetry=0,this.stallReported=!1,this.stalled=null,this.moved=!1,this.seeking=!1,this.config=t,this.media=e,this.fragmentTracker=r,this.hls=i}var e=t.prototype;return e.destroy=function(){this.media=null,this.hls=this.fragmentTracker=null},e.poll=function(t,e){var r=this.config,i=this.media,n=this.stalled;if(null!==i){var a=i.currentTime,s=i.seeking,o=this.seeking&&!s,l=!this.seeking&&s;if(this.seeking=s,a===t)if(l||o)this.stalled=null;else if(i.paused&&!s||i.ended||0===i.playbackRate||!zr.getBuffered(i).length)this.nudgeRetry=0;else{var u=zr.bufferInfo(i,a,0),h=u.nextStart||0;if(s){var d=u.len>2,c=!h||e&&e.start<=a||h-a>2&&!this.fragmentTracker.getPartialFragment(a);if(d||c)return;this.moved=!1}if(!this.moved&&null!==this.stalled){var f;if(!(u.len>0||h))return;var g=Math.max(h,u.start||0)-a,v=this.hls.levels?this.hls.levels[this.hls.currentLevel]:null,m=(null==v||null==(f=v.details)?void 0:f.live)?2*v.details.targetduration:2,p=this.fragmentTracker.getPartialFragment(a);if(g>0&&(g<=m||p))return void(i.paused||this._trySkipBufferHole(p))}var y=self.performance.now();if(null!==n){var E=y-n;if(s||!(E>=250)||(this._reportStall(u),this.media)){var T=zr.bufferInfo(i,a,r.maxBufferHole);this._tryFixBufferStall(T,E)}}else this.stalled=y}else if(this.moved=!0,s||(this.nudgeRetry=0),null!==n){if(this.stallReported){var S=self.performance.now()-n;w.warn("playback not stuck anymore @"+a+", after "+Math.round(S)+"ms"),this.stallReported=!1}this.stalled=null}}},e._tryFixBufferStall=function(t,e){var r=this.config,i=this.fragmentTracker,n=this.media;if(null!==n){var a=n.currentTime,s=i.getPartialFragment(a);if(s&&(this._trySkipBufferHole(s)||!this.media))return;(t.len>r.maxBufferHole||t.nextStart&&t.nextStart-a<r.maxBufferHole)&&e>1e3*r.highBufferWatchdogPeriod&&(w.warn("Trying to nudge playhead over buffer-hole"),this.stalled=null,this._tryNudgeBuffer())}},e._reportStall=function(t){var e=this.hls,r=this.media;if(!this.stallReported&&r){this.stallReported=!0;var i=new Error("Playback stalling at @"+r.currentTime+" due to low buffer ("+JSON.stringify(t)+")");w.warn(i.message),e.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_STALLED_ERROR,fatal:!1,error:i,buffer:t.len})}},e._trySkipBufferHole=function(t){var e=this.config,r=this.hls,i=this.media;if(null===i)return 0;var n=i.currentTime,a=zr.bufferInfo(i,n,0),s=n<a.start?a.start:a.nextStart;if(s){var o=a.len<=e.maxBufferHole,l=a.len>0&&a.len<1&&i.readyState<3,u=s-n;if(u>0&&(o||l)){if(u>e.maxBufferHole){var h=this.fragmentTracker,d=!1;if(0===n){var c=h.getAppendedFrag(0,Ie);c&&s<c.end&&(d=!0)}if(!d){var f=t||h.getAppendedFrag(n,Ie);if(f){for(var g=!1,v=f.end;v<s;){var m=h.getPartialFragment(v);if(!m){g=!0;break}v+=m.duration}if(g)return 0}}}var p=Math.max(s+.05,n+.1);if(w.warn("skipping hole, adjusting currentTime from "+n+" to "+p),this.moved=!0,this.stalled=null,i.currentTime=p,t&&!t.gap){var y=new Error("fragment loaded with buffer holes, seeking from "+n+" to "+p);r.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_SEEK_OVER_HOLE,fatal:!1,error:y,reason:y.message,frag:t})}return p}}return 0},e._tryNudgeBuffer=function(){var t=this.config,e=this.hls,r=this.media,i=this.nudgeRetry;if(null!==r){var n=r.currentTime;if(this.nudgeRetry++,i<t.nudgeMaxRetry){var a=n+(i+1)*t.nudgeOffset,s=new Error("Nudging 'currentTime' from "+n+" to "+a);w.warn(s.message),r.currentTime=a,e.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_NUDGE_ON_STALL,error:s,fatal:!1})}else{var o=new Error("Playhead still not moving while enough data buffered @"+n+" after "+t.nudgeMaxRetry+" nudges");w.error(o.message),e.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_STALLED_ERROR,error:o,fatal:!0})}}},t}(),no=function(t){function e(e,r,i){var n;return(n=t.call(this,e,r,i,"[stream-controller]",Ie)||this).audioCodecSwap=!1,n.gapController=null,n.level=-1,n._forceStartLoad=!1,n.altAudio=!1,n.audioOnly=!1,n.fragPlaying=null,n.onvplaying=null,n.onvseeked=null,n.fragLastKbps=0,n.couldBacktrack=!1,n.backtrackFragment=null,n.audioCodecSwitch=!1,n.videoBuffer=null,n._registerListeners(),n}l(e,t);var r=e.prototype;return r._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.FRAG_LOAD_EMERGENCY_ABORTED,this.onFragLoadEmergencyAborted,this),t.on(S.ERROR,this.onError,this),t.on(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.on(S.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),t.on(S.BUFFER_CREATED,this.onBufferCreated,this),t.on(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.FRAG_LOAD_EMERGENCY_ABORTED,this.onFragLoadEmergencyAborted,this),t.off(S.ERROR,this.onError,this),t.off(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.off(S.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),t.off(S.BUFFER_CREATED,this.onBufferCreated,this),t.off(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this)},r.onHandlerDestroying=function(){this._unregisterListeners(),t.prototype.onHandlerDestroying.call(this)},r.startLoad=function(t){if(this.levels){var e=this.lastCurrentTime,r=this.hls;if(this.stopLoad(),this.setInterval(100),this.level=-1,!this.startFragRequested){var i=r.startLevel;-1===i&&(r.config.testBandwidth&&this.levels.length>1?(i=0,this.bitrateTest=!0):i=r.firstAutoLevel),this.level=r.nextLoadLevel=i,this.loadedmetadata=!1}e>0&&-1===t&&(this.log("Override startPosition with lastCurrentTime @"+e.toFixed(3)),t=e),this.state=fi,this.nextLoadPosition=this.startPosition=this.lastCurrentTime=t,this.tick()}else this._forceStartLoad=!0,this.state=ci},r.stopLoad=function(){this._forceStartLoad=!1,t.prototype.stopLoad.call(this)},r.doTick=function(){switch(this.state){case Ai:var t=this.levels,e=this.level,r=null==t?void 0:t[e],i=null==r?void 0:r.details;if(i&&(!i.live||this.levelLastLoaded===r)){if(this.waitForCdnTuneIn(i))break;this.state=fi;break}if(this.hls.nextLoadLevel!==this.level){this.state=fi;break}break;case mi:var n,a=self.performance.now(),s=this.retryDate;if(!s||a>=s||null!=(n=this.media)&&n.seeking){var o=this.levels,l=this.level,u=null==o?void 0:o[l];this.resetStartWhenNotLoaded(u||null),this.state=fi}}this.state===fi&&this.doTickIdle(),this.onTickEnd()},r.onTickEnd=function(){t.prototype.onTickEnd.call(this),this.checkBuffer(),this.checkFragmentChanged()},r.doTickIdle=function(){var t=this.hls,e=this.levelLastLoaded,r=this.levels,i=this.media,n=t.config,a=t.nextLoadLevel;if(null!==e&&(i||!this.startFragRequested&&n.startFragPrefetch)&&(!this.altAudio||!this.audioOnly)&&null!=r&&r[a]){var s=r[a],o=this.getMainFwdBufferInfo();if(null!==o){var l=this.getLevelDetails();if(l&&this._streamEnded(o,l)){var u={};return this.altAudio&&(u.type="video"),this.hls.trigger(S.BUFFER_EOS,u),void(this.state=Ti)}t.loadLevel!==a&&-1===t.manualLevel&&this.log("Adapting to level "+a+" from level "+this.level),this.level=t.nextLoadLevel=a;var h=s.details;if(!h||this.state===Ai||h.live&&this.levelLastLoaded!==s)return this.level=a,void(this.state=Ai);var d=o.len,c=this.getMaxBufferLength(s.maxBitrate);if(!(d>=c)){this.backtrackFragment&&this.backtrackFragment.start>o.end&&(this.backtrackFragment=null);var f=this.backtrackFragment?this.backtrackFragment.start:o.end,g=this.getNextFragment(f,h);if(this.couldBacktrack&&!this.fragPrevious&&g&&"initSegment"!==g.sn&&this.fragmentTracker.getState(g)!==Yr){var v,m=(null!=(v=this.backtrackFragment)?v:g).sn-h.startSN,p=h.fragments[m-1];p&&g.cc===p.cc&&(g=p,this.fragmentTracker.removeFragment(p))}else this.backtrackFragment&&o.len&&(this.backtrackFragment=null);if(g&&this.isLoopLoading(g,f)){if(!g.gap){var y=this.audioOnly&&!this.altAudio?O:N,E=(y===N?this.videoBuffer:this.mediaBuffer)||this.media;E&&this.afterBufferFlushed(E,y,Ie)}g=this.getNextFragmentLoopLoading(g,h,o,Ie,c)}g&&(!g.initSegment||g.initSegment.data||this.bitrateTest||(g=g.initSegment),this.loadFragment(g,s,f))}}}},r.loadFragment=function(e,r,i){var n=this.fragmentTracker.getState(e);this.fragCurrent=e,n===Kr||n===Vr?"initSegment"===e.sn?this._loadInitSegment(e,r):this.bitrateTest?(this.log("Fragment "+e.sn+" of level "+e.level+" is being downloaded to test bitrate and will not be buffered"),this._loadBitrateTestFrag(e,r)):(this.startFragRequested=!0,t.prototype.loadFragment.call(this,e,r,i)):this.clearTrackerIfNeeded(e)},r.getBufferedFrag=function(t){return this.fragmentTracker.getBufferedFrag(t,Ie)},r.followingBufferedFrag=function(t){return t?this.getBufferedFrag(t.end+.5):null},r.immediateLevelSwitch=function(){this.abortCurrentFrag(),this.flushMainBuffer(0,Number.POSITIVE_INFINITY)},r.nextLevelSwitch=function(){var t=this.levels,e=this.media;if(null!=e&&e.readyState){var r,i=this.getAppendedFrag(e.currentTime);i&&i.start>1&&this.flushMainBuffer(0,i.start-1);var n=this.getLevelDetails();if(null!=n&&n.live){var a=this.getMainFwdBufferInfo();if(!a||a.len<2*n.targetduration)return}if(!e.paused&&t){var s=t[this.hls.nextLoadLevel],o=this.fragLastKbps;r=o&&this.fragCurrent?this.fragCurrent.duration*s.maxBitrate/(1e3*o)+1:0}else r=0;var l=this.getBufferedFrag(e.currentTime+r);if(l){var u=this.followingBufferedFrag(l);if(u){this.abortCurrentFrag();var h=u.maxStartPTS?u.maxStartPTS:u.start,d=u.duration,c=Math.max(l.end,h+Math.min(Math.max(d-this.config.maxFragLookUpTolerance,d*(this.couldBacktrack?.5:.125)),d*(this.couldBacktrack?.75:.25)));this.flushMainBuffer(c,Number.POSITIVE_INFINITY)}}}},r.abortCurrentFrag=function(){var t=this.fragCurrent;switch(this.fragCurrent=null,this.backtrackFragment=null,t&&(t.abortRequests(),this.fragmentTracker.removeFragment(t)),this.state){case gi:case vi:case mi:case yi:case Ei:this.state=fi}this.nextLoadPosition=this.getLoadPosition()},r.flushMainBuffer=function(e,r){t.prototype.flushMainBuffer.call(this,e,r,this.altAudio?"video":null)},r.onMediaAttached=function(e,r){t.prototype.onMediaAttached.call(this,e,r);var i=r.media;this.onvplaying=this.onMediaPlaying.bind(this),this.onvseeked=this.onMediaSeeked.bind(this),i.addEventListener("playing",this.onvplaying),i.addEventListener("seeked",this.onvseeked),this.gapController=new io(this.config,i,this.fragmentTracker,this.hls)},r.onMediaDetaching=function(){var e=this.media;e&&this.onvplaying&&this.onvseeked&&(e.removeEventListener("playing",this.onvplaying),e.removeEventListener("seeked",this.onvseeked),this.onvplaying=this.onvseeked=null,this.videoBuffer=null),this.fragPlaying=null,this.gapController&&(this.gapController.destroy(),this.gapController=null),t.prototype.onMediaDetaching.call(this)},r.onMediaPlaying=function(){this.tick()},r.onMediaSeeked=function(){var t=this.media,e=t?t.currentTime:null;y(e)&&this.log("Media seeked to "+e.toFixed(3));var r=this.getMainFwdBufferInfo();null!==r&&0!==r.len?this.tick():this.warn('Main forward buffer length on "seeked" event '+(r?r.len:"empty")+")")},r.onManifestLoading=function(){this.log("Trigger BUFFER_RESET"),this.hls.trigger(S.BUFFER_RESET,void 0),this.fragmentTracker.removeAllFragments(),this.couldBacktrack=!1,this.startPosition=this.lastCurrentTime=this.fragLastKbps=0,this.levels=this.fragPlaying=this.backtrackFragment=this.levelLastLoaded=null,this.altAudio=this.audioOnly=this.startFragRequested=!1},r.onManifestParsed=function(t,e){var r,i,n=!1,a=!1;e.levels.forEach((function(t){var e=t.audioCodec;e&&(n=n||-1!==e.indexOf("mp4a.40.2"),a=a||-1!==e.indexOf("mp4a.40.5"))})),this.audioCodecSwitch=n&&a&&!("function"==typeof(null==(i=eo())||null==(r=i.prototype)?void 0:r.changeType)),this.audioCodecSwitch&&this.log("Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC"),this.levels=e.levels,this.startFragRequested=!1},r.onLevelLoading=function(t,e){var r=this.levels;if(r&&this.state===fi){var i=r[e.level];(!i.details||i.details.live&&this.levelLastLoaded!==i||this.waitForCdnTuneIn(i.details))&&(this.state=Ai)}},r.onLevelLoaded=function(t,e){var r,i=this.levels,n=e.level,a=e.details,s=a.totalduration;if(i){this.log("Level "+n+" loaded ["+a.startSN+","+a.endSN+"]"+(a.lastPartSn?"[part-"+a.lastPartSn+"-"+a.lastPartIndex+"]":"")+", cc ["+a.startCC+", "+a.endCC+"] duration:"+s);var o=i[n],l=this.fragCurrent;!l||this.state!==vi&&this.state!==mi||l.level!==e.level&&l.loader&&this.abortCurrentFrag();var u=0;if(a.live||null!=(r=o.details)&&r.live){var h;if(this.checkLiveUpdate(a),a.deltaUpdateFailed)return;u=this.alignPlaylists(a,o.details,null==(h=this.levelLastLoaded)?void 0:h.details)}if(o.details=a,this.levelLastLoaded=o,this.hls.trigger(S.LEVEL_UPDATED,{details:a,level:n}),this.state===Ai){if(this.waitForCdnTuneIn(a))return;this.state=fi}this.startFragRequested?a.live&&this.synchronizeToLiveEdge(a):this.setStartPosition(a,u),this.tick()}else this.warn("Levels were reset while loading level "+n)},r._handleFragmentLoadProgress=function(t){var e,r=t.frag,i=t.part,n=t.payload,a=this.levels;if(a){var s=a[r.level],o=s.details;if(!o)return this.warn("Dropping fragment "+r.sn+" of level "+r.level+" after level details were reset"),void this.fragmentTracker.removeFragment(r);var l=s.videoCodec,u=o.PTSKnown||!o.live,h=null==(e=r.initSegment)?void 0:e.data,d=this._getAudioCodec(s),c=this.transmuxer=this.transmuxer||new Bn(this.hls,Ie,this._handleTransmuxComplete.bind(this),this._handleTransmuxerFlush.bind(this)),f=i?i.index:-1,g=-1!==f,v=new Qr(r.level,r.sn,r.stats.chunkCount,n.byteLength,f,g),m=this.initPTS[r.cc];c.push(n,h,d,l,r,i,o.totalduration,u,v,m)}else this.warn("Levels were reset while fragment load was in progress. Fragment "+r.sn+" of level "+r.level+" will not be buffered")},r.onAudioTrackSwitching=function(t,e){var r=this.altAudio;if(!e.url){if(this.mediaBuffer!==this.media){this.log("Switching on main audio, use media.buffered to schedule main fragment loading"),this.mediaBuffer=this.media;var i=this.fragCurrent;i&&(this.log("Switching to main audio track, cancel main fragment load"),i.abortRequests(),this.fragmentTracker.removeFragment(i)),this.resetTransmuxer(),this.resetLoadingState()}else this.audioOnly&&this.resetTransmuxer();var n=this.hls;r&&(n.trigger(S.BUFFER_FLUSHING,{startOffset:0,endOffset:Number.POSITIVE_INFINITY,type:null}),this.fragmentTracker.removeAllFragments()),n.trigger(S.AUDIO_TRACK_SWITCHED,e)}},r.onAudioTrackSwitched=function(t,e){var r=e.id,i=!!this.hls.audioTracks[r].url;if(i){var n=this.videoBuffer;n&&this.mediaBuffer!==n&&(this.log("Switching on alternate audio, use video.buffered to schedule main fragment loading"),this.mediaBuffer=n)}this.altAudio=i,this.tick()},r.onBufferCreated=function(t,e){var r,i,n=e.tracks,a=!1;for(var s in n){var o=n[s];if("main"===o.id){if(i=s,r=o,"video"===s){var l=n[s];l&&(this.videoBuffer=l.buffer)}}else a=!0}a&&r?(this.log("Alternate track found, use "+i+".buffered to schedule main fragment loading"),this.mediaBuffer=r.buffer):this.mediaBuffer=this.media},r.onFragBuffered=function(t,e){var r=e.frag,i=e.part;if(!r||r.type===Ie){if(this.fragContextChanged(r))return this.warn("Fragment "+r.sn+(i?" p: "+i.index:"")+" of level "+r.level+" finished buffering, but was aborted. state: "+this.state),void(this.state===Ei&&(this.state=fi));var n=i?i.stats:r.stats;this.fragLastKbps=Math.round(8*n.total/(n.buffering.end-n.loading.first)),"initSegment"!==r.sn&&(this.fragPrevious=r),this.fragBufferedComplete(r,i)}},r.onError=function(t,e){var r;if(e.fatal)this.state=Si;else switch(e.details){case A.FRAG_GAP:case A.FRAG_PARSING_ERROR:case A.FRAG_DECRYPT_ERROR:case A.FRAG_LOAD_ERROR:case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_ERROR:case A.KEY_LOAD_TIMEOUT:this.onFragmentOrKeyLoadError(Ie,e);break;case A.LEVEL_LOAD_ERROR:case A.LEVEL_LOAD_TIMEOUT:case A.LEVEL_PARSING_ERROR:e.levelRetry||this.state!==Ai||(null==(r=e.context)?void 0:r.type)!==ke||(this.state=fi);break;case A.BUFFER_APPEND_ERROR:case A.BUFFER_FULL_ERROR:if(!e.parent||"main"!==e.parent)return;if(e.details===A.BUFFER_APPEND_ERROR)return void this.resetLoadingState();this.reduceLengthAndFlushBuffer(e)&&this.flushMainBuffer(0,Number.POSITIVE_INFINITY);break;case A.INTERNAL_EXCEPTION:this.recoverWorkerError(e)}},r.checkBuffer=function(){var t=this.media,e=this.gapController;if(t&&e&&t.readyState){if(this.loadedmetadata||!zr.getBuffered(t).length){var r=this.state!==fi?this.fragCurrent:null;e.poll(this.lastCurrentTime,r)}this.lastCurrentTime=t.currentTime}},r.onFragLoadEmergencyAborted=function(){this.state=fi,this.loadedmetadata||(this.startFragRequested=!1,this.nextLoadPosition=this.startPosition),this.tickImmediate()},r.onBufferFlushed=function(t,e){var r=e.type;if(r!==O||this.audioOnly&&!this.altAudio){var i=(r===N?this.videoBuffer:this.mediaBuffer)||this.media;this.afterBufferFlushed(i,r,Ie),this.tick()}},r.onLevelsUpdated=function(t,e){this.level>-1&&this.fragCurrent&&(this.level=this.fragCurrent.level),this.levels=e.levels},r.swapAudioCodec=function(){this.audioCodecSwap=!this.audioCodecSwap},r.seekToStartPos=function(){var t=this.media;if(t){var e=t.currentTime,r=this.startPosition;if(r>=0&&e<r){if(t.seeking)return void this.log("could not seek to "+r+", already seeking at "+e);var i=zr.getBuffered(t),n=(i.length?i.start(0):0)-r;n>0&&(n<this.config.maxBufferHole||n<this.config.maxFragLookUpTolerance)&&(this.log("adjusting start position by "+n+" to match buffer start"),r+=n,this.startPosition=r),this.log("seek to target start position "+r+" from current time "+e),t.currentTime=r}}},r._getAudioCodec=function(t){var e=this.config.defaultAudioCodec||t.audioCodec;return this.audioCodecSwap&&e&&(this.log("Swapping audio codec"),e=-1!==e.indexOf("mp4a.40.5")?"mp4a.40.2":"mp4a.40.5"),e},r._loadBitrateTestFrag=function(t,e){var r=this;t.bitrateTest=!0,this._doFragLoad(t,e).then((function(i){var n=r.hls;if(i&&!r.fragContextChanged(t)){e.fragmentError=0,r.state=fi,r.startFragRequested=!1,r.bitrateTest=!1;var a=t.stats;a.parsing.start=a.parsing.end=a.buffering.start=a.buffering.end=self.performance.now(),n.trigger(S.FRAG_LOADED,i),t.bitrateTest=!1}}))},r._handleTransmuxComplete=function(t){var e,r="main",i=this.hls,n=t.remuxResult,a=t.chunkMeta,s=this.getCurrentContext(a);if(s){var o=s.frag,l=s.part,u=s.level,h=n.video,d=n.text,c=n.id3,f=n.initSegment,g=u.details,v=this.altAudio?void 0:n.audio;if(this.fragContextChanged(o))this.fragmentTracker.removeFragment(o);else{if(this.state=yi,f){if(null!=f&&f.tracks){var m=o.initSegment||o;this._bufferInitSegment(u,f.tracks,m,a),i.trigger(S.FRAG_PARSING_INIT_SEGMENT,{frag:m,id:r,tracks:f.tracks})}var p=f.initPTS,E=f.timescale;y(p)&&(this.initPTS[o.cc]={baseTime:p,timescale:E},i.trigger(S.INIT_PTS_FOUND,{frag:o,id:r,initPTS:p,timescale:E}))}if(h&&g&&"initSegment"!==o.sn){var T=g.fragments[o.sn-1-g.startSN],L=o.sn===g.startSN,A=!T||o.cc>T.cc;if(!1!==n.independent){var R=h.startPTS,k=h.endPTS,b=h.startDTS,D=h.endDTS;if(l)l.elementaryStreams[h.type]={startPTS:R,endPTS:k,startDTS:b,endDTS:D};else if(h.firstKeyFrame&&h.independent&&1===a.id&&!A&&(this.couldBacktrack=!0),h.dropped&&h.independent){var I=this.getMainFwdBufferInfo(),w=(I?I.end:this.getLoadPosition())+this.config.maxBufferHole,C=h.firstKeyFramePTS?h.firstKeyFramePTS:R;if(!L&&w<C-this.config.maxBufferHole&&!A)return void this.backtrack(o);A&&(o.gap=!0),o.setElementaryStreamInfo(h.type,o.start,k,o.start,D,!0)}else L&&R>2&&(o.gap=!0);o.setElementaryStreamInfo(h.type,R,k,b,D),this.backtrackFragment&&(this.backtrackFragment=o),this.bufferFragmentData(h,o,l,a,L||A)}else{if(!L&&!A)return void this.backtrack(o);o.gap=!0}}if(v){var _=v.startPTS,x=v.endPTS,P=v.startDTS,F=v.endDTS;l&&(l.elementaryStreams[O]={startPTS:_,endPTS:x,startDTS:P,endDTS:F}),o.setElementaryStreamInfo(O,_,x,P,F),this.bufferFragmentData(v,o,l,a)}if(g&&null!=c&&null!=(e=c.samples)&&e.length){var M={id:r,frag:o,details:g,samples:c.samples};i.trigger(S.FRAG_PARSING_METADATA,M)}if(g&&d){var N={id:r,frag:o,details:g,samples:d.samples};i.trigger(S.FRAG_PARSING_USERDATA,N)}}}else this.resetWhenMissingContext(a)},r._bufferInitSegment=function(t,e,r,i){var n=this;if(this.state===yi){this.audioOnly=!!e.audio&&!e.video,this.altAudio&&!this.audioOnly&&delete e.audio;var a=e.audio,s=e.video,o=e.audiovideo;if(a){var l=t.audioCodec,u=navigator.userAgent.toLowerCase();this.audioCodecSwitch&&(l&&(l=-1!==l.indexOf("mp4a.40.5")?"mp4a.40.2":"mp4a.40.5"),1!==a.metadata.channelCount&&-1===u.indexOf("firefox")&&(l="mp4a.40.5")),l&&-1!==l.indexOf("mp4a.40.5")&&-1!==u.indexOf("android")&&"audio/mpeg"!==a.container&&(l="mp4a.40.2",this.log("Android: force audio codec to "+l)),t.audioCodec&&t.audioCodec!==l&&this.log('Swapping manifest audio codec "'+t.audioCodec+'" for "'+l+'"'),a.levelCodec=l,a.id="main",this.log("Init audio buffer, container:"+a.container+", codecs[selected/level/parsed]=["+(l||"")+"/"+(t.audioCodec||"")+"/"+a.codec+"]")}s&&(s.levelCodec=t.videoCodec,s.id="main",this.log("Init video buffer, container:"+s.container+", codecs[level/parsed]=["+(t.videoCodec||"")+"/"+s.codec+"]")),o&&this.log("Init audiovideo buffer, container:"+o.container+", codecs[level/parsed]=["+t.codecs+"/"+o.codec+"]"),this.hls.trigger(S.BUFFER_CODECS,e),Object.keys(e).forEach((function(t){var a=e[t].initSegment;null!=a&&a.byteLength&&n.hls.trigger(S.BUFFER_APPENDING,{type:t,data:a,frag:r,part:null,chunkMeta:i,parent:r.type})})),this.tickImmediate()}},r.getMainFwdBufferInfo=function(){return this.getFwdBufferInfo(this.mediaBuffer?this.mediaBuffer:this.media,Ie)},r.backtrack=function(t){this.couldBacktrack=!0,this.backtrackFragment=t,this.resetTransmuxer(),this.flushBufferGap(t),this.fragmentTracker.removeFragment(t),this.fragPrevious=null,this.nextLoadPosition=t.start,this.state=fi},r.checkFragmentChanged=function(){var t=this.media,e=null;if(t&&t.readyState>1&&!1===t.seeking){var r=t.currentTime;if(zr.isBuffered(t,r)?e=this.getAppendedFrag(r):zr.isBuffered(t,r+.1)&&(e=this.getAppendedFrag(r+.1)),e){this.backtrackFragment=null;var i=this.fragPlaying,n=e.level;i&&e.sn===i.sn&&i.level===n||(this.fragPlaying=e,this.hls.trigger(S.FRAG_CHANGED,{frag:e}),i&&i.level===n||this.hls.trigger(S.LEVEL_SWITCHED,{level:n}))}}},s(e,[{key:"nextLevel",get:function(){var t=this.nextBufferedFrag;return t?t.level:-1}},{key:"currentFrag",get:function(){var t=this.media;return t?this.fragPlaying||this.getAppendedFrag(t.currentTime):null}},{key:"currentProgramDateTime",get:function(){var t=this.media;if(t){var e=t.currentTime,r=this.currentFrag;if(r&&y(e)&&y(r.programDateTime)){var i=r.programDateTime+1e3*(e-r.start);return new Date(i)}}return null}},{key:"currentLevel",get:function(){var t=this.currentFrag;return t?t.level:-1}},{key:"nextBufferedFrag",get:function(){var t=this.currentFrag;return t?this.followingBufferedFrag(t):null}},{key:"forceStartLoad",get:function(){return this._forceStartLoad}}]),e}(Ri),ao=function(){function t(e){void 0===e&&(e={}),this.config=void 0,this.userConfig=void 0,this.coreComponents=void 0,this.networkControllers=void 0,this.started=!1,this._emitter=new Mn,this._autoLevelCapping=-1,this._maxHdcpLevel=null,this.abrController=void 0,this.bufferController=void 0,this.capLevelController=void 0,this.latencyController=void 0,this.levelController=void 0,this.streamController=void 0,this.audioTrackController=void 0,this.subtitleTrackController=void 0,this.emeController=void 0,this.cmcdController=void 0,this._media=null,this.url=null,this.triggeringException=void 0,I(e.debug||!1,"Hls instance");var r=this.config=function(t,e){if((e.liveSyncDurationCount||e.liveMaxLatencyDurationCount)&&(e.liveSyncDuration||e.liveMaxLatencyDuration))throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");if(void 0!==e.liveMaxLatencyDurationCount&&(void 0===e.liveSyncDurationCount||e.liveMaxLatencyDurationCount<=e.liveSyncDurationCount))throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');if(void 0!==e.liveMaxLatencyDuration&&(void 0===e.liveSyncDuration||e.liveMaxLatencyDuration<=e.liveSyncDuration))throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');var r=Qs(t),n=["TimeOut","MaxRetry","RetryDelay","MaxRetryTimeout"];return["manifest","level","frag"].forEach((function(t){var i=("level"===t?"playlist":t)+"LoadPolicy",a=void 0===e[i],s=[];n.forEach((function(n){var o=t+"Loading"+n,l=e[o];if(void 0!==l&&a){s.push(o);var u=r[i].default;switch(e[i]={default:u},n){case"TimeOut":u.maxLoadTimeMs=l,u.maxTimeToFirstByteMs=l;break;case"MaxRetry":u.errorRetry.maxNumRetry=l,u.timeoutRetry.maxNumRetry=l;break;case"RetryDelay":u.errorRetry.retryDelayMs=l,u.timeoutRetry.retryDelayMs=l;break;case"MaxRetryTimeout":u.errorRetry.maxRetryDelayMs=l,u.timeoutRetry.maxRetryDelayMs=l}}})),s.length&&w.warn('hls.js config: "'+s.join('", "')+'" setting(s) are deprecated, use "'+i+'": '+JSON.stringify(e[i]))})),i(i({},r),e)}(t.DefaultConfig,e);this.userConfig=e,r.progressive&&Js(r);var n=r.abrController,a=r.bufferController,s=r.capLevelController,o=r.errorController,l=r.fpsController,u=new o(this),h=this.abrController=new n(this),d=this.bufferController=new a(this),c=this.capLevelController=new s(this),f=new l(this),g=new Pe(this),v=new je(this),m=r.contentSteeringController,p=m?new m(this):null,y=this.levelController=new $s(this,p),E=new Wr(this),T=new to(this.config),L=this.streamController=new no(this,E,T);c.setStreamController(L),f.setStreamController(L);var A=[g,y,L];p&&A.splice(1,0,p),this.networkControllers=A;var R=[h,d,c,f,v,E];this.audioTrackController=this.createController(r.audioTrackController,A);var k=r.audioStreamController;k&&A.push(new k(this,E,T)),this.subtitleTrackController=this.createController(r.subtitleTrackController,A);var b=r.subtitleStreamController;b&&A.push(new b(this,E,T)),this.createController(r.timelineController,R),T.emeController=this.emeController=this.createController(r.emeController,R),this.cmcdController=this.createController(r.cmcdController,R),this.latencyController=this.createController(qe,R),this.coreComponents=R,A.push(u);var D=u.onErrorOut;"function"==typeof D&&this.on(S.ERROR,D,u)}t.isMSESupported=function(){return ro()},t.isSupported=function(){return function(){if(!ro())return!1;var t=te();return"function"==typeof(null==t?void 0:t.isTypeSupported)&&(["avc1.42E01E,mp4a.40.2","av01.0.01M.08","vp09.00.50.08"].some((function(e){return t.isTypeSupported(ne(e,"video"))}))||["mp4a.40.2","fLaC"].some((function(e){return t.isTypeSupported(ne(e,"audio"))})))}()},t.getMediaSource=function(){return te()};var e=t.prototype;return e.createController=function(t,e){if(t){var r=new t(this);return e&&e.push(r),r}return null},e.on=function(t,e,r){void 0===r&&(r=this),this._emitter.on(t,e,r)},e.once=function(t,e,r){void 0===r&&(r=this),this._emitter.once(t,e,r)},e.removeAllListeners=function(t){this._emitter.removeAllListeners(t)},e.off=function(t,e,r,i){void 0===r&&(r=this),this._emitter.off(t,e,r,i)},e.listeners=function(t){return this._emitter.listeners(t)},e.emit=function(t,e,r){return this._emitter.emit(t,e,r)},e.trigger=function(t,e){if(this.config.debug)return this.emit(t,t,e);try{return this.emit(t,t,e)}catch(e){if(w.error("An internal error happened while handling event "+t+'. Error message: "'+e.message+'". Here is a stacktrace:',e),!this.triggeringException){this.triggeringException=!0;var r=t===S.ERROR;this.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.INTERNAL_EXCEPTION,fatal:r,event:t,error:e}),this.triggeringException=!1}}return!1},e.listenerCount=function(t){return this._emitter.listenerCount(t)},e.destroy=function(){w.log("destroy"),this.trigger(S.DESTROYING,void 0),this.detachMedia(),this.removeAllListeners(),this._autoLevelCapping=-1,this.url=null,this.networkControllers.forEach((function(t){return t.destroy()})),this.networkControllers.length=0,this.coreComponents.forEach((function(t){return t.destroy()})),this.coreComponents.length=0;var t=this.config;t.xhrSetup=t.fetchSetup=void 0,this.userConfig=null},e.attachMedia=function(t){w.log("attachMedia"),this._media=t,this.trigger(S.MEDIA_ATTACHING,{media:t})},e.detachMedia=function(){w.log("detachMedia"),this.trigger(S.MEDIA_DETACHING,void 0),this._media=null},e.loadSource=function(t){this.stopLoad();var e=this.media,r=this.url,i=this.url=p.buildAbsoluteURL(self.location.href,t,{alwaysNormalize:!0});this._autoLevelCapping=-1,this._maxHdcpLevel=null,w.log("loadSource:"+i),e&&r&&(r!==i||this.bufferController.hasSourceTypes())&&(this.detachMedia(),this.attachMedia(e)),this.trigger(S.MANIFEST_LOADING,{url:t})},e.startLoad=function(t){void 0===t&&(t=-1),w.log("startLoad("+t+")"),this.started=!0,this.networkControllers.forEach((function(e){e.startLoad(t)}))},e.stopLoad=function(){w.log("stopLoad"),this.started=!1,this.networkControllers.forEach((function(t){t.stopLoad()}))},e.resumeBuffering=function(){this.started&&this.networkControllers.forEach((function(t){"fragmentLoader"in t&&t.startLoad(-1)}))},e.pauseBuffering=function(){this.networkControllers.forEach((function(t){"fragmentLoader"in t&&t.stopLoad()}))},e.swapAudioCodec=function(){w.log("swapAudioCodec"),this.streamController.swapAudioCodec()},e.recoverMediaError=function(){w.log("recoverMediaError");var t=this._media;this.detachMedia(),t&&this.attachMedia(t)},e.removeLevel=function(t){this.levelController.removeLevel(t)},e.setAudioOption=function(t){var e;return null==(e=this.audioTrackController)?void 0:e.setAudioOption(t)},e.setSubtitleOption=function(t){var e;return null==(e=this.subtitleTrackController)||e.setSubtitleOption(t),null},s(t,[{key:"levels",get:function(){var t=this.levelController.levels;return t||[]}},{key:"currentLevel",get:function(){return this.streamController.currentLevel},set:function(t){w.log("set currentLevel:"+t),this.levelController.manualLevel=t,this.streamController.immediateLevelSwitch()}},{key:"nextLevel",get:function(){return this.streamController.nextLevel},set:function(t){w.log("set nextLevel:"+t),this.levelController.manualLevel=t,this.streamController.nextLevelSwitch()}},{key:"loadLevel",get:function(){return this.levelController.level},set:function(t){w.log("set loadLevel:"+t),this.levelController.manualLevel=t}},{key:"nextLoadLevel",get:function(){return this.levelController.nextLoadLevel},set:function(t){this.levelController.nextLoadLevel=t}},{key:"firstLevel",get:function(){return Math.max(this.levelController.firstLevel,this.minAutoLevel)},set:function(t){w.log("set firstLevel:"+t),this.levelController.firstLevel=t}},{key:"startLevel",get:function(){var t=this.levelController.startLevel;return-1===t&&this.abrController.forcedAutoLevel>-1?this.abrController.forcedAutoLevel:t},set:function(t){w.log("set startLevel:"+t),-1!==t&&(t=Math.max(t,this.minAutoLevel)),this.levelController.startLevel=t}},{key:"capLevelToPlayerSize",get:function(){return this.config.capLevelToPlayerSize},set:function(t){var e=!!t;e!==this.config.capLevelToPlayerSize&&(e?this.capLevelController.startCapping():(this.capLevelController.stopCapping(),this.autoLevelCapping=-1,this.streamController.nextLevelSwitch()),this.config.capLevelToPlayerSize=e)}},{key:"autoLevelCapping",get:function(){return this._autoLevelCapping},set:function(t){this._autoLevelCapping!==t&&(w.log("set autoLevelCapping:"+t),this._autoLevelCapping=t,this.levelController.checkMaxAutoUpdated())}},{key:"bandwidthEstimate",get:function(){var t=this.abrController.bwEstimator;return t?t.getEstimate():NaN},set:function(t){this.abrController.resetEstimator(t)}},{key:"ttfbEstimate",get:function(){var t=this.abrController.bwEstimator;return t?t.getEstimateTTFB():NaN}},{key:"maxHdcpLevel",get:function(){return this._maxHdcpLevel},set:function(t){(function(t){return Xe.indexOf(t)>-1})(t)&&this._maxHdcpLevel!==t&&(this._maxHdcpLevel=t,this.levelController.checkMaxAutoUpdated())}},{key:"autoLevelEnabled",get:function(){return-1===this.levelController.manualLevel}},{key:"manualLevel",get:function(){return this.levelController.manualLevel}},{key:"minAutoLevel",get:function(){var t=this.levels,e=this.config.minAutoBitrate;if(!t)return 0;for(var r=t.length,i=0;i<r;i++)if(t[i].maxBitrate>=e)return i;return 0}},{key:"maxAutoLevel",get:function(){var t,e=this.levels,r=this.autoLevelCapping,i=this.maxHdcpLevel;if(t=-1===r&&null!=e&&e.length?e.length-1:r,i)for(var n=t;n--;){var a=e[n].attrs["HDCP-LEVEL"];if(a&&a<=i)return n}return t}},{key:"firstAutoLevel",get:function(){return this.abrController.firstAutoLevel}},{key:"nextAutoLevel",get:function(){return this.abrController.nextAutoLevel},set:function(t){this.abrController.nextAutoLevel=t}},{key:"playingDate",get:function(){return this.streamController.currentProgramDateTime}},{key:"mainForwardBufferInfo",get:function(){return this.streamController.getMainFwdBufferInfo()}},{key:"allAudioTracks",get:function(){var t=this.audioTrackController;return t?t.allAudioTracks:[]}},{key:"audioTracks",get:function(){var t=this.audioTrackController;return t?t.audioTracks:[]}},{key:"audioTrack",get:function(){var t=this.audioTrackController;return t?t.audioTrack:-1},set:function(t){var e=this.audioTrackController;e&&(e.audioTrack=t)}},{key:"allSubtitleTracks",get:function(){var t=this.subtitleTrackController;return t?t.allSubtitleTracks:[]}},{key:"subtitleTracks",get:function(){var t=this.subtitleTrackController;return t?t.subtitleTracks:[]}},{key:"subtitleTrack",get:function(){var t=this.subtitleTrackController;return t?t.subtitleTrack:-1},set:function(t){var e=this.subtitleTrackController;e&&(e.subtitleTrack=t)}},{key:"media",get:function(){return this._media}},{key:"subtitleDisplay",get:function(){var t=this.subtitleTrackController;return!!t&&t.subtitleDisplay},set:function(t){var e=this.subtitleTrackController;e&&(e.subtitleDisplay=t)}},{key:"lowLatencyMode",get:function(){return this.config.lowLatencyMode},set:function(t){this.config.lowLatencyMode=t}},{key:"liveSyncPosition",get:function(){return this.latencyController.liveSyncPosition}},{key:"latency",get:function(){return this.latencyController.latency}},{key:"maxLatency",get:function(){return this.latencyController.maxLatency}},{key:"targetLatency",get:function(){return this.latencyController.targetLatency}},{key:"drift",get:function(){return this.latencyController.drift}},{key:"forceStartLoad",get:function(){return this.streamController.forceStartLoad}}],[{key:"version",get:function(){return"1.5.1"}},{key:"Events",get:function(){return S}},{key:"ErrorTypes",get:function(){return L}},{key:"ErrorDetails",get:function(){return A}},{key:"DefaultConfig",get:function(){return t.defaultConfig?t.defaultConfig:zs},set:function(e){t.defaultConfig=e}}]),t}();return ao.defaultConfig=void 0,ao},"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(r="undefined"!=typeof globalThis?globalThis:r||self).Hls=i()}(!1); +// @license-end diff --git a/src/formatters.nim b/src/formatters.nim index 3630917..8267f23 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -82,6 +82,8 @@ proc proxifyVideo*(manifest: string; proxy: bool): string = for line in manifest.splitLines: let url = if line.startsWith("#EXT-X-MAP:URI"): line[16 .. ^2] + elif line.startsWith("#EXT-X-MEDIA") and "URI=" in line: + line[line.find("URI=") + 5 .. -1 + line.find("\"", start= 5 + line.find("URI="))] else: line if url.startsWith('/'): let path = "https://video.twimg.com" & url diff --git a/src/utils.nim b/src/utils.nim index ede9aed..c96a6dd 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -31,9 +31,7 @@ proc getHmac*(data: string): string = proc getVidUrl*(link: string): string = if link.len == 0: return - let - link = link.replace("cmaf", "fmp4") - sig = getHmac(link) + let sig = getHmac(link) if base64Media: &"/video/enc/{sig}/{encode(link, safe=true)}" else: diff --git a/src/views/general.nim b/src/views/general.nim index 87d30f2..5ba40a3 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -73,7 +73,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed") if prefs.hlsPlayback: - script(src="/js/hls.light.min.js", `defer`="") + script(src="/js/hls.min.js", `defer`="") script(src="/js/hlsPlayback.js", `defer`="") if prefs.infiniteScroll: From c6edec04901d0a37799499ed4c6921db640fb5a4 Mon Sep 17 00:00:00 2001 From: somini <somini@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:08:25 +0000 Subject: [PATCH 083/302] Update auth.nim (#1164) Avoid expiring the tokens for now. See: - https://github.com/zedeus/nitter/issues/983#issuecomment-1923046398 - https://github.com/zedeus/nitter/issues/1155#issuecomment-1917167072 Thanks @cmj --- src/auth.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth.nim b/src/auth.nim index b288c50..de1b1d8 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -202,7 +202,7 @@ proc initAccountPool*(cfg: Config; path: string) = quit 1 let accountsPrePurge = accountPool.len - accountPool.keepItIf(not it.hasExpired) + #accountPool.keepItIf(not it.hasExpired) log "Successfully added ", accountPool.len, " valid accounts." if accountsPrePurge > accountPool.len: From 19569bb19f6a725789e45e58bb86fcd7806da8a7 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 00:25:50 +0100 Subject: [PATCH 084/302] Replace old v1 photo rail API with gql --- src/api.nim | 12 +++++------ src/apiutils.nim | 19 ----------------- src/auth.nim | 1 - src/consts.nim | 20 +----------------- src/parser.nim | 45 +++++++++++++++++++++++++---------------- src/redis_cache.nim | 12 +++++------ src/routes/timeline.nim | 2 +- src/types.nim | 1 - 8 files changed, 42 insertions(+), 70 deletions(-) diff --git a/src/api.nim b/src/api.nim index d6a4564..479cb3d 100644 --- a/src/api.nim +++ b/src/api.nim @@ -136,13 +136,13 @@ proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} result = parseGraphSearch[User](await fetch(url, Api.search), after) result.query = query -proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} = - if name.len == 0: return +proc getPhotoRail*(id: string): Future[PhotoRail] {.async.} = + if id.len == 0: return let - ps = genParams({"screen_name": name, "trim_user": "true"}, - count="18", ext=false) - url = photoRail ? ps - result = parsePhotoRail(await fetch(url, Api.photoRail)) + variables = userTweetsVariables % [id, ""] + params = {"variables": variables, "features": gqlFeatures} + url = graphUserMedia ? params + result = parseGraphPhotoRail(await fetch(url, Api.userMedia)) proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} = let client = newAsyncHttpClient(maxRedirects=0) diff --git a/src/apiutils.nim b/src/apiutils.nim index 1ff05eb..75e0e4c 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -10,25 +10,6 @@ const var pool: HttpPool -proc genParams*(pars: openArray[(string, string)] = @[]; cursor=""; - count="20"; ext=true): seq[(string, string)] = - result = timelineParams - for p in pars: - result &= p - if ext: - result &= ("include_ext_alt_text", "1") - result &= ("include_ext_media_stats", "1") - result &= ("include_ext_media_availability", "1") - if count.len > 0: - result &= ("count", count) - if cursor.len > 0: - # The raw cursor often has plus signs, which sometimes get turned into spaces, - # so we need to turn them back into a plus - if " " in cursor: - result &= ("cursor", cursor.replace(" ", "+")) - else: - result &= ("cursor", cursor) - proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = let encodedUrl = url.replace(",", "%2C").replace("+", "%20") diff --git a/src/auth.nim b/src/auth.nim index de1b1d8..a15766b 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -10,7 +10,6 @@ const apiMaxReqs: Table[Api, int] = { Api.search: 50, Api.tweetDetail: 150, - Api.photoRail: 180, Api.userTweets: 500, Api.userTweetsAndReplies: 500, Api.userMedia: 500, diff --git a/src/consts.nim b/src/consts.nim index e1c35e6..3abd1bc 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import uri, sequtils, strutils +import uri, strutils const consumerKey* = "3nVuSoBZnx6U4vzUxf5w" @@ -8,8 +8,6 @@ const api = parseUri("https://api.twitter.com") activate* = $(api / "1.1/guest/activate.json") - photoRail* = api / "1.1/statuses/media_timeline.json" - graphql = api / "graphql" graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" @@ -24,22 +22,6 @@ const graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers" graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" - timelineParams* = { - "include_can_media_tag": "1", - "include_cards": "1", - "include_entities": "1", - "include_profile_interstitial_type": "0", - "include_quote_count": "0", - "include_reply_count": "0", - "include_user_entities": "0", - "include_ext_reply_count": "0", - "include_ext_media_color": "0", - "cards_platform": "Web-13", - "tweet_mode": "extended", - "send_error_codes": "1", - "simple_quoted_tweet": "1" - }.toSeq - gqlFeatures* = """{ "android_graphql_skip_api_media_color_palette": false, "blue_business_profile_image_shape_enabled": false, diff --git a/src/parser.nim b/src/parser.nim index ec856a6..b8812d2 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -289,23 +289,6 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.text.removeSuffix(" Learn more.") result.available = false -proc parsePhotoRail*(js: JsonNode): PhotoRail = - with error, js{"error"}: - if error.getStr == "Not authorized.": - return - - for tweet in js: - let - t = parseTweet(tweet, js{"tweet_card"}) - url = if t.photos.len > 0: t.photos[0] - elif t.video.isSome: get(t.video).thumb - elif t.gif.isSome: get(t.gif).thumb - elif t.card.isSome: get(t.card).image - else: "" - - if url.len == 0: continue - result.add GalleryPhoto(url: url, tweetId: $t.id) - proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet = if js.kind == JNull: return Tweet() @@ -445,6 +428,34 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = tweet.id = parseBiggestInt(entryId) result.pinned = some tweet +proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = + result = @[] + + let instructions = + ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + + for i in instructions: + if i{"__typename"}.getStr == "TimelineAddEntries": + for e in i{"entries"}: + let entryId = e{"entryId"}.getStr + if entryId.startsWith("tweet"): + with tweetResult, e{"content", "content", "tweetResult", "result"}: + let t = parseGraphTweet(tweetResult, false) + if not t.available: + t.id = parseBiggestInt(entryId.getId()) + + let url = + if t.photos.len > 0: t.photos[0] + elif t.video.isSome: get(t.video).thumb + elif t.gif.isSome: get(t.gif).thumb + elif t.card.isSome: get(t.card).image + else: "" + + result.add GalleryPhoto(url: url, tweetId: $t.id) + + if result.len == 16: + break + proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = result = Result[T](beginning: after.len == 0) diff --git a/src/redis_cache.nim b/src/redis_cache.nim index 1d77cca..559d299 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -86,7 +86,7 @@ proc cache*(data: List) {.async.} = await setEx(data.listKey, listCacheTime, compress(toFlatty(data))) proc cache*(data: PhotoRail; name: string) {.async.} = - await setEx("pr:" & toLower(name), baseCacheTime * 2, compress(toFlatty(data))) + await setEx("pr2:" & toLower(name), baseCacheTime * 2, compress(toFlatty(data))) proc cache*(data: User) {.async.} = if data.username.len == 0: return @@ -158,14 +158,14 @@ proc getCachedUsername*(userId: string): Future[string] {.async.} = # if not result.isNil: # await cache(result) -proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} = - if name.len == 0: return - let rail = await get("pr:" & toLower(name)) +proc getCachedPhotoRail*(id: string): Future[PhotoRail] {.async.} = + if id.len == 0: return + let rail = await get("pr2:" & toLower(id)) if rail != redisNil: rail.deserialize(PhotoRail) else: - result = await getPhotoRail(name) - await cache(result, name) + result = await getPhotoRail(id) + await cache(result, id) proc getCachedList*(username=""; slug=""; id=""): Future[List] {.async.} = let list = if id.len == 0: redisNil diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 3568ab7..8cd1fd7 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -47,7 +47,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false; let rail = skipIf(skipRail or query.kind == media, @[]): - getCachedPhotoRail(name) + getCachedPhotoRail(userId) user = getCachedUser(name) diff --git a/src/types.nim b/src/types.nim index ddbebdf..1b9189b 100644 --- a/src/types.nim +++ b/src/types.nim @@ -15,7 +15,6 @@ type Api* {.pure.} = enum tweetDetail tweetResult - photoRail search list listBySlug From 28d3ed7d9fad2302bf357f099c8b8951631dee8c Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 00:32:55 +0100 Subject: [PATCH 085/302] Raise NoAccountsError when all accounts limited --- src/auth.nim | 5 ++++- src/nitter.nim | 5 +++++ src/types.nim | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/auth.nim b/src/auth.nim index a15766b..20e136f 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -125,6 +125,9 @@ proc getAccountPoolDebug*(): JsonNode = proc rateLimitError*(): ref RateLimitError = newException(RateLimitError, "rate limited") +proc noAccountsError*(): ref NoAccountsError = + newException(NoAccountsError, "no accounts available") + proc isLimited(account: GuestAccount; api: Api): bool = if account.isNil: return true @@ -165,7 +168,7 @@ proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = inc result.pending else: log "no accounts available for API: ", api - raise rateLimitError() + raise noAccountsError() proc setLimited*(account: GuestAccount; api: Api) = account.apis[api].limited = true diff --git a/src/nitter.nim b/src/nitter.nim index dfc1dfd..958dc0b 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -97,6 +97,11 @@ routes: resp Http429, showError( &"Instance has been rate limited.<br>Use {link} or try again later.", cfg) + error NoAccountsError: + const link = a("another instance", href = instancesUrl) + resp Http429, showError( + &"Instance has no available accounts.<br>Use {link} or try again later.", cfg) + extend rss, "" extend status, "" extend search, "" diff --git a/src/types.nim b/src/types.nim index 1b9189b..d1981d7 100644 --- a/src/types.nim +++ b/src/types.nim @@ -6,6 +6,7 @@ genPrefsType() type RateLimitError* = object of CatchableError + NoAccountsError* = object of CatchableError InternalError* = object of CatchableError BadClientError* = object of CatchableError From 1aa9b0dba67145fa052422de93e249df3d92b468 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 00:34:50 +0100 Subject: [PATCH 086/302] Move limited flag to be account-level --- src/auth.nim | 49 ++++++++++++++++++++----------------------------- src/types.nim | 4 ++-- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 20e136f..66656d3 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -32,13 +32,6 @@ template log(str: varargs[string, `$`]) = proc snowflakeToEpoch(flake: int64): int64 = int64(((flake shr 22) + 1288834974657) div 1000) -proc hasExpired(account: GuestAccount): bool = - let - created = snowflakeToEpoch(account.id) - now = epochTime().int64 - daysOld = int(now - created) div dayInSeconds - return daysOld > 30 - proc getAccountPoolHealth*(): JsonNode = let now = epochTime().int @@ -58,14 +51,14 @@ proc getAccountPoolHealth*(): JsonNode = oldest = created average += created + if account.limited: + limited.incl account.id + for api in account.apis.keys: let apiStatus = account.apis[api] reqs = apiMaxReqs[api] - apiStatus.remaining - if apiStatus.limited: - limited.incl account.id - # no requests made with this account and endpoint since the limit reset if apiStatus.reset < now: continue @@ -103,6 +96,9 @@ proc getAccountPoolDebug*(): JsonNode = "pending": account.pending, } + if account.limited: + accountJson["limited"] = %true + for api in account.apis.keys: let apiStatus = account.apis[api] @@ -110,13 +106,11 @@ proc getAccountPoolDebug*(): JsonNode = if apiStatus.reset > now.int: obj["remaining"] = %apiStatus.remaining + obj["reset"] = %apiStatus.reset - if "remaining" notin obj and not apiStatus.limited: + if "remaining" notin obj: continue - if apiStatus.limited: - obj["limited"] = %true - accountJson{"apis", $api} = obj list[$account.id] = accountJson @@ -132,14 +126,16 @@ proc isLimited(account: GuestAccount; api: Api): bool = if account.isNil: return true + if account.limited and api != Api.userTweets: + if (epochTime().int - account.limitedAt) > dayInSeconds: + account.limited = false + log "resetting limit: ", account.id + else: + return false + if api in account.apis: let limit = account.apis[api] - - if limit.limited and (epochTime().int - limit.limitedAt) > dayInSeconds: - account.apis[api].limited = false - log "resetting limit, api: ", api, ", id: ", account.id - - return limit.limited or (limit.remaining <= 10 and limit.reset > epochTime().int) + return limit.remaining <= 10 and limit.reset > epochTime().int else: return false @@ -148,7 +144,7 @@ proc isReady(account: GuestAccount; api: Api): bool = proc invalidate*(account: var GuestAccount) = if account.isNil: return - log "invalidating expired account: ", account.id + log "invalidating: ", account.id # TODO: This isn't sufficient, but it works for now let idx = accountPool.find(account) @@ -171,9 +167,9 @@ proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = raise noAccountsError() proc setLimited*(account: GuestAccount; api: Api) = - account.apis[api].limited = true - account.apis[api].limitedAt = epochTime().int - log "rate limited, api: ", api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id + account.limited = true + account.limitedAt = epochTime().int + log "rate limited by api: ", api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id proc setRateLimit*(account: GuestAccount; api: Api; remaining, reset: int) = # avoid undefined behavior in race conditions @@ -203,9 +199,4 @@ proc initAccountPool*(cfg: Config; path: string) = echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests." quit 1 - let accountsPrePurge = accountPool.len - #accountPool.keepItIf(not it.hasExpired) - log "Successfully added ", accountPool.len, " valid accounts." - if accountsPrePurge > accountPool.len: - log "Purged ", accountsPrePurge - accountPool.len, " expired accounts." diff --git a/src/types.nim b/src/types.nim index d1981d7..f8cdda7 100644 --- a/src/types.nim +++ b/src/types.nim @@ -30,14 +30,14 @@ type RateLimit* = object remaining*: int reset*: int - limited*: bool - limitedAt*: int GuestAccount* = ref object id*: int64 oauthToken*: string oauthSecret*: string pending*: int + limited*: bool + limitedAt*: int apis*: Table[Api, RateLimit] Error* = enum From 2e13d7b57c4506b8545845f3d3ec847a8c6ca82b Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 00:35:39 +0100 Subject: [PATCH 087/302] Capture "account locked" API error --- src/apiutils.nim | 9 +++++---- src/types.nim | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 75e0e4c..03de5f1 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -76,8 +76,8 @@ template fetchImpl(result, fetchBody) {.dirty.} = if result.startsWith("{\"errors"): let errors = result.fromJson(Errors) - if errors in {expiredToken, badToken}: - echo "fetch error: ", errors + echo "Fetch error, API: ", api, ", errors: ", errors + if errors in {expiredToken, badToken, locked}: invalidate(account) raise rateLimitError() elif errors in {rateLimited}: @@ -93,6 +93,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = fetchBody if resp.status == $Http400: + echo "ERROR 400, ", api, ": ", result raise newException(InternalError, $url) except InternalError as e: raise e @@ -125,8 +126,8 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = result = newJNull() let error = result.getError - if error in {expiredToken, badToken}: - echo "fetchBody error: ", error + echo "Fetch error, API: ", api, ", error: ", error + if error in {expiredToken, badToken, locked}: invalidate(account) raise rateLimitError() diff --git a/src/types.nim b/src/types.nim index f8cdda7..685fbad 100644 --- a/src/types.nim +++ b/src/types.nim @@ -57,6 +57,7 @@ type tweetNotAuthorized = 179 forbidden = 200 badToken = 239 + locked = 326 noCsrf = 353 tweetUnavailable = 421 tweetCensored = 422 From e38276a63863d8c3ceaf27d593f6542769bfad36 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 00:40:19 +0100 Subject: [PATCH 088/302] Update authority header --- src/apiutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 03de5f1..d7842b1 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -35,7 +35,7 @@ proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = "authorization": header, "content-type": "application/json", "x-twitter-active-user": "yes", - "authority": "api.twitter.com", + "authority": "api.x.com", "accept-encoding": "gzip", "accept-language": "en-US,en;q=0.9", "accept": "*/*", From 5b6dae5228cb58b81df8c54a1c51cfc634e87fe1 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 01:16:52 +0100 Subject: [PATCH 089/302] Add regex for x.com links --- src/formatters.nim | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/formatters.nim b/src/formatters.nim index 8267f23..7428814 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -11,6 +11,8 @@ const let twRegex = re"(?<=(?<!\S)https:\/\/|(?<=\s))(www\.|mobile\.)?twitter\.com" twLinkRegex = re"""<a href="https:\/\/twitter.com([^"]+)">twitter\.com(\S+)</a>""" + xRegex = re"(?<=(?<!\S)https:\/\/|(?<=\s))(www\.|mobile\.)?x\.com" + xLinkRegex = re"""<a href="https:\/\/x.com([^"]+)">x\.com(\S+)</a>""" ytRegex = re(r"([A-z.]+\.)?youtu(be\.com|\.be)", {reStudy, reIgnoreCase}) @@ -56,12 +58,18 @@ proc replaceUrls*(body: string; prefs: Prefs; absolute=""): string = if prefs.replaceYouTube.len > 0 and "youtu" in result: result = result.replace(ytRegex, prefs.replaceYouTube) - if prefs.replaceTwitter.len > 0 and ("twitter.com" in body or tco in body): - result = result.replace(tco, https & prefs.replaceTwitter & "/t.co") - result = result.replace(cards, prefs.replaceTwitter & "/cards") - result = result.replace(twRegex, prefs.replaceTwitter) - result = result.replacef(twLinkRegex, a( - prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1")) + if prefs.replaceTwitter.len > 0: + if tco in result: + result = result.replace(tco, https & prefs.replaceTwitter & "/t.co") + if "x.com" in result: + result = result.replace(xRegex, prefs.replaceTwitter) + result = result.replacef(xLinkRegex, a( + prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1")) + if "twitter.com" in result: + result = result.replace(cards, prefs.replaceTwitter & "/cards") + result = result.replace(twRegex, prefs.replaceTwitter) + result = result.replacef(twLinkRegex, a( + prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1")) if prefs.replaceReddit.len > 0 and ("reddit.com" in result or "redd.it" in result): result = result.replace(rdShortRegex, prefs.replaceReddit & "/comments/") From 81764ea0f802677d0a4cec4025b721d601e2751f Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 01:19:06 +0100 Subject: [PATCH 090/302] Update endpoint versions, switch tweet endpoint --- src/auth.nim | 2 +- src/consts.nim | 46 ++++++++++++++++++++++++++++++---------------- src/parser.nim | 39 ++++++++++++++++++++++----------------- 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 66656d3..fe265c9 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -9,7 +9,7 @@ const dayInSeconds = 24 * 60 * 60 apiMaxReqs: Table[Api, int] = { Api.search: 50, - Api.tweetDetail: 150, + Api.tweetDetail: 500, Api.userTweets: 500, Api.userTweetsAndReplies: 500, Api.userMedia: 500, diff --git a/src/consts.nim b/src/consts.nim index 3abd1bc..7c67706 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -5,22 +5,20 @@ const consumerKey* = "3nVuSoBZnx6U4vzUxf5w" consumerSecret* = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys" - api = parseUri("https://api.twitter.com") - activate* = $(api / "1.1/guest/activate.json") + gql = parseUri("https://api.x.com") / "graphql" - graphql = api / "graphql" - graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" - graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" - graphUserTweets* = graphql / "3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2" - graphUserTweetsAndReplies* = graphql / "8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2" - graphUserMedia* = graphql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" - graphTweet* = graphql / "q94uRCEn65LZThakYcPT6g/TweetDetail" - graphTweetResult* = graphql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" - graphSearchTimeline* = graphql / "gkjsKepM6gl_HmFWoWKfgg/SearchTimeline" - graphListById* = graphql / "iTpgCtbdxrsJfyx0cFjHqg/ListByRestId" - graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug" - graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers" - graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" + graphUser* = gql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" + graphUserById* = gql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" + graphUserTweets* = gql / "JLApJKFY0MxGTzCoK6ps8Q/UserWithProfileTweetsQueryV2" + graphUserTweetsAndReplies* = gql / "Y86LQY7KMvxn5tu3hFTyPg/UserWithProfileTweetsAndRepliesQueryV2" + graphUserMedia* = gql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" + graphTweet* = gql / "Vorskcd2tZ-tc4Gx3zbk4Q/ConversationTimelineV2" + graphTweetResult* = gql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" + graphSearchTimeline* = gql / "KI9jCXUx3Ymt-hDKLOZb9Q/SearchTimeline" + graphListById* = gql / "oygmAig8kjn0pKsx_bUadQ/ListByRestId" + graphListBySlug* = gql / "88GTz-IPPWLn1EiU8XoNVg/ListBySlug" + graphListMembers* = gql / "kSmxeqEeelqdHSR7jMnb_w/ListMembers" + graphListTweets* = gql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" gqlFeatures* = """{ "android_graphql_skip_api_media_color_palette": false, @@ -62,7 +60,23 @@ const "unified_cards_ad_metadata_container_dynamic_card_content_query_enabled": false, "verified_phone_label_enabled": false, "vibe_api_enabled": false, - "view_counts_everywhere_api_enabled": false + "view_counts_everywhere_api_enabled": false, + "premium_content_api_read_enabled": false, + "communities_web_enable_tweet_community_results_fetch": false, + "responsive_web_jetfuel_frame": false, + "responsive_web_grok_analyze_button_fetch_trends_enabled": false, + "responsive_web_grok_image_annotation_enabled": false, + "rweb_tipjar_consumption_enabled": false, + "profile_label_improvements_pcf_label_in_post_enabled": false, + "creator_subscriptions_quote_tweet_preview_enabled": false, + "c9s_tweet_anatomy_moderator_badge_enabled": false, + "responsive_web_grok_analyze_post_followups_enabled": false, + "rweb_video_timestamps_enabled": false, + "responsive_web_grok_share_attachment_enabled": false, + "articles_preview_enabled": false, + "immersive_video_status_linkable_timestamps": false, + "articles_api_enabled": false, + "responsive_web_grok_analysis_button_from_backend": false }""".replace(" ", "").replace("\n", "") tweetVariables* = """{ diff --git a/src/parser.nim b/src/parser.nim index b8812d2..4bdb1e2 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -352,18 +352,23 @@ proc parseGraphTweetResult*(js: JsonNode): Tweet = with tweet, js{"data", "tweet_result", "result"}: result = parseGraphTweet(tweet, false) -proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = +proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) - let instructions = ? js{"data", "threaded_conversation_with_injections_v2", "instructions"} + let + rootKey = if v2: "timeline_response" else: "threaded_conversation_with_injections_v2" + contentKey = if v2: "content" else: "itemContent" + resultKey = if v2: "tweetResult" else: "tweet_results" + + let instructions = ? js{"data", rootKey, "instructions"} if instructions.len == 0: return for e in instructions[0]{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): - with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetResult, true) + with tweetResult, e{"content", contentKey, resultKey, "result"}: + let tweet = parseGraphTweet(tweetResult, not v2) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) @@ -372,26 +377,26 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result.tweet = tweet else: result.before.content.add tweet - elif entryId.startsWith("tombstone"): - let id = entryId.getId() - let tweet = Tweet( - id: parseBiggestInt(id), - available: false, - text: e{"content", "itemContent", "tombstoneInfo", "richText"}.getTombstone - ) - - if id == tweetId: - result.tweet = tweet - else: - result.before.content.add tweet elif entryId.startsWith("conversationthread"): let (thread, self) = parseGraphThread(e) if self: result.after = thread else: result.replies.content.add thread + elif entryId.startsWith("tombstone"): + let id = entryId.getId() + let tweet = Tweet( + id: parseBiggestInt(id), + available: false, + text: e{"content", contentKey, "tombstoneInfo", "richText"}.getTombstone + ) + + if id == tweetId: + result.tweet = tweet + else: + result.before.content.add tweet elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", "itemContent", "value"}.getStr + result.replies.bottom = e{"content", contentKey, "value"}.getStr proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) From b43bfc5d4255dacf8d19575959a9407c1444777b Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 01:19:27 +0100 Subject: [PATCH 091/302] Return 403 on hmac error --- src/routes/media.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/media.nim b/src/routes/media.nim index eacd1f8..ac76ab6 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -122,7 +122,7 @@ proc createMediaRouter*(cfg: Config) = cond "http" in url if getHmac(url) != request.matches[1]: - resp showError("Failed to verify signature", cfg) + resp Http403, showError("Failed to verify signature", cfg) if ".mp4" in url or ".ts" in url or ".m4s" in url: let code = await proxyMedia(request, url) From 77288999485d14070f119813a26a9bd21728356b Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 03:18:49 +0100 Subject: [PATCH 092/302] Add lazy loading for images --- src/views/renderutils.nim | 2 +- src/views/tweet.nim | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index f298fad..41ef8df 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -91,7 +91,7 @@ proc genDate*(pref, state: string): VNode = proc genImg*(url: string; class=""): VNode = buildHtml(): - img(src=getPicUrl(url), class=class, alt="") + img(src=getPicUrl(url), class=class, alt="", loading="lazy") proc getTabClass*(query: Query; tab: QueryKind): string = if query.kind == tab: "tab-item active" diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 2fe4ac9..34dcd4c 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -10,9 +10,7 @@ import general const doctype = "<!DOCTYPE html>\n" proc renderMiniAvatar(user: User; prefs: Prefs): VNode = - let url = getPicUrl(user.getUserPic("_mini")) - buildHtml(): - img(class=(prefs.getAvatarClass & " mini"), src=url) + genImg(user.getUserPic("_mini"), class=(prefs.getAvatarClass & " mini")) proc renderHeader(tweet: Tweet; retweet: string; pinned: bool; prefs: Prefs): VNode = buildHtml(tdiv): @@ -92,10 +90,10 @@ proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode = tdiv(class="attachment video-container"): let thumb = getSmallPic(video.thumb) if not video.available: - img(src=thumb) + img(src=thumb, loading="lazy") renderVideoUnavailable(video) elif not prefs.isPlaybackEnabled(playbackType): - img(src=thumb) + img(src=thumb, loading="lazy") renderVideoDisabled(playbackType, path) else: let @@ -144,7 +142,7 @@ proc renderPoll(poll: Poll): VNode = proc renderCardImage(card: Card): VNode = buildHtml(tdiv(class="card-image-container")): tdiv(class="card-image"): - img(src=getPicUrl(card.image), alt="") + genImg(card.image) if card.kind == player: tdiv(class="card-overlay"): tdiv(class="overlay-circle"): From c0f2eea27659815b5bdc692f409bfe675a8892a4 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 03:19:10 +0100 Subject: [PATCH 093/302] Fix missing video thumbnail being too small --- src/sass/tweet/video.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sass/tweet/video.scss b/src/sass/tweet/video.scss index 1e9096e..98a1c29 100644 --- a/src/sass/tweet/video.scss +++ b/src/sass/tweet/video.scss @@ -16,6 +16,8 @@ video { } .video-container { + min-height: 80px; + min-width: 200px; max-height: 530px; margin: 0; display: flex; From 5edaea23593ebfc28dcd7632445f0144c6242ef6 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 03:20:05 +0100 Subject: [PATCH 094/302] Silence 404 proxy errors --- src/routes/media.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/media.nim b/src/routes/media.nim index ac76ab6..de51061 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -37,7 +37,8 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = try: let res = await client.get(url) if res.status != "200 OK": - echo "[media] Proxying failed, status: $1, url: $2" % [res.status, url] + if res.status != "404 Not Found": + echo "[media] Proxying failed, status: $1, url: $2" % [res.status, url] return Http404 let hashed = $hash(url) From 5265de101d63b8d5dade547a8d5a994457643493 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 03:49:17 +0100 Subject: [PATCH 095/302] Skip null fetch errors --- src/apiutils.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index d7842b1..0a6e0d2 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -126,10 +126,11 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = result = newJNull() let error = result.getError - echo "Fetch error, API: ", api, ", error: ", error - if error in {expiredToken, badToken, locked}: - invalidate(account) - raise rateLimitError() + if error != null: + echo "Fetch error, API: ", api, ", error: ", error + if error in {expiredToken, badToken, locked}: + invalidate(account) + raise rateLimitError() proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = retry: From afad55749be0c6e2c51d495e7a22c09bdfdc6410 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 03:49:34 +0100 Subject: [PATCH 096/302] Increase max concurrent reqs per account --- src/auth.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth.nim b/src/auth.nim index fe265c9..85fe4a7 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -5,7 +5,7 @@ import experimental/parser/guestaccount # max requests at a time per account to avoid race conditions const - maxConcurrentReqs = 2 + maxConcurrentReqs = 3 dayInSeconds = 24 * 60 * 60 apiMaxReqs: Table[Api, int] = { Api.search: 50, From 6fcd849eff51ad1ee6e6078e3236896ab97803b6 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 04:09:36 +0100 Subject: [PATCH 097/302] Rename accounts/guest accounts to sessions The new file loaded by default is now ./sessions.jsonl JSONL is also required, .json support dropped. --- .gitignore | 1 + nitter.example.conf | 2 +- src/apiutils.nim | 28 ++-- src/auth.nim | 147 +++++++++--------- src/experimental/parser/guestaccount.nim | 21 --- src/experimental/parser/session.nim | 15 ++ .../types/{guestaccount.nim => session.nim} | 2 +- src/nitter.nim | 8 +- src/routes/debug.nim | 6 +- src/types.nim | 4 +- 10 files changed, 114 insertions(+), 120 deletions(-) delete mode 100644 src/experimental/parser/guestaccount.nim create mode 100644 src/experimental/parser/session.nim rename src/experimental/types/{guestaccount.nim => session.nim} (71%) diff --git a/.gitignore b/.gitignore index ea520dc..dbd2f6b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ nitter /public/md/*.html nitter.conf guest_accounts.json* +sessions.json* dump.rdb diff --git a/nitter.example.conf b/nitter.example.conf index f0b4214..360e07b 100644 --- a/nitter.example.conf +++ b/nitter.example.conf @@ -23,7 +23,7 @@ redisMaxConnections = 30 hmacKey = "secretkey" # random key for cryptographic signing of video urls base64Media = false # use base64 encoding for proxied media urls enableRSS = true # set this to false to disable RSS feeds -enableDebug = false # enable request logs and debug endpoints (/.accounts) +enableDebug = false # enable request logs and debug endpoints (/.sessions) proxy = "" # http/https url, SOCKS proxies are not supported proxyAuth = "" tokenCount = 10 diff --git a/src/apiutils.nim b/src/apiutils.nim index 0a6e0d2..3a0d12c 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -46,14 +46,14 @@ template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() - var account = await getGuestAccount(api) - if account.oauthToken.len == 0: - echo "[accounts] Empty oauth token, account: ", account.id + var session = await getSession(api) + if session.oauthToken.len == 0: + echo "[sessions] Empty oauth token, session: ", session.id raise rateLimitError() try: var resp: AsyncResponse - pool.use(genHeaders($url, account.oauthToken, account.oauthSecret)): + pool.use(genHeaders($url, session.oauthToken, session.oauthSecret)): template getContent = resp = await c.get($url) result = await resp.body @@ -68,7 +68,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = let remaining = parseInt(resp.headers[rlRemaining]) reset = parseInt(resp.headers[rlReset]) - account.setRateLimit(api, remaining, reset) + session.setRateLimit(api, remaining, reset) if result.len > 0: if resp.headers.getOrDefault("content-encoding") == "gzip": @@ -78,15 +78,15 @@ template fetchImpl(result, fetchBody) {.dirty.} = let errors = result.fromJson(Errors) echo "Fetch error, API: ", api, ", errors: ", errors if errors in {expiredToken, badToken, locked}: - invalidate(account) + invalidate(session) raise rateLimitError() elif errors in {rateLimited}: # rate limit hit, resets after 24 hours - setLimited(account, api) + setLimited(session, api) raise rateLimitError() elif result.startsWith("429 Too Many Requests"): - echo "[accounts] 429 error, API: ", api, ", account: ", account.id - account.apis[api].remaining = 0 + echo "[sessions] 429 error, API: ", api, ", session: ", session.id + session.apis[api].remaining = 0 # rate limit hit, resets after the 15 minute window raise rateLimitError() @@ -102,17 +102,17 @@ template fetchImpl(result, fetchBody) {.dirty.} = except OSError as e: raise e except Exception as e: - let id = if account.isNil: "null" else: $account.id - echo "error: ", e.name, ", msg: ", e.msg, ", accountId: ", id, ", url: ", url + let id = if session.isNil: "null" else: $session.id + echo "error: ", e.name, ", msg: ", e.msg, ", sessionId: ", id, ", url: ", url raise rateLimitError() finally: - release(account) + release(session) template retry(bod) = try: bod except RateLimitError: - echo "[accounts] Rate limited, retrying ", api, " request..." + echo "[sessions] Rate limited, retrying ", api, " request..." bod proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = @@ -129,7 +129,7 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = if error != null: echo "Fetch error, API: ", api, ", error: ", error if error in {expiredToken, badToken, locked}: - invalidate(account) + invalidate(session) raise rateLimitError() proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = diff --git a/src/auth.nim b/src/auth.nim index 85fe4a7..c57008b 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -1,9 +1,9 @@ #SPDX-License-Identifier: AGPL-3.0-only import std/[asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os] import types -import experimental/parser/guestaccount +import experimental/parser/session -# max requests at a time per account to avoid race conditions +# max requests at a time per session to avoid race conditions const maxConcurrentReqs = 3 dayInSeconds = 24 * 60 * 60 @@ -23,16 +23,16 @@ const }.toTable var - accountPool: seq[GuestAccount] + sessionPool: seq[Session] enableLogging = false template log(str: varargs[string, `$`]) = - if enableLogging: echo "[accounts] ", str.join("") + if enableLogging: echo "[sessions] ", str.join("") proc snowflakeToEpoch(flake: int64): int64 = int64(((flake shr 22) + 1288834974657) div 1000) -proc getAccountPoolHealth*(): JsonNode = +proc getSessionPoolHealth*(): JsonNode = let now = epochTime().int var @@ -43,38 +43,38 @@ proc getAccountPoolHealth*(): JsonNode = newest = 0'i64 average = 0'i64 - for account in accountPool: - let created = snowflakeToEpoch(account.id) + for session in sessionPool: + let created = snowflakeToEpoch(session.id) if created > newest: newest = created if created < oldest: oldest = created average += created - if account.limited: - limited.incl account.id + if session.limited: + limited.incl session.id - for api in account.apis.keys: + for api in session.apis.keys: let - apiStatus = account.apis[api] + apiStatus = session.apis[api] reqs = apiMaxReqs[api] - apiStatus.remaining - # no requests made with this account and endpoint since the limit reset + # no requests made with this session and endpoint since the limit reset if apiStatus.reset < now: continue reqsPerApi.mgetOrPut($api, 0).inc reqs totalReqs.inc reqs - if accountPool.len > 0: - average = average div accountPool.len + if sessionPool.len > 0: + average = average div sessionPool.len else: oldest = 0 average = 0 return %*{ - "accounts": %*{ - "total": accountPool.len, + "sessions": %*{ + "total": sessionPool.len, "limited": limited.card, "oldest": $fromUnix(oldest), "newest": $fromUnix(newest), @@ -86,22 +86,22 @@ proc getAccountPoolHealth*(): JsonNode = } } -proc getAccountPoolDebug*(): JsonNode = +proc getSessionPoolDebug*(): JsonNode = let now = epochTime().int var list = newJObject() - for account in accountPool: - let accountJson = %*{ + for session in sessionPool: + let sessionJson = %*{ "apis": newJObject(), - "pending": account.pending, + "pending": session.pending, } - if account.limited: - accountJson["limited"] = %true + if session.limited: + sessionJson["limited"] = %true - for api in account.apis.keys: + for api in session.apis.keys: let - apiStatus = account.apis[api] + apiStatus = session.apis[api] obj = %*{} if apiStatus.reset > now.int: @@ -111,92 +111,91 @@ proc getAccountPoolDebug*(): JsonNode = if "remaining" notin obj: continue - accountJson{"apis", $api} = obj - list[$account.id] = accountJson + sessionJson{"apis", $api} = obj + list[$session.id] = sessionJson return %list proc rateLimitError*(): ref RateLimitError = newException(RateLimitError, "rate limited") -proc noAccountsError*(): ref NoAccountsError = - newException(NoAccountsError, "no accounts available") +proc noSessionsError*(): ref NoSessionsError = + newException(NoSessionsError, "no sessions available") -proc isLimited(account: GuestAccount; api: Api): bool = - if account.isNil: +proc isLimited(session: Session; api: Api): bool = + if session.isNil: return true - if account.limited and api != Api.userTweets: - if (epochTime().int - account.limitedAt) > dayInSeconds: - account.limited = false - log "resetting limit: ", account.id + if session.limited and api != Api.userTweets: + if (epochTime().int - session.limitedAt) > dayInSeconds: + session.limited = false + log "resetting limit: ", session.id else: return false - if api in account.apis: - let limit = account.apis[api] + if api in session.apis: + let limit = session.apis[api] return limit.remaining <= 10 and limit.reset > epochTime().int else: return false -proc isReady(account: GuestAccount; api: Api): bool = - not (account.isNil or account.pending > maxConcurrentReqs or account.isLimited(api)) +proc isReady(session: Session; api: Api): bool = + not (session.isNil or session.pending > maxConcurrentReqs or session.isLimited(api)) -proc invalidate*(account: var GuestAccount) = - if account.isNil: return - log "invalidating: ", account.id +proc invalidate*(session: var Session) = + if session.isNil: return + log "invalidating: ", session.id # TODO: This isn't sufficient, but it works for now - let idx = accountPool.find(account) - if idx > -1: accountPool.delete(idx) - account = nil + let idx = sessionPool.find(session) + if idx > -1: sessionPool.delete(idx) + session = nil -proc release*(account: GuestAccount) = - if account.isNil: return - dec account.pending +proc release*(session: Session) = + if session.isNil: return + dec session.pending -proc getGuestAccount*(api: Api): Future[GuestAccount] {.async.} = - for i in 0 ..< accountPool.len: +proc getSession*(api: Api): Future[Session] {.async.} = + for i in 0 ..< sessionPool.len: if result.isReady(api): break - result = accountPool.sample() + result = sessionPool.sample() if not result.isNil and result.isReady(api): inc result.pending else: - log "no accounts available for API: ", api - raise noAccountsError() + log "no sessions available for API: ", api + raise noSessionsError() -proc setLimited*(account: GuestAccount; api: Api) = - account.limited = true - account.limitedAt = epochTime().int - log "rate limited by api: ", api, ", reqs left: ", account.apis[api].remaining, ", id: ", account.id +proc setLimited*(session: Session; api: Api) = + session.limited = true + session.limitedAt = epochTime().int + log "rate limited by api: ", api, ", reqs left: ", session.apis[api].remaining, ", id: ", session.id -proc setRateLimit*(account: GuestAccount; api: Api; remaining, reset: int) = +proc setRateLimit*(session: Session; api: Api; remaining, reset: int) = # avoid undefined behavior in race conditions - if api in account.apis: - let limit = account.apis[api] + if api in session.apis: + let limit = session.apis[api] if limit.reset >= reset and limit.remaining < remaining: return if limit.reset == reset and limit.remaining >= remaining: - account.apis[api].remaining = remaining + session.apis[api].remaining = remaining return - account.apis[api] = RateLimit(remaining: remaining, reset: reset) + session.apis[api] = RateLimit(remaining: remaining, reset: reset) -proc initAccountPool*(cfg: Config; path: string) = +proc initSessionPool*(cfg: Config; path: string) = enableLogging = cfg.enableDebug - let jsonlPath = if path.endsWith(".json"): (path & 'l') else: path - - if fileExists(jsonlPath): - log "Parsing JSONL guest accounts file: ", jsonlPath - for line in jsonlPath.lines: - accountPool.add parseGuestAccount(line) - elif fileExists(path): - log "Parsing JSON guest accounts file: ", path - accountPool = parseGuestAccounts(path) - else: - echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests." + if path.endsWith(".json"): + echo "[sessions] ERROR: .json is not supported, the file must be a valid JSONL file ending in .jsonl" quit 1 - log "Successfully added ", accountPool.len, " valid accounts." + if not fileExists(path): + echo "[sessions] ERROR: ", path, " not found. This file is required to authenticate API requests." + quit 1 + + log "Parsing JSONL account sessions file: ", path + for line in path.lines: + sessionPool.add parseSession(line) + + log "Successfully added ", sessionPool.len, " valid account sessions." diff --git a/src/experimental/parser/guestaccount.nim b/src/experimental/parser/guestaccount.nim deleted file mode 100644 index f7e6d34..0000000 --- a/src/experimental/parser/guestaccount.nim +++ /dev/null @@ -1,21 +0,0 @@ -import std/strutils -import jsony -import ../types/guestaccount -from ../../types import GuestAccount - -proc toGuestAccount(account: RawAccount): GuestAccount = - let id = account.oauthToken[0 ..< account.oauthToken.find('-')] - result = GuestAccount( - id: parseBiggestInt(id), - oauthToken: account.oauthToken, - oauthSecret: account.oauthTokenSecret - ) - -proc parseGuestAccount*(raw: string): GuestAccount = - let rawAccount = raw.fromJson(RawAccount) - result = rawAccount.toGuestAccount - -proc parseGuestAccounts*(path: string): seq[GuestAccount] = - let rawAccounts = readFile(path).fromJson(seq[RawAccount]) - for account in rawAccounts: - result.add account.toGuestAccount diff --git a/src/experimental/parser/session.nim b/src/experimental/parser/session.nim new file mode 100644 index 0000000..ee9c93e --- /dev/null +++ b/src/experimental/parser/session.nim @@ -0,0 +1,15 @@ +import std/strutils +import jsony +import ../types/session +from ../../types import Session + +proc parseSession*(raw: string): Session = + let + session = raw.fromJson(RawSession) + id = session.oauthToken[0 ..< session.oauthToken.find('-')] + + result = Session( + id: parseBiggestInt(id), + oauthToken: session.oauthToken, + oauthSecret: session.oauthTokenSecret + ) diff --git a/src/experimental/types/guestaccount.nim b/src/experimental/types/session.nim similarity index 71% rename from src/experimental/types/guestaccount.nim rename to src/experimental/types/session.nim index 244edb3..4165204 100644 --- a/src/experimental/types/guestaccount.nim +++ b/src/experimental/types/session.nim @@ -1,4 +1,4 @@ type - RawAccount* = object + RawSession* = object oauthToken*: string oauthTokenSecret*: string diff --git a/src/nitter.nim b/src/nitter.nim index 958dc0b..f81dc1c 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -19,9 +19,9 @@ let configPath = getEnv("NITTER_CONF_FILE", "./nitter.conf") (cfg, fullCfg) = getConfig(configPath) - accountsPath = getEnv("NITTER_ACCOUNTS_FILE", "./guest_accounts.json") + sessionsPath = getEnv("NITTER_SESSIONS_FILE", "./sessions.jsonl") -initAccountPool(cfg, accountsPath) +initSessionPool(cfg, sessionsPath) if not cfg.enableDebug: # Silence Jester's query warning @@ -97,10 +97,10 @@ routes: resp Http429, showError( &"Instance has been rate limited.<br>Use {link} or try again later.", cfg) - error NoAccountsError: + error NoSessionsError: const link = a("another instance", href = instancesUrl) resp Http429, showError( - &"Instance has no available accounts.<br>Use {link} or try again later.", cfg) + &"Instance has no auth tokens, or is fully rate limited.<br>Use {link} or try again later.", cfg) extend rss, "" extend status, "" diff --git a/src/routes/debug.nim b/src/routes/debug.nim index 895a285..97c5bef 100644 --- a/src/routes/debug.nim +++ b/src/routes/debug.nim @@ -6,8 +6,8 @@ import ".."/[auth, types] proc createDebugRouter*(cfg: Config) = router debug: get "/.health": - respJson getAccountPoolHealth() + respJson getSessionPoolHealth() - get "/.accounts": + get "/.sessions": cond cfg.enableDebug - respJson getAccountPoolDebug() + respJson getSessionPoolDebug() diff --git a/src/types.nim b/src/types.nim index 685fbad..6c1f1b6 100644 --- a/src/types.nim +++ b/src/types.nim @@ -6,7 +6,7 @@ genPrefsType() type RateLimitError* = object of CatchableError - NoAccountsError* = object of CatchableError + NoSessionsError* = object of CatchableError InternalError* = object of CatchableError BadClientError* = object of CatchableError @@ -31,7 +31,7 @@ type remaining*: int reset*: int - GuestAccount* = ref object + Session* = ref object id*: int64 oauthToken*: string oauthSecret*: string From 4d5091947c81e181f34f173e4fe0c423e163f9ed Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 04:24:35 +0100 Subject: [PATCH 098/302] Update Dockerfiles --- .github/workflows/run-tests.yml | 4 ++-- Dockerfile | 4 ++-- Dockerfile.arm64 | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ee28e33..a273771 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,9 +14,9 @@ jobs: strategy: matrix: nim: - - "1.6.10" - "1.6.x" - "2.0.x" + - "2.2.x" - "devel" steps: - uses: actions/checkout@v3 @@ -49,7 +49,7 @@ jobs: sed -i 's/enableDebug = false/enableDebug = true/g' nitter.conf nimble md nimble scss - echo '${{ secrets.GUEST_ACCOUNTS }}' > ./guest_accounts.jsonl + echo '${{ secrets.SESSIONS }}' > ./sessions.jsonl - name: Run tests run: | ./nitter & diff --git a/Dockerfile b/Dockerfile index 138dc64..ab442ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nimlang/nim:2.0.0-alpine-regular as nim +FROM nimlang/nim:2.2.0-alpine-regular as nim LABEL maintainer="setenforce@protonmail.com" RUN apk --no-cache add libsass-dev pcre @@ -9,7 +9,7 @@ COPY nitter.nimble . RUN nimble install -y --depsOnly COPY . . -RUN nimble build -d:danger -d:lto -d:strip \ +RUN nimble build -d:danger -d:lto -d:strip --mm:refc \ && nimble scss \ && nimble md diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 70024b2..bf6011f 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,7 +1,7 @@ -FROM alpine:3.18 as nim +FROM alpine:3.21.2 as nim LABEL maintainer="setenforce@protonmail.com" -RUN apk --no-cache add libsass-dev pcre gcc git libc-dev "nim=1.6.14-r0" "nimble=0.13.1-r2" +RUN apk --no-cache add libsass-dev pcre gcc git libc-dev nim nimble WORKDIR /src/nitter @@ -9,13 +9,13 @@ COPY nitter.nimble . RUN nimble install -y --depsOnly COPY . . -RUN nimble build -d:danger -d:lto -d:strip \ +RUN nimble build -d:danger -d:lto -d:strip --mm:refc \ && nimble scss \ && nimble md -FROM alpine:3.18 +FROM alpine:3.21.2 WORKDIR /src/ -RUN apk --no-cache add pcre ca-certificates openssl1.1-compat +RUN apk --no-cache add pcre ca-certificates openssl COPY --from=nim /src/nitter/nitter ./ COPY --from=nim /src/nitter/nitter.example.conf ./nitter.conf COPY --from=nim /src/nitter/public ./public From a3d341e7a68f721fa9f43a5849daebd1959b174c Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 18:10:15 +0100 Subject: [PATCH 099/302] Update README, added an important note --- README.md | 57 +++++++++++++++++++++++++++------------------- public/md/about.md | 44 ++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 4f8235d..05c2be4 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,35 @@ [![Test Matrix](https://github.com/zedeus/nitter/workflows/Docker/badge.svg)](https://github.com/zedeus/nitter/actions/workflows/build-docker.yml) [![License](https://img.shields.io/github/license/zedeus/nitter?style=flat)](#license) +> [!NOTE] +> Running a Nitter instance now requires real accounts, since Twitter removed the previous methods. \ +> This does not affect users. \ +> For instructions on how to obtain session tokens, see [Creating session tokens](https://github.com/zedeus/nitter/wiki/Creating-session-tokens). + A free and open source alternative Twitter front-end focused on privacy and performance. \ -Inspired by the [Invidious](https://github.com/iv-org/invidious) -project. +Inspired by the [Invidious](https://github.com/iv-org/invidious) project. - No JavaScript or ads - All requests go through the backend, client never talks to Twitter - Prevents Twitter from tracking your IP or JavaScript fingerprint -- Uses Twitter's unofficial API (no rate limits or developer account required) +- Uses Twitter's unofficial API (no developer account required) - Lightweight (for [@nim_lang](https://nitter.net/nim_lang), 60KB vs 784KB from twitter.com) - RSS feeds - Themes - Mobile support (responsive design) - AGPLv3 licensed, no proprietary instances permitted -Liberapay: https://liberapay.com/zedeus \ -Patreon: https://patreon.com/nitter \ -BTC: bc1qp7q4qz0fgfvftm5hwz3vy284nue6jedt44kxya \ -ETH: 0x66d84bc3fd031b62857ad18c62f1ba072b011925 \ -LTC: ltc1qhsz5nxw6jw9rdtw9qssjeq2h8hqk2f85rdgpkr \ -XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL +<details> +<summary>Donations</summary> +Liberapay: https://liberapay.com/zedeus<br> +Patreon: https://patreon.com/nitter<br> +BTC: bc1qpqpzjkcpgluhzf7x9yqe7jfe8gpfm5v08mdr55<br> +ETH: 0x24a0DB59A923B588c7A5EBd0dBDFDD1bCe9c4460<br> +XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL<br> +SOL: ANsyGNXFo6osuFwr1YnUqif2RdoYRhc27WdyQNmmETSW<br> +ZEC: u1vndfqtzyy6qkzhkapxelel7ams38wmfeccu3fdpy2wkuc4erxyjm8ncjhnyg747x6t0kf0faqhh2hxyplgaum08d2wnj4n7cyu9s6zhxkqw2aef4hgd4s6vh5hpqvfken98rg80kgtgn64ff70djy7s8f839z00hwhuzlcggvefhdlyszkvwy3c7yw623vw3rvar6q6evd3xcvveypt +</details> ## Roadmap @@ -42,12 +50,13 @@ maintained by the community. ## Why? -It's impossible to use Twitter without JavaScript enabled. For privacy-minded -folks, preventing JavaScript analytics and IP-based tracking is important, but -apart from using a VPN and uBlock/uMatrix, it's impossible. Despite being behind -a VPN and using heavy-duty adblockers, you can get accurately tracked with your -[browser's fingerprint](https://restoreprivacy.com/browser-fingerprinting/), -[no JavaScript required](https://noscriptfingerprint.com/). This all became +It's impossible to use Twitter without JavaScript enabled, and as of 2024 you +need to sign up. For privacy-minded folks, preventing JavaScript analytics and +IP-based tracking is important, but apart from using a VPN and uBlock/uMatrix, +it's impossible. Despite being behind a VPN and using heavy-duty adblockers, +you can get accurately tracked with your [browser's +fingerprint](https://restoreprivacy.com/browser-fingerprinting/), [no +JavaScript required](https://noscriptfingerprint.com/). This all became particularly important after Twitter [removed the ability](https://www.eff.org/deeplinks/2020/04/twitter-removes-privacy-option-and-shows-why-we-need-strong-privacy-laws) for users to control whether their data gets sent to advertisers. @@ -71,19 +80,21 @@ Twitter account. - libpcre - libsass -- redis +- redis/valkey To compile Nitter you need a Nim installation, see -[nim-lang.org](https://nim-lang.org/install.html) for details. It is possible to -install it system-wide or in the user directory you create below. +[nim-lang.org](https://nim-lang.org/install.html) for details. It is possible +to install it system-wide or in the user directory you create below. To compile the scss files, you need to install `libsass`. On Ubuntu and Debian, you can use `libsass-dev`. -Redis is required for caching and in the future for account info. It should be -available on most distros as `redis` or `redis-server` (Ubuntu/Debian). -Running it with the default config is fine, Nitter's default config is set to -use the default Redis port and localhost. +Redis is required for caching and in the future for account info. As of 2024 +Redis is no longer open source, so using the fork Valkey is recommended. It +should be available on most distros as `redis` or `redis-server` +(Ubuntu/Debian), or `valkey`/`valkey-server`. Running it with the default +config is fine, Nitter's default config is set to use the default port and +localhost. Here's how to create a `nitter` user, clone the repo, and build the project along with the scss and md files. @@ -93,7 +104,7 @@ along with the scss and md files. # su nitter $ git clone https://github.com/zedeus/nitter $ cd nitter -$ nimble build -d:release +$ nimble build -d:danger --mm:refc $ nimble scss $ nimble md $ cp nitter.example.conf nitter.conf diff --git a/public/md/about.md b/public/md/about.md index c0adda9..3825e8f 100644 --- a/public/md/about.md +++ b/public/md/about.md @@ -4,15 +4,15 @@ Nitter is a free and open source alternative Twitter front-end focused on privacy and performance. The source is available on GitHub at <https://github.com/zedeus/nitter> -* No JavaScript or ads -* All requests go through the backend, client never talks to Twitter -* Prevents Twitter from tracking your IP or JavaScript fingerprint -* Uses Twitter's unofficial API (no rate limits or developer account required) -* Lightweight (for [@nim_lang](/nim_lang), 60KB vs 784KB from twitter.com) -* RSS feeds -* Themes -* Mobile support (responsive design) -* AGPLv3 licensed, no proprietary instances permitted +- No JavaScript or ads +- All requests go through the backend, client never talks to Twitter +- Prevents Twitter from tracking your IP or JavaScript fingerprint +- Uses Twitter's unofficial API (no developer account required) +- Lightweight (for [@nim_lang](/nim_lang), 60KB vs 784KB from twitter.com) +- RSS feeds +- Themes +- Mobile support (responsive design) +- AGPLv3 licensed, no proprietary instances permitted Nitter's GitHub wiki contains [instances](https://github.com/zedeus/nitter/wiki/Instances) and @@ -21,12 +21,13 @@ maintained by the community. ## Why use Nitter? -It's impossible to use Twitter without JavaScript enabled. For privacy-minded -folks, preventing JavaScript analytics and IP-based tracking is important, but -apart from using a VPN and uBlock/uMatrix, it's impossible. Despite being behind -a VPN and using heavy-duty adblockers, you can get accurately tracked with your -[browser's fingerprint](https://restoreprivacy.com/browser-fingerprinting/), -[no JavaScript required](https://noscriptfingerprint.com/). This all became +It's impossible to use Twitter without JavaScript enabled, and as of 2024 you +need to sign up. For privacy-minded folks, preventing JavaScript analytics and +IP-based tracking is important, but apart from using a VPN and uBlock/uMatrix, +it's impossible. Despite being behind a VPN and using heavy-duty adblockers, +you can get accurately tracked with your [browser's +fingerprint](https://restoreprivacy.com/browser-fingerprinting/), [no +JavaScript required](https://noscriptfingerprint.com/). This all became particularly important after Twitter [removed the ability](https://www.eff.org/deeplinks/2020/04/twitter-removes-privacy-option-and-shows-why-we-need-strong-privacy-laws) for users to control whether their data gets sent to advertisers. @@ -42,12 +43,13 @@ Twitter account. ## Donating -Liberapay: <https://liberapay.com/zedeus> \ -Patreon: <https://patreon.com/nitter> \ -BTC: bc1qp7q4qz0fgfvftm5hwz3vy284nue6jedt44kxya \ -ETH: 0x66d84bc3fd031b62857ad18c62f1ba072b011925 \ -LTC: ltc1qhsz5nxw6jw9rdtw9qssjeq2h8hqk2f85rdgpkr \ -XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL +Liberapay: https://liberapay.com/zedeus \ +Patreon: https://patreon.com/nitter \ +BTC: bc1qpqpzjkcpgluhzf7x9yqe7jfe8gpfm5v08mdr55 \ +ETH: 0x24a0DB59A923B588c7A5EBd0dBDFDD1bCe9c4460 \ +XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL \ +SOL: ANsyGNXFo6osuFwr1YnUqif2RdoYRhc27WdyQNmmETSW \ +ZEC: u1vndfqtzyy6qkzhkapxelel7ams38wmfeccu3fdpy2wkuc4erxyjm8ncjhnyg747x6t0kf0faqhh2hxyplgaum08d2wnj4n7cyu9s6zhxkqw2aef4hgd4s6vh5hpqvfken98rg80kgtgn64ff70djy7s8f839z00hwhuzlcggvefhdlyszkvwy3c7yw623vw3rvar6q6evd3xcvveypt ## Contact From 10b1d9c80f7c8c70f86f12c6ea4f0889651521bd Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 19:03:47 +0100 Subject: [PATCH 100/302] Add Python script to create account sessions --- tools/get_session.py | 161 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 tools/get_session.py diff --git a/tools/get_session.py b/tools/get_session.py new file mode 100644 index 0000000..9ff704d --- /dev/null +++ b/tools/get_session.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +import requests +import json +import sys +import pyotp + +# NOTE: pyotp and requests are dependencies +# > pip install pyotp requests + +TW_CONSUMER_KEY = '3nVuSoBZnx6U4vzUxf5w' +TW_CONSUMER_SECRET = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys' + +def auth(username, password, otp_secret): + bearer_token_req = requests.post("https://api.twitter.com/oauth2/token", + auth=(TW_CONSUMER_KEY, TW_CONSUMER_SECRET), + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data='grant_type=client_credentials' + ).json() + bearer_token = ' '.join(str(x) for x in bearer_token_req.values()) + + guest_token = requests.post( + "https://api.twitter.com/1.1/guest/activate.json", + headers={'Authorization': bearer_token} + ).json().get('guest_token') + + if not guest_token: + print("Failed to obtain guest token.") + sys.exit(1) + + twitter_header = { + 'Authorization': bearer_token, + "Content-Type": "application/json", + "User-Agent": "TwitterAndroid/10.21.0-release.0 (310210000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)", + "X-Twitter-API-Version": '5', + "X-Twitter-Client": "TwitterAndroid", + "X-Twitter-Client-Version": "10.21.0-release.0", + "OS-Version": "28", + "System-User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)", + "X-Twitter-Active-User": "yes", + "X-Guest-Token": guest_token, + "X-Twitter-Client-DeviceID": "" + } + + session = requests.Session() + session.headers = twitter_header + + task1 = session.post( + 'https://api.twitter.com/1.1/onboarding/task.json', + params={ + 'flow_name': 'login', + 'api_version': '1', + 'known_device_token': '', + 'sim_country_code': 'us' + }, + json={ + "flow_token": None, + "input_flow_data": { + "country_code": None, + "flow_context": { + "referrer_context": { + "referral_details": "utm_source=google-play&utm_medium=organic", + "referrer_url": "" + }, + "start_location": { + "location": "deeplink" + } + }, + "requested_variant": None, + "target_user_id": 0 + } + } + ) + + session.headers['att'] = task1.headers.get('att') + + task2 = session.post( + 'https://api.twitter.com/1.1/onboarding/task.json', + json={ + "flow_token": task1.json().get('flow_token'), + "subtask_inputs": [{ + "enter_text": { + "suggestion_id": None, + "text": username, + "link": "next_link" + }, + "subtask_id": "LoginEnterUserIdentifier" + }] + } + ) + + task3 = session.post( + 'https://api.twitter.com/1.1/onboarding/task.json', + json={ + "flow_token": task2.json().get('flow_token'), + "subtask_inputs": [{ + "enter_password": { + "password": password, + "link": "next_link" + }, + "subtask_id": "LoginEnterPassword" + }], + } + ) + + for t3_subtask in task3.json().get('subtasks', []): + if "open_account" in t3_subtask: + return t3_subtask["open_account"] + elif "enter_text" in t3_subtask: + response_text = t3_subtask["enter_text"]["hint_text"] + totp = pyotp.TOTP(otp_secret) + generated_code = totp.now() + task4resp = session.post( + "https://api.twitter.com/1.1/onboarding/task.json", + json={ + "flow_token": task3.json().get("flow_token"), + "subtask_inputs": [ + { + "enter_text": { + "suggestion_id": None, + "text": generated_code, + "link": "next_link", + }, + "subtask_id": "LoginTwoFactorAuthChallenge", + } + ], + } + ) + task4 = task4resp.json() + for t4_subtask in task4.get("subtasks", []): + if "open_account" in t4_subtask: + return t4_subtask["open_account"] + + return None + +if __name__ == "__main__": + if len(sys.argv) != 5: + print("Usage: python3 get_session.py <username> <password> <2fa secret> <path>") + sys.exit(1) + + username = sys.argv[1] + password = sys.argv[2] + otp_secret = sys.argv[3] + path = sys.argv[4] + + result = auth(username, password, otp_secret) + if result is None: + print("Authentication failed.") + sys.exit(1) + + session_entry = { + "oauth_token": result.get("oauth_token"), + "oauth_token_secret": result.get("oauth_token_secret") + } + + try: + with open(path, "a") as f: + f.write(json.dumps(session_entry) + "\n") + print("Authentication successful. Session appended to", path) + except Exception as e: + print(f"Failed to write session information: {e}") + sys.exit(1) From b9af77a9bd460297b9063490d78006af05345e28 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 19:28:10 +0100 Subject: [PATCH 101/302] Change main page search to "Tweets" search --- src/views/search.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/search.nim b/src/views/search.nim index 401e6da..9f7fc95 100644 --- a/src/views/search.nim +++ b/src/views/search.nim @@ -24,9 +24,9 @@ proc renderSearch*(): VNode = buildHtml(tdiv(class="panel-container")): tdiv(class="search-bar"): form(`method`="get", action="/search", autocomplete="off"): - hiddenField("f", "users") + hiddenField("f", "tweets") input(`type`="text", name="q", autofocus="", - placeholder="Enter username...", dir="auto") + placeholder="Search...", dir="auto") button(`type`="submit"): icon "search" proc renderProfileTabs*(query: Query; username: string): VNode = From 066407474921f6b69efdd0bec17d686830f868c4 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 19:37:22 +0100 Subject: [PATCH 102/302] Remove old tokenCount from nitter.example.conf --- nitter.example.conf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nitter.example.conf b/nitter.example.conf index 360e07b..bddb9a4 100644 --- a/nitter.example.conf +++ b/nitter.example.conf @@ -26,12 +26,6 @@ enableRSS = true # set this to false to disable RSS feeds enableDebug = false # enable request logs and debug endpoints (/.sessions) proxy = "" # http/https url, SOCKS proxies are not supported proxyAuth = "" -tokenCount = 10 -# minimum amount of usable tokens. tokens are used to authorize API requests, -# but they expire after ~1 hour, and have a limit of 500 requests per endpoint. -# the limits reset every 15 minutes, and the pool is filled up so there's -# always at least `tokenCount` usable tokens. only increase this if you receive -# major bursts all the time and don't have a rate limiting setup via e.g. nginx # Change default preferences here, see src/prefs_impl.nim for a complete list [Preferences] From bc38315d1242bbaf5ceaa6836140975dd302f9e9 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 20:19:18 +0100 Subject: [PATCH 103/302] Fix tests --- .github/workflows/run-tests.yml | 2 +- tests/test_card.py | 4 ++-- tests/test_profile.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a273771..da92525 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -53,4 +53,4 @@ jobs: - name: Run tests run: | ./nitter & - pytest -n8 tests + pytest -n3 tests diff --git a/tests/test_card.py b/tests/test_card.py index 8da91a2..05b55f6 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -26,8 +26,8 @@ no_thumb = [ 'lnkd.in'], ['Thom_Wolf/status/1122466524860702729', - 'facebookresearch/fairseq', - 'Facebook AI Research Sequence-to-Sequence Toolkit written in Python. - GitHub - facebookresearch/fairseq: Facebook AI Research Sequence-to-Sequence Toolkit written in Python.', + 'GitHub - NVIDIA/Megatron-LM: Ongoing research training transformer models at scale', + 'Ongoing research training transformer models at scale - NVIDIA/Megatron-LM', 'github.com'], ['brent_p/status/1088857328680488961', diff --git a/tests/test_profile.py b/tests/test_profile.py index 38c5189..ea05add 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -4,7 +4,7 @@ from parameterized import parameterized profiles = [ ['mobile_test', 'Test account', 'Test Account. test test Testing username with @mobile_test_2 and a #hashtag', - 'San Francisco, CA', 'example.com/foobar', 'Joined October 2009', '98'], + 'San Francisco, CA', 'example.com/foobar', 'Joined October 2009', '97'], ['mobile_test_2', 'mobile test 2', '', '', '', 'Joined January 2011', '13'] ] From 54ba1e30b5b0be36c36c8de1626c4bed6d7f82ff Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 20:27:23 +0100 Subject: [PATCH 104/302] Fix empty image URLs in photo rail --- src/parser.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser.nim b/src/parser.nim index 4bdb1e2..6e0be73 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -456,7 +456,8 @@ proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = elif t.card.isSome: get(t.card).image else: "" - result.add GalleryPhoto(url: url, tweetId: $t.id) + if url.len > 0: + result.add GalleryPhoto(url: url, tweetId: $t.id) if result.len == 16: break From fb7c1d87108f8e909448ed1707ebbc59e5e62325 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 5 Feb 2025 21:48:29 +0100 Subject: [PATCH 105/302] Improve test workflow --- .github/workflows/run-tests.yml | 98 +++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index da92525..379f901 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -8,49 +8,101 @@ on: - master workflow_call: +# Ensure that multiple runs on the same branch do not overlap. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + jobs: - test: + build-test: + name: Build and test runs-on: buildjet-2vcpu-ubuntu-2204 strategy: matrix: - nim: - - "1.6.x" - - "2.0.x" - - "2.2.x" - - "devel" + nim: ["1.6.x", "2.0.x", "2.2.x", "devel"] steps: - - uses: actions/checkout@v3 + - name: Checkout Code + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Cache nimble + + - name: Cache Nimble Dependencies id: cache-nimble - uses: buildjet/cache@v3 + uses: buildjet/cache@v4 with: path: ~/.nimble - key: ${{ matrix.nim }}-nimble-${{ hashFiles('*.nimble') }} + key: ${{ matrix.nim }}-nimble-v2-${{ hashFiles('*.nimble') }} restore-keys: | - ${{ matrix.nim }}-nimble- - - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: "pip" - - uses: jiro4989/setup-nim-action@v1 + ${{ matrix.nim }}-nimble-v2- + + - name: Setup Nim + uses: jiro4989/setup-nim-action@v2 with: nim-version: ${{ matrix.nim }} + use-nightlies: true repo-token: ${{ secrets.GITHUB_TOKEN }} - - run: nimble build -d:release -Y - - run: pip install seleniumbase - - run: seleniumbase install chromedriver - - uses: supercharge/redis-github-action@1.5.0 - - name: Prepare Nitter + + - name: Build Project + run: nimble build -d:release -Y + + integration-test: + needs: [build-test] + name: Integration test + runs-on: buildjet-2vcpu-ubuntu-2204 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache Nimble Dependencies + id: cache-nimble + uses: buildjet/cache@v4 + with: + path: ~/.nimble + key: devel-nimble-v2-${{ hashFiles('*.nimble') }} + restore-keys: | + devel-nimble-v2- + + - name: Setup Python (3.10) with pip cache + uses: buildjet/setup-python@v4 + with: + python-version: "3.10" + cache: pip + + - name: Setup Nim + uses: jiro4989/setup-nim-action@v2 + with: + nim-version: devel + use-nightlies: true + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Project + run: nimble build -d:release -Y + + - name: Install SeleniumBase and Chromedriver run: | - sudo apt install libsass-dev -y + pip install seleniumbase + seleniumbase install chromedriver + + - name: Start Redis Service + uses: supercharge/redis-github-action@1.5.0 + + - name: Prepare Nitter Environment + run: | + sudo apt-get update && sudo apt-get install -y libsass-dev cp nitter.example.conf nitter.conf sed -i 's/enableDebug = false/enableDebug = true/g' nitter.conf nimble md nimble scss + echo '${{ secrets.SESSIONS }}' | head -n1 echo '${{ secrets.SESSIONS }}' > ./sessions.jsonl - - name: Run tests + + - name: Run Tests run: | ./nitter & pytest -n3 tests From 770257636980bf526e1e6a1da54fc3dca8bb6550 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 12 Feb 2025 14:42:05 +0100 Subject: [PATCH 106/302] Fix GitHub workflow secrets permissions --- .github/workflows/build-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 765e7a0..c9f0392 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -10,6 +10,7 @@ on: jobs: tests: uses: ./.github/workflows/run-tests.yml + secrets: inherit build-docker-amd64: needs: [tests] runs-on: buildjet-2vcpu-ubuntu-2204 From 5be37737eb43902fc1dc45c4862637c1d3fd3178 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 12 Feb 2025 18:36:29 +0100 Subject: [PATCH 107/302] Fix rate limit handling --- src/auth.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index c57008b..bf3c957 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -5,7 +5,7 @@ import experimental/parser/session # max requests at a time per session to avoid race conditions const - maxConcurrentReqs = 3 + maxConcurrentReqs = 2 dayInSeconds = 24 * 60 * 60 apiMaxReqs: Table[Api, int] = { Api.search: 50, @@ -130,8 +130,9 @@ proc isLimited(session: Session; api: Api): bool = if (epochTime().int - session.limitedAt) > dayInSeconds: session.limited = false log "resetting limit: ", session.id - else: return false + else: + return true if api in session.apis: let limit = session.apis[api] From 6da152db07b5adc424e34488b6785550bdc5b863 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 12 Feb 2025 20:43:45 +0100 Subject: [PATCH 108/302] Reduce integration test concurrency --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 379f901..6d02dd2 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -105,4 +105,4 @@ jobs: - name: Run Tests run: | ./nitter & - pytest -n3 tests + pytest -n2 tests From 9ccfd8ee99da7825b288c5f34960017800bedb7a Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 12 Feb 2025 21:22:09 +0100 Subject: [PATCH 109/302] Reduce integration test concurrency even more --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6d02dd2..46e4ace 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -105,4 +105,4 @@ jobs: - name: Run Tests run: | ./nitter & - pytest -n2 tests + pytest -n1 tests From 92cd6abcf6d9935bc0d7f013acbfbfd8ddd896ba Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Feb 2025 02:06:19 +0100 Subject: [PATCH 110/302] Stop logging unimportant errors --- src/apiutils.nim | 20 +++++++++++--------- src/types.nim | 3 +++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 3a0d12c..65dcc29 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -7,6 +7,7 @@ import experimental/types/common const rlRemaining = "x-rate-limit-remaining" rlReset = "x-rate-limit-reset" + errorsToSkip = {doesntExist, tweetNotFound, timeout, unauthorized, badRequest} var pool: HttpPool @@ -76,14 +77,15 @@ template fetchImpl(result, fetchBody) {.dirty.} = if result.startsWith("{\"errors"): let errors = result.fromJson(Errors) - echo "Fetch error, API: ", api, ", errors: ", errors - if errors in {expiredToken, badToken, locked}: - invalidate(session) - raise rateLimitError() - elif errors in {rateLimited}: - # rate limit hit, resets after 24 hours - setLimited(session, api) - raise rateLimitError() + if errors notin errorsToSkip: + echo "Fetch error, API: ", api, ", errors: ", errors + if errors in {expiredToken, badToken, locked}: + invalidate(session) + raise rateLimitError() + elif errors in {rateLimited}: + # rate limit hit, resets after 24 hours + setLimited(session, api) + raise rateLimitError() elif result.startsWith("429 Too Many Requests"): echo "[sessions] 429 error, API: ", api, ", session: ", session.id session.apis[api].remaining = 0 @@ -126,7 +128,7 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = result = newJNull() let error = result.getError - if error != null: + if error != null and error notin errorsToSkip: echo "Fetch error, API: ", api, ", error: ", error if error in {expiredToken, badToken, locked}: invalidate(session) diff --git a/src/types.nim b/src/types.nim index 6c1f1b6..4e565ee 100644 --- a/src/types.nim +++ b/src/types.nim @@ -45,8 +45,10 @@ type noUserMatches = 17 protectedUser = 22 missingParams = 25 + timeout = 29 couldntAuth = 32 doesntExist = 34 + unauthorized = 37 invalidParam = 47 userNotFound = 50 suspended = 63 @@ -56,6 +58,7 @@ type tweetNotFound = 144 tweetNotAuthorized = 179 forbidden = 200 + badRequest = 214 badToken = 239 locked = 326 noCsrf = 353 From cc28d21a622be1787f120ce536929f736454a4e7 Mon Sep 17 00:00:00 2001 From: SoonKhen OwYong <dude@owyong.sk> Date: Sat, 22 Feb 2025 08:36:04 -0800 Subject: [PATCH 111/302] Add mounting of sessions.jsonl in docker-compose.yml (#1221) --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index ec8ade5..3d75751 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: - "127.0.0.1:8080:8080" # Replace with "8080:8080" if you don't use a reverse proxy volumes: - ./nitter.conf:/src/nitter.conf:Z,ro + - ./sessions.jsonl:/src/sessions.jsonl:Z,ro # Run get_sessions.py to get the credentials depends_on: - nitter-redis restart: unless-stopped From cb334a7d6871f6d108051baea3dd73df2c6fd5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20=28perso=29?= <4016501+unixfox@users.noreply.github.com> Date: Sun, 23 Feb 2025 23:07:45 +0100 Subject: [PATCH 112/302] chore: Revert back to nim 2.0 for alpine ARM64 (#1222) --- Dockerfile.arm64 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index bf6011f..46352c7 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,4 +1,4 @@ -FROM alpine:3.21.2 as nim +FROM alpine:3.20.6 as nim LABEL maintainer="setenforce@protonmail.com" RUN apk --no-cache add libsass-dev pcre gcc git libc-dev nim nimble @@ -13,7 +13,7 @@ RUN nimble build -d:danger -d:lto -d:strip --mm:refc \ && nimble scss \ && nimble md -FROM alpine:3.21.2 +FROM alpine:3.20.6 WORKDIR /src/ RUN apk --no-cache add pcre ca-certificates openssl COPY --from=nim /src/nitter/nitter ./ From 4f9ba9c7d69c33d1273ad7a6cd6415c3d0b5c903 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 25 Feb 2025 04:28:45 +0000 Subject: [PATCH 113/302] Always print sessions logs --- src/auth.nim | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index bf3c957..0ff40f8 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -27,7 +27,7 @@ var enableLogging = false template log(str: varargs[string, `$`]) = - if enableLogging: echo "[sessions] ", str.join("") + echo "[sessions] ", str.join("") proc snowflakeToEpoch(flake: int64): int64 = int64(((flake shr 22) + 1288834974657) div 1000) @@ -188,15 +188,15 @@ proc initSessionPool*(cfg: Config; path: string) = enableLogging = cfg.enableDebug if path.endsWith(".json"): - echo "[sessions] ERROR: .json is not supported, the file must be a valid JSONL file ending in .jsonl" + log "ERROR: .json is not supported, the file must be a valid JSONL file ending in .jsonl" quit 1 if not fileExists(path): - echo "[sessions] ERROR: ", path, " not found. This file is required to authenticate API requests." + log "ERROR: ", path, " not found. This file is required to authenticate API requests." quit 1 - log "Parsing JSONL account sessions file: ", path + log "parsing JSONL account sessions file: ", path for line in path.lines: sessionPool.add parseSession(line) - log "Successfully added ", sessionPool.len, " valid account sessions." + log "successfully added ", sessionPool.len, " valid account sessions" From 661be438ec0363dd3b153854765081707ab2d369 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 25 Feb 2025 05:46:18 +0000 Subject: [PATCH 114/302] Support both web and Android sessions --- src/apiutils.nim | 20 +++++++++++++------- src/experimental/parser/session.nim | 27 ++++++++++++++++++--------- src/experimental/types/session.nim | 4 ++++ src/types.nim | 14 ++++++++++++-- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 65dcc29..9f28f7f 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import httpclient, asyncdispatch, options, strutils, uri, times, math, tables +import httpclient, asyncdispatch, options, strformat, strutils, uri, times, math, tables import jsony, packedjson, zippy, oauth1 import types, auth, consts, parserutils, http_pool import experimental/types/common @@ -28,21 +28,27 @@ proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = return getOauth1RequestHeader(params)["authorization"] -proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = - let header = getOauthHeader(url, oauthToken, oauthTokenSecret) - +proc genHeaders*(url: string; session: Session): HttpHeaders = result = newHttpHeaders({ "connection": "keep-alive", - "authorization": header, "content-type": "application/json", "x-twitter-active-user": "yes", "authority": "api.x.com", "accept-encoding": "gzip", "accept-language": "en-US,en;q=0.9", "accept": "*/*", - "DNT": "1" + "DNT": "1", }) + case session.kind + of oauth: + result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) + of cookie: + result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" + result["x-twitter-auth-type"] = "OAuth2Session" + result["x-csrf-token"] = session.ct0 + result["cookie"] = &"ct0={session.ct0}; auth_token={session.authToken}" + template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() @@ -54,7 +60,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = try: var resp: AsyncResponse - pool.use(genHeaders($url, session.oauthToken, session.oauthSecret)): + pool.use(genHeaders($url, session)): template getContent = resp = await c.get($url) result = await resp.body diff --git a/src/experimental/parser/session.nim b/src/experimental/parser/session.nim index ee9c93e..0eea787 100644 --- a/src/experimental/parser/session.nim +++ b/src/experimental/parser/session.nim @@ -1,15 +1,24 @@ import std/strutils import jsony import ../types/session -from ../../types import Session +from ../../types import Session, SessionKind proc parseSession*(raw: string): Session = - let - session = raw.fromJson(RawSession) - id = session.oauthToken[0 ..< session.oauthToken.find('-')] + let session = raw.fromJson(RawSession) - result = Session( - id: parseBiggestInt(id), - oauthToken: session.oauthToken, - oauthSecret: session.oauthTokenSecret - ) + case session.kind + of "oauth": + let id = session.oauthToken[0 ..< session.oauthToken.find('-')] + result = Session( + kind: oauth, + id: parseBiggestInt(id), + oauthToken: session.oauthToken, + oauthSecret: session.oauthTokenSecret + ) + of "cookie": + result = Session( + kind: cookie, + id: 999, + ct0: session.ct0, + authToken: session.authToken + ) diff --git a/src/experimental/types/session.nim b/src/experimental/types/session.nim index 4165204..c1588cf 100644 --- a/src/experimental/types/session.nim +++ b/src/experimental/types/session.nim @@ -1,4 +1,8 @@ type RawSession* = object + kind*: string oauthToken*: string oauthTokenSecret*: string + ct0*: string + authToken*: string + diff --git a/src/types.nim b/src/types.nim index 4e565ee..a99c23a 100644 --- a/src/types.nim +++ b/src/types.nim @@ -31,10 +31,20 @@ type remaining*: int reset*: int + SessionKind* = enum + oauth + cookie + Session* = ref object + case kind*: SessionKind + of oauth: + oauthToken*: string + oauthSecret*: string + of cookie: + ct0*: string + authToken*: string + id*: int64 - oauthToken*: string - oauthSecret*: string pending*: int limited*: bool limitedAt*: int From 41fa47bfbf3917e9b3ac4f7b49c89a75a7a2bd44 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 25 Feb 2025 23:36:02 +0000 Subject: [PATCH 115/302] Revert "Support both web and Android sessions" This reverts commit 661be438ec0363dd3b153854765081707ab2d369. --- src/apiutils.nim | 20 +++++++------------- src/experimental/parser/session.nim | 27 +++++++++------------------ src/experimental/types/session.nim | 4 ---- src/types.nim | 14 ++------------ 4 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 9f28f7f..65dcc29 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import httpclient, asyncdispatch, options, strformat, strutils, uri, times, math, tables +import httpclient, asyncdispatch, options, strutils, uri, times, math, tables import jsony, packedjson, zippy, oauth1 import types, auth, consts, parserutils, http_pool import experimental/types/common @@ -28,27 +28,21 @@ proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = return getOauth1RequestHeader(params)["authorization"] -proc genHeaders*(url: string; session: Session): HttpHeaders = +proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = + let header = getOauthHeader(url, oauthToken, oauthTokenSecret) + result = newHttpHeaders({ "connection": "keep-alive", + "authorization": header, "content-type": "application/json", "x-twitter-active-user": "yes", "authority": "api.x.com", "accept-encoding": "gzip", "accept-language": "en-US,en;q=0.9", "accept": "*/*", - "DNT": "1", + "DNT": "1" }) - case session.kind - of oauth: - result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) - of cookie: - result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" - result["x-twitter-auth-type"] = "OAuth2Session" - result["x-csrf-token"] = session.ct0 - result["cookie"] = &"ct0={session.ct0}; auth_token={session.authToken}" - template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() @@ -60,7 +54,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = try: var resp: AsyncResponse - pool.use(genHeaders($url, session)): + pool.use(genHeaders($url, session.oauthToken, session.oauthSecret)): template getContent = resp = await c.get($url) result = await resp.body diff --git a/src/experimental/parser/session.nim b/src/experimental/parser/session.nim index 0eea787..ee9c93e 100644 --- a/src/experimental/parser/session.nim +++ b/src/experimental/parser/session.nim @@ -1,24 +1,15 @@ import std/strutils import jsony import ../types/session -from ../../types import Session, SessionKind +from ../../types import Session proc parseSession*(raw: string): Session = - let session = raw.fromJson(RawSession) + let + session = raw.fromJson(RawSession) + id = session.oauthToken[0 ..< session.oauthToken.find('-')] - case session.kind - of "oauth": - let id = session.oauthToken[0 ..< session.oauthToken.find('-')] - result = Session( - kind: oauth, - id: parseBiggestInt(id), - oauthToken: session.oauthToken, - oauthSecret: session.oauthTokenSecret - ) - of "cookie": - result = Session( - kind: cookie, - id: 999, - ct0: session.ct0, - authToken: session.authToken - ) + result = Session( + id: parseBiggestInt(id), + oauthToken: session.oauthToken, + oauthSecret: session.oauthTokenSecret + ) diff --git a/src/experimental/types/session.nim b/src/experimental/types/session.nim index c1588cf..4165204 100644 --- a/src/experimental/types/session.nim +++ b/src/experimental/types/session.nim @@ -1,8 +1,4 @@ type RawSession* = object - kind*: string oauthToken*: string oauthTokenSecret*: string - ct0*: string - authToken*: string - diff --git a/src/types.nim b/src/types.nim index a99c23a..4e565ee 100644 --- a/src/types.nim +++ b/src/types.nim @@ -31,20 +31,10 @@ type remaining*: int reset*: int - SessionKind* = enum - oauth - cookie - Session* = ref object - case kind*: SessionKind - of oauth: - oauthToken*: string - oauthSecret*: string - of cookie: - ct0*: string - authToken*: string - id*: int64 + oauthToken*: string + oauthSecret*: string pending*: int limited*: bool limitedAt*: int From 83b0f8b55ae7bfb8a19a0bf14de52f30d06b8db6 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 5 Apr 2025 15:57:14 +0100 Subject: [PATCH 116/302] Retry limited accounts after an hour --- src/auth.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 0ff40f8..81f248a 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -6,7 +6,7 @@ import experimental/parser/session # max requests at a time per session to avoid race conditions const maxConcurrentReqs = 2 - dayInSeconds = 24 * 60 * 60 + hourInSeconds = 60 * 60 apiMaxReqs: Table[Api, int] = { Api.search: 50, Api.tweetDetail: 500, @@ -127,7 +127,7 @@ proc isLimited(session: Session; api: Api): bool = return true if session.limited and api != Api.userTweets: - if (epochTime().int - session.limitedAt) > dayInSeconds: + if (epochTime().int - session.limitedAt) > hourInSeconds: session.limited = false log "resetting limit: ", session.id return false From 94c83f38114abaef10c36903fbcd59d78db7a578 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 15 Apr 2025 02:06:00 +0100 Subject: [PATCH 117/302] Hide ads/promoted tweets Fixes #1234 --- src/parser.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index 6e0be73..01f0341 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -336,7 +336,7 @@ proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = let cursor = t{"item", "content", "value"} result.thread.cursor = cursor.getStr result.thread.hasMore = true - elif "tweet" in entryId: + elif "tweet" in entryId and "promoted" notin entryId: let isLegacy = t{"item"}.hasKey("itemContent") (contentKey, resultKey) = if isLegacy: ("itemContent", "tweet_results") @@ -381,7 +381,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversati let (thread, self) = parseGraphThread(e) if self: result.after = thread - else: + elif thread.content.len > 0: result.replies.content.add thread elif entryId.startsWith("tombstone"): let id = entryId.getId() From e40c61a6ae76431c570951cc4925f38523b00a82 Mon Sep 17 00:00:00 2001 From: Gabriel Simmer <github@gmem.ca> Date: Thu, 1 May 2025 12:39:05 +0100 Subject: [PATCH 118/302] Find TimelineAddEntries in tweets response (#1251) See https://github.com/zedeus/nitter/issues/1250. Sometimes the API gives us more results and the tweets are no longer at index 0. --- src/parser.nim | 62 ++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index 01f0341..5fdfebd 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -364,39 +364,41 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversati if instructions.len == 0: return - for e in instructions[0]{"entries"}: - let entryId = e{"entryId"}.getStr - if entryId.startsWith("tweet"): - with tweetResult, e{"content", contentKey, resultKey, "result"}: - let tweet = parseGraphTweet(tweetResult, not v2) + for i in instructions: + if i{"__typename"}.getStr == "TimelineAddEntries": + for e in i{"entries"}: + let entryId = e{"entryId"}.getStr + if entryId.startsWith("tweet"): + with tweetResult, e{"content", contentKey, resultKey, "result"}: + let tweet = parseGraphTweet(tweetResult, not v2) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) - if $tweet.id == tweetId: - result.tweet = tweet - else: - result.before.content.add tweet - elif entryId.startsWith("conversationthread"): - let (thread, self) = parseGraphThread(e) - if self: - result.after = thread - elif thread.content.len > 0: - result.replies.content.add thread - elif entryId.startsWith("tombstone"): - let id = entryId.getId() - let tweet = Tweet( - id: parseBiggestInt(id), - available: false, - text: e{"content", contentKey, "tombstoneInfo", "richText"}.getTombstone - ) + if $tweet.id == tweetId: + result.tweet = tweet + else: + result.before.content.add tweet + elif entryId.startsWith("conversationthread"): + let (thread, self) = parseGraphThread(e) + if self: + result.after = thread + elif thread.content.len > 0: + result.replies.content.add thread + elif entryId.startsWith("tombstone"): + let id = entryId.getId() + let tweet = Tweet( + id: parseBiggestInt(id), + available: false, + text: e{"content", contentKey, "tombstoneInfo", "richText"}.getTombstone + ) - if id == tweetId: - result.tweet = tweet - else: - result.before.content.add tweet - elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", contentKey, "value"}.getStr + if id == tweetId: + result.tweet = tweet + else: + result.before.content.add tweet + elif entryId.startsWith("cursor-bottom"): + result.replies.bottom = e{"content", contentKey, "value"}.getStr proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) From 662ae90e2246c8a01c811f68750b7e5033e0fa69 Mon Sep 17 00:00:00 2001 From: 0xbarchitect <tbng84@gmail.com> Date: Sun, 12 Oct 2025 14:07:37 +0700 Subject: [PATCH 119/302] Bypass Cloudflare 403 error using cloudscraper (#1291) * Bypass Cloudflare 403 error using cloudscraper * add docs --- tools/get_session.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/get_session.py b/tools/get_session.py index 9ff704d..9f91483 100644 --- a/tools/get_session.py +++ b/tools/get_session.py @@ -3,9 +3,10 @@ import requests import json import sys import pyotp +import cloudscraper -# NOTE: pyotp and requests are dependencies -# > pip install pyotp requests +# NOTE: pyotp, requests and cloudscraper are dependencies +# > pip install pyotp requests cloudscraper TW_CONSUMER_KEY = '3nVuSoBZnx6U4vzUxf5w' TW_CONSUMER_SECRET = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys' @@ -41,10 +42,10 @@ def auth(username, password, otp_secret): "X-Twitter-Client-DeviceID": "" } - session = requests.Session() - session.headers = twitter_header + scraper = cloudscraper.create_scraper() + scraper.headers = twitter_header - task1 = session.post( + task1 = scraper.post( 'https://api.twitter.com/1.1/onboarding/task.json', params={ 'flow_name': 'login', @@ -71,9 +72,9 @@ def auth(username, password, otp_secret): } ) - session.headers['att'] = task1.headers.get('att') + scraper.headers['att'] = task1.headers.get('att') - task2 = session.post( + task2 = scraper.post( 'https://api.twitter.com/1.1/onboarding/task.json', json={ "flow_token": task1.json().get('flow_token'), @@ -88,7 +89,7 @@ def auth(username, password, otp_secret): } ) - task3 = session.post( + task3 = scraper.post( 'https://api.twitter.com/1.1/onboarding/task.json', json={ "flow_token": task2.json().get('flow_token'), @@ -109,7 +110,7 @@ def auth(username, password, otp_secret): response_text = t3_subtask["enter_text"]["hint_text"] totp = pyotp.TOTP(otp_secret) generated_code = totp.now() - task4resp = session.post( + task4resp = scraper.post( "https://api.twitter.com/1.1/onboarding/task.json", json={ "flow_token": task3.json().get("flow_token"), From 32b04a772bba866f51d11eed6708d0e41098d926 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 15 Nov 2025 06:50:48 +0100 Subject: [PATCH 120/302] Include pinned tweets in RSS Fixes #1262 --- src/views/rss.nimf | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/views/rss.nimf b/src/views/rss.nimf index 036a7b9..819f99c 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -28,6 +28,24 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} #end proc # +#proc getTweetsWithPinned(profile: Profile): seq[Tweets] = +#result = profile.tweets.content +#if profile.pinned.isSome and result.len > 0: +# let pinnedTweet = profile.pinned.get +# var inserted = false +# for threadIdx in 0 ..< result.len: +# if not inserted: +# for tweetIdx in 0 ..< result[threadIdx].len: +# if result[threadIdx][tweetIdx].id < pinnedTweet.id: +# result[threadIdx].insert(pinnedTweet, tweetIdx) +# inserted = true +# end if +# end for +# end if +# end for +#end if +#end proc +# #proc renderRssTweet(tweet: Tweet; cfg: Config): string = #let tweet = tweet.retweet.get(tweet) #let urlPrefix = getUrlPrefix(cfg) @@ -106,8 +124,9 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} <width>128</width> <height>128</height> </image> -#if profile.tweets.content.len > 0: -${renderRssTweets(profile.tweets.content, cfg, userId=profile.user.id)} +#let tweetsList = getTweetsWithPinned(profile) +#if tweetsList.len > 0: +${renderRssTweets(tweetsList, cfg, userId=profile.user.id)} #end if </channel> </rss> From 9e956150217dd6958fa8cf0f5e916bb080518642 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 15 Nov 2025 07:01:03 +0100 Subject: [PATCH 121/302] Remove unused skipPinned parameter --- src/routes/rss.nim | 2 +- src/routes/timeline.nim | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/routes/rss.nim b/src/routes/rss.nim index 447f4ad..b0e781d 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -23,7 +23,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async. names = getNames(name) if names.len == 1: - profile = await fetchProfile(after, query, skipRail=true, skipPinned=true) + profile = await fetchProfile(after, query, skipRail=true) else: var q = query q.fromUser = names diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 8cd1fd7..7a10e91 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -27,8 +27,7 @@ template skipIf[T](cond: bool; default; body: Future[T]): Future[T] = else: body -proc fetchProfile*(after: string; query: Query; skipRail=false; - skipPinned=false): Future[Profile] {.async.} = +proc fetchProfile*(after: string; query: Query; skipRail=false): Future[Profile] {.async.} = let name = query.fromUser[0] userId = await getUserId(name) @@ -71,7 +70,7 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; html = renderTweetSearch(timeline, prefs, getPath()) return renderMain(html, request, cfg, prefs, "Multi", rss=rss) - var profile = await fetchProfile(after, query, skipPinned=prefs.hidePins) + var profile = await fetchProfile(after, query) template u: untyped = profile.user if u.suspended: From f89d2329d28c802e88747f7beb28db3b7e90cc5d Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 15 Nov 2025 22:59:35 +0100 Subject: [PATCH 122/302] Add cookie-based authentication support Fixes #1303 --- src/apiutils.nim | 28 ++++++++++++++++++++------- src/experimental/parser/session.nim | 30 ++++++++++++++++++++--------- src/experimental/types/session.nim | 3 +++ src/types.nim | 13 +++++++++++-- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 65dcc29..875fcb0 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -28,12 +28,12 @@ proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = return getOauth1RequestHeader(params)["authorization"] -proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = - let header = getOauthHeader(url, oauthToken, oauthTokenSecret) +proc getCookieHeader(authToken, ct0: string): string = + "auth_token=" & authToken & "; ct0=" & ct0 +proc genHeaders*(session: Session, url: string): HttpHeaders = result = newHttpHeaders({ "connection": "keep-alive", - "authorization": header, "content-type": "application/json", "x-twitter-active-user": "yes", "authority": "api.x.com", @@ -43,18 +43,32 @@ proc genHeaders*(url, oauthToken, oauthTokenSecret: string): HttpHeaders = "DNT": "1" }) + case session.kind + of SessionKind.oauth: + result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) + of SessionKind.cookie: + result["cookie"] = getCookieHeader(session.authToken, session.ct0) + result["x-csrf-token"] = session.ct0 + result["x-twitter-auth-type"] = "OAuth2Session" + template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() var session = await getSession(api) - if session.oauthToken.len == 0: - echo "[sessions] Empty oauth token, session: ", session.id - raise rateLimitError() + case session.kind + of SessionKind.oauth: + if session.oauthToken.len == 0: + echo "[sessions] Empty oauth token, session: ", session.id + raise rateLimitError() + of SessionKind.cookie: + if session.authToken.len == 0 or session.ct0.len == 0: + echo "[sessions] Empty cookie credentials, session: ", session.id + raise rateLimitError() try: var resp: AsyncResponse - pool.use(genHeaders($url, session.oauthToken, session.oauthSecret)): + pool.use(genHeaders(session, $url)): template getContent = resp = await c.get($url) result = await resp.body diff --git a/src/experimental/parser/session.nim b/src/experimental/parser/session.nim index ee9c93e..db72e85 100644 --- a/src/experimental/parser/session.nim +++ b/src/experimental/parser/session.nim @@ -1,15 +1,27 @@ import std/strutils import jsony import ../types/session -from ../../types import Session +from ../../types import Session, SessionKind proc parseSession*(raw: string): Session = - let - session = raw.fromJson(RawSession) - id = session.oauthToken[0 ..< session.oauthToken.find('-')] + let session = raw.fromJson(RawSession) + let kind = if session.kind == "": "oauth" else: session.kind - result = Session( - id: parseBiggestInt(id), - oauthToken: session.oauthToken, - oauthSecret: session.oauthTokenSecret - ) + case kind + of "oauth": + let id = session.oauthToken[0 ..< session.oauthToken.find('-')] + result = Session( + kind: SessionKind.oauth, + id: parseBiggestInt(id), + oauthToken: session.oauthToken, + oauthSecret: session.oauthTokenSecret + ) + of "cookie": + result = Session( + kind: SessionKind.cookie, + id: 999, + authToken: session.authToken, + ct0: session.ct0 + ) + else: + raise newException(ValueError, "Unknown session kind: " & kind) diff --git a/src/experimental/types/session.nim b/src/experimental/types/session.nim index 4165204..c0cb58f 100644 --- a/src/experimental/types/session.nim +++ b/src/experimental/types/session.nim @@ -1,4 +1,7 @@ type RawSession* = object + kind*: string oauthToken*: string oauthTokenSecret*: string + authToken*: string + ct0*: string diff --git a/src/types.nim b/src/types.nim index 4e565ee..5755f65 100644 --- a/src/types.nim +++ b/src/types.nim @@ -31,14 +31,23 @@ type remaining*: int reset*: int + SessionKind* = enum + oauth + cookie + Session* = ref object id*: int64 - oauthToken*: string - oauthSecret*: string pending*: int limited*: bool limitedAt*: int apis*: Table[Api, RateLimit] + case kind*: SessionKind + of oauth: + oauthToken*: string + oauthSecret*: string + of cookie: + authToken*: string + ct0*: string Error* = enum null = 0 From 3768762fca4a89ab7e2aaa1bcf934ec438ec9041 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Nov 2025 05:02:57 +0100 Subject: [PATCH 123/302] Add script for generating cookies --- tools/get_web_session.py | 143 +++++++++++++++++++++++++++++++++++++++ tools/requirements.txt | 2 + 2 files changed, 145 insertions(+) create mode 100644 tools/get_web_session.py create mode 100644 tools/requirements.txt diff --git a/tools/get_web_session.py b/tools/get_web_session.py new file mode 100644 index 0000000..36a40b0 --- /dev/null +++ b/tools/get_web_session.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +""" +Authenticates with X.com/Twitter and extracts session cookies for use with Nitter. +Handles 2FA, extracts user info, and outputs clean JSON for sessions.jsonl. + +Requirements: + pip install -r tools/requirements.txt + +Usage: + python3 tools/get_web_session.py <username> <password> [totp_seed] [--append sessions.jsonl] [--headless] + +Examples: + # Output to terminal + python3 tools/get_web_session.py myusername mypassword TOTP_BASE32_SECRET + + # Append to sessions.jsonl + python3 tools/get_web_session.py myusername mypassword TOTP_SECRET --append sessions.jsonl + + # Headless mode (may increase detection risk) + python3 tools/get_web_session.py myusername mypassword TOTP_SECRET --headless + +Output: + {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."} +""" + +import sys +import json +import asyncio +import pyotp +import nodriver as uc + + +async def login_and_get_cookies(username, password, totp_seed=None, headless=False): + """Authenticate with X.com and extract session cookies""" + # Note: headless mode may increase detection risk from bot-detection systems + browser = await uc.start(headless=headless) + tab = await browser.get('https://x.com/i/flow/login') + + try: + # Enter username + print('[*] Entering username...', file=sys.stderr) + username_input = await tab.find('input[autocomplete="username"]', timeout=10) + await username_input.send_keys(username + '\n') + await asyncio.sleep(1) + + # Enter password + print('[*] Entering password...', file=sys.stderr) + password_input = await tab.find('input[autocomplete="current-password"]', timeout=15) + await password_input.send_keys(password + '\n') + await asyncio.sleep(2) + + # Handle 2FA if needed + page_content = await tab.get_content() + if 'verification code' in page_content or 'Enter code' in page_content: + if not totp_seed: + raise Exception('2FA required but no TOTP seed provided') + + print('[*] 2FA detected, entering code...', file=sys.stderr) + totp_code = pyotp.TOTP(totp_seed).now() + code_input = await tab.select('input[type="text"]') + await code_input.send_keys(totp_code + '\n') + await asyncio.sleep(3) + + # Get cookies + print('[*] Retrieving cookies...', file=sys.stderr) + for _ in range(20): # 20 second timeout + cookies = await browser.cookies.get_all() + cookies_dict = {cookie.name: cookie.value for cookie in cookies} + + if 'auth_token' in cookies_dict and 'ct0' in cookies_dict: + print('[*] Found both cookies', file=sys.stderr) + + # Extract ID from twid cookie (may be URL-encoded) + user_id = None + if 'twid' in cookies_dict: + twid = cookies_dict['twid'] + # Try to extract the ID from twid (format: u%3D<id> or u=<id>) + if 'u%3D' in twid: + user_id = twid.split('u%3D')[1].split('&')[0] + elif 'u=' in twid: + user_id = twid.split('u=')[1].split('&')[0] + + cookies_dict['username'] = username + if user_id: + cookies_dict['id'] = user_id + + return cookies_dict + + await asyncio.sleep(1) + + raise Exception('Timeout waiting for cookies') + + finally: + browser.stop() + + +async def main(): + if len(sys.argv) < 3: + print('Usage: python3 twitter-auth.py username password [totp_seed] [--append sessions.jsonl] [--headless]') + sys.exit(1) + + username = sys.argv[1] + password = sys.argv[2] + totp_seed = None + append_file = None + headless = False + + # Parse optional arguments + for i, arg in enumerate(sys.argv[3:], 3): + if arg == '--append' and i + 1 < len(sys.argv): + append_file = sys.argv[i + 1] + elif arg == '--headless': + headless = True + elif not arg.startswith('--'): + totp_seed = arg + + try: + cookies = await login_and_get_cookies(username, password, totp_seed, headless) + session = { + 'kind': 'cookie', + 'username': cookies['username'], + 'id': cookies.get('id'), + 'auth_token': cookies['auth_token'], + 'ct0': cookies['ct0'] + } + output = json.dumps(session) + + if append_file: + with open(append_file, 'a') as f: + f.write(output + '\n') + print(f'✓ Session appended to {append_file}', file=sys.stderr) + else: + print(output) + + os._exit(0) + + except Exception as error: + print(f'[!] Error: {error}', file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 0000000..4827475 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,2 @@ +nodriver>=0.48.0 +pyotp From 6fe850b2c62dc664ee49645e8aad48bc6db9328e Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Nov 2025 05:03:01 +0100 Subject: [PATCH 124/302] Add optional cookie session fields --- src/experimental/parser/session.nim | 3 ++- src/experimental/types/session.nim | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/experimental/parser/session.nim b/src/experimental/parser/session.nim index db72e85..bb31d83 100644 --- a/src/experimental/parser/session.nim +++ b/src/experimental/parser/session.nim @@ -17,9 +17,10 @@ proc parseSession*(raw: string): Session = oauthSecret: session.oauthTokenSecret ) of "cookie": + let id = if session.id.len > 0: parseBiggestInt(session.id) else: 0 result = Session( kind: SessionKind.cookie, - id: 999, + id: id, authToken: session.authToken, ct0: session.ct0 ) diff --git a/src/experimental/types/session.nim b/src/experimental/types/session.nim index c0cb58f..dd6be22 100644 --- a/src/experimental/types/session.nim +++ b/src/experimental/types/session.nim @@ -1,6 +1,8 @@ type RawSession* = object kind*: string + username*: string + id*: string oauthToken*: string oauthTokenSecret*: string authToken*: string From 4fc7b873c44c0dcd358bb969965345712610b8c5 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Nov 2025 05:22:45 +0100 Subject: [PATCH 125/302] Use dynamic rate limits from API responses --- src/apiutils.nim | 4 +++- src/auth.nim | 13 +++++++------ src/types.nim | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 875fcb0..f6fa9f1 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -7,6 +7,7 @@ import experimental/types/common const rlRemaining = "x-rate-limit-remaining" rlReset = "x-rate-limit-reset" + rlLimit = "x-rate-limit-limit" errorsToSkip = {doesntExist, tweetNotFound, timeout, unauthorized, badRequest} var pool: HttpPool @@ -83,7 +84,8 @@ template fetchImpl(result, fetchBody) {.dirty.} = let remaining = parseInt(resp.headers[rlRemaining]) reset = parseInt(resp.headers[rlReset]) - session.setRateLimit(api, remaining, reset) + limit = parseInt(resp.headers[rlLimit]) + session.setRateLimit(api, remaining, reset, limit) if result.len > 0: if resp.headers.getOrDefault("content-encoding") == "gzip": diff --git a/src/auth.nim b/src/auth.nim index 81f248a..9f9fe8a 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -57,7 +57,8 @@ proc getSessionPoolHealth*(): JsonNode = for api in session.apis.keys: let apiStatus = session.apis[api] - reqs = apiMaxReqs[api] - apiStatus.remaining + limit = if apiStatus.limit > 0: apiStatus.limit else: apiMaxReqs.getOrDefault(api, 0) + reqs = limit - apiStatus.remaining # no requests made with this session and endpoint since the limit reset if apiStatus.reset < now: @@ -172,17 +173,17 @@ proc setLimited*(session: Session; api: Api) = session.limitedAt = epochTime().int log "rate limited by api: ", api, ", reqs left: ", session.apis[api].remaining, ", id: ", session.id -proc setRateLimit*(session: Session; api: Api; remaining, reset: int) = +proc setRateLimit*(session: Session; api: Api; remaining, reset, limit: int) = # avoid undefined behavior in race conditions if api in session.apis: - let limit = session.apis[api] - if limit.reset >= reset and limit.remaining < remaining: + let rateLimit = session.apis[api] + if rateLimit.reset >= reset and rateLimit.remaining < remaining: return - if limit.reset == reset and limit.remaining >= remaining: + if rateLimit.reset == reset and rateLimit.remaining >= remaining: session.apis[api].remaining = remaining return - session.apis[api] = RateLimit(remaining: remaining, reset: reset) + session.apis[api] = RateLimit(limit: limit, remaining: remaining, reset: reset) proc initSessionPool*(cfg: Config; path: string) = enableLogging = cfg.enableDebug diff --git a/src/types.nim b/src/types.nim index 5755f65..a138dae 100644 --- a/src/types.nim +++ b/src/types.nim @@ -28,6 +28,7 @@ type userMedia RateLimit* = object + limit*: int remaining*: int reset*: int From 5aa0b65fea43dcdabe9d191bb67df3e187796bf4 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Nov 2025 05:24:23 +0100 Subject: [PATCH 126/302] Add bearer token for cookie-based auth --- src/apiutils.nim | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index f6fa9f1..0745ee6 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -37,20 +37,23 @@ proc genHeaders*(session: Session, url: string): HttpHeaders = "connection": "keep-alive", "content-type": "application/json", "x-twitter-active-user": "yes", + "x-twitter-client-language": "en", "authority": "api.x.com", "accept-encoding": "gzip", "accept-language": "en-US,en;q=0.9", "accept": "*/*", - "DNT": "1" + "DNT": "1", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" }) case session.kind of SessionKind.oauth: result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) of SessionKind.cookie: - result["cookie"] = getCookieHeader(session.authToken, session.ct0) - result["x-csrf-token"] = session.ct0 + result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" result["x-twitter-auth-type"] = "OAuth2Session" + result["x-csrf-token"] = session.ct0 + result["cookie"] = getCookieHeader(session.authToken, session.ct0) template fetchImpl(result, fetchBody) {.dirty.} = once: From bf36fc471b7335d60168f1f9a08be9288f928ad9 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Nov 2025 06:04:46 +0100 Subject: [PATCH 127/302] Update tests --- tests/test_card.py | 10 +++++----- tests/test_quote.py | 6 ------ tests/test_tweet.py | 8 -------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/tests/test_card.py b/tests/test_card.py index 05b55f6..504c079 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -26,14 +26,14 @@ no_thumb = [ 'lnkd.in'], ['Thom_Wolf/status/1122466524860702729', - 'GitHub - NVIDIA/Megatron-LM: Ongoing research training transformer models at scale', - 'Ongoing research training transformer models at scale - NVIDIA/Megatron-LM', + 'GitHub - facebookresearch/fairseq: Facebook AI Research Sequence-to-Sequence Toolkit written in', + '', 'github.com'], ['brent_p/status/1088857328680488961', - 'Hts Nim Sugar', - 'hts-nim is a library that allows one to use htslib via the nim programming language. Nim is a garbage-collected language that compiles to C and often has similar performance. I have become very...', - 'brentp.github.io'], + 'GitHub - brentp/hts-nim: nim wrapper for htslib for parsing genomics data files', + '', + 'github.com'], ['voidtarget/status/1133028231672582145', 'sinkingsugar/nimqt-example', diff --git a/tests/test_quote.py b/tests/test_quote.py index 4921c21..53ad79f 100644 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -2,12 +2,6 @@ from base import BaseTestCase, Quote, Conversation from parameterized import parameterized text = [ - ['elonmusk/status/1138136540096319488', - 'TREV PAGE', '@Model3Owners', - """As of March 58.4% of new car sales in Norway are electric. - -What are we doing wrong? reuters.com/article/us-norwa…"""], - ['nim_lang/status/1491461266849808397#m', 'Nim', '@nim_lang', """What's better than Nim 1.6.0? diff --git a/tests/test_tweet.py b/tests/test_tweet.py index 839e6c5..bf9e267 100644 --- a/tests/test_tweet.py +++ b/tests/test_tweet.py @@ -28,14 +28,6 @@ invalid = [ ] multiline = [ - [400897186990284800, 'mobile_test_3', - """ -♔ - KEEP - CALM - AND -CLICHÉ - ON"""], [1718660434457239868, 'WebDesignMuseum', """ Happy 32nd Birthday HTML tags! From 55d4469401479f7feed166b745821f7abca8fb9d Mon Sep 17 00:00:00 2001 From: 0xCathiefish <72328723+0xcathiefish@users.noreply.github.com> Date: Mon, 17 Nov 2025 04:18:38 +0800 Subject: [PATCH 128/302] fix: correct argument parsing for --append flag in get_web_session.py (#1305) * fix: correct argument parsing for --append flag * fix: strip quotes from extracted user_id in twid cookie --- tools/get_web_session.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tools/get_web_session.py b/tools/get_web_session.py index 36a40b0..502c7f4 100644 --- a/tools/get_web_session.py +++ b/tools/get_web_session.py @@ -28,6 +28,7 @@ import json import asyncio import pyotp import nodriver as uc +import os async def login_and_get_cookies(username, password, totp_seed=None, headless=False): @@ -76,9 +77,9 @@ async def login_and_get_cookies(username, password, totp_seed=None, headless=Fal twid = cookies_dict['twid'] # Try to extract the ID from twid (format: u%3D<id> or u=<id>) if 'u%3D' in twid: - user_id = twid.split('u%3D')[1].split('&')[0] + user_id = twid.split('u%3D')[1].split('&')[0].strip('"') elif 'u=' in twid: - user_id = twid.split('u=')[1].split('&')[0] + user_id = twid.split('u=')[1].split('&')[0].strip('"') cookies_dict['username'] = username if user_id: @@ -106,13 +107,27 @@ async def main(): headless = False # Parse optional arguments - for i, arg in enumerate(sys.argv[3:], 3): - if arg == '--append' and i + 1 < len(sys.argv): - append_file = sys.argv[i + 1] + i = 3 + while i < len(sys.argv): + arg = sys.argv[i] + if arg == '--append': + if i + 1 < len(sys.argv): + append_file = sys.argv[i + 1] + i += 2 # Skip '--append' and filename + else: + print('[!] Error: --append requires a filename', file=sys.stderr) + sys.exit(1) elif arg == '--headless': headless = True + i += 1 elif not arg.startswith('--'): - totp_seed = arg + if totp_seed is None: + totp_seed = arg + i += 1 + else: + # Unkown args + print(f'[!] Warning: Unknown argument: {arg}', file=sys.stderr) + i += 1 try: cookies = await login_and_get_cookies(username, password, totp_seed, headless) From 68fc7b71c86d34f43cbfb9147c22babe8604b305 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 16 Nov 2025 23:21:15 +0100 Subject: [PATCH 129/302] Fix media support for cookie sessions Fixes #1304 --- src/api.nim | 38 +++++++++++------ src/apiutils.nim | 44 +++++++++++++------ src/consts.nim | 58 ++++++++++++++++--------- src/parser.nim | 109 +++++++++++++++++++++++++++++++++-------------- src/types.nim | 6 ++- 5 files changed, 174 insertions(+), 81 deletions(-) diff --git a/src/api.nim b/src/api.nim index 479cb3d..c0efa58 100644 --- a/src/api.nim +++ b/src/api.nim @@ -4,6 +4,15 @@ import packedjson import types, query, formatters, consts, apiutils, parser import experimental/parser as newParser +proc mediaUrl(id: string; cursor: string): SessionAwareUrl = + let + cookieVariables = userMediaVariables % [id, cursor] + oauthVariables = userTweetsVariables % [id, cursor] + result = SessionAwareUrl( + cookieUrl: graphUserMedia ? {"variables": cookieVariables, "features": gqlFeatures}, + oauthUrl: graphUserMediaV2 ? {"variables": oauthVariables, "features": gqlFeatures} + ) + proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return let @@ -26,12 +35,14 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" variables = userTweetsVariables % [id, cursor] params = {"variables": variables, "features": gqlFeatures} - (url, apiId) = case kind - of TimelineKind.tweets: (graphUserTweets, Api.userTweets) - of TimelineKind.replies: (graphUserTweetsAndReplies, Api.userTweetsAndReplies) - of TimelineKind.media: (graphUserMedia, Api.userMedia) - js = await fetch(url ? params, apiId) - result = parseGraphTimeline(js, "user", after) + js = case kind + of TimelineKind.tweets: + await fetch(graphUserTweets ? params, Api.userTweets) + of TimelineKind.replies: + await fetch(graphUserTweetsAndReplies ? params, Api.userTweetsAndReplies) + of TimelineKind.media: + await fetch(mediaUrl(id, cursor), Api.userMedia) + result = parseGraphTimeline(js, after) proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return @@ -40,19 +51,21 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = variables = listTweetsVariables % [id, cursor] params = {"variables": variables, "features": gqlFeatures} js = await fetch(graphListTweets ? params, Api.listTweets) - result = parseGraphTimeline(js, "list", after).tweets + result = parseGraphTimeline(js, after).tweets proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = let variables = %*{"screenName": name, "listSlug": list} params = {"variables": $variables, "features": gqlFeatures} - result = parseGraphList(await fetch(graphListBySlug ? params, Api.listBySlug)) + url = graphListBySlug ? params + result = parseGraphList(await fetch(url, Api.listBySlug)) proc getGraphList*(id: string): Future[List] {.async.} = let variables = """{"listId": "$1"}""" % id params = {"variables": variables, "features": gqlFeatures} - result = parseGraphList(await fetch(graphListById ? params, Api.list)) + url = graphListById ? params + result = parseGraphList(await fetch(url, Api.list)) proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} = if list.id.len == 0: return @@ -138,11 +151,8 @@ proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} proc getPhotoRail*(id: string): Future[PhotoRail] {.async.} = if id.len == 0: return - let - variables = userTweetsVariables % [id, ""] - params = {"variables": variables, "features": gqlFeatures} - url = graphUserMedia ? params - result = parseGraphPhotoRail(await fetch(url, Api.userMedia)) + let js = await fetch(mediaUrl(id, ""), Api.userMedia) + result = parseGraphPhotoRail(js) proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} = let client = newAsyncHttpClient(maxRedirects=0) diff --git a/src/apiutils.nim b/src/apiutils.nim index 0745ee6..ae459fa 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -55,21 +55,22 @@ proc genHeaders*(session: Session, url: string): HttpHeaders = result["x-csrf-token"] = session.ct0 result["cookie"] = getCookieHeader(session.authToken, session.ct0) +proc getAndValidateSession*(api: Api): Future[Session] {.async.} = + result = await getSession(api) + case result.kind + of SessionKind.oauth: + if result.oauthToken.len == 0: + echo "[sessions] Empty oauth token, session: ", result.id + raise rateLimitError() + of SessionKind.cookie: + if result.authToken.len == 0 or result.ct0.len == 0: + echo "[sessions] Empty cookie credentials, session: ", result.id + raise rateLimitError() + template fetchImpl(result, fetchBody) {.dirty.} = once: pool = HttpPool() - var session = await getSession(api) - case session.kind - of SessionKind.oauth: - if session.oauthToken.len == 0: - echo "[sessions] Empty oauth token, session: ", session.id - raise rateLimitError() - of SessionKind.cookie: - if session.authToken.len == 0 or session.ct0.len == 0: - echo "[sessions] Empty cookie credentials, session: ", session.id - raise rateLimitError() - try: var resp: AsyncResponse pool.use(genHeaders(session, $url)): @@ -136,9 +137,17 @@ template retry(bod) = echo "[sessions] Rate limited, retrying ", api, " request..." bod -proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = +proc fetch*(url: Uri | SessionAwareUrl; api: Api): Future[JsonNode] {.async.} = retry: - var body: string + var + body: string + session = await getAndValidateSession(api) + + when url is SessionAwareUrl: + let url = case session.kind + of SessionKind.oauth: url.oauthUrl + of SessionKind.cookie: url.cookieUrl + fetchImpl body: if body.startsWith('{') or body.startsWith('['): result = parseJson(body) @@ -153,8 +162,15 @@ proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} = invalidate(session) raise rateLimitError() -proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} = +proc fetchRaw*(url: Uri | SessionAwareUrl; api: Api): Future[string] {.async.} = retry: + var session = await getAndValidateSession(api) + + when url is SessionAwareUrl: + let url = case session.kind + of SessionKind.oauth: url.oauthUrl + of SessionKind.cookie: url.cookieUrl + fetchImpl result: if not (result.startsWith('{') or result.startsWith('[')): echo resp.status, ": ", result, " --- url: ", url diff --git a/src/consts.nim b/src/consts.nim index 7c67706..c8ae8d2 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -11,7 +11,8 @@ const graphUserById* = gql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" graphUserTweets* = gql / "JLApJKFY0MxGTzCoK6ps8Q/UserWithProfileTweetsQueryV2" graphUserTweetsAndReplies* = gql / "Y86LQY7KMvxn5tu3hFTyPg/UserWithProfileTweetsAndRepliesQueryV2" - graphUserMedia* = gql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" + graphUserMedia* = gql / "36oKqyQ7E_9CmtONGjJRsA/UserMedia" + graphUserMediaV2* = gql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" graphTweet* = gql / "Vorskcd2tZ-tc4Gx3zbk4Q/ConversationTimelineV2" graphTweetResult* = gql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" graphSearchTimeline* = gql / "KI9jCXUx3Ymt-hDKLOZb9Q/SearchTimeline" @@ -25,28 +26,28 @@ const "blue_business_profile_image_shape_enabled": false, "creator_subscriptions_subscription_count_enabled": false, "creator_subscriptions_tweet_preview_api_enabled": true, - "freedom_of_speech_not_reach_fetch_enabled": false, - "graphql_is_translatable_rweb_tweet_is_translatable_enabled": false, + "freedom_of_speech_not_reach_fetch_enabled": true, + "graphql_is_translatable_rweb_tweet_is_translatable_enabled": true, "hidden_profile_likes_enabled": false, "highlights_tweets_tab_ui_enabled": false, "interactive_text_enabled": false, "longform_notetweets_consumption_enabled": true, - "longform_notetweets_inline_media_enabled": false, + "longform_notetweets_inline_media_enabled": true, "longform_notetweets_richtext_consumption_enabled": true, - "longform_notetweets_rich_text_read_enabled": false, - "responsive_web_edit_tweet_api_enabled": false, + "longform_notetweets_rich_text_read_enabled": true, + "responsive_web_edit_tweet_api_enabled": true, "responsive_web_enhance_cards_enabled": false, "responsive_web_graphql_exclude_directive_enabled": true, "responsive_web_graphql_skip_user_profile_image_extensions_enabled": false, - "responsive_web_graphql_timeline_navigation_enabled": false, + "responsive_web_graphql_timeline_navigation_enabled": true, "responsive_web_media_download_video_enabled": false, "responsive_web_text_conversations_enabled": false, - "responsive_web_twitter_article_tweet_consumption_enabled": false, + "responsive_web_twitter_article_tweet_consumption_enabled": true, "responsive_web_twitter_blue_verified_badge_is_enabled": true, "rweb_lists_timeline_redesign_enabled": true, "spaces_2022_h2_clipping": true, "spaces_2022_h2_spaces_communities": true, - "standardized_nudges_misinfo": false, + "standardized_nudges_misinfo": true, "subscriptions_verification_info_enabled": true, "subscriptions_verification_info_reason_enabled": true, "subscriptions_verification_info_verified_since_enabled": true, @@ -55,28 +56,34 @@ const "super_follow_tweet_api_enabled": false, "super_follow_user_api_enabled": false, "tweet_awards_web_tipping_enabled": false, - "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": false, + "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": true, "tweetypie_unmention_optimization_enabled": false, "unified_cards_ad_metadata_container_dynamic_card_content_query_enabled": false, "verified_phone_label_enabled": false, "vibe_api_enabled": false, - "view_counts_everywhere_api_enabled": false, + "view_counts_everywhere_api_enabled": true, "premium_content_api_read_enabled": false, - "communities_web_enable_tweet_community_results_fetch": false, - "responsive_web_jetfuel_frame": false, + "communities_web_enable_tweet_community_results_fetch": true, + "responsive_web_jetfuel_frame": true, "responsive_web_grok_analyze_button_fetch_trends_enabled": false, - "responsive_web_grok_image_annotation_enabled": false, - "rweb_tipjar_consumption_enabled": false, - "profile_label_improvements_pcf_label_in_post_enabled": false, + "responsive_web_grok_image_annotation_enabled": true, + "responsive_web_grok_imagine_annotation_enabled": true, + "rweb_tipjar_consumption_enabled": true, + "profile_label_improvements_pcf_label_in_post_enabled": true, "creator_subscriptions_quote_tweet_preview_enabled": false, - "c9s_tweet_anatomy_moderator_badge_enabled": false, - "responsive_web_grok_analyze_post_followups_enabled": false, + "c9s_tweet_anatomy_moderator_badge_enabled": true, + "responsive_web_grok_analyze_post_followups_enabled": true, "rweb_video_timestamps_enabled": false, - "responsive_web_grok_share_attachment_enabled": false, - "articles_preview_enabled": false, + "responsive_web_grok_share_attachment_enabled": true, + "articles_preview_enabled": true, "immersive_video_status_linkable_timestamps": false, "articles_api_enabled": false, - "responsive_web_grok_analysis_button_from_backend": false + "responsive_web_grok_analysis_button_from_backend": true, + "rweb_video_screen_enabled": false, + "payments_enabled": false, + "responsive_web_profile_redirect_enabled": false, + "responsive_web_grok_show_grok_translated_post": false, + "responsive_web_grok_community_note_auto_translation_is_enabled": false }""".replace(" ", "").replace("\n", "") tweetVariables* = """{ @@ -110,3 +117,12 @@ const "rest_id": "$1", $2 "count": 20 }""" + + userMediaVariables* = """{ + "userId": "$1", $2 + "count": 20, + "includePromotedContent": false, + "withClientEventToken": false, + "withBirdwatchNotes": false, + "withVoice": true +}""".replace(" ", "").replace("\n", "") diff --git a/src/parser.nim b/src/parser.nim index 5fdfebd..af81469 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -6,6 +6,22 @@ import experimental/parser/unifiedcard proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet +proc extractTweetsFromEntry(e: JsonNode; entryId: string): seq[Tweet] = + if e{"content", "items"}.notNull: + for item in e{"content", "items"}: + with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: + var tweet = parseGraphTweet(tweetResult, false) + if not tweet.available: + tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + result.add tweet + return + + with tweetResult, e{"content", "content", "tweetResult", "result"}: + var tweet = parseGraphTweet(tweetResult, false) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) + result.add tweet + proc parseUser(js: JsonNode; id=""): User = if js.isNull: return result = User( @@ -32,10 +48,26 @@ proc parseGraphUser(js: JsonNode): User = var user = js{"user_result", "result"} if user.isNull: user = ? js{"user_results", "result"} + + if user.isNull: + if js{"core"}.notNull and js{"legacy"}.notNull: + user = js + else: + return + result = parseUser(user{"legacy"}, user{"rest_id"}.getStr) - if result.verifiedType == VerifiedType.none and user{"is_blue_verified"}.getBool(false): - result.verifiedType = blue + # fallback to support UserMedia/recent GraphQL updates + if result.username.len == 0 and user{"core", "screen_name"}.notNull: + result.username = user{"core", "screen_name"}.getStr + result.fullname = user{"core", "name"}.getStr + result.userPic = user{"avatar", "image_url"}.getImageStr.replace("_normal", "") + + if user{"is_blue_verified"}.getBool(false): + result.verifiedType = blue + elif user{"verification", "verified_type"}.notNull: + let verifiedType = user{"verification", "verified_type"}.getStr("None") + result.verifiedType = parseEnum[VerifiedType](verifiedType) proc parseGraphList*(js: JsonNode): List = if js.isNull: return @@ -400,31 +432,43 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversati elif entryId.startsWith("cursor-bottom"): result.replies.bottom = e{"content", contentKey, "value"}.getStr -proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = +proc parseGraphTimeline*(js: JsonNode; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) let instructions = - if root == "list": ? js{"data", "list", "timeline_response", "timeline", "instructions"} - else: ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + if js{"data", "list"}.notNull: + ? js{"data", "list", "timeline_response", "timeline", "instructions"} + elif js{"data", "user"}.notNull: + ? js{"data", "user", "result", "timeline", "timeline", "instructions"} + else: + ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} if instructions.len == 0: return for i in instructions: - if i{"__typename"}.getStr == "TimelineAddEntries": + # TimelineAddToModule instruction is used by UserMedia + if i{"moduleItems"}.notNull: + for item in i{"moduleItems"}: + with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: + let tweet = parseGraphTweet(tweetResult, false) + if not tweet.available: + tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + result.tweets.content.add tweet + continue + + if i{"entries"}.notNull: for e in i{"entries"}: let entryId = e{"entryId"}.getStr - if entryId.startsWith("tweet"): - with tweetResult, e{"content", "content", "tweetResult", "result"}: - let tweet = parseGraphTweet(tweetResult, false) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) + if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): + for tweet in extractTweetsFromEntry(e, entryId): result.tweets.content.add tweet elif "-conversation-" in entryId or entryId.startsWith("homeConversation"): let (thread, self) = parseGraphThread(e) result.tweets.content.add thread.content elif entryId.startsWith("cursor-bottom"): result.tweets.bottom = e{"content", "value"}.getStr + if after.len == 0 and i{"__typename"}.getStr == "TimelinePinEntry": with tweetResult, i{"entry", "content", "content", "tweetResult", "result"}: let tweet = parseGraphTweet(tweetResult, false) @@ -438,31 +482,34 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = result = @[] - let instructions = - ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + var instructions = ? js{"data", "user", "result", "timeline", "timeline", "instructions"} + if instructions.len == 0: + instructions = ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} for i in instructions: - if i{"__typename"}.getStr == "TimelineAddEntries": - for e in i{"entries"}: - let entryId = e{"entryId"}.getStr - if entryId.startsWith("tweet"): - with tweetResult, e{"content", "content", "tweetResult", "result"}: - let t = parseGraphTweet(tweetResult, false) - if not t.available: - t.id = parseBiggestInt(entryId.getId()) + let instrType = i{"type"}.getStr + if instrType.len == 0: + if i{"__typename"}.getStr != "TimelineAddEntries": + continue + elif instrType != "TimelineAddEntries": + continue - let url = - if t.photos.len > 0: t.photos[0] - elif t.video.isSome: get(t.video).thumb - elif t.gif.isSome: get(t.gif).thumb - elif t.card.isSome: get(t.card).image - else: "" + for e in i{"entries"}: + let entryId = e{"entryId"}.getStr + if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): + for t in extractTweetsFromEntry(e, entryId): + let url = + if t.photos.len > 0: t.photos[0] + elif t.video.isSome: get(t.video).thumb + elif t.gif.isSome: get(t.gif).thumb + elif t.card.isSome: get(t.card).image + else: "" - if url.len > 0: - result.add GalleryPhoto(url: url, tweetId: $t.id) + if url.len > 0: + result.add GalleryPhoto(url: url, tweetId: $t.id) - if result.len == 16: - break + if result.len == 16: + return proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = result = Result[T](beginning: after.len == 0) diff --git a/src/types.nim b/src/types.nim index a138dae..092d85f 100644 --- a/src/types.nim +++ b/src/types.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import times, sequtils, options, tables +import times, sequtils, options, tables, uri import prefs_impl genPrefsType() @@ -50,6 +50,10 @@ type authToken*: string ct0*: string + SessionAwareUrl* = object + oauthUrl*: Uri + cookieUrl*: Uri + Error* = enum null = 0 noUserMatches = 17 From 3f3196d10357069153dee43718bdd80b16f5b2fc Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 00:14:30 +0100 Subject: [PATCH 130/302] Fix broken UserMedia photo rail parsing Fixes #1307 --- src/parser.nim | 76 ++++++++++++++++++++++++++------------------- src/parserutils.nim | 10 ++++++ 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index af81469..aa0f8b2 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -6,22 +6,6 @@ import experimental/parser/unifiedcard proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet -proc extractTweetsFromEntry(e: JsonNode; entryId: string): seq[Tweet] = - if e{"content", "items"}.notNull: - for item in e{"content", "items"}: - with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: - var tweet = parseGraphTweet(tweetResult, false) - if not tweet.available: - tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) - result.add tweet - return - - with tweetResult, e{"content", "content", "tweetResult", "result"}: - var tweet = parseGraphTweet(tweetResult, false) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) - result.add tweet - proc parseUser(js: JsonNode; id=""): User = if js.isNull: return result = User( @@ -432,6 +416,22 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversati elif entryId.startsWith("cursor-bottom"): result.replies.bottom = e{"content", contentKey, "value"}.getStr +proc extractTweetsFromEntry*(e: JsonNode; entryId: string): seq[Tweet] = + if e{"content", "items"}.notNull: + for item in e{"content", "items"}: + with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: + var tweet = parseGraphTweet(tweetResult, false) + if not tweet.available: + tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + result.add tweet + return + + with tweetResult, e{"content", "content", "tweetResult", "result"}: + var tweet = parseGraphTweet(tweetResult, false) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) + result.add tweet + proc parseGraphTimeline*(js: JsonNode; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) @@ -482,31 +482,43 @@ proc parseGraphTimeline*(js: JsonNode; after=""): Profile = proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = result = @[] - var instructions = ? js{"data", "user", "result", "timeline", "timeline", "instructions"} + let instructions = + if js{"data", "user"}.notNull: + ? js{"data", "user", "result", "timeline", "timeline", "instructions"} + else: + ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + if instructions.len == 0: - instructions = ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + return for i in instructions: - let instrType = i{"type"}.getStr - if instrType.len == 0: - if i{"__typename"}.getStr != "TimelineAddEntries": - continue - elif instrType != "TimelineAddEntries": + # TimelineAddToModule instruction is used by MediaTimelineV2 + if i{"moduleItems"}.notNull: + for item in i{"moduleItems"}: + with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: + let t = parseGraphTweet(tweetResult, false) + if not t.available: + t.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + + let photo = extractGalleryPhoto(t) + if photo.url.len > 0: + result.add photo + + if result.len == 16: + return + continue + + let instrType = i{"type"}.getStr(i{"__typename"}.getStr) + if instrType != "TimelineAddEntries": continue for e in i{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): for t in extractTweetsFromEntry(e, entryId): - let url = - if t.photos.len > 0: t.photos[0] - elif t.video.isSome: get(t.video).thumb - elif t.gif.isSome: get(t.gif).thumb - elif t.card.isSome: get(t.card).image - else: "" - - if url.len > 0: - result.add GalleryPhoto(url: url, tweetId: $t.id) + let photo = extractGalleryPhoto(t) + if photo.url.len > 0: + result.add photo if result.len == 16: return diff --git a/src/parserutils.nim b/src/parserutils.nim index 00ea6f4..7e246dd 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -319,3 +319,13 @@ proc expandNoteTweetEntities*(tweet: Tweet; js: JsonNode) = tweet.expandTextEntities(entities, text, textSlice) tweet.text = tweet.text.multiReplace((unicodeOpen, xmlOpen), (unicodeClose, xmlClose)) + +proc extractGalleryPhoto*(t: Tweet): GalleryPhoto = + let url = + if t.photos.len > 0: t.photos[0] + elif t.video.isSome: get(t.video).thumb + elif t.gif.isSome: get(t.gif).thumb + elif t.card.isSome: get(t.card).image + else: "" + + result = GalleryPhoto(url: url, tweetId: $t.id) From 778eb35ee33cbdc80733e0d9db65b15059e8a3fa Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 03:55:23 +0100 Subject: [PATCH 131/302] Add curl-based cookie session script --- ...b_session.py => create_session_browser.py} | 11 +- tools/create_session_curl.py | 328 ++++++++++++++++++ tools/requirements.txt | 1 + 3 files changed, 333 insertions(+), 7 deletions(-) rename tools/{get_web_session.py => create_session_browser.py} (90%) create mode 100644 tools/create_session_curl.py diff --git a/tools/get_web_session.py b/tools/create_session_browser.py similarity index 90% rename from tools/get_web_session.py rename to tools/create_session_browser.py index 502c7f4..40e3dcd 100644 --- a/tools/get_web_session.py +++ b/tools/create_session_browser.py @@ -1,23 +1,20 @@ #!/usr/bin/env python3 """ -Authenticates with X.com/Twitter and extracts session cookies for use with Nitter. -Handles 2FA, extracts user info, and outputs clean JSON for sessions.jsonl. - Requirements: pip install -r tools/requirements.txt Usage: - python3 tools/get_web_session.py <username> <password> [totp_seed] [--append sessions.jsonl] [--headless] + python3 tools/create_session_browser.py <username> <password> [totp_seed] [--append sessions.jsonl] [--headless] Examples: # Output to terminal - python3 tools/get_web_session.py myusername mypassword TOTP_BASE32_SECRET + python3 tools/create_session_browser.py myusername mypassword TOTP_SECRET # Append to sessions.jsonl - python3 tools/get_web_session.py myusername mypassword TOTP_SECRET --append sessions.jsonl + python3 tools/create_session_browser.py myusername mypassword TOTP_SECRET --append sessions.jsonl # Headless mode (may increase detection risk) - python3 tools/get_web_session.py myusername mypassword TOTP_SECRET --headless + python3 tools/create_session_browser.py myusername mypassword TOTP_SECRET --headless Output: {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."} diff --git a/tools/create_session_curl.py b/tools/create_session_curl.py new file mode 100644 index 0000000..f569422 --- /dev/null +++ b/tools/create_session_curl.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +""" +Requirements: + pip install curl_cffi pyotp + +Usage: + python3 tools/create_session_curl.py <username> <password> [totp_seed] [--append sessions.jsonl] + +Examples: + # Output to terminal + python3 tools/create_session_curl.py myusername mypassword TOTP_SECRET + + # Append to sessions.jsonl + python3 tools/create_session_curl.py myusername mypassword TOTP_SECRET --append sessions.jsonl + +Output: + {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."} +""" + +import sys +import json +import pyotp +from curl_cffi import requests + +BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" +BASE_URL = "https://api.x.com/1.1/onboarding/task.json" +GUEST_ACTIVATE_URL = "https://api.x.com/1.1/guest/activate.json" + +# Subtask versions required by API +SUBTASK_VERSIONS = { + "action_list": 2, "alert_dialog": 1, "app_download_cta": 1, + "check_logged_in_account": 2, "choice_selection": 3, + "contacts_live_sync_permission_prompt": 0, "cta": 7, "email_verification": 2, + "end_flow": 1, "enter_date": 1, "enter_email": 2, "enter_password": 5, + "enter_phone": 2, "enter_recaptcha": 1, "enter_text": 5, "generic_urt": 3, + "in_app_notification": 1, "interest_picker": 3, "js_instrumentation": 1, + "menu_dialog": 1, "notifications_permission_prompt": 2, "open_account": 2, + "open_home_timeline": 1, "open_link": 1, "phone_verification": 4, + "privacy_options": 1, "security_key": 3, "select_avatar": 4, + "select_banner": 2, "settings_list": 7, "show_code": 1, "sign_up": 2, + "sign_up_review": 4, "tweet_selection_urt": 1, "update_users": 1, + "upload_media": 1, "user_recommendations_list": 4, + "user_recommendations_urt": 1, "wait_spinner": 3, "web_modal": 1 +} + + +def get_base_headers(guest_token=None): + """Build base headers for API requests.""" + headers = { + "Authorization": f"Bearer {BEARER_TOKEN}", + "Content-Type": "application/json", + "Accept": "*/*", + "Accept-Language": "en-US", + "X-Twitter-Client-Language": "en-US", + "Origin": "https://x.com", + "Referer": "https://x.com/", + } + if guest_token: + headers["X-Guest-Token"] = guest_token + return headers + + +def get_cookies_dict(session): + """Extract cookies from session.""" + return session.cookies.get_dict() if hasattr(session.cookies, 'get_dict') else dict(session.cookies) + + +def make_request(session, headers, flow_token, subtask_data, print_msg): + """Generic request handler for flow steps.""" + print(f"[*] {print_msg}...", file=sys.stderr) + + payload = { + "flow_token": flow_token, + "subtask_inputs": [subtask_data] if isinstance(subtask_data, dict) else subtask_data + } + + response = session.post(BASE_URL, json=payload, headers=headers) + response.raise_for_status() + + data = response.json() + new_flow_token = data.get('flow_token') + if not new_flow_token: + raise Exception(f"Failed to get flow token: {print_msg}") + + return new_flow_token, data + + +def get_guest_token(session): + """Get guest token for unauthenticated requests.""" + print("[*] Getting guest token...", file=sys.stderr) + response = session.post(GUEST_ACTIVATE_URL, headers={"Authorization": f"Bearer {BEARER_TOKEN}"}) + response.raise_for_status() + + guest_token = response.json().get('guest_token') + if not guest_token: + raise Exception("Failed to obtain guest token") + + print(f"[*] Got guest token: {guest_token}", file=sys.stderr) + return guest_token + + +def init_flow(session, guest_token): + """Initialize the login flow.""" + print("[*] Initializing login flow...", file=sys.stderr) + + headers = get_base_headers(guest_token) + payload = { + "input_flow_data": { + "flow_context": { + "debug_overrides": {}, + "start_location": {"location": "manual_link"} + }, + "subtask_versions": SUBTASK_VERSIONS + } + } + + response = session.post(f"{BASE_URL}?flow_name=login", json=payload, headers=headers) + response.raise_for_status() + + flow_token = response.json().get('flow_token') + if not flow_token: + raise Exception("Failed to get initial flow token") + + print("[*] Got initial flow token", file=sys.stderr) + return flow_token, headers + + +def submit_username(session, flow_token, headers, guest_token, username): + """Submit username.""" + headers = headers.copy() + headers["X-Guest-Token"] = guest_token + + subtask = { + "subtask_id": "LoginEnterUserIdentifierSSO", + "settings_list": { + "setting_responses": [{ + "key": "user_identifier", + "response_data": {"text_data": {"result": username}} + }], + "link": "next_link" + } + } + + flow_token, data = make_request(session, headers, flow_token, subtask, "Submitting username") + + # Check for denial (suspicious activity) + if data.get('subtasks') and 'cta' in data['subtasks'][0]: + error_msg = data['subtasks'][0]['cta'].get('primary_text', {}).get('text') + if error_msg: + raise Exception(f"Login denied: {error_msg}") + + return flow_token + + +def submit_password(session, flow_token, headers, guest_token, password): + """Submit password and detect if 2FA is needed.""" + headers = headers.copy() + headers["X-Guest-Token"] = guest_token + + subtask = { + "subtask_id": "LoginEnterPassword", + "enter_password": {"password": password, "link": "next_link"} + } + + flow_token, data = make_request(session, headers, flow_token, subtask, "Submitting password") + + needs_2fa = any(s.get('subtask_id') == 'LoginTwoFactorAuthChallenge' for s in data.get('subtasks', [])) + if needs_2fa: + print("[*] 2FA required", file=sys.stderr) + + return flow_token, needs_2fa + + +def submit_2fa(session, flow_token, headers, guest_token, totp_seed): + """Submit 2FA code.""" + if not totp_seed: + raise Exception("2FA required but no TOTP seed provided") + + code = pyotp.TOTP(totp_seed).now() + print("[*] Generating 2FA code...", file=sys.stderr) + + headers = headers.copy() + headers["X-Guest-Token"] = guest_token + + subtask = { + "subtask_id": "LoginTwoFactorAuthChallenge", + "enter_text": {"text": code, "link": "next_link"} + } + + flow_token, _ = make_request(session, headers, flow_token, subtask, "Submitting 2FA code") + return flow_token + + +def submit_js_instrumentation(session, flow_token, headers, guest_token): + """Submit JS instrumentation response.""" + headers = headers.copy() + headers["X-Guest-Token"] = guest_token + + subtask = { + "subtask_id": "LoginJsInstrumentationSubtask", + "js_instrumentation": { + "response": '{"rf":{"a4fc506d24bb4843c48a1966940c2796bf4fb7617a2d515ad3297b7df6b459b6":121,"bff66e16f1d7ea28c04653dc32479cf416a9c8b67c80cb8ad533b2a44fee82a3":-1,"ac4008077a7e6ca03210159dbe2134dea72a616f03832178314bb9931645e4f7":-22,"c3a8a81a9b2706c6fec42c771da65a9597c537b8e4d9b39e8e58de9fe31ff239":-12},"s":"ZHYaDA9iXRxOl2J3AZ9cc23iJx-Fg5E82KIBA_fgeZFugZGYzRtf8Bl3EUeeYgsK30gLFD2jTQx9fAMsnYCw0j8ahEy4Pb5siM5zD6n7YgOeWmFFaXoTwaGY4H0o-jQnZi5yWZRAnFi4lVuCVouNz_xd2BO2sobCO7QuyOsOxQn2CWx7bjD8vPAzT5BS1mICqUWyjZDjLnRZJU6cSQG5YFIHEPBa8Kj-v1JFgkdAfAMIdVvP7C80HWoOqYivQR7IBuOAI4xCeLQEdxlGeT-JYStlP9dcU5St7jI6ExyMeQnRicOcxXLXsan8i5Joautk2M8dAJFByzBaG4wtrPhQ3QAAAZEi-_t7"}', + "link": "next_link" + } + } + + flow_token, _ = make_request(session, headers, flow_token, subtask, "Submitting JS instrumentation") + return flow_token + + +def complete_flow(session, flow_token, headers): + """Complete the login flow.""" + cookies = get_cookies_dict(session) + + headers = headers.copy() + headers["X-Twitter-Auth-Type"] = "OAuth2Session" + if cookies.get('ct0'): + headers["X-Csrf-Token"] = cookies['ct0'] + + subtask = { + "subtask_id": "AccountDuplicationCheck", + "check_logged_in_account": {"link": "AccountDuplicationCheck_false"} + } + + make_request(session, headers, flow_token, subtask, "Completing login flow") + + +def extract_user_id(cookies_dict): + """Extract user ID from twid cookie.""" + twid = cookies_dict.get('twid', '').strip('"') + + for prefix in ['u=', 'u%3D']: + if prefix in twid: + return twid.split(prefix)[1].split('&')[0].strip('"') + + return None + + +def login_and_get_cookies(username, password, totp_seed=None): + """Authenticate with X.com and extract session cookies.""" + session = requests.Session(impersonate="chrome") + + try: + guest_token = get_guest_token(session) + flow_token, headers = init_flow(session, guest_token) + flow_token = submit_js_instrumentation(session, flow_token, headers, guest_token) + flow_token = submit_username(session, flow_token, headers, guest_token, username) + flow_token, needs_2fa = submit_password(session, flow_token, headers, guest_token, password) + + if needs_2fa: + flow_token = submit_2fa(session, flow_token, headers, guest_token, totp_seed) + + complete_flow(session, flow_token, headers) + + cookies_dict = get_cookies_dict(session) + cookies_dict['username'] = username + + user_id = extract_user_id(cookies_dict) + if user_id: + cookies_dict['id'] = user_id + + print("[*] Successfully authenticated", file=sys.stderr) + return cookies_dict + + finally: + session.close() + + +def main(): + if len(sys.argv) < 3: + print('Usage: python3 create_session_curl.py username password [totp_seed] [--append sessions.jsonl]', file=sys.stderr) + sys.exit(1) + + username = sys.argv[1] + password = sys.argv[2] + totp_seed = None + append_file = None + + # Parse optional arguments + i = 3 + while i < len(sys.argv): + arg = sys.argv[i] + if arg == '--append': + if i + 1 < len(sys.argv): + append_file = sys.argv[i + 1] + i += 2 + else: + print('[!] Error: --append requires a filename', file=sys.stderr) + sys.exit(1) + elif not arg.startswith('--'): + if totp_seed is None: + totp_seed = arg + i += 1 + else: + print(f'[!] Warning: Unknown argument: {arg}', file=sys.stderr) + i += 1 + + try: + cookies = login_and_get_cookies(username, password, totp_seed) + + session = { + 'kind': 'cookie', + 'username': cookies['username'], + 'id': cookies.get('id'), + 'auth_token': cookies['auth_token'], + 'ct0': cookies['ct0'] + } + + output = json.dumps(session) + + if append_file: + with open(append_file, 'a') as f: + f.write(output + '\n') + print(f'✓ Session appended to {append_file}', file=sys.stderr) + else: + print(output) + + sys.exit(0) + + except Exception as error: + print(f'[!] Error: {error}', file=sys.stderr) + import traceback + traceback.print_exc(file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/tools/requirements.txt b/tools/requirements.txt index 4827475..2fdac24 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,2 +1,3 @@ nodriver>=0.48.0 pyotp +curl_cffi From a666c4867c9a0b4e187bae6c5d826b4aec34fd29 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 05:42:35 +0100 Subject: [PATCH 132/302] Include username in session logs if available Fixes #1310 --- src/apiutils.nim | 10 +++++----- src/auth.nim | 20 +++++++++++++++++--- src/experimental/parser/session.nim | 2 ++ src/experimental/types/session.nim | 2 +- src/types.nim | 1 + 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index ae459fa..defffd1 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -60,11 +60,11 @@ proc getAndValidateSession*(api: Api): Future[Session] {.async.} = case result.kind of SessionKind.oauth: if result.oauthToken.len == 0: - echo "[sessions] Empty oauth token, session: ", result.id + echo "[sessions] Empty oauth token, session: ", result.pretty raise rateLimitError() of SessionKind.cookie: if result.authToken.len == 0 or result.ct0.len == 0: - echo "[sessions] Empty cookie credentials, session: ", result.id + echo "[sessions] Empty cookie credentials, session: ", result.pretty raise rateLimitError() template fetchImpl(result, fetchBody) {.dirty.} = @@ -107,7 +107,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = setLimited(session, api) raise rateLimitError() elif result.startsWith("429 Too Many Requests"): - echo "[sessions] 429 error, API: ", api, ", session: ", session.id + echo "[sessions] 429 error, API: ", api, ", session: ", session.pretty session.apis[api].remaining = 0 # rate limit hit, resets after the 15 minute window raise rateLimitError() @@ -124,8 +124,8 @@ template fetchImpl(result, fetchBody) {.dirty.} = except OSError as e: raise e except Exception as e: - let id = if session.isNil: "null" else: $session.id - echo "error: ", e.name, ", msg: ", e.msg, ", sessionId: ", id, ", url: ", url + let s = session.pretty + echo "error: ", e.name, ", msg: ", e.msg, ", session: ", s, ", url: ", url raise rateLimitError() finally: release(session) diff --git a/src/auth.nim b/src/auth.nim index 9f9fe8a..6c52918 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -29,6 +29,20 @@ var template log(str: varargs[string, `$`]) = echo "[sessions] ", str.join("") +proc pretty*(session: Session): string = + if session.isNil: + return "<null>" + + if session.id > 0 and session.username.len > 0: + result = $session.id & " (" & session.username & ")" + elif session.username.len > 0: + result = session.username + elif session.id > 0: + result = $session.id + else: + result = "<unknown>" + result = $session.kind & " " & result + proc snowflakeToEpoch(flake: int64): int64 = int64(((flake shr 22) + 1288834974657) div 1000) @@ -130,7 +144,7 @@ proc isLimited(session: Session; api: Api): bool = if session.limited and api != Api.userTweets: if (epochTime().int - session.limitedAt) > hourInSeconds: session.limited = false - log "resetting limit: ", session.id + log "resetting limit: ", session.pretty return false else: return true @@ -146,7 +160,7 @@ proc isReady(session: Session; api: Api): bool = proc invalidate*(session: var Session) = if session.isNil: return - log "invalidating: ", session.id + log "invalidating: ", session.pretty # TODO: This isn't sufficient, but it works for now let idx = sessionPool.find(session) @@ -171,7 +185,7 @@ proc getSession*(api: Api): Future[Session] {.async.} = proc setLimited*(session: Session; api: Api) = session.limited = true session.limitedAt = epochTime().int - log "rate limited by api: ", api, ", reqs left: ", session.apis[api].remaining, ", id: ", session.id + log "rate limited by api: ", api, ", reqs left: ", session.apis[api].remaining, ", ", session.pretty proc setRateLimit*(session: Session; api: Api; remaining, reset, limit: int) = # avoid undefined behavior in race conditions diff --git a/src/experimental/parser/session.nim b/src/experimental/parser/session.nim index bb31d83..2e5a171 100644 --- a/src/experimental/parser/session.nim +++ b/src/experimental/parser/session.nim @@ -13,6 +13,7 @@ proc parseSession*(raw: string): Session = result = Session( kind: SessionKind.oauth, id: parseBiggestInt(id), + username: session.username, oauthToken: session.oauthToken, oauthSecret: session.oauthTokenSecret ) @@ -21,6 +22,7 @@ proc parseSession*(raw: string): Session = result = Session( kind: SessionKind.cookie, id: id, + username: session.username, authToken: session.authToken, ct0: session.ct0 ) diff --git a/src/experimental/types/session.nim b/src/experimental/types/session.nim index dd6be22..dfec428 100644 --- a/src/experimental/types/session.nim +++ b/src/experimental/types/session.nim @@ -1,8 +1,8 @@ type RawSession* = object kind*: string - username*: string id*: string + username*: string oauthToken*: string oauthTokenSecret*: string authToken*: string diff --git a/src/types.nim b/src/types.nim index 092d85f..55d990d 100644 --- a/src/types.nim +++ b/src/types.nim @@ -38,6 +38,7 @@ type Session* = ref object id*: int64 + username*: string pending*: int limited*: bool limitedAt*: int From 0bb0b7e78cc617544231c286a9bb641166010897 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 06:32:13 +0100 Subject: [PATCH 133/302] Support grok_share card Fixes #1306 --- src/experimental/parser/unifiedcard.nim | 15 +++++++++++++++ src/experimental/types/unifiedcard.nim | 7 +++++++ src/formatters.nim | 7 +++++-- src/sass/tweet/card.scss | 1 + 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/experimental/parser/unifiedcard.nim b/src/experimental/parser/unifiedcard.nim index a112974..de4df18 100644 --- a/src/experimental/parser/unifiedcard.nim +++ b/src/experimental/parser/unifiedcard.nim @@ -1,6 +1,7 @@ import std/[options, tables, strutils, strformat, sugar] import jsony import user, ../types/unifiedcard +import ../../formatters from ../../types import Card, CardKind, Video from ../../utils import twimg, https @@ -77,6 +78,18 @@ proc parseMedia(component: Component; card: UnifiedCard; result: var Card) = of model3d: result.title = "Unsupported 3D model ad" +proc parseGrokShare(data: ComponentData; card: UnifiedCard; result: var Card) = + result.kind = summaryLarge + + data.destination.parseDestination(card, result) + result.dest = "Answer by Grok" + + for msg in data.conversationPreview: + if msg.sender == "USER": + result.title = msg.message.shorten(70) + elif msg.sender == "AGENT": + result.text = msg.message.shorten(500) + proc parseUnifiedCard*(json: string): Card = let card = json.fromJson(UnifiedCard) @@ -92,6 +105,8 @@ proc parseUnifiedCard*(json: string): Card = component.parseMedia(card, result) of buttonGroup: discard + of grokShare: + component.data.parseGrokShare(card, result) of ComponentType.jobDetails: component.data.parseJobDetails(card, result) of ComponentType.hidden: diff --git a/src/experimental/types/unifiedcard.nim b/src/experimental/types/unifiedcard.nim index e540a64..cef6f44 100644 --- a/src/experimental/types/unifiedcard.nim +++ b/src/experimental/types/unifiedcard.nim @@ -22,6 +22,7 @@ type communityDetails mediaWithDetailsHorizontal hidden + grokShare unknown Component* = object @@ -42,6 +43,7 @@ type topicDetail*: tuple[title: Text] profileUser*: User shortDescriptionText*: string + conversationPreview*: seq[GrokConversation] MediaItem* = object id*: string @@ -76,6 +78,10 @@ type title*: Text category*: Text + GrokConversation* = object + message*: string + sender*: string + TypeField = Component | Destination | MediaEntity | AppStoreData converter fromText*(text: Text): string = string(text) @@ -96,6 +102,7 @@ proc enumHook*(s: string; v: var ComponentType) = of "community_details": communityDetails of "media_with_details_horizontal": mediaWithDetailsHorizontal of "commerce_drop_details": hidden + of "grok_share": grokShare else: echo "ERROR: Unknown enum value (ComponentType): ", s; unknown proc enumHook*(s: string; v: var AppType) = diff --git a/src/formatters.nim b/src/formatters.nim index 7428814..cafaa4f 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -33,10 +33,13 @@ proc getUrlPrefix*(cfg: Config): string = if cfg.useHttps: https & cfg.hostname else: "http://" & cfg.hostname -proc shortLink*(text: string; length=28): string = - result = text.replace(wwwRegex, "") +proc shorten*(text: string; length=28): string = + result = text if result.len > length: result = result[0 ..< length] & "…" + +proc shortLink*(text: string; length=28): string = + result = text.replace(wwwRegex, "").shorten(length) proc stripHtml*(text: string; shorten=false): string = var html = parseHtml(text) diff --git a/src/sass/tweet/card.scss b/src/sass/tweet/card.scss index 680310c..5575191 100644 --- a/src/sass/tweet/card.scss +++ b/src/sass/tweet/card.scss @@ -42,6 +42,7 @@ .card-description { margin: 0.3em 0; + white-space: pre-wrap; } .card-destination { From bb6eb81a20b482b6ba1dd6da3332cd292427778e Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 10:59:50 +0100 Subject: [PATCH 134/302] Add support for tweet views --- public/css/fontello.css | 44 ++++++++++++++++++------------------ public/fonts/LICENSE.txt | 18 +++++++-------- public/fonts/fontello.eot | Bin 9264 -> 9368 bytes public/fonts/fontello.svg | 32 ++++++++++++++------------ public/fonts/fontello.ttf | Bin 9096 -> 9200 bytes public/fonts/fontello.woff | Bin 5752 -> 5812 bytes public/fonts/fontello.woff2 | Bin 4772 -> 4832 bytes src/parser.nim | 6 ++++- src/types.nim | 1 + src/views/general.nim | 4 ++-- src/views/tweet.nim | 2 ++ 11 files changed, 58 insertions(+), 49 deletions(-) diff --git a/public/css/fontello.css b/public/css/fontello.css index d022bb5..2453575 100644 --- a/public/css/fontello.css +++ b/public/css/fontello.css @@ -1,16 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('/fonts/fontello.eot?21002321'); - src: url('/fonts/fontello.eot?21002321#iefix') format('embedded-opentype'), - url('/fonts/fontello.woff2?21002321') format('woff2'), - url('/fonts/fontello.woff?21002321') format('woff'), - url('/fonts/fontello.ttf?21002321') format('truetype'), - url('/fonts/fontello.svg?21002321#fontello') format('svg'); + src: url('/fonts/fontello.eot?61663884'); + src: url('/fonts/fontello.eot?61663884#iefix') format('embedded-opentype'), + url('/fonts/fontello.woff2?61663884') format('woff2'), + url('/fonts/fontello.woff?61663884') format('woff'), + url('/fonts/fontello.ttf?61663884') format('truetype'), + url('/fonts/fontello.svg?61663884#fontello') format('svg'); font-weight: normal; font-style: normal; } - - [class^="icon-"]:before, [class*=" icon-"]:before { +[class^="icon-"]:before, [class*=" icon-"]:before { font-family: "fontello"; font-style: normal; font-weight: normal; @@ -32,22 +31,23 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - -.icon-heart:before { content: '\2665'; } /* '♥' */ -.icon-quote:before { content: '\275e'; } /* '❞' */ -.icon-comment:before { content: '\e802'; } /* '' */ -.icon-ok:before { content: '\e803'; } /* '' */ -.icon-play:before { content: '\e804'; } /* '' */ -.icon-link:before { content: '\e805'; } /* '' */ -.icon-calendar:before { content: '\e806'; } /* '' */ -.icon-location:before { content: '\e807'; } /* '' */ + +.icon-views:before { content: '\e800'; } /* '' */ +.icon-heart:before { content: '\e801'; } /* '' */ +.icon-quote:before { content: '\e802'; } /* '' */ +.icon-comment:before { content: '\e803'; } /* '' */ +.icon-ok:before { content: '\e804'; } /* '' */ +.icon-play:before { content: '\e805'; } /* '' */ +.icon-link:before { content: '\e806'; } /* '' */ +.icon-calendar:before { content: '\e807'; } /* '' */ +.icon-location:before { content: '\e808'; } /* '' */ .icon-picture:before { content: '\e809'; } /* '' */ .icon-lock:before { content: '\e80a'; } /* '' */ .icon-down:before { content: '\e80b'; } /* '' */ -.icon-retweet:before { content: '\e80d'; } /* '' */ -.icon-search:before { content: '\e80e'; } /* '' */ -.icon-pin:before { content: '\e80f'; } /* '' */ -.icon-cog:before { content: '\e812'; } /* '' */ -.icon-rss-feed:before { content: '\e813'; } /* '' */ +.icon-retweet:before { content: '\e80c'; } /* '' */ +.icon-search:before { content: '\e80d'; } /* '' */ +.icon-pin:before { content: '\e80e'; } /* '' */ +.icon-cog:before { content: '\e80f'; } /* '' */ +.icon-rss:before { content: '\e810'; } /* '' */ .icon-info:before { content: '\f128'; } /* '' */ .icon-bird:before { content: '\f309'; } /* '' */ diff --git a/public/fonts/LICENSE.txt b/public/fonts/LICENSE.txt index c8d90ff..41f18a8 100644 --- a/public/fonts/LICENSE.txt +++ b/public/fonts/LICENSE.txt @@ -1,6 +1,15 @@ Font license info +## Modern Pictograms + + Copyright (c) 2012 by John Caserta. All rights reserved. + + Author: John Caserta + License: SIL (http://scripts.sil.org/OFL) + Homepage: http://thedesignoffice.org/project/modern-pictograms/ + + ## Entypo Copyright (C) 2012 by Daniel Bruce @@ -37,12 +46,3 @@ Font license info Homepage: http://aristeides.com/ -## Modern Pictograms - - Copyright (c) 2012 by John Caserta. All rights reserved. - - Author: John Caserta - License: SIL (http://scripts.sil.org/OFL) - Homepage: http://thedesignoffice.org/project/modern-pictograms/ - - diff --git a/public/fonts/fontello.eot b/public/fonts/fontello.eot index aaddd6bf3959a71b00b92cd01dd4067db5d94b53..2b2982a5711bffac0cb1aad4690f661e34e308c4 100644 GIT binary patch delta 901 zcmYL`Pe>F|9LIle-kaGO-DaKLSzJZM*3HB}5teR{$kHLKgNjIG3c2d~Cw6skm(4=l zhy<xi@(xLm*v&(Pw6Yg5I&=!`P*Na79r`oap~CGTv~IsyCz>~(dEf8v{T}Z(zqi)0 zbU|DS0Q_7OgG}AbZY+itQtg4>EdbID0E5G^u<_az?MJ@`Aj}RY6V({U$9o1p>Zmam z8#=Ln6MY9-=U6yyV7vtVNu1@e=#A0-kF`GlSR;UQc03XuX?h#@fGMlE-a3u}#~u0@ z{U`K+@mQj7=3FkH2hbP>x}#IW;fl4g5%j(2>to>=g9O!w=x?E~o(#t#TaPL#(XU{E zTgFs8@$7s1X8_$QWYd_A7;~Ak8GxEzzC!vaqka4jX-k=~r_i4KHMl~bzynMr$p1$` zDIyfX$^nZZ3wF+Jj9S@^I2RS-3@)LUao|3)<X7FEV*P>%{}irTC&*BT!&3ac;1@lp zBu?l7FQFpOQ>ijrNCdzY6QzZ9lv#lLSyxI%hk$~!ja7tc1I4Iqz=_%hj-a*y6}1hx zP}^A7X4-%TSo|U225i<LDJ%_plHV0pg*T`icwkGYStcS8=OscYnRgHhbe;*=ili(v z?3`RI7Z)ka!5or^d#I*Jr41gfMiUW&tgn-&)+?~wSE2mwfkEz*%Q>H57WxcyKzNRY zx<f8!dY>f~u?R}xI8Ug}ZcmL{mZ}<py5z4t-Fm9Iitvo;XUEFw%lVFal$O=Ucup-% zdtKj1#YT2hz3OWF4K7;f?3a+SY<1Y0xlao^bSo~b@*b_LsTBrc8Fq<4s>!k-3Fn0u z)JHq%HuJGLmK8h29jX2BdO%OV(iB2k__jMkTGIDC7YWh%JH1vRUJ#QLk?V1u(+6C1 VIv!^elcQ5?XkvN<(oyeA^)Fc#!jb?0 delta 802 zcmXw0O-vI}5T3Vhx3p5aZI=RKW02ss3MHhG)+h&#RY)*WNic|6+HDs@VWka0f;L7% zh$nI5PmB`O3nybJ;o!lO2M@(?FkZYUr;TZ12pTbw)Oox5Hs4IXZ)V?ov-|km>x9tN z2C%d&#QB5$NOHM%sW`uwoChEm0AMJUQ}n0zKQ$s>1K=K}W(qB+<Eh`q;K}IO+*s%D zIpklEy0c1NN8OA3J!WTi;zs8AD!mTC_X4=xnyRFmE73T%^kY4yp-{6xSCKCuZ_{#x z;o06x)hY@&5c(&kQcB}|^F8E0kcV^1tWIK{Cjh=6@|H;@r*1uKY(n0H2DkL7e4(>4 z_yG;LvHnJ%R`pw(XEy;_E5<4rri7-9KcuH9t9vebv3d<I(?NKItps5o0TV@ZAb2di z`d3>+m34JsK4j@U;FkhF?n{A_)zGL}W7e9YDeZ>$Tx<zT<4l)XXZrtZ_y)icD_Viz z1?&kz3$%d@va!yEaFXNYSB}Wo!WIEAt;nX~tu~mO&?tk!^8-Z}HX(pz^vA$_nzc57 z<vJVePSaxpm?qi)rr`tzV47ru{n6d94e$Z%;S2`m#{N2Vq?8bfF2M-_IBt|ZQLkU} zI~_;c<MobK`BW_0V0gt=t}kxH#AAnJb3+f>BQxFIinchZrB^y4x6>n6Zr*{CE}A5m zjO-s=6?=YyNUgO$mw)x)AYj=j_~KNerm^lDIB^m**nl#8BO2M}yxd)Ghc?j_y2B^< zw?c#P!r`#&Nu^G|i;ynkw6saOO5dgPgh<AjfK0#`4~$68>3lwtQPng*KAD-~$Hu4A JrH_Gkp1<)Mv_b#? diff --git a/public/fonts/fontello.svg b/public/fonts/fontello.svg index 1f30ccc..2a64343 100644 --- a/public/fonts/fontello.svg +++ b/public/fonts/fontello.svg @@ -1,26 +1,28 @@ <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns="http://www.w3.org/2000/svg"> -<metadata>Copyright (C) 2020 by original authors @ fontello.com</metadata> +<metadata>Copyright (C) 2025 by original authors @ fontello.com</metadata> <defs> <font id="fontello" horiz-adv-x="1000" > <font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" /> <missing-glyph horiz-adv-x="1000" /> -<glyph glyph-name="heart" unicode="♥" d="M790 644q70-64 70-156t-70-158l-360-330-360 330q-70 66-70 158t70 156q62 58 151 58t153-58l56-52 58 52q62 58 150 58t152-58z" horiz-adv-x="860" /> +<glyph glyph-name="views" unicode="" d="M180 516l0-538-180 0 0 538 180 0z m250-138l0-400-180 0 0 400 180 0z m250 344l0-744-180 0 0 744 180 0z" horiz-adv-x="680" /> -<glyph glyph-name="quote" unicode="❞" d="M18 685l335 0 0-334q0-140-98-238t-237-97l0 111q92 0 158 65t65 159l-223 0 0 334z m558 0l335 0 0-334q0-140-98-238t-237-97l0 111q92 0 158 65t65 159l-223 0 0 334z" horiz-adv-x="928" /> +<glyph glyph-name="heart" unicode="" d="M790 644q70-64 70-156t-70-158l-360-330-360 330q-70 66-70 158t70 156q62 58 151 58t153-58l56-52 58 52q62 58 150 58t152-58z" horiz-adv-x="860" /> -<glyph glyph-name="comment" unicode="" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" /> +<glyph glyph-name="quote" unicode="" d="M18 685l335 0 0-334q0-140-98-238t-237-97l0 111q92 0 158 65t65 159l-223 0 0 334z m558 0l335 0 0-334q0-140-98-238t-237-97l0 111q92 0 158 65t65 159l-223 0 0 334z" horiz-adv-x="928" /> -<glyph glyph-name="ok" unicode="" d="M0 260l162 162 166-164 508 510 164-164-510-510-162-162-162 164z" horiz-adv-x="1000" /> +<glyph glyph-name="comment" unicode="" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" /> -<glyph glyph-name="play" unicode="" d="M772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" /> +<glyph glyph-name="ok" unicode="" d="M0 260l162 162 166-164 508 510 164-164-510-510-162-162-162 164z" horiz-adv-x="1000" /> -<glyph glyph-name="link" unicode="" d="M294 116q14 14 34 14t36-14q32-34 0-70l-42-40q-56-56-132-56-78 0-134 56t-56 132q0 78 56 134l148 148q70 68 144 77t128-43q16-16 16-36t-16-36q-36-32-70 0-50 48-132-34l-148-146q-26-26-26-64t26-62q26-26 63-26t63 26z m450 574q56-56 56-132 0-78-56-134l-158-158q-74-72-150-72-62 0-112 50-14 14-14 34t14 36q14 14 35 14t35-14q50-48 122 24l158 156q28 28 28 64 0 38-28 62-24 26-56 31t-60-21l-50-50q-16-14-36-14t-34 14q-34 34 0 70l50 50q54 54 127 51t129-61z" horiz-adv-x="800" /> +<glyph glyph-name="play" unicode="" d="M772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" /> -<glyph glyph-name="calendar" unicode="" d="M800 700q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-700 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l46 0 0-100 160 0 0 100 290 0 0-100 160 0 0 100 44 0z m0-700l0 400-700 0 0-400 700 0z m-540 800l0-170-70 0 0 170 70 0z m450 0l0-170-70 0 0 170 70 0z" horiz-adv-x="900" /> +<glyph glyph-name="link" unicode="" d="M294 116q14 14 34 14t36-14q32-34 0-70l-42-40q-56-56-132-56-78 0-134 56t-56 132q0 78 56 134l148 148q70 68 144 77t128-43q16-16 16-36t-16-36q-36-32-70 0-50 48-132-34l-148-146q-26-26-26-64t26-62q26-26 63-26t63 26z m450 574q56-56 56-132 0-78-56-134l-158-158q-74-72-150-72-62 0-112 50-14 14-14 34t14 36q14 14 35 14t35-14q50-48 122 24l158 156q28 28 28 64 0 38-28 62-24 26-56 31t-60-21l-50-50q-16-14-36-14t-34 14q-34 34 0 70l50 50q54 54 127 51t129-61z" horiz-adv-x="800" /> -<glyph glyph-name="location" unicode="" d="M250 750q104 0 177-73t73-177q0-106-62-243t-126-223l-62-84q-10 12-27 35t-60 89-76 130-60 147-27 149q0 104 73 177t177 73z m0-388q56 0 96 40t40 96-40 95-96 39-95-39-39-95 39-96 95-40z" horiz-adv-x="500" /> +<glyph glyph-name="calendar" unicode="" d="M800 700q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-700 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l46 0 0-100 160 0 0 100 290 0 0-100 160 0 0 100 44 0z m0-700l0 400-700 0 0-400 700 0z m-540 800l0-170-70 0 0 170 70 0z m450 0l0-170-70 0 0 170 70 0z" horiz-adv-x="900" /> + +<glyph glyph-name="location" unicode="" d="M250 750q104 0 177-73t73-177q0-106-62-243t-126-223l-62-84q-10 12-27 35t-60 89-76 130-60 147-27 149q0 104 73 177t177 73z m0-388q56 0 96 40t40 96-40 95-96 39-95-39-39-95 39-96 95-40z" horiz-adv-x="500" /> <glyph glyph-name="picture" unicode="" d="M357 529q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" /> @@ -28,19 +30,19 @@ <glyph glyph-name="down" unicode="" d="M939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" /> -<glyph glyph-name="retweet" unicode="" d="M714 11q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> +<glyph glyph-name="retweet" unicode="" d="M714 11q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> -<glyph glyph-name="search" unicode="" d="M772 78q30-34 6-62l-46-46q-36-32-68 0l-190 190q-74-42-156-42-128 0-223 95t-95 223 90 219 218 91 224-95 96-223q0-88-46-162z m-678 358q0-88 68-156t156-68 151 63 63 153q0 88-68 155t-156 67-151-63-63-151z" horiz-adv-x="789" /> +<glyph glyph-name="search" unicode="" d="M772 78q30-34 6-62l-46-46q-36-32-68 0l-190 190q-74-42-156-42-128 0-223 95t-95 223 90 219 218 91 224-95 96-223q0-88-46-162z m-678 358q0-88 68-156t156-68 151 63 63 153q0 88-68 155t-156 67-151-63-63-151z" horiz-adv-x="789" /> -<glyph glyph-name="pin" unicode="" d="M268 368v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" /> +<glyph glyph-name="pin" unicode="" d="M268 368v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" /> -<glyph glyph-name="cog" unicode="" d="M911 295l-133-56q-8-22-12-31l55-133-79-79-135 53q-9-4-31-12l-55-134-112 0-56 133q-11 4-33 13l-132-55-78 79 53 134q-1 3-4 9t-6 12-4 11l-131 55 0 112 131 56 14 33-54 132 78 79 133-54q22 9 33 13l55 132 112 0 56-132q14-5 31-13l133 55 80-79-54-135q6-12 12-30l133-56 0-112z m-447-111q69 0 118 48t49 118-49 119-118 50-119-50-49-119 49-118 119-48z" horiz-adv-x="928" /> +<glyph glyph-name="cog" unicode="" d="M911 295l-133-56q-8-22-12-31l55-133-79-79-135 53q-9-4-31-12l-55-134-112 0-56 133q-11 4-33 13l-132-55-78 79 53 134q-1 3-4 9t-6 12-4 11l-131 55 0 112 131 56 14 33-54 132 78 79 133-54q22 9 33 13l55 132 112 0 56-132q14-5 31-13l133 55 80-79-54-135q6-12 12-30l133-56 0-112z m-447-111q69 0 118 48t49 118-49 119-118 50-119-50-49-119 49-118 119-48z" horiz-adv-x="928" /> -<glyph glyph-name="rss-feed" unicode="" d="M184 93c0-51-43-91-93-91s-91 40-91 91c0 50 41 91 91 91s93-41 93-91z m261-85l-125 0c0 174-140 323-315 323l0 118c231 0 440-163 440-441z m259 0l-136 0c0 300-262 561-563 561l0 129c370 0 699-281 699-690z" horiz-adv-x="704" /> +<glyph glyph-name="rss" unicode="" d="M184 93c0-51-43-91-93-91s-91 40-91 91c0 50 41 91 91 91s93-41 93-91z m261-85l-125 0c0 174-140 323-315 323l0 118c231 0 440-163 440-441z m259 0l-136 0c0 300-262 561-563 561l0 129c370 0 699-281 699-690z" horiz-adv-x="704" /> <glyph glyph-name="info" unicode="" d="M393 149v-134q0-9-7-15t-15-7h-134q-9 0-16 7t-7 15v134q0 9 7 16t16 6h134q9 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 11t-6 20v26q0 46 37 87t79 60q33 16 47 32t14 42q0 24-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" /> <glyph glyph-name="bird" unicode="" d="M920 636q-36-54-94-98l0-24q0-130-60-250t-186-203-290-83q-160 0-290 84 14-2 46-2 132 0 234 80-62 2-110 38t-66 94q10-4 34-4 26 0 50 6-66 14-108 66t-42 120l0 2q36-20 84-24-84 58-84 158 0 48 26 94 154-188 390-196-6 18-6 42 0 78 55 133t135 55q82 0 136-58 60 12 120 44-20-66-82-104 56 8 108 30z" horiz-adv-x="920" /> </font> </defs> -</svg> \ No newline at end of file +</svg> diff --git a/public/fonts/fontello.ttf b/public/fonts/fontello.ttf index 29f1ec6d6ee124b4f25d2cad015933efaf7f982b..ef775f87308c85319ecd0d8c36791622adf5c7eb 100644 GIT binary patch delta 903 zcmYL{TSyd97{|XeXJ&WC%j{LWpt7vCiSAlhvv!e9ddP}UiB&R{9Nk^l*vsJBX5yur zB|aHL1VKx^Nkm91zJ$<&kJ&@@6huK6279Q`pdPmE8y#uR{O0`s-#KT_cfOgqwjXT^ zu9-k>a2<ej0>HpfTvlFLV!h~>0GP?4^q33dc$FN&hZs?Y<AaTVCeWWmTQn@E6pUNZ z@5i1Wj$MiLe(-z;V7&lElhLpo^1Z2jk5d+L+#kh&`6hjY{v-O@Xnd?^{CF;(2e7di z=!_+Y<jSS;5c(kc`nWu<kY@2d`m5+&2{|5Ke^6P4{vjr~t|U`q&%Omd132|WR+Z7P zGQDzi1)wH)=XVcfX^$EtEi)Qy3iNFLBAlmB;2zE-$p4E$$srWM!~t_5bH<$O7}aNc zu`k$;v(Sy6$A;@P>(9DPK|g^8y9!739eAk2rYm(H<0g)5CsRzSwzfwB9QukK^!IDq zs1vkfhDV2hfW3jfvt|H=s12Y9wE+~PHUJT|0a#EQ=$}e602^S&JAfUqLOUcdH7rDa zgIQ!=qcY=$Qfm`WIKs`E2%%)wOeoM<mcjF<d7i~`@r8V0fxwzsvx&ns)Fzlp8yvP8 z8;1~Nd6_)bZ-&i%Wh&<XDrAA2%at)&ww1jta0O5bd)1f4O?F3(oi|lCG&@ah&q4oz zrfQ;!qC2psyuN~PQ(bMRx-W$yM~)FX*XYfJqRIK39P%IT_ejzisk}bkeYe4vmLxfP zD-jK4yuNFpzW%8j*_f!2%I90F;sr~~FR)O(^zfxs$?W#3Vr@1%^*FPrKDKuF{4f9+ z*dPpXkql#EPB1TM8EvO;*)n#T-QwE0ccuWY*8dG>)@>6AkyNjJg-F>?_S1wo)kddB iASJ-1N5Yp=YL~OmLPt|6b|eu=vV$X|A;_*d*TjFl`^A+2 delta 787 zcmXw0O-vI}5T3VhTl&ZLXUmUBOhs&~w1hO$9^`;0jF3PCNiY?&wA(I*!b%B2f;L7% zj0c0fsEJX6o;Z1_;oyP9!~=(7IC=46IBiT15;WF>sq=RAZN8a&-^{-GX7~HR!-4fs zX=QC}1%QkIz<8#h=r1=uw;<mJ5T0cg7ec7x2_9gOa(ccn)%|-J`B$W#yi(Lr_aWcI z?8wjD$-Um9w*ka{0FOsgm26uzk;0ZCtS2=T8dm8R@>S%WT47;gssCENjsgyZp_#di z(z4RFj{FDmXhB)hNm6<N;BQABnpFzw{;QT&<b7yxU!N;3bXP||p#cxp-|O?LesAaE z4nRlsNo|4>nqgz4uM}2~o%Cw`7F?&J@Ekh{!a6og6cIy+Sa|!dwv8(9ieYw{x(N88 zAb{&q5W;F?(r7T;hS!jfqQ_3YgQ*FI(`YmT|22FA;G`MNK=1<ov_l9wAq-*W5+XRs z&MpZxHX^vs@I?R&GxBkGtp#o;GReWxbhzZiCIs+|206G<!)*b0ZnOY+ry*GYu6Zp0 z*Kh&{a80(r|L9TF0{8)raRvu>V|^Z)E^mn?r_BLPaF%sS312`CIP9moQbBu1_(C$# z%rvP(7)-Ip(&-b)<?*Lo@x|U=MO&NIvYWB^{cQTi-3L(KlMK>67O(xe>FxUoUTUuW zrSfYK2LaEl;!9DP8qDP%K6f58cn1~uMl^CD_=HEoA#J6b^iaGk?%JAdZ|ru{o?Kq@ xI|=Dw>wz88Qy!475F)dUV3+{=5KPOC`C>7iQ`M|EJ)4^or>5t#<yg~4=`Wr=u$=$^ diff --git a/public/fonts/fontello.woff b/public/fonts/fontello.woff index 8428cf8cf6b3362734cae51568f5c293ef1e9eb8..63c3c233509e89b741c92971efe70a441b972b8b 100644 GIT binary patch delta 3748 zcmXw*byyQ#7stm&jP6omgwmry8gYzCC@DWcVsuSVKqhQVN8>;V8A^$OfGCZ$gruaP zNC}Ps5<j|;c<1}P?|q)nbI<wS6Zigg&yj4699QUVI2=F)pe&vm0PEjfY%7^d9S5UW zF1q5@gKj8fAOJu$LGeET06@$TtG|QW!(a%-j-zOd6j&$#JOjN@6fF$^pk1d(Os>%5 zFfU{fC6_*mat!_-Y+nA4JSiH1LJt7IgmIX=y6NqKbO!)9ttm0e6x8I#aN<9`DJn($ z+XO;EoGuEK=Zy+RQ?z1=uS>z);OyeUKfvv8+%m<N{)<2bzdH(vrZnc_qWF-%P^q)L zx`#x0P&5_FV}dCc9UROS5DW|mqS)Mj>pi2;QnjYL5ct3&5C8)3TuU{jG6f-%+*32D z#Bfdum?d|XliV)0uI3n|rg;XtAv-6)x11>qowc5Fodd7HWGIZ6Oh|Y@&y&*wYS<~B z)U5Bx-D!zl(Y|WtgWO^8e_}sl2>Sjsfl&0$rsRi!aSJK$*vSo~*j&`$z~gaR#qnZX zB5^=%YI>PbPRm9sJE-7y=SxPA?L9)+2gIT<_ECiP<?2$I6^Q9d(@ihrs6)mcKO8GT zU}A$q!D2E|H;0k#2@1!dNcU$7I|x4hD}<TVi1)o!8$Aga<xm-dbAF}r!|FmHGab&s zmb>-$?BVe6L3QqhwpJ2(V)!7)SK{8Dt6ummM9WBwT9i1m<Eyk9Gvo2AT#exCq#F*= zs`MVFt6Z8T@8D84HfDx595vt4zwpLm=RpY0cSkkt;_#BUN@f^78**To)r2mxRqt>% z^b|96oISUBw*x#Grte(1$pLqtzlvLI{O}k&pFrK_Vn_?0NYIxODdIL{16#}_IE2tw zK}<x?V^*M2mf%$%$4<^I44L`pie7QWu}EUe$Ujj;mj}wmo1Bm%<XCXz5#sg6E;#L9 z&|Pgo-#p72037k~iOsuf54C<p^G8M|_9ym7MxL$h`+m4kb1Z9B9YJ%((Bfv_{qQZc zWOg>n+-ycTi%HX`mW4S1_7eV>@ptP}EpyFE4kTQQI-}AlCa>5Qz(g`eZ&;T3Yc;4~ z)R@|qwe)hFVh(+_arr+kt;v4^4eQ{j;pbW%t%g79cqXtXTCU0HmO^fDTLo{gE(R#$ z9jNK{R_ZOC!KH%1EOGLWFSr+SJySG8(B-10*Wd<Az4c1YwvF-#v<OjeKXsh&PEp!? ziM?4wV}XcG4N~}s3Ek+3Ee&)(=q4!mNdeK;uM^rZZ#*Tp(Fy_Of)jPxR@n_-_tv0I z&qj=4jbcg_jA2)R?7fby^v?qPhwjpmPME!aEz>^Y=#hI{4Vjn!iA$E6YSORz64n<F zku;s~-=_sSov6!W3Kb<vb(g$_XpHha&+E?HQua7GB(MeL=UmA!pXfMz9KAjh*ijTH zDq2A^N*x&}3yytL{zGbWxi{lOWd&61+^#2yPxrZLX;i}#aC&4~b)bm*1os}uC<9j7 zIjDduyvtr6k|Z&P1DLFBNu(aHzUW#lY&S`o%T-+8HmMvnXVQIlr<{fPaN1tGd@{Ve zoBTCw<{;Gjt&f)9+*cLlF$>3L%a$4~^POE{@U!x+m~}oKrxweW#vkLF9QKE^w(~c> zW?ZUnXjn&R2lU2#u$@pcNdfy@Sr<3+T#cn!MRpd}$5kx^;rs>)@f~-+(6h&j_nB&v zX!!MY<J9?+II;*=Q<p8c5K7smnI-7(W}UA*sKk%m_Jlx5zLB$6CZgb6XC(%g%gFcN zg1`88j4_gO9xx2Q&KZp)sU(egq!0Y?Js$iih{Xza`MEf)&CFm?&!r>xpwQMY#1;2X zYO%S4KesI1aLHE!NWbaRx8zTrd-GX@wlJ!?<hJ=#e*xSVvqFmn^DwVOw6Y>3m!BSq z!9ZrPIs8ZVH@cDxiz%En*(n~P51uXk82a|dCdjqTF0(u-Q7(xv66?3ACUi?+i;GCS z$1+f^I<)n@OHtGxg=&HR>wfReYlE89?1h&nXooo9XWRvFaP5AcYv*SKGm;O>2Ifvz zC@Hq$&}uKu0NO$Dk_>e5kT|19+)1nFuI<Kku+#Oha0Nc|g7m9`l>`m|$(lbt+3aP? z`&fc8!*MzBJ=pV~ZYEoW&-I>d)n~CM+pn?Dgv*5SY6xvz`O;s>6LyoJ)pN^(?PCI{ zP*y7wCuh-8n#*F!1g`DQH<H*iy*@o`<FU)8ZoO<~$m}olCB3b0B=Z#hkJSyKigMQh z_7u@uEwQ%guqYZ)sj6PS#rFHF0;dGcuih%$W*#??I&4h!1t0NF^DAx2DWhW^1+<1G z@de>u&dgVC>Wee+Gy??<$nuFR-E_GfJ!Jbp-24%G;&yC+l`^y@1R!uM_D{{W6B$9) zVYnmc+frg}n|KgX$h!a0i*G&d@Sb1S++=1j9er|oBMIdz6m|&PGDrq|W@@@7|74f1 zZxsk#E*5L`7O>*<C1*@y`8G$W1UKrvrkHszJBK!riZ-G&*B`2%6Z$uNLNQH3dfbh; z75;&K-adUSkseR|ndvyP*u>;Wej++5h{0SG^x}3PT&+@~_~6>4p1Hc^r1uwaD78&V z%X7m-qr0JRRu7has=-Y;^;i2=0u!E9-SrsAZq^RV%bF6}zT_QC9knks{kqlY+J%U5 znh2W&R>XXxJ~bzVn3o+@<T#%O7?xhc<rMr$!pkHwf5NvJMK7`5@{PGH^F%2vfKS!_ zd$`cBj;atod{n)QwRu&i{4i(AcP90Af>T=PRm*7A@x*oAx_t-FQ977};;7wR9@#Cv z`T?IJvNxIL-q<%=rT+CpI<fXFNEd@0^qp1%+CD-e{*|bgg+=D6yMd_vfVg@LuA8Vf z3)p#B$=(w9M?gr-vpK``-h%q$=7vfYjbOi>j)wj3LDLbK$Q+}j#+%7&I#-3-g_rDG zjol636#KZhU$LAGE;<riwxCK}_O|t^Fk*eFLki?%mJH&ZdaNZTs^s(b34vjF<m=tO zm9OltJvX*rCduv<M{u(q<>Z}JGvlo3F*RGZ)Gk${=|x?4&>!V;14>Oh=EV$cxFe1e zJ@v_)cI~<(#^Zx5JnU0??2xa7ite>q!T#Y1!*TdSw#<La^s)yF$R8Br+gVDlxE}Fa zNbdSL-Gu2{#>96Eva<%$kVR_jwxUmmTxX#ZeFr?rp<(X_;<8dP&xh<ZG;p@<uAlRp z86Ug=e>}~JV=sZZ%Sy1bOX8!j7xM|C)^OailJrxXx=L+J+8Ah-Ub%*tOw46oRjkKA za>0x4vG63paAeYuewlCm9e6*!s!_CFI=|F(2O(<zHr+^&SkWviePnR^^~w3b)1KvR z#-OM*CfZZAGu4e$QE?;beO$@}EGgy6%+Y;Oxh*(zvjthyRHX8e2-RfyZVsm@-BC^n z$NbqO=`xR^W7UH@jfC}UuD_`N+z;~II(B$Ls!B!m_2_H=86X~*QT`@0inoHXYp676 zcetfOL+iJ8$b>X8wqNczz^wW1vZf<+c&I$*Rc_PN<bGts%4e@(b=>x`I+o$kR(I6o z)_H<T*HV8S)-5%JF_a6DACRx)x}7Ul&&v(l&<w*oZ;D83(w(v&MqQp)Textz_1&?- zTm<PMF6Go4(;%KP&I_p1N1V7I+^T4E9X{-V-~l^=j#CSGOOi(3mYA7ch|ep(?~9Lq z3fH=l5I~6pFUi~l9C5%@<(o)p)v0PUgH>H4%everLd?i1vvfxetk0CzI-t&WKa!Nd z-4l6UfWkXhJ9fNYu!X*?jYly@v_Z%8lC>wTgeQ{wKU(;Q$iYYC722btKHH-<+5#IL zEBLg>Wmu1c>SN}w|ExhbL;6VTI`6)^*|Hf$I=2Ggozg^{^q>qwgH^J7tmDwPPZvks zwZCf@cDB!7KK3=DV{va}?JUEHXE$!_?MI)K?h(fLvFGGWx@Eqf4xHks2z6%q+J=pJ z$17}t8aBJ)bh3+jkIf+y0Cbj{i@S>pVE(5w!)({?G{)nXR|FLo61{4!zzV;(xX?@O zdaS|MyXb;*0mcIXL`N3-|0n&OY}?x2P=YRoWoeo8*|{J<K)M92ETw2fds|x#{>c*! z+BjaoHlrjDwJQze9@53-wBx@idZtN+!icB9ebwuG3W5MvKS($&2n0~tVW!O5|6OVT z7eEeRl?q4&p~?Z$0tMl~HV_}^7U&x_A9V!v9?dP9DcZjSJrxD6)LBj;+?;QR0#__< zdrmDH<zF$q=qu@F!#E4Z{~F0$hsV_Ak5Aq474zZ3B({7O@|3UinjF5=V?Lg;q~(0; z)+p!~?Ht>Lo?wy=W0vw%5S7=m9duOB0h;VAOSJf$xyW$Vv?wjaZeK>?5q=Lb<|XhP z)yM|QmC5o3N^QvTuH?$%h`l=f9y9T_{oG-554bs#?aD~kG0fk5OUVjKRl44Yy2XbJ z!7|)7Sz+;e<5w*;Q?VMhCG;sUjZgMMXo0A;&av1nh1<aFF6kGjq7d_Z2y=}WezGj= zBfX>B(u1<^mMo~4yFRsG3>nt_zHxAAZdc)P2ZMumr@sv~C|(nXimZOhV-&065#zq3 zD+6a>rB94i4TT*Wch?UqdZmrh4j_72FcCzF2vddzJtQ}_Doo%uqDgsv#-zrsE+j=& zwD8ee;`HR^Pi24fD-*wZzfhCxIb&C}^^GdwrdpcB+nWoHiZzZZ$LG5^J+gn)ZFClD zV44FD;esP0uHzK7h&m7wls_5uSfd3y6g(Z;08N+dye)R_XQ6M92ct>6bXjG>HCBh^ z+I*BUwEj((Sz3URiFCM<BC-6XvQIx5A;PO&+?V-m2g+2dnq%PQx<2{p-)qZ#liEg> uetgzFCL_r^25F@=L;ms-yakIz1Su(Aq4yimhXxp`d{CKWLFylF?0*2}8o!zV delta 3710 zcmXYz2Q=JG7shvyjb0KpN;XTZkawf5)rl6-6D84EHEOV|5`R6261)UiohZ?X5MA`> zy+rRdgsjfD`Mz(?`JK7<nS0O7oH=vOQ}xfuLwZk5B_$9TL>P=%5Y6A2Gc1-~6p$xG zdcL7LzGz2t7Z3<MNyz&^AfiNE+&B~3!yQV9^9dRS0crvuYZn_wf>s3rL3Rler3K>D z+s53D&`Xv>SSI~Hv^EZ2)&#AHfGY?@Ntu~J{My#a+!6%34JFhF5<uSB0R%;D2`WL< zAqamLxNZg$XV^NrV+oo&A(tSaCaZgW?cj|5TUSQNdH%xWd*{8QIhL^T?Z1bH{soMr zPH{4Kv?6FHgv${6iTe6dnV4Oi-3T#*1ED6Gz&FIs^L`gsD;E$kh~Xk$4Xj3NsbL-e z5zGmUK9g}1ym>ib5oSScokUG<P0|epEgw^KgRr>OI04sIep<!!NMcenn}VubESS7s z;e+9dYyoPbI+8~A%3G0Gx$9M$h&Ng_<q{(8pIj5hI2;u`QR<L#5jnPE=vRgC?Gf($ zH8RFHK4n&S?$sM}Q>~=jp&=p9ry%Fon;k%Gzg_#gaW=c*tYv@T{g>;QqRlD(jjG}~ zc;7t!4EU0krl;R7!GV32XP@C@!Js+Ln^vMLiTRwr{X+4wllLjn53qD8(KTxk#eOZ# zg-11+izrDAA9&9rAa2!<`d<MwH4i{tCHD83usF1tEadOAf(dc4zP%x9XC;mZ4*^(h zSb+5720(hoCjA=6-uZf`@{PxCTNDC6<yO%~;b>d=J||N(KCW7&dpbA^@tB|X)#ozq zmn`uSkKwvwJh_FuI>H62<|$g?+>>x=|6r~5JSuL14&em5rSJK7KCENNXW;#w$5*wT zo&)C>Y4g9l$Y>=eAI`$0Z`qzm5&`$m!SHmNFWL_bNI1FbntAov>u&FNL@q9_t*)&u zE(Y`|6-&E(EJ4n(y=nw=$~z;s{<zjgmu44xn&gzyB?PN@W<x>dmg*3tsT2}=|9&%h zbCWg%8|j7|`=ch<{fL%-IpSOyX1a41E1f_c^H(i9y!EQ8*V_%_KYdRfvA|wPHa~uz z!;jGzsA`kWXu8iBE;>CO0E+Yu*y)6qg+c~wdW92Uillb$k#2F3GJN{wZ1%J`Ip&NY ztw19$fBZC=+1;Wh;GRC}G8-RRD8Q?=Mpu7NY~{n75eoitZ_mZn^KBRRs}3B$^8+HR zPDVttZ<aOB0k+J{neAs2Ag=$hZs_+m;@qDOMz<N=)r{C0BUONUUUEy;mrHrF2XBjJ zgg3CR9Edv!q}~W;-L;CqBi<K9n0UZQ$!}NBGz9p6xay-YeTC>Mj1fywZZXMcxK?hm zQ8^<EkM|p%#|A#hs!~#rYXd<c!6md{I=abQb^&rI#$frO`E?z@V^zr7=V8&0Fkrl# zGn9sFgct1q4cvsTKE#o-craWxb3lf8-Gm}d#q^1sQkPqVn}>g2SypJ(RT$L?`*t6k z3w@IEdZ5P<-lF_vdMtJbZ1|2z*k^tWD>)rDb5(DhQeAnVt}Y~WI{iCgI#(*7BE2cc z#+4KKs&gZJI$;_ZdsCgLX)ASZ5*4F>e$~6SR?T@(Lso;=pW;=GBejF4a;dM~Q6||k zZ_BL-t5|Zg@68QtHR@5JV~(}tPCW{wgx+mc3PqFLT<+%broR!V1|PBJon!f>=B_Z4 zy<3jtWFv>TH?75ywyuAOgiR_j3yGu(8txy-)qIU><;w^1j?7R|m+m3=DgJny`)D>c z$(VkFtbHljm6`}G5LElMbTn`xoQfD_+Mk>wmT^E-uKXj(fhN1oIt~xz+p=?41)bl} z_=tgou_m{K4tfda+`N#6W@bEEilL<vrh7v%trT{a!KKrqG3e2g7~UoI;wNf^S*x$$ zo(;MjDNqhbB9(qQUzB?RTN!y(-(1~nUuS!T4j?pX7l*QyODXD-Kl0?$rdo=6eG-_W z%WB>?%<z&z=tN?f3+_c`(GX>}l0z7npX_iASdd$WlV<U-!2+Qi%P&zS=c<Z)vy2Cx zTd6H(va!<!?rp+PLBY=%xRj7qPb)YC3m>dBnN$NXW^)!!qju6|9C(15jiWgpFH_&- z#Vigz*sm`NRJO3OqRGG6Gxc^|i2C2k9fxYQKYL(tVG*v;k2D;(Z$iZEGP()7-+yXl ziZY0ly+Ij5;PTGDA;~QwEAn_NBe&q3u#t_`<ok5y;ZGtS3M!p0p)k~Mlu~VlQzAZ9 zWR?LOq!6QL%G!?(T^1NrHaE$i1!avY3^ycz^YPr!TM!D9kov(3Rk!NXxxD>@97deD z01JL(Xl68$g5AqM8vwmS&f9S~Hp;iWl5KdDuvId0wRg1G*iV^#ik*+vyGU)2^Ngjn zvuB=5T+8%~x$%gej6bsM-Qg`%Ol%QiArOcb|9!ajS|k%0Q#Y$z79zPa`+A^=6@hHp z^7tMzONJTWb-H57o2}eC(UA7zJsK2Z{_yC1W=95DAW1gmX;8i@-wA_UVz3*z7K}JS z(^aXu44#iiq4-DnpH12Jkgf+6j=t>8>fkJ?)5iGxsd^dM(pjS7Dvn!fc&N|fCJJcA zNayExcuU?CzXvvNm_Tm)qU{I5ZdsAGJuoHxS7I;6X2i-iZ{fiQ6lZf~&nNh1WA=iY z+q(hizINuHx6e}+JJd2LS6MrvJpQD0x@kteT}V5^97vUV4{SH&RkI$iJkMfUO%2yE z?^8$T^-G;m32nYrfC~(dP3A76!z%$-dI9sU*!xZ+!}@{)yEG)6EjVa<#7jhBT`ktY z4<4QZv(eTzys&%>Gw9*^s>H2d?FU{dekSC-h!>OW?8QDv{>keA_CniRd9REgg}8m( zUiY{-xg!<BhKav5;X^4IDAV)BFSRQ|L3^YtPBo`~qko@qLjWALf%Vic(+6mh8x~zC zsPbKy#Qh~WVS;v_0wT!=28WEMHpl2P^mY!CqaPmS`!Q&qRA*jR66=upR-NdRn0=ep z&dBw^R%dH%<ZFEYO{}QP;CKGTTK4ev%NFrPic@@As6wWRe~AH;@VrDrWny~SyP}{S z7;<U^%llEKa|V4Y$XL&?Z66TI(pnp-j|=n7p(O6(|8d$uG$C)xxA-n(HaF0?Dn8W_ zR(h0_OeY3Eg{D@zx|T$AhuT=tx{i@1Ud)Bj6)0J%!RhFD0)jEuODQm&PE&W5vDK8B z7wn%w@=X2d6S#7b?@{q7jmX6(!v=>14V^JfRkG_)eIX<JWd^yIroe5Zu>%aZ7Z=?> z&p$6mtG+O1VDQa^Yx#KYtu7z6h1_VxK$tEVFB!Jfk}OreXfqmiX;(b9Yz?n!qY&AD z92L(W)V3B*vf|%wui<ZRll6k?;B03yGznoS#%J7O!L~?T)KQ$-(&?<H%-27nOFJNa z*natZ-T<Y2m&7A;6a!$)4{ASDuQTD`(JvCnr+q!Q5-g4n6M8O|&NkaaJ{!-@8zxD) z;xDL?*Pk>O=6aORx(ZC=5(g#U%!mrimbr)q(4RpHVcb_@uv^?=KOK+ow4Ue8o<8*r zu`>c~|8!k{#o5)kW?HoNJb6~1MCB&NUknF-m3eYv_5@u4Ne4a_Q-exoSL$l3y4!QM z79i2B$0Am<xppNOf_DH$BSJXI*m%-nAM=_Zg@&|D^sG+#$Qi7iFn8zly>C!u{<*6Y z)DT{gOqoB&QMz^WI|H9O<WgmZq{+H2*u1;cNO1eP#M$}1I<;#Ba!Y5ltv+H#uDtgP zi(FGP2~!-^AyD<@yme8dVrfZ0|1?hKtsM`?sQSB7CN8o1N*NLFUEVC){quM;g`%gm z)8DegBy?3gCRf!iC1{+DeB@M1(wLJd@;%%)%s#D~?er)QZ*FS<fNh4-0LplXNxISN zy~f}hjNCBe@!)$c(~SqSx<o_eBN$EBu4h-o^mbGlAmG2&%+kyZv_v~QmV9u5G-WwS zHU%j%JEfoQQDt9WU#E9lG_z5zxbpJ{C=fmx)#d-}9HEE$EWN$CIg3zpJ@61hDN9EW zr358%L-+`G&#$GqnT@!O!vEw3Jq1xY5$#ugetti5Gqa)V>$}{YSu{C{EX+wMvQY0N zYEZezd#RuRFz6CZ89{K1gux541yzB1L0e#3@E{Q_Q3TNvF*9)*@e;`slExd1H?klQ zf|VqA@Ay0P96;aG@3QntO+#lBff0d*zn&Vj2}6F;lZQk&xpNK|=h&s><Rl9wGoIaP z45C?9J$dM-o(E&R&F!|AEaAz`)XdVDQRcUY8ZUC`vh9;V$xa*~E*qBboOT~@+`(e% zHLurdqV2h&Sy7!5eEHLK*<{jee7M5fof0U$57C~R&H&p)mvN_0jn+W)@g`>{hEiv< zCV=nH)I?=&ciiaH%K5lv8ebNK;&Us-BvsVc0%Y!yWf2AIdMzF%HM_5h>pdKFXzh~> zQ%~M}>?8$bb1cJ<nNiB0w^m_fMeIK&Hwq+cD3e*F!a28d$NR;1@xKlS-*!-{GFv+6 zl2gdV1J&(f{-#td?^#-VF?yjy_gqtG-S(b*PKbLQgYM1Gh)Rx)mr}UU_<BL{VDSp4 z+dIBBkCin@jny@A`SI;aZ=ACrU+{ZRwBMeIMau}0!4KolhACTmfp29*f-hebtJ}i7 zOSd9&cQg8q)347VqtAmCu{g)xnOlKSdL?r|Mc|%<P3e$4okKj`R47WrIbCQLZ(Q>3 zKKa2&*tG#Y&pqwT(7+CADm-c->~??6LIkV9PsOo#m0IsdIF*+i$`Q2-avCkZmiw;< z$4Mgkx!5ODcEk$1XX?4)44HG5MG6(X?)Utu?{hHKkWNfA74JLD=5!tIj9wjq!`qmw O#fW*Jpm<TnH~#_s{>Ul- diff --git a/public/fonts/fontello.woff2 b/public/fonts/fontello.woff2 index 551f49d02d766d86f92cf78bc80f94379da58c6e..b7541f0278434295a1178b8d5a6adff4deea570c 100644 GIT binary patch literal 4832 zcmV<65+Ch%Pew8T0RR91021H;4*&oF03+}K01}A+0RR9100000000000000000000 z0000SR0d!GgnS4N37iZO2nv{dl^F{v00A}vBm+<cAO(d@2Z22dfgBs!AhWV=kXAoJ z_Fo@xV+eLedBvzIT6C3Xd4>~VPdJ_!2WSTttvBuOt5t?NL)YmCJG~_qJ(8)J$XD0k zAj9=sLs%nncEeRTi$yi=m=Ti9^8CZSdT!<vjo^<bSn$ck(li*`toSs;|8LE{`-+;9 zbTtRLhoL?&8?kIN$M3RmgpQP0qgIcv?oFp-O-q(@BM+dC_4HBqO75=WErA83gw6@v zv;?RnH4&sD#vnk~I&}O!tIc{nCQ0kb9ILFe>Qw7*T1%LT*w=n#cfU6?OMFBsM2Y~^ z*2b|s{^x=!lE|2oF)EIH;t!rrTe=Pu6_5g7`Kj#D2FQRqO8D%M6qI}$hN9lCdRjZw zR~cI8L!l2uf;to*P-tuMu5E*V;+X#}5^!3g0hCoYK%W2KTwnIxN@{)$plUxz!k}d5 z(v?n5y-Geq`V*!Hc!5YLRR4@5$^xQV+ivmrwd>Zn6>_a#VdM^D7l4UtI6Z30w4#I< zC65(qHZt+QrGSwoIS|$iuivRkm-*}aswx6Npm}I6U%kkcUF{_Z1iMN_ecAT?)K<OF zy4x{My!3wi{0j?A&?4J{|GeGvK~w!zUORU2U-NM1L0?2*)uOsoTf70*m<KxFpF~t$ z#JPFA@NNZbCpoGXhw;f-`9DgXfZjEh@rE$mBK|)fo5STPC@S%32_%$Ake=XK6LZcO zTMJ5Ei7cfhOKHneI<l0mETtz)>B~|EvXr4LWh6@(%UWszrA%chGg-=9ma>qgEax1? z%4+t>(em9G|L4Afg6lAdBG>+6Y<~@P`5>^i%=kV%m-fyb%=156Y_BG|X{-hJfZFd6 zoJ!t7pq-yP7}TnDa7eSoXZBSmdqk9!d|@*^Uk2T$V7&8FC>V^r+#t?=cC4$5$<ui| zVqxrcrP-;IM+8RoT@<2T@(Rwvv)HA9EsR8pClUtYy{9w?{Gdh};jb|wKIq1w$iZZQ zs)B`k+;|usrcHZaxqh}~qwCljjBVXVDJaPeV;oF)cVfvdMWVl^$a86Dc_Nq7&bG<c z9f%FuXh|_Xdl8sZ-~?O*a9Cx#HU^7kY4b4u)$)pZ-oH{_#@OTtr5=E%_!W{;eRN33 zKoFiA!Qwbzna4J7tc_51C{=re(nzsdUIMhJvB|oAY5`d$$fmvc;FPxg`NLEyOT7a` zTn}k%@<t3AZ4}I9AlB$4$j0Ry7H~Mzq07W%e<;Vi3b2C=YdweBP{wLH90<^3N}T)^ z@=_8PF*yd;r+(pDE;?sSP7VCDwHoqnY(;T_!7EczK!hh5w7nvYqU*41j7i$d7-hA< z{Wk8We@}{WYw7<0Zni`vEzIlRDvk)>j)OI7<Fs}7D6FgE^_#?@AxlK?c{O%^7%?Bw z6S#m*909bikWBe2<W<`%1WeLC*X~`CW`?+SClt}?$CDJ_^yiMEzV&eJcD>eE=|n5W zD46116V?}piYG`B5az!4CFqMmb1HBjhoZVtg!^_Ti%fLW>X&d78yc~E_FGdUK1XPn zKe-7*-4gS?WPz-$4-xGAQV7j2EkX68uI-&bXkG@C%u_Uwy`(?og((CQ$w}{}r}*_j zrNjd{M>ESOq9aAYZ0eK=qOxFA5uB<*P)$gx3q=iKQB&B|5>8hR)%`_L`P!YJQbW&c z$`c)P0TrqHzU6$y(ET`NZB#$ZlZ<^(`Bm5GMzWy;MHh-56n!WLPz<3MK{1A60>u>C zy8v0VR$5Fa%2|54jC$rQQf9$jYnE_hk~}?4$1+4lvLuU|XLX}~%c<5#PAi34v6T8M zXS)>3%_76>Ae@d8#;dBi<@DY)$OX`y>!{G(RQ#SAN-#oF5p(~*$27}2Th>A%=PB8M zEXoLa3n2dkIW|!rSafWKOCV03zQ{HTlhmb_5wJC7=t?^qSscj{Hq93I4Vdq?0?qE7 zcI*JmeP^Opc=E$!mx7$c<IJ;m!9*6@EwGK<2B}#Mpv*^X6P|BRjx^_COX%*lsUvZB zHTtdWs7q;kk*BG{gyO1d)0VEVN4kJuY81)bsAB}PK{C~DGJ0!PdHDiShk4d0E{8C0 zK8J@*QnHFzPdW8zBRwM-wjp8$gfp+fR`dpZ2BKq64{M1CGPdaA>~Y0@Jub}*p<W<o zJ>$R^q@ZPQx6xIoWl^mO>sBCDiojast2<peZYdb>W*t3PQr7If>aeRdAGV$~(az5s zp?OCPRB8=+IXhz*n~fM`G1g@%#4|TrpV)W9jPL$rvRfSad7lD1X<<`OgWc4&wRW#Z z*lvYOp;)u@n$FZ?&Nbtx^|4IByx7`0jXv*}T4!0O&!V_J?%{22&;N>%18aHoHtgw( zI%}+}G};J}&UIlUX@t+OJl$xutR=aT__z?yuJUyq)^4-sOl>rG<l@jJ6of@Bn<Tjm zwi{i!8s;jMT83?}XsYfithtseQ|cPfJK-!tNRYy_5F!E_rQ@84X+-f^2nj(t+1(Ow ziX!EZ(z6jV4wPLa5ONMFKO3RoV2k{4MwD<yl#wPXIB2UyoEJ4+r1~s`h9GJ}!fDa+ zo7A3-&~b>mNF($dQhzqWz+rOOE!ttcXf#oaU$|og>_p*y0YNi8jG-%L5@#-<MYDD? z#M0e~mAf-u4-updfFIi+kaiN<H)|Iw9Nb+vy1Q_4ci|i$x=83M;Tx>jjk~)W4|g}7 z?ryvSL~jXwB)#9ccjr;<y9dhkZZg=xSH&6|-@6XBI^gbIvZKC8uR;MHuP`P)fM;1! zzAobfpzQzv`Z@q};EbEYbbx?7xJZB(W`jFx6vGHdYZxBs88%#P=MU~Xj-^cvOx(oX z-Q2*i=WhTPwP|2LIRla}Xb^wbGu|UnzGNAN2^R=1mnVK!xe@K-MFB`?1<=Z_k+b)? z2;uBS{vcDSwg%N2wT+F&CO~tb&%uSF1ei9I4@{6GU5H4LRRVdvav0S6WW=gLg^D$V z)uF)HS)9UnLammoLM_im|HtQ#qH{!mYz{5tFGBbdHFkio`d27CfcfwL&9_jATTdqN z7DzcU2RJ$l8PDcHolh2E7)7usH-17lX72$I=%42qL82G<eDx#+%L~M`MT8ck@I{oA zp|gt=rX}YZ0MiH&sn`@(S~5_TjTxb_*1^;W1D$1>;`1&#*aF9ln4@F&Z{EFd=f*w6 zi0;xS&W9;#F>nV0!Sv;n4?Hws2goH#+<KWBvk__tt7@r!Lx>8kLzULXxZ2uUt%m9D zY=7^KKX+u*5(3JDM2uSUKapVc01Q(BeH|Jb6GCNWrBVSj_-LQ}kZ<f+f59SzQ!Uy} zBrwabLP;5ZbSa3baU}qtDne6<j#@(^rP30@t(QXherrq%(`S==r+kcv33S|>Mv!uT zw<W?CC_VX*K<&*J2s1TgEdYfsQw=g=4UCM}Fv3^aAr-8%sYQ|$qO<rswg4074pl<i zsR9ZDj}S8E7al3pEm>|811Q%iY7qQHWA*Yfs$z8a!Ly^Dg9l0IJwh9E<^c8|8y9Vf zN=U|fwT(@TlJe<Xz<J)VKm`-R->7gE;X_d%_$P}3u@DmJ{r7ng+~0E+bALZUw?Aor zsB7!Vt%rmUPCj6TRkfxioJ~uTW=XTEC;v(b7mt5jsknI5-0WaM{>Gnw#(}z$77Qr8 z9+Wrv`Nv~rm2Cre%R7A(H5J>vMk2Oe_5;m07VeYGe_7I)&s7hFVd8B7FRP%XrF@Jd z>3rrr{UDBvJ$r@*K7Sn;*tKiVfNp3~-krYR>`d6k4!`5Fu+;ThdDZvp%h#I5-nzQp zMuj3RMPbJKK0Z!pxp29PZ9TbR**%Y|8+#l3`?E4~<IS%6PWWGS$;jyM8_Pb^^zoe8 zSKq5<7G}sco!D^NxPOno$hXgb+;_}Z<bTdLSh8E<o-Y_Mwsm|fL~rj;->(neV8zF$ z%C>Fua<c>^j@Rdev?Og{4JB_cAZ>{(D5x*UtIt#CD+raC23;)z<B|m6br;bu#^`(s zWyg*ei5wlBcCXXx(HqtqE9`XclKMS`X2V_)iR(NOdhdJmVj*S!dwKPNK5q0qE^Y&$ zlh^x%7tu>%+-3ujsA`ZKlluxR$~_w6Y;l}1(+J0~qmOOr-_h&bD~9(w?<*-D*(VI` zcRXKQjDFH~e?od+*4yZC%{>5z6v#|iP}6bpfa};XvdfV-E2EMO(nK<*<fc*6F>>7X z;Bm6a0Wp?Q$p-17ADKoy^|lPIxhH5t&zBh^$)Sn)bMmVR-zO%r{Fb2fjR7Z40aFvG zC@AaxK2b-<(RJ&NceH`c1L3+)`aPyS`k&L9abslmxucQpr-h(N)8F`}NrZ8lu!iI8 z)MIyXcePVrWZ$$iXY)D6M!xiE<*bfmx8~=!Gh2l0wt7zMfT)F?m(OgYc@=vxn%VWu zoLN>2n9|A;4X|78%A;jbua#Yp&uTB`wsIz~rB#-1tgP$;#noU(xj6MqiMJ#-HJ9&Y z*WJTdzD~ebru%0G3f8O^Gpm_V74cG9TW4*MG(g<nFAk6f)?xS;aYeDXgta!ZnbWCk zH@IBiZg5pU6Phu0G}ZZaL;1F><=2g%|4=#VpE9ry&f;!2nF<dx_1qBG@B^gxYH8YP z7mi4=Kd8tpBPs}yMK!Nw_1QKzKL0#4xEtfmxwcjp%#~-@DZ*DL8S$j4w20TdC`o94 z&)8!<u5Erz&EBH1DT^esG0jx}h6n)wd*1pMR_@#B)})(VO0^p+0a|9~Kd+D4X8)S8 zO@Sfn5sg`&43>46fG4)?4&;h4SIB$H#Q^(|bw~B(#xJ;Vln8hdSD5P0%~TK!u}SG3 ziPY7Wh<D|b;@L2Wg8VsYTaD(XQX`NjiiD#em;s*jyFI4*@}C3eW<E2qk7l+JL(E7I z8JG;3C17ytfw@J=qIB`S-Hr9hxZkZ+lfcSM?ulTfFCd@GBR4zuDwZ!WXZc)Lk|8FG z-6AD2U2aL@Z7i}+x7+o4xl9<JYWKRGdMoCdjdJaKs#ek@j>15foQOVVTBW&}SHz=& zC@!@L60ULrbQgloYM=1>SU=MlDy)RcKh4cX%*d()E9Fw&h2b_6@)Hmo@>wd;7_AEe zB6{!;{4(6bZ3GXe1HF(BdzDPLf>soTwi?LIDgcl09%3#9Gezq{q%Ld4l!NKep+TGk z;g1E58mUi<s&8UcE5=#K(`q^F67dd+f0Psx<Lr>4r|8o*35iNwH~{C%!(B;moIPpM z&Y;{$lX2v#vD{1oVipPM1PBwC(rkcmJ_u){EE)+l_)cwGABNxwm~4raivkmRjua(c z?G0161uZXkdVVfi4dU)<lJNNOCkFjqy;fD6tA>><)f_olwNj43BV;Gw$gQ{tQ6vd3 znok!OrYreMyH$}ET)!rHmk6^g2x)tkC77eYIx@S&V}djZ;;0u|ZvC$)WnKEgzkc5g z;U@D3KxC!U2rv&x3F5N2-FLcXMxh>!X3HP`fpJE0-W>Lo8qUqh$!O4E(!)R+X~8{D z%H^*M`DH=4R*dAq1h6G>Jqo(R6f{vkysPhLH+|;s{crpB7l2>J`KD)%!+||t;<tdC zWrfK8jks3$h8y~KQ|&gO#iCYUJm1K8Bmbg~aawHP|HL7Arf;BjgXW_vEzi_;dhIh` z+`<9)%LwB*f~SD{r2=qw13w|qwqUfB$YI-3Az>%mnLNM_<%y*MMO@$r<?W>ftU>pc zi)i8%4tgHIJ$yt<i5mT-LWN^&P|mYMxxX}^!z&!2EG=FNK@k6GFue2Ue=^Y4Ysq&Q z4{uNh{`GeC4Q)D)g?{NdM8Q|ig!Ql&_?vZ|sZUSe=<NfgUm|(@HITf<Du>kYQw0NG zog4A`z{sA>k@Bcyia=4*UQ5x~F!O9=4tEyW5nftlVHt{8p@2eUAMpmU#MwGw5wkH* zzmI1#Nqj-AvJgEb%cI=%BtJ&YK2k~3FB<6^M!-3;B1J5rT7cc^ix`iQ$0G?~<N<oB zqEofK$RV?~WBx4aYlH*SvNnjfOtuCLcqd$Zm2qe^Is+pUbIh~AB1<f@!YXSqi+L<! z87rj6Kt%d1L}WgPN;+>s5wmsT_P-@^B~&rK09A}9K_#94z!IJjmdFuc=tWBqi$11u z7#RHWA{NfuGVb|4k#E5gz9TP-Ah`64AC~}$XA#hK!V_;H(n$zRds`Qt{sZ7uU-VA~ z;PhKRo$D`PKKCa{BF1fBTV)+syvP(^b;{p9RTOWUUft$ue13?^^p}5z_!YJ<{g5>Z GNo@dwG#q~b literal 4772 zcmV;V5?k$ePew8T0RR9101~7C4*&oF03(P101`?70RR9100000000000000000000 z0000SR0dW6g%$`737iZO2nv`Cls^k700A}vBm+zYAO(d@2Z1^afgBr&AXNuyj{~vB z)nxyFNYKUzMcaX2n?ji|pw{Y7@lv-)^UAq(HT$QmmPZpoQIJ4cyvmT)ilW<P;x4D} zvu{J|;zQwwC}yU697^QNfH8H`SW((go5qzhBP5xH=l}cj`=4uVoO1%-q{`<)PJu`% z1>nga?vbM}H`;Ed`_!&P*R`dX+&yp$DD^INt5~3xd_{dmhH>QBxt;p>w@9y@wm%nL zhzFP_CUDadq!MakKt+r}fQmxL-!q}JUXMxAdNK#^#Jv8du#zwnf#=g^hM)ve;443s zJ=y>rpiBw-9Fl_C^PcOJE^2kv(>mabS<6gvNKsX?7VI=Zs~Lw7XD<}D0Wx$(I6dd@ z|2J2keYcW2Qw^U$Vm_fTOOl;SS2{VVN~Zq+rDth4|8ug04a+DLsySaWbuR#^we1#- zS*2FUwQ_~=JB(e_L_Wr5yG1pi+fd2DDt!6qPl5mmhAhza8@KN#bG3H&I?Gs!l2kdl zwOqYauMrkTuvkD?@`$sVv+_k8=wtm2@WJ4Zm;W=Ig5nI#ceVS%hI9O(I{zs$dtdfB zYWDFBdJYJOFob>V^vW-h>c^2l3bPD}8J4G?q#(zj9p^hwHUddm#gCg$(aTU>Q$^AW zRhxrCA-DVru{IsvVbVKZ{vRG85ebr{NRuH;PF?}xfozI=gVF_M7aIb}cfLS*LtuDA zV0lB}cthZMLlE$WAj~%q$Tw~v-?)Q(LlLC%4OO!kYM_CTfCfSUK`#GsBgp4JMS?;_ zzGdAs^a&gYgDCQ9N#^Jk{^UzwY>}?3G?RSb61Dm;q&{j+b>CPEM_`@yTZBA$hk!WG z9168+9R|`Y%+#eLzDLm|C0}%^=SzF1qyAwoq;!8fy}H33&Rgy1@Z@Q3->`^eo0&dZ z-H<b(zAf@({74GB!$EAz+z}Yrgb6fxAa*^KUF=6S(g=STV~a0s7Z3YjI>4u3F%~CZ zLt)b7!@};vx2ozcrbgSRuA&qesvEknJ7eeAkRI|yf7nfClHaRON~rYFF<K`^Y=R~R zQjBG%L5-A8VQ+wK1!bSU!E9A})lR+4(Uf@5rK3iSO&yW2xy)1i1}VGx=x~sMV6AS1 zz{i0=J)wGIZA7xe%i1Osww9pb1tWP4P1I#?08YyxTxk;@-6h9wzvfVex(!?Gg)}zx zBnFK(3e}T9v{8v0jiWgN5P7#nbxh&Miqxw>YgMC-8PY^DLFtGfj2<H6)1{?`054Z_ z#CNBD`a}t|(Fpm#cdXG+veJmc6oqq3P*{hxB!k>qOC#wnmTO}d@gh}e4PY!A_Ba1{ z(yMmT)&Xi}DI>XQe32fa&NL3zsEr-#$f9UR;#|#;BL|rhA?q(!F6>*?M{+aRi`<YS z5A8^#0a;pV=GL0LC&{OUe3G=00$zOx#kTtKF6p+;9mUV+UU>Ds)M>0Vip!T!DELRe zu-pl$J^2v%*{bGCwv62EkQo(>8C_(T#+uLXNsvRi(rSx02@Q#~FgevYlJ$|WQ6W-A zQ}yWYj0G-eA8}at73Rv<)`TMkO&<aU^)eu%p0vVgFX`WsxuY~r7sz`rJe3+o>Qd?p zydM_tL`NBgrr@Y-OlS#HTFMM9W0saPM=O}8l`NoDETq*ey1o~x`orS$s}Dh?LhkmI zmpl3}ZDsTEqWx_h=F=XFafECTaTKZllq6J$hGxWQfuI$FHVE1w=zyRTf-VTUA?SgO z3_v!Fl?IcEC0TlU4|@6>mdt>?#zSB*jC-ea3`1ljTXI<I6+NiePt-<m8YvcsOQt7T z+mRS9i{t4*xE}=!M`cq#d1MUogz4+XaReSCh4<=6V8cTVQ~pQuPW>%iDPue><{p`V zY{-c90a(ZXLXJsPS2i3|-i8pLmY!xBMQO&Qk`c7-De;T^-i8iaYYF@2h}(qvc8YoW zV#hH9D1TQ(r?t`Xa`KX*illcB@_>qL<SB3ovprJL8po^m`H6+ohe4<wg@u)qVivYI zxe&cqA>oo+eNR${3GchAPDi@p8R-z#Q=>Sq8|B+|tHF7ySM?v&DwU@wbXcz#CE-KV zuoJ>_Cdq0ArBR(U5jm@;svgSNA_B~mS=^)O{lXcKjyW}~AtJb9KsQ$Y+vdATko+N( z0m>~{T0lFT6qRg$HrO93*-&e|(hY*sLxi`-BVJs$6g61W(%mhk)!a*q4M*{A)i$ph z=UL9v@75|*Dg`z&q~c+0CNaoHtjZ8ibu$-VbIpkvKla-7>h}GX*MXT3*zW7WY{;>e z_VqmTl(z)lD~76RLpNpH3=5@iuSu!rTN<aY<zrIm3hneplth<#c(Bd+Uoo;^DULjb zy>>%ajJ1_s8(E}H*E16oqBcKl-D@=`1*za4Tf(MYzN=;L_AkW5M0r`Z#@kSkTPKrb zXgio*X~*#>CzVQuxwTeQ*G?F7Cp*r>F0?RY*v&YCtSrE0X@`sr=1L$h3-DRV01F~= ziqHT>IY4Zr6F-bu%9v%0S<YA&;UTdSiPex;9Z5IH*knyPq*fNtW(6IV5RERP(L*%) zh{gcX7$O=YL}QG&Yl1>dd%<+36D7<Uw4O`cNyGSyUjRxkGY&b&ii6hu#3^E9a%@c= z?$R){2cS^7SUPafv7ZDjoJ@hUDR40bt}f7xgYF#a0ox>=ro_vXc$*R*7wF4DKaP=q zAbyiqweKIl*xHWH6x13Uqwf;7F>B-biTW^ZOaytEEX{0@=x~%a=tp3#kWzgYejW+x zA+r=47=S{597S@;WM`0C89SV{*(eHfIh0K_wPXaPT6<8G&gF*w8<7(TPduAM1v5B@ z!_~j4#g>AY_#kfPLkr8koP9_Wgt1J#9Vtq+8>rT(-Q6|r(VG1u-u@)XN6n#i7&t*t z1+WBO#FxvIyFg2Hw`->g5ovJOgE5%~j6}HtvzenpT+XEb$K&lmjCi!vh}7~-5H?GV z?#EsK6$tjDQTu+2s8wQa6FJ-&LXH~&`-%aw!_I?rbcVqI?qVx9Y@*sx<Nzf0t=obV z$ORrxJwcKrh??tgGfHA6gpeWz6N#D$V<+GM0W1+YWTguYQMo(f8aFzshG~c)#Q{6- zPe*6ij<}JE-oJVG!kruUU|T8#qJD=+(iF1+h8W<;WH@8zxF=9GO3ZDx8g&<FaMunA zZ3<Q)^AV++9j113Q>z(N2-~O6I7_$gae)A3G$cY?cumCLwx7WOZQ$F`xZC3@S67t^ z&?LTBelQ~QOcdV)VPvm)6#=}$YextXes&3nsxc)1qQW(8$R0OHs8qT@m|H!B?Q=uT z3@SFUZ8F>zA4kT*C^#YKX<c9*Um3!K`06koUl6I-jSmX^&lukAE?_`E)NJLcJmFT_ z4U;B<5FiHZJUW9L+aQ$WlMsl*#89}|q*&5Kt}FtOFyDn51W}DE16MU5TMnGr6SCs~ z3i{ibGfwYE|6^gIoU4Qc>{h!Q#5O`cRSDQm(|i>J7yL#hsqk=;1i`aR5=7$(Sl@SY z+%h=7SC}e)Pi*fi$~jY%Cn+23#g>1koQuakE>v7R;^cUsrh4ViKWAWmV=oFcU5~Ar z{qnQX=C=OrcUy+S6?0qHhwe^ZdpQzxWIG2<r2Na2WPEOaC`c5o1Gr^1y}d1?Z1GT; zUHVacXj`}L-2VCN_U)TDZ{2Q@kXLnQ<Tp=ymYw(SxFuXHLKj|*`1<m-rb|D6zP?ML zC@N4ma=$lo_;Walb-K#La^2&OE`8U=jb$a3*^XBu#-pzKmy~QA87)8E{qd~h*NCf* z&W`XpdS()3EJ_z8j2MX;ix`a%MxBj_6K@d*O^1vgT|2fGq6X|M-e(1xZ2kDOyKdci zxnmBT#U1g5d-GN>cjmtjA?Z!6sgc!G$*R=BY7)c_u~+NBtUNwwxQ==;gZ_y~j~=TN z`uO;6S!SuT++{ghyV2PrTIp&XcZDYBE(?LFsUI<v2{->A8ae`o2U55C2W|&_L*M7T zNS~DvXo!K+IzUcF<twnR@<@iC^D)|V7bAm}ess;o4SGMl*s{-WZ)3xu!b7~z=UhVr z`bB&G30aQF-02*rV8Eb8YR`naj}iL=MvoH1K3s#8%(E^MN>ju)ZM%;WV*v+_5#8Rf zos`VCE*AcnVylyR&^S&ZpgXl%Y6s^h<W?KyS989PkC#QxLTGcq@sq&hI3kQKyT4aB zICx~)vSWk&VAB}J{7+Uo2c6Y_`I0d^*zo*@t%ar*wD*g$yJ??nM^qHdW&8Q+JTGo( z`HrNHOhs(L+|1OM^@F`QmDN{WJ&@8XVD-z`ecMAXtExJspJGWUt>=>Uuno-Ks_H(b za67A4#_lzVkE^DdIncoAW0yE_t*EVKWn0@YDCz(QTSSGY8^gqvg_XRD4qEpzK2KR3 zRT{%zvRIVTk&@P$Eur)e&5M;pi#Bc)MN4Alqu3WwYlEnfxiqzhJ)|74zAR&zt5$@Z zWb{a(-|Nnnb!Bs|+d|i0t!Q=9+AFS%v)+C(DbXQhMONnzkfo@;Xt6(AsMr@<7g&-O z3ro{_mNG{?db*x}-nnB7$~6Y`b<`MJN<0-wi}P%`l0r)IYi^o2VWV#Ju_ec&dhXn9 zb)%Ecc|=#y-Zakt0GKc6FW}{UlXh?ZW+lwZ6+r)>f2ALFTK`<tQdZd_0{2Tx8Na0W z(PIU}n}HZLZ+cM?J)#z<)SM(_PazLjd`Y_GSM@%YE~gRMo))$s$eY0Zww9dnmEMmt zGQu-~ezM0j-uX9i*1ZWv4f+ZegEg0+uE}b$ho76h&bBEs-!rionfWKIroMtGj0j)J zCNm~VNNA$4?!@2&<LhY&M1n(W9LOnzoqSgu2Z7_z#Fyk*8pL9L6gvLT@41#~0KvK% zc`8+MLb><R??tv)5tX-$pvn;=pfwcTlgkRF(JDEc%1Flf{ioTxvc!nV3X(~uBCD)e z#;V<pVp01FJ`f|`)DckO64p6;2y5LboOQeMxv8&m*Vbbk0}#nC?R|^_)?+W>!@wD% z-qcYMJ){(WN$^t~mxhT+$8_XZc9+w#?^7ubmnwwa)WBj3P3&JXpCs^s_*WwFHt_a& zC`uq2)Um)$gVda0*|WA<ZZ4hFG8?%0)#CKsd0LGZRJS7-m93Nm;$SFyiWR6izpPG7 z2Omd}?HDz05rkfmfJ>26QI#WucO!brii+;)?}Hs1*>0L)d`)ZG+w#Eo1S8!Yu5IZo zp_*(a?Va_g*^U@_6FVf^jSBejdVA)n+xjhuUEWuEA7f5b5aksDO8SyvF_|xlbcsZl z<WBjri46k8aJ#=`PqyvX+U~>E4y;jQZ-dCBpW`srtxK>&?9}M4<<(A4FGOF-ef%TU zsI$~ZRU!Bly^poson^>4>z0;5=ZuX+#B%n9!oA2{gbI14z-+3t0&rGsz>Q;jf2{BS z*|!R9?sF+?8UX+LzkfCGhvhEWA+wL|Aqu{o__GK*n4wQ!1tFu0G&;?3k8kq^@`&k; z9{Frd51p}t)YPM|`4{?!jLy<G9m<P~)?-hs#5VIGa4VCF|JnTC@M+wgSVG9Sr3B1d zhLH8Je7ogHvfuKAoU#)1s-HVa(U&oZjeyJiAY}=;vX+vO)yl+pzp{|eTaI!&l!yGR zmEfS<c}<dz{`oHol;7R)&kgtDl-&gJy80G0=@cPfEcU?tk{GY#4cr7`*AGw2e1*e* z`p*6O$lbdal6kdnBzm6%=q9eI2<sEGjvL(M5x2QSor~Nh)@2>tVk3q+QS1!5E_TDm znJ;mj8dr(cxW`>CBGnzFsz_bn>Uu<-$Li`e`2lCS!8MfHi=sOB(5u$CO{hLQc#!k- zgG;E%$A%3U@_|jkbzeaZvH2X^(H-tHHG*s&TwpM`&CsjGHWmhBH___yN1crY0}4@h zO*CV0s7tgSrFl6Ld~?NAj978vC6G`eNk~#sl9r5QB`0|)NKs0-#07zKNN5k72!{2> z6&q8pt@}VJjI}R?asKUrqky(a7_@!q-VbgAcORVhp?3xFP4Y0;&Dk6E@b;ZM{Z0ki zCNt31(c`SZHi{+xutO34ir?}W@9w_}K~<nLxOn2Vhj8S4;k1VsqJC-Pm%U4>FNbHT y#k#IthLlTDKbq26*zJG#d_=lx`p%v%mCb_}GUZbKKk51`9pCiAwN8nB%=XPqG8yUs diff --git a/src/parser.nim b/src/parser.nim index aa0f8b2..f132dea 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -231,7 +231,8 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = replies: js{"reply_count"}.getInt, retweets: js{"retweet_count"}.getInt, likes: js{"favorite_count"}.getInt, - quotes: js{"quote_count"}.getInt + quotes: js{"quote_count"}.getInt, + views: js{"views_count"}.getInt ) ) @@ -339,6 +340,9 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet = result.id = js{"rest_id"}.getId result.user = parseGraphUser(js{"core"}) + with count, js{"views", "count"}: + result.stats.views = count.getStr("0").parseInt + with noteTweet, js{"note_tweet", "note_tweet_results", "result"}: result.expandNoteTweetEntities(noteTweet) diff --git a/src/types.nim b/src/types.nim index 55d990d..5a08bb7 100644 --- a/src/types.nim +++ b/src/types.nim @@ -203,6 +203,7 @@ type retweets*: int likes*: int quotes*: int + views*: int Tweet* = ref object id*: int64 diff --git a/src/views/general.nim b/src/views/general.nim index 5ba40a3..0571aa7 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -53,7 +53,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; buildHtml(head): link(rel="stylesheet", type="text/css", href="/css/style.css?v=19") - link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=2") + link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=3") if theme.len > 0: link(rel="stylesheet", type="text/css", href=(&"/css/themes/{theme}.css")) @@ -119,7 +119,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; # this is last so images are also preloaded # if this is done earlier, Chrome only preloads one image for some reason link(rel="preload", type="font/woff2", `as`="font", - href="/fonts/fontello.woff2?21002321", crossorigin="anonymous") + href="/fonts/fontello.woff2?61663884", crossorigin="anonymous") proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs; titleText=""; desc=""; ogTitle=""; rss=""; video=""; diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 34dcd4c..6d76755 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -184,6 +184,8 @@ proc renderStats(stats: TweetStats; views: string): VNode = span(class="tweet-stat"): icon "retweet", formatStat(stats.retweets) span(class="tweet-stat"): icon "quote", formatStat(stats.quotes) span(class="tweet-stat"): icon "heart", formatStat(stats.likes) + if stats.views > 0: + span(class="tweet-stat"): icon "views", formatStat(stats.views) if views.len > 0: span(class="tweet-stat"): icon "play", insertSep(views, ',') From 886f2d2a4540e238c3ff71b7c2341aba159c3f8c Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 11:00:38 +0100 Subject: [PATCH 135/302] Bump API versions, use more SessionAwareUrls --- src/api.nim | 41 ++++++++++++++---- src/consts.nim | 64 ++++++++++++++++++---------- src/experimental/parser/graphql.nim | 32 +++++++++++--- src/experimental/types/graphuser.nim | 30 +++++++++++-- src/parser.nim | 36 +++++++++------- 5 files changed, 145 insertions(+), 58 deletions(-) diff --git a/src/api.nim b/src/api.nim index c0efa58..aeb0f17 100644 --- a/src/api.nim +++ b/src/api.nim @@ -7,12 +7,39 @@ import experimental/parser as newParser proc mediaUrl(id: string; cursor: string): SessionAwareUrl = let cookieVariables = userMediaVariables % [id, cursor] - oauthVariables = userTweetsVariables % [id, cursor] + oauthVariables = restIdVariables % [id, cursor] result = SessionAwareUrl( cookieUrl: graphUserMedia ? {"variables": cookieVariables, "features": gqlFeatures}, oauthUrl: graphUserMediaV2 ? {"variables": oauthVariables, "features": gqlFeatures} ) +proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = + let + cookieVariables = userTweetsVariables % [id, cursor] + oauthVariables = restIdVariables % [id, cursor] + result = SessionAwareUrl( + cookieUrl: graphUserTweets ? {"variables": cookieVariables, "features": gqlFeatures, "fieldToggles": fieldToggles}, + oauthUrl: graphUserTweetsV2 ? {"variables": oauthVariables, "features": gqlFeatures} + ) + +proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl = + let + cookieVariables = userTweetsAndRepliesVariables % [id, cursor] + oauthVariables = restIdVariables % [id, cursor] + result = SessionAwareUrl( + cookieUrl: graphUserTweetsAndReplies ? {"variables": cookieVariables, "features": gqlFeatures, "fieldToggles": fieldToggles}, + oauthUrl: graphUserTweetsAndRepliesV2 ? {"variables": oauthVariables, "features": gqlFeatures} + ) + +proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = + let + cookieVariables = tweetDetailVariables % [id, cursor] + oauthVariables = tweetVariables % [id, cursor] + result = SessionAwareUrl( + cookieUrl: graphTweetDetail ? {"variables": cookieVariables, "features": gqlFeatures, "fieldToggles": tweetDetailFieldToggles}, + oauthUrl: graphTweet ? {"variables": oauthVariables, "features": gqlFeatures} + ) + proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return let @@ -33,13 +60,11 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - variables = userTweetsVariables % [id, cursor] - params = {"variables": variables, "features": gqlFeatures} js = case kind of TimelineKind.tweets: - await fetch(graphUserTweets ? params, Api.userTweets) + await fetch(userTweetsUrl(id, cursor), Api.userTweets) of TimelineKind.replies: - await fetch(graphUserTweetsAndReplies ? params, Api.userTweetsAndReplies) + await fetch(userTweetsAndRepliesUrl(id, cursor), Api.userTweetsAndReplies) of TimelineKind.media: await fetch(mediaUrl(id, cursor), Api.userMedia) result = parseGraphTimeline(js, after) @@ -48,7 +73,7 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - variables = listTweetsVariables % [id, cursor] + variables = restIdVariables % [id, cursor] params = {"variables": variables, "features": gqlFeatures} js = await fetch(graphListTweets ? params, Api.listTweets) result = parseGraphTimeline(js, after).tweets @@ -94,9 +119,7 @@ proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - variables = tweetVariables % [id, cursor] - params = {"variables": variables, "features": gqlFeatures} - js = await fetch(graphTweet ? params, Api.tweetDetail) + js = await fetch(tweetDetailUrl(id, cursor), Api.tweetDetail) result = parseGraphConversation(js, id) proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} = diff --git a/src/consts.nim b/src/consts.nim index c8ae8d2..2623484 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -9,16 +9,19 @@ const graphUser* = gql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" graphUserById* = gql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" - graphUserTweets* = gql / "JLApJKFY0MxGTzCoK6ps8Q/UserWithProfileTweetsQueryV2" - graphUserTweetsAndReplies* = gql / "Y86LQY7KMvxn5tu3hFTyPg/UserWithProfileTweetsAndRepliesQueryV2" + graphUserTweetsV2* = gql / "JLApJKFY0MxGTzCoK6ps8Q/UserWithProfileTweetsQueryV2" + graphUserTweetsAndRepliesV2* = gql / "Y86LQY7KMvxn5tu3hFTyPg/UserWithProfileTweetsAndRepliesQueryV2" + graphUserTweets* = gql / "oRJs8SLCRNRbQzuZG93_oA/UserTweets" + graphUserTweetsAndReplies* = gql / "kkaJ0Mf34PZVarrxzLihjg/UserTweetsAndReplies" graphUserMedia* = gql / "36oKqyQ7E_9CmtONGjJRsA/UserMedia" graphUserMediaV2* = gql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" graphTweet* = gql / "Vorskcd2tZ-tc4Gx3zbk4Q/ConversationTimelineV2" + graphTweetDetail* = gql / "YVyS4SfwYW7Uw5qwy0mQCA/TweetDetail" graphTweetResult* = gql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" - graphSearchTimeline* = gql / "KI9jCXUx3Ymt-hDKLOZb9Q/SearchTimeline" - graphListById* = gql / "oygmAig8kjn0pKsx_bUadQ/ListByRestId" - graphListBySlug* = gql / "88GTz-IPPWLn1EiU8XoNVg/ListBySlug" - graphListMembers* = gql / "kSmxeqEeelqdHSR7jMnb_w/ListMembers" + graphSearchTimeline* = gql / "7r8ibjHuK3MWUyzkzHNMYQ/SearchTimeline" + graphListById* = gql / "cIUpT1UjuGgl_oWiY7Snhg/ListByRestId" + graphListBySlug* = gql / "K6wihoTiTrzNzSF8y1aeKQ/ListBySlug" + graphListMembers* = gql / "fuVHh5-gFn8zDBBxb8wOMA/ListMembers" graphListTweets* = gql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" gqlFeatures* = """{ @@ -96,24 +99,20 @@ const "withV2Timeline": true }""".replace(" ", "").replace("\n", "") -# oldUserTweetsVariables* = """{ -# "userId": "$1", $2 -# "count": 20, -# "includePromotedContent": false, -# "withDownvotePerspective": false, -# "withReactionsMetadata": false, -# "withReactionsPerspective": false, -# "withVoice": false, -# "withV2Timeline": true -# } -# """ + tweetDetailVariables* = """{ + "focalTweetId": "$1", + $2 + "referrer": "profile", + "with_rux_injections": false, + "rankingMode": "Relevance", + "includePromotedContent": true, + "withCommunity": true, + "withQuickPromoteEligibilityTweetFields": true, + "withBirdwatchNotes": true, + "withVoice": true +}""".replace(" ", "").replace("\n", "") - userTweetsVariables* = """{ - "rest_id": "$1", $2 - "count": 20 -}""" - - listTweetsVariables* = """{ + restIdVariables* = """{ "rest_id": "$1", $2 "count": 20 }""" @@ -126,3 +125,22 @@ const "withBirdwatchNotes": false, "withVoice": true }""".replace(" ", "").replace("\n", "") + + userTweetsVariables* = """{ + "userId": "$1", $2 + "count": 20, + "includePromotedContent": false, + "withQuickPromoteEligibilityTweetFields": true, + "withVoice": true +}""".replace(" ", "").replace("\n", "") + + userTweetsAndRepliesVariables* = """{ + "userId": "$1", $2 + "count": 20, + "includePromotedContent": false, + "withCommunity": true, + "withVoice": true +}""".replace(" ", "").replace("\n", "") + + fieldToggles* = """{"withArticlePlainText":false}""" + tweetDetailFieldToggles* = """{"withArticleRichContentState":true,"withArticlePlainText":false,"withGrokAnalyze":false,"withDisallowedReplyControls":false}""" diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 69837ab..045a5d6 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -1,21 +1,39 @@ -import options +import options, strutils import jsony import user, ../types/[graphuser, graphlistmembers] from ../../types import User, VerifiedType, Result, Query, QueryKind +proc parseUserResult*(userResult: UserResult): User = + result = userResult.legacy + + if result.verifiedType == none and userResult.isBlueVerified: + result.verifiedType = blue + + if result.username.len == 0 and userResult.core.screenName.len > 0: + result.id = userResult.restId + result.username = userResult.core.screenName + result.fullname = userResult.core.name + result.userPic = userResult.avatar.imageUrl.replace("_normal", "") + + if userResult.verification.isSome: + let v = userResult.verification.get + if v.verifiedType != VerifiedType.none: + result.verifiedType = v.verifiedType + + if userResult.profileBio.isSome: + result.bio = userResult.profileBio.get.description + proc parseGraphUser*(json: string): User = if json.len == 0 or json[0] != '{': return let raw = json.fromJson(GraphUser) + let userResult = raw.data.userResult.result - if raw.data.userResult.result.unavailableReason.get("") == "Suspended": + if userResult.unavailableReason.get("") == "Suspended": return User(suspended: true) - result = raw.data.userResult.result.legacy - result.id = raw.data.userResult.result.restId - if result.verifiedType == VerifiedType.none and raw.data.userResult.result.isBlueVerified: - result.verifiedType = blue + result = parseUserResult(userResult) proc parseGraphListMembers*(json, cursor: string): Result[User] = result = Result[User]( @@ -31,7 +49,7 @@ proc parseGraphListMembers*(json, cursor: string): Result[User] = of TimelineTimelineItem: let userResult = entry.content.itemContent.userResults.result if userResult.restId.len > 0: - result.content.add userResult.legacy + result.content.add parseUserResult(userResult) of TimelineTimelineCursor: if entry.content.cursorType == "Bottom": result.bottom = entry.content.value diff --git a/src/experimental/types/graphuser.nim b/src/experimental/types/graphuser.nim index 08100f9..d732b4e 100644 --- a/src/experimental/types/graphuser.nim +++ b/src/experimental/types/graphuser.nim @@ -1,5 +1,5 @@ -import options -from ../../types import User +import options, strutils +from ../../types import User, VerifiedType type GraphUser* = object @@ -8,8 +8,32 @@ type UserData* = object result*: UserResult - UserResult = object + UserCore* = object + name*: string + screenName*: string + createdAt*: string + + UserBio* = object + description*: string + + UserAvatar* = object + imageUrl*: string + + Verification* = object + verifiedType*: VerifiedType + + UserResult* = object legacy*: User restId*: string isBlueVerified*: bool unavailableReason*: Option[string] + core*: UserCore + avatar*: UserAvatar + profileBio*: Option[UserBio] + verification*: Option[Verification] + +proc enumHook*(s: string; v: var VerifiedType) = + v = try: + parseEnum[VerifiedType](s) + except: + VerifiedType.none diff --git a/src/parser.nim b/src/parser.nim index f132dea..c4ccab0 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -42,16 +42,16 @@ proc parseGraphUser(js: JsonNode): User = result = parseUser(user{"legacy"}, user{"rest_id"}.getStr) # fallback to support UserMedia/recent GraphQL updates - if result.username.len == 0 and user{"core", "screen_name"}.notNull: + if result.username.len == 0: result.username = user{"core", "screen_name"}.getStr result.fullname = user{"core", "name"}.getStr result.userPic = user{"avatar", "image_url"}.getImageStr.replace("_normal", "") if user{"is_blue_verified"}.getBool(false): result.verifiedType = blue - elif user{"verification", "verified_type"}.notNull: - let verifiedType = user{"verification", "verified_type"}.getStr("None") - result.verifiedType = parseEnum[VerifiedType](verifiedType) + + with verifiedType, user{"verification", "verified_type"}: + result.verifiedType = parseEnum[VerifiedType](verifiedType.getStr) proc parseGraphList*(js: JsonNode): List = if js.isNull: return @@ -372,10 +372,10 @@ proc parseGraphTweetResult*(js: JsonNode): Tweet = with tweet, js{"data", "tweet_result", "result"}: result = parseGraphTweet(tweet, false) -proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversation = +proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) - let + v2 = js{"data", "timeline_response"}.notNull rootKey = if v2: "timeline_response" else: "threaded_conversation_with_injections_v2" contentKey = if v2: "content" else: "itemContent" resultKey = if v2: "tweetResult" else: "tweet_results" @@ -385,7 +385,8 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversati return for i in instructions: - if i{"__typename"}.getStr == "TimelineAddEntries": + let instrType = i{"__typename"}.getStr(i{"type"}.getStr) + if instrType == "TimelineAddEntries": for e in i{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): @@ -421,20 +422,23 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversati result.replies.bottom = e{"content", contentKey, "value"}.getStr proc extractTweetsFromEntry*(e: JsonNode; entryId: string): seq[Tweet] = - if e{"content", "items"}.notNull: - for item in e{"content", "items"}: - with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: - var tweet = parseGraphTweet(tweetResult, false) - if not tweet.available: - tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) - result.add tweet - return + var tweetResult = e{"content", "itemContent", "tweet_results", "result"} + if tweetResult.isNull: + tweetResult = e{"content", "content", "tweetResult", "result"} - with tweetResult, e{"content", "content", "tweetResult", "result"}: + if tweetResult.notNull: var tweet = parseGraphTweet(tweetResult, false) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) result.add tweet + return + + for item in e{"content", "items"}: + with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: + var tweet = parseGraphTweet(tweetResult, false) + if not tweet.available: + tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + result.add tweet proc parseGraphTimeline*(js: JsonNode; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) From 6b655cddd803fb1efc424b70d8f7be760b6c7c60 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 11:01:20 +0100 Subject: [PATCH 136/302] Cleanup --- src/auth.nim | 17 +---------------- src/experimental/parser/user.nim | 18 ------------------ src/experimental/types/timeline.nim | 23 ----------------------- 3 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 src/experimental/types/timeline.nim diff --git a/src/auth.nim b/src/auth.nim index 6c52918..734b43e 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -7,20 +7,6 @@ import experimental/parser/session const maxConcurrentReqs = 2 hourInSeconds = 60 * 60 - apiMaxReqs: Table[Api, int] = { - Api.search: 50, - Api.tweetDetail: 500, - Api.userTweets: 500, - Api.userTweetsAndReplies: 500, - Api.userMedia: 500, - Api.userRestId: 500, - Api.userScreenName: 500, - Api.tweetResult: 500, - Api.list: 500, - Api.listTweets: 500, - Api.listMembers: 500, - Api.listBySlug: 500 - }.toTable var sessionPool: seq[Session] @@ -71,8 +57,7 @@ proc getSessionPoolHealth*(): JsonNode = for api in session.apis.keys: let apiStatus = session.apis[api] - limit = if apiStatus.limit > 0: apiStatus.limit else: apiMaxReqs.getOrDefault(api, 0) - reqs = limit - apiStatus.remaining + reqs = apiStatus.limit - apiStatus.remaining # no requests made with this session and endpoint since the limit reset if apiStatus.reset < now: diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 07e0477..498757a 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -72,21 +72,3 @@ proc parseHook*(s: string; i: var int; v: var User) = var u: RawUser parseHook(s, i, u) v = toUser u - -proc parseUser*(json: string; username=""): User = - handleErrors: - case error.code - of suspended: return User(username: username, suspended: true) - of userNotFound: return - else: echo "[error - parseUser]: ", error - - result = json.fromJson(User) - -proc parseUsers*(json: string; after=""): Result[User] = - result = Result[User](beginning: after.len == 0) - - # starting with '{' means it's an error - if json[0] == '[': - let raw = json.fromJson(seq[RawUser]) - for user in raw: - result.content.add user.toUser diff --git a/src/experimental/types/timeline.nim b/src/experimental/types/timeline.nim deleted file mode 100644 index 5ce6d9f..0000000 --- a/src/experimental/types/timeline.nim +++ /dev/null @@ -1,23 +0,0 @@ -import std/tables -from ../../types import User - -type - Search* = object - globalObjects*: GlobalObjects - timeline*: Timeline - - GlobalObjects = object - users*: Table[string, User] - - Timeline = object - instructions*: seq[Instructions] - - Instructions = object - addEntries*: tuple[entries: seq[Entry]] - - Entry = object - entryId*: string - content*: tuple[operation: Operation] - - Operation = object - cursor*: tuple[value, cursorType: string] From e8de18317ec072e47a56b42dff44f7136b20117e Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 11:21:21 +0100 Subject: [PATCH 137/302] Fix broken pinned tweet parsing --- src/parser.nim | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index c4ccab0..614ab57 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -385,7 +385,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = return for i in instructions: - let instrType = i{"__typename"}.getStr(i{"type"}.getStr) + let instrType = i{"type"}.getStr(i{"__typename"}.getStr) if instrType == "TimelineAddEntries": for e in i{"entries"}: let entryId = e{"entryId"}.getStr @@ -421,7 +421,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = elif entryId.startsWith("cursor-bottom"): result.replies.bottom = e{"content", contentKey, "value"}.getStr -proc extractTweetsFromEntry*(e: JsonNode; entryId: string): seq[Tweet] = +proc extractTweetsFromEntry*(e: JsonNode): seq[Tweet] = var tweetResult = e{"content", "itemContent", "tweet_results", "result"} if tweetResult.isNull: tweetResult = e{"content", "content", "tweetResult", "result"} @@ -429,7 +429,7 @@ proc extractTweetsFromEntry*(e: JsonNode; entryId: string): seq[Tweet] = if tweetResult.notNull: var tweet = parseGraphTweet(tweetResult, false) if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) + tweet.id = parseBiggestInt(e.getEntryId()) result.add tweet return @@ -469,7 +469,7 @@ proc parseGraphTimeline*(js: JsonNode; after=""): Profile = for e in i{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): - for tweet in extractTweetsFromEntry(e, entryId): + for tweet in extractTweetsFromEntry(e): result.tweets.content.add tweet elif "-conversation-" in entryId or entryId.startsWith("homeConversation"): let (thread, self) = parseGraphThread(e) @@ -477,15 +477,14 @@ proc parseGraphTimeline*(js: JsonNode; after=""): Profile = elif entryId.startsWith("cursor-bottom"): result.tweets.bottom = e{"content", "value"}.getStr - if after.len == 0 and i{"__typename"}.getStr == "TimelinePinEntry": - with tweetResult, i{"entry", "content", "content", "tweetResult", "result"}: - let tweet = parseGraphTweet(tweetResult, false) - tweet.pinned = true - if not tweet.available and tweet.tombstone.len == 0: - let entryId = i{"entry", "entryId"}.getEntryId - if entryId.len > 0: - tweet.id = parseBiggestInt(entryId) - result.pinned = some tweet + if after.len == 0: + let instrType = i{"type"}.getStr(i{"__typename"}.getStr) + if instrType == "TimelinePinEntry": + let tweets = extractTweetsFromEntry(i{"entry"}) + if tweets.len > 0: + var tweet = tweets[0] + tweet.pinned = true + result.pinned = some tweet proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = result = @[] @@ -523,7 +522,7 @@ proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = for e in i{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): - for t in extractTweetsFromEntry(e, entryId): + for t in extractTweetsFromEntry(e): let photo = extractGalleryPhoto(t) if photo.url.len > 0: result.add photo From 824a7e346a730a3eeb87a945e8268b3e40867f0d Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 17 Nov 2025 12:08:44 +0100 Subject: [PATCH 138/302] Fix rss icon tag --- src/views/general.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/general.nim b/src/views/general.nim index 0571aa7..0091c74 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -30,7 +30,7 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode = tdiv(class="nav-item right"): icon "search", title="Search", href="/search" if cfg.enableRss and rss.len > 0: - icon "rss-feed", title="RSS Feed", href=rss + icon "rss", title="RSS Feed", href=rss icon "bird", title="Open in Twitter", href=canonical a(href="https://liberapay.com/zedeus"): verbatim lp icon "info", title="About", href="/about" From 78d788b27f977ec8f7d6c91d9742c1b8343d08bf Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Wed, 19 Nov 2025 07:33:32 +0100 Subject: [PATCH 139/302] Fix verified parsing for oauth endpoints --- src/parser.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/parser.nim b/src/parser.nim index 614ab57..8712a5f 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -21,11 +21,16 @@ proc parseUser(js: JsonNode; id=""): User = tweets: js{"statuses_count"}.getInt, likes: js{"favourites_count"}.getInt, media: js{"media_count"}.getInt, - verifiedType: parseEnum[VerifiedType](js{"verified_type"}.getStr("None")), protected: js{"protected"}.getBool, joinDate: js{"created_at"}.getTime ) + if js{"is_blue_verified"}.getBool(false): + result.verifiedType = blue + + with verifiedType, js{"verified_type"}: + result.verifiedType = parseEnum[VerifiedType](verifiedType.getStr) + result.expandUserEntities(js) proc parseGraphUser(js: JsonNode): User = From b0d9c1d51a4039cecbb58ab0180883baf5325d59 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 22 Nov 2025 21:21:10 +0100 Subject: [PATCH 140/302] Update endpoints, fix parser, remove quotes stat --- src/api.nim | 58 +++++---- src/consts.nim | 35 +++-- src/parser.nim | 308 ++++++++++++++++++++++++++------------------ src/parserutils.nim | 53 ++++---- src/types.nim | 2 - src/views/tweet.nim | 13 +- 6 files changed, 269 insertions(+), 200 deletions(-) diff --git a/src/api.nim b/src/api.nim index aeb0f17..ef3a0f9 100644 --- a/src/api.nim +++ b/src/api.nim @@ -1,16 +1,23 @@ # SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, httpclient, uri, strutils, sequtils, sugar +import asyncdispatch, httpclient, uri, strutils, sequtils, sugar, tables import packedjson import types, query, formatters, consts, apiutils, parser import experimental/parser as newParser +# Helper to generate params object for GraphQL requests +proc genParams(variables: string; fieldToggles = ""): seq[(string, string)] = + result.add ("variables", variables) + result.add ("features", gqlFeatures) + if fieldToggles.len > 0: + result.add ("fieldToggles", fieldToggles) + proc mediaUrl(id: string; cursor: string): SessionAwareUrl = let cookieVariables = userMediaVariables % [id, cursor] oauthVariables = restIdVariables % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserMedia ? {"variables": cookieVariables, "features": gqlFeatures}, - oauthUrl: graphUserMediaV2 ? {"variables": oauthVariables, "features": gqlFeatures} + cookieUrl: graphUserMedia ? genParams(cookieVariables), + oauthUrl: graphUserMediaV2 ? genParams(oauthVariables) ) proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = @@ -18,17 +25,19 @@ proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = cookieVariables = userTweetsVariables % [id, cursor] oauthVariables = restIdVariables % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserTweets ? {"variables": cookieVariables, "features": gqlFeatures, "fieldToggles": fieldToggles}, - oauthUrl: graphUserTweetsV2 ? {"variables": oauthVariables, "features": gqlFeatures} + # cookieUrl: graphUserTweets ? genParams(cookieVariables, fieldToggles), + oauthUrl: graphUserTweetsV2 ? genParams(oauthVariables) ) + # might change this in the future pending testing + result.cookieUrl = result.oauthUrl proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl = let cookieVariables = userTweetsAndRepliesVariables % [id, cursor] oauthVariables = restIdVariables % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserTweetsAndReplies ? {"variables": cookieVariables, "features": gqlFeatures, "fieldToggles": fieldToggles}, - oauthUrl: graphUserTweetsAndRepliesV2 ? {"variables": oauthVariables, "features": gqlFeatures} + cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVariables, fieldToggles), + oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVariables) ) proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = @@ -36,24 +45,22 @@ proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = cookieVariables = tweetDetailVariables % [id, cursor] oauthVariables = tweetVariables % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphTweetDetail ? {"variables": cookieVariables, "features": gqlFeatures, "fieldToggles": tweetDetailFieldToggles}, - oauthUrl: graphTweet ? {"variables": oauthVariables, "features": gqlFeatures} + cookieUrl: graphTweetDetail ? genParams(cookieVariables, tweetDetailFieldToggles), + oauthUrl: graphTweet ? genParams(oauthVariables) ) proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return let - variables = """{"screen_name": "$1"}""" % username - params = {"variables": variables, "features": gqlFeatures} - js = await fetchRaw(graphUser ? params, Api.userScreenName) + url = graphUser ? genParams("""{"screen_name": "$1"}""" % username) + js = await fetchRaw(url, Api.userScreenName) result = parseGraphUser(js) proc getGraphUserById*(id: string): Future[User] {.async.} = if id.len == 0 or id.any(c => not c.isDigit): return let - variables = """{"rest_id": "$1"}""" % id - params = {"variables": variables, "features": gqlFeatures} - js = await fetchRaw(graphUserById ? params, Api.userRestId) + url = graphUserById ? genParams("""{"rest_id": "$1"}""" % id) + js = await fetchRaw(url, Api.userRestId) result = parseGraphUser(js) proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profile] {.async.} = @@ -73,23 +80,18 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - variables = restIdVariables % [id, cursor] - params = {"variables": variables, "features": gqlFeatures} - js = await fetch(graphListTweets ? params, Api.listTweets) - result = parseGraphTimeline(js, after).tweets + url = graphListTweets ? genParams(restIdVariables % [id, cursor]) + result = parseGraphTimeline(await fetch(url, Api.listTweets), after).tweets proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = let variables = %*{"screenName": name, "listSlug": list} - params = {"variables": $variables, "features": gqlFeatures} - url = graphListBySlug ? params + url = graphListBySlug ? genParams($variables) result = parseGraphList(await fetch(url, Api.listBySlug)) proc getGraphList*(id: string): Future[List] {.async.} = let - variables = """{"listId": "$1"}""" % id - params = {"variables": variables, "features": gqlFeatures} - url = graphListById ? params + url = graphListById ? genParams("""{"listId": "$1"}""" % id) result = parseGraphList(await fetch(url, Api.list)) proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} = @@ -104,7 +106,7 @@ proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} } if after.len > 0: variables["cursor"] = % after - let url = graphListMembers ? {"variables": $variables, "features": gqlFeatures} + let url = graphListMembers ? genParams($variables) result = parseGraphListMembers(await fetchRaw(url, Api.listMembers), after) proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} = @@ -139,6 +141,7 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = var variables = %*{ "rawQuery": q, + "query_source": "typedQuery", "count": 20, "product": "Latest", "withDownvotePerspective": false, @@ -147,7 +150,7 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = } if after.len > 0: variables["cursor"] = % after - let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} + let url = graphSearchTimeline ? genParams($variables) result = parseGraphSearch[Tweets](await fetch(url, Api.search), after) result.query = query @@ -158,6 +161,7 @@ proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} var variables = %*{ "rawQuery": query.text, + "query_source": "typedQuery", "count": 20, "product": "People", "withDownvotePerspective": false, @@ -168,7 +172,7 @@ proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} variables["cursor"] = % after result.beginning = false - let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures} + let url = graphSearchTimeline ? genParams($variables) result = parseGraphSearch[User](await fetch(url, Api.search), after) result.query = query diff --git a/src/consts.nim b/src/consts.nim index 2623484..792a519 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -7,26 +7,29 @@ const gql = parseUri("https://api.x.com") / "graphql" - graphUser* = gql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" - graphUserById* = gql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" - graphUserTweetsV2* = gql / "JLApJKFY0MxGTzCoK6ps8Q/UserWithProfileTweetsQueryV2" - graphUserTweetsAndRepliesV2* = gql / "Y86LQY7KMvxn5tu3hFTyPg/UserWithProfileTweetsAndRepliesQueryV2" + graphUser* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" + graphUserById* = gql / "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery" + graphUserTweetsV2* = gql / "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2" + graphUserTweetsAndRepliesV2* = gql / "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2" graphUserTweets* = gql / "oRJs8SLCRNRbQzuZG93_oA/UserTweets" graphUserTweetsAndReplies* = gql / "kkaJ0Mf34PZVarrxzLihjg/UserTweetsAndReplies" graphUserMedia* = gql / "36oKqyQ7E_9CmtONGjJRsA/UserMedia" - graphUserMediaV2* = gql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" - graphTweet* = gql / "Vorskcd2tZ-tc4Gx3zbk4Q/ConversationTimelineV2" + graphUserMediaV2* = gql / "bp0e_WdXqgNBIwlLukzyYA/MediaTimelineV2" + graphTweet* = gql / "Y4Erk_-0hObvLpz0Iw3bzA/ConversationTimeline" graphTweetDetail* = gql / "YVyS4SfwYW7Uw5qwy0mQCA/TweetDetail" - graphTweetResult* = gql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" - graphSearchTimeline* = gql / "7r8ibjHuK3MWUyzkzHNMYQ/SearchTimeline" + graphTweetResult* = gql / "nzme9KiYhfIOrrLrPP_XeQ/TweetResultByIdQuery" + graphSearchTimeline* = gql / "bshMIjqDk8LTXTq4w91WKw/SearchTimeline" graphListById* = gql / "cIUpT1UjuGgl_oWiY7Snhg/ListByRestId" graphListBySlug* = gql / "K6wihoTiTrzNzSF8y1aeKQ/ListBySlug" graphListMembers* = gql / "fuVHh5-gFn8zDBBxb8wOMA/ListMembers" - graphListTweets* = gql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" + graphListTweets* = gql / "VQf8_XQynI3WzH6xopOMMQ/ListTimeline" gqlFeatures* = """{ + "android_ad_formats_media_component_render_overlay_enabled": false, "android_graphql_skip_api_media_color_palette": false, + "android_professional_link_spotlight_display_enabled": false, "blue_business_profile_image_shape_enabled": false, + "commerce_android_shop_module_enabled": false, "creator_subscriptions_subscription_count_enabled": false, "creator_subscriptions_tweet_preview_api_enabled": true, "freedom_of_speech_not_reach_fetch_enabled": true, @@ -36,8 +39,9 @@ const "interactive_text_enabled": false, "longform_notetweets_consumption_enabled": true, "longform_notetweets_inline_media_enabled": true, - "longform_notetweets_richtext_consumption_enabled": true, "longform_notetweets_rich_text_read_enabled": true, + "longform_notetweets_richtext_consumption_enabled": true, + "mobile_app_spotlight_module_enabled": false, "responsive_web_edit_tweet_api_enabled": true, "responsive_web_enhance_cards_enabled": false, "responsive_web_graphql_exclude_directive_enabled": true, @@ -46,6 +50,7 @@ const "responsive_web_media_download_video_enabled": false, "responsive_web_text_conversations_enabled": false, "responsive_web_twitter_article_tweet_consumption_enabled": true, + "unified_cards_destination_url_params_enabled": false, "responsive_web_twitter_blue_verified_badge_is_enabled": true, "rweb_lists_timeline_redesign_enabled": true, "spaces_2022_h2_clipping": true, @@ -86,11 +91,17 @@ const "payments_enabled": false, "responsive_web_profile_redirect_enabled": false, "responsive_web_grok_show_grok_translated_post": false, - "responsive_web_grok_community_note_auto_translation_is_enabled": false + "responsive_web_grok_community_note_auto_translation_is_enabled": false, + "profile_label_improvements_pcf_label_in_profile_enabled": false, + "grok_android_analyze_trend_fetch_enabled": false, + "grok_translations_community_note_auto_translation_is_enabled": false, + "grok_translations_post_auto_translation_is_enabled": false, + "grok_translations_community_note_translation_is_enabled": false, + "grok_translations_timeline_user_bio_auto_translation_is_enabled": false }""".replace(" ", "").replace("\n", "") tweetVariables* = """{ - "focalTweetId": "$1", + "postId": "$1", $2 "includeHasBirdwatchNotes": false, "includePromotedContent": false, diff --git a/src/parser.nim b/src/parser.nim index 8712a5f..700e896 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -1,10 +1,10 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, options, times, math +import strutils, options, times, math, tables import packedjson, packedjson/deserialiser import types, parserutils, utils import experimental/parser/unifiedcard -proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet +proc parseGraphTweet(js: JsonNode): Tweet proc parseUser(js: JsonNode; id=""): User = if js.isNull: return @@ -46,6 +46,9 @@ proc parseGraphUser(js: JsonNode): User = result = parseUser(user{"legacy"}, user{"rest_id"}.getStr) + if result.verifiedType == none and user{"is_blue_verified"}.getBool(false): + result.verifiedType = blue + # fallback to support UserMedia/recent GraphQL updates if result.username.len == 0: result.username = user{"core", "screen_name"}.getStr @@ -95,16 +98,24 @@ proc parsePoll(js: JsonNode): Poll = result.leader = result.values.find(max(result.values)) result.votes = result.values.sum -proc parseGif(js: JsonNode): Gif = - result = Gif( - url: js{"video_info", "variants"}[0]{"url"}.getImageStr, - thumb: js{"media_url_https"}.getImageStr - ) +proc parseVideoVariants(variants: JsonNode): seq[VideoVariant] = + result = @[] + for v in variants: + let + url = v{"url"}.getStr + contentType = parseEnum[VideoType](v{"content_type"}.getStr("video/mp4")) + bitrate = v{"bit_rate"}.getInt(v{"bitrate"}.getInt(0)) + + result.add VideoVariant( + contentType: contentType, + bitrate: bitrate, + url: url, + resolution: if contentType == mp4: getMp4Resolution(url) else: 0 + ) proc parseVideo(js: JsonNode): Video = result = Video( thumb: js{"media_url_https"}.getImageStr, - views: getVideoViewCount(js), available: true, title: js{"ext_alt_text"}.getStr, durationMs: js{"video_info", "duration_millis"}.getInt @@ -121,17 +132,62 @@ proc parseVideo(js: JsonNode): Video = with description, js{"additional_media_info", "description"}: result.description = description.getStr - for v in js{"video_info", "variants"}: - let - contentType = parseEnum[VideoType](v{"content_type"}.getStr("summary")) - url = v{"url"}.getStr + result.variants = parseVideoVariants(js{"video_info", "variants"}) - result.variants.add VideoVariant( - contentType: contentType, - bitrate: v{"bitrate"}.getInt, - url: url, - resolution: if contentType == mp4: getMp4Resolution(url) else: 0 - ) +proc parseLegacyMediaEntities(js: JsonNode; result: var Tweet) = + with jsMedia, js{"extended_entities", "media"}: + for m in jsMedia: + case m.getTypeName: + of "photo": + result.photos.add m{"media_url_https"}.getImageStr + of "video": + result.video = some(parseVideo(m)) + with user, m{"additional_media_info", "source_user"}: + if user{"id"}.getInt > 0: + result.attribution = some(parseUser(user)) + else: + result.attribution = some(parseGraphUser(user)) + of "animated_gif": + result.gif = some Gif( + url: m{"video_info", "variants"}[0]{"url"}.getImageStr, + thumb: m{"media_url_https"}.getImageStr + ) + else: discard + + with url, m{"url"}: + if result.text.endsWith(url.getStr): + result.text.removeSuffix(url.getStr) + result.text = result.text.strip() + +proc parseMediaEntities(js: JsonNode; result: var Tweet) = + with mediaEntities, js{"media_entities"}: + for mediaEntity in mediaEntities: + with mediaInfo, mediaEntity{"media_results", "result", "media_info"}: + case mediaInfo.getTypeName + of "ApiImage": + result.photos.add mediaInfo{"original_img_url"}.getImageStr + of "ApiVideo": + let status = mediaEntity{"media_results", "result", "media_availability_v2", "status"} + result.video = some Video( + available: status.getStr == "Available", + thumb: mediaInfo{"preview_image", "original_img_url"}.getImageStr, + durationMs: mediaInfo{"duration_millis"}.getInt, + variants: parseVideoVariants(mediaInfo{"variants"}) + ) + of "ApiGif": + result.gif = some Gif( + url: mediaInfo{"variants"}[0]{"url"}.getImageStr, + thumb: mediaInfo{"preview_image", "original_img_url"}.getImageStr + ) + else: discard + + # Remove media URLs from text + with mediaList, js{"legacy", "entities", "media"}: + for url in mediaList: + let expandedUrl = url{"expanded_url"}.getStr + if result.text.endsWith(expandedUrl): + result.text.removeSuffix(expandedUrl) + result.text = result.text.strip() proc parsePromoVideo(js: JsonNode): Video = result = Video( @@ -223,12 +279,17 @@ proc parseCard(js: JsonNode; urls: JsonNode): Card = proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = if js.isNull: return + + let time = + if js{"created_at"}.notNull: js{"created_at"}.getTime + else: js{"created_at_ms"}.getTimeFromMs + result = Tweet( id: js{"id_str"}.getId, threadId: js{"conversation_id_str"}.getId, replyId: js{"in_reply_to_status_id_str"}.getId, text: js{"full_text"}.getStr, - time: js{"created_at"}.getTime, + time: time, hasThread: js{"self_thread"}.notNull, available: true, user: User(id: js{"user_id_str"}.getStr), @@ -236,7 +297,6 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = replies: js{"reply_count"}.getInt, retweets: js{"retweet_count"}.getInt, likes: js{"favorite_count"}.getInt, - quotes: js{"quote_count"}.getInt, views: js{"views_count"}.getInt ) ) @@ -262,6 +322,12 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.retweet = some parseGraphTweet(rt) return + with reposts, js{"repostedStatusResults"}: + with rt, reposts{"result"}: + if "legacy" in rt: + result.retweet = some parseGraphTweet(rt) + return + if jsCard.kind != JNull: let name = jsCard{"name"}.getStr if "poll" in name: @@ -275,27 +341,7 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.card = some parseCard(jsCard, js{"entities", "urls"}) result.expandTweetEntities(js) - - with jsMedia, js{"extended_entities", "media"}: - for m in jsMedia: - case m{"type"}.getStr - of "photo": - result.photos.add m{"media_url_https"}.getImageStr - of "video": - result.video = some(parseVideo(m)) - with user, m{"additional_media_info", "source_user"}: - if user{"id"}.getInt > 0: - result.attribution = some(parseUser(user)) - else: - result.attribution = some(parseGraphUser(user)) - of "animated_gif": - result.gif = some(parseGif(m)) - else: discard - - with url, m{"url"}: - if result.text.endsWith(url.getStr): - result.text.removeSuffix(url.getStr) - result.text = result.text.strip() + parseLegacyMediaEntities(js, result) with jsWithheld, js{"withheld_in_countries"}: let withheldInCountries: seq[string] = @@ -311,95 +357,108 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = result.text.removeSuffix(" Learn more.") result.available = false -proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet = +proc parseGraphTweet(js: JsonNode): Tweet = if js.kind == JNull: return Tweet() - case js{"__typename"}.getStr + case js.getTypeName: of "TweetUnavailable": return Tweet() of "TweetTombstone": - with text, js{"tombstone", "richText"}: - return Tweet(text: text.getTombstone) - with text, js{"tombstone", "text"}: + with text, select(js{"tombstone", "richText"}, js{"tombstone", "text"}): return Tweet(text: text.getTombstone) return Tweet() of "TweetPreviewDisplay": return Tweet(text: "You're unable to view this Tweet because it's only available to the Subscribers of the account owner.") of "TweetWithVisibilityResults": - return parseGraphTweet(js{"tweet"}, isLegacy) + return parseGraphTweet(js{"tweet"}) else: discard if not js.hasKey("legacy"): return Tweet() - var jsCard = copy(js{if isLegacy: "card" else: "tweet_card", "legacy"}) + var jsCard = select(js{"card"}, js{"tweet_card"}, js{"legacy", "tweet_card"}) if jsCard.kind != JNull: - var values = newJObject() - for val in jsCard["binding_values"]: - values[val["key"].getStr] = val["value"] - jsCard["binding_values"] = values + let legacyCard = jsCard{"legacy"} + if legacyCard.kind != JNull: + let bindingArray = legacyCard{"binding_values"} + if bindingArray.kind == JArray: + var bindingObj: seq[(string, JsonNode)] + for item in bindingArray: + bindingObj.add((item{"key"}.getStr, item{"value"})) + # Create a new card object with flattened structure + jsCard = %*{ + "name": legacyCard{"name"}, + "url": legacyCard{"url"}, + "binding_values": %bindingObj + } result = parseTweet(js{"legacy"}, jsCard) result.id = js{"rest_id"}.getId result.user = parseGraphUser(js{"core"}) + if result.replyId == 0: + result.replyId = js{"reply_to_results", "rest_id"}.getId + with count, js{"views", "count"}: result.stats.views = count.getStr("0").parseInt with noteTweet, js{"note_tweet", "note_tweet_results", "result"}: result.expandNoteTweetEntities(noteTweet) + parseMediaEntities(js, result) + if result.quote.isSome: - result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"}, isLegacy)) + result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"})) + + with quoted, js{"quotedPostResults", "result"}: + result.quote = some(parseGraphTweet(quoted)) proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] = - for t in js{"content", "items"}: - let entryId = t{"entryId"}.getStr + for t in ? js{"content", "items"}: + let entryId = t.getEntryId if "cursor-showmore" in entryId: let cursor = t{"item", "content", "value"} result.thread.cursor = cursor.getStr result.thread.hasMore = true elif "tweet" in entryId and "promoted" notin entryId: - let - isLegacy = t{"item"}.hasKey("itemContent") - (contentKey, resultKey) = if isLegacy: ("itemContent", "tweet_results") - else: ("content", "tweetResult") + with tweet, t.getTweetResult("item"): + result.thread.content.add parseGraphTweet(tweet) - with content, t{"item", contentKey}: - result.thread.content.add parseGraphTweet(content{resultKey, "result"}, isLegacy) - - if content{"tweetDisplayType"}.getStr == "SelfThread": + let tweetDisplayType = select( + t{"item", "content", "tweet_display_type"}, + t{"item", "itemContent", "tweetDisplayType"} + ) + if tweetDisplayType.getStr == "SelfThread": result.self = true proc parseGraphTweetResult*(js: JsonNode): Tweet = with tweet, js{"data", "tweet_result", "result"}: - result = parseGraphTweet(tweet, false) + result = parseGraphTweet(tweet) proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) - let - v2 = js{"data", "timeline_response"}.notNull - rootKey = if v2: "timeline_response" else: "threaded_conversation_with_injections_v2" - contentKey = if v2: "content" else: "itemContent" - resultKey = if v2: "tweetResult" else: "tweet_results" - let instructions = ? js{"data", rootKey, "instructions"} + let instructions = ? select( + js{"data", "timelineResponse", "instructions"}, + js{"data", "timeline_response", "instructions"}, + js{"data", "threaded_conversation_with_injections_v2", "instructions"} + ) if instructions.len == 0: return for i in instructions: - let instrType = i{"type"}.getStr(i{"__typename"}.getStr) - if instrType == "TimelineAddEntries": + if i.getTypeName == "TimelineAddEntries": for e in i{"entries"}: - let entryId = e{"entryId"}.getStr + let entryId = e.getEntryId if entryId.startsWith("tweet"): - with tweetResult, e{"content", contentKey, resultKey, "result"}: - let tweet = parseGraphTweet(tweetResult, not v2) + let tweetResult = getTweetResult(e) + if tweetResult.notNull: + let tweet = parseGraphTweet(tweetResult) if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) + tweet.id = entryId.getId if $tweet.id == tweetId: result.tweet = tweet @@ -412,67 +471,64 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = elif thread.content.len > 0: result.replies.content.add thread elif entryId.startsWith("tombstone"): - let id = entryId.getId() - let tweet = Tweet( - id: parseBiggestInt(id), - available: false, - text: e{"content", contentKey, "tombstoneInfo", "richText"}.getTombstone - ) + let + content = select(e{"content", "content"}, e{"content", "itemContent"}) + tweet = Tweet( + id: entryId.getId, + available: false, + text: content{"tombstoneInfo", "richText"}.getTombstone + ) - if id == tweetId: + if $tweet.id == tweetId: result.tweet = tweet else: result.before.content.add tweet elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", contentKey, "value"}.getStr + var cursorValue = select( + e{"content", "content", "value"}, + e{"content", "itemContent", "value"} + ) + result.replies.bottom = cursorValue.getStr proc extractTweetsFromEntry*(e: JsonNode): seq[Tweet] = - var tweetResult = e{"content", "itemContent", "tweet_results", "result"} - if tweetResult.isNull: - tweetResult = e{"content", "content", "tweetResult", "result"} - - if tweetResult.notNull: - var tweet = parseGraphTweet(tweetResult, false) + with tweetResult, getTweetResult(e): + var tweet = parseGraphTweet(tweetResult) if not tweet.available: - tweet.id = parseBiggestInt(e.getEntryId()) + tweet.id = e.getEntryId.getId result.add tweet return for item in e{"content", "items"}: - with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: - var tweet = parseGraphTweet(tweetResult, false) + with tweetResult, item.getTweetResult("item"): + var tweet = parseGraphTweet(tweetResult) if not tweet.available: - tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + tweet.id = item.getEntryId.getId result.add tweet proc parseGraphTimeline*(js: JsonNode; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0)) - let instructions = - if js{"data", "list"}.notNull: - ? js{"data", "list", "timeline_response", "timeline", "instructions"} - elif js{"data", "user"}.notNull: - ? js{"data", "user", "result", "timeline", "timeline", "instructions"} - else: - ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} - + let instructions = ? select( + js{"data", "list", "timeline_response", "timeline", "instructions"}, + js{"data", "user", "result", "timeline", "timeline", "instructions"}, + js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + ) if instructions.len == 0: return for i in instructions: - # TimelineAddToModule instruction is used by UserMedia if i{"moduleItems"}.notNull: for item in i{"moduleItems"}: - with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetResult, false) + with tweetResult, item.getTweetResult("item"): + let tweet = parseGraphTweet(tweetResult) if not tweet.available: - tweet.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + tweet.id = item.getEntryId.getId result.tweets.content.add tweet continue if i{"entries"}.notNull: for e in i{"entries"}: - let entryId = e{"entryId"}.getStr + let entryId = e.getEntryId if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): for tweet in extractTweetsFromEntry(e): result.tweets.content.add tweet @@ -483,8 +539,7 @@ proc parseGraphTimeline*(js: JsonNode; after=""): Profile = result.tweets.bottom = e{"content", "value"}.getStr if after.len == 0: - let instrType = i{"type"}.getStr(i{"__typename"}.getStr) - if instrType == "TimelinePinEntry": + if i.getTypeName == "TimelinePinEntry": let tweets = extractTweetsFromEntry(i{"entry"}) if tweets.len > 0: var tweet = tweets[0] @@ -494,23 +549,20 @@ proc parseGraphTimeline*(js: JsonNode; after=""): Profile = proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = result = @[] - let instructions = - if js{"data", "user"}.notNull: - ? js{"data", "user", "result", "timeline", "timeline", "instructions"} - else: - ? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} - + let instructions = select( + js{"data", "user", "result", "timeline", "timeline", "instructions"}, + js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"} + ) if instructions.len == 0: return for i in instructions: - # TimelineAddToModule instruction is used by MediaTimelineV2 if i{"moduleItems"}.notNull: for item in i{"moduleItems"}: - with tweetResult, item{"item", "itemContent", "tweet_results", "result"}: - let t = parseGraphTweet(tweetResult, false) + with tweetResult, item.getTweetResult("item"): + let t = parseGraphTweet(tweetResult) if not t.available: - t.id = parseBiggestInt(item{"entryId"}.getStr.getId()) + t.id = item.getEntryId.getId let photo = extractGalleryPhoto(t) if photo.url.len > 0: @@ -520,12 +572,11 @@ proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = return continue - let instrType = i{"type"}.getStr(i{"__typename"}.getStr) - if instrType != "TimelineAddEntries": + if i.getTypeName != "TimelineAddEntries": continue for e in i{"entries"}: - let entryId = e{"entryId"}.getStr + let entryId = e.getEntryId if entryId.startsWith("tweet") or entryId.startsWith("profile-grid"): for t in extractTweetsFromEntry(e): let photo = extractGalleryPhoto(t) @@ -538,21 +589,24 @@ proc parseGraphPhotoRail*(js: JsonNode): PhotoRail = proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] = result = Result[T](beginning: after.len == 0) - let instructions = js{"data", "search_by_raw_query", "search_timeline", "timeline", "instructions"} + let instructions = select( + js{"data", "search", "timeline_response", "timeline", "instructions"}, + js{"data", "search_by_raw_query", "search_timeline", "timeline", "instructions"} + ) if instructions.len == 0: return for instruction in instructions: - let typ = instruction{"type"}.getStr + let typ = getTypeName(instruction) if typ == "TimelineAddEntries": for e in instruction{"entries"}: - let entryId = e{"entryId"}.getStr + let entryId = e.getEntryId when T is Tweets: if entryId.startsWith("tweet"): - with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: + with tweetRes, getTweetResult(e): let tweet = parseGraphTweet(tweetRes) if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) + tweet.id = entryId.getId result.content.add tweet elif T is User: if entryId.startsWith("user"): diff --git a/src/parserutils.nim b/src/parserutils.nim index 7e246dd..f40082c 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -36,6 +36,12 @@ template `?`*(js: JsonNode): untyped = if j.isNull: return j +template select*(a, b: JsonNode): untyped = + if a.notNull: a else: b + +template select*(a, b, c: JsonNode): untyped = + if a.notNull: a elif b.notNull: b else: c + template with*(ident, value, body): untyped = if true: let ident {.inject.} = value @@ -54,6 +60,20 @@ template getError*(js: JsonNode): Error = if js.kind != JArray or js.len == 0: null else: Error(js[0]{"code"}.getInt) +proc getTweetResult*(js: JsonNode; root="content"): JsonNode = + select( + js{root, "content", "tweet_results", "result"}, + js{root, "itemContent", "tweet_results", "result"}, + js{root, "content", "tweetResult", "result"} + ) + +template getTypeName*(js: JsonNode): string = + js{"__typename"}.getStr(js{"type"}.getStr) + +template getEntryId*(e: JsonNode): string = + e{"entryId"}.getStr(e{"entry_id"}.getStr) + + template parseTime(time: string; f: static string; flen: int): DateTime = if time.len != flen: return parse(time, f, utc()) @@ -64,29 +84,24 @@ proc getDateTime*(js: JsonNode): DateTime = proc getTime*(js: JsonNode): DateTime = parseTime(js.getStr, "ddd MMM dd hh:mm:ss \'+0000\' yyyy", 30) -proc getId*(id: string): string {.inline.} = +proc getTimeFromMs*(js: JsonNode): DateTime = + let ms = js.getInt(0) + if ms == 0: return + let seconds = ms div 1000 + return fromUnix(seconds).utc() + +proc getId*(id: string): int64 {.inline.} = let start = id.rfind("-") - if start < 0: return id - id[start + 1 ..< id.len] + if start < 0: + return parseBiggestInt(id) + return parseBiggestInt(id[start + 1 ..< id.len]) proc getId*(js: JsonNode): int64 {.inline.} = case js.kind - of JString: return parseBiggestInt(js.getStr("0")) + of JString: return js.getStr("0").getId of JInt: return js.getBiggestInt() else: return 0 -proc getEntryId*(js: JsonNode): string {.inline.} = - let entry = js{"entryId"}.getStr - if entry.len == 0: return - - if "tweet" in entry or "sq-I-t" in entry: - return entry.getId - elif "tombstone" in entry: - return js{"content", "item", "content", "tombstone", "tweet", "id"}.getStr - else: - echo "unknown entry: ", entry - return - template getStrVal*(js: JsonNode; default=""): string = js{"string_value"}.getStr(default) @@ -157,12 +172,6 @@ proc getMp4Resolution*(url: string): int = # cannot determine resolution (e.g. m3u8/non-mp4 video) return 0 -proc getVideoViewCount*(js: JsonNode): string = - with stats, js{"ext_media_stats"}: - return stats{"view_count"}.getStr($stats{"viewCount"}.getInt) - - return $js{"mediaStats", "viewCount"}.getInt(0) - proc extractSlice(js: JsonNode): Slice[int] = result = js["indices"][0].getInt ..< js["indices"][1].getInt diff --git a/src/types.nim b/src/types.nim index 5a08bb7..f16fe6f 100644 --- a/src/types.nim +++ b/src/types.nim @@ -121,7 +121,6 @@ type durationMs*: int url*: string thumb*: string - views*: string available*: bool reason*: string title*: string @@ -202,7 +201,6 @@ type replies*: int retweets*: int likes*: int - quotes*: int views*: int Tweet* = ref object diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 6d76755..8ff8cb1 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -178,16 +178,12 @@ func formatStat(stat: int): string = if stat > 0: insertSep($stat, ',') else: "" -proc renderStats(stats: TweetStats; views: string): VNode = +proc renderStats(stats: TweetStats): VNode = buildHtml(tdiv(class="tweet-stats")): span(class="tweet-stat"): icon "comment", formatStat(stats.replies) span(class="tweet-stat"): icon "retweet", formatStat(stats.retweets) - span(class="tweet-stat"): icon "quote", formatStat(stats.quotes) span(class="tweet-stat"): icon "heart", formatStat(stats.likes) - if stats.views > 0: - span(class="tweet-stat"): icon "views", formatStat(stats.views) - if views.len > 0: - span(class="tweet-stat"): icon "play", insertSep(views, ',') + span(class="tweet-stat"): icon "views", formatStat(stats.views) proc renderReply(tweet: Tweet): VNode = buildHtml(tdiv(class="replying-to")): @@ -303,7 +299,6 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; a(class="tweet-link", href=getLink(tweet)) tdiv(class="tweet-body"): - var views = "" renderHeader(tweet, retweet, pinned, prefs) if not afterTweet and index == 0 and tweet.reply.len > 0 and @@ -327,10 +322,8 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; renderAlbum(tweet) elif tweet.video.isSome: renderVideo(tweet.video.get(), prefs, path) - views = tweet.video.get().views elif tweet.gif.isSome: renderGif(tweet.gif.get(), prefs) - views = "GIF" if tweet.poll.isSome: renderPoll(tweet.poll.get()) @@ -345,7 +338,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; renderMediaTags(tweet.mediaTags) if not prefs.hideTweetStats: - renderStats(tweet.stats, views) + renderStats(tweet.stats) if showThread: a(class="show-thread", href=("/i/status/" & $tweet.threadId)): From f8a17fdaa5d88ae425be3cf24a56fcc364d35a11 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 23 Nov 2025 17:28:11 +0100 Subject: [PATCH 141/302] Remove Nim 1.6.x support Fixes #1311 --- .github/workflows/run-tests.yml | 2 +- nitter.nimble | 2 +- src/parserutils.nim | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 46e4ace..f4639a4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -23,7 +23,7 @@ jobs: runs-on: buildjet-2vcpu-ubuntu-2204 strategy: matrix: - nim: ["1.6.x", "2.0.x", "2.2.x", "devel"] + nim: ["2.0.x", "2.2.x", "devel"] steps: - name: Checkout Code uses: actions/checkout@v4 diff --git a/nitter.nimble b/nitter.nimble index 37f9229..7ff8196 100644 --- a/nitter.nimble +++ b/nitter.nimble @@ -10,7 +10,7 @@ bin = @["nitter"] # Dependencies -requires "nim >= 1.6.10" +requires "nim >= 2.0.0" requires "jester#baca3f" requires "karax#5cf360c" requires "sass#7dfdd03" diff --git a/src/parserutils.nim b/src/parserutils.nim index f40082c..72c50e1 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -50,8 +50,7 @@ template with*(ident, value, body): untyped = template with*(ident; value: JsonNode; body): untyped = if true: let ident {.inject.} = value - # value.notNull causes a compilation error for versions < 1.6.14 - if notNull(value): body + if value.notNull: body template getCursor*(js: JsonNode): string = js{"content", "operation", "cursor", "value"}.getStr From 5b4a3fe691ce7a1a8e7cd491b0b37d3c95a621ce Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 23 Nov 2025 19:26:48 +0100 Subject: [PATCH 142/302] Redirect /i/status/id/history to /i/status/id Fixes #1231 --- src/routes/status.nim | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/routes/status.nim b/src/routes/status.nim index 7e89220..0168dac 100644 --- a/src/routes/status.nim +++ b/src/routes/status.nim @@ -31,8 +31,6 @@ proc createStatusRouter*(cfg: Config) = resp $renderReplies(replies, prefs, getPath()) let conv = await getTweet(id, getCursor()) - if conv == nil: - echo "nil conv" if conv == nil or conv.tweet == nil or conv.tweet.id == 0: var error = "Tweet not found" @@ -68,7 +66,7 @@ proc createStatusRouter*(cfg: Config) = get "/@name/@s/@id/@m/?@i?": cond @"s" in ["status", "statuses"] - cond @"m" in ["video", "photo"] + cond @"m" in ["video", "photo", "history"] redirect("/$1/status/$2" % [@"name", @"id"]) get "/@name/statuses/@id/?": @@ -76,6 +74,6 @@ proc createStatusRouter*(cfg: Config) = get "/i/web/status/@id": redirect("/i/status/" & @"id") - + get "/@name/thread/@id/?": redirect("/$1/status/$2" % [@"name", @"id"]) From 53edbbc4e9d5cc8177596472894d49ea753a7049 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 23 Nov 2025 19:58:24 +0100 Subject: [PATCH 143/302] Fix broken tweet pagination ("Load more" button) Fixes #1277 --- src/parser.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/src/parser.nim b/src/parser.nim index 700e896..5bf2b0b 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -485,6 +485,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result.before.content.add tweet elif entryId.startsWith("cursor-bottom"): var cursorValue = select( + e{"content", "value"}, e{"content", "content", "value"}, e{"content", "itemContent", "value"} ) From 25df68209472133bf0b9f9abecc725587d9facba Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 144/302] Expose username as HTML attribute Fixes #551 --- src/views/timeline.nim | 2 +- src/views/tweet.nim | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/timeline.nim b/src/views/timeline.nim index abeb6d3..a205c04 100644 --- a/src/views/timeline.nim +++ b/src/views/timeline.nim @@ -56,7 +56,7 @@ proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode = index=i, last=(i == thread.high), showThread=show) proc renderUser(user: User; prefs: Prefs): VNode = - buildHtml(tdiv(class="timeline-item")): + buildHtml(tdiv(class="timeline-item", data-username=user.username)): a(class="tweet-link", href=("/" & user.username)) tdiv(class="tweet-body profile-result"): tdiv(class="tweet-header"): diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 8ff8cb1..552ab89 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -272,7 +272,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; divClass = "thread-last " & class if not tweet.available: - return buildHtml(tdiv(class=divClass & "unavailable timeline-item")): + return buildHtml(tdiv(class=divClass & "unavailable timeline-item", data-username=tweet.user.username)): tdiv(class="unavailable-box"): if tweet.tombstone.len > 0: text tweet.tombstone @@ -294,7 +294,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; tweet = tweet.retweet.get retweet = fullTweet.user.fullname - buildHtml(tdiv(class=("timeline-item " & divClass))): + buildHtml(tdiv(class=("timeline-item " & divClass), data-username=tweet.user.username)): if not mainTweet: a(class="tweet-link", href=getLink(tweet)) From 1657eeb769d79ffe414ec31bd4ce4686e3a3400c Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 145/302] Fix canonical link causing redirects to Twitter Fixes #526 --- src/formatters.nim | 2 +- src/views/general.nim | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/formatters.nim b/src/formatters.nim index cafaa4f..7bbbe8b 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -6,7 +6,7 @@ import types, utils, query const cards = "cards.twitter.com/cards" tco = "https://t.co" - twitter = parseUri("https://twitter.com") + twitter = parseUri("https://x.com") let twRegex = re"(?<=(?<!\S)https:\/\/|(?<=\s))(www\.|mobile\.)?twitter\.com" diff --git a/src/views/general.nim b/src/views/general.nim index 0091c74..faf4c1d 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -31,14 +31,14 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode = icon "search", title="Search", href="/search" if cfg.enableRss and rss.len > 0: icon "rss", title="RSS Feed", href=rss - icon "bird", title="Open in Twitter", href=canonical + icon "bird", title="Open in X", href=canonical a(href="https://liberapay.com/zedeus"): verbatim lp icon "info", title="About", href="/about" icon "cog", title="Preferences", href=("/settings?referer=" & encodeUrl(path)) proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; video=""; images: seq[string] = @[]; banner=""; ogTitle=""; - rss=""; canonical=""): VNode = + rss=""; alternate=""): VNode = var theme = prefs.theme.toTheme if "theme" in req.params: theme = req.params["theme"].toTheme @@ -66,8 +66,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; link(rel="search", type="application/opensearchdescription+xml", title=cfg.title, href=opensearchUrl) - if canonical.len > 0: - link(rel="canonical", href=canonical) + if alternate.len > 0: + link(rel="alternate", href=alternate, title="View on X") if cfg.enableRss and rss.len > 0: link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed") @@ -125,14 +125,14 @@ proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs; titleText=""; desc=""; ogTitle=""; rss=""; video=""; images: seq[string] = @[]; banner=""): string = - let canonical = getTwitterLink(req.path, req.params) + let twitterLink = getTwitterLink(req.path, req.params) let node = buildHtml(html(lang="en")): renderHead(prefs, cfg, req, titleText, desc, video, images, banner, ogTitle, - rss, canonical) + rss, twitterLink) body: - renderNavbar(cfg, req, rss, canonical) + renderNavbar(cfg, req, rss, twitterLink) tdiv(class="container"): body From d47eb8f0eb6ba34055ba80e1537e7b415107a7af Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 146/302] Fix double slashes in url replacements Fixes #520 --- src/formatters.nim | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/formatters.nim b/src/formatters.nim index 7bbbe8b..e491928 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -59,25 +59,28 @@ proc replaceUrls*(body: string; prefs: Prefs; absolute=""): string = result = body if prefs.replaceYouTube.len > 0 and "youtu" in result: - result = result.replace(ytRegex, prefs.replaceYouTube) + let youtubeHost = strip(prefs.replaceYouTube, chars={'/'}) + result = result.replace(ytRegex, youtubeHost) if prefs.replaceTwitter.len > 0: + let twitterHost = strip(prefs.replaceTwitter, chars={'/'}) if tco in result: - result = result.replace(tco, https & prefs.replaceTwitter & "/t.co") + result = result.replace(tco, https & twitterHost & "/t.co") if "x.com" in result: - result = result.replace(xRegex, prefs.replaceTwitter) + result = result.replace(xRegex, twitterHost) result = result.replacef(xLinkRegex, a( - prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1")) + twitterHost & "$2", href = https & twitterHost & "$1")) if "twitter.com" in result: - result = result.replace(cards, prefs.replaceTwitter & "/cards") - result = result.replace(twRegex, prefs.replaceTwitter) + result = result.replace(cards, twitterHost & "/cards") + result = result.replace(twRegex, twitterHost) result = result.replacef(twLinkRegex, a( - prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1")) + twitterHost & "$2", href = https & twitterHost & "$1")) if prefs.replaceReddit.len > 0 and ("reddit.com" in result or "redd.it" in result): - result = result.replace(rdShortRegex, prefs.replaceReddit & "/comments/") - result = result.replace(rdRegex, prefs.replaceReddit) - if prefs.replaceReddit in result and "/gallery/" in result: + let redditHost = strip(prefs.replaceReddit, chars={'/'}) + result = result.replace(rdShortRegex, redditHost & "/comments/") + result = result.replace(rdRegex, redditHost) + if redditHost in result and "/gallery/" in result: result = result.replace("/gallery/", "/comments/") if absolute.len > 0 and "href" in result: From 4748311f8dd9de0e3cab7cd00027c333b024dd9b Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 147/302] Fix intent/follow URL redirect Fixes #629 --- src/routes/timeline.nim | 6 ++++++ src/routes/unsupported.nim | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 7a10e91..49c7ce2 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -105,6 +105,12 @@ proc createTimelineRouter*(cfg: Config) = get "/intent/user": respUserId() + get "/intent/follow/?": + let username = request.params.getOrDefault("screen_name") + if username.len == 0: + resp Http400, showError("Missing screen_name parameter", cfg) + redirect("/" & username) + get "/@name/?@tab?/?": cond '.' notin @"name" cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"] diff --git a/src/routes/unsupported.nim b/src/routes/unsupported.nim index 0c085d4..362b36b 100644 --- a/src/routes/unsupported.nim +++ b/src/routes/unsupported.nim @@ -17,7 +17,7 @@ proc createUnsupportedRouter*(cfg: Config) = get "/@name/lists/?": feature() get "/intent/?@i?": - cond @"i" notin ["user"] + cond @"i" notin ["user", "follow"] feature() get "/i/@i?/?@j?": From f038b53fa2790866742dc1d6ca511950ed8bf276 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 148/302] Fix body font size to match x.com Fixes #711 --- src/sass/index.scss | 2 +- src/views/general.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sass/index.scss b/src/sass/index.scss index 6cab48e..36a8a93 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -51,7 +51,7 @@ body { background-color: var(--bg_color); color: var(--fg_color); font-family: $font_0, $font_1, $font_2, $font_3; - font-size: 14px; + font-size: 15px; line-height: 1.3; margin: 0; } diff --git a/src/views/general.nim b/src/views/general.nim index faf4c1d..d431e98 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -52,7 +52,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; let opensearchUrl = getUrlPrefix(cfg) & "/opensearch" buildHtml(head): - link(rel="stylesheet", type="text/css", href="/css/style.css?v=19") + link(rel="stylesheet", type="text/css", href="/css/style.css?v=20") link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=3") if theme.len > 0: From 4979d07f2ed9ee15238fd29d52acca42710a5333 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 149/302] Add spaces filter, remove broken filters --- src/query.nim | 5 ++--- src/views/search.nim | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/query.nim b/src/query.nim index 06e1da2..b6ff5df 100644 --- a/src/query.nim +++ b/src/query.nim @@ -6,10 +6,9 @@ import types const validFilters* = @[ "media", "images", "twimg", "videos", - "native_video", "consumer_video", "pro_video", + "native_video", "consumer_video", "spaces", "links", "news", "quote", "mentions", - "replies", "retweets", "nativeretweets", - "verified", "safe" + "replies", "retweets", "nativeretweets" ] emptyQuery* = "include:nativeretweets" diff --git a/src/views/search.nim b/src/views/search.nim index 9f7fc95..35af526 100644 --- a/src/views/search.nim +++ b/src/views/search.nim @@ -10,14 +10,12 @@ const toggles = { "media": "Media", "videos": "Videos", "news": "News", - "verified": "Verified", "native_video": "Native videos", "replies": "Replies", "links": "Links", "images": "Images", - "safe": "Safe", "quote": "Quotes", - "pro_video": "Pro videos" + "spaces": "Spaces" }.toOrderedTable proc renderSearch*(): VNode = From 12bbddf204e805885e0760b93120f90560b8354b Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 150/302] Update search panel grid layout and animation --- src/sass/include/_mixins.css | 13 +------------ src/sass/search.scss | 15 +++++++-------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/sass/include/_mixins.css b/src/sass/include/_mixins.css index 94e11ee..5fde51a 100644 --- a/src/sass/include/_mixins.css +++ b/src/sass/include/_mixins.css @@ -66,18 +66,7 @@ } #search-panel-toggle:checked ~ .search-panel { - @if $rows == 6 { - max-height: 200px !important; - } - @if $rows == 5 { - max-height: 300px !important; - } - @if $rows == 4 { - max-height: 300px !important; - } - @if $rows == 3 { - max-height: 365px !important; - } + max-height: 380px !important; } } } diff --git a/src/sass/search.scss b/src/sass/search.scss index f70f7ea..db0bc66 100644 --- a/src/sass/search.scss +++ b/src/sass/search.scss @@ -42,7 +42,7 @@ @include input-colors; } - @include create-toggle(search-panel, 200px); + @include create-toggle(search-panel, 380px); } .search-panel { @@ -104,19 +104,18 @@ .search-toggles { flex-grow: 1; display: grid; - grid-template-columns: repeat(6, auto); + grid-template-columns: repeat(5, auto); grid-column-gap: 10px; } .profile-tabs { @include search-resize(820px, 5); - @include search-resize(725px, 4); - @include search-resize(600px, 6); - @include search-resize(560px, 5); - @include search-resize(480px, 4); + @include search-resize(715px, 4); + @include search-resize(700px, 5); + @include search-resize(485px, 4); @include search-resize(410px, 3); } -@include search-resize(560px, 5); -@include search-resize(480px, 4); +@include search-resize(700px, 5); +@include search-resize(485px, 4); @include search-resize(410px, 3); From 78101df2cc22e30158bd77d18cc8267ca42834f2 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Mon, 24 Nov 2025 23:04:25 +0100 Subject: [PATCH 151/302] Style number input field --- src/query.nim | 15 ++++++++++----- src/sass/inputs.scss | 22 ++++++++++++++++++++-- src/sass/search.scss | 3 ++- src/types.nim | 2 +- src/views/general.nim | 2 +- src/views/renderutils.nim | 7 +++++++ src/views/search.nim | 6 +++--- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/query.nim b/src/query.nim index b6ff5df..c77bf5f 100644 --- a/src/query.nim +++ b/src/query.nim @@ -17,6 +17,11 @@ template `@`(param: string): untyped = if param in pms: pms[param] else: "" +proc validateNumber(value: string): string = + if value.anyIt(not it.isDigit): + return "" + return value + proc initQuery*(pms: Table[string, string]; name=""): Query = result = Query( kind: parseEnum[QueryKind](@"f", tweets), @@ -25,7 +30,7 @@ proc initQuery*(pms: Table[string, string]; name=""): Query = excludes: validFilters.filterIt("e-" & it in pms), since: @"since", until: @"until", - near: @"near" + minLikes: validateNumber(@"min_faves") ) if name.len > 0: @@ -77,8 +82,8 @@ proc genQueryParam*(query: Query): string = result &= " since:" & query.since if query.until.len > 0: result &= " until:" & query.until - if query.near.len > 0: - result &= &" near:\"{query.near}\" within:15mi" + if query.minLikes.len > 0: + result &= " min_faves:" & query.minLikes if query.text.len > 0: if result.len > 0: result &= " " & query.text @@ -102,8 +107,8 @@ proc genQueryUrl*(query: Query): string = params.add "since=" & query.since if query.until.len > 0: params.add "until=" & query.until - if query.near.len > 0: - params.add "near=" & query.near + if query.minLikes.len > 0: + params.add "min_faves=" & query.minLikes if params.len > 0: result &= params.join("&") diff --git a/src/sass/inputs.scss b/src/sass/inputs.scss index 17c2a22..d6cbb1d 100644 --- a/src/sass/inputs.scss +++ b/src/sass/inputs.scss @@ -14,6 +14,7 @@ button { input[type="text"], input[type="date"], +input[type="number"], select { @include input-colors; background-color: var(--bg_elements); @@ -24,7 +25,12 @@ select { font-size: 14px; } -input[type="text"] { +input[type="number"] { + -moz-appearance: textfield; +} + +input[type="text"], +input[type="number"] { height: 16px; } @@ -38,6 +44,17 @@ input[type="date"]::-webkit-inner-spin-button { display: none; } +input[type="number"] { + -moz-appearance: textfield; +} + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; +} + input[type="date"]::-webkit-clear-button { margin-left: 17px; filter: grayscale(100%); @@ -164,7 +181,8 @@ input::-webkit-datetime-edit-year-field:focus { appearance: none; } - input[type="text"] { + input[type="text"], + input[type="number"] { position: absolute; right: 0; max-width: 140px; diff --git a/src/sass/search.scss b/src/sass/search.scss index db0bc66..234d677 100644 --- a/src/sass/search.scss +++ b/src/sass/search.scss @@ -24,7 +24,8 @@ height: 23px; } - input[type="text"] { + input[type="text"], + input[type="number"] { height: calc(100% - 4px); width: calc(100% - 8px); } diff --git a/src/types.nim b/src/types.nim index f16fe6f..20a49c9 100644 --- a/src/types.nim +++ b/src/types.nim @@ -140,7 +140,7 @@ type fromUser*: seq[string] since*: string until*: string - near*: string + minLikes*: string sep*: string Gif* = object diff --git a/src/views/general.nim b/src/views/general.nim index d431e98..23681b5 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -52,7 +52,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; let opensearchUrl = getUrlPrefix(cfg) & "/opensearch" buildHtml(head): - link(rel="stylesheet", type="text/css", href="/css/style.css?v=20") + link(rel="stylesheet", type="text/css", href="/css/style.css?v=21") link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=3") if theme.len > 0: diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index 41ef8df..fcdf06f 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -89,6 +89,13 @@ proc genDate*(pref, state: string): VNode = input(name=pref, `type`="date", value=state) icon "calendar" +proc genNumberInput*(pref, label, state, placeholder: string; class=""; autofocus=true; min="0"): VNode = + let p = placeholder + buildHtml(tdiv(class=("pref-group pref-input " & class))): + if label.len > 0: + label(`for`=pref): text label + input(name=pref, `type`="number", placeholder=p, value=state, autofocus=(autofocus and state.len == 0), min=min, step="1") + proc genImg*(url: string; class=""): VNode = buildHtml(): img(src=getPicUrl(url), class=class, alt="", loading="lazy") diff --git a/src/views/search.nim b/src/views/search.nim index 35af526..a43008f 100644 --- a/src/views/search.nim +++ b/src/views/search.nim @@ -51,7 +51,7 @@ proc renderSearchTabs*(query: Query): VNode = proc isPanelOpen(q: Query): bool = q.fromUser.len == 0 and (q.filters.len > 0 or q.excludes.len > 0 or - @[q.near, q.until, q.since].anyIt(it.len > 0)) + @[q.minLikes, q.until, q.since].anyIt(it.len > 0)) proc renderSearchPanel*(query: Query): VNode = let user = query.fromUser.join(",") @@ -83,8 +83,8 @@ proc renderSearchPanel*(query: Query): VNode = span(class="search-title"): text "-" genDate("until", query.until) tdiv: - span(class="search-title"): text "Near" - genInput("near", "", query.near, "Location...", autofocus=false) + span(class="search-title"): text "Minimum likes" + genNumberInput("min_faves", "", query.minLikes, "Number...", autofocus=false) proc renderTweetSearch*(results: Timeline; prefs: Prefs; path: string; pinned=none(Tweet)): VNode = From 2b922c049af946253df17a40f78d7e6027fdea0d Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 25 Nov 2025 01:02:45 +0100 Subject: [PATCH 152/302] Embed quote tweet in RSS (#1316) Fixes #132 Closes #820 --- src/views/rss.nimf | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/views/rss.nimf b/src/views/rss.nimf index 819f99c..a6f069c 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -25,7 +25,7 @@ #end proc # #proc getDescription(desc: string; cfg: Config): string = -Twitter feed for: ${desc}. Generated by ${cfg.hostname} +Twitter feed for: ${desc}. Generated by ${getUrlPrefix(cfg)} #end proc # #proc getTweetsWithPinned(profile: Profile): seq[Tweets] = @@ -51,10 +51,6 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} #let urlPrefix = getUrlPrefix(cfg) #let text = replaceUrls(tweet.text, defaultPrefs, absolute=urlPrefix) <p>${text.replace("\n", "<br>\n")}</p> -#if tweet.quote.isSome and get(tweet.quote).available: -# let quoteLink = getLink(get(tweet.quote)) -<p><a href="${urlPrefix}${quoteLink}">${cfg.hostname}${quoteLink}</a></p> -#end if #if tweet.photos.len > 0: # for photo in tweet.photos: <img src="${urlPrefix}${getPicUrl(photo)}" style="max-width:250px;" /> @@ -72,6 +68,20 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname} <img src="${urlPrefix}${getPicUrl(card.image)}" style="max-width:250px;" /> # end if #end if +#if tweet.quote.isSome and get(tweet.quote).available: +# let quoteTweet = get(tweet.quote) +# let quoteLink = urlPrefix & getLink(quoteTweet) +<hr/> +<blockquote> +<b>${quoteTweet.user.fullname} (@${quoteTweet.user.username})</b> +<p> +${renderRssTweet(quoteTweet, cfg)} +</p> +<footer> +— <cite><a href="${quoteLink}">${quoteLink}</a> +</footer> +</blockquote> +#end if #end proc # #proc renderRssTweets(tweets: seq[Tweets]; cfg: Config; userId=""): string = From 404b06b5f35ae66f7f25bcc62edd5611ff00d77a Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 25 Nov 2025 01:03:45 +0100 Subject: [PATCH 153/302] Include "Video" and link for video tweets in RSS (#1315) Fixes #836 --- src/views/rss.nimf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/rss.nimf b/src/views/rss.nimf index a6f069c..23744e5 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -56,7 +56,10 @@ Twitter feed for: ${desc}. Generated by ${getUrlPrefix(cfg)} <img src="${urlPrefix}${getPicUrl(photo)}" style="max-width:250px;" /> # end for #elif tweet.video.isSome: -<img src="${urlPrefix}${getPicUrl(get(tweet.video).thumb)}" style="max-width:250px;" /> +<a href="${urlPrefix}${tweet.getLink}"> +<br>Video<br> + <img src="${urlPrefix}${getPicUrl(get(tweet.video).thumb)}" style="max-width:250px;" /> +</a> #elif tweet.gif.isSome: # let thumb = &"{urlPrefix}{getPicUrl(get(tweet.gif).thumb)}" # let url = &"{urlPrefix}{getPicUrl(get(tweet.gif).url)}" From b83227aaf5acbd8e8803c85dfd2b8f311a021604 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Tue, 25 Nov 2025 17:23:04 +0100 Subject: [PATCH 154/302] Implement temp fix for cookie sessions Fixes #1319 --- src/api.nim | 47 ++++++++++++++++------------ src/apiutils.nim | 2 +- src/consts.nim | 24 ++++++++------ src/experimental/parser/graphql.nim | 24 +++++++++++--- src/experimental/parser/user.nim | 4 ++- src/experimental/types/graphuser.nim | 13 ++++++-- src/parser.nim | 2 +- 7 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/api.nim b/src/api.nim index ef3a0f9..fb9c516 100644 --- a/src/api.nim +++ b/src/api.nim @@ -13,47 +13,54 @@ proc genParams(variables: string; fieldToggles = ""): seq[(string, string)] = proc mediaUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = userMediaVariables % [id, cursor] - oauthVariables = restIdVariables % [id, cursor] + cookieVars = userMediaVars % [id, cursor] + oauthVars = restIdVars % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserMedia ? genParams(cookieVariables), - oauthUrl: graphUserMediaV2 ? genParams(oauthVariables) + cookieUrl: graphUserMedia ? genParams(cookieVars), + oauthUrl: graphUserMediaV2 ? genParams(oauthVars) ) proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = userTweetsVariables % [id, cursor] - oauthVariables = restIdVariables % [id, cursor] + cookieVars = userTweetsVars % [id, cursor] + oauthVars = restIdVars % [id, cursor] result = SessionAwareUrl( - # cookieUrl: graphUserTweets ? genParams(cookieVariables, fieldToggles), - oauthUrl: graphUserTweetsV2 ? genParams(oauthVariables) + # cookieUrl: graphUserTweets ? genParams(cookieVars, userTweetsFieldToggles), + oauthUrl: graphUserTweetsV2 ? genParams(oauthVars) ) # might change this in the future pending testing result.cookieUrl = result.oauthUrl proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = userTweetsAndRepliesVariables % [id, cursor] - oauthVariables = restIdVariables % [id, cursor] + cookieVars = userTweetsAndRepliesVars % [id, cursor] + oauthVars = restIdVars % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVariables, fieldToggles), - oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVariables) + cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVars, userTweetsFieldToggles), + oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVars) ) proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = tweetDetailVariables % [id, cursor] - oauthVariables = tweetVariables % [id, cursor] + cookieVars = tweetDetailVars % [id, cursor] + oauthVars = tweetVars % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphTweetDetail ? genParams(cookieVariables, tweetDetailFieldToggles), - oauthUrl: graphTweet ? genParams(oauthVariables) + cookieUrl: graphTweetDetail ? genParams(cookieVars, tweetDetailFieldToggles), + oauthUrl: graphTweet ? genParams(oauthVars) + ) + +proc userUrl(username: string): SessionAwareUrl = + let + cookieVars = """{"screen_name":"$1","withGrokTranslatedBio":false}""" % username + oauthVars = """{"screen_name": "$1"}""" % username + result = SessionAwareUrl( + cookieUrl: graphUser ? genParams(cookieVars, tweetDetailFieldToggles), + oauthUrl: graphUserV2 ? genParams(oauthVars) ) proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return - let - url = graphUser ? genParams("""{"screen_name": "$1"}""" % username) - js = await fetchRaw(url, Api.userScreenName) + let js = await fetchRaw(userUrl(username), Api.userScreenName) result = parseGraphUser(js) proc getGraphUserById*(id: string): Future[User] {.async.} = @@ -80,7 +87,7 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - url = graphListTweets ? genParams(restIdVariables % [id, cursor]) + url = graphListTweets ? genParams(restIdVars % [id, cursor]) result = parseGraphTimeline(await fetch(url, Api.listTweets), after).tweets proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = diff --git a/src/apiutils.nim b/src/apiutils.nim index defffd1..94d4e8a 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -50,7 +50,7 @@ proc genHeaders*(session: Session, url: string): HttpHeaders = of SessionKind.oauth: result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) of SessionKind.cookie: - result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" + result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F" result["x-twitter-auth-type"] = "OAuth2Session" result["x-csrf-token"] = session.ct0 result["cookie"] = getCookieHeader(session.authToken, session.ct0) diff --git a/src/consts.nim b/src/consts.nim index 792a519..e55ba0e 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -7,7 +7,8 @@ const gql = parseUri("https://api.x.com") / "graphql" - graphUser* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" + graphUser* = gql / "-oaLodhGbbnzJBACb1kk2Q/UserByScreenName" + graphUserV2* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" graphUserById* = gql / "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery" graphUserTweetsV2* = gql / "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2" graphUserTweetsAndRepliesV2* = gql / "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2" @@ -97,10 +98,14 @@ const "grok_translations_community_note_auto_translation_is_enabled": false, "grok_translations_post_auto_translation_is_enabled": false, "grok_translations_community_note_translation_is_enabled": false, - "grok_translations_timeline_user_bio_auto_translation_is_enabled": false + "grok_translations_timeline_user_bio_auto_translation_is_enabled": false, + "subscriptions_feature_can_gift_premium": false, + "responsive_web_twitter_article_notes_tab_enabled": false, + "subscriptions_verification_info_is_identity_verified_enabled": false, + "hidden_profile_subscriptions_enabled": false }""".replace(" ", "").replace("\n", "") - tweetVariables* = """{ + tweetVars* = """{ "postId": "$1", $2 "includeHasBirdwatchNotes": false, @@ -110,7 +115,7 @@ const "withV2Timeline": true }""".replace(" ", "").replace("\n", "") - tweetDetailVariables* = """{ + tweetDetailVars* = """{ "focalTweetId": "$1", $2 "referrer": "profile", @@ -123,12 +128,12 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - restIdVariables* = """{ + restIdVars* = """{ "rest_id": "$1", $2 "count": 20 }""" - userMediaVariables* = """{ + userMediaVars* = """{ "userId": "$1", $2 "count": 20, "includePromotedContent": false, @@ -137,7 +142,7 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - userTweetsVariables* = """{ + userTweetsVars* = """{ "userId": "$1", $2 "count": 20, "includePromotedContent": false, @@ -145,7 +150,7 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - userTweetsAndRepliesVariables* = """{ + userTweetsAndRepliesVars* = """{ "userId": "$1", $2 "count": 20, "includePromotedContent": false, @@ -153,5 +158,6 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - fieldToggles* = """{"withArticlePlainText":false}""" + userFieldToggles = """{"withPayments":false,"withAuxiliaryUserLabels":true}""" + userTweetsFieldToggles* = """{"withArticlePlainText":false}""" tweetDetailFieldToggles* = """{"withArticleRichContentState":true,"withArticlePlainText":false,"withGrokAnalyze":false,"withDisallowedReplyControls":false}""" diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 045a5d6..65ebc3d 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -1,6 +1,6 @@ import options, strutils import jsony -import user, ../types/[graphuser, graphlistmembers] +import user, utils, ../types/[graphuser, graphlistmembers] from ../../types import User, VerifiedType, Result, Query, QueryKind proc parseUserResult*(userResult: UserResult): User = @@ -15,22 +15,36 @@ proc parseUserResult*(userResult: UserResult): User = result.fullname = userResult.core.name result.userPic = userResult.avatar.imageUrl.replace("_normal", "") + if userResult.privacy.isSome: + result.protected = userResult.privacy.get.protected + + if userResult.location.isSome: + result.location = userResult.location.get.location + + if userResult.core.createdAt.len > 0: + result.joinDate = parseTwitterDate(userResult.core.createdAt) + if userResult.verification.isSome: let v = userResult.verification.get if v.verifiedType != VerifiedType.none: result.verifiedType = v.verifiedType - if userResult.profileBio.isSome: + if userResult.profileBio.isSome and result.bio.len == 0: result.bio = userResult.profileBio.get.description proc parseGraphUser*(json: string): User = if json.len == 0 or json[0] != '{': return - let raw = json.fromJson(GraphUser) - let userResult = raw.data.userResult.result + let + raw = json.fromJson(GraphUser) + userResult = + if raw.data.userResult.isSome: raw.data.userResult.get.result + elif raw.data.user.isSome: raw.data.user.get.result + else: UserResult() - if userResult.unavailableReason.get("") == "Suspended": + if userResult.unavailableReason.get("") == "Suspended" or + userResult.reason.get("") == "Suspended": return User(suspended: true) result = parseUserResult(userResult) diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 498757a..8517bdc 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -58,11 +58,13 @@ proc toUser*(raw: RawUser): User = media: raw.mediaCount, verifiedType: raw.verifiedType, protected: raw.protected, - joinDate: parseTwitterDate(raw.createdAt), banner: getBanner(raw), userPic: getImageUrl(raw.profileImageUrlHttps).replace("_normal", "") ) + if raw.createdAt.len > 0: + result.joinDate = parseTwitterDate(raw.createdAt) + if raw.pinnedTweetIdsStr.len > 0: result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0]) diff --git a/src/experimental/types/graphuser.nim b/src/experimental/types/graphuser.nim index d732b4e..62c6612 100644 --- a/src/experimental/types/graphuser.nim +++ b/src/experimental/types/graphuser.nim @@ -3,7 +3,7 @@ from ../../types import User, VerifiedType type GraphUser* = object - data*: tuple[userResult: UserData] + data*: tuple[userResult: Option[UserData], user: Option[UserData]] UserData* = object result*: UserResult @@ -22,15 +22,24 @@ type Verification* = object verifiedType*: VerifiedType + Location* = object + location*: string + + Privacy* = object + protected*: bool + UserResult* = object legacy*: User restId*: string isBlueVerified*: bool - unavailableReason*: Option[string] core*: UserCore avatar*: UserAvatar + unavailableReason*: Option[string] + reason*: Option[string] + privacy*: Option[Privacy] profileBio*: Option[UserBio] verification*: Option[Verification] + location*: Option[Location] proc enumHook*(s: string; v: var VerifiedType) = v = try: diff --git a/src/parser.nim b/src/parser.nim index 5bf2b0b..7b1e1c0 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -21,7 +21,7 @@ proc parseUser(js: JsonNode; id=""): User = tweets: js{"statuses_count"}.getInt, likes: js{"favourites_count"}.getInt, media: js{"media_count"}.getInt, - protected: js{"protected"}.getBool, + protected: js{"protected"}.getBool(js{"privacy", "protected"}.getBool), joinDate: js{"created_at"}.getTime ) From 8516ebe2b7ac772f4f33ee141f2e03fdf4d21467 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 00:36:44 +0100 Subject: [PATCH 155/302] Fix 'key not found in object: expanded_url' error Fixes #1318 --- src/parser.nim | 4 ++-- src/parserutils.nim | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/parser.nim b/src/parser.nim index 7b1e1c0..9b4dc2f 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -184,7 +184,7 @@ proc parseMediaEntities(js: JsonNode; result: var Tweet) = # Remove media URLs from text with mediaList, js{"legacy", "entities", "media"}: for url in mediaList: - let expandedUrl = url{"expanded_url"}.getStr + let expandedUrl = url.getExpandedUrl if result.text.endsWith(expandedUrl): result.text.removeSuffix(expandedUrl) result.text = result.text.strip() @@ -267,7 +267,7 @@ proc parseCard(js: JsonNode; urls: JsonNode): Card = for u in ? urls: if u{"url"}.getStr == result.url: - result.url = u{"expanded_url"}.getStr + result.url = u.getExpandedUrl(result.url) break if kind in {videoDirectMessage, imageDirectMessage}: diff --git a/src/parserutils.nim b/src/parserutils.nim index 72c50e1..b6ccd52 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -112,6 +112,9 @@ proc getImageStr*(js: JsonNode): string = template getImageVal*(js: JsonNode): string = js{"image_value", "url"}.getImageStr +template getExpandedUrl*(js: JsonNode; fallback=""): string = + js{"expanded_url"}.getStr(js{"url"}.getStr(fallback)) + proc getCardUrl*(js: JsonNode; kind: CardKind): string = result = js{"website_url"}.getStrVal if kind == promoVideoConvo: @@ -177,7 +180,7 @@ proc extractSlice(js: JsonNode): Slice[int] = proc extractUrls(result: var seq[ReplaceSlice]; js: JsonNode; textLen: int; hideTwitter = false) = let - url = js["expanded_url"].getStr + url = js.getExpandedUrl slice = js.extractSlice if hideTwitter and slice.b.succ >= textLen and url.isTwitterUrl: @@ -238,7 +241,7 @@ proc expandUserEntities*(user: var User; js: JsonNode) = ent = ? js{"entities"} with urls, ent{"url", "urls"}: - user.website = urls[0]{"expanded_url"}.getStr + user.website = urls[0].getExpandedUrl var replacements = newSeq[ReplaceSlice]() @@ -268,7 +271,7 @@ proc expandTextEntities(tweet: Tweet; entities: JsonNode; text: string; textSlic replacements.extractUrls(u, textSlice.b, hideTwitter = hasRedundantLink) if hasCard and u{"url"}.getStr == get(tweet.card).url: - get(tweet.card).url = u{"expanded_url"}.getStr + get(tweet.card).url = u.getExpandedUrl with media, entities{"media"}: for m in media: From dae68b4f13c773ca0394e30dc87d7b001a74431f Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 01:05:57 +0100 Subject: [PATCH 156/302] Ignore null errors, they're internal API errors --- src/apiutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index 94d4e8a..f48cda2 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -8,7 +8,7 @@ const rlRemaining = "x-rate-limit-remaining" rlReset = "x-rate-limit-reset" rlLimit = "x-rate-limit-limit" - errorsToSkip = {doesntExist, tweetNotFound, timeout, unauthorized, badRequest} + errorsToSkip = {null, doesntExist, tweetNotFound, timeout, unauthorized, badRequest} var pool: HttpPool From 31d210ca47f09a756d761699bd41a35cabb4c3ab Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 01:13:08 +0100 Subject: [PATCH 157/302] Add experimental x-client-transaction-id support (#1324) * Add experimental x-client-transaction-id support * Remove broken test --- nitter.example.conf | 1 + src/api.nim | 131 ++++++++++++++++---------------- src/apiutils.nim | 74 ++++++++++-------- src/auth.nim | 32 +++++--- src/config.nim | 3 +- src/consts.nim | 40 +++++----- src/experimental/parser/tid.nim | 8 ++ src/experimental/types/tid.nim | 4 + src/nitter.nim | 3 +- src/tid.nim | 62 +++++++++++++++ src/types.nim | 29 +++---- tests/test_card.py | 7 +- tools/create_session_browser.py | 2 +- 13 files changed, 239 insertions(+), 157 deletions(-) create mode 100644 src/experimental/parser/tid.nim create mode 100644 src/experimental/types/tid.nim create mode 100644 src/tid.nim diff --git a/nitter.example.conf b/nitter.example.conf index bddb9a4..dfdaf50 100644 --- a/nitter.example.conf +++ b/nitter.example.conf @@ -26,6 +26,7 @@ enableRSS = true # set this to false to disable RSS feeds enableDebug = false # enable request logs and debug endpoints (/.sessions) proxy = "" # http/https url, SOCKS proxies are not supported proxyAuth = "" +disableTid = false # enable this if cookie-based auth is failing # Change default preferences here, see src/prefs_impl.nim for a complete list [Preferences] diff --git a/src/api.nim b/src/api.nim index fb9c516..e97b4e0 100644 --- a/src/api.nim +++ b/src/api.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import asyncdispatch, httpclient, uri, strutils, sequtils, sugar, tables +import asyncdispatch, httpclient, strutils, sequtils, sugar import packedjson import types, query, formatters, consts, apiutils, parser import experimental/parser as newParser @@ -11,95 +11,91 @@ proc genParams(variables: string; fieldToggles = ""): seq[(string, string)] = if fieldToggles.len > 0: result.add ("fieldToggles", fieldToggles) -proc mediaUrl(id: string; cursor: string): SessionAwareUrl = - let - cookieVars = userMediaVars % [id, cursor] - oauthVars = restIdVars % [id, cursor] - result = SessionAwareUrl( - cookieUrl: graphUserMedia ? genParams(cookieVars), - oauthUrl: graphUserMediaV2 ? genParams(oauthVars) +proc apiUrl(endpoint, variables: string; fieldToggles = ""): ApiUrl = + return ApiUrl(endpoint: endpoint, params: genParams(variables, fieldToggles)) + +proc apiReq(endpoint, variables: string; fieldToggles = ""): ApiReq = + let url = apiUrl(endpoint, variables, fieldToggles) + return ApiReq(cookie: url, oauth: url) + +proc mediaUrl(id: string; cursor: string): ApiReq = + result = ApiReq( + cookie: apiUrl(graphUserMedia, userMediaVars % [id, cursor]), + oauth: apiUrl(graphUserMediaV2, restIdVars % [id, cursor]) ) -proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = - let - cookieVars = userTweetsVars % [id, cursor] - oauthVars = restIdVars % [id, cursor] - result = SessionAwareUrl( - # cookieUrl: graphUserTweets ? genParams(cookieVars, userTweetsFieldToggles), - oauthUrl: graphUserTweetsV2 ? genParams(oauthVars) +proc userTweetsUrl(id: string; cursor: string): ApiReq = + result = ApiReq( + # cookie: apiUrl(graphUserTweets, userTweetsVars % [id, cursor], userTweetsFieldToggles), + oauth: apiUrl(graphUserTweetsV2, restIdVars % [id, cursor]) ) # might change this in the future pending testing - result.cookieUrl = result.oauthUrl + result.cookie = result.oauth -proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl = - let - cookieVars = userTweetsAndRepliesVars % [id, cursor] - oauthVars = restIdVars % [id, cursor] - result = SessionAwareUrl( - cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVars, userTweetsFieldToggles), - oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVars) +proc userTweetsAndRepliesUrl(id: string; cursor: string): ApiReq = + let cookieVars = userTweetsAndRepliesVars % [id, cursor] + result = ApiReq( + cookie: apiUrl(graphUserTweetsAndReplies, cookieVars, userTweetsFieldToggles), + oauth: apiUrl(graphUserTweetsAndRepliesV2, restIdVars % [id, cursor]) ) -proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = - let - cookieVars = tweetDetailVars % [id, cursor] - oauthVars = tweetVars % [id, cursor] - result = SessionAwareUrl( - cookieUrl: graphTweetDetail ? genParams(cookieVars, tweetDetailFieldToggles), - oauthUrl: graphTweet ? genParams(oauthVars) +proc tweetDetailUrl(id: string; cursor: string): ApiReq = + let cookieVars = tweetDetailVars % [id, cursor] + result = ApiReq( + cookie: apiUrl(graphTweetDetail, cookieVars, tweetDetailFieldToggles), + oauth: apiUrl(graphTweet, tweetVars % [id, cursor]) ) -proc userUrl(username: string): SessionAwareUrl = - let - cookieVars = """{"screen_name":"$1","withGrokTranslatedBio":false}""" % username - oauthVars = """{"screen_name": "$1"}""" % username - result = SessionAwareUrl( - cookieUrl: graphUser ? genParams(cookieVars, tweetDetailFieldToggles), - oauthUrl: graphUserV2 ? genParams(oauthVars) +proc userUrl(username: string): ApiReq = + let cookieVars = """{"screen_name":"$1","withGrokTranslatedBio":false}""" % username + result = ApiReq( + cookie: apiUrl(graphUser, cookieVars, tweetDetailFieldToggles), + oauth: apiUrl(graphUserV2, """{"screen_name": "$1"}""" % username) ) proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return - let js = await fetchRaw(userUrl(username), Api.userScreenName) + let js = await fetchRaw(userUrl(username)) result = parseGraphUser(js) proc getGraphUserById*(id: string): Future[User] {.async.} = if id.len == 0 or id.any(c => not c.isDigit): return let - url = graphUserById ? genParams("""{"rest_id": "$1"}""" % id) - js = await fetchRaw(url, Api.userRestId) + url = apiReq(graphUserById, """{"rest_id": "$1"}""" % id) + js = await fetchRaw(url) result = parseGraphUser(js) proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profile] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - js = case kind - of TimelineKind.tweets: - await fetch(userTweetsUrl(id, cursor), Api.userTweets) - of TimelineKind.replies: - await fetch(userTweetsAndRepliesUrl(id, cursor), Api.userTweetsAndReplies) - of TimelineKind.media: - await fetch(mediaUrl(id, cursor), Api.userMedia) + url = case kind + of TimelineKind.tweets: userTweetsUrl(id, cursor) + of TimelineKind.replies: userTweetsAndRepliesUrl(id, cursor) + of TimelineKind.media: mediaUrl(id, cursor) + js = await fetch(url) result = parseGraphTimeline(js, after) proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - url = graphListTweets ? genParams(restIdVars % [id, cursor]) - result = parseGraphTimeline(await fetch(url, Api.listTweets), after).tweets + url = apiReq(graphListTweets, restIdVars % [id, cursor]) + js = await fetch(url) + result = parseGraphTimeline(js, after).tweets proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = let variables = %*{"screenName": name, "listSlug": list} - url = graphListBySlug ? genParams($variables) - result = parseGraphList(await fetch(url, Api.listBySlug)) + url = apiReq(graphListBySlug, $variables) + js = await fetch(url) + result = parseGraphList(js) proc getGraphList*(id: string): Future[List] {.async.} = - let - url = graphListById ? genParams("""{"listId": "$1"}""" % id) - result = parseGraphList(await fetch(url, Api.list)) + let + url = apiReq(graphListById, """{"listId": "$1"}""" % id) + js = await fetch(url) + result = parseGraphList(js) proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} = if list.id.len == 0: return @@ -113,22 +109,23 @@ proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.} } if after.len > 0: variables["cursor"] = % after - let url = graphListMembers ? genParams($variables) - result = parseGraphListMembers(await fetchRaw(url, Api.listMembers), after) + let + url = apiReq(graphListMembers, $variables) + js = await fetchRaw(url) + result = parseGraphListMembers(js, after) proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} = if id.len == 0: return let - variables = """{"rest_id": "$1"}""" % id - params = {"variables": variables, "features": gqlFeatures} - js = await fetch(graphTweetResult ? params, Api.tweetResult) + url = apiReq(graphTweetResult, """{"rest_id": "$1"}""" % id) + js = await fetch(url) result = parseGraphTweetResult(js) proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - js = await fetch(tweetDetailUrl(id, cursor), Api.tweetDetail) + js = await fetch(tweetDetailUrl(id, cursor)) result = parseGraphConversation(js, id) proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} = @@ -157,8 +154,10 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = } if after.len > 0: variables["cursor"] = % after - let url = graphSearchTimeline ? genParams($variables) - result = parseGraphSearch[Tweets](await fetch(url, Api.search), after) + let + url = apiReq(graphSearchTimeline, $variables) + js = await fetch(url) + result = parseGraphSearch[Tweets](js, after) result.query = query proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} = @@ -179,13 +178,15 @@ proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} variables["cursor"] = % after result.beginning = false - let url = graphSearchTimeline ? genParams($variables) - result = parseGraphSearch[User](await fetch(url, Api.search), after) + let + url = apiReq(graphSearchTimeline, $variables) + js = await fetch(url) + result = parseGraphSearch[User](js, after) result.query = query proc getPhotoRail*(id: string): Future[PhotoRail] {.async.} = if id.len == 0: return - let js = await fetch(mediaUrl(id, ""), Api.userMedia) + let js = await fetch(mediaUrl(id, "")) result = parseGraphPhotoRail(js) proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} = diff --git a/src/apiutils.nim b/src/apiutils.nim index f48cda2..b288141 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only import httpclient, asyncdispatch, options, strutils, uri, times, math, tables import jsony, packedjson, zippy, oauth1 -import types, auth, consts, parserutils, http_pool +import types, auth, consts, parserutils, http_pool, tid import experimental/types/common const @@ -10,7 +10,21 @@ const rlLimit = "x-rate-limit-limit" errorsToSkip = {null, doesntExist, tweetNotFound, timeout, unauthorized, badRequest} -var pool: HttpPool +var + pool: HttpPool + disableTid: bool + +proc setDisableTid*(disable: bool) = + disableTid = disable + +proc toUrl(req: ApiReq; sessionKind: SessionKind): Uri = + case sessionKind + of oauth: + let o = req.oauth + parseUri("https://api.x.com/graphql") / o.endpoint ? o.params + of cookie: + let c = req.cookie + parseUri("https://x.com/i/api/graphql") / c.endpoint ? c.params proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = let @@ -32,15 +46,15 @@ proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string = proc getCookieHeader(authToken, ct0: string): string = "auth_token=" & authToken & "; ct0=" & ct0 -proc genHeaders*(session: Session, url: string): HttpHeaders = +proc genHeaders*(session: Session, url: Uri): Future[HttpHeaders] {.async.} = result = newHttpHeaders({ "connection": "keep-alive", "content-type": "application/json", "x-twitter-active-user": "yes", "x-twitter-client-language": "en", - "authority": "api.x.com", + "origin": "https://x.com", "accept-encoding": "gzip", - "accept-language": "en-US,en;q=0.9", + "accept-language": "en-US,en;q=0.5", "accept": "*/*", "DNT": "1", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" @@ -48,15 +62,20 @@ proc genHeaders*(session: Session, url: string): HttpHeaders = case session.kind of SessionKind.oauth: - result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) + result["authority"] = "api.x.com" + result["authorization"] = getOauthHeader($url, session.oauthToken, session.oauthSecret) of SessionKind.cookie: - result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F" result["x-twitter-auth-type"] = "OAuth2Session" result["x-csrf-token"] = session.ct0 result["cookie"] = getCookieHeader(session.authToken, session.ct0) + if disableTid: + result["authorization"] = bearerToken2 + else: + result["authorization"] = bearerToken + result["x-client-transaction-id"] = await genTid(url.path) -proc getAndValidateSession*(api: Api): Future[Session] {.async.} = - result = await getSession(api) +proc getAndValidateSession*(req: ApiReq): Future[Session] {.async.} = + result = await getSession(req) case result.kind of SessionKind.oauth: if result.oauthToken.len == 0: @@ -73,7 +92,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = try: var resp: AsyncResponse - pool.use(genHeaders(session, $url)): + pool.use(await genHeaders(session, url)): template getContent = resp = await c.get($url) result = await resp.body @@ -89,7 +108,7 @@ template fetchImpl(result, fetchBody) {.dirty.} = remaining = parseInt(resp.headers[rlRemaining]) reset = parseInt(resp.headers[rlReset]) limit = parseInt(resp.headers[rlLimit]) - session.setRateLimit(api, remaining, reset, limit) + session.setRateLimit(req, remaining, reset, limit) if result.len > 0: if resp.headers.getOrDefault("content-encoding") == "gzip": @@ -98,24 +117,22 @@ template fetchImpl(result, fetchBody) {.dirty.} = if result.startsWith("{\"errors"): let errors = result.fromJson(Errors) if errors notin errorsToSkip: - echo "Fetch error, API: ", api, ", errors: ", errors + echo "Fetch error, API: ", url.path, ", errors: ", errors if errors in {expiredToken, badToken, locked}: invalidate(session) raise rateLimitError() elif errors in {rateLimited}: # rate limit hit, resets after 24 hours - setLimited(session, api) + setLimited(session, req) raise rateLimitError() elif result.startsWith("429 Too Many Requests"): - echo "[sessions] 429 error, API: ", api, ", session: ", session.pretty - session.apis[api].remaining = 0 - # rate limit hit, resets after the 15 minute window + echo "[sessions] 429 error, API: ", url.path, ", session: ", session.pretty raise rateLimitError() fetchBody if resp.status == $Http400: - echo "ERROR 400, ", api, ": ", result + echo "ERROR 400, ", url.path, ": ", result raise newException(InternalError, $url) except InternalError as e: raise e @@ -134,19 +151,16 @@ template retry(bod) = try: bod except RateLimitError: - echo "[sessions] Rate limited, retrying ", api, " request..." + echo "[sessions] Rate limited, retrying ", req.cookie.endpoint, " request..." bod -proc fetch*(url: Uri | SessionAwareUrl; api: Api): Future[JsonNode] {.async.} = +proc fetch*(req: ApiReq): Future[JsonNode] {.async.} = retry: var body: string - session = await getAndValidateSession(api) + session = await getAndValidateSession(req) - when url is SessionAwareUrl: - let url = case session.kind - of SessionKind.oauth: url.oauthUrl - of SessionKind.cookie: url.cookieUrl + let url = req.toUrl(session.kind) fetchImpl body: if body.startsWith('{') or body.startsWith('['): @@ -157,19 +171,15 @@ proc fetch*(url: Uri | SessionAwareUrl; api: Api): Future[JsonNode] {.async.} = let error = result.getError if error != null and error notin errorsToSkip: - echo "Fetch error, API: ", api, ", error: ", error + echo "Fetch error, API: ", url.path, ", error: ", error if error in {expiredToken, badToken, locked}: invalidate(session) raise rateLimitError() -proc fetchRaw*(url: Uri | SessionAwareUrl; api: Api): Future[string] {.async.} = +proc fetchRaw*(req: ApiReq): Future[string] {.async.} = retry: - var session = await getAndValidateSession(api) - - when url is SessionAwareUrl: - let url = case session.kind - of SessionKind.oauth: url.oauthUrl - of SessionKind.cookie: url.cookieUrl + var session = await getAndValidateSession(req) + let url = req.toUrl(session.kind) fetchImpl result: if not (result.startsWith('{') or result.startsWith('[')): diff --git a/src/auth.nim b/src/auth.nim index 734b43e..5d7ef0e 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -1,6 +1,6 @@ #SPDX-License-Identifier: AGPL-3.0-only -import std/[asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os] -import types +import std/[asyncdispatch, times, json, random, strutils, tables, packedsets, os] +import types, consts import experimental/parser/session # max requests at a time per session to avoid race conditions @@ -15,6 +15,11 @@ var template log(str: varargs[string, `$`]) = echo "[sessions] ", str.join("") +proc endpoint(req: ApiReq; session: Session): string = + case session.kind + of oauth: req.oauth.endpoint + of cookie: req.cookie.endpoint + proc pretty*(session: Session): string = if session.isNil: return "<null>" @@ -122,11 +127,12 @@ proc rateLimitError*(): ref RateLimitError = proc noSessionsError*(): ref NoSessionsError = newException(NoSessionsError, "no sessions available") -proc isLimited(session: Session; api: Api): bool = +proc isLimited(session: Session; req: ApiReq): bool = if session.isNil: return true - if session.limited and api != Api.userTweets: + let api = req.endpoint(session) + if session.limited and api != graphUserTweetsV2: if (epochTime().int - session.limitedAt) > hourInSeconds: session.limited = false log "resetting limit: ", session.pretty @@ -140,8 +146,8 @@ proc isLimited(session: Session; api: Api): bool = else: return false -proc isReady(session: Session; api: Api): bool = - not (session.isNil or session.pending > maxConcurrentReqs or session.isLimited(api)) +proc isReady(session: Session; req: ApiReq): bool = + not (session.isNil or session.pending > maxConcurrentReqs or session.isLimited(req)) proc invalidate*(session: var Session) = if session.isNil: return @@ -156,24 +162,26 @@ proc release*(session: Session) = if session.isNil: return dec session.pending -proc getSession*(api: Api): Future[Session] {.async.} = +proc getSession*(req: ApiReq): Future[Session] {.async.} = for i in 0 ..< sessionPool.len: - if result.isReady(api): break + if result.isReady(req): break result = sessionPool.sample() - if not result.isNil and result.isReady(api): + if not result.isNil and result.isReady(req): inc result.pending else: - log "no sessions available for API: ", api + log "no sessions available for API: ", req.cookie.endpoint raise noSessionsError() -proc setLimited*(session: Session; api: Api) = +proc setLimited*(session: Session; req: ApiReq) = + let api = req.endpoint(session) session.limited = true session.limitedAt = epochTime().int log "rate limited by api: ", api, ", reqs left: ", session.apis[api].remaining, ", ", session.pretty -proc setRateLimit*(session: Session; api: Api; remaining, reset, limit: int) = +proc setRateLimit*(session: Session; req: ApiReq; remaining, reset, limit: int) = # avoid undefined behavior in race conditions + let api = req.endpoint(session) if api in session.apis: let rateLimit = session.apis[api] if rateLimit.reset >= reset and rateLimit.remaining < remaining: diff --git a/src/config.nim b/src/config.nim index 1b05ffe..571508b 100644 --- a/src/config.nim +++ b/src/config.nim @@ -40,7 +40,8 @@ proc getConfig*(path: string): (Config, parseCfg.Config) = enableRss: cfg.get("Config", "enableRSS", true), enableDebug: cfg.get("Config", "enableDebug", false), proxy: cfg.get("Config", "proxy", ""), - proxyAuth: cfg.get("Config", "proxyAuth", "") + proxyAuth: cfg.get("Config", "proxyAuth", ""), + disableTid: cfg.get("Config", "disableTid", false) ) return (conf, cfg) diff --git a/src/consts.nim b/src/consts.nim index e55ba0e..6456efc 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -1,29 +1,29 @@ # SPDX-License-Identifier: AGPL-3.0-only -import uri, strutils +import strutils const consumerKey* = "3nVuSoBZnx6U4vzUxf5w" consumerSecret* = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys" + bearerToken* = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" + bearerToken2* = "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F" - gql = parseUri("https://api.x.com") / "graphql" - - graphUser* = gql / "-oaLodhGbbnzJBACb1kk2Q/UserByScreenName" - graphUserV2* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" - graphUserById* = gql / "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery" - graphUserTweetsV2* = gql / "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2" - graphUserTweetsAndRepliesV2* = gql / "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2" - graphUserTweets* = gql / "oRJs8SLCRNRbQzuZG93_oA/UserTweets" - graphUserTweetsAndReplies* = gql / "kkaJ0Mf34PZVarrxzLihjg/UserTweetsAndReplies" - graphUserMedia* = gql / "36oKqyQ7E_9CmtONGjJRsA/UserMedia" - graphUserMediaV2* = gql / "bp0e_WdXqgNBIwlLukzyYA/MediaTimelineV2" - graphTweet* = gql / "Y4Erk_-0hObvLpz0Iw3bzA/ConversationTimeline" - graphTweetDetail* = gql / "YVyS4SfwYW7Uw5qwy0mQCA/TweetDetail" - graphTweetResult* = gql / "nzme9KiYhfIOrrLrPP_XeQ/TweetResultByIdQuery" - graphSearchTimeline* = gql / "bshMIjqDk8LTXTq4w91WKw/SearchTimeline" - graphListById* = gql / "cIUpT1UjuGgl_oWiY7Snhg/ListByRestId" - graphListBySlug* = gql / "K6wihoTiTrzNzSF8y1aeKQ/ListBySlug" - graphListMembers* = gql / "fuVHh5-gFn8zDBBxb8wOMA/ListMembers" - graphListTweets* = gql / "VQf8_XQynI3WzH6xopOMMQ/ListTimeline" + graphUser* = "-oaLodhGbbnzJBACb1kk2Q/UserByScreenName" + graphUserV2* = "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" + graphUserById* = "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery" + graphUserTweetsV2* = "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2" + graphUserTweetsAndRepliesV2* = "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2" + graphUserTweets* = "oRJs8SLCRNRbQzuZG93_oA/UserTweets" + graphUserTweetsAndReplies* = "kkaJ0Mf34PZVarrxzLihjg/UserTweetsAndReplies" + graphUserMedia* = "36oKqyQ7E_9CmtONGjJRsA/UserMedia" + graphUserMediaV2* = "bp0e_WdXqgNBIwlLukzyYA/MediaTimelineV2" + graphTweet* = "Y4Erk_-0hObvLpz0Iw3bzA/ConversationTimeline" + graphTweetDetail* = "YVyS4SfwYW7Uw5qwy0mQCA/TweetDetail" + graphTweetResult* = "nzme9KiYhfIOrrLrPP_XeQ/TweetResultByIdQuery" + graphSearchTimeline* = "bshMIjqDk8LTXTq4w91WKw/SearchTimeline" + graphListById* = "cIUpT1UjuGgl_oWiY7Snhg/ListByRestId" + graphListBySlug* = "K6wihoTiTrzNzSF8y1aeKQ/ListBySlug" + graphListMembers* = "fuVHh5-gFn8zDBBxb8wOMA/ListMembers" + graphListTweets* = "VQf8_XQynI3WzH6xopOMMQ/ListTimeline" gqlFeatures* = """{ "android_ad_formats_media_component_render_overlay_enabled": false, diff --git a/src/experimental/parser/tid.nim b/src/experimental/parser/tid.nim new file mode 100644 index 0000000..28fccea --- /dev/null +++ b/src/experimental/parser/tid.nim @@ -0,0 +1,8 @@ +import jsony +import ../types/tid +export TidPair + +proc parseTidPairs*(raw: string): seq[TidPair] = + result = raw.fromJson(seq[TidPair]) + if result.len == 0: + raise newException(ValueError, "Parsing pairs failed: " & raw) diff --git a/src/experimental/types/tid.nim b/src/experimental/types/tid.nim new file mode 100644 index 0000000..ad036d9 --- /dev/null +++ b/src/experimental/types/tid.nim @@ -0,0 +1,4 @@ +type + TidPair* = object + animationKey*: string + verification*: string diff --git a/src/nitter.nim b/src/nitter.nim index f81dc1c..e6d66ab 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -6,7 +6,7 @@ from os import getEnv import jester -import types, config, prefs, formatters, redis_cache, http_pool, auth +import types, config, prefs, formatters, redis_cache, http_pool, auth, apiutils import views/[general, about] import routes/[ preferences, timeline, status, media, search, rss, list, debug, @@ -37,6 +37,7 @@ setHmacKey(cfg.hmacKey) setProxyEncoding(cfg.base64Media) setMaxHttpConns(cfg.httpMaxConns) setHttpProxy(cfg.proxy, cfg.proxyAuth) +setDisableTid(cfg.disableTid) initAboutPage(cfg.staticDir) waitFor initRedisPool(cfg) diff --git a/src/tid.nim b/src/tid.nim new file mode 100644 index 0000000..7b453fb --- /dev/null +++ b/src/tid.nim @@ -0,0 +1,62 @@ +import std/[asyncdispatch, base64, httpclient, random, strutils, sequtils, times] +import nimcrypto +import experimental/parser/tid + +randomize() + +const defaultKeyword = "obfiowerehiring"; +const pairsUrl = + "https://raw.githubusercontent.com/fa0311/x-client-transaction-id-pair-dict/refs/heads/main/pair.json"; + +var + cachedPairs: seq[TidPair] = @[] + lastCached = 0 + # refresh every hour + ttlSec = 60 * 60 + +proc getPair(): Future[TidPair] {.async.} = + if cachedPairs.len == 0 or int(epochTime()) - lastCached > ttlSec: + lastCached = int(epochTime()) + + let client = newAsyncHttpClient() + defer: client.close() + + let resp = await client.get(pairsUrl) + if resp.status == $Http200: + cachedPairs = parseTidPairs(await resp.body) + + return sample(cachedPairs) + +proc encodeSha256(text: string): array[32, byte] = + let + data = cast[ptr byte](addr text[0]) + dataLen = uint(len(text)) + digest = sha256.digest(data, dataLen) + return digest.data + +proc encodeBase64[T](data: T): string = + return encode(data).replace("=", "") + +proc decodeBase64(data: string): seq[byte] = + return cast[seq[byte]](decode(data)) + +proc genTid*(path: string): Future[string] {.async.} = + let + pair = await getPair() + + timeNow = int(epochTime() - 1682924400) + timeNowBytes = @[ + byte(timeNow and 0xff), + byte((timeNow shr 8) and 0xff), + byte((timeNow shr 16) and 0xff), + byte((timeNow shr 24) and 0xff) + ] + + data = "GET!" & path & "!" & $timeNow & defaultKeyword & pair.animationKey + hashBytes = encodeSha256(data) + keyBytes = decodeBase64(pair.verification) + bytesArr = keyBytes & timeNowBytes & hashBytes[0 ..< 16] & @[3'u8] + randomNum = byte(rand(256)) + tid = @[randomNum] & bytesArr.mapIt(it xor randomNum) + + return encodeBase64(tid) diff --git a/src/types.nim b/src/types.nim index 20a49c9..815e223 100644 --- a/src/types.nim +++ b/src/types.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import times, sequtils, options, tables, uri +import times, sequtils, options, tables import prefs_impl genPrefsType() @@ -13,19 +13,13 @@ type TimelineKind* {.pure.} = enum tweets, replies, media - Api* {.pure.} = enum - tweetDetail - tweetResult - search - list - listBySlug - listMembers - listTweets - userRestId - userScreenName - userTweets - userTweetsAndReplies - userMedia + ApiUrl* = object + endpoint*: string + params*: seq[(string, string)] + + ApiReq* = object + oauth*: ApiUrl + cookie*: ApiUrl RateLimit* = object limit*: int @@ -42,7 +36,7 @@ type pending*: int limited*: bool limitedAt*: int - apis*: Table[Api, RateLimit] + apis*: Table[string, RateLimit] case kind*: SessionKind of oauth: oauthToken*: string @@ -51,10 +45,6 @@ type authToken*: string ct0*: string - SessionAwareUrl* = object - oauthUrl*: Uri - cookieUrl*: Uri - Error* = enum null = 0 noUserMatches = 17 @@ -285,6 +275,7 @@ type enableDebug*: bool proxy*: string proxyAuth*: string + disableTid*: bool rssCacheTime*: int listCacheTime*: int diff --git a/tests/test_card.py b/tests/test_card.py index 504c079..129e65a 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -11,12 +11,7 @@ card = [ ['voidtarget/status/1094632512926605312', 'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too)', 'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too) - obsplugin.nim', - 'gist.github.com', True], - - ['nim_lang/status/1082989146040340480', - 'Nim in 2018: A short recap', - 'There were several big news in the Nim world in 2018 – two new major releases, partnership with Status, and much more. But let us go chronologically.', - 'nim-lang.org', True] + 'gist.github.com', True] ] no_thumb = [ diff --git a/tools/create_session_browser.py b/tools/create_session_browser.py index 40e3dcd..3a05cb1 100644 --- a/tools/create_session_browser.py +++ b/tools/create_session_browser.py @@ -94,7 +94,7 @@ async def login_and_get_cookies(username, password, totp_seed=None, headless=Fal async def main(): if len(sys.argv) < 3: - print('Usage: python3 twitter-auth.py username password [totp_seed] [--append sessions.jsonl] [--headless]') + print('Usage: python3 create_session_browser.py username password [totp_seed] [--append file.jsonl] [--headless]') sys.exit(1) username = sys.argv[1] From 7a08a9e1321a511a35bee7ca736db035f9fce52a Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 03:36:21 +0100 Subject: [PATCH 158/302] Format css --- src/sass/general.scss | 56 +++--- src/sass/include/_variables.scss | 49 +++-- src/sass/index.scss | 242 ++++++++++++------------ src/sass/inputs.scss | 258 ++++++++++++------------- src/sass/navbar.scss | 121 ++++++------ src/sass/search.scss | 170 ++++++++--------- src/sass/timeline.scss | 208 ++++++++++----------- src/sass/tweet/_base.scss | 310 ++++++++++++++++--------------- src/sass/tweet/embed.scss | 26 +-- src/sass/tweet/media.scss | 146 +++++++-------- src/sass/tweet/poll.scss | 48 ++--- src/sass/tweet/quote.scss | 149 +++++++-------- src/sass/tweet/thread.scss | 195 +++++++++---------- src/sass/tweet/video.scss | 97 +++++----- 14 files changed, 1041 insertions(+), 1034 deletions(-) diff --git a/src/sass/general.scss b/src/sass/general.scss index 9feb3d3..ce97564 100644 --- a/src/sass/general.scss +++ b/src/sass/general.scss @@ -1,39 +1,39 @@ -@import '_variables'; -@import '_mixins'; +@import "_variables"; +@import "_mixins"; .panel-container { - margin: auto; - font-size: 130%; + margin: auto; + font-size: 130%; } .error-panel { - @include center-panel(var(--error_red)); - text-align: center; + @include center-panel(var(--error_red)); + text-align: center; } .search-bar > form { - @include center-panel(var(--darkest_grey)); + @include center-panel(var(--darkest_grey)); - button { - background: var(--bg_elements); - color: var(--fg_color); - border: 0; - border-radius: 3px; - cursor: pointer; - font-weight: bold; - width: 30px; - height: 30px; - } + button { + background: var(--bg_elements); + color: var(--fg_color); + border: 0; + border-radius: 3px; + cursor: pointer; + font-weight: bold; + width: 30px; + height: 30px; + } - input { - font-size: 16px; - width: 100%; - background: var(--bg_elements); - color: var(--fg_color); - border: 0; - border-radius: 4px; - padding: 4px; - margin-right: 8px; - height: unset; - } + input { + font-size: 16px; + width: 100%; + background: var(--bg_elements); + color: var(--fg_color); + border: 0; + border-radius: 4px; + padding: 4px; + margin-right: 8px; + height: unset; + } } diff --git a/src/sass/include/_variables.scss b/src/sass/include/_variables.scss index 0c95ff6..127cccb 100644 --- a/src/sass/include/_variables.scss +++ b/src/sass/include/_variables.scss @@ -1,46 +1,43 @@ // colors -$bg_color: #0F0F0F; -$fg_color: #F8F8F2; -$fg_faded: #F8F8F2CF; -$fg_dark: #FF6C60; -$fg_nav: #FF6C60; +$bg_color: #0f0f0f; +$fg_color: #f8f8f2; +$fg_faded: #f8f8f2cf; +$fg_dark: #ff6c60; +$fg_nav: #ff6c60; $bg_panel: #161616; $bg_elements: #121212; -$bg_overlays: #1F1F1F; -$bg_hover: #1A1A1A; +$bg_overlays: #1f1f1f; +$bg_hover: #1a1a1a; $grey: #888889; $dark_grey: #404040; $darker_grey: #282828; $darkest_grey: #222222; -$border_grey: #3E3E35; +$border_grey: #3e3e35; -$accent: #FF6C60; -$accent_light: #FFACA0; -$accent_dark: #8A3731; -$accent_border: #FF6C6091; +$accent: #ff6c60; +$accent_light: #ffaca0; +$accent_dark: #8a3731; +$accent_border: #ff6c6091; -$play_button: #D8574D; -$play_button_hover: #FF6C60; +$play_button: #d8574d; +$play_button_hover: #ff6c60; -$more_replies_dots: #AD433B; -$error_red: #420A05; +$more_replies_dots: #ad433b; +$error_red: #420a05; -$verified_blue: #1DA1F2; -$verified_business: #FAC82B; -$verified_government: #C1B6A4; +$verified_blue: #1da1f2; +$verified_business: #fac82b; +$verified_government: #c1b6a4; $icon_text: $fg_color; $tab: $fg_color; $tab_selected: $accent; -$shadow: rgba(0,0,0,.6); -$shadow_dark: rgba(0,0,0,.2); +$shadow: rgba(0, 0, 0, 0.6); +$shadow_dark: rgba(0, 0, 0, 0.2); //fonts -$font_0: Helvetica Neue; -$font_1: Helvetica; -$font_2: Arial; -$font_3: sans-serif; -$font_4: fontello; +$font_0: sans-serif; +$font_1: fontello; diff --git a/src/sass/index.scss b/src/sass/index.scss index 36a8a93..a19165a 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -1,180 +1,182 @@ -@import '_variables'; +@import "_variables"; -@import 'tweet/_base'; -@import 'profile/_base'; -@import 'general'; -@import 'navbar'; -@import 'inputs'; -@import 'timeline'; -@import 'search'; +@import "tweet/_base"; +@import "profile/_base"; +@import "general"; +@import "navbar"; +@import "inputs"; +@import "timeline"; +@import "search"; body { - // colors - --bg_color: #{$bg_color}; - --fg_color: #{$fg_color}; - --fg_faded: #{$fg_faded}; - --fg_dark: #{$fg_dark}; - --fg_nav: #{$fg_nav}; + // colors + --bg_color: #{$bg_color}; + --fg_color: #{$fg_color}; + --fg_faded: #{$fg_faded}; + --fg_dark: #{$fg_dark}; + --fg_nav: #{$fg_nav}; - --bg_panel: #{$bg_panel}; - --bg_elements: #{$bg_elements}; - --bg_overlays: #{$bg_overlays}; - --bg_hover: #{$bg_hover}; + --bg_panel: #{$bg_panel}; + --bg_elements: #{$bg_elements}; + --bg_overlays: #{$bg_overlays}; + --bg_hover: #{$bg_hover}; - --grey: #{$grey}; - --dark_grey: #{$dark_grey}; - --darker_grey: #{$darker_grey}; - --darkest_grey: #{$darkest_grey}; - --border_grey: #{$border_grey}; + --grey: #{$grey}; + --dark_grey: #{$dark_grey}; + --darker_grey: #{$darker_grey}; + --darkest_grey: #{$darkest_grey}; + --border_grey: #{$border_grey}; - --accent: #{$accent}; - --accent_light: #{$accent_light}; - --accent_dark: #{$accent_dark}; - --accent_border: #{$accent_border}; + --accent: #{$accent}; + --accent_light: #{$accent_light}; + --accent_dark: #{$accent_dark}; + --accent_border: #{$accent_border}; - --play_button: #{$play_button}; - --play_button_hover: #{$play_button_hover}; + --play_button: #{$play_button}; + --play_button_hover: #{$play_button_hover}; - --more_replies_dots: #{$more_replies_dots}; - --error_red: #{$error_red}; + --more_replies_dots: #{$more_replies_dots}; + --error_red: #{$error_red}; - --verified_blue: #{$verified_blue}; - --verified_business: #{$verified_business}; - --verified_government: #{$verified_government}; - --icon_text: #{$icon_text}; + --verified_blue: #{$verified_blue}; + --verified_business: #{$verified_business}; + --verified_government: #{$verified_government}; + --icon_text: #{$icon_text}; - --tab: #{$fg_color}; - --tab_selected: #{$accent}; + --tab: #{$fg_color}; + --tab_selected: #{$accent}; - --profile_stat: #{$fg_color}; + --profile_stat: #{$fg_color}; - background-color: var(--bg_color); - color: var(--fg_color); - font-family: $font_0, $font_1, $font_2, $font_3; - font-size: 15px; - line-height: 1.3; - margin: 0; + background-color: var(--bg_color); + color: var(--fg_color); + font-family: $font_0, $font_1, $font_2, $font_3; + font-size: 15px; + line-height: 1.3; + margin: 0; } * { - outline: unset; - margin: 0; - text-decoration: none; + outline: unset; + margin: 0; + text-decoration: none; } h1 { - display: inline; + display: inline; } -h2, h3 { - font-weight: normal; +h2, +h3 { + font-weight: normal; } p { - margin: 14px 0; + margin: 14px 0; } a { - color: var(--accent); + color: var(--accent); - &:hover { - text-decoration: underline; - } + &:hover { + text-decoration: underline; + } } fieldset { - border: 0; - padding: 0; - margin-top: -0.6em; + border: 0; + padding: 0; + margin-top: -0.6em; } legend { - width: 100%; - padding: .6em 0 .3em 0; - border: 0; - font-size: 16px; - font-weight: 600; - border-bottom: 1px solid var(--border_grey); - margin-bottom: 8px; + width: 100%; + padding: 0.6em 0 0.3em 0; + border: 0; + font-size: 16px; + font-weight: 600; + border-bottom: 1px solid var(--border_grey); + margin-bottom: 8px; } .preferences .note { - border-top: 1px solid var(--border_grey); - border-bottom: 1px solid var(--border_grey); - padding: 6px 0 8px 0; - margin-bottom: 8px; - margin-top: 16px; + border-top: 1px solid var(--border_grey); + border-bottom: 1px solid var(--border_grey); + padding: 6px 0 8px 0; + margin-bottom: 8px; + margin-top: 16px; } ul { - padding-left: 1.3em; + padding-left: 1.3em; } .container { - display: flex; - flex-wrap: wrap; - box-sizing: border-box; - padding-top: 50px; - margin: auto; - min-height: 100vh; + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + padding-top: 50px; + margin: auto; + min-height: 100vh; } .icon-container { - display: inline; + display: inline; } .overlay-panel { - max-width: 600px; - width: 100%; - margin: 0 auto; - margin-top: 10px; - background-color: var(--bg_overlays); - padding: 10px 15px; - align-self: start; + max-width: 600px; + width: 100%; + margin: 0 auto; + margin-top: 10px; + background-color: var(--bg_overlays); + padding: 10px 15px; + align-self: start; - ul { - margin-bottom: 14px; - } + ul { + margin-bottom: 14px; + } - p { - word-break: break-word; - } + p { + word-break: break-word; + } } .verified-icon { - color: var(--icon_text); - border-radius: 50%; - flex-shrink: 0; - margin: 2px 0 3px 3px; - padding-top: 3px; - height: 11px; - width: 14px; - font-size: 8px; - display: inline-block; - text-align: center; - vertical-align: middle; + color: var(--icon_text); + border-radius: 50%; + flex-shrink: 0; + margin: 2px 0 3px 3px; + padding-top: 3px; + height: 11px; + width: 14px; + font-size: 8px; + display: inline-block; + text-align: center; + vertical-align: middle; - &.blue { - background-color: var(--verified_blue); - } + &.blue { + background-color: var(--verified_blue); + } - &.business { - color: var(--bg_panel); - background-color: var(--verified_business); - } + &.business { + color: var(--bg_panel); + background-color: var(--verified_business); + } - &.government { - color: var(--bg_panel); - background-color: var(--verified_government); - } + &.government { + color: var(--bg_panel); + background-color: var(--verified_government); + } } -@media(max-width: 600px) { - .preferences-container { - max-width: 95vw; - } +@media (max-width: 600px) { + .preferences-container { + max-width: 95vw; + } - .nav-item, .nav-item .icon-container { - font-size: 16px; - } + .nav-item, + .nav-item .icon-container { + font-size: 16px; + } } diff --git a/src/sass/inputs.scss b/src/sass/inputs.scss index d6cbb1d..aafa5b8 100644 --- a/src/sass/inputs.scss +++ b/src/sass/inputs.scss @@ -1,203 +1,203 @@ -@import '_variables'; -@import '_mixins'; +@import "_variables"; +@import "_mixins"; button { - @include input-colors; - background-color: var(--bg_elements); - color: var(--fg_color); - border: 1px solid var(--accent_border); - padding: 3px 6px; - font-size: 14px; - cursor: pointer; - float: right; + @include input-colors; + background-color: var(--bg_elements); + color: var(--fg_color); + border: 1px solid var(--accent_border); + padding: 3px 6px; + font-size: 14px; + cursor: pointer; + float: right; } input[type="text"], input[type="date"], input[type="number"], select { - @include input-colors; - background-color: var(--bg_elements); - padding: 1px 4px; - color: var(--fg_color); - border: 1px solid var(--accent_border); - border-radius: 0; - font-size: 14px; + @include input-colors; + background-color: var(--bg_elements); + padding: 1px 4px; + color: var(--fg_color); + border: 1px solid var(--accent_border); + border-radius: 0; + font-size: 14px; } input[type="number"] { - -moz-appearance: textfield; + -moz-appearance: textfield; } input[type="text"], input[type="number"] { - height: 16px; + height: 16px; } select { - height: 20px; - padding: 0 2px; - line-height: 1; + height: 20px; + padding: 0 2px; + line-height: 1; } input[type="date"]::-webkit-inner-spin-button { - display: none; + display: none; } input[type="number"] { - -moz-appearance: textfield; + -moz-appearance: textfield; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { - display: none; - -webkit-appearance: none; - margin: 0; + display: none; + -webkit-appearance: none; + margin: 0; } input[type="date"]::-webkit-clear-button { - margin-left: 17px; - filter: grayscale(100%); - filter: hue-rotate(120deg); + margin-left: 17px; + filter: grayscale(100%); + filter: hue-rotate(120deg); } input::-webkit-calendar-picker-indicator { - opacity: 0; + opacity: 0; } input::-webkit-datetime-edit-day-field:focus, input::-webkit-datetime-edit-month-field:focus, input::-webkit-datetime-edit-year-field:focus { - background-color: var(--accent); - color: var(--fg_color); - outline: none; + background-color: var(--accent); + color: var(--fg_color); + outline: none; } .date-range { - .date-input { - display: inline-block; - position: relative; - } + .date-input { + display: inline-block; + position: relative; + } - .icon-container { - pointer-events: none; - position: absolute; - top: 2px; - right: 5px; - } + .icon-container { + pointer-events: none; + position: absolute; + top: 2px; + right: 5px; + } - .search-title { - margin: 0 2px; - } + .search-title { + margin: 0 2px; + } } .icon-button button { - color: var(--accent); - text-decoration: none; - background: none; - border: none; - float: none; - padding: unset; - padding-left: 4px; + color: var(--accent); + text-decoration: none; + background: none; + border: none; + float: none; + padding: unset; + padding-left: 4px; - &:hover { - color: var(--accent_light); - } + &:hover { + color: var(--accent_light); + } } .checkbox { - position: absolute; - top: 1px; - right: 0; - height: 17px; - width: 17px; - background-color: var(--bg_elements); - border: 1px solid var(--accent_border); + position: absolute; + top: 1px; + right: 0; + height: 17px; + width: 17px; + background-color: var(--bg_elements); + border: 1px solid var(--accent_border); - &:after { - content: ""; - position: absolute; - display: none; - } + &:after { + content: ""; + position: absolute; + display: none; + } } .checkbox-container { - display: block; - position: relative; - margin-bottom: 5px; + display: block; + position: relative; + margin-bottom: 5px; + cursor: pointer; + user-select: none; + padding-right: 22px; + + input { + position: absolute; + opacity: 0; cursor: pointer; - user-select: none; - padding-right: 22px; + height: 0; + width: 0; - input { - position: absolute; - opacity: 0; - cursor: pointer; - height: 0; - width: 0; - - &:checked ~ .checkbox:after { - display: block; - } + &:checked ~ .checkbox:after { + display: block; } + } - &:hover input ~ .checkbox { - border-color: var(--accent); - } + &:hover input ~ .checkbox { + border-color: var(--accent); + } - &:active input ~ .checkbox { - border-color: var(--accent_light); - } + &:active input ~ .checkbox { + border-color: var(--accent_light); + } - .checkbox:after { - left: 2px; - bottom: 0; - font-size: 13px; - font-family: $font_4; - content: '\e803'; - } + .checkbox:after { + left: 2px; + bottom: 0; + font-size: 13px; + font-family: $font_1; + content: "\e811"; + } } .pref-group { - display: inline; + display: inline; } .preferences { - button { - margin: 6px 0 3px 0; - } + button { + margin: 6px 0 3px 0; + } - label { - padding-right: 150px; - } + label { + padding-right: 150px; + } - select { - position: absolute; - top: 0; - right: 0; - display: block; - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; - } + select { + position: absolute; + top: 0; + right: 0; + display: block; + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + } - input[type="text"], - input[type="number"] { - position: absolute; - right: 0; - max-width: 140px; - } + input[type="text"], + input[type="number"] { + position: absolute; + right: 0; + max-width: 140px; + } - .pref-group { - display: block; - } + .pref-group { + display: block; + } - .pref-input { - position: relative; - margin-bottom: 6px; - } + .pref-input { + position: relative; + margin-bottom: 6px; + } - .pref-reset { - float: left; - } + .pref-reset { + float: left; + } } diff --git a/src/sass/navbar.scss b/src/sass/navbar.scss index 47a8765..4e150e0 100644 --- a/src/sass/navbar.scss +++ b/src/sass/navbar.scss @@ -1,89 +1,90 @@ -@import '_variables'; +@import "_variables"; nav { - display: flex; - align-items: center; - position: fixed; - background-color: var(--bg_overlays); - box-shadow: 0 0 4px $shadow; - padding: 0; - width: 100%; - height: 50px; - z-index: 1000; - font-size: 16px; + display: flex; + align-items: center; + position: fixed; + background-color: var(--bg_overlays); + box-shadow: 0 0 4px $shadow; + padding: 0; + width: 100%; + height: 50px; + z-index: 1000; + font-size: 16px; - a, .icon-button button { - color: var(--fg_nav); - } + a, + .icon-button button { + color: var(--fg_nav); + } } .inner-nav { - margin: auto; - box-sizing: border-box; - padding: 0 10px; - display: flex; - align-items: center; - flex-basis: 920px; - height: 50px; + margin: auto; + box-sizing: border-box; + padding: 0 10px; + display: flex; + align-items: center; + flex-basis: 920px; + height: 50px; } .site-name { - font-size: 15px; - font-weight: 600; - line-height: 1; + font-size: 15px; + font-weight: 600; + line-height: 1; - &:hover { - color: var(--accent_light); - text-decoration: unset; - } + &:hover { + color: var(--accent_light); + text-decoration: unset; + } } .site-logo { - display: block; - width: 35px; - height: 35px; + display: block; + width: 35px; + height: 35px; } .nav-item { - display: flex; - flex: 1; - line-height: 50px; - height: 50px; - overflow: hidden; - flex-wrap: wrap; - align-items: center; + display: flex; + flex: 1; + line-height: 50px; + height: 50px; + overflow: hidden; + flex-wrap: wrap; + align-items: center; - &.right { - text-align: right; - justify-content: flex-end; - } - - &.right a { - padding-left: 4px; - - &:hover { - color: var(--accent_light); - text-decoration: unset; - } + &.right { + text-align: right; + justify-content: flex-end; + } + + &.right a { + padding-left: 4px; + + &:hover { + color: var(--accent_light); + text-decoration: unset; } + } } .lp { - height: 14px; - display: inline-block; - position: relative; - top: 2px; - fill: var(--fg_nav); + height: 14px; + display: inline-block; + position: relative; + top: 2px; + fill: var(--fg_nav); - &:hover { - fill: var(--accent_light); - } + &:hover { + fill: var(--accent_light); + } } .icon-info:before { - margin: 0 -3px; + margin: 0 -3px; } .icon-cog { - font-size: 15px; + font-size: 15px; } diff --git a/src/sass/search.scss b/src/sass/search.scss index 234d677..444f9bb 100644 --- a/src/sass/search.scss +++ b/src/sass/search.scss @@ -1,120 +1,120 @@ -@import '_variables'; -@import '_mixins'; +@import "_variables"; +@import "_mixins"; .search-title { - font-weight: bold; - display: inline-block; - margin-top: 4px; + font-weight: bold; + display: inline-block; + margin-top: 4px; } .search-field { + display: flex; + flex-wrap: wrap; + + button { + margin: 0 2px 0 0; + height: 23px; display: flex; - flex-wrap: wrap; + align-items: center; + } - button { - margin: 0 2px 0 0; - height: 23px; - display: flex; - align-items: center; - } + .pref-input { + margin: 0 4px 0 0; + flex-grow: 1; + height: 23px; + } - .pref-input { - margin: 0 4px 0 0; - flex-grow: 1; - height: 23px; - } + input[type="text"], + input[type="number"] { + height: calc(100% - 4px); + width: calc(100% - 8px); + } - input[type="text"], - input[type="number"] { - height: calc(100% - 4px); - width: calc(100% - 8px); - } + > label { + display: inline; + background-color: var(--bg_elements); + color: var(--fg_color); + border: 1px solid var(--accent_border); + padding: 1px 6px 2px 6px; + font-size: 14px; + cursor: pointer; + margin-bottom: 2px; - > label { - display: inline; - background-color: var(--bg_elements); - color: var(--fg_color); - border: 1px solid var(--accent_border); - padding: 1px 6px 2px 6px; - font-size: 14px; - cursor: pointer; - margin-bottom: 2px; + @include input-colors; + } - @include input-colors; - } - - @include create-toggle(search-panel, 380px); + @include create-toggle(search-panel, 380px); } .search-panel { - width: 100%; - max-height: 0; - overflow: hidden; - transition: max-height 0.4s; + width: 100%; + max-height: 0; + overflow: hidden; + transition: max-height 0.4s; - flex-grow: 1; - font-weight: initial; - text-align: left; + flex-grow: 1; + font-weight: initial; + text-align: left; - > div { - line-height: 1.7em; - } + > div { + line-height: 1.7em; + } - .checkbox-container { - display: inline; - padding-right: unset; - margin-bottom: unset; - margin-left: 23px; - } + .checkbox-container { + display: inline; + padding-right: unset; + margin-bottom: unset; + margin-left: 23px; + } - .checkbox { - right: unset; - left: -22px; - } + .checkbox { + right: unset; + left: -22px; + } - .checkbox-container .checkbox:after { - top: -4px; - } + .checkbox-container .checkbox:after { + top: -4px; + } } .search-row { - display: flex; - flex-wrap: wrap; - line-height: unset; + display: flex; + flex-wrap: wrap; + line-height: unset; - > div { - flex-grow: 1; - flex-shrink: 1; - } + > div { + flex-grow: 1; + flex-shrink: 1; + } + + input { + height: 21px; + } + + .pref-input { + display: block; + padding-bottom: 5px; input { - height: 21px; - } - - .pref-input { - display: block; - padding-bottom: 5px; - - input { - height: 21px; - margin-top: 1px; - } + height: 21px; + margin-top: 1px; } + } } .search-toggles { - flex-grow: 1; - display: grid; - grid-template-columns: repeat(5, auto); - grid-column-gap: 10px; + flex-grow: 1; + display: grid; + grid-template-columns: repeat(5, auto); + grid-column-gap: 10px; } .profile-tabs { - @include search-resize(820px, 5); - @include search-resize(715px, 4); - @include search-resize(700px, 5); - @include search-resize(485px, 4); - @include search-resize(410px, 3); + @include search-resize(820px, 5); + @include search-resize(715px, 4); + @include search-resize(700px, 5); + @include search-resize(485px, 4); + @include search-resize(410px, 3); } @include search-resize(700px, 5); diff --git a/src/sass/timeline.scss b/src/sass/timeline.scss index c8ce309..40882b2 100644 --- a/src/sass/timeline.scss +++ b/src/sass/timeline.scss @@ -1,162 +1,162 @@ -@import '_variables'; +@import "_variables"; .timeline-container { - @include panel(100%, 600px); + @include panel(100%, 600px); } .timeline { - background-color: var(--bg_panel); + background-color: var(--bg_panel); - > div:not(:first-child) { - border-top: 1px solid var(--border_grey); - } + > div:not(:first-child) { + border-top: 1px solid var(--border_grey); + } } .timeline-header { - width: 100%; - background-color: var(--bg_panel); - text-align: center; - padding: 8px; - display: block; - font-weight: bold; - margin-bottom: 5px; - box-sizing: border-box; + width: 100%; + background-color: var(--bg_panel); + text-align: center; + padding: 8px; + display: block; + font-weight: bold; + margin-bottom: 5px; + box-sizing: border-box; - button { - float: unset; - } + button { + float: unset; + } } .timeline-banner img { - width: 100%; + width: 100%; } .timeline-description { - font-weight: normal; + font-weight: normal; } .tab { - align-items: center; - display: flex; - flex-wrap: wrap; - list-style: none; - margin: 0 0 5px 0; - background-color: var(--bg_panel); - padding: 0; + align-items: center; + display: flex; + flex-wrap: wrap; + list-style: none; + margin: 0 0 5px 0; + background-color: var(--bg_panel); + padding: 0; } .tab-item { - flex: 1 1 0; - text-align: center; - margin-top: 0; + flex: 1 1 0; + text-align: center; + margin-top: 0; - a { - border-bottom: .1rem solid transparent; - color: var(--tab); - display: block; - padding: 8px 0; - text-decoration: none; - font-weight: bold; + a { + border-bottom: 0.1rem solid transparent; + color: var(--tab); + display: block; + padding: 8px 0; + text-decoration: none; + font-weight: bold; - &:hover { - text-decoration: none; - } - - &.active { - border-bottom-color: var(--tab_selected); - color: var(--tab_selected); - } + &:hover { + text-decoration: none; } - &.active a { - border-bottom-color: var(--tab_selected); - color: var(--tab_selected); + &.active { + border-bottom-color: var(--tab_selected); + color: var(--tab_selected); } + } - &.wide { - flex-grow: 1.2; - flex-basis: 50px; - } + &.active a { + border-bottom-color: var(--tab_selected); + color: var(--tab_selected); + } + + &.wide { + flex-grow: 1.2; + flex-basis: 50px; + } } .timeline-footer { - background-color: var(--bg_panel); - padding: 6px 0; + background-color: var(--bg_panel); + padding: 6px 0; } .timeline-protected { - text-align: center; + text-align: center; - p { - margin: 8px 0; - } + p { + margin: 8px 0; + } - h2 { - color: var(--accent); - font-size: 20px; - font-weight: 600; - } -} - -.timeline-none { + h2 { color: var(--accent); font-size: 20px; font-weight: 600; - text-align: center; + } +} + +.timeline-none { + color: var(--accent); + font-size: 20px; + font-weight: 600; + text-align: center; } .timeline-end { - background-color: var(--bg_panel); - color: var(--accent); - font-size: 16px; - font-weight: 600; - text-align: center; + background-color: var(--bg_panel); + color: var(--accent); + font-size: 16px; + font-weight: 600; + text-align: center; } .show-more { - background-color: var(--bg_panel); - text-align: center; - padding: .75em 0; - display: block !important; + background-color: var(--bg_panel); + text-align: center; + padding: 0.75em 0; + display: block !important; - a { - background-color: var(--darkest_grey); - display: inline-block; - height: 2em; - padding: 0 2em; - line-height: 2em; + a { + background-color: var(--darkest_grey); + display: inline-block; + height: 2em; + padding: 0 2em; + line-height: 2em; - &:hover { - background-color: var(--darker_grey); - } + &:hover { + background-color: var(--darker_grey); } + } } .top-ref { - background-color: var(--bg_color); - border-top: none !important; + background-color: var(--bg_color); + border-top: none !important; - .icon-down { - font-size: 20px; - display: flex; - justify-content: center; - text-decoration: none; + .icon-down { + font-size: 20px; + display: flex; + justify-content: center; + text-decoration: none; - &:hover { - color: var(--accent_light); - } - - &::before { - transform: rotate(180deg) translateY(-1px); - } + &:hover { + color: var(--accent_light); } + + &::before { + transform: rotate(180deg) translateY(-1px); + } + } } .timeline-item { - overflow-wrap: break-word; - border-left-width: 0; - min-width: 0; - padding: .75em; - display: flex; - position: relative; + overflow-wrap: break-word; + border-left-width: 0; + min-width: 0; + padding: 0.75em; + display: flex; + position: relative; } diff --git a/src/sass/tweet/_base.scss b/src/sass/tweet/_base.scss index 69f51c0..7f2d931 100644 --- a/src/sass/tweet/_base.scss +++ b/src/sass/tweet/_base.scss @@ -1,240 +1,244 @@ -@import '_variables'; -@import '_mixins'; -@import 'thread'; -@import 'media'; -@import 'video'; -@import 'embed'; -@import 'card'; -@import 'poll'; -@import 'quote'; +@import "_variables"; +@import "_mixins"; +@import "thread"; +@import "media"; +@import "video"; +@import "embed"; +@import "card"; +@import "poll"; +@import "quote"; .tweet-body { - flex: 1; - min-width: 0; - margin-left: 58px; - pointer-events: none; - z-index: 1; + flex: 1; + min-width: 0; + margin-left: 58px; + pointer-events: none; + z-index: 1; } .tweet-content { - font-family: $font_3; - line-height: 1.3em; - pointer-events: all; - display: inline; + line-height: 1.3em; + pointer-events: all; + display: inline; } .tweet-bidi { - display: block !important; + display: block !important; } .tweet-header { - padding: 0; - vertical-align: bottom; - flex-basis: 100%; - margin-bottom: .2em; + padding: 0; + vertical-align: bottom; + flex-basis: 100%; + margin-bottom: 0.2em; - a { - display: inline-block; - word-break: break-all; - max-width: 100%; - pointer-events: all; - } + a { + display: inline-block; + word-break: break-all; + max-width: 100%; + pointer-events: all; + } } .tweet-name-row { - padding: 0; - display: flex; - justify-content: space-between; + padding: 0; + display: flex; + justify-content: space-between; } .fullname-and-username { - display: flex; - min-width: 0; + display: flex; + min-width: 0; } .fullname { - @include ellipsis; - flex-shrink: 2; - max-width: 80%; - font-size: 14px; - font-weight: 700; - color: var(--fg_color); + @include ellipsis; + flex-shrink: 2; + max-width: 80%; + font-size: 14px; + font-weight: 700; + color: var(--fg_color); } .username { - @include ellipsis; - min-width: 1.6em; - margin-left: .4em; - word-wrap: normal; + @include ellipsis; + min-width: 1.6em; + margin-left: 0.4em; + word-wrap: normal; } .tweet-date { - display: flex; - flex-shrink: 0; - margin-left: 4px; + display: flex; + flex-shrink: 0; + margin-left: 4px; } -.tweet-date a, .username, .show-more a { - color: var(--fg_dark); +.tweet-date a, +.username, +.show-more a { + color: var(--fg_dark); } .tweet-published { - margin: 0; - margin-top: 5px; - color: var(--grey); - pointer-events: all; + margin: 0; + margin-top: 5px; + color: var(--grey); + pointer-events: all; } .tweet-avatar { - display: contents !important; + display: contents !important; - img { - float: left; - margin-top: 3px; - margin-left: -58px; - width: 48px; - height: 48px; - } + img { + float: left; + margin-top: 3px; + margin-left: -58px; + width: 48px; + height: 48px; + } } .avatar { - &.round { - border-radius: 50%; - -webkit-user-select: none; - } - - &.mini { - position: unset; - margin-right: 5px; - margin-top: -1px; - width: 20px; - height: 20px; - } + &.round { + border-radius: 50%; + -webkit-user-select: none; + } + + &.mini { + position: unset; + margin-right: 5px; + margin-top: -1px; + width: 20px; + height: 20px; + } } .tweet-embed { + display: flex; + flex-direction: column; + justify-content: center; + height: 100%; + background-color: var(--bg_panel); + + .tweet-content { + font-size: 18px; + } + + .tweet-body { display: flex; flex-direction: column; - justify-content: center; - height: 100%; - background-color: var(--bg_panel); + max-height: calc(100vh - 0.75em * 2); + } - .tweet-content { - font-size: 18px; - } - - .tweet-body { - display: flex; - flex-direction: column; - max-height: calc(100vh - 0.75em * 2); - } + .card-image img { + height: auto; + } - .card-image img { - height: auto; - } - - .avatar { - position: absolute; - } + .avatar { + position: absolute; + } } .attribution { - display: flex; - pointer-events: all; - margin: 5px 0; + display: flex; + pointer-events: all; + margin: 5px 0; - strong { - color: var(--fg_color); - } + strong { + color: var(--fg_color); + } } .media-tag-block { - padding-top: 5px; - pointer-events: all; + padding-top: 5px; + pointer-events: all; + color: var(--fg_faded); + + .icon-container { + padding-right: 2px; + } + + .media-tag, + .icon-container { color: var(--fg_faded); - - .icon-container { - padding-right: 2px; - } - - .media-tag, .icon-container { - color: var(--fg_faded); - } + } } .timeline-container .media-tag-block { - font-size: 13px; + font-size: 13px; } .tweet-geo { - color: var(--fg_faded); + color: var(--fg_faded); } .replying-to { - color: var(--fg_faded); - margin: -2px 0 4px; + color: var(--fg_faded); + margin: -2px 0 4px; - a { - pointer-events: all; - } + a { + pointer-events: all; + } } -.retweet-header, .pinned, .tweet-stats { - align-content: center; - color: var(--grey); - display: flex; - flex-shrink: 0; - flex-wrap: wrap; - font-size: 14px; - font-weight: 600; - line-height: 22px; +.retweet-header, +.pinned, +.tweet-stats { + align-content: center; + color: var(--grey); + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + font-size: 14px; + font-weight: 600; + line-height: 22px; - span { - @include ellipsis; - } + span { + @include ellipsis; + } } .retweet-header { - margin-top: -5px !important; + margin-top: -5px !important; } .tweet-stats { - margin-bottom: -3px; - -webkit-user-select: none; + margin-bottom: -3px; + -webkit-user-select: none; } .tweet-stat { - padding-top: 5px; - min-width: 1em; - margin-right: 0.8em; + padding-top: 5px; + min-width: 1em; + margin-right: 0.8em; } .show-thread { - display: block; - pointer-events: all; - padding-top: 2px; + display: block; + pointer-events: all; + padding-top: 2px; } .unavailable-box { - width: 100%; - height: 100%; - padding: 12px; - border: solid 1px var(--dark_grey); - box-sizing: border-box; - border-radius: 10px; - background-color: var(--bg_color); - z-index: 2; + width: 100%; + height: 100%; + padding: 12px; + border: solid 1px var(--dark_grey); + box-sizing: border-box; + border-radius: 10px; + background-color: var(--bg_color); + z-index: 2; } .tweet-link { - height: 100%; - width: 100%; - left: 0; - top: 0; - position: absolute; - -webkit-user-select: none; + height: 100%; + width: 100%; + left: 0; + top: 0; + position: absolute; + -webkit-user-select: none; - &:hover { - background-color: var(--bg_hover); - } + &:hover { + background-color: var(--bg_hover); + } } diff --git a/src/sass/tweet/embed.scss b/src/sass/tweet/embed.scss index 227fc5e..fbdbd41 100644 --- a/src/sass/tweet/embed.scss +++ b/src/sass/tweet/embed.scss @@ -1,17 +1,17 @@ -@import '_variables'; -@import '_mixins'; +@import "_variables"; +@import "_mixins"; .embed-video { - .gallery-video { - width: 100%; - height: 100%; - position: absolute; - background-color: black; - top: 0%; - left: 0%; - } + .gallery-video { + width: 100%; + height: 100%; + position: absolute; + background-color: black; + top: 0%; + left: 0%; + } - .video-container { - max-height: unset; - } + .video-container { + max-height: unset; + } } diff --git a/src/sass/tweet/media.scss b/src/sass/tweet/media.scss index 91c9dab..66a300f 100644 --- a/src/sass/tweet/media.scss +++ b/src/sass/tweet/media.scss @@ -1,76 +1,76 @@ -@import '_variables'; +@import "_variables"; .gallery-row { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - overflow: hidden; - flex-grow: 1; - max-height: 379.5px; - max-width: 533px; - pointer-events: all; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + overflow: hidden; + flex-grow: 1; + max-height: 379.5px; + max-width: 533px; + pointer-events: all; - .still-image { - width: 100%; - display: flex; - } + .still-image { + width: 100%; + display: flex; + } } .attachments { - margin-top: .35em; - display: flex; - flex-direction: row; - width: 100%; - max-height: 600px; - border-radius: 7px; - overflow: hidden; - flex-flow: column; - background-color: var(--bg_color); - align-items: center; - pointer-events: all; + margin-top: 0.35em; + display: flex; + flex-direction: row; + width: 100%; + max-height: 600px; + border-radius: 7px; + overflow: hidden; + flex-flow: column; + background-color: var(--bg_color); + align-items: center; + pointer-events: all; - .image-attachment { - width: 100%; - } + .image-attachment { + width: 100%; + } } .attachment { - position: relative; - line-height: 0; - overflow: hidden; - margin: 0 .25em 0 0; - flex-grow: 1; - box-sizing: border-box; - min-width: 2em; + position: relative; + line-height: 0; + overflow: hidden; + margin: 0 0.25em 0 0; + flex-grow: 1; + box-sizing: border-box; + min-width: 2em; - &:last-child { - margin: 0; - max-height: 530px; - } + &:last-child { + margin: 0; + max-height: 530px; + } } .gallery-gif video { - max-height: 530px; - background-color: #101010; + max-height: 530px; + background-color: #101010; } .still-image { - max-height: 379.5px; - max-width: 533px; - justify-content: center; + max-height: 379.5px; + max-width: 533px; + justify-content: center; - img { - object-fit: cover; - max-width: 100%; - max-height: 379.5px; - flex-basis: 300px; - flex-grow: 1; - } + img { + object-fit: cover; + max-width: 100%; + max-height: 379.5px; + flex-basis: 300px; + flex-grow: 1; + } } .image { - display: inline-block; + display: inline-block; } // .single-image { @@ -86,34 +86,34 @@ // } .overlay-circle { - border-radius: 50%; - background-color: var(--dark_grey); - width: 40px; - height: 40px; - align-items: center; - display: flex; - border-width: 5px; - border-color: var(--play_button); - border-style: solid; + border-radius: 50%; + background-color: var(--dark_grey); + width: 40px; + height: 40px; + align-items: center; + display: flex; + border-width: 5px; + border-color: var(--play_button); + border-style: solid; } .overlay-triangle { - width: 0; - height: 0; - border-style: solid; - border-width: 12px 0 12px 17px; - border-color: transparent transparent transparent var(--play_button); - margin-left: 14px; + width: 0; + height: 0; + border-style: solid; + border-width: 12px 0 12px 17px; + border-color: transparent transparent transparent var(--play_button); + margin-left: 14px; } .media-gif { - display: table; - background-color: unset; - width: unset; + display: table; + background-color: unset; + width: unset; } .media-body { - flex: 1; - padding: 0; - white-space: pre-wrap; + flex: 1; + padding: 0; + white-space: pre-wrap; } diff --git a/src/sass/tweet/poll.scss b/src/sass/tweet/poll.scss index 57590c8..6d54e00 100644 --- a/src/sass/tweet/poll.scss +++ b/src/sass/tweet/poll.scss @@ -1,42 +1,42 @@ -@import '_variables'; +@import "_variables"; .poll-meter { - overflow: hidden; - position: relative; - margin: 6px 0; - height: 26px; - background: var(--bg_color); - border-radius: 5px; - display: flex; - align-items: center; + overflow: hidden; + position: relative; + margin: 6px 0; + height: 26px; + background: var(--bg_color); + border-radius: 5px; + display: flex; + align-items: center; } .poll-choice-bar { - height: 100%; - position: absolute; - background: var(--dark_grey); + height: 100%; + position: absolute; + background: var(--dark_grey); } .poll-choice-value { - position: relative; - font-weight: bold; - margin-left: 5px; - margin-right: 6px; - min-width: 30px; - text-align: right; - pointer-events: all; + position: relative; + font-weight: bold; + margin-left: 5px; + margin-right: 6px; + min-width: 30px; + text-align: right; + pointer-events: all; } .poll-choice-option { - position: relative; - pointer-events: all; + position: relative; + pointer-events: all; } .poll-info { - color: var(--grey); - pointer-events: all; + color: var(--grey); + pointer-events: all; } .leader .poll-choice-bar { - background: var(--accent_dark); + background: var(--accent_dark); } diff --git a/src/sass/tweet/quote.scss b/src/sass/tweet/quote.scss index b4bc60e..1db4f7e 100644 --- a/src/sass/tweet/quote.scss +++ b/src/sass/tweet/quote.scss @@ -1,94 +1,95 @@ -@import '_variables'; +@import "_variables"; .quote { - margin-top: 10px; - border: solid 1px var(--dark_grey); - border-radius: 10px; - background-color: var(--bg_elements); + margin-top: 10px; + border: solid 1px var(--dark_grey); + border-radius: 10px; + background-color: var(--bg_elements); + overflow: hidden; + pointer-events: all; + position: relative; + width: 100%; + + &:hover { + border-color: var(--grey); + } + + &.unavailable:hover { + border-color: var(--dark_grey); + } + + .tweet-name-row { + padding: 6px 8px; + margin-top: 1px; + } + + .quote-text { overflow: hidden; - pointer-events: all; - position: relative; - width: 100%; + white-space: pre-wrap; + word-wrap: break-word; + padding: 0px 8px 8px 8px; + } - &:hover { - border-color: var(--grey); - } + .show-thread { + padding: 0px 8px 6px 8px; + margin-top: -6px; + } - &.unavailable:hover { - border-color: var(--dark_grey); - } - - .tweet-name-row { - padding: 6px 8px; - margin-top: 1px; - } - - .quote-text { - overflow: hidden; - white-space: pre-wrap; - word-wrap: break-word; - padding: 0px 8px 8px 8px; - } - - .show-thread { - padding: 0px 8px 6px 8px; - margin-top: -6px; - } - - .replying-to { - padding: 0px 8px; - margin: unset; - } + .replying-to { + padding: 0px 8px; + margin: unset; + } } .unavailable-quote { - padding: 12px; + padding: 12px; } .quote-link { - width: 100%; - height: 100%; - left: 0; - top: 0; - position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + position: absolute; } .quote-media-container { - max-height: 300px; + max-height: 300px; + display: flex; + + .card { + margin: unset; + } + + .attachments { + border-radius: 0; + } + + .media-gif { + width: 100%; display: flex; + justify-content: center; + } - .card { - margin: unset; + .gallery-gif .attachment { + display: flex; + justify-content: center; + background-color: var(--bg_color); + + video { + height: unset; + width: unset; + max-height: 100%; + max-width: 100%; } + } - .attachments { - border-radius: 0; - } + .gallery-video, + .gallery-gif { + max-height: 300px; + } - .media-gif { - width: 100%; - display: flex; - justify-content: center; - } - - .gallery-gif .attachment { - display: flex; - justify-content: center; - background-color: var(--bg_color); - - video { - height: unset; - width: unset; - max-height: 100%; - max-width: 100%; - } - } - - .gallery-video, .gallery-gif { - max-height: 300px; - } - - .still-image img { - max-height: 250px - } + .still-image img { + max-height: 250px; + } } diff --git a/src/sass/tweet/thread.scss b/src/sass/tweet/thread.scss index 19fb3e0..9d2fb64 100644 --- a/src/sass/tweet/thread.scss +++ b/src/sass/tweet/thread.scss @@ -1,138 +1,139 @@ -@import '_variables'; -@import '_mixins'; +@import "_variables"; +@import "_mixins"; .conversation { - @include panel(100%, 600px); + @include panel(100%, 600px); - .show-more { - margin-bottom: 10px; - } + .show-more { + margin-bottom: 10px; + } } .main-thread { - margin-bottom: 20px; - background-color: var(--bg_panel); + margin-bottom: 20px; + background-color: var(--bg_panel); } -.main-tweet, .replies { - padding-top: 50px; - margin-top: -50px; +.main-tweet, +.replies { + padding-top: 50px; + margin-top: -50px; } .main-tweet .tweet-content { - font-size: 18px; + font-size: 18px; } -@media(max-width: 600px) { - .main-tweet .tweet-content { - font-size: 16px; - } +@media (max-width: 600px) { + .main-tweet .tweet-content { + font-size: 16px; + } } .reply { - background-color: var(--bg_panel); - margin-bottom: 10px; + background-color: var(--bg_panel); + margin-bottom: 10px; } .thread-line { - .timeline-item::before, - &.timeline-item::before { - background: var(--accent_dark); - content: ''; - position: relative; - min-width: 3px; - width: 3px; - left: 26px; - border-radius: 2px; - margin-left: -3px; - margin-bottom: 37px; - top: 56px; - z-index: 1; - pointer-events: none; - } + .timeline-item::before, + &.timeline-item::before { + background: var(--accent_dark); + content: ""; + position: relative; + min-width: 3px; + width: 3px; + left: 26px; + border-radius: 2px; + margin-left: -3px; + margin-bottom: 37px; + top: 56px; + z-index: 1; + pointer-events: none; + } - .with-header:not(:first-child)::after { - background: var(--accent_dark); - content: ''; - position: relative; - float: left; - min-width: 3px; - width: 3px; - right: calc(100% - 26px); - border-radius: 2px; - margin-left: -3px; - margin-bottom: 37px; - bottom: 10px; - height: 30px; - z-index: 1; - pointer-events: none; - } + .with-header:not(:first-child)::after { + background: var(--accent_dark); + content: ""; + position: relative; + float: left; + min-width: 3px; + width: 3px; + right: calc(100% - 26px); + border-radius: 2px; + margin-left: -3px; + margin-bottom: 37px; + bottom: 10px; + height: 30px; + z-index: 1; + pointer-events: none; + } - .unavailable::before { - top: 48px; - margin-bottom: 28px; - } + .unavailable::before { + top: 48px; + margin-bottom: 28px; + } - .more-replies::before { - content: '...'; - background: unset; - color: var(--more_replies_dots); - font-weight: bold; - font-size: 20px; - line-height: 0.25em; - left: 1.2em; - width: 5px; - top: 2px; - margin-bottom: 0; - margin-left: -2.5px; - } + .more-replies::before { + content: "..."; + background: unset; + color: var(--more_replies_dots); + font-weight: bold; + font-size: 20px; + line-height: 0.25em; + left: 1.2em; + width: 5px; + top: 2px; + margin-bottom: 0; + margin-left: -2.5px; + } - .earlier-replies { - padding-bottom: 0; - margin-bottom: -5px; - } + .earlier-replies { + padding-bottom: 0; + margin-bottom: -5px; + } } .timeline-item.thread-last::before { - background: unset; - min-width: unset; - width: 0; - margin: 0; + background: unset; + min-width: unset; + width: 0; + margin: 0; } .more-replies { - padding-top: 0.3em !important; + padding-top: 0.3em !important; } .more-replies-text { - @include ellipsis; - display: block; - margin-left: 58px; - padding: 7px 0; + @include ellipsis; + display: block; + margin-left: 58px; + padding: 7px 0; } .timeline-item.thread.more-replies-thread { - padding: 0 0.75em; + padding: 0 0.75em; + + &::before { + top: 40px; + margin-bottom: 31px; + } + + .more-replies { + display: flex; + padding-top: unset !important; + margin-top: 8px; &::before { - top: 40px; - margin-bottom: 31px; + display: inline-block; + position: relative; + top: -1px; + line-height: 0.4em; } - .more-replies { - display: flex; - padding-top: unset !important; - margin-top: 8px; - - &::before { - display: inline-block; - position: relative; - top: -1px; - line-height: 0.4em; - } - - .more-replies-text { - display: inline; - } + .more-replies-text { + display: inline; } + } } diff --git a/src/sass/tweet/video.scss b/src/sass/tweet/video.scss index 98a1c29..1e00d39 100644 --- a/src/sass/tweet/video.scss +++ b/src/sass/tweet/video.scss @@ -1,68 +1,69 @@ -@import '_variables'; -@import '_mixins'; +@import "_variables"; +@import "_mixins"; video { - max-height: 100%; - width: 100%; + max-height: 100%; + width: 100%; } .gallery-video { - display: flex; - overflow: hidden; + display: flex; + overflow: hidden; } .gallery-video.card-container { - flex-direction: column; + flex-direction: column; } .video-container { - min-height: 80px; - min-width: 200px; - max-height: 530px; - margin: 0; - display: flex; - align-items: center; - justify-content: center; + min-height: 80px; + min-width: 200px; + max-height: 530px; + margin: 0; + display: flex; + align-items: center; + justify-content: center; - img { - max-height: 100%; - max-width: 100%; - } + img { + max-height: 100%; + max-width: 100%; + } } .video-overlay { - @include play-button; - background-color: $shadow; + @include play-button; + background-color: $shadow; - p { - position: relative; - z-index: 0; - text-align: center; - top: calc(50% - 20px); - font-size: 20px; - line-height: 1.3; - margin: 0 20px; - } + p { + position: relative; + z-index: 0; + text-align: center; + top: calc(50% - 20px); + font-size: 20px; + line-height: 1.3; + margin: 0 20px; + } - div { - position: relative; - z-index: 0; - top: calc(50% - 20px); - margin: 0 auto; - width: 40px; - height: 40px; - } + .overlay-circle { + position: relative; + z-index: 0; + top: calc(50% - 20px); + margin: 0 auto; + width: 40px; + height: 40px; + } - form { - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - display: flex; - } - button { - padding: 5px 8px; - font-size: 16px; - } + form { + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + display: flex; + } + + button { + padding: 5px 8px; + font-size: 16px; + } } From 96ec75fc7f358471dd0390b4f9a0d9f547e3dacd Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 03:38:40 +0100 Subject: [PATCH 159/302] Add video duration to overlay Fixes #498 --- src/formatters.nim | 13 +++++++++++++ src/sass/tweet/video.scss | 10 ++++++++++ src/views/tweet.nim | 1 + 3 files changed, 24 insertions(+) diff --git a/src/formatters.nim b/src/formatters.nim index e491928..3ad1da6 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -154,6 +154,19 @@ proc getShortTime*(tweet: Tweet): string = else: result = "now" +proc getDuration*(video: Video): string = + let + ms = video.durationMs + sec = int(ms / 1000) + min = int(sec / 60) + hour = int(min / 60) + if hour > 1: + return &"{hour}:{min mod 60}:{sec mod 60:02}" + elif min > 1: + return &"{min mod 60}:{sec mod 60:02}" + else: + return &"0:{sec mod 60:02}" + proc getLink*(tweet: Tweet; focus=true): string = if tweet.id == 0: return var username = tweet.user.username diff --git a/src/sass/tweet/video.scss b/src/sass/tweet/video.scss index 1e00d39..790b3da 100644 --- a/src/sass/tweet/video.scss +++ b/src/sass/tweet/video.scss @@ -53,6 +53,16 @@ video { height: 40px; } + .overlay-duration { + position: absolute; + bottom: 8px; + left: 8px; + background-color: #0000007a; + line-height: 1em; + padding: 4px 6px 4px 6px; + border-radius: 5px; + font-weight: bold; + } form { width: 100%; diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 552ab89..58d03a9 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -109,6 +109,7 @@ proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode = video(poster=thumb, data-url=source, data-autoload="false", muted=prefs.muteVideos) verbatim "<div class=\"video-overlay\" onclick=\"playVideo(this)\">" tdiv(class="overlay-circle"): span(class="overlay-triangle") + tdiv(class="overlay-duration"): text getDuration(video) verbatim "</div>" if container.len > 0: tdiv(class="card-content"): From 436a873e4b495e40c7394b3eedaf240ac79dcfe5 Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 03:39:29 +0100 Subject: [PATCH 160/302] Improve verified checkmark icon, css improvements --- public/css/fontello.css | 145 ++++++++++++++++++++++++++++-------- public/fonts/fontello.eot | Bin 9368 -> 9544 bytes public/fonts/fontello.svg | 6 +- public/fonts/fontello.ttf | Bin 9200 -> 9376 bytes public/fonts/fontello.woff | Bin 5812 -> 5924 bytes public/fonts/fontello.woff2 | Bin 4832 -> 4896 bytes src/sass/general.scss | 1 + src/sass/index.scss | 52 +++++++++---- src/sass/navbar.scss | 13 ++-- src/sass/search.scss | 10 +-- src/sass/tweet/video.scss | 6 +- src/views/general.nim | 4 +- src/views/renderutils.nim | 4 +- 13 files changed, 172 insertions(+), 69 deletions(-) diff --git a/public/css/fontello.css b/public/css/fontello.css index 2453575..52362d8 100644 --- a/public/css/fontello.css +++ b/public/css/fontello.css @@ -1,53 +1,138 @@ @font-face { - font-family: 'fontello'; - src: url('/fonts/fontello.eot?61663884'); - src: url('/fonts/fontello.eot?61663884#iefix') format('embedded-opentype'), - url('/fonts/fontello.woff2?61663884') format('woff2'), - url('/fonts/fontello.woff?61663884') format('woff'), - url('/fonts/fontello.ttf?61663884') format('truetype'), - url('/fonts/fontello.svg?61663884#fontello') format('svg'); + font-family: "fontello"; + src: url("/fonts/fontello.eot?77185648"); + src: + url("/fonts/fontello.eot?77185648#iefix") format("embedded-opentype"), + url("/fonts/fontello.woff2?77185648") format("woff2"), + url("/fonts/fontello.woff?77185648") format("woff"), + url("/fonts/fontello.ttf?77185648") format("truetype"), + url("/fonts/fontello.svg?77185648#fontello") format("svg"); font-weight: normal; font-style: normal; } -[class^="icon-"]:before, [class*=" icon-"]:before { + +[class^="icon-"]:before, +[class*=" icon-"]:before { font-family: "fontello"; font-style: normal; font-weight: normal; speak: never; - + display: inline-block; text-decoration: inherit; width: 1em; + margin-right: 0.2em; text-align: center; /* For safety - reset parent styles, that can break glyph codes*/ font-variant: normal; text-transform: none; - + /* fix buttons height, for twitter bootstrap */ line-height: 1em; - + /* Font smoothing. That was taken from TWBS */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -.icon-views:before { content: '\e800'; } /* '' */ -.icon-heart:before { content: '\e801'; } /* '' */ -.icon-quote:before { content: '\e802'; } /* '' */ -.icon-comment:before { content: '\e803'; } /* '' */ -.icon-ok:before { content: '\e804'; } /* '' */ -.icon-play:before { content: '\e805'; } /* '' */ -.icon-link:before { content: '\e806'; } /* '' */ -.icon-calendar:before { content: '\e807'; } /* '' */ -.icon-location:before { content: '\e808'; } /* '' */ -.icon-picture:before { content: '\e809'; } /* '' */ -.icon-lock:before { content: '\e80a'; } /* '' */ -.icon-down:before { content: '\e80b'; } /* '' */ -.icon-retweet:before { content: '\e80c'; } /* '' */ -.icon-search:before { content: '\e80d'; } /* '' */ -.icon-pin:before { content: '\e80e'; } /* '' */ -.icon-cog:before { content: '\e80f'; } /* '' */ -.icon-rss:before { content: '\e810'; } /* '' */ -.icon-info:before { content: '\f128'; } /* '' */ -.icon-bird:before { content: '\f309'; } /* '' */ +.icon-views:before { + content: "\e800"; +} + +/* '' */ +.icon-heart:before { + content: "\e801"; +} + +/* '' */ +.icon-quote:before { + content: "\e802"; +} + +/* '' */ +.icon-comment:before { + content: "\e803"; +} + +/* '' */ +.icon-play:before { + content: "\e805"; +} + +/* '' */ +.icon-link:before { + content: "\e806"; +} + +/* '' */ +.icon-calendar:before { + content: "\e807"; +} + +/* '' */ +.icon-location:before { + content: "\e808"; +} + +/* '' */ +.icon-picture:before { + content: "\e809"; +} + +/* '' */ +.icon-lock:before { + content: "\e80a"; +} + +/* '' */ +.icon-down:before { + content: "\e80b"; +} + +/* '' */ +.icon-retweet:before { + content: "\e80c"; +} + +/* '' */ +.icon-search:before { + content: "\e80d"; +} + +/* '' */ +.icon-pin:before { + content: "\e80e"; +} + +/* '' */ +.icon-cog:before { + content: "\e80f"; +} + +/* '' */ +.icon-rss:before { + content: "\e810"; +} + +/* '' */ +.icon-ok:before { + content: "\e811"; +} + +/* '' */ +.icon-circle:before { + content: "\f111"; +} + +/* '' */ +.icon-info:before { + content: "\f128"; +} + +/* '' */ +.icon-bird:before { + content: "\f309"; +} + +/* '' */ diff --git a/public/fonts/fontello.eot b/public/fonts/fontello.eot index 2b2982a5711bffac0cb1aad4690f661e34e308c4..8671134d7bcae39b2c2b05872362415a5a3bfbe7 100644 GIT binary patch delta 957 zcmYL|Ur19?9LIm>oO`b`b@y)WE(`x<TB~e?$%u>^J%p1z^bm*;*_@jjxj)V8G9OeT zd+H&X4`WbBB?!G#10O<q5>vuA5dwpH2rfQ^!-9h5?RW0gI^%P`_xn4)o!{@AyX6C` zJyLfYz+CH$<P!p$Yr>2_KY4uRRS|%^2B3v-R%-@xs5**0njT6Fcz=%rsK3xw4~0h3 z7+27LLA4DH-;Z{WKkfz)f&euTi-ZQx?)20EIM9PHhJlI)>^b^}=v!k6ZR}dF@dy0^ z`WwTka3IuZKNiIR2BAF>8cUN4_5y&@gT6T#N<`M5);FNXB6K~S8qq%VZS`UUb-2Eg z&P37^i(QMjw~(77AA1WxFb*&a%zSD`J%q<AfB-R!SAz{G2s+apB}d7#TWJ^oI_Tnm zXMUeg1hbuRntyU{=_%2LA?d|eN}T3KW(Lp0;mYl>^OGWOg#cH{0=QOK09Ts@wgGyj z1#rbCIlNiD$^y7nTVN}o<Gph5qCvM?09V<<5BvdN4u`)a-C+T!rG4;3Q1VznEx;}o z=K!9rxFLKZ#m~~LZPL6&Hc#`QLWBRPOn{IeP(r8iTF5l!XI+O~OgeIgxGIR$a*~|r zyh3~(O~jnsY+l)GR@#-lZCMVYz9dPUg9>%KaY2@e+qB9&NhwND24a+rH>{sk;XxgK zd*{C@YAu!Ou!x<sc<E)*(MVWdW<_63O5;mE-c68OqsyAMyv?U#TGmOUu(lcKmoNPU znd#Uje7nwk2WpgaSw~Y_+1M4%nLiJ{a4=M!PZt2#O=K?j-r>8-L#)pz(=OEfex?s@ z!!y_>Hu8vkr(U|ifce;x076vQ5`AJ)d?M9LQ)U6R=yg^xqUJ8Do(AFuF&mHE8xiq; tk<rAvqbV&?5l$r%k))QJQUflUx+8_-necE#h$o{dVIZCvEN-aF_P;fX)KUNd delta 738 zcmXw0OK1~O6g_X|Wty5e`81!ZRRaw++A5`mRIHTZLXC^kMRC)GO`BgaX@)jPmC}lI z(QTPc6bv;4aakPbrUpb@6r^qxp@Jx)IOsx4;zF>-JL&5i&fIg(eV=>Z+qzbaDaA1G zt8-O}a<y9DTpcbhy>?~C0k#fE3sbiWAvA~oJOwqSrE`<#{@ellS4le4@i~orH|buA z_H_1kYW(A=p8)RxoC}#`JaP6-_ybLOLiuQhjFx5j5$R8)!<k%R{?cBf0eCp+(QJMy z7VlU;k|1M@j5E3Tyv7F92c++k4$Z`K$?b<7$4ReIf!kVsuJH87&=<gG6;jn^liK3O z;0Dk+rmwMAS60Bt2kt>`vL1?usJ#_%Tqoa7p9Glh-AJv~D#XMAYp!FIk07GIkWSME zim@w6%nfHVS8x7-HeKTVOVogAHDRMD0EM^!T8InKo-hT_N?gFI%oKowxPWz@DS*So z1)vfa=!1)dfb}A#06d_U_5m+Yqr)tq&8YJo*8NO2Wl*JxVUt;3w`U;8f~>3jnEkEn zO_UigSJ*#d6<T3zDfjJ$-~El9*sNF88?KAL;gU`38omx|rGwaL=tsRLgN;Uw_loPc zQv1WFk9z%j+}nRa8U*B`u%nnj3E!E-LaZby(q-wn+%AvEZ+Sak<aOJK?Y%NYbFJ6( o8SlIfnfdh3{)p}j9FwK|O}#4+bMWbzRGv>x&n93j2VScG0pvrdS^xk5 diff --git a/public/fonts/fontello.svg b/public/fonts/fontello.svg index 2a64343..31bd38c 100644 --- a/public/fonts/fontello.svg +++ b/public/fonts/fontello.svg @@ -14,8 +14,6 @@ <glyph glyph-name="comment" unicode="" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" /> -<glyph glyph-name="ok" unicode="" d="M0 260l162 162 166-164 508 510 164-164-510-510-162-162-162 164z" horiz-adv-x="1000" /> - <glyph glyph-name="play" unicode="" d="M772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" /> <glyph glyph-name="link" unicode="" d="M294 116q14 14 34 14t36-14q32-34 0-70l-42-40q-56-56-132-56-78 0-134 56t-56 132q0 78 56 134l148 148q70 68 144 77t128-43q16-16 16-36t-16-36q-36-32-70 0-50 48-132-34l-148-146q-26-26-26-64t26-62q26-26 63-26t63 26z m450 574q56-56 56-132 0-78-56-134l-158-158q-74-72-150-72-62 0-112 50-14 14-14 34t14 36q14 14 35 14t35-14q50-48 122 24l158 156q28 28 28 64 0 38-28 62-24 26-56 31t-60-21l-50-50q-16-14-36-14t-34 14q-34 34 0 70l50 50q54 54 127 51t129-61z" horiz-adv-x="800" /> @@ -40,6 +38,10 @@ <glyph glyph-name="rss" unicode="" d="M184 93c0-51-43-91-93-91s-91 40-91 91c0 50 41 91 91 91s93-41 93-91z m261-85l-125 0c0 174-140 323-315 323l0 118c231 0 440-163 440-441z m259 0l-136 0c0 300-262 561-563 561l0 129c370 0 699-281 699-690z" horiz-adv-x="704" /> +<glyph glyph-name="ok" unicode="" d="M933 534q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="circle" unicode="" d="M857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + <glyph glyph-name="info" unicode="" d="M393 149v-134q0-9-7-15t-15-7h-134q-9 0-16 7t-7 15v134q0 9 7 16t16 6h134q9 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 11t-6 20v26q0 46 37 87t79 60q33 16 47 32t14 42q0 24-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" /> <glyph glyph-name="bird" unicode="" d="M920 636q-36-54-94-98l0-24q0-130-60-250t-186-203-290-83q-160 0-290 84 14-2 46-2 132 0 234 80-62 2-110 38t-66 94q10-4 34-4 26 0 50 6-66 14-108 66t-42 120l0 2q36-20 84-24-84 58-84 158 0 48 26 94 154-188 390-196-6 18-6 42 0 78 55 133t135 55q82 0 136-58 60 12 120 44-20-66-82-104 56 8 108 30z" horiz-adv-x="920" /> diff --git a/public/fonts/fontello.ttf b/public/fonts/fontello.ttf index ef775f87308c85319ecd0d8c36791622adf5c7eb..0c04c6c934c632486fc0fc56ce07b794a1fcba07 100644 GIT binary patch delta 918 zcmYL{Pe>F|9LIm}z1eYfb^hqgX63)^-&UK5*$^0I9m<V8cnCy@Y+ZL<$^FB+iFwe7 z^whx`53*25BnVxK1rOp;%m_P1frl=^;2~TrFle2AZ$@i|&->2z_kJ_K-+OPiZ_bBi zS|^XMy(s{Yw*WLB9nCd?bLkd}6weGM`&)jG1L(VGD+ePZ8H_9FzoB{uhabkf#-DZp z2w{LKNW>xo=eKWF0jTJ~NMNAsF?)sn3HlR>WNz$6kNpSzBKq6I>1Zg@ARUWi0D}-r zM#eJal9UIi4xn#JMUt_N7j^aMu?XGBq(^e^dpCQqfm)nj%VcAjiKUJu{I>9H_hU~U z2!;V>fjLh>)Cyc(9t22Wyb`=XL9m#m7S&>4r`)yyEU-NPo%22)2xeEqS$>-p^o&Qt zkZ4|`yJk1_$(3%(ezs<Iu?v$P{1yUSMHk>&<^o*3F4zZH<u1S#yX5e0EuRZ;#X219 z1uWcG4sJA9k_&K^T{?k3;NfuiAz7*mpcePRB|$OZ0yO|TSeyg6yh2d;N(vXnS?{E? zjckF=fe!VZM`Qwogn<$|h5JILFn?4#q%raEIii&jvH3JP)qa&2ZH>ek{A@wlYf^&B z-j*zfQD2i3j$wuR{Wu}Z#P3*Tfut2BECaDi_B+<ce7I1xQ)>TLMX#Y^Ef$eTa|^vf z+8PM!&8}JNDRF$|=kf%(`(QPfTixPAVO_FFgYe;Vs87E93uI<toAAAx83$^Y%uh<= z$&$Szv^xI>pE$U5o=+D6*d1iv^sB~o9%8+AiFTmQ?PnTr4_?9+@sersgSOB`2Fze9 z0tj(o(_?s2o*A)DeB>0+3)8BP5#4;Q1t{^G%i2-%ix$#o`o0()%0`D{!cZ!n7W#*> K1BIZzD*XkEM#mNa delta 811 zcmXw%Pe>GT6vw~6`OU0r+ODg+XrZx?)Rtz8frf@ehfE0Rk95c&S6y|>aVOo(Lfi-s z9*enDkeDptQCjd)LWgc*9qLjPbtoF@P@zFZwtZ*%Im2h(`@Z*^H}C!CH`np4{boG- zEI8Z^h($n~>>JNS;a!n~G=TwY(Clsabq$Cdqbwavj9HAAQO9ZhgTq$`y5H^k3h4Df z>Euu{(ZBCS?OQHcV7_UHfud=7pZYy@?U0$7II>YF03n_F#BjQ=BT>D$t)GD|2KJhX z2}?8w?owZ;j*cYE<m$cZ8tOSVxN4=xG7rDBeE`C4Bg@uk(wbUoT>^G?-TJxGJ_Erp za0Alw>S?!dx*6y=!?>Tf1~_HkV{6539}YG>?>)hM3^9AVwuc3L_DSux-J=x;OTFy6 z_XH~Jv+_We6FBY^2CV`<`U(`$SFkD^1&Zk_+}<1oO6V)V?>P!=rLRDMzM>I93WfVi z90fw)Q2#+Wu*Q$Mpdx=<e<kW3OGkp8`tpVpjdh{sN>M3x&p!}<avMGKLZ8oxzw~l^ zBOmqM@#oFpCy{-$z8p9oJoE!W>A2hQ@vt?!X$yryN{`Fa)ZX^Gad-RWslo*wjs488 z>2dXIyrlOCyOp#Bt~*EO2(<?%kWn(tGTd}lu%Cx&TC`TiZmR-K=)o*L3r$4Dtme~> zYENX9?36F{Dt$^{H#&{izBaCP4@5ZMP+lTM*lvl$cEA`<C9jMbJe#8#<I?4HCRx;% fHqGQnMoV9`vyqMxJvB0r)_YT<{m8$GJPrH<_3pM8 diff --git a/public/fonts/fontello.woff b/public/fonts/fontello.woff index 63c3c233509e89b741c92971efe70a441b972b8b..e4582ad0ee7148607d5a5e183e595a785167af4e 100644 GIT binary patch delta 3442 zcmXw+XH=70u!g@R^w15xLqMcS6R8^MMan@CkX`}^1W+SY5_%KF&`UT}0R;pEsnVhb zq*sw<s7e<Ulxl9yUH9&_p0#J*nb~`O&NRuE%fW)p_4NS|Ksj7+faSj%UW|j8U^@T+ zoG(VKZgdYq3sdY&N(4%QnF8P$=!K$a#Q;FFOOY7e<W9o8TptAj0R3}HnC|~z_3{h# zq-ePSz#s$wOn_lnilH~s)dK)7wNi2vDX7Tq8#8%RM2faU@r5bCX```_Qg0MG1OQ-+ z6hW7QmA(!6+%LeLk_fY)_%i<?$SmTCat)z;40}THh5rMj%AD`-ib7H}JOF^ultzb# z3;6{C10GRqPF6}z76oFT-GWWvLnNh^vx?$}QNUxGHJU^4?k*k~jvx#cXU`msVALD! zH(}My)MH&GI6QZLUEdu`?R`~mF}4ujc7GBr(TM<gj0}Gg(K3=0bTakTDzn>G3m&!q zz0)t-=qmW_{!8bp@-@dulHu2gZ|#CejiRixr{Y=WFC+GPHPoLS;<?}bA#9|Ilf56m zFjw*Ei%M=@=KH9!R*LFZGm-|Y`5_~l7@qGz${$BI(XYJ|0GFeRNp@VqS~P}dz~&W% zI@O-iuXv3SP3k=@69M@y5SpN-(U<_Hr^QD2UHZ<0E&9`WwP^QO;mwg}tE#`%ZqRBd z;M|KGFGKxlcs%Ub6?xe?h3jGcUnZ)2W|ct$#_0{O*{$`(W7UZg$x;<&%4lorhN`(S z<=r&XR}xL#bF~xd@ltoKFUoX2%>Gh8bSLjT3I5U7*%khk^y?fms>3b97rZ%mh$3JU zZ!eTvNlOYxx57d4w&_u9&2S6jetJDNDs5QH%3a53nf~-dvTM8v!*{U$0P6wxol`gy z=A*e(O}Ba3=;!atp}DqcV;WR+f_}-b*@O;eo(M*zJ4R+)jyAPQUh82TkgI=+tQ=zq zasSqXxX`{J^dLw+wU)abW?a4jhcj5`gx<f`eWraHY(LJ76E#N1Z4!;O%Nw&ffZCOy zJMrPg0GgeTAwhSC;NQ(Azdylo<zFZ~wK($%2mUt0jUT73YoF69IhP&t$u)nBBaYha z_dh7nx*SgmVmM7nxyay+ABm=JZ!0{70Uamdj(vKW<>*VOyUC)OtW&~BBpl0w4XW_a z_^lGyd`oDNCbS=R<{d1;yfgCi%u8vutw_1$C2tuRDcH-G8PmQ~kQ+7ru|Ua-<Qxx4 zczqk1;HL+qF<7zXnor#p=Y)DdrNe_{&1@>_ly};RjGPFp@pTV|I6M^I;Q=d@j~<FW z@~k8MctPGL%xo#W*>ZpO4f{_$m)}y`!R72?AsqLnc;{P#M7XhuE=+fU)y;SV%*N^( zHJnb<8tZPN+SI9KcHbntV49zaNAXl?7e=UHfe*^9IEkd}WhttdQ4MSJ?DhyoyK$W; zekjZR1aU50Udnax-3=4P)jHto@ItCTHo^+5xiy4jne~@rjMpcYuolj@o+958Mob!P z%Q>YtcJq-O_^e9hVISYRA?F%AY47URxwxad^mvJOrr=A58izT%vANZIGb3E?F}NyT z+5L5DgB91<@y-KLdk=bciQ)3B@_sa`8=;lb3gh~R^ND|Io0UbbiJFwC0zA*ipi>yq zAKrjl7Vhme6piA<>P5mWlQOeX-V*Pv%HqBmK5F{JNDaa0%s8;q>)7TD@xz8#^_`^d zUt5@jl<Zlu*G1x#!6mTYcf_;%?^e$q?e2DV?w$;~IPTe!FrkrBkV6Tx?xD&}-DdTe zzSE$Pt{bE5Gy5SMCdav7#d!Cssy`>;zlsO)$|xbaaYlYvI;~jfrvD->oO?g+NPZk~ zX}lTH%V;UI7H2t555t$DO1|$2*{j)Y->E&oc&*za(P&Ia?pOQJ4otw4w9DPCfp5uQ zj4$9tg+k&4B9T~4xcg0lL9r{#Pdsg!crO~pje#oH^~Eu;LS3t%I^Hpes`|*{(mlf+ z$uu_x4{SDFspO7$w6NgXKdNpUC-_1ihV5E?U*RR2@(P+x`IHr)#N#D@JbEJCL(Drn zqu;B{JAuuWhfPk5pz1|fjW|2qFYjd8tKEy?OuVToR=tG#a%C2>wdZ!)o2NpXd_PFa zv0UFvXjkY5*Q=wmKWU`g*zBU}fzumZ79}<u)7b8=tBzEasZQ%mu1uz_gI&+UnKR%I zd=@p|z(t4Hjc|HINfLkM$k)-Amy&eTaUSa2iszti6Q55!dd##=%jGyJk!zC12CCDJ zTQLuV`3WNR%X;2iTi9yzY0??h4gN{+ssT}3U7+2pHg{}-IdEyzW%5+x2yzrG`Q=*G zZ!A2c(0Xn@Z*FFCj_4g;<8|l#-%n1J_J+@ur*8-7ydP^%0CO^)@rdrMyrUX%hgU;e z5oOQL-(YN0tB>CM|H1f`2+S=ijArtUOm}Yvy*FQc@OWVNHJPJw@y^~CPfd&{dORa> zFvkdsDxi+0(qTM_amwM`m(I10on){Og<!MN9_p(+F}LV^P~Xw*DKUvF=kZ~wvh<Zp z%}>QFCQHqs%4;<J!oBn1mD)yMO&(=6H2>V+h_Zax*ymo%vBIj@4wbkNYyah3s>`Fk z(t4F{fGL>JxTW}Zb-&644_kVGoX!X&Sp-&e5xvF^*h&r(R`WNBhQ*BQ-1<YFN7&}R zNA{`I%gu+V1FgES`R&eHm4cS_)l#nY{3I*aBkM;>0jTqAyF)nz!%Xy4GxxFWN)5ku zk^EY}jPvA}&BMexk^mj8Vl-^{MO&5s`pW%nGw?M%v$9f^<4>_6a8kA{AHThAI8(p< zN!Z%X(PRj-eZv#chHOX3&(k@=2kg?@q{d2{9P@y=1KBVdwJnLz*MI0V)v<}b7m7HJ zq|yqUU5;TWd*PbwSPWRMRIE@S%3sX;ae_8e03xYi47|Y2rj_Bw+h4WM$TSQq^oIyc z^#~U6^lPWRstzFpo9>cii;b7c+Pcdi5(Bb!`2q2L_HSmhe^L#RY^<!8u`MKTiK(0E z97C77iUa!iKQvCupsm!4|9)Ng=7b@RG;qF&gZ2NOE8A?gs!@|t_~Ldw&a{v%PO<1) zmhPGul1}-q>XR~)Rv~RZP|=|OuotvVyY#KmS{|R6l8hHzs9Z5fe<V}k%f1XM=C)eO zm3JC`@q?~B?~Qa>)@~6rF<5^iwgIu5=)Col<aq~vIgrk!5&SrQS$r}zBfmj;%W%T- zVq74B<)(&CrN|uxM-wseTXs(D4(m9iL@1oiMuN+LKc~J`QtDVct^4QR;iH62T%%I# zHlzQs?5V_OE!T9B^?U#Lg*;2AmRVE9d4`tK{9E;aT>|$-^LgZsN(gp@zWn?hjTCJt zCMX0XT9F(?G|Q3~NHFDhx#7=fdIM9YY4vczbXGF4Qt)ix^+BcAGpiPmM!}C(<wrY7 zUNbDis%bkdCND}X@BG$Sy}G7uo@t!$Om<YDplxdWBqs0WMdgTQ4cV!btKQQ0rCtP6 z?X4@iYd2>KgncF3CR@5h<mYs@(e)BZ8G9URdm;#b$h|1|;alYiXv9nz>U<s5w( z9no*#;vTVT9mMKBualDhd)7cZO1~}1?)|{zjit9UYZ=|$zY^3o&#vFZ{8Hu*IsJUc ze6f8nAT{@tC4$OVQ)pA94{OS+k3DJ+K$m*DbHq+z*;^uGwM^>;v7EZh*Gu2!ypMSP zUTeZ(&`<8e#3s28U(=@8>#*RTQEn?Iu%hTW>kO8ex2OW&O8vz9#I<>Wc0=K*P1cpC zPhIlPaD+5v<m`+4uaP<5t+m_?be2__VWTc8Jks}2IG2u^fuGl5Qn#J97h06aV7XW# z*5YH(ho^T5bzvRvUSXLlr1OQK8%fdB?RNv|A?dI2Ew5G!LRaNF5M=E)9tO2o6NyD_ z1+D9OAV-&Is2=!ax{CKftI->Ct&5ut+$q$IgWt}mjhIoNKY0e1q%?T_Jj>}}oPXig z_MmTVHB)@nm22)5DLiofr_w#Q14B}iv#jpvpQgvVNlxd8{4;Fa<UHR_^;m|P!CD?m z-Cju<+ROTRll%OAi>eXydVtj5{(Xn(jE$BoGWeyFDirwN!PmtFaLdkEyuNyZ>QdZS z>IRbKHJMygV#dkjzoQ-sF3dC!)wpvJrGUwIXbJHDHO!|)AA5RA$>bm8Q5hOW1RG44 z0SjcE(nwPt#q{>{G(|^6snH~I0SeDYW#~RIAQlKcJ;!lTQF>%Dw*DB(%4hf#e58D9 zN=YDcE9sn^^NH7Rl+p{}zrd10nanAR0^kDt3v7YtK=GhGupGDw0zgzC@2CJOPpSoK z6>5L#=QR8@kpPI2%R$)~s2T-fc_6IN&V_e}v_2JNXmj^b#eCP`;|5(34DW&SQ!qP& zxRm#lZAmXLpI^g!L0bL?1Rk!Y*5se4P_y+6)1h@=hP-QWt}aE>8$$+a+h_SH?xZ~1 z4ur(~(vkNSbKEq0*mT_Nc7Leni1j_Q!_2aybb<~sGkMI$Lrm@NXHrc2>=%0a1L_@G MX=MP&xXu~zKlz+IHvj+t delta 3326 zcmXw*dpy(c`^VolhdG~_%4s8~%pvD<Vpb@J<l`{R`4~kJo7J4x(2yzTBBXN6c^Tyt zAv%mo4b|`=<ed1f&*S&I9*^hazOL7O9q#|`s{+xiE*5(pi3HdHmPs@Nf`3o=Dp|}H z{{#R)*ipf755H?Max6ccb;HX-fCUg36%@hJ@&JHynI-Z0B6j10Fws!}z@5yxhWsBw zLE+Z}SsIDO008h|J-!_O861G|2LRCvtelf9^wcL6J_oZzmiBjs918`mWIVV$I3gyF zC04TpGZqe(e-01BBmMp+GFh?eUyjvF`A1;lScApHSh3t+*z^VNUBW~JurwV2V2fcr z^!4lFW71KP(JWv5Z@+97)W#*V`KT)atXc^JRvgcwD6z%3C9cTN$3GX%mWa<eB|^~T zvz8?&6Ny*2B}xXs?d#Q3Mr{M7>(rD5ljHUwn021;o9>^i!S8MnNmY+rYCar0OJ$Vr zdijC1x~keeBm(vuMee*;${f*~{>bE2H+C^Dj;{Fe@-8pf^%5!m$v0Wt^+c1SO|@zZ za&`-yXM!*j9t9gxNSqRhPe>h!fWXy~&x~RGiJH5y82@a|bre)ek@RUX@$q2e$^a32 zGFFY`{h;CGH2`SJ1#xrXBf~vh#k+sZY>$m?HI*Kk7-!AV$F`zFl`d`gnkSq?QO8sD zk`?&T_wt(U&%Qq{)&V)iIPH<5%N=01C}vdi2&v-YVsCvKZS;`)PA~yC3r2}P+G*}l zK-N6ew#PzU=AxcD)6Fp5=G!x|``FRl;x9ivdm&Tt7T%RVMUejZ+2h|jp4@=U64_}! z)|^N>(LzPOO59oq;`oW^ah1DK&K7o%x`0q|f-HuhUy80`=lFLN&8zEo<uj?{|0Gj< zuADskQ&esTGZ~Y#gDPA31<Cst>}et$TJF>gAW_$Dx;!$tX8bipDk&**ICD5D>Ce(; z=#xV|^wVzL@i<X@D(B3j522M#{N6T&Kf6%ga8>Tyr}19E2JyGORKEoqQ*1_gglLfb zht0HlZo##}O1@y?wA0gY<8~da9v_WqY+mA(x*f8HfAHbRlE!oJm?@qed0^bzZT+ED zf{xoW_9e$rE5#wSddWdEEJ!Ul%E<4b%0n*8BNeZU6z1?BUfoe2nC2KoFsnMtAni`( z%MGHmmAb?@d5ZaF?t9WBEmeo_!d>!)^Aucev}Ox7cBMD1Hp+jipQIV40*bp(Mx<fy z2P&_{X-3vb(_2lPi`(C9EFn6x$In`Iz_sgn;}t=|gXnJV?8xv@PcFtDfACi(=XH?* z^@mMzvv`fqWR>Yoi^f;+Lm6_)cJ%N~PLS80z6Q2ZOR3iEd$0_LO?lu!>j5oiLsUcw zS5bE$Mz#t`NhhRpTkt{9Fc1t@&oRNC6m<fUcE9d}%1`EC!IOr11pL5lAPZ`C+padb z{U#(oDX%F?-k&bM0k$cys(txd6JO~m+;%k!uQUnRF0Cr%?k?WxTdeG{&H7TRwY+BA zFyX*w_UK}r0RQ$!cayrQgu4E@H}RjgVuK%s7>9j%qjPf75#8lPZ8mmT|3!((uIo!( zhMIa&ov0lj-W!RyZ_l{So_<qsq^Z4q8D$bVnEJ$(u5FtG2~k{Dun$~J<5<MJtZYkf z#Lq{EjZ_kPJzsGPXDAHW88JAdEX>mNrLshdNXK)Tj$$b7V!OhcxP&g#Hxdz<E&cAK zC}rsQpL@12NU66rkIzxe<F_%d!h0uq86{VE#>z@2k{CK!lL7f7A3}Fuf0oAKr2E2r zyp}$F!bRLxP1-;px?fQi{GaQkmA?MGig)rOD@HPYaObaT?A;EAI>u6Yb$v={Ax*D< z%W&s7c#H)9LSnZdN|||U2W|znxB5b85x#Gx%=0Zrw7EDZ0Cpw&`-joDzg?nzX>Nse zS()ltr17+{pL#Oqj;)GOD3=6A>U2j}-}Pz1!XqN6h=2Vr-7m9j&Mlt5yBFt?jt9Ls z1ifCmT<-hw1&SX7#R);g^EGR#okfg$Y70Paas*`_t_-<!o9p6PiwC|vXIriIm*W#O zp$--K$E6!cA^_upR0i4pZqDO0(pjF}I?7{6;6MF*u9`2}0%=Wu()QNMaM`j?WeIvH z6Elt4uVlL06nOE#X=`nl1g<=RH!f6nq}G-S*zrN0^*^vt`e}FS<CsgpFCqO4OnYno zaG6*6w4w3BedIsRr)BEvd`E<HVCSf5uK8BU959u}LFl)h%ZtbMNk(sib;RuhPGd}k z_}VI32wt-bw39g#lK~Z+)-@Rw39tS*ES#}W;FIVANn6foWG?h`mG%zI;oYNVcjD;h z(;}TuBATxP$9CcWG_QHhq2^3^E=q?|Yc9BEZe6Vu+-wPgE~jr_3hVnaRT#s?P0sIN zM1;!3Z(FTel7Sa|ody~=e?f;9K?r6wygT@qvuNmC!ABhQ=Qx}6N?Xu0zvNNx*iJ^( zO0v=NHT?t9@JdK5wo}F&FW#{rH8LzYWPzh_6WCwy?Iu;*+U{u3Q<9^39AMx(=cAB% z4NBEp22<t^`c6~9uOPAPE;ZEK)|ob*vG*6Zm?2G&&XTs{Lkm&F>_*Rkk>W0s`0}D@ znYAOqY3#|HG9SyjZ43_O&*sSsDdFTDR@!n)u2Ra2ldI6PdB7N6b*ZG{cNRe{lm9t^ zW|Q(=@LXu>QMH@ed67_E_jd_0W2U+?goFwGKEbX<)4J`F>CjKP=ZRi<vB#ZKbl+z# zo3(Cw1Ws^SDQQi(eJP*w%V@d+)xr#tIWA4Ub=4kT-sXb$6v_4RxFH@Hx5v3BDrMY* z1vxqv?)zK9+(#7f`l<aCy%}KrT7xh(>i01jcwkq7-KBZ`8(r-UI)*V}>%Hxp@1j2@ zVv|a2vO3O?mrRe#^vHg9?>_5qeZM-yzemw&CZ=jfn(4@v$qaT4s<#omYs!cc<yVfD zoW5ZUhiQjAyh-928-L?DwD3l_EO2G*Zq|v7>O^tDos#lD_$K}f+}P$-S9YJqiTtX* zi*YULrIEFEy|Zv@7xBd1%s`7dQMVp52Jh}x5y9$te%ffLl8%|dGwI<my7hbHHKD?P zpPCoHu9$nGmC++mtLVEU<*58C#OsWenNw;;zqGJm49A>&v)gLQ{;2N^f<Ck*L5_`o zJd$3Ni@iPShBq{H?eTr_po{m)9Z1W5NxE>2mH!DPVPR!LGVXAe2)ls9GqqK3xwJNz zIB}*Tip=W_;cBTzC3SHDBjk!Z{gVk<(g~QXQH!UcZ5NTlgvJh7o9csFyLA-I{b9b1 zG^M`lgzApv`Lex(ky``IHQwmtB|gr5y+67uxiAGAyy|8S-6|_b@zc&_nEEP`zsqqB zW+&fpU7l^K?pKMZ9oJ4tjmYeuDaKI=Ep+3Riyfq81K+Rgzb{9JuI_r=VKnAO3=LSA z{2rlf*|YwpxCqI5-oDY==wD;hdPCzMCRgdIl(b>>-AMZ`Po@zXF*aIPa<8;=dTKMN zec?qAeoTLDS0Bf-?P@k*d+va!)AxP273Y_Gl{Z!l^&s+rw(nXgyiHQvYQ-oXd%H6+ zuhVSWeJtYWtls>g$GLatb_aP(fP#wGU~0QU!Fx%d)dIEWgYs+SEcJM@0Y*lyOQWaf z2~G^d@>RIK+trYJVeh_O|1A5=mw^H)5rdTRr9{ezoz7eN+Qxm|6dvc+4uNI$=cLr} zeSX#6600FQ&I^&PuA4~=qWD14K}7_?y9wP}Ht&kK`z#}ZKaqx*G$)%(Im^<?!!3^C zSJjc@8k#*R$uHa^R%lTj=z6Hhdo{t`=(g0^Q=gmTy5xo!%chUs__+$%BzboO$d`Ck zit1}Mo_1%le;Ru9?eoK({%1d)&9AQ=+`SQM!zJL~A^7qsR-w3KWn(jCuXckpDTO<j zE97EAKYNHOL?r6-b3bcenMErKNgKNSQs6rA&HRRg93A*?C-w0GW<PuL%=G>Ghz<A} zl*lHAiCc8g6hs~#9-8O&-7tg>;=lR$fHFV;1uek+|3w0OrF}HoeOA)p*a=QP3t=%i z5Rk9Ld4kn6v4=)$CfvMf$eAt)tnn&Ku={ezUBdYI?DzgxVs4jZEsMGZ+SEO@p(zdc zhRG#xg290HIzMZ*{?GIPAD{$SWCO9uv6X;iIYCGe4GaaJ1HWa5vL~`{aGc|q=KQ<A zv#}6sB8U~U;VGta41^Rp;)Wp=HyZOXwjGhL{PH~fAk5LivfPRz^T<&hx^J4PfWd6? zNkrTIKKr~#8(Y-`Z7oXO-IF21b0~R9lj@<u>~#d+Gu;x)Am8Pwd;c0Rho($yoWh_N r25iU4>y~-7&7<KON|F`dsz@p-k}{815Z5fRY!99)vsL6)N#Onm1w{Mr diff --git a/public/fonts/fontello.woff2 b/public/fonts/fontello.woff2 index b7541f0278434295a1178b8d5a6adff4deea570c..d2c246d1aa701c85f3438d4d3baa7e3a245aff90 100644 GIT binary patch literal 4896 zcmV+*6W{E2Pew8T0RR91023ep4*&oF03@IQ020Um0RR9100000000000000000000 z0000SR0d!Gg&GJB37iZO2nw12mJ$mr00A}vBm-0gAO(d@2Z2TmfgBrtAyo%z*f;<- zb8SadX)@XW9}=`NLb2b0whC8LgHzUc5`?&*CiWn^qifwJ<%Z;LAzs8v#EOFKPNn~= z52hO;R+Mf|woCCXPhLa2<8JtwTesd7dg#JeOFhceLtf)^<{&h8${&xAWEP(9*M8^8 z7tE4Z)1+!iBQ!h_iELv?q$qYP($_$pc9JCn$`~M1pm7;g0Gb|fn)}<r`wvgs{C9^{ zXC-c8HDDEHqD>hEW`dRM2Vzuh=rRlYewyy?Yx?e;c}zo?YeP4OG@@zHsWJ==q(LzJ zL*CxMJfyZsB7wxQc1iGj+R}3XNP#c^RKC#$$bdRZ*k&mx`PNs|F6!;7rwv<bHt!Qs zT4`B-@DvpxjlLo$`;Jo&O$kJx=ii)l@rcr4!Bj}$`Pp8N#NmtH)x_Q|23*BtjKS*$ zuiHu8*N9Hpz9mS;)}e??_4iA?@4%hjxujGJP&6EWi(<wD7_$HckOPAGzq!)>|2H`T zx3<W_pk(J}C!4&?B#TFwJ<gR9RVD`{6rm*A5`aBdG^R>b$PQzdG=m4n;WTt&iq+A< zO#X6{cTj*pW;uZ?JsUfbS|n{Qqe_7Gh+E9=)neMr7$T)B3<l88D(y@23Uv}d@55Vw z)6wVE3)#Vdh8wiM-g;L}vj4e1QFF&nN6a?MUqBJ;G{S=+&9E1BTiu<X97w4|Vdl>F zr@`n@m>7xpSq7s&!{s9nf(skaSJ(s$*L%kG0rFoSItC^dHV!Tx0z#G?c|duQ4}ysm zw`f2>r9uJm(T?~SM|`YA5F|n%5eA6}NJK#*1`=_QNPt8VBvK%e28j$v5O98cBK4#1 z6Qw_HHbzkw-p~CBQt%iCQRLk}OozNfz5g6o++g&YCel2<Kt2E0!zif7x^JunuYlTb zA%Mv{2uSn9L7`TyLxI#kmBs5F-y@`?<O^Nw`O+WQhjRN+uno#yu3<NaEp5^L<jI=5 z7Rp}hE=}#-6)4rWp@hBU72Je_*d{__s}0PfBw_}_-b11z@7G8z{LjGR=XNB{{$M;n z?FWnEaq~-f=rzrYa&xm4QYTrROj&)6Qh<|dMp$29cQh+!so?(%GLhyj7cfQI$rLWl z2`$5NWs33H1)z@2F}Mrhyd-RI8cbb(_dNd-VT?WQKOU+W>l`5|>dZrYi6m4X9UL+c znCDv1xE|2VqnkI@Mo4z3vStL5TeQjxg7a#dwDUnF$kKuL?8W_4n)LtYR4TJWeOTNG zY2D<_7&O`_$ooK~QHh?8(>XL?u-l@gW6&SSFs}k^FGH&dq=sa)p2L7JI-AS!KNczp zoI>V^E=hgwTqas$8XWi;YcyajYDBgj!R1L(K$s^PG)0kG(n)M+V>;;uQd*TLerxx` zUmM47g7ODqiFt52H`o5tnZy8LJy@eQ&RB<sLR*J?e&aX<q)!BoZ+(Z7Yd#_t@D7FJ z@S!z{q?Lax)J;+FnWTBIkzInO4e)L|6!z1P4{;hj+(Fp95#DW862{6v*ms12`TZR; z@UuaR$4`+Tm%1-r6~#=DWfc@RvdGR4bKks4oQ~*O{bI~)g993}TD?8uaU>WDoh(LE zOQXMw7KE_qLkNz36Ct%17Nb*W5w-(?c^TlE$1#w-q`&pmDa_$2y!XOGbZwAQqS1;U z-Hqw!NKmj;MW_*hI#Fnl0!?DjA`Wd*p+g#UNrxU8^%Ye0Cq>Qgwu4Fq`P@;MSa}kW znJB*_Ph}32zeARyLs$~A8B%^%I9Nn9%uq2$#R3&eRP3YT02PO*I6}n<R2-u=O+yxq z6=uANdX}EHR!^TSCYiR^_$+SBBo9x|F$a;6Y{-=6S>3BIJysgQVLn_=6?gd1+D^q> zuW*S6;c*nuUXabw<C_*CFPN@dLI<p&pWN0Vi46mm!t!6VXFFKd*|I3Hn1^H;vM3|g zD*^c*<XAy<bJ4L%=0G@pdVw_*8kYM~$q3kv(*CA-8`<J!3H#=NdmZN6RUz@U8OJ)n z^3R$`Fkdy2^u-QmSnM7&(<ZXmtb);Olt@*YCaC5qtA+Q6lbPjh_{sEmYP=hdH={qu zcAAu?pguqyCRA>#G9Bo`C!`ZFrdr|5wVI7F8#q&WvGi40<>ker4)d&0Ob(%r$pqG! zBrg>RJryY)Fx)dd!fIGd0l84Teh^(KnbzpoRKp-50*{rtID4GARf35#hmgl8vd-An zI0-A6?S^>>l`N_?Mnwus5r#~t-mUWHvL&p+C0e?^p`@96bf?7?Z`7IDYMLhnL%*(s zN~J(%)7Kc*&1MX;7^^ZXQTJxl-N`r2`kMEGEj9Hn?}POe+l&WbBQ>e!_O+hPDwzkB z6|=A5umsDtX-B0`$=K%k=EiC2^A@ReHfrw?iaFT#aJifFe=)LPE)GA0ZM&$m#@b4m zjV!`(J@zVvh?9=#GOGnH$RhmXslBw#CtK{?=GC*T<=)twwV$IvnglDvxd3YcF3*PL zLzPN~O;Hq;yn-<&P;M8Gi&j)K%utZc0p=)~$72l@IDldfutYVM@l!Nbh>8vb)m&gL zkgT^zQG=i+K`lbIlfx?NphaD@sE3yH@K{SeA48A>3@IyO2`eeqYS7OG4gy(2zyXc| z!8jK<2{e6*B8T>@*+K$UD09^0LgJc-dUJvTK8vMFV5I`<3K<dNMj5zOhP|7j4toSu zJm^R`s=%p2W>k1qCSH_@sWS1Z1GuTcT}3=#=EA44@TDw#D+@n5fUgStRGR*1m5bUL zN~3PHtd*}E)EXP3@1i#-yX%KX>XW$66cE141MQp`S!0}Lgbv*llnL#NEuzCj5ugA8 zEg%DHu&Ax*^~eD9gd)J0IBN~hb>vog9-d)n;?il)jHjosoVJUZ(`N=HGt0>gmY!;W zjc4j${iT?uO*4G{qT&9yJaFKU_^wmQI;JR)mco0U*~acsL9s#Mx9ft)1yLM%UhFN6 zzdV^b(-_L_+y#)-y6txG9yIe0#XBK87t7+eW!tL4@I$_}2SQfp*;2;%Bdf`!*J^A( zR!|&__!n6$`TiHa<n!pF{ef_Yb{-nnZL;S0_bN<@G0@1z5pocda@Z5~sk$}?GJTv= zQnLw+qag#k7U1@XwA={A^fgvhhFe{`<7D#5rE?h(6dG9QWo7I(@`yCB>bi^=o7u?6 zH)dt6p8x;(cjC{Fe<WEC0iW^R7`5yvfe1VqUWncePIlV-K)4qp?)kASUbv*Mi-hbJ z3*3~5TsMkPJrtiG-)L*yj|J!?C(b!mz@LD2enAK%z^jP8v<!>IFmyC4Kec^Zw5?|e zRZy7w_JMZ3<FibQa4nYrk3`@g4P|_WeRSdBfW1g4+@nx@cDY6MV(9ho!lWEz0}JY; zZo>}?1k+$1hBR7$sh_HLmjZugiIWe5iI6!zxj39mPDg!TMrrM{su<(rNcMg^1G%9F z0!cK{4I|--)P*hDSV44!aBDEdlS*SPN$GPAOW7?%0PwhoWO$6d{Mz$z-rKJ*d_0rg z+Wm!>HwaO^D}Zt+i9B;*v}R<tV_9(;_V3UiHETk-&=>%E*BiiBYVsIxE6FJ2nBQgp zV=E}tRn@ts&ehJjsOPKCu@+<&!FiM5n&Ok<<C*-MoNp}uNZ2<PWs%vSz}7>DngKnI zSp(wlnFM&x-l-;mR5$V@u~CnmmDG)yu<E>J03mp}9eb_MU{maWkzYhvP`W^vPY{!t z$iq_`pRuPWRyTJIc+5SjB8`($JBHtZybvbuj{ctypZ+WAq0@Vc*ldD38wulgT<1is zkPvnk$KS%2^NLo~Q`Fbz<K^#!sOP?Ict^|2tB=d`-6;978~LMu2a!V}x&$38;Vnas zAyJ=exTVk2CmQb7H>D0zci0me@jACU#Zc&J&uI~Gok>65#9dt%%Mj-X$1tu&1k<$& zJLa}MK?>6*D2Ndhzz9&s3o@t5nB0km99<*9HAkav*bDv)GM1L3iR$VagB8LY;c;PJ zaD?5b6yXFT;}|QK3Y`el=pACP5eero7%rrzjT+L@9)UD4-<;puh1zSMH;%BSK_T}3 zAE9XfMSD%TWq|`l;`TVZrH;Pd9?c%nF|9cj7n_CN+&-<oCpH#+(<&##gt-hQ!4TP< zz!a1rjTI|dzA}U6EnR6+4?CZ6)m4;0%&?|DkStlcvW1>qzEYwVA(e5}P1KW^ZY{}S zC<}zh>Oe}UzznGfH*=T33uSkl`P<t*2BFpT`oPK>*tcCxH1T;hMQmtTtf*LSr~@M! zi_?FKaAY_l&!})qD&qV*bxEQBf+1;5L#YSSR#JqhEWD-$N9D#KO@nL0-LHw?JVOVY z4|*(ofenro7+7!3jKbA1@T?Ic6BiI@T?b=gF#@$XMlJq4mI=Bt3rifqF&TK~WnmUB zC=gp88^Xfxy-Oe^wvtFq5QPRcBvL#!;&iG09{%ANmH#;cnH7=YyF3lOjUx*SDAqJ< z+a#wHxUMnVB*lo**GDl*F{alv|Mrq%DRJ0xn_9eGsG=hO-o3xZe-#wo8Nc)M<HH>p z&C1rS-uqt-Y*#`-&Y>fD&yVG09TI#|a)_7rd3RpLA*69vz3455&AuZ7)oFN(9-5zW z61#jd?@5Z?!(CBiO;pv?C!||?pQVM2PL*R$s+ce(hESk|C$gtaqP4wjO(HUEYs;}* z<=Ucmzs5!fYr-y|S+t-FiC!vf%K}%)uoMs2>Qk64)x3|xdnFvA9GI0g9L?J&=X#}x zAcLF-0Mh}_0pLDEt-}V<98g&(4L|7p@E(9G9)tgXRzGA5A9Ar2fi;6MCTSdt1{+-h zzUrMj(9JHvndTMDfIZ-{k(-s2o^#=p5cnE3Xl_-~kP!yoAa7EnUV|e}dGYWz@I&7= z3Ox@VW~sD3B6;Buryw{2_*&)#&CTjhi61Iyi;Nr*9wT5f=sO$+w*k>g<R$VmY;G(s zO^m*}t!C_N=?KO%%{_<KHFVz>XBVu^u(Q_noQ5pR-eHmT9Gv0=P5SFJZMW-nf+I_d zJZtxsV7F6my`yF$iK8&^h1(LFvMJP!N}8zTDI>*qBP6^k1T=-9Tj3F}XZJQm4GUa| z;=d-e6H(eC!G-y`=EB|*b@>GZhx|LTXiV!tK*Ru#@MpNk1mSV*i|J}mG-{&f_rfsH zbGwpO06gIXva2vtS`U$WR*G4l&5XA9qu39A6dAc+qUcDyoU)vXYJn0~rE^XQI|Sun zDvSBOF}>2?bP7dP4+q$}Moc;3xZRUU)~{ztJPOS`6c;w|i7W}}0)&YhDHi}755jF& z3x_Os-euWp4?^$)CVNM9DX=gYAxeB5bx;$=_H@@%li`9N<qL6~#Dlx`%c9+Cs>YK+ zqm~E-oosPoKEo5T3)rxP1%j7A0-m%Q&rFZ!tNFCo;HFz*&6D;C-xau&gJBVa6qqE_ z3!KAGVm}Itz+`h(y?sE`2;~K$nAA!#4tIu<e;|C2^Pk?$iyXjB<{u%N_=bRaBm|&m znlxXxq#n_R=zHP8U!`#zRoy|U=3XV;CWieE4Ug?46wJJlO3-$kP#czok>*83^F{5@ z&j%rJDDU0TPW?a88Y9vUU-U5h5AYdmaHC7||G0B*jtT<+KNL`F&kt4sn|T9&i)&Ov zOcWk{56qy&I~0t-BseKi{{byNOv)?-A*NIx(U$3F`fh#vKe_;}wFG*^@E~vrP0Cvv zu(nH}-4N+68<4NhdMR|4dTBJaggH|WgoWxwFAo+E!VA@Ry#lGETt0|UH-p#)FmDX> zB8a4WQCxK>6sVdA235yioTLDuLY30X<f*)EEC!wUw+O2Dlasz(x$)e&?)WcDUu?&- zfAFWp4WNG<j!`j2x+7eD_Vn!XA}?+5Z*0%xY;I1wvqh@0#^)XCPDG0&b>Fz7V<?d; zO^Q_c(Db=8fM&_u3Tzf+T!%)dVEAI$NHU?36v&qf6?s^=Ad%dO>=Qx5BC{U2DGItQ zm<UBxDGJb5NpdlZUlfs<M{lLU4jj#bDRT<+va+cx5+p`tyDCp16PAc^E15wnm!Ykp zd6sI>7;urk6O=Q{R3ReY?3x`jW}=nCw8tTkmm3pIqcfN+c5IyCEay1S1uk+4LLga4 z4w8oyAVnbP%wT!kDJ)MWjEFDegtQqGq;;2w`ZFm+{aG9$KF0`YvusG~s$lw>%FUgr z$@aQFnC9}#%Z5UqF}Zmgnx5hP-V4&YR!Ez@<&ppze*6pVb%^@Yw@4mV&iy#3@+BZ; zH0sQ@y8(hq7u*d4m2X*N8}UIlzAVCL8r44P6nv<>Vx8mC3{Uz2v~S8kg|Fr9<2zVL S*`!;CpE=t5KRzi04o(09?GiZv literal 4832 zcmV<65+Ch%Pew8T0RR91021H;4*&oF03+}K01}A+0RR9100000000000000000000 z0000SR0d!GgnS4N37iZO2nv{dl^F{v00A}vBm+<cAO(d@2Z22dfgBs!AhWV=kXAoJ z_Fo@xV+eLedBvzIT6C3Xd4>~VPdJ_!2WSTttvBuOt5t?NL)YmCJG~_qJ(8)J$XD0k zAj9=sLs%nncEeRTi$yi=m=Ti9^8CZSdT!<vjo^<bSn$ck(li*`toSs;|8LE{`-+;9 zbTtRLhoL?&8?kIN$M3RmgpQP0qgIcv?oFp-O-q(@BM+dC_4HBqO75=WErA83gw6@v zv;?RnH4&sD#vnk~I&}O!tIc{nCQ0kb9ILFe>Qw7*T1%LT*w=n#cfU6?OMFBsM2Y~^ z*2b|s{^x=!lE|2oF)EIH;t!rrTe=Pu6_5g7`Kj#D2FQRqO8D%M6qI}$hN9lCdRjZw zR~cI8L!l2uf;to*P-tuMu5E*V;+X#}5^!3g0hCoYK%W2KTwnIxN@{)$plUxz!k}d5 z(v?n5y-Geq`V*!Hc!5YLRR4@5$^xQV+ivmrwd>Zn6>_a#VdM^D7l4UtI6Z30w4#I< zC65(qHZt+QrGSwoIS|$iuivRkm-*}aswx6Npm}I6U%kkcUF{_Z1iMN_ecAT?)K<OF zy4x{My!3wi{0j?A&?4J{|GeGvK~w!zUORU2U-NM1L0?2*)uOsoTf70*m<KxFpF~t$ z#JPFA@NNZbCpoGXhw;f-`9DgXfZjEh@rE$mBK|)fo5STPC@S%32_%$Ake=XK6LZcO zTMJ5Ei7cfhOKHneI<l0mETtz)>B~|EvXr4LWh6@(%UWszrA%chGg-=9ma>qgEax1? z%4+t>(em9G|L4Afg6lAdBG>+6Y<~@P`5>^i%=kV%m-fyb%=156Y_BG|X{-hJfZFd6 zoJ!t7pq-yP7}TnDa7eSoXZBSmdqk9!d|@*^Uk2T$V7&8FC>V^r+#t?=cC4$5$<ui| zVqxrcrP-;IM+8RoT@<2T@(Rwvv)HA9EsR8pClUtYy{9w?{Gdh};jb|wKIq1w$iZZQ zs)B`k+;|usrcHZaxqh}~qwCljjBVXVDJaPeV;oF)cVfvdMWVl^$a86Dc_Nq7&bG<c z9f%FuXh|_Xdl8sZ-~?O*a9Cx#HU^7kY4b4u)$)pZ-oH{_#@OTtr5=E%_!W{;eRN33 zKoFiA!Qwbzna4J7tc_51C{=re(nzsdUIMhJvB|oAY5`d$$fmvc;FPxg`NLEyOT7a` zTn}k%@<t3AZ4}I9AlB$4$j0Ry7H~Mzq07W%e<;Vi3b2C=YdweBP{wLH90<^3N}T)^ z@=_8PF*yd;r+(pDE;?sSP7VCDwHoqnY(;T_!7EczK!hh5w7nvYqU*41j7i$d7-hA< z{Wk8We@}{WYw7<0Zni`vEzIlRDvk)>j)OI7<Fs}7D6FgE^_#?@AxlK?c{O%^7%?Bw z6S#m*909bikWBe2<W<`%1WeLC*X~`CW`?+SClt}?$CDJ_^yiMEzV&eJcD>eE=|n5W zD46116V?}piYG`B5az!4CFqMmb1HBjhoZVtg!^_Ti%fLW>X&d78yc~E_FGdUK1XPn zKe-7*-4gS?WPz-$4-xGAQV7j2EkX68uI-&bXkG@C%u_Uwy`(?og((CQ$w}{}r}*_j zrNjd{M>ESOq9aAYZ0eK=qOxFA5uB<*P)$gx3q=iKQB&B|5>8hR)%`_L`P!YJQbW&c z$`c)P0TrqHzU6$y(ET`NZB#$ZlZ<^(`Bm5GMzWy;MHh-56n!WLPz<3MK{1A60>u>C zy8v0VR$5Fa%2|54jC$rQQf9$jYnE_hk~}?4$1+4lvLuU|XLX}~%c<5#PAi34v6T8M zXS)>3%_76>Ae@d8#;dBi<@DY)$OX`y>!{G(RQ#SAN-#oF5p(~*$27}2Th>A%=PB8M zEXoLa3n2dkIW|!rSafWKOCV03zQ{HTlhmb_5wJC7=t?^qSscj{Hq93I4Vdq?0?qE7 zcI*JmeP^Opc=E$!mx7$c<IJ;m!9*6@EwGK<2B}#Mpv*^X6P|BRjx^_COX%*lsUvZB zHTtdWs7q;kk*BG{gyO1d)0VEVN4kJuY81)bsAB}PK{C~DGJ0!PdHDiShk4d0E{8C0 zK8J@*QnHFzPdW8zBRwM-wjp8$gfp+fR`dpZ2BKq64{M1CGPdaA>~Y0@Jub}*p<W<o zJ>$R^q@ZPQx6xIoWl^mO>sBCDiojast2<peZYdb>W*t3PQr7If>aeRdAGV$~(az5s zp?OCPRB8=+IXhz*n~fM`G1g@%#4|TrpV)W9jPL$rvRfSad7lD1X<<`OgWc4&wRW#Z z*lvYOp;)u@n$FZ?&Nbtx^|4IByx7`0jXv*}T4!0O&!V_J?%{22&;N>%18aHoHtgw( zI%}+}G};J}&UIlUX@t+OJl$xutR=aT__z?yuJUyq)^4-sOl>rG<l@jJ6of@Bn<Tjm zwi{i!8s;jMT83?}XsYfithtseQ|cPfJK-!tNRYy_5F!E_rQ@84X+-f^2nj(t+1(Ow ziX!EZ(z6jV4wPLa5ONMFKO3RoV2k{4MwD<yl#wPXIB2UyoEJ4+r1~s`h9GJ}!fDa+ zo7A3-&~b>mNF($dQhzqWz+rOOE!ttcXf#oaU$|og>_p*y0YNi8jG-%L5@#-<MYDD? z#M0e~mAf-u4-updfFIi+kaiN<H)|Iw9Nb+vy1Q_4ci|i$x=83M;Tx>jjk~)W4|g}7 z?ryvSL~jXwB)#9ccjr;<y9dhkZZg=xSH&6|-@6XBI^gbIvZKC8uR;MHuP`P)fM;1! zzAobfpzQzv`Z@q};EbEYbbx?7xJZB(W`jFx6vGHdYZxBs88%#P=MU~Xj-^cvOx(oX z-Q2*i=WhTPwP|2LIRla}Xb^wbGu|UnzGNAN2^R=1mnVK!xe@K-MFB`?1<=Z_k+b)? z2;uBS{vcDSwg%N2wT+F&CO~tb&%uSF1ei9I4@{6GU5H4LRRVdvav0S6WW=gLg^D$V z)uF)HS)9UnLammoLM_im|HtQ#qH{!mYz{5tFGBbdHFkio`d27CfcfwL&9_jATTdqN z7DzcU2RJ$l8PDcHolh2E7)7usH-17lX72$I=%42qL82G<eDx#+%L~M`MT8ck@I{oA zp|gt=rX}YZ0MiH&sn`@(S~5_TjTxb_*1^;W1D$1>;`1&#*aF9ln4@F&Z{EFd=f*w6 zi0;xS&W9;#F>nV0!Sv;n4?Hws2goH#+<KWBvk__tt7@r!Lx>8kLzULXxZ2uUt%m9D zY=7^KKX+u*5(3JDM2uSUKapVc01Q(BeH|Jb6GCNWrBVSj_-LQ}kZ<f+f59SzQ!Uy} zBrwabLP;5ZbSa3baU}qtDne6<j#@(^rP30@t(QXherrq%(`S==r+kcv33S|>Mv!uT zw<W?CC_VX*K<&*J2s1TgEdYfsQw=g=4UCM}Fv3^aAr-8%sYQ|$qO<rswg4074pl<i zsR9ZDj}S8E7al3pEm>|811Q%iY7qQHWA*Yfs$z8a!Ly^Dg9l0IJwh9E<^c8|8y9Vf zN=U|fwT(@TlJe<Xz<J)VKm`-R->7gE;X_d%_$P}3u@DmJ{r7ng+~0E+bALZUw?Aor zsB7!Vt%rmUPCj6TRkfxioJ~uTW=XTEC;v(b7mt5jsknI5-0WaM{>Gnw#(}z$77Qr8 z9+Wrv`Nv~rm2Cre%R7A(H5J>vMk2Oe_5;m07VeYGe_7I)&s7hFVd8B7FRP%XrF@Jd z>3rrr{UDBvJ$r@*K7Sn;*tKiVfNp3~-krYR>`d6k4!`5Fu+;ThdDZvp%h#I5-nzQp zMuj3RMPbJKK0Z!pxp29PZ9TbR**%Y|8+#l3`?E4~<IS%6PWWGS$;jyM8_Pb^^zoe8 zSKq5<7G}sco!D^NxPOno$hXgb+;_}Z<bTdLSh8E<o-Y_Mwsm|fL~rj;->(neV8zF$ z%C>Fua<c>^j@Rdev?Og{4JB_cAZ>{(D5x*UtIt#CD+raC23;)z<B|m6br;bu#^`(s zWyg*ei5wlBcCXXx(HqtqE9`XclKMS`X2V_)iR(NOdhdJmVj*S!dwKPNK5q0qE^Y&$ zlh^x%7tu>%+-3ujsA`ZKlluxR$~_w6Y;l}1(+J0~qmOOr-_h&bD~9(w?<*-D*(VI` zcRXKQjDFH~e?od+*4yZC%{>5z6v#|iP}6bpfa};XvdfV-E2EMO(nK<*<fc*6F>>7X z;Bm6a0Wp?Q$p-17ADKoy^|lPIxhH5t&zBh^$)Sn)bMmVR-zO%r{Fb2fjR7Z40aFvG zC@AaxK2b-<(RJ&NceH`c1L3+)`aPyS`k&L9abslmxucQpr-h(N)8F`}NrZ8lu!iI8 z)MIyXcePVrWZ$$iXY)D6M!xiE<*bfmx8~=!Gh2l0wt7zMfT)F?m(OgYc@=vxn%VWu zoLN>2n9|A;4X|78%A;jbua#Yp&uTB`wsIz~rB#-1tgP$;#noU(xj6MqiMJ#-HJ9&Y z*WJTdzD~ebru%0G3f8O^Gpm_V74cG9TW4*MG(g<nFAk6f)?xS;aYeDXgta!ZnbWCk zH@IBiZg5pU6Phu0G}ZZaL;1F><=2g%|4=#VpE9ry&f;!2nF<dx_1qBG@B^gxYH8YP z7mi4=Kd8tpBPs}yMK!Nw_1QKzKL0#4xEtfmxwcjp%#~-@DZ*DL8S$j4w20TdC`o94 z&)8!<u5Erz&EBH1DT^esG0jx}h6n)wd*1pMR_@#B)})(VO0^p+0a|9~Kd+D4X8)S8 zO@Sfn5sg`&43>46fG4)?4&;h4SIB$H#Q^(|bw~B(#xJ;Vln8hdSD5P0%~TK!u}SG3 ziPY7Wh<D|b;@L2Wg8VsYTaD(XQX`NjiiD#em;s*jyFI4*@}C3eW<E2qk7l+JL(E7I z8JG;3C17ytfw@J=qIB`S-Hr9hxZkZ+lfcSM?ulTfFCd@GBR4zuDwZ!WXZc)Lk|8FG z-6AD2U2aL@Z7i}+x7+o4xl9<JYWKRGdMoCdjdJaKs#ek@j>15foQOVVTBW&}SHz=& zC@!@L60ULrbQgloYM=1>SU=MlDy)RcKh4cX%*d()E9Fw&h2b_6@)Hmo@>wd;7_AEe zB6{!;{4(6bZ3GXe1HF(BdzDPLf>soTwi?LIDgcl09%3#9Gezq{q%Ld4l!NKep+TGk z;g1E58mUi<s&8UcE5=#K(`q^F67dd+f0Psx<Lr>4r|8o*35iNwH~{C%!(B;moIPpM z&Y;{$lX2v#vD{1oVipPM1PBwC(rkcmJ_u){EE)+l_)cwGABNxwm~4raivkmRjua(c z?G0161uZXkdVVfi4dU)<lJNNOCkFjqy;fD6tA>><)f_olwNj43BV;Gw$gQ{tQ6vd3 znok!OrYreMyH$}ET)!rHmk6^g2x)tkC77eYIx@S&V}djZ;;0u|ZvC$)WnKEgzkc5g z;U@D3KxC!U2rv&x3F5N2-FLcXMxh>!X3HP`fpJE0-W>Lo8qUqh$!O4E(!)R+X~8{D z%H^*M`DH=4R*dAq1h6G>Jqo(R6f{vkysPhLH+|;s{crpB7l2>J`KD)%!+||t;<tdC zWrfK8jks3$h8y~KQ|&gO#iCYUJm1K8Bmbg~aawHP|HL7Arf;BjgXW_vEzi_;dhIh` z+`<9)%LwB*f~SD{r2=qw13w|qwqUfB$YI-3Az>%mnLNM_<%y*MMO@$r<?W>ftU>pc zi)i8%4tgHIJ$yt<i5mT-LWN^&P|mYMxxX}^!z&!2EG=FNK@k6GFue2Ue=^Y4Ysq&Q z4{uNh{`GeC4Q)D)g?{NdM8Q|ig!Ql&_?vZ|sZUSe=<NfgUm|(@HITf<Du>kYQw0NG zog4A`z{sA>k@Bcyia=4*UQ5x~F!O9=4tEyW5nftlVHt{8p@2eUAMpmU#MwGw5wkH* zzmI1#Nqj-AvJgEb%cI=%BtJ&YK2k~3FB<6^M!-3;B1J5rT7cc^ix`iQ$0G?~<N<oB zqEofK$RV?~WBx4aYlH*SvNnjfOtuCLcqd$Zm2qe^Is+pUbIh~AB1<f@!YXSqi+L<! z87rj6Kt%d1L}WgPN;+>s5wmsT_P-@^B~&rK09A}9K_#94z!IJjmdFuc=tWBqi$11u z7#RHWA{NfuGVb|4k#E5gz9TP-Ah`64AC~}$XA#hK!V_;H(n$zRds`Qt{sZ7uU-VA~ z;PhKRo$D`PKKCa{BF1fBTV)+syvP(^b;{p9RTOWUUft$ue13?^^p}5z_!YJ<{g5>Z GNo@dwG#q~b diff --git a/src/sass/general.scss b/src/sass/general.scss index ce97564..e6247d6 100644 --- a/src/sass/general.scss +++ b/src/sass/general.scss @@ -23,6 +23,7 @@ font-weight: bold; width: 30px; height: 30px; + padding: 0px 5px 1px 8px; } input { diff --git a/src/sass/index.scss b/src/sass/index.scss index a19165a..3f4b123 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -50,7 +50,7 @@ body { background-color: var(--bg_color); color: var(--fg_color); - font-family: $font_0, $font_1, $font_2, $font_3; + font-family: $font_0, $font_1; font-size: 15px; line-height: 1.3; margin: 0; @@ -143,30 +143,50 @@ ul { } .verified-icon { - color: var(--icon_text); - border-radius: 50%; - flex-shrink: 0; - margin: 2px 0 3px 3px; - padding-top: 3px; - height: 11px; - width: 14px; - font-size: 8px; display: inline-block; - text-align: center; - vertical-align: middle; + width: 14px; + height: 14px; + margin-left: 2px; + + .verified-icon-circle { + position: absolute; + font-size: 15px; + } + + .verified-icon-check { + position: absolute; + font-size: 9px; + margin: 5px 3px; + } &.blue { - background-color: var(--verified_blue); + .verified-icon-circle { + color: var(--verified_blue); + } + + .verified-icon-check { + color: var(--icon_text); + } } &.business { - color: var(--bg_panel); - background-color: var(--verified_business); + .verified-icon-circle { + color: var(--verified_business); + } + + .verified-icon-check { + color: var(--bg_panel); + } } &.government { - color: var(--bg_panel); - background-color: var(--verified_government); + .verified-icon-circle { + color: var(--verified_government); + } + + .verified-icon-check { + color: var(--bg_panel); + } } } diff --git a/src/sass/navbar.scss b/src/sass/navbar.scss index 4e150e0..86bfbe7 100644 --- a/src/sass/navbar.scss +++ b/src/sass/navbar.scss @@ -59,13 +59,9 @@ nav { justify-content: flex-end; } - &.right a { - padding-left: 4px; - - &:hover { - color: var(--accent_light); - text-decoration: unset; - } + &.right a:hover { + color: var(--accent_light); + text-decoration: unset; } } @@ -81,10 +77,11 @@ nav { } } -.icon-info:before { +.icon-info { margin: 0 -3px; } .icon-cog { font-size: 15px; + padding-left: 0 !important; } diff --git a/src/sass/search.scss b/src/sass/search.scss index 444f9bb..c2adaf7 100644 --- a/src/sass/search.scss +++ b/src/sass/search.scss @@ -13,6 +13,7 @@ button { margin: 0 2px 0 0; + padding: 0px 1px 1px 4px; height: 23px; display: flex; align-items: center; @@ -35,7 +36,7 @@ background-color: var(--bg_elements); color: var(--fg_color); border: 1px solid var(--accent_border); - padding: 1px 6px 2px 6px; + padding: 1px 1px 2px 4px; font-size: 14px; cursor: pointer; margin-bottom: 2px; @@ -56,20 +57,17 @@ font-weight: initial; text-align: left; - > div { - line-height: 1.7em; - } - .checkbox-container { display: inline; padding-right: unset; - margin-bottom: unset; + margin-bottom: 5px; margin-left: 23px; } .checkbox { right: unset; left: -22px; + line-height: 1.6em; } .checkbox-container .checkbox:after { diff --git a/src/sass/tweet/video.scss b/src/sass/tweet/video.scss index 790b3da..ba77b14 100644 --- a/src/sass/tweet/video.scss +++ b/src/sass/tweet/video.scss @@ -2,7 +2,7 @@ @import "_mixins"; video { - max-height: 100%; + height: 100%; width: 100%; } @@ -13,6 +13,7 @@ video { .gallery-video.card-container { flex-direction: column; + width: 100%; } .video-container { @@ -20,9 +21,6 @@ video { min-width: 200px; max-height: 530px; margin: 0; - display: flex; - align-items: center; - justify-content: center; img { max-height: 100%; diff --git a/src/views/general.nim b/src/views/general.nim index 23681b5..2525841 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -52,8 +52,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; let opensearchUrl = getUrlPrefix(cfg) & "/opensearch" buildHtml(head): - link(rel="stylesheet", type="text/css", href="/css/style.css?v=21") - link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=3") + link(rel="stylesheet", type="text/css", href="/css/style.css?v=22") + link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=4") if theme.len > 0: link(rel="stylesheet", type="text/css", href=(&"/css/themes/{theme}.css")) diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index fcdf06f..377a443 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -26,7 +26,9 @@ proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode = template verifiedIcon*(user: User): untyped {.dirty.} = if user.verifiedType != VerifiedType.none: let lower = ($user.verifiedType).toLowerAscii() - icon "ok", class=(&"verified-icon {lower}"), title=(&"Verified {lower} account") + buildHtml(tdiv(class=(&"verified-icon {lower}"))): + icon "circle", class="verified-icon-circle", title=(&"Verified {lower} account") + icon "ok", class="verified-icon-check", title=(&"Verified {lower} account") else: text "" From 71e65c84d767ebb1d788e138d853cf0567fc970c Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sat, 29 Nov 2025 04:34:04 +0100 Subject: [PATCH 161/302] Round video duration properly --- src/formatters.nim | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/formatters.nim b/src/formatters.nim index 3ad1da6..fc0e197 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, strformat, times, uri, tables, xmltree, htmlparser, htmlgen +import strutils, strformat, times, uri, tables, xmltree, htmlparser, htmlgen, math import std/[enumerate, re] import types, utils, query @@ -157,15 +157,13 @@ proc getShortTime*(tweet: Tweet): string = proc getDuration*(video: Video): string = let ms = video.durationMs - sec = int(ms / 1000) - min = int(sec / 60) - hour = int(min / 60) - if hour > 1: + sec = int(round(ms / 1000)) + min = floorDiv(sec, 60) + hour = floorDiv(min, 60) + if hour > 0: return &"{hour}:{min mod 60}:{sec mod 60:02}" - elif min > 1: - return &"{min mod 60}:{sec mod 60:02}" else: - return &"0:{sec mod 60:02}" + return &"{min mod 60}:{sec mod 60:02}" proc getLink*(tweet: Tweet; focus=true): string = if tweet.id == 0: return From 064ec8808022abb071f93f0fc976a8aa123699dc Mon Sep 17 00:00:00 2001 From: Zed <zedeus@pm.me> Date: Sun, 30 Nov 2025 02:56:19 +0100 Subject: [PATCH 162/302] Transition to ID-only RSS GUIDs on Dec 14, 2025 Fixes #447 --- src/views/rss.nimf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/views/rss.nimf b/src/views/rss.nimf index 23744e5..717ad99 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -2,6 +2,9 @@ ## SPDX-License-Identifier: AGPL-3.0-only #import strutils, xmltree, strformat, options, unicode #import ../types, ../utils, ../formatters, ../prefs +## Snowflake ID cutoff for RSS GUID format transition +## Corresponds to approximately December 14, 2025 UTC +#const guidCutoff = 2000000000000000000'i64 # #proc getTitle(tweet: Tweet; retweet: string): string = #if tweet.pinned: result = "Pinned: " @@ -101,12 +104,17 @@ ${renderRssTweet(quoteTweet, cfg)} # if link in links: continue # end if # links.add link +# let useGlobalGuid = tweet.id >= guidCutoff <item> <title>${getTitle(tweet, retweet)} @${tweet.user.username} ${getRfc822Time(tweet)} +#if useGlobalGuid: + ${tweet.id} +#else: ${urlPrefix & link} +#end if ${urlPrefix & link} # end for From 4b9aec6fdebbadfaf91786e934e43679242f5c13 Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 30 Nov 2025 02:57:34 +0100 Subject: [PATCH 163/302] Use graphTweet for cookie sessions for now --- src/api.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api.nim b/src/api.nim index e97b4e0..acd25f1 100644 --- a/src/api.nim +++ b/src/api.nim @@ -42,7 +42,8 @@ proc userTweetsAndRepliesUrl(id: string; cursor: string): ApiReq = proc tweetDetailUrl(id: string; cursor: string): ApiReq = let cookieVars = tweetDetailVars % [id, cursor] result = ApiReq( - cookie: apiUrl(graphTweetDetail, cookieVars, tweetDetailFieldToggles), + # cookie: apiUrl(graphTweetDetail, cookieVars, tweetDetailFieldToggles), + cookie: apiUrl(graphTweet, tweetVars % [id, cursor]), oauth: apiUrl(graphTweet, tweetVars % [id, cursor]) ) From a62ec9cbb40066c2e05b5e81cb8aa61f921644d1 Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 30 Nov 2025 03:58:43 +0100 Subject: [PATCH 164/302] Normalize headers --- src/apiutils.nim | 14 ++++++-------- src/routes/media.nim | 8 ++++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index b288141..b392ef7 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -48,21 +48,19 @@ proc getCookieHeader(authToken, ct0: string): string = proc genHeaders*(session: Session, url: Uri): Future[HttpHeaders] {.async.} = result = newHttpHeaders({ + "accept": "*/*", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", "connection": "keep-alive", "content-type": "application/json", - "x-twitter-active-user": "yes", - "x-twitter-client-language": "en", "origin": "https://x.com", - "accept-encoding": "gzip", - "accept-language": "en-US,en;q=0.5", - "accept": "*/*", - "DNT": "1", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36", + "x-twitter-active-user": "yes", + "x-twitter-client-language": "en" }) case session.kind of SessionKind.oauth: - result["authority"] = "api.x.com" result["authorization"] = getOauthHeader($url, session.oauthToken, session.oauthSecret) of SessionKind.cookie: result["x-twitter-auth-type"] = "OAuth2Session" diff --git a/src/routes/media.nim b/src/routes/media.nim index de51061..186b8d8 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -52,10 +52,10 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = "" let headers = newHttpHeaders({ - "Content-Type": res.headers["content-type", 0], - "Content-Length": contentLength, - "Cache-Control": maxAge, - "ETag": hashed + "content-type": res.headers["content-type", 0], + "content-length": contentLength, + "cache-control": maxAge, + "etag": hashed }) respond(request, headers) From 7734d976f7a6aee4d1abf16a14f0174500f04e7c Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 30 Nov 2025 04:12:38 +0100 Subject: [PATCH 165/302] Add username validation Fixes #1317 --- src/routes/timeline.nim | 1 + tests/test_profile.py | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 49c7ce2..0894d83 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -114,6 +114,7 @@ proc createTimelineRouter*(cfg: Config) = get "/@name/?@tab?/?": cond '.' notin @"name" cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"] + cond @"name".allCharsInSet({'a'..'z', 'A'..'Z', '0'..'9', '_'}) cond @"tab" in ["with_replies", "media", "search", ""] let prefs = cookiePrefs() diff --git a/tests/test_profile.py b/tests/test_profile.py index ea05add..cbf0256 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -15,7 +15,19 @@ protected = [ ['Poop', 'Randy', 'Social media fanatic.'] ] -invalid = [['thisprofiledoesntexist'], ['%']] +invalid = [['thisprofiledoesntexist']] + +malformed = [ + ['${userId}'], + ['$%7BuserId%7D'], # URL encoded version + ['%'], # Percent sign is invalid + ['user@name'], + ['user.name'], + ['user-name'], + ['user$name'], + ['user{name}'], + ['user name'], # space +] banner_image = [ ['mobile_test', 'profile_banners%2F82135242%2F1384108037%2F1500x500'] @@ -65,6 +77,13 @@ class ProfileTest(BaseTestCase): self.open_nitter(username) self.assert_text(f'User "{username}" not found') + @parameterized.expand(malformed) + def test_malformed_username(self, username): + """Test that malformed usernames (with invalid characters) return 404""" + self.open_nitter(username) + # Malformed usernames should return 404 page not found, not try to fetch from Twitter + self.assert_text('Page not found') + def test_suspended(self): self.open_nitter('suspendme') self.assert_text('User "suspendme" has been suspended') From 693a1894625e0ba035554d23b3e9b94e99dab8a4 Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 30 Nov 2025 05:43:17 +0100 Subject: [PATCH 166/302] Add heuristics to detect when to show "Load more" Fixes #1328 --- src/views/status.nim | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/views/status.nim b/src/views/status.nim index 71c2c67..96af807 100644 --- a/src/views/status.nim +++ b/src/views/status.nim @@ -28,14 +28,19 @@ proc renderReplyThread(thread: Chain; prefs: Prefs; path: string): VNode = if thread.hasMore: renderMoreReplies(thread) -proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string): VNode = +proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string; tweet: Tweet = nil): VNode = buildHtml(tdiv(class="replies", id="r")): + var hasReplies = false + var replyCount = 0 for thread in replies.content: if thread.content.len == 0: continue + hasReplies = true + replyCount += thread.content.len renderReplyThread(thread, prefs, path) - if replies.bottom.len > 0: - renderMore(Query(), replies.bottom, focus="#r") + if hasReplies and replies.bottom.len > 0: + if tweet == nil or not replies.beginning or replyCount < tweet.stats.replies: + renderMore(Query(), replies.bottom, focus="#r") proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode = let hasAfter = conv.after.content.len > 0 @@ -70,6 +75,6 @@ proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode if not conv.replies.beginning: renderNewer(Query(), getLink(conv.tweet), focus="#r") if conv.replies.content.len > 0 or conv.replies.bottom.len > 0: - renderReplies(conv.replies, prefs, path) + renderReplies(conv.replies, prefs, path, conv.tweet) renderToTop(focus="#m") From e7413858286cc4c188aef14433dbd9e76256180c Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 30 Nov 2025 18:06:22 +0100 Subject: [PATCH 167/302] Allow , in username to support multiple users Fixes #1329 --- src/routes/timeline.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 0894d83..4d8561b 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -114,7 +114,7 @@ proc createTimelineRouter*(cfg: Config) = get "/@name/?@tab?/?": cond '.' notin @"name" cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"] - cond @"name".allCharsInSet({'a'..'z', 'A'..'Z', '0'..'9', '_'}) + cond @"name".allCharsInSet({'a'..'z', 'A'..'Z', '0'..'9', '_', ","}) cond @"tab" in ["with_replies", "media", "search", ""] let prefs = cookiePrefs() From 17fc2628f91f70b9bfda1915c76e94708a5197bf Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 30 Nov 2025 18:07:13 +0100 Subject: [PATCH 168/302] Minor fix --- src/routes/timeline.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 4d8561b..2ac87bb 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -114,7 +114,7 @@ proc createTimelineRouter*(cfg: Config) = get "/@name/?@tab?/?": cond '.' notin @"name" cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"] - cond @"name".allCharsInSet({'a'..'z', 'A'..'Z', '0'..'9', '_', ","}) + cond @"name".allCharsInSet({'a'..'z', 'A'..'Z', '0'..'9', '_', ','}) cond @"tab" in ["with_replies", "media", "search", ""] let prefs = cookiePrefs() From 663f5a52e13783075287e779b1b947b862259e7e Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 6 Dec 2025 05:00:34 +0100 Subject: [PATCH 169/302] Improve headers --- src/apiutils.nim | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/apiutils.nim b/src/apiutils.nim index b392ef7..7a49e3e 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -56,7 +56,8 @@ proc genHeaders*(session: Session, url: Uri): Future[HttpHeaders] {.async.} = "origin": "https://x.com", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36", "x-twitter-active-user": "yes", - "x-twitter-client-language": "en" + "x-twitter-client-language": "en", + "priority": "u=1, i" }) case session.kind @@ -66,6 +67,12 @@ proc genHeaders*(session: Session, url: Uri): Future[HttpHeaders] {.async.} = result["x-twitter-auth-type"] = "OAuth2Session" result["x-csrf-token"] = session.ct0 result["cookie"] = getCookieHeader(session.authToken, session.ct0) + result["sec-ch-ua"] = """"Google Chrome";v="142", "Chromium";v="142", "Not A(Brand";v="24"""" + result["sec-ch-ua-mobile"] = "?0" + result["sec-ch-ua-platform"] = "Windows" + result["sec-fetch-dest"] = "empty" + result["sec-fetch-mode"] = "cors" + result["sec-fetch-site"] = "same-site" if disableTid: result["authorization"] = bearerToken2 else: From 51b54852dcf617c6131376a469c537ab6b8326e3 Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 6 Dec 2025 05:15:01 +0100 Subject: [PATCH 170/302] Add preliminary support for nitter-proxy --- nitter.example.conf | 1 + src/apiutils.nim | 13 ++++++++++++- src/config.nim | 1 + src/nitter.nim | 1 + src/types.nim | 1 + 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/nitter.example.conf b/nitter.example.conf index dfdaf50..7e4c846 100644 --- a/nitter.example.conf +++ b/nitter.example.conf @@ -26,6 +26,7 @@ enableRSS = true # set this to false to disable RSS feeds enableDebug = false # enable request logs and debug endpoints (/.sessions) proxy = "" # http/https url, SOCKS proxies are not supported proxyAuth = "" +apiProxy = "" # nitter-proxy host, e.g. localhost:7000 disableTid = false # enable this if cookie-based auth is failing # Change default preferences here, see src/prefs_impl.nim for a complete list diff --git a/src/apiutils.nim b/src/apiutils.nim index 7a49e3e..ddb5027 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -13,10 +13,17 @@ const var pool: HttpPool disableTid: bool + apiProxy: string proc setDisableTid*(disable: bool) = disableTid = disable +proc setApiProxy*(url: string) = + if url.len > 0: + apiProxy = url.strip(chars={'/'}) & "/" + if "http" notin apiProxy: + apiProxy = "http://" & apiProxy + proc toUrl(req: ApiReq; sessionKind: SessionKind): Uri = case sessionKind of oauth: @@ -99,7 +106,11 @@ template fetchImpl(result, fetchBody) {.dirty.} = var resp: AsyncResponse pool.use(await genHeaders(session, url)): template getContent = - resp = await c.get($url) + # TODO: this is a temporary simple implementation + if apiProxy.len > 0: + resp = await c.get(($url).replace("https://", apiProxy)) + else: + resp = await c.get($url) result = await resp.body getContent() diff --git a/src/config.nim b/src/config.nim index 571508b..2b38d86 100644 --- a/src/config.nim +++ b/src/config.nim @@ -41,6 +41,7 @@ proc getConfig*(path: string): (Config, parseCfg.Config) = enableDebug: cfg.get("Config", "enableDebug", false), proxy: cfg.get("Config", "proxy", ""), proxyAuth: cfg.get("Config", "proxyAuth", ""), + apiProxy: cfg.get("Config", "apiProxy", ""), disableTid: cfg.get("Config", "disableTid", false) ) diff --git a/src/nitter.nim b/src/nitter.nim index e6d66ab..91a3a9f 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -37,6 +37,7 @@ setHmacKey(cfg.hmacKey) setProxyEncoding(cfg.base64Media) setMaxHttpConns(cfg.httpMaxConns) setHttpProxy(cfg.proxy, cfg.proxyAuth) +setApiProxy(cfg.apiProxy) setDisableTid(cfg.disableTid) initAboutPage(cfg.staticDir) diff --git a/src/types.nim b/src/types.nim index 815e223..c994148 100644 --- a/src/types.nim +++ b/src/types.nim @@ -275,6 +275,7 @@ type enableDebug*: bool proxy*: string proxyAuth*: string + apiProxy*: string disableTid*: bool rssCacheTime*: int From baeaf685d32098cd90ae1424cf8a19f3de9b0e2c Mon Sep 17 00:00:00 2001 From: jackyzy823 Date: Mon, 8 Dec 2025 17:05:08 +0800 Subject: [PATCH 171/302] Make maxConcurrentReqs configurable (#1341) --- nitter.example.conf | 1 + src/auth.nim | 11 +++++++---- src/config.nim | 3 ++- src/nitter.nim | 1 + src/types.nim | 1 + 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/nitter.example.conf b/nitter.example.conf index 7e4c846..4a6a026 100644 --- a/nitter.example.conf +++ b/nitter.example.conf @@ -28,6 +28,7 @@ proxy = "" # http/https url, SOCKS proxies are not supported proxyAuth = "" apiProxy = "" # nitter-proxy host, e.g. localhost:7000 disableTid = false # enable this if cookie-based auth is failing +maxConcurrentReqs = 2 # max requests at a time per session to avoid race conditions # Change default preferences here, see src/prefs_impl.nim for a complete list [Preferences] diff --git a/src/auth.nim b/src/auth.nim index 5d7ef0e..d801489 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -3,14 +3,17 @@ import std/[asyncdispatch, times, json, random, strutils, tables, packedsets, os import types, consts import experimental/parser/session -# max requests at a time per session to avoid race conditions -const - maxConcurrentReqs = 2 - hourInSeconds = 60 * 60 +const hourInSeconds = 60 * 60 var sessionPool: seq[Session] enableLogging = false + # max requests at a time per session to avoid race conditions + maxConcurrentReqs = 2 + +proc setMaxConcurrentReqs*(reqs: int) = + if reqs > 0: + maxConcurrentReqs = reqs template log(str: varargs[string, `$`]) = echo "[sessions] ", str.join("") diff --git a/src/config.nim b/src/config.nim index 2b38d86..8cb334a 100644 --- a/src/config.nim +++ b/src/config.nim @@ -42,7 +42,8 @@ proc getConfig*(path: string): (Config, parseCfg.Config) = proxy: cfg.get("Config", "proxy", ""), proxyAuth: cfg.get("Config", "proxyAuth", ""), apiProxy: cfg.get("Config", "apiProxy", ""), - disableTid: cfg.get("Config", "disableTid", false) + disableTid: cfg.get("Config", "disableTid", false), + maxConcurrentReqs: cfg.get("Config", "maxConcurrentReqs", 2) ) return (conf, cfg) diff --git a/src/nitter.nim b/src/nitter.nim index 91a3a9f..e2d6bec 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -39,6 +39,7 @@ setMaxHttpConns(cfg.httpMaxConns) setHttpProxy(cfg.proxy, cfg.proxyAuth) setApiProxy(cfg.apiProxy) setDisableTid(cfg.disableTid) +setMaxConcurrentReqs(cfg.maxConcurrentReqs) initAboutPage(cfg.staticDir) waitFor initRedisPool(cfg) diff --git a/src/types.nim b/src/types.nim index c994148..90487ab 100644 --- a/src/types.nim +++ b/src/types.nim @@ -277,6 +277,7 @@ type proxyAuth*: string apiProxy*: string disableTid*: bool + maxConcurrentReqs*: int rssCacheTime*: int listCacheTime*: int From a92e79ebc3581702dc427434a782a5fc1d28cc91 Mon Sep 17 00:00:00 2001 From: yav <150280490+796176@users.noreply.github.com> Date: Wed, 24 Dec 2025 10:22:20 +0300 Subject: [PATCH 172/302] Fix the checkmark position (#1347) Co-authored-by: yav <796176@protonmail.com> --- src/views/profile.nim | 1 + src/views/renderutils.nim | 1 - src/views/timeline.nim | 1 + src/views/tweet.nim | 2 ++ 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/profile.nim b/src/views/profile.nim index 2b2e410..ee3f71d 100644 --- a/src/views/profile.nim +++ b/src/views/profile.nim @@ -26,6 +26,7 @@ proc renderUserCard*(user: User; prefs: Prefs): VNode = tdiv(class="profile-card-tabs-name"): linkUser(user, class="profile-card-fullname") + verifiedIcon(user) linkUser(user, class="profile-card-username") tdiv(class="profile-card-extra"): diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index 377a443..a5fe3b2 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -42,7 +42,6 @@ proc linkUser*(user: User, class=""): VNode = buildHtml(a(href=href, class=class, title=nameText)): text nameText if isName: - verifiedIcon(user) if user.protected: text " " icon "lock", title="Protected account" diff --git a/src/views/timeline.nim b/src/views/timeline.nim index a205c04..fee45bc 100644 --- a/src/views/timeline.nim +++ b/src/views/timeline.nim @@ -66,6 +66,7 @@ proc renderUser(user: User; prefs: Prefs): VNode = tdiv(class="tweet-name-row"): tdiv(class="fullname-and-username"): linkUser(user, class="fullname") + verifiedIcon(user) linkUser(user, class="username") tdiv(class="tweet-content media-body", dir="auto"): diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 58d03a9..d680509 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -31,6 +31,7 @@ proc renderHeader(tweet: Tweet; retweet: string; pinned: bool; prefs: Prefs): VN tdiv(class="tweet-name-row"): tdiv(class="fullname-and-username"): linkUser(tweet.user, class="fullname") + verifiedIcon(tweet.user) linkUser(tweet.user, class="username") span(class="tweet-date"): @@ -235,6 +236,7 @@ proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode = tdiv(class="fullname-and-username"): renderMiniAvatar(quote.user, prefs) linkUser(quote.user, class="fullname") + verifiedIcon(quote.user) linkUser(quote.user, class="username") span(class="tweet-date"): From a45227b8835719dfb443600052d69374db8b515c Mon Sep 17 00:00:00 2001 From: cmj <129799+cmj@users.noreply.github.com> Date: Thu, 29 Jan 2026 08:27:41 -0800 Subject: [PATCH 173/302] Add user-agent to guest_token request (#1359) --- tools/get_session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/get_session.py b/tools/get_session.py index 9f91483..da03322 100644 --- a/tools/get_session.py +++ b/tools/get_session.py @@ -21,7 +21,10 @@ def auth(username, password, otp_secret): guest_token = requests.post( "https://api.twitter.com/1.1/guest/activate.json", - headers={'Authorization': bearer_token} + headers={ + 'Authorization': bearer_token, + "User-Agent": "TwitterAndroid/10.21.0-release.0 (310210000-r-0) ONEPLUS+A3010/9" + } ).json().get('guest_token') if not guest_token: From 33dd9b66683e6838d6da16f5563c20de5d32ef5a Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 6 Feb 2026 20:32:44 +0100 Subject: [PATCH 174/302] Fix /pic/ exploit --- src/routes/media.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/routes/media.nim b/src/routes/media.nim index 186b8d8..011d0f3 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -93,6 +93,8 @@ proc createMediaRouter*(cfg: Config) = get re"^\/pic\/orig\/(enc)?\/?(.+)": var url = decoded(request, 1) + cond "amplify_video" notin url + if "twimg.com" notin url: url.insert(twimg) if not url.startsWith(https): @@ -107,6 +109,8 @@ proc createMediaRouter*(cfg: Config) = get re"^\/pic\/(enc)?\/?(.+)": var url = decoded(request, 1) + cond "amplify_video" notin url + if "twimg.com" notin url: url.insert(twimg) if not url.startsWith(https): From 0a6e79e6263b0c3aba2c0fec3b6af906d71a1622 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 9 Feb 2026 02:55:07 +0100 Subject: [PATCH 175/302] Add bulk script create_sessions_browser.py --- tools/create_session_browser.py | 135 ++++++++++++------- tools/create_sessions_browser.py | 219 +++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 47 deletions(-) create mode 100644 tools/create_sessions_browser.py diff --git a/tools/create_session_browser.py b/tools/create_session_browser.py index 3a05cb1..eb3936b 100644 --- a/tools/create_session_browser.py +++ b/tools/create_session_browser.py @@ -20,73 +20,112 @@ Output: {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."} """ -import sys -import json import asyncio -import pyotp -import nodriver as uc +import json import os +import sys + +import nodriver as uc +import pyotp async def login_and_get_cookies(username, password, totp_seed=None, headless=False): """Authenticate with X.com and extract session cookies""" # Note: headless mode may increase detection risk from bot-detection systems browser = await uc.start(headless=headless) - tab = await browser.get('https://x.com/i/flow/login') + tab = await browser.get("https://x.com/i/flow/login") try: # Enter username - print('[*] Entering username...', file=sys.stderr) - username_input = await tab.find('input[autocomplete="username"]', timeout=10) - await username_input.send_keys(username + '\n') - await asyncio.sleep(1) + print(f"[*] Entering username {username}...", file=sys.stderr) + + retry = 0 + while retry < 5: + username_input = await tab.find( + 'input[autocomplete="username"]', timeout=10 + ) + + pos = await username_input.get_position() + await tab.mouse_move(pos.x, pos.y, steps=50, flash=True) + await asyncio.sleep(0.1) + + await username_input.click() + await asyncio.sleep(0.5) + await username_input.send_keys(username) + await asyncio.sleep(0.2) + await username_input.send_keys("\n") + await asyncio.sleep(2) + + page_content = await tab.get_content() + if "Could not log you in" in page_content: + retry += 1 + wait = retry * 10 + print(f"Retrying in {wait} seconds...") + await asyncio.sleep(wait) + else: + break # Enter password - print('[*] Entering password...', file=sys.stderr) - password_input = await tab.find('input[autocomplete="current-password"]', timeout=15) - await password_input.send_keys(password + '\n') - await asyncio.sleep(2) + print("[*] Entering password...", file=sys.stderr) + pretry = 0 + while pretry < 5: + password_input = await tab.find( + 'input[autocomplete="current-password"]', timeout=15 + ) + await password_input.click() + await asyncio.sleep(0.5) + await password_input.send_keys(password) + await asyncio.sleep(0.2) + await password_input.send_keys("\n") + await asyncio.sleep(2) + + page_content = await tab.get_content() + if "Could not log you in" in page_content: + pretry += 1 + wait = pretry * 10 + print(f"Retrying in {wait} seconds...") + await asyncio.sleep(wait) + else: + break # Handle 2FA if needed page_content = await tab.get_content() - if 'verification code' in page_content or 'Enter code' in page_content: + if "verification code" in page_content or "Enter code" in page_content: if not totp_seed: - raise Exception('2FA required but no TOTP seed provided') + raise Exception("2FA required but no TOTP seed provided") - print('[*] 2FA detected, entering code...', file=sys.stderr) + print("[*] 2FA detected, entering code...", file=sys.stderr) totp_code = pyotp.TOTP(totp_seed).now() code_input = await tab.select('input[type="text"]') - await code_input.send_keys(totp_code + '\n') + await code_input.send_keys(totp_code + "\n") await asyncio.sleep(3) # Get cookies - print('[*] Retrieving cookies...', file=sys.stderr) + print("[*] Retrieving cookies...", file=sys.stderr) for _ in range(20): # 20 second timeout cookies = await browser.cookies.get_all() cookies_dict = {cookie.name: cookie.value for cookie in cookies} - if 'auth_token' in cookies_dict and 'ct0' in cookies_dict: - print('[*] Found both cookies', file=sys.stderr) - + if "auth_token" in cookies_dict and "ct0" in cookies_dict: # Extract ID from twid cookie (may be URL-encoded) user_id = None - if 'twid' in cookies_dict: - twid = cookies_dict['twid'] + if "twid" in cookies_dict: + twid = cookies_dict["twid"] # Try to extract the ID from twid (format: u%3D or u=) - if 'u%3D' in twid: - user_id = twid.split('u%3D')[1].split('&')[0].strip('"') - elif 'u=' in twid: - user_id = twid.split('u=')[1].split('&')[0].strip('"') + if "u%3D" in twid: + user_id = twid.split("u%3D")[1].split("&")[0].strip('"') + elif "u=" in twid: + user_id = twid.split("u=")[1].split("&")[0].strip('"') - cookies_dict['username'] = username + cookies_dict["username"] = username if user_id: - cookies_dict['id'] = user_id + cookies_dict["id"] = user_id return cookies_dict await asyncio.sleep(1) - raise Exception('Timeout waiting for cookies') + raise Exception("Timeout waiting for cookies") finally: browser.stop() @@ -94,7 +133,9 @@ async def login_and_get_cookies(username, password, totp_seed=None, headless=Fal async def main(): if len(sys.argv) < 3: - print('Usage: python3 create_session_browser.py username password [totp_seed] [--append file.jsonl] [--headless]') + print( + "Usage: python3 create_session_browser.py username password [totp_seed] [--append file.jsonl] [--headless]" + ) sys.exit(1) username = sys.argv[1] @@ -107,49 +148,49 @@ async def main(): i = 3 while i < len(sys.argv): arg = sys.argv[i] - if arg == '--append': + if arg == "--append": if i + 1 < len(sys.argv): append_file = sys.argv[i + 1] i += 2 # Skip '--append' and filename else: - print('[!] Error: --append requires a filename', file=sys.stderr) + print("[!] Error: --append requires a filename", file=sys.stderr) sys.exit(1) - elif arg == '--headless': + elif arg == "--headless": headless = True i += 1 - elif not arg.startswith('--'): - if totp_seed is None: + elif not arg.startswith("--"): + if totp_seed is None: totp_seed = arg i += 1 else: # Unkown args - print(f'[!] Warning: Unknown argument: {arg}', file=sys.stderr) + print(f"[!] Warning: Unknown argument: {arg}", file=sys.stderr) i += 1 try: cookies = await login_and_get_cookies(username, password, totp_seed, headless) session = { - 'kind': 'cookie', - 'username': cookies['username'], - 'id': cookies.get('id'), - 'auth_token': cookies['auth_token'], - 'ct0': cookies['ct0'] + "kind": "cookie", + "username": cookies["username"], + "id": cookies.get("id"), + "auth_token": cookies["auth_token"], + "ct0": cookies["ct0"], } output = json.dumps(session) if append_file: - with open(append_file, 'a') as f: - f.write(output + '\n') - print(f'✓ Session appended to {append_file}', file=sys.stderr) + with open(append_file, "a") as f: + f.write(output + "\n") + print(f"✓ Session appended to {append_file}", file=sys.stderr) else: print(output) os._exit(0) except Exception as error: - print(f'[!] Error: {error}', file=sys.stderr) + print(f"[!] Error: {error}", file=sys.stderr) sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/tools/create_sessions_browser.py b/tools/create_sessions_browser.py new file mode 100644 index 0000000..003eec3 --- /dev/null +++ b/tools/create_sessions_browser.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +Requirements: + pip install -r tools/requirements.txt + +Usage: + python3 tools/create_sessions_browser.py [--append sessions.jsonl] [--headless] [--delay] + +Examples: + # Output to terminal + python3 tools/create_sessions_browser.py + + # Append to sessions.jsonl + python3 tools/create_sessions_browser.py --append sessions.jsonl + + # Add 5 second delay between sessions (default: 1) + python3 tools/create_sessions_browser.py --delay 5 + + # Headless mode (may increase detection risk) + python3 tools/create_sessions_browser.py --headless + +Input (accounts_file): + [{"username": "user", "password": "pass", "totp": "totp_code"}, {...}, ...] + +Output: + {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."} + {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."} + ... +""" + +import asyncio +import json +import sys +from time import sleep + +import nodriver as uc +import pyotp + + +async def login_and_get_cookies(account, headless=False): + """Authenticate with X.com and extract session cookies""" + # Note: headless mode may increase detection risk from bot-detection systems + browser = await uc.start(headless=headless) + tab = await browser.get("https://x.com/i/flow/login") + + username = account["username"] + password = account["password"] + totp_seed = account["totp"] + + try: + # Enter username + print(f"[*] Entering username {username}...", file=sys.stderr) + + retry = 0 + while retry < 5: + username_input = await tab.find( + 'input[autocomplete="username"]', timeout=10 + ) + + pos = await username_input.get_position() + await tab.mouse_move(pos.x, pos.y, steps=50, flash=True) + await asyncio.sleep(0.1) + + await username_input.click() + await asyncio.sleep(0.5) + await username_input.send_keys(username) + await asyncio.sleep(0.2) + await username_input.send_keys("\n") + await asyncio.sleep(2) + + page_content = await tab.get_content() + if "Could not log you in" in page_content: + retry += 1 + wait = retry * 10 + print(f"Retrying in {wait} seconds...") + await asyncio.sleep(wait) + else: + break + + # Enter password + print("[*] Entering password...", file=sys.stderr) + pretry = 0 + while pretry < 5: + password_input = await tab.find( + 'input[autocomplete="current-password"]', timeout=15 + ) + await password_input.click() + await asyncio.sleep(0.5) + await password_input.send_keys(password) + await asyncio.sleep(0.2) + await password_input.send_keys("\n") + await asyncio.sleep(2) + + page_content = await tab.get_content() + if "Could not log you in" in page_content: + pretry += 1 + wait = pretry * 10 + print(f"Retrying in {wait} seconds...") + await asyncio.sleep(wait) + else: + break + + # Handle 2FA if needed + page_content = await tab.get_content() + if "verification code" in page_content or "Enter code" in page_content: + if not totp_seed: + raise Exception("2FA required but no TOTP seed provided") + + print("[*] 2FA detected, entering code...", file=sys.stderr) + totp_code = pyotp.TOTP(totp_seed).now() + code_input = await tab.select('input[type="text"]') + await code_input.send_keys(totp_code + "\n") + await asyncio.sleep(3) + + # Get cookies + print("[*] Retrieving cookies...", file=sys.stderr) + for _ in range(20): # 20 second timeout + cookies = await browser.cookies.get_all() + cookies_dict = {cookie.name: cookie.value for cookie in cookies} + + if "auth_token" in cookies_dict and "ct0" in cookies_dict: + # Extract ID from twid cookie (may be URL-encoded) + user_id = None + if "twid" in cookies_dict: + twid = cookies_dict["twid"] + # Try to extract the ID from twid (format: u%3D or u=) + if "u%3D" in twid: + user_id = twid.split("u%3D")[1].split("&")[0].strip('"') + elif "u=" in twid: + user_id = twid.split("u=")[1].split("&")[0].strip('"') + + cookies_dict["username"] = username + if user_id: + cookies_dict["id"] = user_id + + return cookies_dict + + await asyncio.sleep(1) + + raise Exception("Timeout waiting for cookies") + + finally: + browser.stop() + + +async def main(): + if len(sys.argv) < 2: + print( + "Usage: python3 create_sessions_browser.py [--append sessions.jsonl] [--headless]" + ) + sys.exit(1) + + input = sys.argv[1] + append_file = None + headless = False + delay = 1 + + # Parse optional arguments + i = 2 + while i < len(sys.argv): + arg = sys.argv[i] + if arg == "--append": + if i + 1 < len(sys.argv): + append_file = sys.argv[i + 1] + i += 2 # Skip '--append' and filename + else: + print("[!] Error: --append requires a filename", file=sys.stderr) + sys.exit(1) + elif arg == "--headless": + headless = True + i += 1 + elif arg == "--delay": + delay = int(sys.argv[i + 1]) + i += 2 + else: + # Unkown args + print(f"[!] Warning: Unknown argument: {arg}", file=sys.stderr) + i += 1 + + accounts = [] + with open(input) as f: + accounts = json.load(f) + + if len(accounts) == 0: + print("no accounts in file") + sys.exit(0) + + sessions = 0 + for acc in accounts: + sessions += 1 + try: + cookies = await login_and_get_cookies(acc, headless) + session = { + "kind": "cookie", + "username": cookies["username"], + "id": cookies.get("id"), + "auth_token": cookies["auth_token"], + "ct0": cookies["ct0"], + } + + if append_file: + with open(append_file, "a") as f: + f.write(json.dumps(session) + "\n") + else: + print(json.dumps(session)) + + print(f"Progress: {sessions} / {len(accounts)}") + if sessions < len(accounts): + print("Waiting", delay, "seconds") + sleep(delay) + except Exception as error: + print( + f"[!] Error getting session for {acc["username"]}, skipping: {error}", + file=sys.stderr, + ) + + +if __name__ == "__main__": + asyncio.run(main()) From 5d28bd18c631417129a6c6a1ed45e20ef4528269 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 9 Feb 2026 17:32:03 +0100 Subject: [PATCH 176/302] Add preference for configuring sticky navbar Fixes #1354 --- src/nitter.nim | 4 ++-- src/prefs_impl.nim | 3 +++ src/routes/router_utils.nim | 7 +------ src/routes/unsupported.nim | 2 +- src/sass/index.scss | 5 ++++- src/sass/navbar.scss | 5 ++++- src/sass/profile/_base.scss | 6 +++++- src/sass/tweet/thread.scss | 6 ++++-- src/views/general.nim | 3 ++- 9 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/nitter.nim b/src/nitter.nim index e2d6bec..9e20ecb 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -66,10 +66,10 @@ settings: routes: get "/": - resp renderMain(renderSearch(), request, cfg, themePrefs()) + resp renderMain(renderSearch(), request, cfg, cookiePrefs()) get "/about": - resp renderMain(renderAbout(), request, cfg, themePrefs()) + resp renderMain(renderAbout(), request, cfg, cookiePrefs()) get "/explore": redirect("/about") diff --git a/src/prefs_impl.nim b/src/prefs_impl.nim index 8e2ac8f..e55c2b8 100644 --- a/src/prefs_impl.nim +++ b/src/prefs_impl.nim @@ -60,6 +60,9 @@ genPrefs: stickyProfile(checkbox, true): "Make profile sidebar stick to top" + stickyNav(checkbox, true): + "Keep navbar fixed to top" + bidiSupport(checkbox, false): "Support bidirectional text (makes clicking on tweets harder)" diff --git a/src/routes/router_utils.nim b/src/routes/router_utils.nim index a071a0d..34fd163 100644 --- a/src/routes/router_utils.nim +++ b/src/routes/router_utils.nim @@ -17,13 +17,8 @@ template cookiePrefs*(): untyped {.dirty.} = template cookiePref*(pref): untyped {.dirty.} = getPref(cookies(request), pref) -template themePrefs*(): Prefs = - var res = defaultPrefs - res.theme = cookiePref(theme) - res - template showError*(error: string; cfg: Config): string = - renderMain(renderError(error), request, cfg, themePrefs(), "Error") + renderMain(renderError(error), request, cfg, cookiePrefs(), "Error") template getPath*(): untyped {.dirty.} = $(parseUri(request.path) ? filterParams(request.params)) diff --git a/src/routes/unsupported.nim b/src/routes/unsupported.nim index 362b36b..e06a183 100644 --- a/src/routes/unsupported.nim +++ b/src/routes/unsupported.nim @@ -10,7 +10,7 @@ export feature proc createUnsupportedRouter*(cfg: Config) = router unsupported: template feature {.dirty.} = - resp renderMain(renderFeature(), request, cfg, themePrefs()) + resp renderMain(renderFeature(), request, cfg, cookiePrefs()) get "/about/feature": feature() get "/login/?@i?": feature() diff --git a/src/sass/index.scss b/src/sass/index.scss index 3f4b123..c12b814 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -115,11 +115,14 @@ ul { display: flex; flex-wrap: wrap; box-sizing: border-box; - padding-top: 50px; margin: auto; min-height: 100vh; } +body.fixed-nav .container { + padding-top: 50px; +} + .icon-container { display: inline; } diff --git a/src/sass/navbar.scss b/src/sass/navbar.scss index 86bfbe7..c999022 100644 --- a/src/sass/navbar.scss +++ b/src/sass/navbar.scss @@ -3,7 +3,6 @@ nav { display: flex; align-items: center; - position: fixed; background-color: var(--bg_overlays); box-shadow: 0 0 4px $shadow; padding: 0; @@ -16,6 +15,10 @@ nav { .icon-button button { color: var(--fg_nav); } + + body.fixed-nav & { + position: fixed; + } } .inner-nav { diff --git a/src/sass/profile/_base.scss b/src/sass/profile/_base.scss index b7f33e6..3abc736 100644 --- a/src/sass/profile/_base.scss +++ b/src/sass/profile/_base.scss @@ -39,7 +39,11 @@ text-align: left; vertical-align: top; max-width: 32%; - top: 50px; + top: 0; + + body.fixed-nav & { + top: 50px; + } } .profile-result { diff --git a/src/sass/tweet/thread.scss b/src/sass/tweet/thread.scss index 9d2fb64..d9bc457 100644 --- a/src/sass/tweet/thread.scss +++ b/src/sass/tweet/thread.scss @@ -16,8 +16,10 @@ .main-tweet, .replies { - padding-top: 50px; - margin-top: -50px; + body.fixed-nav & { + padding-top: 50px; + margin-top: -50px; + } } .main-tweet .tweet-content { diff --git a/src/views/general.nim b/src/views/general.nim index 2525841..3bae9d3 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -131,7 +131,8 @@ proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs; renderHead(prefs, cfg, req, titleText, desc, video, images, banner, ogTitle, rss, twitterLink) - body: + let bodyClass = if prefs.stickyNav: "fixed-nav" else: "" + body(class=bodyClass): renderNavbar(cfg, req, rss, twitterLink) tdiv(class="container"): From db36f75519b2295c184d107f29210141bdd01082 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 9 Feb 2026 20:23:31 +0100 Subject: [PATCH 177/302] Support restoring preferences via new prefs param Fixes #1352 Fixes #553 Fixes #249 --- src/nitter.nim | 5 +++++ src/prefs.nim | 9 +++++++-- src/prefs_impl.nim | 30 ++++++++++++++++++++++++++++++ src/routes/preferences.nim | 4 +++- src/routes/router_utils.nim | 32 +++++++++++++++++++++++++++++--- src/sass/index.scss | 18 ++++++++++++------ src/sass/inputs.scss | 12 ++++++++++++ src/utils.nim | 2 +- src/views/preferences.nim | 9 ++++++++- 9 files changed, 107 insertions(+), 14 deletions(-) diff --git a/src/nitter.nim b/src/nitter.nim index 9e20ecb..442a8c0 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -65,6 +65,11 @@ settings: reusePort = true routes: + before: + # skip all file URLs + cond "." notin request.path + applyUrlPrefs() + get "/": resp renderMain(renderSearch(), request, cfg, cookiePrefs()) diff --git a/src/prefs.nim b/src/prefs.nim index fa40a6d..573ccae 100644 --- a/src/prefs.nim +++ b/src/prefs.nim @@ -1,10 +1,10 @@ # SPDX-License-Identifier: AGPL-3.0-only -import tables +import tables, strutils, base64 import types, prefs_impl from config import get from parsecfg import nil -export genUpdatePrefs, genResetPrefs +export genUpdatePrefs, genResetPrefs, genApplyPrefs var defaultPrefs*: Prefs @@ -20,3 +20,8 @@ template getPref*(cookies: Table[string, string], pref): untyped = var res = defaultPrefs.`pref` genCookiePref(cookies, pref, res) res + +proc encodePrefs*(prefs: Prefs): string = + var encPairs: seq[string] + genEncodePrefs(prefs) + encode(encPairs.join("&"), safe=true) diff --git a/src/prefs_impl.nim b/src/prefs_impl.nim index e55c2b8..2faf8ef 100644 --- a/src/prefs_impl.nim +++ b/src/prefs_impl.nim @@ -205,6 +205,36 @@ macro genResetPrefs*(): untyped = result.add quote do: savePref(`name`, "", `req`, expire=true) +macro genEncodePrefs*(prefs): untyped = + result = nnkStmtList.newTree() + for pref in allPrefs(): + let + name = newLit(pref.name) + ident = ident(pref.name) + kind = newLit(pref.kind) + defaultIdent = nnkDotExpr.newTree(ident("defaultPrefs"), ident(pref.name)) + + result.add quote do: + when `kind` == checkbox: + if `prefs`.`ident` != `defaultIdent`: + if `prefs`.`ident`: + encPairs.add `name` & "=on" + else: + encPairs.add `name` & "=" + else: + if `prefs`.`ident` != `defaultIdent`: + encPairs.add `name` & "=" & `prefs`.`ident` + +macro genApplyPrefs*(params, req): untyped = + result = nnkStmtList.newTree() + for pref in allPrefs(): + let name = newLit(pref.name) + result.add quote do: + if `name` in `params`: + savePref(`name`, `params`[`name`], `req`) + else: + savePref(`name`, "", `req`, expire=true) + macro genPrefsType*(): untyped = let name = nnkPostfix.newTree(ident("*"), ident("Prefs")) result = quote do: diff --git a/src/routes/preferences.nim b/src/routes/preferences.nim index b8af03d..345ff34 100644 --- a/src/routes/preferences.nim +++ b/src/routes/preferences.nim @@ -20,7 +20,9 @@ proc createPrefRouter*(cfg: Config) = get "/settings": let prefs = cookiePrefs() - html = renderPreferences(prefs, refPath(), findThemes(cfg.staticDir)) + prefsCode = encodePrefs(prefs) + prefsUrl = getUrlPrefix(cfg) & "/?prefs=" & prefsCode + html = renderPreferences(prefs, refPath(), findThemes(cfg.staticDir), prefsUrl) resp renderMain(html, request, cfg, prefs, "Preferences") get "/settings/@i?": diff --git a/src/routes/router_utils.nim b/src/routes/router_utils.nim index 34fd163..2ef248a 100644 --- a/src/routes/router_utils.nim +++ b/src/routes/router_utils.nim @@ -1,15 +1,15 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, sequtils, uri, tables, json +import strutils, sequtils, uri, tables, json, base64 from jester import Request, cookies import ../views/general import ".."/[utils, prefs, types] -export utils, prefs, types, uri +export utils, prefs, types, uri, base64 template savePref*(pref, value: string; req: Request; expire=false) = if not expire or pref in cookies(req): setCookie(pref, value, daysForward(when expire: -10 else: 360), - httpOnly=true, secure=cfg.useHttps, sameSite=None) + httpOnly=true, secure=cfg.useHttps, sameSite=None, path="/") template cookiePrefs*(): untyped {.dirty.} = getPrefs(cookies(request)) @@ -38,5 +38,31 @@ template getCursor*(req: Request): string = proc getNames*(name: string): seq[string] = name.strip(chars={'/'}).split(",").filterIt(it.len > 0) +template applyUrlPrefs*() {.dirty.} = + if @"prefs".len > 0: + try: + let decoded = decode(@"prefs") + var params = initTable[string, string]() + for pair in decoded.split('&'): + let kv = pair.split('=', maxsplit=1) + if kv.len == 2: + params[kv[0]] = kv[1] + elif kv.len == 1 and kv[0].len > 0: + params[kv[0]] = "" + genApplyPrefs(params, request) + except: discard + + # Rebuild URL without prefs param + var params: seq[(string, string)] + for k, v in request.params: + if k != "prefs": + params.add (k, v) + + if params.len > 0: + let cleanUrl = request.getNativeReq.url ? params + redirect($cleanUrl) + else: + redirect(request.path) + template respJson*(node: JsonNode) = resp $node, "application/json" diff --git a/src/sass/index.scss b/src/sass/index.scss index c12b814..4ca4f3d 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -99,12 +99,18 @@ legend { margin-bottom: 8px; } -.preferences .note { - border-top: 1px solid var(--border_grey); - border-bottom: 1px solid var(--border_grey); - padding: 6px 0 8px 0; - margin-bottom: 8px; - margin-top: 16px; +.preferences { + .note { + border-top: 1px solid var(--border_grey); + border-bottom: 1px solid var(--border_grey); + padding: 6px 0 8px 0; + margin-bottom: 8px; + margin-top: 16px; + } + + .bookmark-note { + margin: 0; + } } ul { diff --git a/src/sass/inputs.scss b/src/sass/inputs.scss index aafa5b8..7ea2b0a 100644 --- a/src/sass/inputs.scss +++ b/src/sass/inputs.scss @@ -200,4 +200,16 @@ input::-webkit-datetime-edit-year-field:focus { .pref-reset { float: left; } + + .prefs-code { + background-color: var(--bg_elements); + border: 1px solid var(--accent_border); + color: var(--fg_color); + font-size: 12px; + padding: 6px 8px; + margin: 4px 0; + word-break: break-all; + white-space: pre-wrap; + user-select: all; + } } diff --git a/src/utils.nim b/src/utils.nim index c96a6dd..667299c 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -9,7 +9,7 @@ var const https* = "https://" twimg* = "pbs.twimg.com/" - nitterParams = ["name", "tab", "id", "list", "referer", "scroll"] + nitterParams* = ["name", "tab", "id", "list", "referer", "scroll", "prefs"] twitterDomains = @[ "twitter.com", "pic.twitter.com", diff --git a/src/views/preferences.nim b/src/views/preferences.nim index 1787704..40e9e11 100644 --- a/src/views/preferences.nim +++ b/src/views/preferences.nim @@ -32,7 +32,8 @@ macro renderPrefs*(): untyped = result[2].add stmt -proc renderPreferences*(prefs: Prefs; path: string; themes: seq[string]): VNode = +proc renderPreferences*(prefs: Prefs; path: string; themes: seq[string]; + prefsUrl: string): VNode = buildHtml(tdiv(class="overlay-panel")): fieldset(class="preferences"): form(`method`="post", action="/saveprefs", autocomplete="off"): @@ -40,6 +41,12 @@ proc renderPreferences*(prefs: Prefs; path: string; themes: seq[string]): VNode renderPrefs() + legend: text "Bookmark" + p(class="bookmark-note"): + text "Save this URL to restore your preferences (?prefs works on all pages)" + pre(class="prefs-code"): + text prefsUrl + h4(class="note"): text "Preferences are stored client-side using cookies without any personal information." From b85e8c5d7d7af91a2d86a497265142b6f6a4f490 Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 9 Feb 2026 21:54:57 +0100 Subject: [PATCH 178/302] Support preference overrides using URL params Fixes #186 --- src/nitter.nim | 6 +++--- src/prefs.nim | 15 +++++---------- src/prefs_impl.nim | 34 +++++++--------------------------- src/routes/embed.nim | 2 +- src/routes/list.nim | 4 ++-- src/routes/media.nim | 2 +- src/routes/preferences.nim | 2 +- src/routes/resolver.nim | 4 ++-- src/routes/router_utils.nim | 32 +++++++++++++------------------- src/routes/rss.nim | 19 ++++++++++++------- src/routes/search.nim | 2 +- src/routes/status.nim | 2 +- src/routes/timeline.nim | 2 +- src/routes/unsupported.nim | 2 +- src/sass/index.scss | 1 + src/sass/inputs.scss | 2 +- src/views/general.nim | 4 +--- src/views/preferences.nim | 2 ++ src/views/renderutils.nim | 6 +++--- src/views/rss.nimf | 22 +++++++++++----------- 20 files changed, 70 insertions(+), 95 deletions(-) diff --git a/src/nitter.nim b/src/nitter.nim index 442a8c0..ec2decf 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -71,10 +71,10 @@ routes: applyUrlPrefs() get "/": - resp renderMain(renderSearch(), request, cfg, cookiePrefs()) + resp renderMain(renderSearch(), request, cfg, requestPrefs()) get "/about": - resp renderMain(renderAbout(), request, cfg, cookiePrefs()) + resp renderMain(renderAbout(), request, cfg, requestPrefs()) get "/explore": redirect("/about") @@ -85,7 +85,7 @@ routes: get "/i/redirect": let url = decodeUrl(@"url") if url.len == 0: resp Http404 - redirect(replaceUrls(url, cookiePrefs())) + redirect(replaceUrls(url, requestPrefs())) error Http404: resp Http404, showError("Page not found", cfg) diff --git a/src/prefs.nim b/src/prefs.nim index 573ccae..1a75f75 100644 --- a/src/prefs.nim +++ b/src/prefs.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import tables, strutils, base64 +import tables, strutils import types, prefs_impl from config import get from parsecfg import nil @@ -11,17 +11,12 @@ var defaultPrefs*: Prefs proc updateDefaultPrefs*(cfg: parsecfg.Config) = genDefaultPrefs() -proc getPrefs*(cookies: Table[string, string]): Prefs = +proc getPrefs*(cookies, params: Table[string, string]): Prefs = result = defaultPrefs - genCookiePrefs(cookies) - -template getPref*(cookies: Table[string, string], pref): untyped = - bind genCookiePref - var res = defaultPrefs.`pref` - genCookiePref(cookies, pref, res) - res + genParsePrefs(cookies) + genParsePrefs(params) proc encodePrefs*(prefs: Prefs): string = var encPairs: seq[string] genEncodePrefs(prefs) - encode(encPairs.join("&"), safe=true) + encPairs.join(",") diff --git a/src/prefs_impl.nim b/src/prefs_impl.nim index 2faf8ef..149eadf 100644 --- a/src/prefs_impl.nim +++ b/src/prefs_impl.nim @@ -130,7 +130,7 @@ macro genDefaultPrefs*(): untyped = result.add quote do: defaultPrefs.`ident` = cfg.get("Preferences", `name`, `default`) -macro genCookiePrefs*(cookies): untyped = +macro genParsePrefs*(prefs): untyped = result = nnkStmtList.newTree() for pref in allPrefs(): let @@ -140,37 +140,17 @@ macro genCookiePrefs*(cookies): untyped = options = pref.options result.add quote do: - if `name` in `cookies`: + if `name` in `prefs`: when `kind` == input or `name` == "theme": - result.`ident` = `cookies`[`name`] + result.`ident` = `prefs`[`name`] elif `kind` == checkbox: - result.`ident` = `cookies`[`name`] == "on" + result.`ident` = `prefs`[`name`] == "on" or + `prefs`[`name`] == "true" or + `prefs`[`name`] == "1" else: - let value = `cookies`[`name`] + let value = `prefs`[`name`] if value in `options`: result.`ident` = value -macro genCookiePref*(cookies, prefName, res): untyped = - result = nnkStmtList.newTree() - for pref in allPrefs(): - let ident = ident(pref.name) - if ident != prefName: - continue - - let - name = pref.name - kind = newLit(pref.kind) - options = pref.options - - result.add quote do: - if `name` in `cookies`: - when `kind` == input or `name` == "theme": - `res` = `cookies`[`name`] - elif `kind` == checkbox: - `res` = `cookies`[`name`] == "on" - else: - let value = `cookies`[`name`] - if value in `options`: `res` = value - macro genUpdatePrefs*(): untyped = result = nnkStmtList.newTree() let req = ident("request") diff --git a/src/routes/embed.nim b/src/routes/embed.nim index 994364b..0527d3d 100644 --- a/src/routes/embed.nim +++ b/src/routes/embed.nim @@ -19,7 +19,7 @@ proc createEmbedRouter*(cfg: Config) = get "/@user/status/@id/embed": let tweet = await getGraphTweetResult(@"id") - prefs = cookiePrefs() + prefs = requestPrefs() path = getPath() if tweet == nil: diff --git a/src/routes/list.nim b/src/routes/list.nim index ac3e97e..7dadc22 100644 --- a/src/routes/list.nim +++ b/src/routes/list.nim @@ -36,7 +36,7 @@ proc createListRouter*(cfg: Config) = get "/i/lists/@id/?": cond '.' notin @"id" let - prefs = cookiePrefs() + prefs = requestPrefs() list = await getCachedList(id=(@"id")) timeline = await getGraphListTweets(list.id, getCursor()) vnode = renderTimelineTweets(timeline, prefs, request.path) @@ -45,7 +45,7 @@ proc createListRouter*(cfg: Config) = get "/i/lists/@id/members": cond '.' notin @"id" let - prefs = cookiePrefs() + prefs = requestPrefs() list = await getCachedList(id=(@"id")) members = await getGraphListMembers(list, getCursor()) respList(list, members, list.title, renderTimelineUsers(members, prefs, request.path)) diff --git a/src/routes/media.nim b/src/routes/media.nim index 011d0f3..b3e5374 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -143,6 +143,6 @@ proc createMediaRouter*(cfg: Config) = if ".m3u8" in url: let vid = await safeFetch(url) - content = proxifyVideo(vid, cookiePref(proxyVideos)) + content = proxifyVideo(vid, requestPrefs().proxyVideos) resp content, m3u8Mime diff --git a/src/routes/preferences.nim b/src/routes/preferences.nim index 345ff34..5886c0e 100644 --- a/src/routes/preferences.nim +++ b/src/routes/preferences.nim @@ -19,7 +19,7 @@ proc createPrefRouter*(cfg: Config) = router preferences: get "/settings": let - prefs = cookiePrefs() + prefs = requestPrefs() prefsCode = encodePrefs(prefs) prefsUrl = getUrlPrefix(cfg) & "/?prefs=" & prefsCode html = renderPreferences(prefs, refPath(), findThemes(cfg.staticDir), prefsUrl) diff --git a/src/routes/resolver.nim b/src/routes/resolver.nim index 1baf873..5f074a5 100644 --- a/src/routes/resolver.nim +++ b/src/routes/resolver.nim @@ -18,8 +18,8 @@ proc createResolverRouter*(cfg: Config) = router resolver: get "/cards/@card/@id": let url = "https://cards.twitter.com/cards/$1/$2" % [@"card", @"id"] - respResolved(await resolve(url, cookiePrefs()), "card") + respResolved(await resolve(url, requestPrefs()), "card") get "/t.co/@url": let url = "https://t.co/" & @"url" - respResolved(await resolve(url, cookiePrefs()), "t.co") + respResolved(await resolve(url, requestPrefs()), "t.co") diff --git a/src/routes/router_utils.nim b/src/routes/router_utils.nim index 2ef248a..379280c 100644 --- a/src/routes/router_utils.nim +++ b/src/routes/router_utils.nim @@ -1,24 +1,21 @@ # SPDX-License-Identifier: AGPL-3.0-only -import strutils, sequtils, uri, tables, json, base64 +import strutils, sequtils, uri, tables, json from jester import Request, cookies import ../views/general import ".."/[utils, prefs, types] -export utils, prefs, types, uri, base64 +export utils, prefs, types, uri template savePref*(pref, value: string; req: Request; expire=false) = if not expire or pref in cookies(req): setCookie(pref, value, daysForward(when expire: -10 else: 360), httpOnly=true, secure=cfg.useHttps, sameSite=None, path="/") -template cookiePrefs*(): untyped {.dirty.} = - getPrefs(cookies(request)) - -template cookiePref*(pref): untyped {.dirty.} = - getPref(cookies(request), pref) +template requestPrefs*(): untyped {.dirty.} = + getPrefs(cookies(request), params(request)) template showError*(error: string; cfg: Config): string = - renderMain(renderError(error), request, cfg, cookiePrefs(), "Error") + renderMain(renderError(error), request, cfg, requestPrefs(), "Error") template getPath*(): untyped {.dirty.} = $(parseUri(request.path) ? filterParams(request.params)) @@ -40,17 +37,14 @@ proc getNames*(name: string): seq[string] = template applyUrlPrefs*() {.dirty.} = if @"prefs".len > 0: - try: - let decoded = decode(@"prefs") - var params = initTable[string, string]() - for pair in decoded.split('&'): - let kv = pair.split('=', maxsplit=1) - if kv.len == 2: - params[kv[0]] = kv[1] - elif kv.len == 1 and kv[0].len > 0: - params[kv[0]] = "" - genApplyPrefs(params, request) - except: discard + var prefParams = initTable[string, string]() + for pair in @"prefs".split(','): + let kv = pair.split('=', maxsplit=1) + if kv.len == 2: + prefParams[kv[0]] = kv[1] + elif kv.len == 1 and kv[0].len > 0: + prefParams[kv[0]] = "" + genApplyPrefs(prefParams, request) # Rebuild URL without prefs param var params: seq[(string, string)] diff --git a/src/routes/rss.nim b/src/routes/rss.nim index b0e781d..6902001 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -15,7 +15,7 @@ proc redisKey*(page, name, cursor: string): string = if cursor.len > 0: result &= ":" & cursor -proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async.} = +proc timelineRss*(req: Request; cfg: Config; query: Query; prefs: Prefs): Future[Rss] {.async.} = var profile: Profile let name = req.params.getOrDefault("name") @@ -39,7 +39,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async. return Rss(feed: profile.user.username, cursor: "suspended") if profile.user.fullname.len > 0: - let rss = renderTimelineRss(profile, cfg, multi=(names.len > 1)) + let rss = renderTimelineRss(profile, cfg, prefs, multi=(names.len > 1)) return Rss(feed: rss, cursor: profile.tweets.bottom) template respRss*(rss, page) = @@ -64,7 +64,9 @@ proc createRssRouter*(cfg: Config) = if @"q".len > 200: resp Http400, showError("Search input too long.", cfg) - let query = initQuery(params(request)) + let + prefs = requestPrefs() + query = initQuery(params(request)) if query.kind != tweets: resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg) @@ -78,7 +80,7 @@ proc createRssRouter*(cfg: Config) = let tweets = await getGraphTweetSearch(query, cursor) rss.cursor = tweets.bottom - rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg) + rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg, prefs) await cacheRss(key, rss) respRss(rss, "Search") @@ -87,6 +89,7 @@ proc createRssRouter*(cfg: Config) = cond cfg.enableRss cond '.' notin @"name" let + prefs = requestPrefs() name = @"name" key = redisKey("twitter", name, getCursor()) @@ -94,7 +97,7 @@ proc createRssRouter*(cfg: Config) = if rss.cursor.len > 0: respRss(rss, "User") - rss = await timelineRss(request, cfg, Query(fromUser: @[name])) + rss = await timelineRss(request, cfg, Query(fromUser: @[name]), prefs) await cacheRss(key, rss) respRss(rss, "User") @@ -104,6 +107,7 @@ proc createRssRouter*(cfg: Config) = cond '.' notin @"name" cond @"tab" in ["with_replies", "media", "search"] let + prefs = requestPrefs() name = @"name" tab = @"tab" query = @@ -122,7 +126,7 @@ proc createRssRouter*(cfg: Config) = if rss.cursor.len > 0: respRss(rss, "User") - rss = await timelineRss(request, cfg, query) + rss = await timelineRss(request, cfg, query, prefs) await cacheRss(key, rss) respRss(rss, "User") @@ -147,6 +151,7 @@ proc createRssRouter*(cfg: Config) = get "/i/lists/@id/rss": cond cfg.enableRss let + prefs = requestPrefs() id = @"id" cursor = getCursor() key = redisKey("lists", id, cursor) @@ -159,7 +164,7 @@ proc createRssRouter*(cfg: Config) = list = await getCachedList(id=id) timeline = await getGraphListTweets(list.id, cursor) rss.cursor = timeline.bottom - rss.feed = renderListRss(timeline.content, list, cfg) + rss.feed = renderListRss(timeline.content, list, cfg, prefs) await cacheRss(key, rss) respRss(rss, "List") diff --git a/src/routes/search.nim b/src/routes/search.nim index e9f991d..4220427 100644 --- a/src/routes/search.nim +++ b/src/routes/search.nim @@ -19,7 +19,7 @@ proc createSearchRouter*(cfg: Config) = resp Http400, showError("Search input too long.", cfg) let - prefs = cookiePrefs() + prefs = requestPrefs() query = initQuery(params(request)) title = "Search" & (if q.len > 0: " (" & q & ")" else: "") diff --git a/src/routes/status.nim b/src/routes/status.nim index 0168dac..838b327 100644 --- a/src/routes/status.nim +++ b/src/routes/status.nim @@ -21,7 +21,7 @@ proc createStatusRouter*(cfg: Config) = if id.len > 19 or id.any(c => not c.isDigit): resp Http404, showError("Invalid tweet ID", cfg) - let prefs = cookiePrefs() + let prefs = requestPrefs() # used for the infinite scroll feature if @"scroll".len > 0: diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 2ac87bb..d6d8c21 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -117,7 +117,7 @@ proc createTimelineRouter*(cfg: Config) = cond @"name".allCharsInSet({'a'..'z', 'A'..'Z', '0'..'9', '_', ','}) cond @"tab" in ["with_replies", "media", "search", ""] let - prefs = cookiePrefs() + prefs = requestPrefs() after = getCursor() names = getNames(@"name") diff --git a/src/routes/unsupported.nim b/src/routes/unsupported.nim index e06a183..345dee7 100644 --- a/src/routes/unsupported.nim +++ b/src/routes/unsupported.nim @@ -10,7 +10,7 @@ export feature proc createUnsupportedRouter*(cfg: Config) = router unsupported: template feature {.dirty.} = - resp renderMain(renderFeature(), request, cfg, cookiePrefs()) + resp renderMain(renderFeature(), request, cfg, requestPrefs()) get "/about/feature": feature() get "/login/?@i?": feature() diff --git a/src/sass/index.scss b/src/sass/index.scss index 4ca4f3d..a85002e 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -110,6 +110,7 @@ legend { .bookmark-note { margin: 0; + margin-bottom: 10px; } } diff --git a/src/sass/inputs.scss b/src/sass/inputs.scss index 7ea2b0a..c69711a 100644 --- a/src/sass/inputs.scss +++ b/src/sass/inputs.scss @@ -205,7 +205,7 @@ input::-webkit-datetime-edit-year-field:focus { background-color: var(--bg_elements); border: 1px solid var(--accent_border); color: var(--fg_color); - font-size: 12px; + font-size: 13px; padding: 6px 8px; margin: 4px 0; word-break: break-all; diff --git a/src/views/general.nim b/src/views/general.nim index 3bae9d3..74ddcb4 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -39,9 +39,7 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode = proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; video=""; images: seq[string] = @[]; banner=""; ogTitle=""; rss=""; alternate=""): VNode = - var theme = prefs.theme.toTheme - if "theme" in req.params: - theme = req.params["theme"].toTheme + let theme = prefs.theme.toTheme let ogType = if video.len > 0: "video" diff --git a/src/views/preferences.nim b/src/views/preferences.nim index 40e9e11..b051a01 100644 --- a/src/views/preferences.nim +++ b/src/views/preferences.nim @@ -46,6 +46,8 @@ proc renderPreferences*(prefs: Prefs; path: string; themes: seq[string]; text "Save this URL to restore your preferences (?prefs works on all pages)" pre(class="prefs-code"): text prefsUrl + p(class="bookmark-note"): + verbatim "You can override preferences with query parameters (e.g. ?hlsPlayback=on). These overrides aren't saved to cookies, and links won't retain the parameters. Intended for configuring RSS feeds and other cookieless environments. Hover over a preference to see its name." h4(class="note"): text "Preferences are stored client-side using cookies without any personal information." diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index a5fe3b2..6753c5a 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -65,20 +65,20 @@ proc buttonReferer*(action, text, path: string; class=""; `method`="post"): VNod text text proc genCheckbox*(pref, label: string; state: bool): VNode = - buildHtml(label(class="pref-group checkbox-container")): + buildHtml(label(class="pref-group checkbox-container", title=pref)): text label input(name=pref, `type`="checkbox", checked=state) span(class="checkbox") proc genInput*(pref, label, state, placeholder: string; class=""; autofocus=true): VNode = let p = placeholder - buildHtml(tdiv(class=("pref-group pref-input " & class))): + buildHtml(tdiv(class=("pref-group pref-input " & class), title=pref)): if label.len > 0: label(`for`=pref): text label input(name=pref, `type`="text", placeholder=p, value=state, autofocus=(autofocus and state.len == 0)) proc genSelect*(pref, label, state: string; options: seq[string]): VNode = - buildHtml(tdiv(class="pref-group pref-input")): + buildHtml(tdiv(class="pref-group pref-input", title=pref)): label(`for`=pref): text label select(name=pref): for opt in options: diff --git a/src/views/rss.nimf b/src/views/rss.nimf index 717ad99..46d7eaf 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -49,10 +49,10 @@ Twitter feed for: ${desc}. Generated by ${getUrlPrefix(cfg)} #end if #end proc # -#proc renderRssTweet(tweet: Tweet; cfg: Config): string = +#proc renderRssTweet(tweet: Tweet; cfg: Config; prefs: Prefs): string = #let tweet = tweet.retweet.get(tweet) #let urlPrefix = getUrlPrefix(cfg) -#let text = replaceUrls(tweet.text, defaultPrefs, absolute=urlPrefix) +#let text = replaceUrls(tweet.text, prefs, absolute=urlPrefix)

${text.replace("\n", "
\n")}

#if tweet.photos.len > 0: # for photo in tweet.photos: @@ -81,7 +81,7 @@ Twitter feed for: ${desc}. Generated by ${getUrlPrefix(cfg)}
${quoteTweet.user.fullname} (@${quoteTweet.user.username})

-${renderRssTweet(quoteTweet, cfg)} +${renderRssTweet(quoteTweet, cfg, prefs)}