import Foundation#if canImport(FoundationNetworking)import FoundationNetworking#endif #if os(macOS)import SweetCookieKit#endif #if os(macOS)private let t3ChatCookieImportOrder: BrowserCookieImportOrder = ProviderDefaults.metadata[.t3chat]?.browserCookieOrder ?? Browser.defaultImportOrder public enum T3ChatCookieImporter { private static let cookieClient = BrowserCookieClient() private static let cookieDomains = ["t3.chat", "www.t3.chat"] public struct SessionInfo: Sendable { public let cookieHeader: String public let sourceLabel: String public init(cookieHeader: String, sourceLabel: String) { self.cookieHeader = cookieHeader self.sourceLabel = sourceLabel } } public static func importSession( browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) throws -> SessionInfo { let log: (String) -> Void = { msg in logger?("[t3chat-cookie] \(msg)") } let installed = t3ChatCookieImportOrder.cookieImportCandidates(using: browserDetection) for browserSource in installed { do { let query = BrowserCookieQuery(domains: self.cookieDomains) let sources = try self.cookieClient.codexBarRecords( matching: query, in: browserSource, logger: log) for source in sources where !source.records.isEmpty { let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin) guard !cookies.isEmpty else { continue } let names = cookies.map(\.name).joined(separator: ", ") log("\(source.label) cookies: \(names)") let header = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ") return SessionInfo(cookieHeader: header, sourceLabel: source.label) } } catch { BrowserCookieAccessGate.recordIfNeeded(error) log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)") } } throw T3ChatUsageError.noSessionCookie }}#endif public struct T3ChatUsageFetcher: Sendable { private static let log = CodexBarLog.logger(LogCategories.t3chat) private static let baseURL = URL(string: "https://t3.chat")! private static let refererURL = URL(string: "https://t3.chat/settings/customization")! /// Browser fingerprint defaults are only fallbacks; full cURL captures override these forwarded headers. private static let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" /// Captured from T3 Chat's getCustomerData tRPC request shape in May 2026. private static let input = #"{"0":{"json":{"sessionId":null},"meta":{"values":{"sessionId":["undefined"]}}}}"# private static let forwardedManualHeaders = [ "accept": "Accept", "accept-language": "Accept-Language", "cache-control": "Cache-Control", "pragma": "Pragma", "priority": "Priority", "referer": "Referer", "sec-fetch-dest": "Sec-Fetch-Dest", "sec-fetch-mode": "Sec-Fetch-Mode", "sec-fetch-site": "Sec-Fetch-Site", "trpc-accept": "trpc-accept", "user-agent": "User-Agent", "x-client-context": "x-client-context", "x-deployment-id": "X-Deployment-Id", "x-trpc-batch": "x-trpc-batch", "x-trpc-source": "x-trpc-source", ] public struct RequestContext: Sendable { public let cookieHeader: String public let headers: [String: String] public init(cookieHeader: String, headers: [String: String] = [:]) { self.cookieHeader = cookieHeader self.headers = headers } } public let browserDetection: BrowserDetection public init(browserDetection: BrowserDetection) { self.browserDetection = browserDetection } public func fetch( cookieHeaderOverride: String? = nil, timeout: TimeInterval = 15, logger: ((String) -> Void)? = nil, now: Date = Date(), transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot { let log: (String) -> Void = { msg in logger?("[t3chat] \(msg)") } let context = try await self.resolveRequestContext(override: cookieHeaderOverride, logger: log) if let logger { let names = CookieHeaderNormalizer.pairs(from: context.cookieHeader).map(\.name) if !names.isEmpty { logger("[t3chat] Cookie names: \(names.joined(separator: ", "))") } if !context.headers.isEmpty { let headerNames = context.headers.keys.sorted().joined(separator: ", ") logger("[t3chat] Forwarding captured headers: \(headerNames)") } } return try await Self.fetchCustomerData( context: context, timeout: timeout, now: now, transport: transport) } public func debugRawProbe(cookieHeaderOverride: String? = nil) async -> String { let stamp = ISO8601DateFormatter().string(from: Date()) var lines: [String] = [] lines.append("=== T3 Chat Debug Probe @ \(stamp) ===") lines.append("") do { let snapshot = try await self.fetch( cookieHeaderOverride: cookieHeaderOverride, logger: { msg in lines.append(msg) }) lines.append("") lines.append("Fetch Success") lines.append("subTier=\(snapshot.customerData.subTier ?? "nil")") lines.append("usageBand=\(snapshot.customerData.usageBand ?? "nil")") lines .append( "usageFourHourPercentage=\(snapshot.customerData.usageFourHourPercentage?.description ?? "nil")") lines.append("usageMonthPercentage=\(snapshot.customerData.usageMonthPercentage?.description ?? "nil")") lines.append("usagePeriodPercentage=\(snapshot.customerData.usagePeriodPercentage?.description ?? "nil")") lines .append( "usageFourHourNextResetAt=\(snapshot.customerData.usageFourHourNextResetAt?.description ?? "nil")") lines.append("billingNextResetAt=\(snapshot.customerData.billingNextResetAt?.description ?? "nil")") } catch { lines.append("") lines.append("Probe Failed: \(error.localizedDescription)") } return lines.joined(separator: "\n") } public static func fetchCustomerData( cookieHeader: String, timeout: TimeInterval = 15, now: Date = Date(), transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot { guard let normalizedCookieHeader = CookieHeaderNormalizer.normalize(cookieHeader) else { throw T3ChatUsageError.noSessionCookie } return try await self.fetchCustomerData( context: RequestContext(cookieHeader: normalizedCookieHeader), timeout: timeout, now: now, transport: transport) } public static func fetchCustomerData( context: RequestContext, timeout: TimeInterval = 15, now: Date = Date(), transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot { guard let normalizedCookieHeader = CookieHeaderNormalizer.normalize(context.cookieHeader) else { throw T3ChatUsageError.noSessionCookie } let url = try self.customerDataURL() var request = URLRequest(url: url) request.httpMethod = "GET" request.timeoutInterval = timeout self.applyDefaultHeaders(to: &request) for (name, value) in context.headers { request.setValue(value, forHTTPHeaderField: name) } request.setValue(self.baseURL.absoluteString, forHTTPHeaderField: "Origin") request.setValue(normalizedCookieHeader, forHTTPHeaderField: "Cookie") let response = try await transport.response(for: request) let data = response.data guard response.statusCode == 200 else { let body = String(data: data.prefix(200), encoding: .utf8) ?? "<binary>" Self.log.error("T3 Chat API returned \(response.statusCode): \(body)") if response.statusCode == 401 || response.statusCode == 403 { throw T3ChatUsageError.invalidCredentials } if response.statusCode == 429, response.response.value(forHTTPHeaderField: "x-vercel-mitigated") == "challenge" { throw T3ChatUsageError.vercelChallenge } throw T3ChatUsageError.apiError("HTTP \(response.statusCode)") } do { return try T3ChatUsageParser.parseJSONLines(data, now: now) } catch { let preview = String(data: data.prefix(500), encoding: .utf8) ?? "<binary>" Self.log.error("T3 Chat parse failed: \(error.localizedDescription) response=\(preview)") throw error } } private func resolveRequestContext( override: String?, logger: ((String) -> Void)?) async throws -> RequestContext { if let override = Self.requestContext(from: override) { let source = override.headers.isEmpty ? "manual cookie header" : "manual cURL capture" logger?("[t3chat] Using \(source)") return override } #if os(macOS) let session = try T3ChatCookieImporter.importSession( browserDetection: self.browserDetection, logger: logger) logger?("[t3chat] Using cookies from \(session.sourceLabel)") return RequestContext(cookieHeader: session.cookieHeader) #else throw T3ChatUsageError.noSessionCookie #endif } static func requestContext(from raw: String?) -> RequestContext? { guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil } let headerFields = Self.headerFields(from: raw) guard let cookieHeader = Self.cookieHeader(from: headerFields) ?? CookieHeaderNormalizer.normalize(raw) else { return nil } let headers = Self.forwardedHeaders(from: headerFields) return RequestContext(cookieHeader: cookieHeader, headers: headers) } private static func applyDefaultHeaders(to request: inout URLRequest) { request.setValue("*/*", forHTTPHeaderField: "Accept") request.setValue("application/jsonl", forHTTPHeaderField: "trpc-accept") request.setValue("web-client", forHTTPHeaderField: "x-trpc-source") request.setValue("true", forHTTPHeaderField: "x-trpc-batch") request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "Accept-Language") request.setValue(self.userAgent, forHTTPHeaderField: "User-Agent") request.setValue(self.refererURL.absoluteString, forHTTPHeaderField: "Referer") request.setValue("empty", forHTTPHeaderField: "Sec-Fetch-Dest") request.setValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode") request.setValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site") request.setValue("u=4", forHTTPHeaderField: "Priority") request.setValue("no-cache", forHTTPHeaderField: "Pragma") request.setValue("no-cache", forHTTPHeaderField: "Cache-Control") } private static func forwardedHeaders(from fields: [String]) -> [String: String] { var headers: [String: String] = [:] for field in fields { guard let colon = field.firstIndex(of: ":") else { continue } let rawName = field[..<colon].trimmingCharacters(in: .whitespacesAndNewlines) let value = field[field.index(after: colon)...].trimmingCharacters(in: .whitespacesAndNewlines) guard !rawName.isEmpty, !value.isEmpty else { continue } guard let canonical = self.forwardedManualHeaders[rawName.lowercased()] else { continue } headers[canonical] = value } return headers } private static func cookieHeader(from fields: [String]) -> String? { for field in fields { guard let colon = field.firstIndex(of: ":") else { continue } let rawName = field[..<colon].trimmingCharacters(in: .whitespacesAndNewlines) guard rawName.caseInsensitiveCompare("Cookie") == .orderedSame else { continue } let value = field[field.index(after: colon)...].trimmingCharacters(in: .whitespacesAndNewlines) if let normalized = CookieHeaderNormalizer.normalize(String(value)) { return normalized } } return nil } private static func headerFields(from raw: String) -> [String] { var fields: [String] = [] let pattern = #"(?s)(?:^|\s)(?:-H|--header)(?:\s+|=|(?=['"$]))"# + #"(?:\$'((?:\\.|[^'])*)'|'([^']*)'|"((?:\\.|[^"])*)"|(\S+))"# guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return fields } let range = NSRange(raw.startIndex..<raw.endIndex, in: raw) for match in regex.matches(in: raw, options: [], range: range) { if let ansi = self.capture(1, in: match, raw: raw) { fields.append(self.unescapeShellSegment(ansi, ansi: true)) } else if let single = self.capture(2, in: match, raw: raw) { fields.append(single) } else if let double = self.capture(3, in: match, raw: raw) { fields.append(self.unescapeShellSegment(double, ansi: false)) } else if let bare = self.capture(4, in: match, raw: raw) { fields.append(self.unescapeShellSegment(bare, ansi: false)) } } return fields } private static func capture(_ index: Int, in match: NSTextCheckingResult, raw: String) -> String? { guard match.numberOfRanges > index, let range = Range(match.range(at: index), in: raw) else { return nil } return String(raw[range]) } private static func unescapeShellSegment(_ raw: String, ansi: Bool) -> String { var output = "" var index = raw.startIndex while index < raw.endIndex { guard raw[index] == "\\" else { output.append(raw[index]) index = raw.index(after: index) continue } let next = raw.index(after: index) guard next < raw.endIndex else { return output } switch raw[next] { case "n" where ansi: output.append("\n") case "r" where ansi: output.append("\r") case "t" where ansi: output.append("\t") case "\n": break default: output.append(raw[next]) } index = raw.index(after: next) } return output } private static func customerDataURL() throws -> URL { var components = URLComponents(string: "https://t3.chat/api/trpc/getCustomerData")! components.queryItems = [ URLQueryItem(name: "batch", value: "1"), URLQueryItem(name: "input", value: self.input), ] guard let url = components.url else { throw T3ChatUsageError.apiError("Failed to build customer data URL.") } return url }}
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if os(macOS)
import SweetCookieKit
#endif
#if os(macOS)
private let t3ChatCookieImportOrder: BrowserCookieImportOrder =
ProviderDefaults.metadata[.t3chat]?.browserCookieOrder ?? Browser.defaultImportOrder
public enum T3ChatCookieImporter {
private static let cookieClient = BrowserCookieClient()
private static let cookieDomains = ["t3.chat", "www.t3.chat"]
public struct SessionInfo: Sendable {
public let cookieHeader: String
public let sourceLabel: String
public init(cookieHeader: String, sourceLabel: String) {
self.cookieHeader = cookieHeader
self.sourceLabel = sourceLabel
}
}
public static func importSession(
browserDetection: BrowserDetection,
logger: ((String) -> Void)? = nil) throws -> SessionInfo
{
let log: (String) -> Void = { msg in logger?("[t3chat-cookie] \(msg)") }
let installed = t3ChatCookieImportOrder.cookieImportCandidates(using: browserDetection)
for browserSource in installed {
do {
let query = BrowserCookieQuery(domains: self.cookieDomains)
let sources = try self.cookieClient.codexBarRecords(
matching: query,
in: browserSource,
logger: log)
for source in sources where !source.records.isEmpty {
let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)
guard !cookies.isEmpty else { continue }
let names = cookies.map(\.name).joined(separator: ", ")
log("\(source.label) cookies: \(names)")
let header = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
return SessionInfo(cookieHeader: header, sourceLabel: source.label)
}
} catch {
BrowserCookieAccessGate.recordIfNeeded(error)
log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)")
}
}
throw T3ChatUsageError.noSessionCookie
}
}
#endif
public struct T3ChatUsageFetcher: Sendable {
private static let log = CodexBarLog.logger(LogCategories.t3chat)
private static let baseURL = URL(string: "https://t3.chat")!
private static let refererURL = URL(string: "https://t3.chat/settings/customization")!
/// Browser fingerprint defaults are only fallbacks; full cURL captures override these forwarded headers.
private static let userAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
/// Captured from T3 Chat's getCustomerData tRPC request shape in May 2026.
private static let input = #"{"0":{"json":{"sessionId":null},"meta":{"values":{"sessionId":["undefined"]}}}}"#
private static let forwardedManualHeaders = [
"accept": "Accept",
"accept-language": "Accept-Language",
"cache-control": "Cache-Control",
"pragma": "Pragma",
"priority": "Priority",
"referer": "Referer",
"sec-fetch-dest": "Sec-Fetch-Dest",
"sec-fetch-mode": "Sec-Fetch-Mode",
"sec-fetch-site": "Sec-Fetch-Site",
"trpc-accept": "trpc-accept",
"user-agent": "User-Agent",
"x-client-context": "x-client-context",
"x-deployment-id": "X-Deployment-Id",
"x-trpc-batch": "x-trpc-batch",
"x-trpc-source": "x-trpc-source",
]
public struct RequestContext: Sendable {
public let cookieHeader: String
public let headers: [String: String]
public init(cookieHeader: String, headers: [String: String] = [:]) {
self.cookieHeader = cookieHeader
self.headers = headers
}
}
public let browserDetection: BrowserDetection
public init(browserDetection: BrowserDetection) {
self.browserDetection = browserDetection
}
public func fetch(
cookieHeaderOverride: String? = nil,
timeout: TimeInterval = 15,
logger: ((String) -> Void)? = nil,
now: Date = Date(),
transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot
{
let log: (String) -> Void = { msg in logger?("[t3chat] \(msg)") }
let context = try await self.resolveRequestContext(override: cookieHeaderOverride, logger: log)
if let logger {
let names = CookieHeaderNormalizer.pairs(from: context.cookieHeader).map(\.name)
if !names.isEmpty {
logger("[t3chat] Cookie names: \(names.joined(separator: ", "))")
}
if !context.headers.isEmpty {
let headerNames = context.headers.keys.sorted().joined(separator: ", ")
logger("[t3chat] Forwarding captured headers: \(headerNames)")
}
}
return try await Self.fetchCustomerData(
context: context,
timeout: timeout,
now: now,
transport: transport)
}
public func debugRawProbe(cookieHeaderOverride: String? = nil) async -> String {
let stamp = ISO8601DateFormatter().string(from: Date())
var lines: [String] = []
lines.append("=== T3 Chat Debug Probe @ \(stamp) ===")
lines.append("")
do {
let snapshot = try await self.fetch(
cookieHeaderOverride: cookieHeaderOverride,
logger: { msg in lines.append(msg) })
lines.append("")
lines.append("Fetch Success")
lines.append("subTier=\(snapshot.customerData.subTier ?? "nil")")
lines.append("usageBand=\(snapshot.customerData.usageBand ?? "nil")")
lines
.append(
"usageFourHourPercentage=\(snapshot.customerData.usageFourHourPercentage?.description ?? "nil")")
lines.append("usageMonthPercentage=\(snapshot.customerData.usageMonthPercentage?.description ?? "nil")")
lines.append("usagePeriodPercentage=\(snapshot.customerData.usagePeriodPercentage?.description ?? "nil")")
lines
.append(
"usageFourHourNextResetAt=\(snapshot.customerData.usageFourHourNextResetAt?.description ?? "nil")")
lines.append("billingNextResetAt=\(snapshot.customerData.billingNextResetAt?.description ?? "nil")")
} catch {
lines.append("")
lines.append("Probe Failed: \(error.localizedDescription)")
}
return lines.joined(separator: "\n")
}
public static func fetchCustomerData(
cookieHeader: String,
timeout: TimeInterval = 15,
now: Date = Date(),
transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot
{
guard let normalizedCookieHeader = CookieHeaderNormalizer.normalize(cookieHeader) else {
throw T3ChatUsageError.noSessionCookie
}
return try await self.fetchCustomerData(
context: RequestContext(cookieHeader: normalizedCookieHeader),
timeout: timeout,
now: now,
transport: transport)
}
public static func fetchCustomerData(
context: RequestContext,
timeout: TimeInterval = 15,
now: Date = Date(),
transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot
{
guard let normalizedCookieHeader = CookieHeaderNormalizer.normalize(context.cookieHeader) else {
throw T3ChatUsageError.noSessionCookie
}
let url = try self.customerDataURL()
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.timeoutInterval = timeout
self.applyDefaultHeaders(to: &request)
for (name, value) in context.headers {
request.setValue(value, forHTTPHeaderField: name)
}
request.setValue(self.baseURL.absoluteString, forHTTPHeaderField: "Origin")
request.setValue(normalizedCookieHeader, forHTTPHeaderField: "Cookie")
let response = try await transport.response(for: request)
let data = response.data
guard response.statusCode == 200 else {
let body = String(data: data.prefix(200), encoding: .utf8) ?? "<binary>"
Self.log.error("T3 Chat API returned \(response.statusCode): \(body)")
if response.statusCode == 401 || response.statusCode == 403 {
throw T3ChatUsageError.invalidCredentials
}
if response.statusCode == 429,
response.response.value(forHTTPHeaderField: "x-vercel-mitigated") == "challenge"
{
throw T3ChatUsageError.vercelChallenge
}
throw T3ChatUsageError.apiError("HTTP \(response.statusCode)")
}
do {
return try T3ChatUsageParser.parseJSONLines(data, now: now)
} catch {
let preview = String(data: data.prefix(500), encoding: .utf8) ?? "<binary>"
Self.log.error("T3 Chat parse failed: \(error.localizedDescription) response=\(preview)")
throw error
}
}
private func resolveRequestContext(
override: String?,
logger: ((String) -> Void)?) async throws -> RequestContext
{
if let override = Self.requestContext(from: override) {
let source = override.headers.isEmpty ? "manual cookie header" : "manual cURL capture"
logger?("[t3chat] Using \(source)")
return override
}
#if os(macOS)
let session = try T3ChatCookieImporter.importSession(
browserDetection: self.browserDetection,
logger: logger)
logger?("[t3chat] Using cookies from \(session.sourceLabel)")
return RequestContext(cookieHeader: session.cookieHeader)
#else
throw T3ChatUsageError.noSessionCookie
#endif
}
static func requestContext(from raw: String?) -> RequestContext? {
guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil }
let headerFields = Self.headerFields(from: raw)
guard let cookieHeader = Self.cookieHeader(from: headerFields) ?? CookieHeaderNormalizer.normalize(raw) else {
return nil
}
let headers = Self.forwardedHeaders(from: headerFields)
return RequestContext(cookieHeader: cookieHeader, headers: headers)
}
private static func applyDefaultHeaders(to request: inout URLRequest) {
request.setValue("*/*", forHTTPHeaderField: "Accept")
request.setValue("application/jsonl", forHTTPHeaderField: "trpc-accept")
request.setValue("web-client", forHTTPHeaderField: "x-trpc-source")
request.setValue("true", forHTTPHeaderField: "x-trpc-batch")
request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "Accept-Language")
request.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
request.setValue(self.refererURL.absoluteString, forHTTPHeaderField: "Referer")
request.setValue("empty", forHTTPHeaderField: "Sec-Fetch-Dest")
request.setValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode")
request.setValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site")
request.setValue("u=4", forHTTPHeaderField: "Priority")
request.setValue("no-cache", forHTTPHeaderField: "Pragma")
request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
}
private static func forwardedHeaders(from fields: [String]) -> [String: String] {
var headers: [String: String] = [:]
for field in fields {
guard let colon = field.firstIndex(of: ":") else { continue }
let rawName = field[..<colon].trimmingCharacters(in: .whitespacesAndNewlines)
let value = field[field.index(after: colon)...].trimmingCharacters(in: .whitespacesAndNewlines)
guard !rawName.isEmpty, !value.isEmpty else { continue }
guard let canonical = self.forwardedManualHeaders[rawName.lowercased()] else { continue }
headers[canonical] = value
}
return headers
}
private static func cookieHeader(from fields: [String]) -> String? {
for field in fields {
guard let colon = field.firstIndex(of: ":") else { continue }
let rawName = field[..<colon].trimmingCharacters(in: .whitespacesAndNewlines)
guard rawName.caseInsensitiveCompare("Cookie") == .orderedSame else { continue }
let value = field[field.index(after: colon)...].trimmingCharacters(in: .whitespacesAndNewlines)
if let normalized = CookieHeaderNormalizer.normalize(String(value)) {
return normalized
}
}
return nil
}
private static func headerFields(from raw: String) -> [String] {
var fields: [String] = []
let pattern =
#"(?s)(?:^|\s)(?:-H|--header)(?:\s+|=|(?=['"$]))"# +
#"(?:\$'((?:\\.|[^'])*)'|'([^']*)'|"((?:\\.|[^"])*)"|(\S+))"#
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return fields }
let range = NSRange(raw.startIndex..<raw.endIndex, in: raw)
for match in regex.matches(in: raw, options: [], range: range) {
if let ansi = self.capture(1, in: match, raw: raw) {
fields.append(self.unescapeShellSegment(ansi, ansi: true))
} else if let single = self.capture(2, in: match, raw: raw) {
fields.append(single)
} else if let double = self.capture(3, in: match, raw: raw) {
fields.append(self.unescapeShellSegment(double, ansi: false))
} else if let bare = self.capture(4, in: match, raw: raw) {
fields.append(self.unescapeShellSegment(bare, ansi: false))
}
}
return fields
}
private static func capture(_ index: Int, in match: NSTextCheckingResult, raw: String) -> String? {
guard match.numberOfRanges > index,
let range = Range(match.range(at: index), in: raw)
else {
return nil
}
return String(raw[range])
}
private static func unescapeShellSegment(_ raw: String, ansi: Bool) -> String {
var output = ""
var index = raw.startIndex
while index < raw.endIndex {
guard raw[index] == "\\" else {
output.append(raw[index])
index = raw.index(after: index)
continue
}
let next = raw.index(after: index)
guard next < raw.endIndex else { return output }
switch raw[next] {
case "n" where ansi:
output.append("\n")
case "r" where ansi:
output.append("\r")
case "t" where ansi:
output.append("\t")
case "\n":
break
default:
output.append(raw[next])
}
index = raw.index(after: next)
}
return output
}
private static func customerDataURL() throws -> URL {
var components = URLComponents(string: "https://t3.chat/api/trpc/getCustomerData")!
components.queryItems = [
URLQueryItem(name: "batch", value: "1"),
URLQueryItem(name: "input", value: self.input),
]
guard let url = components.url else {
throw T3ChatUsageError.apiError("Failed to build customer data URL.")
}
return url
}
}