Adds a column showing comic type (Manga/Manhwa/Manhua).
当前为
// ==UserScript==
// @name Comick Comic Type Column
// @namespace https://github.com/GooglyBlox
// @version 1.0
// @description Adds a column showing comic type (Manga/Manhwa/Manhua).
// @author GooglyBlox
// @match https://comick.io/user/*/list*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let comicCountryMap = new Map();
let isProcessing = false;
function extractUserIdFromUrl() {
const urlParts = window.location.pathname.split('/');
const userIndex = urlParts.indexOf('user');
return userIndex !== -1 && userIndex + 1 < urlParts.length ? urlParts[userIndex + 1] : null;
}
async function fetchUserFollows(userId) {
try {
const response = await fetch(`https://api.comick.io/user/${userId}/follows`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error('Failed to fetch user follows:', error);
return [];
}
}
function getComicTypeFromCountry(country) {
switch (country) {
case 'kr':
case 'gb':
return 'Manhwa';
case 'cn':
return 'Manhua';
case 'jp':
return 'Manga';
default:
return 'Unknown';
}
}
function buildComicCountryMap(followsData) {
comicCountryMap.clear();
followsData.forEach(item => {
if (item.md_comics) {
const comic = item.md_comics;
const key = comic.slug || comic.title;
if (key) {
comicCountryMap.set(key, comic.country);
}
}
});
}
function addTypeColumnHeader() {
const headerRow = document.querySelector('.flex.w-full.items-center.min-w-0:first-child');
if (!headerRow || headerRow.querySelector('[data-comic-type-header]')) return;
const typeHeader = document.createElement('div');
typeHeader.className = 'text-center pl-3 flex items-center h-12 w-20 lg:w-24 font-semibold flex-none text-sm';
typeHeader.setAttribute('data-comic-type-header', 'true');
typeHeader.innerHTML = '<div class="w-full text-center">Type</div>';
const lastReadColumn = headerRow.children[headerRow.children.length - 3];
headerRow.insertBefore(typeHeader, lastReadColumn);
}
function addTypeColumnToRow(row) {
if (row.querySelector('[data-comic-type-cell]')) return;
const titleLink = row.querySelector('a[href*="/comic/"]');
if (!titleLink) return;
const href = titleLink.getAttribute('href');
const slug = href.split('/comic/')[1];
const country = comicCountryMap.get(slug);
const comicType = country ? getComicTypeFromCountry(country) : 'Unknown';
const typeCell = document.createElement('div');
typeCell.className = 'flex justify-center text-sm md:text-base w-20 lg:w-24 flex-none';
typeCell.setAttribute('data-comic-type-cell', 'true');
typeCell.innerHTML = `<div class="w-full text-center">${comicType}</div>`;
const lastReadColumn = row.children[row.children.length - 3];
row.insertBefore(typeCell, lastReadColumn);
}
function processTable() {
if (isProcessing) return;
const rows = document.querySelectorAll('.flex.w-full.items-center.min-w-0');
if (rows.length === 0) return;
addTypeColumnHeader();
rows.forEach((row, index) => {
if (index === 0) return;
addTypeColumnToRow(row);
});
}
async function initializeScript() {
if (isProcessing) return;
isProcessing = true;
const userId = extractUserIdFromUrl();
if (!userId) {
isProcessing = false;
return;
}
const followsData = await fetchUserFollows(userId);
buildComicCountryMap(followsData);
processTable();
isProcessing = false;
}
function observeChanges() {
const observer = new MutationObserver((mutations) => {
const hasRelevantChanges = mutations.some(mutation =>
mutation.type === 'childList' &&
Array.from(mutation.addedNodes).some(node =>
node.nodeType === 1 &&
(node.matches && node.matches('.flex.w-full.items-center.min-w-0') ||
node.querySelector && node.querySelector('.flex.w-full.items-center.min-w-0'))
)
);
if (hasRelevantChanges) {
setTimeout(processTable, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function handleNavigation() {
let currentUrl = window.location.href;
const checkForNavigation = () => {
if (window.location.href !== currentUrl) {
currentUrl = window.location.href;
if (currentUrl.includes('/user/') && currentUrl.includes('/list')) {
setTimeout(initializeScript, 500);
}
}
};
setInterval(checkForNavigation, 1000);
}
setTimeout(initializeScript, 1000);
observeChanges();
handleNavigation();
})();