mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-10 22:01:16 +00:00
feat: add channel categories and improved creator detection
- Add category field to TelegramChannel (filterable tag like STL, PDF, D&D) - Category column in channels table with edit via dropdown menu - Improved creator extraction: filename patterns + channel title fallback - extractCreatorFromChannelTitle strips [Completed], (Paid), emoji, etc. - Fix ArchiveType in PackageListItem and PackageRow for new types - Add Prisma migration for category column
This commit is contained in:
@@ -1,21 +1,71 @@
|
||||
/**
|
||||
* Extract a creator name from common archive file naming patterns.
|
||||
*
|
||||
* Priority in the worker: topic name > filename extraction.
|
||||
* This is the fallback when no forum topic name is available.
|
||||
* Priority in the worker: topic name > filename extraction > channel title > null.
|
||||
*
|
||||
* Patterns handled (split on ` - `):
|
||||
* Patterns handled:
|
||||
* "Mammoth Factory - 2026-01.zip" → "Mammoth Factory"
|
||||
* "Artist Name - Pack Title.part01.rar" → "Artist Name"
|
||||
* "ArtistName_PackTitle.zip" → null (ambiguous)
|
||||
* "some_random_file.zip" → null
|
||||
*/
|
||||
export function extractCreatorFromFileName(fileName: string): string | null {
|
||||
// Strip archive extensions (.zip, .rar, .part01.rar, .z01, etc.)
|
||||
const bare = fileName.replace(/(\.(part\d+\.rar|z\d{2}|zip|rar))+$/i, "");
|
||||
// Strip archive/document extensions
|
||||
const bare = fileName.replace(
|
||||
/(\.(part\d+\.rar|z\d{2}|zip|rar|7z|pdf|stl|obj|3mf|step|stp|blend|gcode|svg|dxf|ai|eps|psd))+$/i,
|
||||
""
|
||||
);
|
||||
|
||||
const idx = bare.indexOf(" - ");
|
||||
if (idx <= 0) return null;
|
||||
// Pattern 1: "Creator - Title" (most common)
|
||||
const dashIdx = bare.indexOf(" - ");
|
||||
if (dashIdx > 0) {
|
||||
const creator = bare.slice(0, dashIdx).trim();
|
||||
if (creator.length > 1) return creator;
|
||||
}
|
||||
|
||||
const creator = bare.slice(0, idx).trim();
|
||||
return creator.length > 0 ? creator : null;
|
||||
// Pattern 2: "Creator_Title" with underscores where first segment looks like a name
|
||||
// Only match if the first segment has a space or capital letter pattern suggesting a name
|
||||
const underscoreIdx = bare.indexOf("_");
|
||||
if (underscoreIdx > 2) {
|
||||
const candidate = bare.slice(0, underscoreIdx).trim();
|
||||
// Accept if it contains a space (multi-word) or starts with upper + has lower (proper name)
|
||||
if (candidate.includes(" ") || /^[A-Z][a-z]/.test(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a creator name from a Telegram channel title.
|
||||
* Strips common suffixes like "[Completed]", "(Paid)", dates, etc.
|
||||
*/
|
||||
export function extractCreatorFromChannelTitle(title: string): string | null {
|
||||
let clean = title
|
||||
// Remove bracketed suffixes: [Completed], [Open], [Closed], etc.
|
||||
.replace(/\s*\[.*?\]\s*/g, " ")
|
||||
// Remove parenthesized suffixes: (Paid), (partial upload...), etc.
|
||||
.replace(/\s*\(.*?\)\s*/g, " ")
|
||||
// Remove common emoji
|
||||
.replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}]/gu, "")
|
||||
.trim();
|
||||
|
||||
// If there's a " - " separator, take the first part as creator
|
||||
const dashIdx = clean.indexOf(" - ");
|
||||
if (dashIdx > 0) {
|
||||
clean = clean.slice(0, dashIdx).trim();
|
||||
}
|
||||
|
||||
// Too generic or too short
|
||||
if (clean.length < 2) return null;
|
||||
|
||||
// Skip overly generic channel names
|
||||
const generic = [
|
||||
"3d printing", "stl", "free stl", "stl zone", "stl forest", "stl all",
|
||||
"marvel stl", "dc stl", "star wars stl", "pokemon stl",
|
||||
];
|
||||
if (generic.includes(clean.toLowerCase())) return null;
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import { isChatForum, getForumTopicList, getTopicMessages } from "./tdlib/topics
|
||||
import { matchPreviewToArchive } from "./preview/match.js";
|
||||
import { groupArchiveSets } from "./archive/multipart.js";
|
||||
import type { ArchiveSet } from "./archive/multipart.js";
|
||||
import { extractCreatorFromFileName } from "./archive/creator.js";
|
||||
import { extractCreatorFromFileName, extractCreatorFromChannelTitle } from "./archive/creator.js";
|
||||
import { hashParts } from "./archive/hash.js";
|
||||
import { readZipCentralDirectory } from "./archive/zip-reader.js";
|
||||
import { readRarContents } from "./archive/rar-reader.js";
|
||||
@@ -968,8 +968,11 @@ async function processOneArchiveSet(
|
||||
previewMsgId = matchedPhoto.id;
|
||||
}
|
||||
|
||||
// ── Resolve creator: topic name > filename extraction > null ──
|
||||
const creator = topicCreator ?? extractCreatorFromFileName(archiveName) ?? null;
|
||||
// ── Resolve creator: topic name > filename extraction > channel title > null ──
|
||||
const creator = topicCreator
|
||||
?? extractCreatorFromFileName(archiveName)
|
||||
?? extractCreatorFromChannelTitle(channelTitle)
|
||||
?? null;
|
||||
|
||||
// ── Indexing ──
|
||||
await updateRunActivity(runId, {
|
||||
|
||||
Reference in New Issue
Block a user