Skip to content

Commit

Permalink
extracting ytcfg to make innertube work again
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeichhorn committed Jan 19, 2025
1 parent 809bcce commit ca8a8ed
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 6 deletions.
34 changes: 34 additions & 0 deletions Sources/YouTubeKit/Extraction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,40 @@ class Extraction {
return nil
}

struct YtCfg: Decodable {
let VISITOR_DATA: String?
let INNERTUBE_CONTEXT: Context?

struct Context: Decodable {
let client: Client

struct Client: Decodable {
let visitorData: String?
let userAgent: String?
}
}

var visitorData: String? {
VISITOR_DATA ?? INNERTUBE_CONTEXT?.client.visitorData
}

var userAgent: String? {
INNERTUBE_CONTEXT?.client.userAgent
}
}

class func extractYtCfg(from html: String) throws -> YtCfg {
if #available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) {
let regex = #/ytcfg\.set\s*\(\s*(?={)/#
let cfg = try parseForObject(YtCfg.self, html: html, precedingRegex: regex)
return cfg
} else {
let regex = NSRegularExpression(#"ytcfg\.set\s*\(\s*"#)
let cfg = try parseForObject(YtCfg.self, html: html, precedingRegex: regex)
return cfg
}
}

/// Parses input html to find the end of a JavaScript object.
/// - parameter html: HTML to be parsed for an object.
/// - parameter precedingRegex: Regex to find the string preceding the object.
Expand Down
10 changes: 7 additions & 3 deletions Sources/YouTubeKit/InnerTube.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,12 @@ class InnerTube {
request.httpMethod = "post"
request.httpBody = data
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Mozilla/5.0", forHTTPHeaderField: "User-Agent")
request.addValue(ytcfg.userAgent ?? "Mozilla/5.0", forHTTPHeaderField: "User-Agent")
request.addValue("en-US,en", forHTTPHeaderField: "accept-language")

request.addValue(ytcfg.visitorData ?? "", forHTTPHeaderField: "X-Goog-Visitor-Id")
request.addValue("https://www.youtube.com", forHTTPHeaderField: "Origin")

for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
Expand Down Expand Up @@ -235,8 +238,9 @@ class InnerTube {
func player(videoID: String) async throws -> VideoInfo {
let endpoint = baseURL + "/player"
let query = [
URLQueryItem(name: "key", value: apiKey)
]
URLQueryItem(name: "key", value: apiKey),
URLQueryItem(name: "prettyPrint", value: "false")
].filter { !($0.value?.isEmpty ?? true) }
let request = playerRequest(forVideoID: videoID)
return try await callAPI(endpoint: endpoint, query: query, object: request)
}
Expand Down
21 changes: 18 additions & 3 deletions Sources/YouTubeKit/YouTube.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class YouTube {
private var playerConfigArgs: [String: Any]?
private var _ageRestricted: Bool?
private var _signatureTimestamp: Int?
private var _ytcfg: Extraction.YtCfg?

private var _fmtStreams: [Stream]?

Expand Down Expand Up @@ -206,6 +207,17 @@ public class YouTube {
}
}

var ytcfg: Extraction.YtCfg {
get async throws {
if let cached = _ytcfg {
return cached
}

_ytcfg = try await Extraction.extractYtCfg(from: watchHTML)
return _ytcfg!
}
}

/// Interface to query both adaptive (DASH) and progressive streams.
/// Returns a list of streams if they have been initialized.
/// If the streams have not been initialized, finds all relevant streams and initializes them.
Expand Down Expand Up @@ -335,11 +347,12 @@ public class YouTube {
}

let signatureTimestamp = try await signatureTimestamp
let ytcfg = try await ytcfg

let innertubeClients: [InnerTube.ClientType] = [.ios, .mWeb]

let results: [Result<InnerTube.VideoInfo, Error>] = await innertubeClients.concurrentMap { [videoID, useOAuth, allowOAuthCache] client in
let innertube = InnerTube(client: client, signatureTimestamp: signatureTimestamp, useOAuth: useOAuth, allowCache: allowOAuthCache)
let innertube = InnerTube(client: client, signatureTimestamp: signatureTimestamp, ytcfg: ytcfg, useOAuth: useOAuth, allowCache: allowOAuthCache)

do {
let innertubeResponse = try await innertube.player(videoID: videoID)
Expand Down Expand Up @@ -383,7 +396,8 @@ public class YouTube {

private func loadAdditionalVideoInfos(forClient client: InnerTube.ClientType) async throws -> InnerTube.VideoInfo {
let signatureTimestamp = try await signatureTimestamp
let innertube = InnerTube(client: client, signatureTimestamp: signatureTimestamp, useOAuth: useOAuth, allowCache: allowOAuthCache)
let ytcfg = try await ytcfg
let innertube = InnerTube(client: client, signatureTimestamp: signatureTimestamp, ytcfg: ytcfg, useOAuth: useOAuth, allowCache: allowOAuthCache)
let videoInfo = try await innertube.player(videoID: videoID)

// ignore if incorrect videoID
Expand All @@ -397,7 +411,8 @@ public class YouTube {

private func bypassAgeGate() async throws {
let signatureTimestamp = try await signatureTimestamp
let innertube = InnerTube(client: .tvEmbed, signatureTimestamp: signatureTimestamp, useOAuth: useOAuth, allowCache: allowOAuthCache)
let ytcfg = try await ytcfg
let innertube = InnerTube(client: .tvEmbed, signatureTimestamp: signatureTimestamp, ytcfg: ytcfg, useOAuth: useOAuth, allowCache: allowOAuthCache)
let innertubeResponse = try await innertube.player(videoID: videoID)

if innertubeResponse.playabilityStatus?.status == "UNPLAYABLE" || innertubeResponse.playabilityStatus?.status == "LOGIN_REQUIRED" {
Expand Down

0 comments on commit ca8a8ed

Please sign in to comment.