Fix Suno title badges and add stop control

This commit is contained in:
DESKTOP-KSVGT20\shkim
2026-05-02 01:43:51 +09:00
parent d90b2b0329
commit 8efd24a50a
2 changed files with 116 additions and 3 deletions
+76 -3
View File
@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name ChatGPT to Suno Prompt Copier // @name ChatGPT to Suno Prompt Copier
// @namespace local.suno-helper // @namespace local.suno-helper
// @version 0.9.0 // @version 0.9.1
// @description Copy structured ChatGPT song prompt versions into Suno fields. // @description Copy structured ChatGPT song prompt versions into Suno fields.
// @author local // @author local
// @match https://chatgpt.com/* // @match https://chatgpt.com/*
@@ -30,7 +30,7 @@
(function () { (function () {
"use strict"; "use strict";
const SCRIPT_VERSION = "0.9.0"; const SCRIPT_VERSION = "0.9.1";
const STORAGE_KEY = "chatgptSunoPromptSlots"; const STORAGE_KEY = "chatgptSunoPromptSlots";
const SLOT_COUNT = 2; const SLOT_COUNT = 2;
const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com"); const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com");
@@ -726,6 +726,46 @@
.trim(); .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() { function getCurrentSunoUsername() {
const text = document.body.innerText || ""; const text = document.body.innerText || "";
const match = /([A-Za-z0-9_.-]{3,40})\s+\d+\s+credits/i.exec(text); const match = /([A-Za-z0-9_.-]{3,40})\s+\d+\s+credits/i.exec(text);
@@ -753,9 +793,12 @@
const key = normalizeMatchText(text); const key = normalizeMatchText(text);
if (!seen.has(key)) { if (!seen.has(key)) {
seen.add(key); seen.add(key);
const mediaText = `${readNodeMediaText(node)}\n${current.innerHTML || ""}`;
rows.push({ rows.push({
text, text,
normalized: key, normalized: key,
title: inferVisibleRowTitle(text),
ids: extractIdsFromText(mediaText),
}); });
} }
break; break;
@@ -774,7 +817,10 @@
if (!rows.length) return -1; if (!rows.length) return -1;
const title = normalizeMatchText(track.title); const title = normalizeMatchText(track.title);
const style = normalizeMatchText(track.style || track.lyrics || ""); 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) => { return rows.findIndex((row) => {
if (!row.normalized.includes(title)) return false; if (!row.normalized.includes(title)) return false;
if (!style) return true; if (!style) return true;
@@ -820,6 +866,10 @@
} }
const visibleRows = getVisibleLibraryRows(); 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(); const currentUser = getCurrentSunoUsername();
return [...tracks.values()] return [...tracks.values()]
.filter((track) => track.id || track.audioUrl || track.imageUrl) .filter((track) => track.id || track.audioUrl || track.imageUrl)
@@ -1543,6 +1593,24 @@
return tryParseJson(text) || {}; 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) { function formatCompanionStatus(data) {
if (!data || typeof data !== "object") return "상태 정보 없음"; if (!data || typeof data !== "object") return "상태 정보 없음";
const active = Number(data.activeCount || 0); const active = Number(data.activeCount || 0);
@@ -1586,6 +1654,7 @@
</div> </div>
<div class="cgs-row"> <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-test-companion>연결 테스트</button>
<button class="cgs-btn cgs-wide" type="button" data-cgs-stop-companion>작업 중단</button>
</div> </div>
<div class="cgs-row"> <div class="cgs-row">
<button class="cgs-btn cgs-wide" type="button" data-cgs-copy-diagnostics>진단 복사</button> <button class="cgs-btn cgs-wide" type="button" data-cgs-copy-diagnostics>진단 복사</button>
@@ -1851,6 +1920,10 @@
await testCompanion(body); await testCompanion(body);
}); });
body.querySelector("[data-cgs-stop-companion]").addEventListener("click", async () => {
await stopCompanionWork(body);
});
body.querySelector("[data-cgs-copy-diagnostics]").addEventListener("click", async () => { body.querySelector("[data-cgs-copy-diagnostics]").addEventListener("click", async () => {
if (!libraryTracks.length) { if (!libraryTracks.length) {
mergeLibraryTracks(scanSunoLibraryTracks()); mergeLibraryTracks(scanSunoLibraryTracks());
+40
View File
@@ -251,6 +251,28 @@ function Get-QueueSnapshot {
} }
} }
function Stop-AllWork {
$stopped = 0
foreach ($job in @($script:activeJobs)) {
try {
if ($job.Process -and -not $job.Process.HasExited) {
Stop-Process -Id $job.Process.Id -Force -ErrorAction Stop
$stopped++
}
} catch {}
[void]$script:activeIds.Remove($job.TrackId)
}
$dropped = $script:queue.Count
$script:queue.Clear()
$script:queueIds.Clear()
$script:activeJobs.Clear()
Show-Dashboard -Force
return [pscustomobject]@{
stopped = $stopped
dropped = $dropped
}
}
function Show-Dashboard { function Show-Dashboard {
param([switch]$Force) param([switch]$Force)
@@ -496,6 +518,24 @@ try {
continue continue
} }
if ($request.Method -eq "POST" -and $request.Path -eq "/stop") {
$result = Stop-AllWork
Write-Host "Stopped work: active=$($result.stopped), queued=$($result.dropped)"
$snapshot = Get-QueueSnapshot
Write-Response $stream 200 "OK" (@{
ok = $true
stopped = $result.stopped
dropped = $result.dropped
queued = $snapshot.queued
activeCount = $snapshot.activeCount
total = $snapshot.total
completed = $snapshot.completed
failed = $snapshot.failed
percent = $snapshot.percent
} | ConvertTo-Json -Compress)
continue
}
if ($request.Method -eq "POST" -and $request.Path -eq "/sync-index") { if ($request.Method -eq "POST" -and $request.Path -eq "/sync-index") {
$manifest = $request.Body | ConvertFrom-Json $manifest = $request.Body | ConvertFrom-Json
$result = Get-MissingTracks $manifest.tracks $result = Get-MissingTracks $manifest.tracks