diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json
index ed033741..8ac66e73 100644
--- a/public/_locales/en/messages.json
+++ b/public/_locales/en/messages.json
@@ -769,17 +769,29 @@
"checkTimeDanmakuSkip": {
"message": "检查片段附近是否已有上传的跳过信息,已有则不触发此功能"
},
- "danmakuRegexPattern": {
- "message": "匹配正则: "
+ "danmakuTimeMatchingRegexPattern": {
+ "message": "空降时间匹配正则: "
},
"danmakuRegexPatternPlaceholder": {
- "message": "正则要求能捕获2到3组数字"
+ "message": "能匹配2~3组数字的正则表达式"
},
"danmakuRegexTitle": {
- "message": "请输入一个匹配时间格式的正则表达式,要求能捕获2到3组数字。如果不清楚如何写正则表达式,请勿修改此项"
+ "message": "请输入一个匹配时间格式的正则表达式,要求能捕获2~3组数字。如果不清楚如何写正则表达式,请勿修改此项"
},
- "danmakuRegexPatternDescription": {
- "message": "启用基于弹幕的跳过功能,目前处于实验状态"
+ "danmakuTimeMatchingRegexPatternDescription": {
+ "message": "空降时间匹配类似“01:23”“四分五十六秒”“1分2秒”等格式的弹幕。"
+ },
+ "danmakuOffsetMatchingRegexPattern": {
+ "message": "时间偏移匹配正则: "
+ },
+ "danmakuOffsetRegexPatternPlaceholder": {
+ "message": "能匹配时间偏移指令的正则表达式"
+ },
+ "danmakuOffsetRegexTitle": {
+ "message": "请输入一个匹配时间偏移指令的正则表达式。如果不清楚如何写正则表达式,请勿修改此项"
+ },
+ "danmakuOffsetRegexPatternDescription": {
+ "message": "时间偏移匹配类似“向右12下”“右方向34次”等格式的弹幕,并以此计算空降时间。"
},
"muteSegments": {
"message": "允许片段静音而不是跳过"
diff --git a/public/_locales/zh_CN/messages.json b/public/_locales/zh_CN/messages.json
index ed033741..8ac66e73 100644
--- a/public/_locales/zh_CN/messages.json
+++ b/public/_locales/zh_CN/messages.json
@@ -769,17 +769,29 @@
"checkTimeDanmakuSkip": {
"message": "检查片段附近是否已有上传的跳过信息,已有则不触发此功能"
},
- "danmakuRegexPattern": {
- "message": "匹配正则: "
+ "danmakuTimeMatchingRegexPattern": {
+ "message": "空降时间匹配正则: "
},
"danmakuRegexPatternPlaceholder": {
- "message": "正则要求能捕获2到3组数字"
+ "message": "能匹配2~3组数字的正则表达式"
},
"danmakuRegexTitle": {
- "message": "请输入一个匹配时间格式的正则表达式,要求能捕获2到3组数字。如果不清楚如何写正则表达式,请勿修改此项"
+ "message": "请输入一个匹配时间格式的正则表达式,要求能捕获2~3组数字。如果不清楚如何写正则表达式,请勿修改此项"
},
- "danmakuRegexPatternDescription": {
- "message": "启用基于弹幕的跳过功能,目前处于实验状态"
+ "danmakuTimeMatchingRegexPatternDescription": {
+ "message": "空降时间匹配类似“01:23”“四分五十六秒”“1分2秒”等格式的弹幕。"
+ },
+ "danmakuOffsetMatchingRegexPattern": {
+ "message": "时间偏移匹配正则: "
+ },
+ "danmakuOffsetRegexPatternPlaceholder": {
+ "message": "能匹配时间偏移指令的正则表达式"
+ },
+ "danmakuOffsetRegexTitle": {
+ "message": "请输入一个匹配时间偏移指令的正则表达式。如果不清楚如何写正则表达式,请勿修改此项"
+ },
+ "danmakuOffsetRegexPatternDescription": {
+ "message": "时间偏移匹配类似“向右12下”“右方向34次”等格式的弹幕,并以此计算空降时间。"
},
"muteSegments": {
"message": "允许片段静音而不是跳过"
diff --git a/public/_locales/zh_TW/messages.json b/public/_locales/zh_TW/messages.json
index 69fcb567..34da1866 100644
--- a/public/_locales/zh_TW/messages.json
+++ b/public/_locales/zh_TW/messages.json
@@ -769,18 +769,30 @@
"checkTimeDanmakuSkip": {
"message": "檢查片段附近是否已有上傳的跳過資訊,已有則不觸發此功能"
},
- "danmakuRegexPattern": {
- "message": "匹配正則: "
+ "danmakuTimeMatchingRegexPattern": {
+ "message": "空降時間匹配正則: "
},
"danmakuRegexPatternPlaceholder": {
- "message": "正則要求能捕獲2到3組數字"
+ "message": "能匹配2~3組數字的正則表達式"
},
"danmakuRegexTitle": {
- "message": "請輸入一個匹配時間格式的正則表達式,要求能捕獲2到3組數字。如果不清楚如何寫正則表達式,請勿修改此項"
+ "message": "請輸入一個匹配時間格式的正則表達式,要求能捕獲2~3組數字。如果不清楚如何寫正則表達式,請勿修改此項"
},
- "danmakuRegexPatternDescription": {
- "message": "啟用基於彈幕的跳過功能,目前處於實驗狀態"
+ "danmakuTimeMatchingRegexPatternDescription": {
+ "message": "空降時間匹配類似“01:23”“四分五十六秒”“1分2秒”等格式的彈幕。"
},
+ "danmakuOffsetMatchingRegexPattern": {
+ "message": "時間偏移匹配正則: "
+ },
+ "danmakuOffsetRegexPatternPlaceholder": {
+ "message": "能匹配時間偏移指令的正則表達式"
+ },
+ "danmakuOffsetRegexTitle": {
+ "message": "請輸入一個匹配時間偏移指令的正則表達式。如果不清楚如何寫正則表達式,請勿修改此項"
+ },
+ "danmakuOffsetRegexPatternDescription": {
+ "message": "時間偏移匹配類似“向右12下”“右方向34次”等格式的彈幕,並以此計算空降時間。"
+ },
"muteSegments": {
"message": "允許片段靜音而不是跳過"
},
diff --git a/public/options/options.html b/public/options/options.html
index fc7f22f8..1313f8ee 100644
--- a/public/options/options.html
+++ b/public/options/options.html
@@ -751,16 +751,27 @@
__MSG_exportOtherData__
-
-
+
+
-
-
+
+
-
__MSG_danmakuRegexPatternDescription__
+
__MSG_danmakuTimeMatchingRegexPatternDescription__
+
+
+
+
+
+
+
+
+
+
__MSG_danmakuOffsetRegexPatternDescription__
diff --git a/src/config.ts b/src/config.ts
index f22826f6..dd7456de 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -33,7 +33,8 @@ interface SBConfig {
enableDanmakuSkip: boolean;
enableAutoSkipDanmakuSkip: boolean;
enableMenuDanmakuSkip: boolean;
- danmakuRegexPattern: string;
+ danmakuTimeMatchingRegexPattern: string;
+ danmakuOffsetMatchingRegexPattern: string;
checkTimeDanmakuSkip: boolean;
muteSegments: boolean;
fullVideoSegments: boolean;
@@ -189,13 +190,8 @@ function migrateOldSyncFormats(config: SBConfig) {
config["serverAddress"] = CompileConfig.serverAddress;
}
- // danmaku regex update since 0.5.0
- const oldDanmakuRegexPatterns = [
- "(?:空降\\s*)?(\\d{1,2}):(\\d{1,2})(?::(\\d{1,2}))?", // 0.5.0
- ];
- if (oldDanmakuRegexPatterns.includes(config["danmakuRegexPattern"])) {
- config["danmakuRegexPattern"] = syncDefaults.danmakuRegexPattern;
- }
+ // "danmakuRegexPattern" 参数在 0.5.9 版本(预计)中被移除,取而代之的是 "danmakuTimeMatchingRegexPattern" 和 "danmakuOffsetMatchingRegexPattern" 参数
+ delete config["danmakuRegexPattern"];
}
const syncDefaults = {
@@ -217,7 +213,9 @@ const syncDefaults = {
enableDanmakuSkip: false,
enableAutoSkipDanmakuSkip: false,
enableMenuDanmakuSkip: false,
- danmakuRegexPattern: "(?:空降\\s*)?(\\d{1,2})[::](\\d{1,2})(?:[::](\\d{1,2}))?$",
+ danmakuTimeMatchingRegexPattern:
+ "(?:(\\d{1,2})\\s*(?:小时|h|H|:|:|;|;|\\.|-|—)\\s*)?(?:(\\d{1,2})\\s*(?:分钟|分|:|:|;|;|\\.|-|—|m|M)\\s*)?(?:(\\d{1,2})\\s*(秒|s|S)?)",
+ danmakuOffsetMatchingRegexPattern: "(?:^|(右|右滑|按|右下|右向|右方向|→|⇒|⇢|⇨|⮕|🡆|🠺|🠾|🢒|👉))(\\d+)(下|次)?$",
checkTimeDanmakuSkip: true,
muteSegments: true,
diff --git a/src/content.ts b/src/content.ts
index c6fd11b5..fa80ff47 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -42,6 +42,7 @@ import { isFirefox, isFirefoxOrSafari, isSafari, sleep, waitFor } from "./utils/
import { AnimationUtils } from "./utils/animationUtils";
import { addCleanupListener, cleanPage } from "./utils/cleanup";
import { defaultPreviewTime } from "./utils/constants";
+import { parseTargetTimeFromDanmaku } from "./utils/danmakusUtils";
import { findValidElement } from "./utils/dom";
import { importTimes } from "./utils/exporter";
import { getErrorMessage, getFormattedTime } from "./utils/formating";
@@ -778,29 +779,12 @@ async function startSponsorSchedule(
}
function checkDanmaku(text: string, offset: number) {
- const match = new RegExp(Config.config.danmakuRegexPattern).exec(text);
- if (!match) {
- return;
- }
+ const targetTime = parseTargetTimeFromDanmaku(text, getVirtualTime());
+ if (targetTime === null) return;
- const timeComponents = match
- .slice(1)
- .filter(Boolean)
- .map((value) => parseInt(value, 10));
- let hours = 0,
- minutes = 0,
- seconds = 0;
-
- if (timeComponents.length === 2) {
- minutes = timeComponents[0];
- seconds = timeComponents[1];
- } else if (timeComponents.length === 3) {
- hours = timeComponents[0];
- minutes = timeComponents[1];
- seconds = timeComponents[2];
- }
+ // [DEBUG]
+ // console.debug("检测到空降弹幕: ", text, "请求跳转到: ", targetTime);
- const targetTime = hours * 3600 + minutes * 60 + seconds;
const startTime = getVirtualTime() + offset;
// ignore if the time is in the past
diff --git a/src/utils/danmakusUtils.ts b/src/utils/danmakusUtils.ts
new file mode 100644
index 00000000..62adea0e
--- /dev/null
+++ b/src/utils/danmakusUtils.ts
@@ -0,0 +1,138 @@
+import Config from "../config";
+
+/**
+ * 解析弹幕文本中的目标时间
+ *
+ * @param text 输入需要解析的弹幕文本
+ * @param currentTime 弹幕出现的时间
+ * @returns 返回弹幕指向目标时间。若无法解析,则会返回null。
+ */
+export function parseTargetTimeFromDanmaku(text: string, currentTime: number) {
+ /**
+ * 解析时间字符串并将其转换为总秒数。
+ *
+ * @param text - 包含需要解析的时间的输入字符串。
+ * @returns 由时间字符串表示的总秒数,如果时间字符串无效则返回null。
+ *
+ * 该函数使用在 `Config.config.danmakuTimeMatchingRegexPattern` 中定义的正则表达式模式
+ * 来匹配和提取输入字符串中的小时、分钟和秒。如果匹配成功且有效,
+ * 它通过将小时转换为秒、分钟转换为秒并将它们加到解析的秒数中来计算总秒数。
+ */
+ function parseTime(text: string) {
+ const regex = new RegExp(Config.config.danmakuTimeMatchingRegexPattern, "g");
+
+ let match: RegExpExecArray | null;
+ while ((match = regex.exec(text)) !== null) {
+ const [, , minutes, seconds, secondsSuffix] = match;
+
+ if (seconds && (secondsSuffix || minutes)) {
+ const hours = parseInt(match[1] || "0");
+ const minutes = parseInt(match[2] || "0");
+ const seconds = parseInt(match[3] || "0");
+ return hours * 3600 + minutes * 60 + seconds;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * 解析弹幕文本中的偏移时间。
+ *
+ * @param text - 包含偏移时间的弹幕文本。
+ * @returns 如果找到匹配的偏移时间,返回偏移时间(以秒为单位);否则返回 null。
+ *
+ * @remarks
+ * 该函数使用配置中的正则表达式模式来匹配偏移时间。匹配的偏移时间格式类似于“向右x下”,
+ * 其中 x 是一个整数,表示偏移的时间单位。偏移时间等价于当前时间加上 5 倍的 x 秒。
+ */
+ function parseOffsetTime(text: string) {
+ const regex = new RegExp(Config.config.danmakuOffsetMatchingRegexPattern, "g");
+
+ let match: RegExpExecArray | null;
+ while ((match = regex.exec(text)) !== null) {
+ const [, direction, offset, suffix] = match;
+
+ if (offset && (direction || suffix)) {
+ // “向右x下”等价于当前时间 + 5x秒
+ return parseInt(offset) * 5;
+ }
+ }
+
+ return null;
+ }
+
+ text = text.replace(/[零一二三四五六七八九两壹贰叁肆伍陆柒捌玖十百千万]+/g, (cnNum) => parseChineseNumber(cnNum));
+
+ const directParsedTime = parseTime(text);
+ if (directParsedTime) return directParsedTime;
+ else {
+ const offsetParsedTime = parseOffsetTime(text);
+ if (offsetParsedTime) return offsetParsedTime + currentTime;
+ }
+ return null;
+}
+
+/**
+ * 将中文数字字符串转换为阿拉伯数字字符串。
+ *
+ * @param inputText - 包含中文数字的字符串。
+ * @returns 转换后的阿拉伯数字字符串。如果输入包含无效字符,则返回 null。
+ *
+ * @example
+ * ```typescript
+ * parseChineseNumber("一"); // 返回 "1"
+ * parseChineseNumber("十二"); // 返回 "12"
+ * parseChineseNumber("二十"); // 返回 "20"
+ * parseChineseNumber("二十一"); // 返回 "21"
+ * parseChineseNumber("一百"); // 返回 null
+ * ```
+ */
+export function parseChineseNumber(inputText: string) {
+ const cnChrMap: { [key: string]: number } = {
+ 零: 0,
+ 一: 1,
+ 二: 2,
+ 三: 3,
+ 四: 4,
+ 五: 5,
+ 六: 6,
+ 七: 7,
+ 八: 8,
+ 九: 9,
+ 两: 2,
+ 壹: 1,
+ 贰: 2,
+ 叁: 3,
+ 肆: 4,
+ 伍: 5,
+ 陆: 6,
+ 柒: 7,
+ 捌: 8,
+ 玖: 9,
+ };
+
+ const cnUnitMap: { [key: string]: number } = {
+ 十: 10,
+ };
+
+ let num = 0;
+ let unit = 1;
+ for (let i = 0; i < inputText.length; i++) {
+ const chr = inputText[i];
+ if (chr in cnChrMap) {
+ num += cnChrMap[chr] * unit;
+ } else if (chr in cnUnitMap) {
+ unit = cnUnitMap[chr];
+ if (num === 0 && unit === 10) {
+ num = 1;
+ }
+ num = num * unit;
+ unit = 1;
+ } else {
+ return null;
+ }
+ }
+
+ return num.toString();
+}