// ==UserScript==
// @name Bangumi章节批量添加时间
// @namespace http://tampermonkey.net/
// @version 2.5
// @description Bangumi 章节批量添加时间
// @include /^https?:\/\/(bangumi\.tv|bgm\.tv|chii\.in)\/subject\/.*\/ep\/.*/
// @include /^https?:\/\/(bangumi\.tv|bgm\.tv|chii\.in)\/subject\/.*\/ep\/create/
// @include /^https?:\/\/(bangumi\.tv|bgm\.tv|chii\.in)\/ep\/\d+\/edit/
// @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 isDarkMode() {
const dataTheme = document.documentElement.getAttribute('data-theme');
return dataTheme === 'dark' ||
document.documentElement.classList.contains('night') ||
document.body.classList.contains('dark') ||
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function getModeColor() {
if (isDarkMode()) {
return {
text: '#e0e0e0',
background: '#1f1f1f',
primary: '#f6a1b2',
secondary: '#4CAF50',
border: '#333',
calendar: '#F09100'
};
}
return {
text: '#333',
background: '#f7f7f7',
primary: '#f6a1b2',
secondary: '#4CAF50',
border: '#ddd',
calendar: '#F09100'
};
}
function createCalendarIcon() {
const colors = getModeColor();
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("width", "18");
svg.setAttribute("height", "18");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", colors.calendar);
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.style.cssText = "vertical-align: middle; margin-right: 5px;";
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", "3");
rect.setAttribute("y", "4");
rect.setAttribute("width", "18");
rect.setAttribute("height", "18");
rect.setAttribute("rx", "2");
rect.setAttribute("ry", "2");
svg.appendChild(rect);
const line1 = document.createElementNS("http://www.w3.org/2000/svg", "line");
line1.setAttribute("x1", "16");
line1.setAttribute("y1", "2");
line1.setAttribute("x2", "16");
line1.setAttribute("y2", "6");
svg.appendChild(line1);
const line2 = document.createElementNS("http://www.w3.org/2000/svg", "line");
line2.setAttribute("x1", "8");
line2.setAttribute("y1", "2");
line2.setAttribute("x2", "8");
line2.setAttribute("y2", "6");
svg.appendChild(line2);
const line3 = document.createElementNS("http://www.w3.org/2000/svg", "line");
line3.setAttribute("x1", "3");
line3.setAttribute("y1", "10");
line3.setAttribute("x2", "21");
line3.setAttribute("y2", "10");
svg.appendChild(line3);
return svg;
}
function createModal(onConfirm) {
const darkMode = isDarkMode();
const colors = getModeColor();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
height: 100% !important;
background: rgba(0, 0, 0, 0.8);
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
`;
const modal = document.createElement('div');
const bgColor = darkMode ? '#1a1a1a' : '#f7f7f7';
const textColor = darkMode ? '#e0e0e0' : '#333';
modal.style.cssText = `
background: ${bgColor} !important;
padding: 25px;
border-radius: 10px;
min-width: 350px;
max-width: 90%;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
color: ${textColor} !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
* { color: ${textColor} !important; }
input, select { background: ${darkMode ? '#2a2a2a' : '#fff'} !important; border: 1px solid ${darkMode ? '#444' : '#ddd'} !important; color: ${textColor} !important; }
button { background: ${darkMode ? '#333' : '#f7f7f7'} !important; color: ${textColor} !important; }
`;
overlay.appendChild(modal);
const title = document.createElement('h3');
title.textContent = "批量添加章节日期";
title.style.cssText = `text-align: center; margin-bottom: 20px; color: ${colors.text}; border-bottom: 1px solid ${colors.border}; padding-bottom: 10px;`;
modal.appendChild(title);
const createInputGroup = (label, htmlContent) => {
const div = document.createElement('div');
div.style.cssText = `margin-bottom: 15px; font-size: 14px; font-weight: 500; color: ${colors.text};`;
div.innerHTML = `
${label}
${htmlContent}`;
return div;
};
const inputBg = darkMode ? '#2a2a2a' : '#fff';
const inputText = darkMode ? '#e0e0e0' : '#333';
const inputBorder = darkMode ? '#444' : '#ddd';
const inputStyle = `width:100%; box-sizing: border-box; padding: 10px; border: 1px solid ${inputBorder}; border-radius: 6px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.06); outline: none; transition: border-color 0.2s ease, box-shadow 0.2s ease; font-size: 14px; background: ${inputBg} !important; color: ${inputText} !important;`;
const focusInputStyle = `border-color: ${colors.primary}; box-shadow: 0 0 0 2px rgba(246, 161, 178, 0.2);`;
const startEpDiv = createInputGroup(
'从第几集开始(选填):',
``
);
startEpDiv.querySelector('input').addEventListener('focus', (e) => e.target.style.cssText += focusInputStyle);
startEpDiv.querySelector('input').addEventListener('blur', (e) => e.target.style.cssText = inputStyle);
const autoBtnBg = darkMode ? '#4CAF50' : '#4CAF50';
const dateInputDiv = createInputGroup(
'选择日期:',
`
`
);
const dateInput = dateInputDiv.querySelector('input');
const autoDateBtn = dateInputDiv.querySelector('button');
dateInput.addEventListener('focus', (e) => e.target.style.cssText += focusInputStyle);
dateInput.addEventListener('blur', (e) => e.target.style.cssText = inputStyle);
dateInput.addEventListener('wheel', (e) => {
e.preventDefault();
});
autoDateBtn.addEventListener('click', async () => {
try {
const match = window.location.href.match(/subject\/(\d+)/);
if (!match) {
alert('无法从当前URL提取条目ID');
return;
}
const subjectId = match[1];
const subjectUrl = `https://bangumi.tv/subject/${subjectId}`;
autoDateBtn.textContent = '识别中...';
autoDateBtn.disabled = true;
const response = await fetch(subjectUrl);
if (!response.ok) {
throw new Error('无法加载条目页面');
}
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const dateSelectors = [
'li:has(span.tip:contains("放送开始"))',
'li:has(span.tip:contains("上映年度"))',
'li:has(span.tip:contains("发售日"))',
'li:has(span.tip:contains("开始"))',
'li:has(span.tip:contains("首播"))'
];
let dateText = null;
for (const selector of dateSelectors) {
const lis = doc.querySelectorAll('li');
for (const li of lis) {
const tip = li.querySelector('span.tip');
if (tip && (tip.textContent.includes('放送开始') ||
tip.textContent.includes('上映年度') ||
tip.textContent.includes('发售日') ||
tip.textContent.includes('开始') ||
tip.textContent.includes('首播'))) {
dateText = li.textContent.replace(tip.textContent, '').trim();
break;
}
}
if (dateText) break;
}
if (!dateText) {
throw new Error('未找到开播日期');
}
const dateMatch = dateText.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
if (!dateMatch) {
throw new Error('日期格式不正确');
}
const [, year, month, day] = dateMatch;
const formattedDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
dateInput.value = formattedDate;
alert('成功识别开播日期!');
} catch (error) {
alert('识别开播日期失败: ' + error.message);
} finally {
autoDateBtn.textContent = '自动识别';
autoDateBtn.disabled = false;
}
});
const cycleSelectDiv = createInputGroup(
'选择更新周期:',
``
);
const cycleSelect = cycleSelectDiv.querySelector('select');
cycleSelect.addEventListener('focus', (e) => e.target.style.cssText += focusInputStyle);
cycleSelect.addEventListener('blur', (e) => e.target.style.cssText = inputStyle);
const dailyMultiDiv = createInputGroup(
'一天几更(默认为1):',
``
);
dailyMultiDiv.querySelector('input').addEventListener('focus', (e) => e.target.style.cssText += focusInputStyle);
dailyMultiDiv.querySelector('input').addEventListener('blur', (e) => e.target.style.cssText = inputStyle);
const initialEpDiv = createInputGroup(
'首播集数(留空则不启用):',
``
);
initialEpDiv.querySelector('input').addEventListener('focus', (e) => e.target.style.cssText += focusInputStyle);
initialEpDiv.querySelector('input').addEventListener('blur', (e) => e.target.style.cssText = inputStyle);
const weekdayDiv = document.createElement('div');
weekdayDiv.style.cssText = "margin-top:10px; display: none;";
weekdayDiv.innerHTML = `
选择星期(至少选2个):
`;
const monthlyDateDiv = document.createElement('div');
monthlyDateDiv.style.cssText = "margin-top:10px; display: none;";
monthlyDateDiv.innerHTML = `
选择每月更新日期:
提示:输入1-31的数字或"月底","月底"代表每月最后一天,多个用逗号分隔(如: 1,15,月底)
`;
cycleSelect.addEventListener('change', function () {
weekdayDiv.style.display = cycleSelect.value === "特定星期更" ? "block" : "none";
monthlyDateDiv.style.display = cycleSelect.value === "月更" ? "block" : "none";
});
const btnDiv = document.createElement('div');
btnDiv.style.cssText = "text-align:center; margin-top:20px;";
const confirmBtn = document.createElement('button');
confirmBtn.textContent = "确定";
const confirmBg = darkMode ? '#f6a1b2' : '#f6a1b2';
const cancelBg = darkMode ? '#333' : '#d1d1d1';
const cancelText = darkMode ? '#e0e0e0' : '#333';
confirmBtn.style.cssText = `
background-color: ${confirmBg} !important;
color: white !important;
border: none;
padding: 10px 25px;
border-radius: 20px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.2s ease;
margin-right: 15px;
`;
confirmBtn.addEventListener('mouseenter', () => {
confirmBtn.style.backgroundColor = darkMode ? '#f491a1' : '#f491a1';
confirmBtn.style.transform = 'translateY(-1px)';
confirmBtn.style.boxShadow = '0 3px 6px rgba(0,0,0,0.15)';
});
confirmBtn.addEventListener('mouseleave', () => {
confirmBtn.style.backgroundColor = darkMode ? '#f6a1b2' : '#f6a1b2';
confirmBtn.style.transform = 'translateY(0)';
confirmBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
const cancelBtn = document.createElement('button');
cancelBtn.textContent = "取消";
cancelBtn.style.cssText = `
background-color: ${cancelBg} !important;
color: ${cancelText} !important;
border: none;
padding: 10px 25px;
border-radius: 20px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.2s ease;
`;
cancelBtn.addEventListener('mouseenter', () => {
cancelBtn.style.backgroundColor = darkMode ? '#444' : '#c1c1c1';
cancelBtn.style.transform = 'translateY(-1px)';
cancelBtn.style.boxShadow = '0 3px 6px rgba(0,0,0,0.15)';
});
cancelBtn.addEventListener('mouseleave', () => {
cancelBtn.style.backgroundColor = darkMode ? '#333' : '#d1d1d1';
cancelBtn.style.transform = 'translateY(0)';
cancelBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
btnDiv.appendChild(confirmBtn);
btnDiv.appendChild(cancelBtn);
modal.append(
title,
startEpDiv,
dateInputDiv,
cycleSelectDiv,
dailyMultiDiv,
initialEpDiv,
weekdayDiv,
monthlyDateDiv,
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;
}
} else if (cycleVal === "月更") {
const monthlyDateInput = monthlyDateDiv.querySelector("input[type='text']");
const inputValue = monthlyDateInput.value.trim();
extra = [];
if (!inputValue) {
alert("请输入每月更新日期!");
return;
}
const dates = inputValue.split(/[,,]/).map(d => d.trim()).filter(d => d);
for (const date of dates) {
if (date === "月底" || date === "last") {
extra.push("last");
} else {
const dateNum = parseInt(date, 10);
if (isNaN(dateNum) || dateNum < 1 || dateNum > 31) {
alert("请输入有效的日期(1-31或'月底')!");
return;
}
extra.push(dateNum);
}
}
if (extra.length === 0) {
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);
const initialEpInput = initialEpDiv.querySelector("input[type='number']");
const initialEpVal = initialEpInput.value.trim();
const initialEpisodes = initialEpVal === "" ? 0 : parseInt(initialEpVal, 10);
if (isNaN(initialEpisodes) || initialEpisodes < 0) {
alert("请输入有效的首播集数(非负整数)");
return;
}
onConfirm(dVal, cycleVal, extra, dailyMulti, startEp, initialEpisodes);
document.body.removeChild(overlay);
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(overlay);
});
}
function parseDateUTC(dateStr) {
const parts = dateStr.split('-');
return new Date(Date.UTC(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10)));
}
function formatDate(date) {
const y = date.getUTCFullYear();
const m = String(date.getUTCMonth() + 1).padStart(2, '0');
const d = String(date.getUTCDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
function addDays(date, n) {
let d = new Date(date);
d.setUTCDate(d.getUTCDate() + 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.setUTCDate(d.getUTCDate() + 1);
const day = d.getUTCDay();
if (day !== 0 && day !== 6) n--;
}
return d;
}
function addMonths(date, n) {
let d = new Date(date);
const originalDate = d.getUTCDate();
d.setUTCMonth(d.getUTCMonth() + n);
if (d.getUTCDate() !== originalDate) {
d.setUTCDate(0);
}
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 addMonths(date, offset);
if (cycle === "当天更完") return date;
return addDays(date, offset);
}
function getWeekday(d) {
let wd = d.getUTCDay();
return wd === 0 ? 7 : wd;
}
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 = new Date(startDate);
let scheduledDate = new Date(d);
let episodeCount = 0;
while (episodeCount < offset) {
scheduledDate = getNextScheduledDate(scheduledDate, selectedDays);
episodeCount++;
}
return scheduledDate;
}
function getDaysInMonth(year, month) {
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
}
function computeMonthlyDate(startDate, monthlyDay, offset) {
let d = new Date(startDate);
if (monthlyDay === "last") {
d.setUTCMonth(d.getUTCMonth() + offset);
d.setUTCDate(0);
} else {
d.setUTCMonth(d.getUTCMonth() + offset);
const daysInMonth = getDaysInMonth(d.getUTCFullYear(), d.getUTCMonth());
d.setUTCDate(Math.min(monthlyDay, daysInMonth));
}
return d;
}
function computeMonthlyDates(startDate, monthlyDays, totalEpisodes) {
const dates = [];
let currentDate = new Date(startDate);
dates.push(new Date(currentDate));
let monthOffset = 0;
let dayIndex = 0;
for (let i = 1; i < totalEpisodes; i++) {
const day = monthlyDays[dayIndex];
let d = new Date(startDate);
d.setUTCMonth(d.getUTCMonth() + monthOffset);
if (day === "last") {
d.setUTCDate(0);
} else {
const daysInMonth = getDaysInMonth(d.getUTCFullYear(), d.getUTCMonth());
d.setUTCDate(Math.min(day, daysInMonth));
}
if (dayIndex === monthlyDays.length - 1) {
dayIndex = 0;
monthOffset++;
} else {
const nextDay = monthlyDays[dayIndex + 1];
let nextDate = new Date(startDate);
nextDate.setUTCMonth(nextDate.getUTCMonth() + monthOffset);
if (nextDay === "last") {
nextDate.setUTCDate(0);
} else {
const daysInMonth = getDaysInMonth(nextDate.getUTCFullYear(), nextDate.getUTCMonth());
nextDate.setUTCDate(Math.min(nextDay, daysInMonth));
}
if (nextDate <= d) {
dayIndex = 0;
monthOffset++;
d = new Date(startDate);
d.setUTCMonth(d.getUTCMonth() + monthOffset);
const currentDay = monthlyDays[0];
if (currentDay === "last") {
d.setUTCDate(0);
} else {
const daysInMonth = getDaysInMonth(d.getUTCFullYear(), d.getUTCMonth());
d.setUTCDate(Math.min(currentDay, daysInMonth));
}
} else {
dayIndex++;
}
}
dates.push(d);
}
return dates;
}
function processData(startDate, cycle, extra, dailyMulti, startEp, initialEpisodes) {
let ta = document.querySelector('textarea[name="ep_list"]');
if (!ta) {
ta = document.querySelector('textarea[name="eplist"]');
}
if (!ta) {
alert("未找到目标文本域!");
return;
}
if (!ta.value.trim()) {
alert("文本域为空,请先点击'批量生成'按钮生成章节列表!");
return;
}
const lines = ta.value.split(/\r?\n/);
let started = (startEp === null);
let uniqueCount = 0;
const mapping = {};
const totalEpisodes = lines.filter(line => line.trim()).length;
const monthlyDatesCache = cycle === "月更" && Array.isArray(extra) ? computeMonthlyDates(startDate, extra, totalEpisodes) : null;
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 {
if (uniqueCount < initialEpisodes) {
effectiveOffset = 0;
} else {
effectiveOffset = Math.floor((uniqueCount - initialEpisodes) / dailyMulti) + (initialEpisodes > 0 ? 1 : 0);
}
mapping[chapter] = effectiveOffset;
uniqueCount++;
}
let newDate;
if (cycle === "特定星期更" && Array.isArray(extra)) {
newDate = computeSpecificDate(new Date(startDate), extra, effectiveOffset);
} else if (cycle === "月更" && Array.isArray(extra) && monthlyDatesCache) {
newDate = monthlyDatesCache[effectiveOffset] || new Date(startDate);
} else if (cycle === "月更" && (extra === "last" || typeof extra === "number")) {
newDate = computeMonthlyDate(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"], textarea[name="eplist"]', function (el) {
const colors = getModeColor();
const newDiv = document.createElement('div');
newDiv.style.cssText = 'display: block; margin-bottom: 5px;';
const link = document.createElement('a');
link.href = "#";
link.title = "批量添加章节时间";
link.style.cssText = `
display: inline-flex;
align-items: center;
text-decoration: none;
color: ${colors.calendar};
font-size: 14px;
font-weight: bold;
cursor: pointer;
`;
link.appendChild(createCalendarIcon());
link.appendChild(document.createTextNode("批量添加时间"));
newDiv.appendChild(link);
el.parentNode.insertBefore(newDiv, el);
link.addEventListener('click', e => {
e.preventDefault();
createModal((dateVal, cycleVal, extra, dailyMulti, startEp, initialEpisodes) => {
if (!dateVal) {
alert("请选择日期!");
return;
}
const d = parseDateUTC(dateVal);
if (isNaN(d.getTime())) {
alert("无效的日期!");
return;
}
if (!cycleVal) {
alert("请选择更新周期!");
return;
}
processData(d, cycleVal, extra, dailyMulti, startEp, initialEpisodes);
});
});
});
}
function injectDateButtonForEditPage() {
waitForEl('input[name="airdate"]', function (airdateInput) {
const colors = getModeColor();
const parentTd = airdateInput.parentNode;
const dateButton = document.createElement('button');
dateButton.type = 'button';
dateButton.style.cssText = `
margin-left: 10px;
padding: 2px 8px;
background-color: #f6a1b2;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 12px;
font-weight: bold;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.2s ease;
`;
dateButton.addEventListener('mouseenter', () => {
dateButton.style.backgroundColor = '#f491a1';
dateButton.style.transform = 'translateY(-1px)';
dateButton.style.boxShadow = '0 3px 6px rgba(0,0,0,0.15)';
});
dateButton.addEventListener('mouseleave', () => {
dateButton.style.backgroundColor = '#f6a1b2';
dateButton.style.transform = 'translateY(0)';
dateButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
dateButton.textContent = '选择日期';
const calendarIcon = createCalendarIcon();
calendarIcon.setAttribute('width', '14');
calendarIcon.setAttribute('height', '14');
dateButton.insertBefore(calendarIcon, dateButton.firstChild);
parentTd.appendChild(dateButton);
dateButton.addEventListener('click', () => {
const colors = getModeColor();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
`;
const modal = document.createElement('div');
modal.style.cssText = `
background: ${colors.background};
padding: 20px;
border-radius: 8px;
min-width: 300px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
color: ${colors.text};
`;
const title = document.createElement('h4');
title.textContent = '选择放送日期';
title.style.cssText = `margin-top: 0; margin-bottom: 15px; color: ${colors.text};`;
modal.appendChild(title);
const dateInput = document.createElement('input');
dateInput.type = 'date';
dateInput.style.cssText = `width: 100%; padding: 8px; box-sizing: border-box; margin-bottom: 15px; background: ${isDarkMode() ? '#333' : '#fff'}; color: ${colors.text}; border: 1px solid ${colors.border};`;
modal.appendChild(dateInput);
const btnDiv = document.createElement('div');
btnDiv.style.cssText = 'text-align: center;';
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '确定';
confirmBtn.style.cssText = `
background-color: #f6a1b2;
color: white;
border: none;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
margin-right: 10px;
font-weight: bold;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.2s ease;
`;
confirmBtn.addEventListener('mouseenter', () => {
confirmBtn.style.backgroundColor = '#f491a1';
confirmBtn.style.transform = 'translateY(-1px)';
confirmBtn.style.boxShadow = '0 3px 6px rgba(0,0,0,0.15)';
});
confirmBtn.addEventListener('mouseleave', () => {
confirmBtn.style.backgroundColor = '#f6a1b2';
confirmBtn.style.transform = 'translateY(0)';
confirmBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = `
background-color: #f0f0f0;
color: #333;
border: 1px solid #ddd;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
font-weight: bold;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transition: all 0.2s ease;
`;
cancelBtn.addEventListener('mouseenter', () => {
cancelBtn.style.backgroundColor = '#e8e8e8';
cancelBtn.style.transform = 'translateY(-1px)';
cancelBtn.style.boxShadow = '0 3px 6px rgba(0,0,0,0.1)';
});
cancelBtn.addEventListener('mouseleave', () => {
cancelBtn.style.backgroundColor = '#f0f0f0';
cancelBtn.style.transform = 'translateY(0)';
cancelBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
});
btnDiv.appendChild(confirmBtn);
btnDiv.appendChild(cancelBtn);
modal.appendChild(btnDiv);
overlay.appendChild(modal);
document.body.appendChild(overlay);
confirmBtn.addEventListener('click', () => {
if (dateInput.value) {
airdateInput.value = dateInput.value;
}
document.body.removeChild(overlay);
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(overlay);
});
});
});
}
function injectButtonForCreatePage() {
waitForEl('a#number_btn.chiiBtn.rr', function (numberBtn) {
const colors = getModeColor();
const newDiv = document.createElement('div');
newDiv.style.cssText = 'display: inline-block; margin-left: 10px;';
const link = document.createElement('a');
link.href = "#";
link.title = "批量添加章节时间";
link.style.cssText = `
display: inline-flex;
align-items: center;
text-decoration: none;
color: ${colors.calendar};
font-size: 14px;
font-weight: bold;
cursor: pointer;
`;
link.appendChild(createCalendarIcon());
link.appendChild(document.createTextNode("批量添加时间"));
newDiv.appendChild(link);
numberBtn.parentNode.insertBefore(newDiv, numberBtn.nextSibling);
link.addEventListener('click', e => {
e.preventDefault();
createModal((dateVal, cycleVal, extra, dailyMulti, startEp, initialEpisodes) => {
if (!dateVal) {
alert("请选择日期!");
return;
}
const d = parseDateUTC(dateVal);
if (isNaN(d.getTime())) {
alert("无效的日期!");
return;
}
if (!cycleVal) {
alert("请选择更新周期!");
return;
}
processData(d, cycleVal, extra, dailyMulti, startEp, initialEpisodes);
});
});
});
waitForEl('input[name="airdate"]', function (airdateInput) {
const colors = getModeColor();
const parentTd = airdateInput.parentNode;
const dateButton = document.createElement('button');
dateButton.type = 'button';
dateButton.style.cssText = `
margin-left: 10px;
padding: 2px 8px;
background-color: #f6a1b2;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 12px;
font-weight: bold;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.2s ease;
`;
dateButton.addEventListener('mouseenter', () => {
dateButton.style.backgroundColor = '#f491a1';
dateButton.style.transform = 'translateY(-1px)';
dateButton.style.boxShadow = '0 3px 6px rgba(0,0,0,0.15)';
});
dateButton.addEventListener('mouseleave', () => {
dateButton.style.backgroundColor = '#f6a1b2';
dateButton.style.transform = 'translateY(0)';
dateButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
dateButton.textContent = '选择日期';
const calendarIcon = createCalendarIcon();
calendarIcon.setAttribute('width', '14');
calendarIcon.setAttribute('height', '14');
dateButton.insertBefore(calendarIcon, dateButton.firstChild);
parentTd.appendChild(dateButton);
dateButton.addEventListener('click', () => {
const colors = getModeColor();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
`;
const modal = document.createElement('div');
modal.style.cssText = `
background: ${colors.background};
padding: 20px;
border-radius: 8px;
min-width: 300px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
color: ${colors.text};
`;
const title = document.createElement('h4');
title.textContent = '选择放送日期';
title.style.cssText = `margin-top: 0; margin-bottom: 15px; color: ${colors.text};`;
modal.appendChild(title);
const dateInput = document.createElement('input');
dateInput.type = 'date';
dateInput.style.cssText = `width: 100%; padding: 8px; box-sizing: border-box; margin-bottom: 15px; background: ${isDarkMode() ? '#333' : '#fff'}; color: ${colors.text}; border: 1px solid ${colors.border};`;
modal.appendChild(dateInput);
const btnDiv = document.createElement('div');
btnDiv.style.cssText = 'text-align: center;';
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '确定';
confirmBtn.style.cssText = `
background-color: ${colors.secondary};
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = `
background-color: ${isDarkMode() ? '#444' : '#d1d1d1'};
color: ${colors.text};
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
`;
btnDiv.appendChild(confirmBtn);
btnDiv.appendChild(cancelBtn);
modal.appendChild(btnDiv);
overlay.appendChild(modal);
document.body.appendChild(overlay);
confirmBtn.addEventListener('click', () => {
if (dateInput.value) {
airdateInput.value = dateInput.value;
}
document.body.removeChild(overlay);
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(overlay);
});
});
});
}
function isCreatePage() {
return /\/ep\/create/.test(window.location.href);
}
function isEditPage() {
return /\/ep\/\d+\/edit/.test(window.location.href);
}
if (isCreatePage()) {
injectButtonForCreatePage();
} else if (isEditPage()) {
injectDateButtonForEditPage();
} else {
injectButton();
}
})();