-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgenerate.yaml
More file actions
153 lines (135 loc) · 5.8 KB
/
Copy pathgenerate.yaml
File metadata and controls
153 lines (135 loc) · 5.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
site: suno
name: generate
description: 提交音乐生成任务,返回新歌 ID(生成需 1-3 分钟,完成后用 suno download 下载)
domain: suno.com
strategy: cookie
browser: true
timeout: 120
args:
prompt:
type: string
required: true
description: "歌词内容 (包含结构标记 [Verse], [Chorus] 等)"
tags:
type: string
default: "pop, male-vocals"
description: "风格标签"
title:
type: string
default: "Untitled"
description: "歌曲标题"
columns: [id, title, status]
pipeline:
- navigate: https://suno.com/create
- wait: 3
- evaluate: |
(async () => {
const prompt = ${{ args.prompt | json }};
const tags = ${{ args.tags | json }};
const title = ${{ args.title | json }};
// 1. 获取 JWT Token
let token = null;
if (window.Clerk && window.Clerk.session) {
token = await window.Clerk.session.getToken();
}
if (!token) throw new Error('JWT Token not found. Please log in to Suno in Chrome.');
// 2. 生成前快照(记录已有 ID,用于识别新歌)
let existingIds = new Set();
try {
const preResp = await fetch('https://studio-api.prod.suno.com/api/feed/v2?page_size=20', {
headers: { 'Authorization': `Bearer ${token}` }
});
const preData = await preResp.json();
existingIds = new Set((preData.clips || []).map(s => s.id));
} catch(e) {}
// 3. 强制改写 React 受控组件
const forceSetValue = (el, value) => {
const nativeSetter = Object.getOwnPropertyDescriptor(
el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
'value'
)?.set;
if (nativeSetter) nativeSetter.call(el, value);
else el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
};
// 4. 展开折叠面板
const sections = Array.from(document.querySelectorAll('div, button, span'))
.filter(el => ['Lyrics', 'Styles', 'Song Title', '自定义', '风格', '标题'].some(t => el.textContent.trim() === t));
for (const section of sections) {
const parent = section.closest('div');
if (parent && !parent.querySelector('textarea')) {
section.click();
await new Promise(r => setTimeout(r, 400));
}
}
// 5. 填写表单
const inputs = Array.from(document.querySelectorAll('textarea, input'));
const textareas = inputs.filter(i => i.tagName === 'TEXTAREA');
const lyricsInput = textareas[0];
const styleInput = textareas[1];
const titleInput = inputs.find(i => i.tagName === 'INPUT' && (i.placeholder.toLowerCase().includes('title') || i.placeholder.includes('标题')));
if (lyricsInput) forceSetValue(lyricsInput, prompt);
if (styleInput) forceSetValue(styleInput, tags);
if (titleInput) forceSetValue(titleInput, title);
// 6. 等待 Create 按钮可用(最多 8 秒)
let createBtn = null;
for (let i = 0; i < 16; i++) {
await new Promise(r => setTimeout(r, 500));
createBtn = Array.from(document.querySelectorAll('button')).find(b => {
const text = b.textContent.trim().toLowerCase();
return (text === 'create' || text === '创建') && b.offsetWidth > 0 && !b.disabled;
});
if (createBtn) break;
}
if (!createBtn) throw new Error('Create button not found or still disabled.');
// 7. 幂等检查:防止重复提交
const idempotencyKey = `suno_create_${title}`;
const lastCreateTime = localStorage.getItem(idempotencyKey);
let alreadySubmitted = lastCreateTime && (Date.now() - parseInt(lastCreateTime)) < 600000;
if (!alreadySubmitted) {
try {
const checkResp = await fetch('https://studio-api.prod.suno.com/api/feed/v2?page_size=20', {
headers: { 'Authorization': `Bearer ${token}` }
});
const checkData = await checkResp.json();
const tenMinAgo = (Date.now() / 1000) - 600;
const recent = (checkData.clips || []).filter(s =>
s.title === title &&
(['submitted', 'queued', 'streaming'].includes(s.status) ||
(s.status === 'complete' && s.created_at && s.created_at > tenMinAgo))
);
if (recent.length > 0) alreadySubmitted = true;
} catch(e) {}
}
if (!alreadySubmitted) {
createBtn.scrollIntoView();
createBtn.click();
localStorage.setItem(idempotencyKey, String(Date.now()));
}
// 8. 等待 15 秒,抓取新出现的 ID(不等完成)
await new Promise(r => setTimeout(r, 15000));
let newIds = [];
for (let attempt = 0; attempt < 6; attempt++) {
try {
const feedResp = await fetch('https://studio-api.prod.suno.com/api/feed/v2?page_size=20', {
headers: { 'Authorization': `Bearer ${token}` }
});
const feedData = await feedResp.json();
const clips = feedData.clips || [];
newIds = clips
.filter(s => !existingIds.has(s.id))
.map(s => ({ id: s.id, title: s.title || title, status: s.status }));
if (newIds.length > 0) break;
} catch(e) {}
await new Promise(r => setTimeout(r, 3000));
}
if (newIds.length === 0) {
return [{ id: 'pending', title: title, status: 'submitted - check suno history in 1-2 min' }];
}
return newIds;
})()
- map:
id: ${{ item.id }}
title: ${{ item.title }}
status: ${{ item.status }}