mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2026-09-10 12:00:58 +00:00
fix(translate): align oneshot client with DeepL iOS URLSession profile
Replace the fabricated iOS 27.0 UA/os_version (which DeepL rate-limits and can temp-ban) with a coherent iOS 26 profile matching the 26.42 IPA: CFNetwork User-Agent, app_information, and ClientInfos x-app-* headers over HelloIOS TLS.
This commit is contained in:
parent
5cd1667f7f
commit
66090baa7e
@ -42,28 +42,52 @@ import (
|
|||||||
// traffic hard; oneshot lives on a separate pool and accepts the literal
|
// traffic hard; oneshot lives on a separate pool and accepts the literal
|
||||||
// header `Authorization: None` for free requests.
|
// header `Authorization: None` for free requests.
|
||||||
//
|
//
|
||||||
// Request shape below is reverse-engineered from DeepL iOS 26.42
|
// Request shape reverse-engineered from DeepL iOS 26.42 (build 5443737,
|
||||||
// (build 5443737, bundle com.linguee.DeepLMobileTranslator):
|
// bundle com.linguee.DeepLMobileTranslator, IPA Info.plist + ItaClient.framework):
|
||||||
// - Free URL → https://oneshot-free.www.deepl.com/v1/translate
|
//
|
||||||
// - Pro URL → https://oneshot-pro.www.deepl.com/v1/translate
|
// Transport
|
||||||
// (iOS also constructs https://oneshot. + .pro.deepl.com/v1/translate)
|
// ItaClient oneshot uses Ktor Darwin engine
|
||||||
// - Body → OneShotTranslator + ItaClient.AppInformation
|
// (io.ktor.client.engine.darwin.KtorNSURLSessionDelegate) → real
|
||||||
// - Headers → ClientInfos.appHeaders (x-app-*) + Authorization
|
// URLSession TLS. We approximate that ClientHello with utls HelloIOS.
|
||||||
// - usage_type → ItaClient.OneShotUsageType.translate
|
//
|
||||||
|
// Free URL → https://oneshot-free.www.deepl.com/v1/translate
|
||||||
|
// Pro URL → https://oneshot. + <cell> + .pro.deepl.com/v1/translate
|
||||||
|
// (we keep oneshot-pro.www as the free-tier Pro fallback)
|
||||||
|
//
|
||||||
|
// Headers (ClientInfos.appHeaders + LoginNone):
|
||||||
|
// Authorization: None (ItaClient.LoginNone)
|
||||||
|
// x-app-os-version (UIDevice.systemVersion)
|
||||||
|
// x-app-instance-id (stable install UUID)
|
||||||
|
// x-app-session-id (session UUID)
|
||||||
|
// User-Agent (URLSession / CFNetwork product form)
|
||||||
|
// Accept / Accept-Encoding (URLSession defaults)
|
||||||
|
//
|
||||||
|
// Body (ItaClient OneShotTranslationRequestDto + AppInformation):
|
||||||
|
// text[], target_lang, source_lang?, usage_type, app_information
|
||||||
|
// usage_type ∈ {translate, ocr, voiceforconversations}
|
||||||
|
//
|
||||||
|
// DeepL rate-limits / temporarily bans clients whose TLS + UA + app_information
|
||||||
|
// story is inconsistent (e.g. iOS TLS fingerprint + "iOS 27.0" in the UA when
|
||||||
|
// 27 is not a shipping OS). Keep every field on one coherent iOS profile.
|
||||||
const (
|
const (
|
||||||
oneshotFreeEndpoint = "https://oneshot-free.www.deepl.com/v1/translate"
|
oneshotFreeEndpoint = "https://oneshot-free.www.deepl.com/v1/translate"
|
||||||
oneshotProEndpoint = "https://oneshot-pro.www.deepl.com/v1/translate"
|
oneshotProEndpoint = "https://oneshot-pro.www.deepl.com/v1/translate"
|
||||||
|
|
||||||
// Pinned to DeepL iOS IPA (Info.plist CFBundleShortVersionString /
|
// Pinned to DeepL iOS IPA (CFBundleShortVersionString / CFBundleVersion).
|
||||||
// CFBundleVersion). Keep app_information in lockstep with the TLS
|
|
||||||
// fingerprint and User-Agent so the request tells one consistent story.
|
|
||||||
iosAppVersion = "26.42"
|
iosAppVersion = "26.42"
|
||||||
iosAppBuild = "5443737"
|
iosAppBuild = "5443737"
|
||||||
iosBundleID = "com.linguee.DeepLMobileTranslator"
|
|
||||||
// Stable OS version reported in app_information + x-app-os-version.
|
// Reported OS version for app_information.os_version + x-app-os-version.
|
||||||
// HelloIOS_Auto does not pin a specific iOS minor; pin a fixed value
|
// IPA is built against iphoneos26.5 (DTPlatformVersion). Must be a real
|
||||||
// so UA / x-app-os-version / app_information stay consistent.
|
// shipping major — a future value (e.g. 27.0) combined with an iOS TLS
|
||||||
iosOSVersion = "27.0"
|
// fingerprint is rejected with HTTP 429 and can temp-ban the IP.
|
||||||
|
iosOSVersion = "26.0"
|
||||||
|
|
||||||
|
// CFNetwork / Darwin versions that accompany URLSession User-Agents on
|
||||||
|
// the same OS generation as DTPlatformVersion 26.x (build machine
|
||||||
|
// BuildMachineOSBuild 25E246 → Darwin 25).
|
||||||
|
iosCFNetworkVersion = "3826.600.41"
|
||||||
|
iosDarwinVersion = "25.0.0"
|
||||||
|
|
||||||
// oneshot enforces a 1500-character hard cap on the total length of
|
// oneshot enforces a 1500-character hard cap on the total length of
|
||||||
// the `text` array for anonymous traffic (same limit the Chrome
|
// the `text` array for anonymous traffic (same limit the Chrome
|
||||||
@ -277,16 +301,21 @@ func getOneshotClient(proxyURL string) (*req.Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newOneshotClient(proxyURL string) (*req.Client, error) {
|
func newOneshotClient(proxyURL string) (*req.Client, error) {
|
||||||
// iOS TLS ClientHello via utls HelloIOS_Auto. Headers are set per
|
// Match ItaClient's Ktor Darwin / URLSession profile:
|
||||||
// request in callOneshot to match ClientInfos.appHeaders rather than
|
// - TLS ClientHello ≈ real iOS (utls HelloIOS_Auto)
|
||||||
// a browser navigation profile.
|
// - Cookie jar shared like URLSession's HTTPCookieStorage
|
||||||
|
// - Common headers = what URLSession attaches by default
|
||||||
|
// Per-request headers (Authorization, x-app-*) are set in callOneshot.
|
||||||
client := req.C().
|
client := req.C().
|
||||||
SetTLSFingerprintIOS().
|
SetTLSFingerprintIOS().
|
||||||
SetCookieJar(sharedCookieJar()).
|
SetCookieJar(sharedCookieJar()).
|
||||||
SetTimeout(oneshotTimeout).
|
SetTimeout(oneshotTimeout).
|
||||||
SetUserAgent(iosUserAgent()).
|
SetUserAgent(iosUserAgent()).
|
||||||
|
// URLSession default Accept-Encoding for data tasks.
|
||||||
SetCommonHeader("Accept-Encoding", "gzip, deflate, br").
|
SetCommonHeader("Accept-Encoding", "gzip, deflate, br").
|
||||||
SetCommonHeader("Accept", "*/*").
|
SetCommonHeader("Accept", "*/*").
|
||||||
|
// Preferred language list — mirrors a US-locale device; DeepL
|
||||||
|
// does not hard-require a specific value for free oneshot.
|
||||||
SetCommonHeader("Accept-Language", "en-US,en;q=0.9")
|
SetCommonHeader("Accept-Language", "en-US,en;q=0.9")
|
||||||
|
|
||||||
if proxyURL != "" {
|
if proxyURL != "" {
|
||||||
@ -299,12 +328,14 @@ func newOneshotClient(proxyURL string) (*req.Client, error) {
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// iosUserAgent approximates the CFNetwork-style UA the DeepL iOS app
|
// iosUserAgent is the URLSession product-style User-Agent the app's Ktor
|
||||||
// advertises via ClientInfos.userAgent.
|
// Darwin stack emits (CFBundleName/CFBundleShortVersionString + CFNetwork
|
||||||
|
// + Darwin). Do NOT invent alternate formats (e.g. embedding the bundle
|
||||||
|
// ID) — mismatched UA + iOS TLS fingerprint is a cheap ban signal.
|
||||||
func iosUserAgent() string {
|
func iosUserAgent() string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"DeepL/%s (%s; build:%s; iOS %s)",
|
"DeepL/%s CFNetwork/%s Darwin/%s",
|
||||||
iosAppVersion, iosBundleID, iosAppBuild, iosOSVersion,
|
iosAppVersion, iosCFNetworkVersion, iosDarwinVersion,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +349,7 @@ func callOneshot(endpoint string, body []byte, bearerToken, proxyURL string) (gj
|
|||||||
return gjson.Result{}, 0, err
|
return gjson.Result{}, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoginNone → literal "None"; LoginPro/Free → "Bearer <access_token>".
|
||||||
authValue := "None"
|
authValue := "None"
|
||||||
if bearerToken != "" {
|
if bearerToken != "" {
|
||||||
authValue = "Bearer " + bearerToken
|
authValue = "Bearer " + bearerToken
|
||||||
@ -327,7 +359,8 @@ func callOneshot(endpoint string, body []byte, bearerToken, proxyURL string) (gj
|
|||||||
DisableAutoReadResponse().
|
DisableAutoReadResponse().
|
||||||
SetHeader("Content-Type", "application/json").
|
SetHeader("Content-Type", "application/json").
|
||||||
SetHeader("Authorization", authValue).
|
SetHeader("Authorization", authValue).
|
||||||
// ClientInfos.appHeaders from DeepL iOS (Util/ClientInfos.swift).
|
// ClientInfos.appHeaders (Util/ClientInfos.swift) — only these three
|
||||||
|
// x-app-* keys exist in the iOS binary.
|
||||||
SetHeader("x-app-os-version", iosOSVersion).
|
SetHeader("x-app-os-version", iosOSVersion).
|
||||||
SetHeader("x-app-instance-id", instanceID).
|
SetHeader("x-app-instance-id", instanceID).
|
||||||
SetHeader("x-app-session-id", sessionID).
|
SetHeader("x-app-session-id", sessionID).
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user