Skip to content
Closed
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
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/

## [Unreleased]

## [6.5.3](../../compare/v6.5.2...v6.5.3) - 2026-10-17

### 新增

- 详情页新增独立的“详情鉴定快捷键”插件,可为屏蔽、收藏、已下载和已观看录制单键或 Ctrl / Alt / Shift / Meta 组合键,鉴定按钮内显示对应按键。
- 设置新增“快捷键”面板,支持冲突提示、清除和即时保存;面板入口随插件开关显示或隐藏,配置可随备份导入和恢复。
- 从列表打开详情弹窗后,启用的快捷键插件自动聚焦详情 iframe;输入框和可见弹窗内不触发鉴定快捷键。

## [6.5.2](../../compare/v6.5.1...v6.5.2) - 2026-10-16

### 新增
Expand Down Expand Up @@ -672,7 +680,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/
- 增加 `escapeHtml()` 修复多处 XSS 风险。
- 加密存储敏感凭据。

[Unreleased]: ../../compare/v6.5.1...HEAD
[Unreleased]: ../../compare/v6.5.3...HEAD
[6.2.1]: ../../compare/v6.2.0...v6.2.1
[6.2.0]: ../../compare/v6.1.1...v6.2.0
[6.1.1]: ../../compare/v6.1.0...v6.1.1
Expand Down
165 changes: 86 additions & 79 deletions JHS.user.js

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ JHS 将作品状态、演员收藏、新作追踪、外部检索、媒体预览

> 本项目仅提供网页信息整理与效率增强能力。请遵守所在地法律、目标网站条款及第三方服务规则,并仅处理你有权访问的内容。

## 6.5.2 更新
## 6.5.3 更新

当前正式版为 **6.5.2**(2026-10-16)。这次主要更新:
当前版本为 **6.5.3**(2026-10-17)。这次主要更新:

- **全部未下载筛选**:“更多筛选”可以查看所有未下载且未被屏蔽的作品,包含待鉴定、收藏和已看内容。
- **默认选项卡更完整**:设置可选择全部筛选,保留“屏蔽项”,加入“全部未下载”等智能筛选,下次进入列表页生效。
- **新作品保持浏览位置**:打开作品后,已浏览卡片渐隐移除,后续作品自动补位;保留筛选、滚动位置和其他选择项。连续移除、切换筛选和关闭重开弹窗也能正确处理。
- **详情鉴定快捷键**:屏蔽、收藏、已下载和已观看支持自定义单键及组合键,按钮内显示对应按键。
- **按键录制设置**:在“设置 → 快捷键”录制或清除按键,重复配置会提示冲突,修改即时保存并支持备份恢复。
- **独立插件开关**:在“插件管理 → 详情页”启停;禁用后隐藏设置入口,启用后从列表打开详情弹窗即可使用快捷键。

