diff --git a/model/core.js b/model/core.js index a89860b..6360f72 100644 --- a/model/core.js +++ b/model/core.js @@ -15,6 +15,7 @@ import _ from 'lodash' import { getChatHistoryGroup } from '../utils/chat.js' import { APTool } from '../utils/tools/APTool.js' import BingDrawClient from '../utils/BingDraw.js' +import BingSunoClient from '../utils/BingSuno.js' import { solveCaptchaOneShot } from '../utils/bingCaptcha.js' import { OfficialChatGPTClient } from '../utils/message.js' import ChatGLMClient from '../utils/chatglm.js' @@ -251,6 +252,20 @@ class Core { }) } } + opt.onSunoCreateRequest = prompt => { + logger.mark(`开始生成内容:Suno ${prompt.songtId}`) + let client = new BingSunoClient({ + cookies: cookies + }) + redis.set(`CHATGPT:SUNO:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => { + try { + client.getSuno(prompt, e) + } catch (err) { + redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) + this.reply('歌曲生成失败:' + err) + } + }) + } } response = await bingAIClient.sendMessage(prompt, opt, (token) => { reply += token diff --git a/utils/BingSuno.js b/utils/BingSuno.js new file mode 100644 index 0000000..b348f0c --- /dev/null +++ b/utils/BingSuno.js @@ -0,0 +1,192 @@ +import { downloadFile } from '../utils/common.js' +import common from '../../../lib/common/common.js' +import fs from 'fs' +import crypto from 'crypto' +import fetch from 'node-fetch' + +export default class BingSunoClient { + constructor(opts) { + this.opts = opts + } + + async replyMsg(song, e) { + let messages = [] + messages.push(`歌名:${song.title}\n风格: ${song.musicalStyle}\n长度: ${song.duration}秒\n歌词:\n${song.prompt}\n`) + messages.push(`音频链接:${song.audioURL}\n视频链接:${song.videoURL}\n封面链接:${song.imageURL}\n`) + messages.push(segment.image(song.imageURL)) + let retry = 3 + let videoPath + while (!videoPath && retry >= 0) { + try { + videoPath = await downloadFile(song.video_url, `suno/${song.title}.mp4`, false, false, { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36' + }) + } catch (err) { + retry-- + await common.sleep(1000) + } + } + if (videoPath) { + const data = fs.readFileSync(videoPath) + messages.push(segment.video(`base64://${data.toString('base64')}`)) + // 60秒后删除文件避免占用体积 + setTimeout(() => { + fs.unlinkSync(videoPath) + }, 60000) + } else { + logger.warn(`${song.title}下载视频失败,仅发送视频链接`) + } + + await e.reply(await common.makeForwardMsg(e, messages, '音乐合成结果')) + } + + async getSuno(prompt, e) { + if (prompt.cookie) { + this.opts.cookies = prompt.cookie + } + + const sunoResult = await this.getSunoResult(prompt.songtId) + if (sunoResult) { + const { + duration, + title, + musicalStyle, + requestId, + } = sunoResult + const generateURL = id => `https://th.bing.com/th?&id=${id}` + const audioURL = generateURL(`OIG.a_${requestId}`) + const imageURL = generateURL(`OIG.i_${requestId}`) + const videoURL = generateURL(`OIG.v_${requestId}`) + const sunoURL = `https://cdn1.suno.ai/${requestId}.mp4` + const sunoDisplayResult = { + title, + duration, + musicalStyle, + audioURL, + imageURL, + videoURL, + sunoURL, + prompt: prompt.songPrompt + } + await e.reply('Bing Suno 生成中,请稍后') + replyMsg(sunoDisplayResult, e) + } else { + await e.reply('Bing Suno 数据获取失败') + redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) + } + } + + async getSunoResult(requestId) { + const skey = await this.#getSunoMetadata(requestId) + if (skey) { + const sunoMedia = await this.#getSunoMedia(requestId, skey) + return sunoMedia + } + return null + } + + async #getSunoMetadata(requestId) { + const fetchURL = new URL('https://www.bing.com/videos/music') + const searchParams = new URLSearchParams({ + vdpp: 'suno', + kseed: '7500', + SFX: '2', + q: '', + iframeid: crypto.randomUUID(), + requestId, + }) + fetchURL.search = searchParams.toString() + const response = await fetch(fetchURL, { + headers: { + accept: 'text/html', + cookie: this.opts.cookies, + }, + method: 'GET', + }) + if (response.status === 200) { + const document = await response.text() + + const patternSkey = /(?<=skey=)[^&]+/ + const matchSkey = document.match(patternSkey) + const skey = matchSkey ? matchSkey[0] : null + + const patternIG = /(?<=IG:"|IG:\s")[0-9A-F]{32}(?=")/ + const matchIG = document.match(patternIG) + const ig = matchIG ? matchIG[0] : null + + return { skey, ig } + } else { + console.error(`HTTP error! Error: ${response.error}, Status: ${response.status}`) + return null + } + } + + async #getSunoMedia(requestId, sunoMetadata) { + let sfx = 1 + const maxTries = 30 + const { skey, ig } = sunoMetadata + + let rawResponse + const result = await new Promise((resolve, reject) => { + const intervalId = setInterval(async () => { + const fetchURL = new URL('https://www.bing.com/videos/api/custom/music') + const searchParams = new URLSearchParams({ + skey, + safesearch: 'Moderate', + vdpp: 'suno', + requestId, + ig, + iid: 'vsn', + sfx: sfx.toString(), + }) + fetchURL.search = searchParams.toString() + + const response = await fetch(fetchURL, { + headers: { + accept: '*/*', + cookie: this.opts.cookies, + }, + method: 'GET', + }) + try { + const body = await response.json() + rawResponse = JSON.parse(body.RawResponse) + const { status } = rawResponse + const done = status === 'complete' + + if (done) { + clearInterval(intervalId) + resolve() + } else { + sfx++ + if (sfx === maxTries) { + reject(new Error('Maximum number of tries exceeded')) + } + } + } catch (error) { + console.log(`获取音乐失败 ${response.status}`) + reject(new Error(error)) + } + + }, 2000) + }) + .then(() => { + if (rawResponse?.status === 'complete') { + return { + duration: rawResponse.duration, + title: rawResponse.gptPrompt, + musicalStyle: rawResponse.musicalStyle, + requestId: rawResponse.id, + } + } else { + throw Error('Suno response could not be completed.') + } + }) + .catch((err) => { + console.error(err) + return null + }) + return result + } + +} diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index 3d890d2..a94acad 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -257,6 +257,7 @@ export default class SydneyAIClient { messageType = 'Chat', toSummaryFileContent, onImageCreateRequest = prompt => {}, + onSunoCreateRequest = prompt => {}, isPro = this.pro } = opts // if (messageType === 'Chat') { @@ -503,7 +504,12 @@ export default class SydneyAIClient { } } } - + if (Config.enableGenerateContents){ + argument0.plugins.push({ + "id": "22b7f79d-8ea4-437e-b5fd-3e21f09f7bc1", + "category": 1 + }) + } if (encryptedconversationsignature) { delete argument0.conversationSignature } @@ -707,6 +713,14 @@ export default class SydneyAIClient { adaptiveCardsSoFar = message.adaptiveCards suggestedResponsesSoFar = message.suggestedResponses } + if (messages[0].contentType === 'SUNO') { + console.log() + onSunoCreateRequest({ + songtId: messages[0]?.hiddenText.split('=')[1], + songPrompt: messages[0]?.text, + cookie: this.opts.cookies + }) + } const updatedText = messages[0].text if (!updatedText || updatedText === replySoFar[cursor]) { return @@ -889,6 +903,9 @@ export default class SydneyAIClient { let imgBase64 try { const response = await fetch(url) + if (!response.ok) { + throw new Error(`图片${url}获取失败:${response.status}`) + } const arrayBuffer = await response.arrayBuffer() const buffer = Buffer.from(arrayBuffer) imgBase64 = buffer.toString('base64') @@ -975,6 +992,11 @@ function getOptionSet (tone, generateContent = false) { 'responsible_ai_policy_235', 'enablemm', 'dv3sugg', + 'uquopt', + 'bicfluxv2', + 'langdtwb', + 'fluxprod', + 'eredirecturl', 'autosave', 'iyxapbing', 'iycapbing', @@ -992,7 +1014,6 @@ function getOptionSet (tone, generateContent = false) { 'elec2t', 'elecgnd', 'gndlogcf', - 'eredirecturl', 'clgalileo', 'gencontentv3' ]) @@ -1008,7 +1029,6 @@ function getOptionSet (tone, generateContent = false) { 'elec2t', 'elecgnd', 'gndlogcf', - 'eredirecturl' ]) break case 'Creative': @@ -1021,11 +1041,16 @@ function getOptionSet (tone, generateContent = false) { 'elec2t', 'elecgnd', 'gndlogcf', - 'eredirecturl', 'clgalileo', 'gencontentv3' ]) break } + if (Config.enableGenerateContents){ + optionset.push(...[ + '014CB21D', + 'B3FF9F21' + ]) + } return optionset }