合并自建歌曲源支线(selfhost-source)+ 版本号按约定修正为 1.6.0

版本号:
- 1.5.11 越界(规则 3 规定 patch ≤ 10)→ 修正为 1.6.0(进位到 minor、patch 归 0)
- versionCode 43 不变:43 从未对外发布,本次修正只是把命名拉回约定

合并支线(原在工作区根目录、App 仓库之外,DESIGN.md 自称"支线"):
- selfhost-source/server/   自建歌曲源服务器(纯 Python 标准库、零 pip 依赖)
- selfhost-source/deploy/、DESIGN.md、LIBRARY.md、build_inventory.py
- selfhost-source/inventory/ 小清单(playlists.csv、song-stats.md 等)
- ⚠️ inventory 生成物(songs-*.csv / singers-top.csv,共 84MB)不入库,
  已写入 .gitignore;需要时用 build_inventory.py 依据 muse.db 重新生成

同时入库的 App 侧支线改动(此前为工作树未提交状态):
- Whoami.kt(whoami 设备心跳)、DeviceIdentity.kt(设备身份伪装)
- PushDownload.kt / PushDownloadClient.kt(点歌推送)
- AdVideoFilter 带 query/fragment 的地址闸门;KTV_BRIDGE_API 11 → 13
- 修 ktv_api.js 的 global.KtvBridgeApiVersion(12 → 13,与注释及 Kotlin 两处校验一致)
- 对应单测:WhoamiTest / DeviceIdentityTest / PushDownloadTest / AdVideoFilterTest(共 39 例)

文档:
- DESIGN.md 的"在 App 仓库之外"约定改为"已并入仓库统一版本化"
- AGENTS.md 版本号行更新为 1.6.0/43,并新增支线并入说明

验证:assembleRelease + testReleaseUnitTest 全过(39 例)
This commit is contained in:
zijunchiang
2026-10-14 02:06:28 +08:00
parent e060ae5ebe
commit 87aa979abd
68 changed files with 21305 additions and 105 deletions
+30 -9
View File
@@ -12,7 +12,7 @@
(function(global) {
'use strict';
// KTV_BRIDGE_API: 12 (Android 缓存校验标记,请勿删除)
// KTV_BRIDGE_API: 13 (Android 缓存校验标记,请勿删除)
// ─── 配置 ──────────────────────────────────────────
const CONFIG = {
@@ -125,28 +125,49 @@
return JSON.parse(body.slice(start));
}
// 把 URL 拆成 host / path(**剥离 query 与 fragment**)。
// 所有"看路径/看域名"的判定都基于它:真源地址带 `?token=…&t=…`(2026-10-11 抓包),
// 拿整串判定就会出现"带 query 匹配不到文件名"(漏放)或"query 里的随机串被当广告特征"(误杀)。
function splitUrl(url) {
var s = String(url || '');
var head = s.split('#')[0].split('?')[0];
var m = head.match(/^[a-zA-Z][a-zA-Z0-9+.\-]*:\/\/([^\/]*)(\/.*)?$/);
if (!m) return { host: '', path: head };
var host = m[1].split('@').pop();
if (host.charAt(0) === '[') {
host = host.indexOf(']') > 0 ? host.slice(0, host.indexOf(']')) : host;
} else {
host = host.replace(/:\d+$/, '');
}
return { host: host.toLowerCase(), path: m[2] || '' };
}
// 匹配 demo/广告/测试资源 URL 的多种模式:
// 1) wb66 / demo 关键字 (服务端注入广告示例: https://wb66.cn/f/d/mJMSQ/719.ts)
// 2) IPv4 地址 (如 http://154.222.26.244:656/722.ts)
// 3) Cloudflare R2 demo bucket (pub-*.r2.dev/ss/ 通用测试文件)
// 4) 任务4: ts 文件名编号与请求的 musicno 不一致 → 视为广告/错误资源
// (例: 请求 7018878 却返回 /719.ts 或 /689.ts)
// ⚠️ 判定一律在 **host + path** 上做(见 splitUrl),**不搜 query**。
function isDemoUrl(url, musicno) {
var s = String(url || '');
if (/(?:wb66|demo)/i.test(s)) return true;
var u = splitUrl(s);
var hostPath = u.host + u.path;
if (/(?:wb66|demo)/i.test(hostPath)) return true;
// 广告视频专用域名(含子域)。2026-09-26 事故:新设备(WebView 正常 → 走本 JS 取址)
// 点歌拿到了 gz.ac16.vip 的广告视频,而它通过了下述所有旧规则 → 被当成歌曲下载播放。
if (/^https?:\/\/(?:[^\/]+\.)?ac16\.vip(?::\d+)?\//i.test(s)) return true;
if (/\/ad_files\/|my_ad_video/i.test(s)) return true;
if (/^https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?\//i.test(s)) return true;
if (/(?:^|\.)ac16\.vip$/i.test(u.host)) return true;
if (/\/ad_files\/|my_ad_video/i.test(u.path)) return true;
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(u.host)) return true;
// R2 demo bucket: pub-*.r2.dev/ss/ 通用测试资源
if (/pub-[a-z0-9]+\.r2\.dev\/ss\//i.test(s)) return true;
if (/^pub-[a-z0-9]+\.r2\.dev$/i.test(u.host) && /\/ss\//i.test(u.path)) return true;
// ts 文件名必须等于 musicno。原版真源 URL 恒为 /<musicno>.ts(已实测两个 CDN 域
// txog.ktvsky.com / download.origjoy.com 的 6 首歌全部如此)。
// txog.ktvsky.com / download.origjoy.com 的 6 首歌全部如此),且**带 query/fragment**。
// ⚠️ 旧规则只比对"纯数字文件名"(/\/(\d+)\.ts/),像 my_ad_video.ts 这种非数字名字
// 会直接漏过 —— 这正是广告漏放的直接原因之一。
// ⚠️ 旧正则收尾写的是 `(?:\?|$)`:遇到 `#fragment` 会**完全匹配不到 → 整条规则被跳过**。
if (musicno) {
var m = s.match(/\/([^\/?]+)\.ts(?:\?|$)/i);
var m = u.path.match(/\/([^\/]+)\.ts(?:\/|$)/i);
if (m && m[1] !== String(musicno)) return true;
}
return false;
@@ -437,7 +458,7 @@
// ─── 导出 ──────────────────────────────────────────
global.KtvApi = KtvApi;
global.KtvApiConfig = CONFIG;
global.KtvBridgeApiVersion = 12;
global.KtvBridgeApiVersion = 13;
if (typeof module !== 'undefined' && module.exports) {
module.exports = {KtvApi: KtvApi, CONFIG: CONFIG};