From e060ae5ebe7f20a40ef29ce00028e9282330c5b0 Mon Sep 17 00:00:00 2001 From: zijunchiang Date: Fri, 9 Oct 2026 21:42:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BA=90=E5=88=97=E8=A1=A8=E5=8F=AA=E4=BF=9D?= =?UTF-8?q?=E7=95=99=20zk.ktv1.top:6677,=20=E5=88=A0=E9=99=A4=E5=85=B6?= =?UTF-8?q?=E4=BD=99=E5=85=A8=E9=83=A8=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测(同一出口 IP, 编号 7009126, 三节点同时刻对照): zk.ktv1.top:6677 → i.php 200(与 e.ac19.cn 逐字段一致) + do.php 真源 7009126.ts / 7303235.ts ✅ e.ac19.cn → 同款 i.php + 真源 7009126.ts ✅ 124.223.20.123:58→ 同款 i.php, 但 do.php 返回 0021.ts(非本曲) ❌ 三者同一后台; 昨日 e.ac19.cn/124.223.20.123:58 的 HTTP 403 空响应为临时额度/风控, 今日已恢复 改动: - SongUrlResolver.DEFAULT_HOSTS 与 ktv_api.js CONFIG.HOSTS(含 HOST) 均只留 http://zk.ktv1.top:6677 - KTV_BRIDGE_API 11 → 12 (ktv_api.js 两处 + KtvJsBridge.kt 两处校验) - ensureConfig(): api.php 失败也写负缓存(该节点无 api.php, 否则每首歌白请求一次) - MainActivity 日志不再写死旧源名, 改用 SongUrlResolver.lastHost - AdVideoFilter.AD_HOSTS 不变(管"返回地址"的 host, 与"取址入口"无关) 版本号按规则 3: 1.5.9(41) → 1.5.10(42); 本地打包(未发布) 验证: assembleRelease + 单元测试(9 例全过); 解包确认 APK 内 ktv_api.js 只含新源且标记为 12 --- AGENTS.md | 17 +++++++++++-- app/build.gradle | 4 +-- app/src/main/assets/mobile/ktv_api.js | 24 ++++++------------ .../main/java/com/local/ktv/KtvJsBridge.kt | 4 +-- .../main/java/com/local/ktv/MainActivity.kt | 3 ++- .../java/com/local/ktv/SongUrlResolver.kt | 25 ++++++++++--------- 6 files changed, 41 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cd57c9f..f0197b0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -195,8 +195,8 @@ $BT/apksigner verify 目标名.apk ### 版本号 -- `app/build.gradle`:`versionName "1.5.9"` / `versionCode 41` - (`1.5.5`/`37` 已发布;`1.5.6` = 更新源加 Gitee、`1.5.7` = 删除「歌曲源临时受限」弹窗、`1.5.8` = 精简远程日志、`1.5.9` = 按 HAR 对齐原版协议,均尚未发布。18~21 曾被本机临时诊断包占用) +- `app/build.gradle`:`versionName "1.5.10"` / `versionCode 42` + (`1.5.5`/`37` 已发布;`1.5.6` = 更新源加 Gitee、`1.5.7` = 删除「歌曲源临时受限」弹窗、`1.5.8` = 精简远程日志、`1.5.9` = 按 HAR 对齐原版协议、`1.5.10` = 源列表只留 zk.ktv1.top:6677,均尚未发布。18~21 曾被本机临时诊断包占用) - App 内显示统一用纯版本号(不加 `(versionCode)` 括号) - ⚠️ **发布构建已开启 NewApi 闸门**(`lint { checkReleaseBuilds true; fatal += ['NewApi'] }`): 任何「调用了高于 `minSdk 21` 且未做版本守卫的 API」都会让 `assembleRelease` 直接失败。 @@ -309,6 +309,19 @@ $BT/apksigner verify 目标名.apk ⚠️ 其 `do.php` 取址行为**尚未定性**(测试时本机出口 IP 配额已被消耗殆尽), 因此置于次位、由运行时自动升降序。 - 该条改动把 `KTV_BRIDGE_API` 由 10 提升到 **11**(同样四处)。 +- **⚠️ 源列表只保留 `zk.ktv1.top:6677`(2026-10-09,按用户要求)**: + 实测该入口与 `e.ac19.cn` **同一个后台** —— `i.php` 响应逐字段一致 + (`{"code":200,"end_date":"2026-10-05 14:58:21",...,"retry":5}`)、`do.php` 同款 + `code=200/msg=SUCCESS` + 同一条真源 CDN 路径(`txog.ktvsky.com/.../<编号>.ts`), + 且能连续两首返回**文件名=编号**的真源;其余入口要么只发广告/测试资源、要么整体 403。 + - 两处同步:`ktv_api.js` 的 `CONFIG.HOSTS`(含 `HOST` 默认值)与 `SongUrlResolver.DEFAULT_HOSTS`, + **都只留 `http://zk.ktv1.top:6677` 一个**;`KTV_BRIDGE_API` 由 11 → **12**(同样四处)。 + - ⚠️ 该源**没有 `api.php`**(404):`ensureConfig()` 现在**失败也写负缓存**(`configAt`), + 否则每首歌取址前都会白请求一次。 + - ⚠️ 它返回的是**裸 IP:端口**形式(`zk.ktv1.top` 在开发机解析到网关 fake-IP `198.18.18.178`), + 裸 socket 会正确使用 `uri.port`(6677)—— 以前只有域名源,改这块要注意别丢端口。 + - `AdVideoFilter.AD_HOSTS`(媒体层广告域名黑名单)**不变**:它管的是"返回地址"的 host, + 与"取址入口"无关(见上文判定原则)。 - **⚠️ 原版协议对齐(2026-10-08,依据 HAR 抓包实测)**:抓包(Reqable,`e.ac19.cn`)显示原版客户端是**三步**: 1. **`POST /api.php`**:`Content-Type: application/x-www-form-urlencoded`, body `action=get_config&device_id=`,UA diff --git a/app/build.gradle b/app/build.gradle index 539ec4c..6e693a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.local.ktv" minSdk 21 targetSdk 25 - versionCode 41 - versionName "1.5.9" + versionCode 42 + versionName "1.5.10" ndk { // 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版 // 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则 diff --git a/app/src/main/assets/mobile/ktv_api.js b/app/src/main/assets/mobile/ktv_api.js index 0a66868..de7647b 100644 --- a/app/src/main/assets/mobile/ktv_api.js +++ b/app/src/main/assets/mobile/ktv_api.js @@ -12,7 +12,7 @@ (function(global) { 'use strict'; - // KTV_BRIDGE_API: 11 (Android 缓存校验标记,请勿删除) + // KTV_BRIDGE_API: 12 (Android 缓存校验标记,请勿删除) // ─── 配置 ────────────────────────────────────────── const CONFIG = { @@ -21,23 +21,13 @@ SDK_KEY: '19042303a8374f67ae3fe1e25c97936f', VN: '4.100.5.11121504', VER: '4', - // 歌曲源(均为同一后台的多个入口; 运行时按可用性自动排序, 解析失败的源沉底)。 + // 歌曲源入口(2026-10-09 起**只保留这一个**)。 + // zk.ktv1.top:6677 实测与 e.ac19.cn 同后台(i.php 逐字段一致、do.php 同款 SUCCESS + 真源 CDN), + // 连续两首均返回"文件名=编号"的真源;其余入口只发广告或整体 403,已按用户要求删除。 HOSTS: [ - // 2026-10 实测: 其余入口对每首歌统一返回广告/测试资源时,本入口仍稳定下发真源 CDN 地址, - // 故置于首位;它自己域名下的媒体始终是广告,由 AdVideoFilter 继续拦截。 - 'http://gz.ac16.vip', - // 与 w345.my 同一台服务器(154.219.102.181)的兄弟入口,登录层正常; - // 取址行为尚未定性 → 置于次位,由运行时自动升降序。 - 'http://a.ktv8.my', - 'http://w.w345.my', - 'http://desktok.top', - 'http://mm.kk456.top', - 'http://w.w345.my:66', - 'http://z.w345.my', - 'http://b.sktv.top', - 'http://w345.my', + 'http://zk.ktv1.top:6677', ], - HOST: 'http://w.w345.my', + HOST: 'http://zk.ktv1.top:6677', MWS: 'https://mws.cherryonline.cn', RSA_PUBKEY: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAucL0oFErd7REM6TSNa3EZdN1YaOs4J1eCybLPyoQ9ru3q1HU67agC9FzhrCG/RvAQUya5iPmQ8Caed05vqcCVcyJChmkSOGQ7DVShe2rGuTMNlpoRV6UzfcraaVS++7m2K/+kSZJ8OAhhhVuqPruMjsFYpdtstAwvyZT28b+eENwzpp9UHqsooZc7FZ0H8kTbs6XMkw4nIWo+4HoPAhNLEY+xdHvwY6drF/3WDTvsaoMrs73TVQCEEHzZNIz2H/is9VLMnIyOfnfcJi9br78Fj2xHzxu3sAySBOTVLmUMxqYh/g1ox5OXGcW93HJkQLkBi42tFAEkWYlYyl93+jbbQIDAQAB' }; @@ -447,7 +437,7 @@ // ─── 导出 ────────────────────────────────────────── global.KtvApi = KtvApi; global.KtvApiConfig = CONFIG; - global.KtvBridgeApiVersion = 11; + global.KtvBridgeApiVersion = 12; if (typeof module !== 'undefined' && module.exports) { module.exports = {KtvApi: KtvApi, CONFIG: CONFIG}; diff --git a/app/src/main/java/com/local/ktv/KtvJsBridge.kt b/app/src/main/java/com/local/ktv/KtvJsBridge.kt index cd6b710..ee02284 100644 --- a/app/src/main/java/com/local/ktv/KtvJsBridge.kt +++ b/app/src/main/java/com/local/ktv/KtvJsBridge.kt @@ -358,7 +358,7 @@ class KtvJsBridge(context: Context) { runCatching { file.isFile && file.length() in 1_001..500_000 && file.readText(Charsets.UTF_8).let { - it.contains("KtvApi") && it.contains("KTV_BRIDGE_API: 11") + it.contains("KtvApi") && it.contains("KTV_BRIDGE_API: 12") } }.getOrDefault(false) @@ -384,7 +384,7 @@ class KtvJsBridge(context: Context) { val script = String(bytes, StandardCharsets.UTF_8) require( script.length > 1000 && script.contains("KtvApi") && - script.contains("KTV_BRIDGE_API: 11"), + script.contains("KTV_BRIDGE_API: 12"), ) { "Invalid or incompatible JS file" } tempFile.writeText(script, Charsets.UTF_8) if (!tempFile.renameTo(cacheFile)) { diff --git a/app/src/main/java/com/local/ktv/MainActivity.kt b/app/src/main/java/com/local/ktv/MainActivity.kt index eaf81e7..6f86dfe 100644 --- a/app/src/main/java/com/local/ktv/MainActivity.kt +++ b/app/src/main/java/com/local/ktv/MainActivity.kt @@ -5163,7 +5163,8 @@ class MainActivity : AppCompatActivity() { } if (url == null || url.isEmpty()) { MiliLog.error( - "点歌编号=${song?.let(::extractTid)} 标题=${song?.title} 请求源=w.w345.my 状态=失败 详情=无法获取播放地址", + "点歌编号=${song?.let(::extractTid)} 标题=${song?.title} 请求源=" + + "${SongUrlResolver.lastHost.ifEmpty { "unknown" }} 状态=失败 详情=无法获取播放地址", ) main.post(Runnable { hideDownloadProgress() diff --git a/app/src/main/java/com/local/ktv/SongUrlResolver.kt b/app/src/main/java/com/local/ktv/SongUrlResolver.kt index 1ea0fe5..ba72c5e 100644 --- a/app/src/main/java/com/local/ktv/SongUrlResolver.kt +++ b/app/src/main/java/com/local/ktv/SongUrlResolver.kt @@ -49,18 +49,16 @@ object SongUrlResolver { /** `api.php` 配置缓存时长(原版每次启动/长时间后取一次即可)。 */ private const val CONFIG_TTL_MS = 30 * 60_000L + /** + * 歌曲源入口(**2026-10-09 起只保留这一个**)。 + * + * `zk.ktv1.top:6677` 实测与 `e.ac19.cn` **同一个后台**(`i.php` 响应逐字段一致、 + * `do.php` 同款 SUCCESS + 同一条真源 CDN 路径),且能连续下发**文件名=编号**的真源地址; + * 而其它入口要么只发广告/测试资源,要么整体 403。故按用户要求**清空其余入口**, + * 只留它一个(少扫源同时也避免了把同后台节点的额度一起打光)。 + */ private val DEFAULT_HOSTS = listOf( - // 与其他入口同一后台、同一协议;2026-10 实测唯一仍下发真源 CDN 地址的入口,放首位。 - "http://gz.ac16.vip", - // 与 w345.my 同机(154.219.102.181)的兄弟入口;实测登录层正常,取址行为待观察。 - "http://a.ktv8.my", - "http://w.w345.my", - "http://desktok.top", - "http://mm.kk456.top", - "http://w.w345.my:66", - "http://z.w345.my", - "http://b.sktv.top", - "http://w345.my", + "http://zk.ktv1.top:6677", ) /** 运行时源优先级(与 JS 一样:解析失败的源沉到队尾)。 */ @@ -152,9 +150,12 @@ object SongUrlResolver { val form = "action=get_config&device_id=$mac" val raw = runCatching { channel("$host/api.php", form) }.getOrNull() ?: return val envelope = runCatching { JSONObject(raw) }.getOrNull() ?: return + // ⚠️ 失败也要进负缓存:本节点(zk.ktv1.top:6677)**没有 api.php**(404), + // 不缓存的话每首歌取址前都要白请求一次,既拖慢又白耗额度。 + configAt = System.currentTimeMillis() val status = envelope.optInt("status") if (status !in 200..299) { - MiliLog.localWarn("SOURCE-CONFIG api.php HTTP $status") + MiliLog.localWarn("SOURCE-CONFIG api.php HTTP $status(本节点无配置接口,按全部源尝试)") return } val body = runCatching { JSONObject(envelope.optString("body")) }.getOrNull() ?: return