Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# JAVGG 1.0.3 列表加载修复 — 2026-10-04

- 针对 iPhone DreamBy 首页和分类 `stage=http-timeout`:列表普通 HTTP 使用 6 秒截止时间,包含异步正文;失败、验证页或不完整列表时,最多一次 12 秒网页 HTML 回退。浏览器只读取网页,不等待视频媒体,正常列表继续直接用 HTTP。
- 兼容宿主嵌套对象/JSON、异步 `text()` 和 Promise 正文。普通 Cloudflare jsd 脚本不误作验证页;真实验证、404/410、缺失内容和超时保留明确阶段。
- 首页/分区/分类/搜索失败返回 `error` 和空条目,撤回站点 Logo 的假内容失败卡。真实热门分区的影片补充排名;翻页地址及稳定详情动作保持。
- 原路径、库 ID 和 `baseUrl` 保留,播放/详情解析的 19 个函数逐字保持恢复版。此次只更新 JAVGG;其他九个已恢复下载入口继续与任务前快照相同。恢复记录将 JAVGG 原快照移入 `supersededFiles`,注明用户要求的新版本。
- 67 项媒体库回归、60 个脚本语法、39 个库契约、兼容副本、既有 SexBJCam 网络测试及发布包装检查通过。桌面真实八类两页及最近发布匿名请求验证;热门普通请求仍可能超时。iPhone HTML 回传与显示待用户更新测试。

# 更正恢复时间:任务开始前的线上版本 — 2026-10-04

- 用户更正为昨天(2026-10-03)开始本轮任务之前的版本。采用上海时间 2026-10-03 零点之前主分支最后快照 `2fd0121bfbf0cc812d1e9e39501384710e684ade`;十个目标文件与本轮最初审查基线 `8e43017532ec81e3338046ba6e32dff7655bd3dd` 相同。
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

已经导入 DreamBy 的入口继续使用原来的下载地址和媒体库 ID。每个下载入口都包含完整 JavaScript,不需要远程加载器。

## JAVGG 1.0.3:首页与分类加载修复 — 2026-10-04

用户在 iPhone DreamBy 中报告首页及各分类显示站点 Logo 的失败卡,错误为 `stage=http-timeout`。JAVGG 原版列表请求禁用浏览器回退,HTTP 失败后直接返回错误卡。此次仅修列表读取和失败显示:普通 HTTP 最多 6 秒,包含正文读取;失败或收到验证/不完整页面时,最多一次 12 秒隐藏网页 HTML 回退,不等待视频媒体。正常 HTTP 列表不打开浏览器,可使用宿主提供的正常浏览器会话;不提取或保存 Cookie。

首页仍只立即加载最近发布,其他分区保持懒加载。兼容原始 HTML、嵌套对象/JSON 正文及异步 `text()`。失败返回明确错误和空条目,避免把站点 Logo 的失败提示作为影片或排名内容显示;热门影片提供真实排名。原文件名、库 ID、`baseUrl` 参数、详情/播放载荷保留。

桌面验证:匿名 iPhone User-Agent 的最近发布/第二页及精选页返回 HTTP 200;当前浏览器会话验证八个分类的前两页,第二页均有不同影片。热门分类普通请求出现超时,网页导航曾返回 40 条。67 项离线回归通过,包含 HTTP/正文挂起、一次 HTML 回退、验证页、异步响应、翻页、空搜索及明确失败。iPhone 的网页 HTML 回传和实际列表显示仍需用户更新测试。

播放与详情解析的 19 个相关函数和原恢复版逐字相同;其他九个历史下载入口仍与任务前快照一致。此次列表修复更新为 1.0.3,之前的播放修复没有重新引入。

## 2026-10-04:更正恢复到任务开始前的版本

用户更正恢复时间为昨天(2026-10-03)开始本轮任务之前。按上海时间 `2026-10-03 00:00:00 +08:00` 之前主分支最后的快照 `2fd0121bfbf0cc812d1e9e39501384710e684ade` 恢复。这十个目标路径与原审查基线 `8e43017532ec81e3338046ba6e32dff7655bd3dd` 完全同字节;两提交之间仅新增另一个库 `hsex-mini-library.js`。
Expand All @@ -15,14 +25,14 @@
| [MissAV download-working](missav-mini-library-download-working.js) / [working 6](missav-mini-library-download-working%206.js) | 1.5.9 |
| [SexBJCam](sexbjcam-mini-library.js) | 1.1.7 |
| [ASMRLIB](asmrlib-mini-library.js) | 1.2.0 |
| [JAVGG](javgg-mini-library.js) | 1.0.0 |
| [JAVGG](javgg-mini-library.js) | 1.0.3(随后按用户要求修复列表加载) |
| [麻豆亚洲版](madou8-mini-library%205.js) | 1.1.0 |
| [套路 SM](taolusm-mini-library.js) | 1.0.0 |
| [KBJ fan](kbjfan-mini-library.js) | 1.0.0 |