完整内容见 [6.5.2 更新记录](CHANGELOG.md#652---2026-10-16)。
完整内容见 [6.5.3 更新记录](CHANGELOG.md#653---2026-10-17)。

## 功能概览

Expand Down Expand Up @@ -86,6 +86,8 @@ JHS 将作品状态、演员收藏、新作追踪、外部检索、媒体预览

移动端可从右下角悬浮菜单访问鉴定、新作品、黑名单、排序、筛选和设置。

在“设置 → 快捷键”中录制屏蔽、收藏、已下载和已观看的单键或组合键,录制后自动保存,详情按钮显示对应键位。默认未设置;独立开关位于“插件管理 → 详情页 → 详情鉴定快捷键”,开关修改后刷新页面。输入内容或打开弹窗时不触发鉴定快捷键。

启用“浏览后自动移除新作品标记”后,新作品列表中的已浏览卡片会渐隐移除,并由后续作品补位,保留浏览位置、筛选和其他选择项;最后一页移空时回到上一页,全部移空时显示空状态;减少动态效果时直接完成补位。

列表“更多筛选”中的“全部未下载”显示所有未标记为已下载且未被屏蔽的作品,包括待鉴定、收藏和已看内容。
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture/feature-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

本矩阵冻结 Feature 的能力边界;独立启停单位以 Contribution ID 为准。FeatureRuntime 在注册时校验 Feature ID 与 Contribution owner 唯一性,旧 Plugin 名通过 `LEGACY_PLUGIN_CONTRIBUTION_MAP` 迁移。

详情鉴定快捷键由 `detail.shortcuts` Contribution 独立启停,仅在详情页监听按键,设置录制面板使用 SettingsService 持久化 `detailShortcuts`,不注册列表页快捷键。

| Feature | Kind | Sites / route | Startup | Contributions |
| --- | --- | --- | --- | --- |
| `settings` | system | all | on-command | `settings.core` |
Expand Down
13 changes: 7 additions & 6 deletions docs/sleazyfork.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@

JHS(JAV Helper Suite)是一款面向 JavDB、JavBus 的 Tampermonkey 用户脚本,用于整理作品状态、追踪演员新作、聚合资源与媒体信息,并提供统一离线、备份和统计工具。

> 当前稳定版本:**6.5.2**(2026-10-16)
> 当前版本:**6.5.3**(2026-10-17)
>
> [GitHub 项目](https://github.com/Teamper/JHS) · [完整更新记录](https://github.com/Teamper/JHS/blob/main/CHANGELOG.md) · [问题反馈](https://github.com/Teamper/JHS/issues)

## 6.5.2 更新了什么
## 6.5.3 更新了什么

- **全部未下载筛选**:“更多筛选”新增“全部未下载”,排除屏蔽项和已下载作品,保留未下载的待鉴定、收藏和已看内容。
- **默认选项卡更完整**:设置支持全部筛选,保留“屏蔽项”,加入“全部未下载”等智能筛选,下次进入列表页生效。
- **新作品保持浏览位置**:打开作品后,卡片渐隐移除,后续作品自动补位;保留筛选、浏览位置和其他选择项,连续移除、快速切换筛选和关闭重开弹窗也能正确处理。
- **详情鉴定快捷键**:屏蔽、收藏、已下载和已观看支持自定义单键及 Ctrl / Alt / Shift / Meta 组合键,按钮内显示对应按键。
- **按键录制设置**:在“设置 → 快捷键”录制或清除按键,支持冲突提示、即时保存及备份恢复。
- **独立插件开关**:在“插件管理 → 详情页”启停;禁用后隐藏设置入口,启用后从列表打开详情弹窗即可使用快捷键。

完整版本记录见 [6.5.2 更新说明](../CHANGELOG.md#652---2026-10-16)。
完整版本记录见 [6.5.3 更新说明](../CHANGELOG.md#653---2026-10-17)。

## 主要功能

Expand All @@ -29,6 +29,7 @@ JHS(JAV Helper Suite)是一款面向 JavDB、JavBus 的 Tampermonkey 用户
- 支持自动翻页、跨页批量收藏和标记已下载、鉴定记录、状态撤销和跨标签页同步。
- 桌面使用快捷筛选;移动端通过右下角悬浮菜单访问鉴定、筛选、排序和常用工具。
- 设置中的“默认显示选项卡”支持全部筛选,包括“屏蔽项”和“全部未下载”等智能筛选,下次进入列表页生效。
- “设置 → 快捷键”支持录制四种详情鉴定操作的单键或组合键,自动保存并在按钮上显示键位;默认未设置,独立开关位于“插件管理 → 详情页 → 详情鉴定快捷键”,开关修改后刷新页面。输入内容或打开弹窗时不触发。

### 演员与新作品

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jhs-userscript",
"version": "6.5.2",
"version": "6.5.3",
"description": "JAV Helper Suite for JavDB and JavBus",
"private": true,
"license": "MIT",
Expand Down
6 changes: 3 additions & 3 deletions scripts/regression-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function extractContributionOrder(registrySource, site) {
return registrySource.split(/\r?\n/).flatMap((line) => {
const match = line.match(/manifest\("[^"]+",\s*"[^"]+",\s*(\w+),\s*\[([^\]]+)\],\s*\{([^}]+)\}(?:,\s*\[[^\]]*\])?(?:,\s*\{[^}]*\})?\)/);
if (!match || !match[2].includes(`"${site}"`)) return [];
const order = match[3].match(new RegExp(`(?:^|,\\s*)\\s*(?:"${site}"|${site})\\s*:\\s*(\\d+)`));
const order = match[3].match(new RegExp(`(?:^|,\\s*)\\s*(?:"${site}"|${site})\\s*:\\s*(\\d+(?:\\.\\d+)?)`));
assert(order, `Missing ${site} order for ${match[1]}`);
return [{ plugin: match[1], order: Number(order[1]) }];
}).filter((item) => item.order > 0).sort((left, right) => left.order - right.order).map((item) => item.plugin);
Expand Down Expand Up @@ -324,11 +324,11 @@ for (const [file, className, pluginName] of expectedPlugins) {
const javdbPlugins = extractContributionOrder(registry, "javdb");
const javbusPlugins = extractContributionOrder(registry, "javbus");
assert(
javdbPlugins.join(",") === "ListPagePlugin,AutoPagePlugin,Fc2Plugin,Fc2NavigationPlugin,FoldCategoryPlugin,ListPageButtonPlugin,HistoryPlugin,SettingPlugin,NavBarPlugin,HitShowPlugin,Top250Plugin,SearchByImagePlugin,CoverButtonPlugin,Fc2By123AvPlugin,DetailPagePlugin,DetailWorkspacePlugin,ReviewPlugin,RelatedPlugin,DetailPageButtonPlugin,HighlightMagnetPlugin,PreviewVideoPlugin,FilterTitleKeywordPlugin,ActressInfoPlugin,OtherSitePlugin,TranslatePlugin,WantAndWatchedVideosPlugin,MagnetHubPlugin,ScreenShotPlugin,BlacklistPlugin,FavoriteActressesPlugin,NewVideoPlugin,TaskPlugin,StatsPlugin,MobileBottomBarPlugin,OneOneFiveMatchPlugin,UnifiedOfflinePlugin,CompatibilityEnhancementsPlugin",
javdbPlugins.join(",") === "ListPagePlugin,AutoPagePlugin,Fc2Plugin,Fc2NavigationPlugin,FoldCategoryPlugin,ListPageButtonPlugin,HistoryPlugin,SettingPlugin,NavBarPlugin,HitShowPlugin,Top250Plugin,SearchByImagePlugin,CoverButtonPlugin,Fc2By123AvPlugin,DetailPagePlugin,DetailWorkspacePlugin,ReviewPlugin,RelatedPlugin,DetailPageButtonPlugin,DetailShortcutPlugin,HighlightMagnetPlugin,PreviewVideoPlugin,FilterTitleKeywordPlugin,ActressInfoPlugin,OtherSitePlugin,TranslatePlugin,WantAndWatchedVideosPlugin,MagnetHubPlugin,ScreenShotPlugin,BlacklistPlugin,FavoriteActressesPlugin,NewVideoPlugin,TaskPlugin,StatsPlugin,MobileBottomBarPlugin,OneOneFiveMatchPlugin,UnifiedOfflinePlugin,CompatibilityEnhancementsPlugin",
"JavDB plugin registration order changed"
);
assert(
javbusPlugins.join(",") === "ListPagePlugin,ListPageButtonPlugin,SettingPlugin,HistoryPlugin,AutoPagePlugin,SearchByImagePlugin,BusNavBarPlugin,CoverButtonPlugin,BusImgPlugin,BusDetailPagePlugin,DetailWorkspacePlugin,DetailPageButtonPlugin,ReviewPlugin,FilterTitleKeywordPlugin,HighlightMagnetPlugin,BusPreviewVideoPlugin,MagnetHubPlugin,ScreenShotPlugin,OtherSitePlugin,TranslatePlugin,BlacklistPlugin,TaskPlugin,StatsPlugin,MobileBottomBarPlugin,OneOneFiveMatchPlugin,UnifiedOfflinePlugin,CompatibilityEnhancementsPlugin",
javbusPlugins.join(",") === "ListPagePlugin,ListPageButtonPlugin,SettingPlugin,HistoryPlugin,AutoPagePlugin,SearchByImagePlugin,BusNavBarPlugin,CoverButtonPlugin,BusImgPlugin,BusDetailPagePlugin,DetailWorkspacePlugin,DetailPageButtonPlugin,DetailShortcutPlugin,ReviewPlugin,FilterTitleKeywordPlugin,HighlightMagnetPlugin,BusPreviewVideoPlugin,MagnetHubPlugin,ScreenShotPlugin,OtherSitePlugin,TranslatePlugin,BlacklistPlugin,TaskPlugin,StatsPlugin,MobileBottomBarPlugin,OneOneFiveMatchPlugin,UnifiedOfflinePlugin,CompatibilityEnhancementsPlugin",
"JavBus plugin registration order changed"
);
assertIncludes(registry, 'OneTwoThreeOfflinePlugin, ["javdb", "javbus", "123pan"]', "shared registry");
Expand Down
2 changes: 1 addition & 1 deletion src/app/feature-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class FeatureRuntime {
this.commands.setActivator((featureId) => this.activate(featureId).then(() => undefined));
}

/** @param {Array<Record<string, any>>} manifests */
/** @param {ReadonlyArray<Record<string, any>>} manifests */
setContributionManifests(manifests) { this.contributionManifests = new Map(manifests.map((item) => [item.id, item])); }

activeSurfaces() {
Expand Down
4 changes: 2 additions & 2 deletions src/contracts/manifests.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function defineFeature(manifest) {
return Object.freeze({ ...manifest });
}

/** @param {Record<string, unknown>} manifest */
/** @template {Record<string, unknown> & { sites: string[], routes: string[], surfaces: string[], order: Record<string, number> }} T @param {T} manifest @returns {Readonly<Omit<T, "sites" | "routes" | "surfaces" | "order"> & { sites: readonly string[], routes: readonly string[], surfaces: readonly string[], order: Readonly<Record<string, number>> }>} */
export function defineContribution(manifest) {
requireNonEmptyString(manifest.id, "Contribution id");
requireNonEmptyString(manifest.featureId, "Contribution featureId");
Expand All @@ -55,7 +55,7 @@ export function defineContribution(manifest) {
requireUniqueTokens(manifest.requires, "requires");
if (typeof manifest.plugin !== "function") throw new TypeError("Contribution plugin must be a class");
if (!manifest.order || typeof manifest.order !== "object") throw new TypeError("Contribution order must be explicit");
return Object.freeze({ ...manifest, sites: Object.freeze([...(/** @type {unknown[]} */ (manifest.sites))]), routes: Object.freeze([...(/** @type {unknown[]} */ (manifest.routes))]), surfaces: Object.freeze([...(/** @type {unknown[]} */ (manifest.surfaces))]), order: Object.freeze({ ...manifest.order }) });
return Object.freeze({ ...manifest, sites: Object.freeze([...manifest.sites]), routes: Object.freeze([...manifest.routes]), surfaces: Object.freeze([...manifest.surfaces]), order: Object.freeze({ ...manifest.order }) });
}

/** @param {Record<string, unknown>} manifest */
Expand Down
31 changes: 31 additions & 0 deletions src/core/detail-shortcuts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// @ts-check

export const DETAIL_SHORTCUT_ACTIONS = Object.freeze({
blocked: { id: "filterBtn", label: "屏蔽" },
favorite: { id: "favoriteBtn", label: "收藏" },
downloaded: { id: "hasDownBtn", label: "已下载" },
watched: { id: "hasWatchBtn", label: "已观看" }
});

export function shortcutFromEvent(event = new KeyboardEvent("keydown")) {
if (event.isComposing || event.repeat || !event.code || /^(Control|Shift|Alt|Meta)/.test(event.code) || ["Escape", "Tab", "Unidentified"].includes(event.code)) return null;
return { code: event.code, ctrl: event.ctrlKey, alt: event.altKey, shift: event.shiftKey, meta: event.metaKey };
}

export function shortcutLabel(shortcut = Object.create(null)) {
if (!shortcut || typeof shortcut.code !== "string") return "";
const key = shortcut.code.replace(/^Key(?=[A-Z]$)|^Digit(?=\d$)/g, "");
return [shortcut.ctrl && "Ctrl", shortcut.alt && "Alt", shortcut.shift && "Shift", shortcut.meta && "Meta", key].filter(Boolean).join("+");
}

export function shortcutMatches(left = Object.create(null), right = Object.create(null)) {
return !!left && !!right && left.code === right.code && ["ctrl", "alt", "shift", "meta"].every(key => !!left[key] === !!right[key]);
}

export function readDetailShortcuts(value = Object.create(null)) {
if (!value || typeof value !== "object" || Array.isArray(value)) return Object.create(null);
return Object.fromEntries(Object.keys(DETAIL_SHORTCUT_ACTIONS).filter(key => {
const item = value[key];
return item && typeof item.code === "string" && /^[A-Za-z][A-Za-z0-9]*$/.test(item.code) && !/^(Control|Shift|Alt|Meta)/.test(item.code) && !["Escape", "Tab", "Unidentified"].includes(item.code);
}).map(key => [key, value[key]]));
}
1 change: 1 addition & 0 deletions src/core/legacy-plugin-contributions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const LEGACY_PLUGIN_CONTRIBUTION_MAP = Object.freeze({
PreviewVideoPlugin: "detail.javdb-preview",
CoverButtonPlugin: "detail.cover-state-actions",
DetailPageButtonPlugin: "detail.page-state-actions",
DetailShortcutPlugin: "detail.shortcuts",
HighlightMagnetPlugin: "detail.native-magnets",
OtherSitePlugin: "detail.external-sites",
SubTitleCatPlugin: "external-bridge.subtitle",
Expand Down
2 changes: 1 addition & 1 deletion src/features/detail/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DetailController } from "./detail-controller.js";
export default defineFeature({
id: "detail", kind: "feature", disableable: true, sites: ["javdb", "javbus"], routes: ["detail", "owned-detail"], startup: "eager",
requires: [PORT.host, SERVICE.movie, SERVICE.review, SERVICE.related, SERVICE.magnet, SERVICE.screenshot],
contributes: ["detail.javdb-native", "detail.javbus-native", "detail.workspace", "detail.fc2-owned", "detail.fc2-navigation", "detail.fc2-lookup", "detail.cover-state-actions", "detail.page-state-actions", "detail.javdb-preview", "detail.javbus-images", "detail.javbus-preview", "detail.reviews", "detail.related", "detail.native-magnets", "detail.external-magnets", "detail.screenshot", "detail.external-sites"],
contributes: ["detail.javdb-native", "detail.javbus-native", "detail.workspace", "detail.fc2-owned", "detail.fc2-navigation", "detail.fc2-lookup", "detail.cover-state-actions", "detail.page-state-actions", "detail.shortcuts", "detail.javdb-preview", "detail.javbus-images", "detail.javbus-preview", "detail.reviews", "detail.related", "detail.native-magnets", "detail.external-magnets", "detail.screenshot", "detail.external-sites"],
providesCommands: [],
activate: (/** @type {any} */ deps, /** @type {any} */ runtime) => {
if (runtime.route === "owned-detail") return { dispose: () => {} };
Expand Down
2 changes: 1 addition & 1 deletion src/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ==UserScript==
// @name JHS
// @namespace https://sleazyfork.org/zh-CN/scripts/578503-jhs-ya
// @version 6.5.2
// @version 6.5.3
// @author JHS Contributors
// @description JAV Helper Suite:为 JavDB / JavBus 提供浏览、收藏、筛选、资源检索、数据备份与统计增强。
// @license MIT
Expand Down
Loading
Loading