// ==UserScript==
// @name Bangumi章节批量添加时间
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Bangumi 章节批量添加时间
// @include /^https?:\/\/(bangumi\.tv|bgm\.tv|chii\.in)\/subject\/.*\/ep\/.*/
// @author 墨云
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 等待指定选择器的元素出现后执行回调
function waitForEl(selector, cb, interval = 300, timeout = 10000) {
const start = Date.now();
const timer = setInterval(() => {
const el = document.querySelector(selector);
if (el) {
clearInterval(timer);
cb(el);
} else if (Date.now() - start > timeout) {
clearInterval(timer);
}
}, interval);
}
//
waitForEl('div.markItUpHeader', function (toolbarToRemove) {
if (toolbarToRemove) {
toolbarToRemove.remove();
}
});
// ----------------------------------------
// 日历图标
function createCalendarIcon() {
const icon = document.createElement('div');
icon.style.cssText = "width:16px;height:16px;position:relative;border:1px solid #007AFF;border-radius:2px;background:#fff;";
const header = document.createElement('div');
header.style.cssText = "position:absolute;top:0;left:0;width:100%;height:5px;background:#007AFF;border-top-left-radius:2px;border-top-right-radius:2px;";
icon.appendChild(header);
const dotStyle = "width:3px;height:3px;background:#007AFF;border-radius:50%;position:absolute;top:1px;";
const dotLeft = document.createElement('div');
dotLeft.style.cssText = dotStyle + "left:2px;";
const dotRight = document.createElement('div');
dotRight.style.cssText = dotStyle + "right:2px;";
icon.appendChild(dotLeft);
icon.appendChild(dotRight);
return icon;
}
// 创建弹窗
function createModal(onConfirm) {
const overlay = document.createElement('div');
overlay.style.cssText = "position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:10000;";
const modal = document.createElement('div');
modal.style.cssText = "position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);background:#fff;padding:20px;border-radius:8px;min-width:300px;";
overlay.appendChild(modal);
// 从第几集开始(选填)
const startEpDiv = document.createElement('div');
startEpDiv.style.cssText = "margin-bottom:20px;";
startEpDiv.innerHTML = `<div>从第几集开始(选填):</div><input type="number" min="1" placeholder="留空则从当前第一个开始" style="width:100%;" />`;
// 日期选择区域
const dateLabel = document.createElement('label');
dateLabel.textContent = "选择日期:";
const dateInput = document.createElement('input');
dateInput.type = "date";
dateInput.style.cssText = "width:100%;margin-bottom:20px;";
// 更新周期选择区域
const cycleLabel = document.createElement('label');
cycleLabel.textContent = "选择更新周期:";
const cycleSelect = document.createElement('select');
cycleSelect.style.cssText = "width:100%;margin-bottom:20px;";
const defOpt = document.createElement('option');
defOpt.value = "";
defOpt.textContent = "更新周期";
defOpt.disabled = true;
defOpt.selected = true;
cycleSelect.appendChild(defOpt);
// 更新周期选项
["周更", "日更", "工作日更", "特定星期更", "当天更完"].forEach(t => {
const opt = document.createElement('option');
opt.value = t;
opt.textContent = t;
cycleSelect.appendChild(opt);
});
// 一天几更(默认为1)
const dailyMultiDiv = document.createElement('div');
dailyMultiDiv.style.cssText = "margin-top:10px;";
dailyMultiDiv.innerHTML = `<div>一天几更(默认为1):</div><input type="number" min="1" value="1" style="width:100%;" />`;
// 复选框区域:仅当选择“特定星期更”时显示
const weekdayDiv = document.createElement('div');
weekdayDiv.style.cssText = "margin-top:10px;display:none;";
weekdayDiv.innerHTML = `
<div>选择星期(至少选2个):</div>
<label><input type='checkbox' value='1'> 星期一</label>
<label><input type='checkbox' value='2'> 星期二</label>
<label><input type='checkbox' value='3'> 星期三</label>
<label><input type='checkbox' value='4'> 星期四</label>
<label><input type='checkbox' value='5'> 星期五</label>
<label><input type='checkbox' value='6'> 星期六</label>
<label><input type='checkbox' value='7'> 星期日</label>
`;
cycleSelect.addEventListener('change', function () {
if (cycleSelect.value === "特定星期更") {
weekdayDiv.style.display = "block";
} else {
weekdayDiv.style.display = "none";
}
});
// 按钮区域
const btnDiv = document.createElement('div');
btnDiv.style.cssText = "text-align:right;margin-top:20px;";
const confirmBtn = document.createElement('button');
confirmBtn.textContent = "确定";
confirmBtn.style.marginRight = "10px";
const cancelBtn = document.createElement('button');
cancelBtn.textContent = "取消";
btnDiv.appendChild(confirmBtn);
btnDiv.appendChild(cancelBtn);
// 按顺序将各部分添加到弹窗中
modal.append(startEpDiv, dateLabel, dateInput, cycleLabel, cycleSelect, dailyMultiDiv, weekdayDiv, btnDiv);
document.body.appendChild(overlay);
confirmBtn.addEventListener('click', () => {
const dVal = dateInput.value;
const cycleVal = cycleSelect.value;
let extra = null;
if (cycleVal === "特定星期更") {
const checkboxes = weekdayDiv.querySelectorAll("input[type='checkbox']");
extra = [];
checkboxes.forEach(chk => {
if (chk.checked) extra.push(parseInt(chk.value, 10));
});
if (extra.length < 2) {
alert("请至少选择两个星期!");
return;
}
}
const dailyMultiInput = dailyMultiDiv.querySelector("input[type='number']");
const dailyMulti = parseInt(dailyMultiInput.value, 10);
if (isNaN(dailyMulti) || dailyMulti < 1) {
alert("请输入有效的一天几更数字(至少1)");
return;
}
// 获取“从第几集开始”的值
const startEpInput = startEpDiv.querySelector("input[type='number']");
const startEpVal = startEpInput.value.trim();
const startEp = startEpVal === "" ? null : parseInt(startEpVal, 10);
onConfirm(dVal, cycleVal, extra, dailyMulti, startEp);
document.body.removeChild(overlay);
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(overlay);
});
}
// 日期处理及增量函数
function formatDate(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
function addDays(date, n) {
let d = new Date(date);
d.setDate(d.getDate() + n);
return d;
}
function addWeeks(date, n) {
return addDays(date, 7 * n);
}
function addBusinessDays(date, n) {
let d = new Date(date);
while (n > 0) {
d.setDate(d.getDate() + 1);
if (d.getDay() !== 0 && d.getDay() !== 6) n--;
}
return d;
}
// “当天更完”选项
function computeDate(date, cycle, offset) {
if (cycle === "日更") return addDays(date, offset);
if (cycle === "周更") return addWeeks(date, offset);
if (cycle === "工作日更") return addBusinessDays(date, offset);
if (cycle === "当天更完") return date;
return addDays(date, offset);
}
// 返回日期对应的星期(1 为星期一,7 为星期日)
function getWeekday(d) {
let wd = d.getDay();
return wd === 0 ? 7 : wd;
}
// 针对“特定星期更”的处理
function getNextScheduledDateOnOrAfter(date, selectedDays) {
let d = new Date(date);
while (!selectedDays.includes(getWeekday(d))) {
d = addDays(d, 1);
}
return d;
}
function getNextScheduledDate(d, selectedDays) {
let candidate = addDays(d, 1);
while (!selectedDays.includes(getWeekday(candidate))) {
candidate = addDays(candidate, 1);
}
return candidate;
}
function computeSpecificDate(startDate, selectedDays, offset) {
let d = computeDate(startDate, "日更", 0); // 使用日更逻辑计算第一集日期
let scheduledDate = new Date(d);
let episodeCount = 0;
while (episodeCount < offset) {
scheduledDate = getNextScheduledDate(scheduledDate, selectedDays);
episodeCount++;
}
return scheduledDate;
}
// 处理文本域数据
function processData(startDate, cycle, extra, dailyMulti, startEp) {
const ta = document.querySelector('textarea[name="ep_list"]');
if (!ta) {
alert("未找到目标文本域!");
return;
}
const lines = ta.value.split(/\r?\n/);
let started = (startEp === null);
let uniqueCount = 0; // 累计处理的【唯一】章节数
const mapping = {}; // 记录第一列章节号到对应的有效偏移量
const res = lines.map(line => {
if (!line.trim()) return line;
const parts = line.split("|");
const chapter = parts[0].trim();
// 若尚未开始处理
if (!started) {
if (chapter === String(startEp)) {
started = true;
} else {
return line;
}
}
let effectiveOffset;
if (mapping.hasOwnProperty(chapter)) {
effectiveOffset = mapping[chapter];
} else {
effectiveOffset = Math.floor(uniqueCount / dailyMulti);
mapping[chapter] = effectiveOffset;
uniqueCount++;
}
let newDate;
if (cycle === "特定星期更" && Array.isArray(extra)) {
newDate = computeSpecificDate(new Date(startDate), extra, effectiveOffset);
} else {
newDate = computeDate(new Date(startDate), cycle, effectiveOffset);
}
parts[4] = formatDate(newDate);
return parts.join("|");
});
ta.value = res.join("\n");
}
// 注入逻辑
function injectButton() {
waitForEl('textarea[name="ep_list"]', function (el) {
const newDiv = document.createElement('div');
newDiv.style.cssText = 'display: block; margin-bottom: 5px;';
const link = document.createElement('a');
link.href = "#"; // 使用#作为链接占位符
link.title = "添加时间";
link.style.cursor = "pointer";
link.appendChild(createCalendarIcon());
newDiv.appendChild(link);
el.parentNode.insertBefore(newDiv, el);
link.addEventListener('click', e => {
e.preventDefault();
createModal((dateVal, cycleVal, extra, dailyMulti, startEp) => {
if (!dateVal) {
alert("请选择日期!");
return;
}
const d = new Date(dateVal);
if (isNaN(d.getTime())) {
alert("无效的日期!");
return;
}
if (!cycleVal) {
alert("请选择更新周期!");
return;
}
processData(d, cycleVal, extra, dailyMulti, startEp);
});
});
});
}
injectButton();
})();