Fix Suno title badges and add stop control
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name ChatGPT to Suno Prompt Copier
|
||||
// @namespace local.suno-helper
|
||||
// @version 0.9.0
|
||||
// @version 0.9.1
|
||||
// @description Copy structured ChatGPT song prompt versions into Suno fields.
|
||||
// @author local
|
||||
// @match https://chatgpt.com/*
|
||||
@@ -30,7 +30,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const SCRIPT_VERSION = "0.9.0";
|
||||
const SCRIPT_VERSION = "0.9.1";
|
||||
const STORAGE_KEY = "chatgptSunoPromptSlots";
|
||||
const SLOT_COUNT = 2;
|
||||
const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com");
|
||||
@@ -726,6 +726,46 @@
|
||||
.trim();
|
||||
}
|
||||
|
||||
function isBadTrackTitle(value) {
|
||||
const text = String(value || "").trim();
|
||||
if (!text) return true;
|
||||
if (/^v\d+(?:\.\d+)*$/i.test(text)) return true;
|
||||
if (/^\d+:\d{2}$/.test(text)) return true;
|
||||
if (/^(liked|public|uploads|newest|songs|playlists)$/i.test(text)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractIdsFromText(value) {
|
||||
return unique([...String(value || "").matchAll(/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi)].map((match) => match[0]));
|
||||
}
|
||||
|
||||
function readNodeMediaText(node) {
|
||||
const values = [];
|
||||
let current = node;
|
||||
for (let depth = 0; depth < 5 && current; depth += 1) {
|
||||
if (current.currentSrc || current.src) values.push(current.currentSrc || current.src);
|
||||
const style = current.getAttribute?.("style") || "";
|
||||
if (style) values.push(style);
|
||||
current = current.parentElement;
|
||||
}
|
||||
return values.join("\n");
|
||||
}
|
||||
|
||||
function inferVisibleRowTitle(text) {
|
||||
const lines = String(text || "")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
for (const line of lines) {
|
||||
if (isBadTrackTitle(line)) continue;
|
||||
if (/^(play|like|liked|public|uploads|newest|filters?)$/i.test(line)) continue;
|
||||
if (/^\d+$/.test(line)) continue;
|
||||
if (line.length > 90) continue;
|
||||
return line;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getCurrentSunoUsername() {
|
||||
const text = document.body.innerText || "";
|
||||
const match = /([A-Za-z0-9_.-]{3,40})\s+\d+\s+credits/i.exec(text);
|
||||
@@ -753,9 +793,12 @@
|
||||
const key = normalizeMatchText(text);
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
const mediaText = `${readNodeMediaText(node)}\n${current.innerHTML || ""}`;
|
||||
rows.push({
|
||||
text,
|
||||
normalized: key,
|
||||
title: inferVisibleRowTitle(text),
|
||||
ids: extractIdsFromText(mediaText),
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -774,7 +817,10 @@
|
||||
if (!rows.length) return -1;
|
||||
const title = normalizeMatchText(track.title);
|
||||
const style = normalizeMatchText(track.style || track.lyrics || "");
|
||||
if (!title || title.length > 160) return -1;
|
||||
const id = String(track.id || "");
|
||||
const idIndex = rows.findIndex((row) => id && row.ids?.includes(id));
|
||||
if (idIndex >= 0) return idIndex;
|
||||
if (!title || title.length > 160 || isBadTrackTitle(track.title)) return -1;
|
||||
return rows.findIndex((row) => {
|
||||
if (!row.normalized.includes(title)) return false;
|
||||
if (!style) return true;
|
||||
@@ -820,6 +866,10 @@
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleLibraryRows();
|
||||
for (const track of tracks.values()) {
|
||||
const row = visibleRows.find((item) => track.id && item.ids?.includes(track.id));
|
||||
if (row?.title && isBadTrackTitle(track.title)) track.title = row.title;
|
||||
}
|
||||
const currentUser = getCurrentSunoUsername();
|
||||
return [...tracks.values()]
|
||||
.filter((track) => track.id || track.audioUrl || track.imageUrl)
|
||||
@@ -1543,6 +1593,24 @@
|
||||
return tryParseJson(text) || {};
|
||||
}
|
||||
|
||||
async function stopCompanionWork(statusTarget) {
|
||||
try {
|
||||
const response = await fetch("http://127.0.0.1:17873/stop", {
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
setStatus(statusTarget, `중단 실패: ${response.status} ${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
const data = tryParseJson(text) || {};
|
||||
setStatus(statusTarget, `작업 중단: 실행 ${data.stopped || 0}개 종료, 대기 ${data.dropped || 0}개 제거`);
|
||||
} catch (error) {
|
||||
setStatus(statusTarget, `중단 실패: companion 연결 확인 필요. ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function formatCompanionStatus(data) {
|
||||
if (!data || typeof data !== "object") return "상태 정보 없음";
|
||||
const active = Number(data.activeCount || 0);
|
||||
@@ -1586,6 +1654,7 @@
|
||||
</div>
|
||||
<div class="cgs-row">
|
||||
<button class="cgs-btn cgs-wide" type="button" data-cgs-test-companion>연결 테스트</button>
|
||||
<button class="cgs-btn cgs-wide" type="button" data-cgs-stop-companion>작업 중단</button>
|
||||
</div>
|
||||
<div class="cgs-row">
|
||||
<button class="cgs-btn cgs-wide" type="button" data-cgs-copy-diagnostics>진단 복사</button>
|
||||
@@ -1851,6 +1920,10 @@
|
||||
await testCompanion(body);
|
||||
});
|
||||
|
||||
body.querySelector("[data-cgs-stop-companion]").addEventListener("click", async () => {
|
||||
await stopCompanionWork(body);
|
||||
});
|
||||
|
||||
body.querySelector("[data-cgs-copy-diagnostics]").addEventListener("click", async () => {
|
||||
if (!libraryTracks.length) {
|
||||
mergeLibraryTracks(scanSunoLibraryTracks());
|
||||
|
||||
Reference in New Issue
Block a user