From 58965716bfc34eb2a7dada8dd4a3f93da6fe41dc Mon Sep 17 00:00:00 2001 From: "DESKTOP-KSVGT20\\shkim" Date: Fri, 1 May 2026 17:53:48 +0900 Subject: [PATCH] Filter Suno library detection to visible rows --- chatgpt-suno-tampermonkey.user.js | 70 +++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/chatgpt-suno-tampermonkey.user.js b/chatgpt-suno-tampermonkey.user.js index 3de1c25..b3e61bd 100644 --- a/chatgpt-suno-tampermonkey.user.js +++ b/chatgpt-suno-tampermonkey.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ChatGPT to Suno Prompt Copier // @namespace local.suno-helper -// @version 0.4.0 +// @version 0.4.1 // @description Copy structured ChatGPT song prompt versions into Suno fields. // @author local // @match https://chatgpt.com/* @@ -620,6 +620,60 @@ return target; } + function normalizeMatchText(value) { + return String(value || "") + .toLowerCase() + .replace(/\s+/g, " ") + .trim(); + } + + function getVisibleLibraryRows() { + const rows = []; + const seen = new Set(); + const mediaNodes = [...document.querySelectorAll("img, [style*='background-image']")]; + for (const node of mediaNodes) { + const rect = node.getBoundingClientRect(); + if (rect.width < 30 || rect.height < 30 || rect.bottom < 0 || rect.top > window.innerHeight) continue; + let current = node; + for (let depth = 0; depth < 8 && current; depth += 1) { + const text = (current.innerText || current.textContent || "").trim(); + const currentRect = current.getBoundingClientRect(); + if ( + text.length > 20 && + text.length < 1800 && + currentRect.width > 250 && + currentRect.height > 45 && + /v\d|BPM|Korean|rock|pop|metal|랩|가사|female|male|vocal|Suno/i.test(text) + ) { + const key = normalizeMatchText(text); + if (!seen.has(key)) { + seen.add(key); + rows.push({ + text, + normalized: key, + }); + } + break; + } + current = current.parentElement; + } + } + return rows; + } + + function trackMatchesVisibleRows(track, rows) { + if (!rows.length) return true; + const title = normalizeMatchText(track.title); + const style = normalizeMatchText(track.style || track.lyrics || ""); + if (!title || title.length > 160) return false; + return rows.some((row) => { + if (!row.normalized.includes(title)) return false; + if (!style) return true; + const styleNeedle = style.slice(0, Math.min(42, style.length)); + return styleNeedle.length < 16 || row.normalized.includes(styleNeedle); + }); + } + function scanSunoLibraryTracks() { const sources = readSunoDataSources(); const objects = sources.flatMap((item) => flattenObjects(item)); @@ -651,17 +705,18 @@ }); } + const visibleRows = getVisibleLibraryRows(); return [...tracks.values()] .filter((track) => track.id || track.audioUrl || track.imageUrl) + .filter((track) => trackMatchesVisibleRows(track, visibleRows)) .sort((a, b) => String(a.title).localeCompare(String(b.title), "ko")); } function visibleSunoRowsHint() { - const titles = [...document.querySelectorAll("a, h1, h2, h3, [role='button'], div, span")] - .map((node) => (node.innerText || node.textContent || "").trim()) - .filter((text) => text && text.length < 80 && !/^(Songs|Playlists|Workspaces|Studio Projects|Voices|Cover Art|Hooks|History|Search|Newest|Liked|Public|Uploads)$/i.test(text)); - const likelyTitles = titles.filter((text, index) => titles.indexOf(text) === index).slice(0, 5); - return likelyTitles; + return getVisibleLibraryRows() + .map((row) => row.text.split("\n").map((line) => line.trim()).filter(Boolean)[0]) + .filter((text, index, list) => text && list.indexOf(text) === index) + .slice(0, 5); } function findCurrentSunoTrack() { @@ -893,7 +948,8 @@ updateSummary(); const sample = libraryTracks.slice(0, 3).map((track) => track.title || track.id).join(", "); if (libraryTracks.length) { - setStatus(body, `${libraryTracks.length}곡 감지: ${sample}`); + const visibleCount = getVisibleLibraryRows().length; + setStatus(body, `${libraryTracks.length}곡 감지: ${sample} / 화면 행 ${visibleCount}개 기준`); return; } const visibleHints = visibleSunoRowsHint();