Fix Suno title badges and add stop control
This commit is contained in:
@@ -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());
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user