每个文件均恢复该时间点的原始内容、版本号和参数,原 raw 地址与库 ID 保留。MissAV 普通版和 CloudFlare 版虽然版本号相同,内容和参数不同,分别保留;只有内容相同的两个 download-working 文件继续同步。

时间截止点、基线提交及十份文件 SHA-256 记录在 [tools/library-restoration.json](tools/library-restoration.json)。恢复检查校验每个路径的历史内容、真实 manifest、原版公共入口和对应版本行为。此前恢复最早上传版的操作已更正,下文播放修复为撤回的历史记录。
时间截止点、基线提交及历史文件 SHA-256 记录在 [tools/library-restoration.json](tools/library-restoration.json)。JAVGG 的旧快照移入 `supersededFiles` 并标明此后按用户要求更新到 1.0.3;其余九个入口继续校验历史内容。恢复检查校验每个路径的历史内容、真实 manifest、原版公共入口和对应版本行为。此前恢复最早上传版的操作已更正,下文播放修复为撤回的历史记录。

## 主维护文件与兼容入口

Expand Down
130 changes: 114 additions & 16 deletions javgg-mini-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const WidgetMetadata = {
id: 'javgg-mini-library',
name: 'JAVGG',
title: 'JAVGG',
version: '1.0.0',
version: '1.0.3',
requiredVersion: '0.0.1',
author: 'Alan huang',
site: JAVGG_DEFAULT_BASE,
Expand Down Expand Up @@ -64,10 +64,11 @@ function getManifest() {
async function getHome(rawCtx) {
const ctx = normalizeContext(rawCtx);
let immediate = [];
let loadError = '';
try {
immediate = parseCards(ctx, await fetchText(ctx, sectionURL(ctx, JAVGG_SECTIONS[0], 1))).slice(0, 18);
immediate = (await fetchListingPage(ctx, sectionURL(ctx, JAVGG_SECTIONS[0], 1))).items.slice(0, 18);
} catch (error) {
immediate = [diagnosticItem('首页加载失败', error)];
loadError = error.message || '首页加载失败';
}
const browse = {
id: 'javgg-browse',
Expand All @@ -76,11 +77,12 @@ async function getHome(rawCtx) {
lazy: false,
items: JAVGG_SECTIONS.map(categoryCard)
};
const first = sectionResult(JAVGG_SECTIONS[0], immediate);
const first = sectionResult(JAVGG_SECTIONS[0], immediate, loadError);
return {
pageType: 'home',
id: 'javgg-home',
title: 'JAVGG',
error: loadError || undefined,
heroAspectRatio: '2:3',
hero: immediate.filter(isMediaItem).slice(0, 5),
sections: [browse, first].concat(JAVGG_SECTIONS.slice(1).map(sectionShell))
Expand All @@ -91,10 +93,10 @@ async function getHomeSection(rawCtx) {
const ctx = normalizeContext(rawCtx);
const section = findSection(firstNonEmpty(ctx.sectionId, ctx.id, ctx.pageId)) || JAVGG_SECTIONS[0];
try {
const items = parseCards(ctx, await fetchText(ctx, sectionURL(ctx, section, 1))).slice(0, 18);
return sectionResult(section, items.length ? items : [diagnosticItem(section.title + '暂无内容')]);
const items = (await fetchListingPage(ctx, sectionURL(ctx, section, 1))).items.slice(0, 18);
return sectionResult(section, items);
} catch (error) {
return sectionResult(section, [diagnosticItem(section.title + '加载失败', error)]);
return sectionResult(section, [], error.message || section.title + '加载失败');
}
}

Expand All @@ -107,8 +109,9 @@ async function getCategory(rawCtx) {
const title = cleanText(firstNonEmpty(ctx.title, section && section.title, dynamicPath && dynamicPath.title, 'JAVGG'));
const url = section ? sectionURL(ctx, section, page) : pagedURL(baseURL(ctx) + (dynamicPath ? dynamicPath.path : '/new-post/'), page);
try {
const html = await fetchText(ctx, url);
const items = parseCards(ctx, html);
const listing = await fetchListingPage(ctx, url);
const html = listing.html;
const items = listing.items;
return {
pageType: 'category',
id: pageId,
Expand All @@ -123,8 +126,8 @@ async function getCategory(rawCtx) {
} catch (error) {
return {
pageType: 'category', id: pageId, title: title, style: 'media.posterGrid',
itemAspectRatio: '2:3', page: page, hasMore: false,
items: [diagnosticItem(title + '加载失败', error)]
itemAspectRatio: '2:3', page: page, nextPage: page, hasMore: false,
error: error.message || title + '加载失败', items: []
};
}
}
Expand All @@ -136,8 +139,9 @@ async function search(rawCtx) {
if (!query) return { pageType: 'search', title: '搜索', keyword: '', page: page, hasMore: false, items: [] };
const url = searchURL(ctx, query, page);
try {
const html = await fetchText(ctx, url);
const items = parseCards(ctx, html);
const listing = await fetchListingPage(ctx, url);
const html = listing.html;
const items = listing.items;
return {
pageType: 'search', title: '搜索:' + query, keyword: query,
style: 'media.posterGrid', itemAspectRatio: '2:3',
Expand All @@ -146,7 +150,8 @@ async function search(rawCtx) {
} catch (error) {
return {
pageType: 'search', title: '搜索:' + query, keyword: query,
page: page, hasMore: false, items: [diagnosticItem('搜索失败', error)]
page: page, nextPage: page, hasMore: false,
error: error.message || '搜索失败', items: []
};
}
}
Expand Down Expand Up @@ -279,10 +284,14 @@ function sectionShell(section) {
};
}

function sectionResult(section, items) {
function sectionResult(section, items, error) {
return {
id: section.id, title: section.title, style: section.style,
lazy: false, moreAction: categoryAction(section), items: items
lazy: false, moreAction: categoryAction(section),
error: error || undefined, subtitle: error || undefined,
items: section.style === 'discover.ranked' ? items.map(function (item, index) {
return Object.assign({}, item, { rank: index + 1 });
}) : items
};
}

Expand Down Expand Up @@ -450,6 +459,95 @@ function resolveRelativeURL(base, value) {
return String(base || '').replace(/[?#].*$/, '').replace(/\/[^/]*$/, '/') + value;
}

// Only list pages use this bounded HTML fallback. Player/detail requests retain
// their existing implementation and never enter this list-page browser path.
async function fetchListingPage(ctx, url) {
let httpError;
try {
return await listingDeadline(async function () {
const response = await httpGet(url, {
headers: requestHeaders(url), timeout: 6, timeoutSeconds: 6,
useBrowserCookie: true, attachBrowserCookie: true,
useBrowserFallback: false, browserFallback: false, allowBrowserFallback: false
});
return readListingResponse(ctx, response, 'http');
}, 6, 'http-timeout');
} catch (error) {
httpError = error || new Error('HTTP 请求失败');
if (httpError.status === 404 || httpError.status === 410) throw httpError;
}
const httpStage = httpError.stage || (/timeout|超时/i.test(String(httpError.message)) ? 'http-timeout' : 'http-error');
if (typeof Widget === 'undefined' || !Widget.browser || typeof Widget.browser.fetch !== 'function') {
throw listingError(httpStage, '列表读取失败,当前环境没有可用的网页回退');
}
try {
return await listingDeadline(async function () {
const response = await Widget.browser.fetch(url, {
visible: false, timeout: 12, timeoutSeconds: 12,
waitAfterLoad: 1, headers: requestHeaders(url)
});
return readListingResponse(ctx, response, 'browser');
}, 12, 'browser-timeout');
} catch (error) {
const stage = (error && error.stage) || 'browser-error';
throw listingError(stage, '列表读取失败(HTTP阶段:' + httpStage + ')' +
(stage === 'browser-verification' ? ',请在原站完成验证后重试' : ',请稍后重试'));
}
}

async function readListingResponse(ctx, response, stage) {
const status = Number(response && firstNonEmpty(response.statusCode, response.status)) || 0;
const html = await listingResponseText(response, 0);
if (/Just a moment|Checking(?:\s+your)?\s+browser|cf-browser-verification|cf-chl-/i.test(html.slice(0, 30000))) {
throw listingError(stage + '-verification', '来源需要浏览器验证');
}
if (status >= 400) throw listingError(stage + '-status', '来源返回 HTTP ' + status, status);
const items = parseCards(ctx, html);
const emptyList = /<[^>]+class=["'][^"']*\b(?:no-results|noresults|not-found)\b/i.test(html) ||
/(?:No results found|Nothing found|No posts found)/i.test(cleanText(html));
if (!items.length && !emptyList) throw listingError(stage + '-content', '来源没有返回影片列表');
return { html: html, items: items };
}

async function listingResponseText(response, depth) {
if (depth > 4 || response === undefined || response === null) return '';
response = await response;
if (response === undefined || response === null) return '';
if (typeof response === 'string') {
if (/^\s*\{/.test(response)) {
try { return await listingResponseText(JSON.parse(response), depth + 1); } catch (error) { /* Plain HTML below. */ }
}
return response;
}
if (typeof response.text === 'function') return String(await response.text());
const keys = ['html', 'data', 'body', 'text', 'content'];
for (let index = 0; index < keys.length; index += 1) {
if (response[keys[index]] === undefined || response[keys[index]] === null) continue;
const text = await listingResponseText(response[keys[index]], depth + 1);
if (text) return text;
}
return '';
}

function listingError(stage, message, status) {
const error = new Error(message + ';stage=' + stage);
error.stage = stage;
if (status) error.status = status;
return error;
}

async function listingDeadline(operation, seconds, stage) {
let timer;
try {
return await Promise.race([
Promise.resolve().then(operation),
new Promise(function (_, reject) {
timer = setTimeout(function () { reject(listingError(stage, '来源请求超时')); }, seconds * 1000);
})
]);
} finally { clearTimeout(timer); }
}

async function fetchText(ctx, url, referer) {
const response = await httpGet(url, {
headers: requestHeaders(referer || url),
Expand Down
Loading