fix: improve title and episode parsing regex for sub-episodes, seasons, and clean formatting

main
I Luk Kim 3 weeks ago
parent 0c1013d94f
commit 224f28042f

@ -7,22 +7,36 @@ export interface ParsedTitle {
export function parseTitleAndEpisode(rawTitle: string | null): ParsedTitle {
if (!rawTitle) return { title: '', episode: null, fullCleanTitle: '' };
// Remove site name suffixes like "- 마나토키 330", "- 뉴토끼", "- newtoki"
let clean = rawTitle
.replace(/\s*[-|_]\s*(마나토키|뉴토끼|newtoki|manatoki|웹툰|만화).*$/i, '')
let clean = rawTitle.trim();
// 1. Remove site name / brand suffixes & domain noise
// Matches: "- 마나토키 330", "> 뉴토끼 340", ":: manatoki 100", "| 뉴토끼", " - 웹툰", " - 만화", etc.
clean = clean
.replace(/\s*[-|_|>|:|=|~|/|\\]+\s*(마나토키|뉴토끼|newtoki|manatoki|웹툰|만화|toki|토끼)\s*\d*\s*$/i, '')
.replace(/\s*[-|_|>|:|=|~|/|\\]+\s*(https?:\/\/)?[\w.-]+\.(com|org|net|xyz|me|info|live)\s*$/i, '')
.replace(/\s*[\(\[]\s*(마나토키|뉴토끼|newtoki|manatoki|웹툰|만화)\s*\d*\s*[\)\]]\s*$/i, '')
.replace(/\s+(마나토키|뉴토끼|newtoki|manatoki)\s*\d+\s*$/i, '')
.trim();
// 1. Match explicit episode format like "150화", "150.5화", "EP.15"
const epMatch = clean.match(/(\d+(?:\.\d+)?\s*화|ep\.?\s*\d+)/i);
// 2. Main Episode Pattern:
// Group 1 (optional season/part): (시즌\s*\d+|[1-9]기|[1-9]부|[Pp]art\s*\d+)
// Group 2 (episode number with optional sub-part or comma): (\d{1,4}(?:,\d{3})*(?:[._-]\d+)?)
// Group 3 (unit): (화|회|화차|강|話)
const mainEpRegex = /(?:((?:시즌\s*\d+|[1-9]기|[1-9]부|[Pp]art\s*\d+))\s+)?(?:제\s*)?(\d{1,4}(?:,\d{3})*(?:[._-]\d+)?)\s*(화|회|화차|강|話)/i;
const epMatch = clean.match(mainEpRegex);
if (epMatch) {
let episode = epMatch[1].replace(/\s+/g, '');
if (!episode.endsWith('화') && !episode.toLowerCase().startsWith('ep')) {
episode += '화';
}
// Remove the matched episode part from the title
let title = clean.replace(epMatch[0], '').trim();
// Clean up trailing dashes/colons/dots left over
title = title.replace(/[-|_:]\s*$/, '').trim();
const season = epMatch[1] ? epMatch[1].trim() + ' ' : '';
const num = epMatch[2].replace(/\s+/g, '');
const unit = epMatch[3] || '화';
const fullEpString = epMatch[0]; // e.g. "2기 150화" or "150-1화"
const episode = `${season}${num}${unit}`;
// Remove the episode portion from clean to extract remaining title
let title = clean.replace(fullEpString, ' ');
title = cleanUpTitleString(title);
return {
title: title || clean,
@ -31,12 +45,33 @@ export function parseTitleAndEpisode(rawTitle: string | null): ParsedTitle {
};
}
// 2. Match trailing numbers like "제목 150"
const numMatch = clean.match(/(\d+(?:\.\d+)?)\s*$/);
if (numMatch) {
const episode = `${numMatch[1]}`;
let title = clean.slice(0, numMatch.index).trim();
title = title.replace(/[-|_:]\s*$/, '').trim();
// 3. "EP.150", "Ep 150", "#150", "No.150" pattern
const epPrefixRegex = /(?:ep\.?|#|no\.?)\s*(\d+(?:[.-_]\d+)?)/i;
const prefixMatch = clean.match(epPrefixRegex);
if (prefixMatch) {
const episode = `EP.${prefixMatch[1]}`;
let title = clean.replace(prefixMatch[0], ' ');
title = cleanUpTitleString(title);
return {
title: title || clean,
episode,
fullCleanTitle: clean,
};
}
// 4. Trailing number without '화' (e.g. "원피스 1100", "원피스 1100-1")
const trailingNumRegex = /(?:((?:시즌\s*\d+|[1-9]기|[1-9]부|[Pp]art\s*\d+))\s+)?(\d{1,4}(?:[.-_]\d+)?)\s*$/i;
const trailingMatch = clean.match(trailingNumRegex);
if (trailingMatch) {
const season = trailingMatch[1] ? trailingMatch[1].trim() + ' ' : '';
const num = trailingMatch[2];
const episode = `${season}${num}`;
let title = clean.slice(0, trailingMatch.index).trim();
title = cleanUpTitleString(title);
return {
title: title || clean,
@ -47,3 +82,12 @@ export function parseTitleAndEpisode(rawTitle: string | null): ParsedTitle {
return { title: clean, episode: null, fullCleanTitle: clean };
}
function cleanUpTitleString(str: string): string {
let s = str.replace(/\s+/g, ' ').trim();
s = s
.replace(/^[\s\-_|:;=><~,/\\\\]+/, '')
.replace(/[\s\-_|:;=><~,/\\\\]+$/, '')
.trim();
return s;
}

Loading…
Cancel
Save