mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 22:07:10 +00:00
Merge branch 'v2' into v2
This commit is contained in:
commit
be91e25415
26 changed files with 1029 additions and 632 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import fetch from 'node-fetch'
|
||||
import fetch, { FormData } from 'node-fetch'
|
||||
import { makeForwardMsg } from './common.js'
|
||||
import { Config } from './config.js'
|
||||
|
||||
|
|
@ -22,8 +22,8 @@ export default class BingDrawClient {
|
|||
async getImages (prompt, e) {
|
||||
let urlEncodedPrompt = encodeURIComponent(prompt)
|
||||
let url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=4&FORM=GENCRE`
|
||||
let d = Math.ceil(Math.random() * 255)
|
||||
let randomIp = '141.11.138.' + d
|
||||
// let d = Math.ceil(Math.random() * 255)
|
||||
// let randomIp = '141.11.138.' + d
|
||||
let headers = {
|
||||
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'accept-language': 'en-US,en;q=0.9',
|
||||
|
|
@ -31,31 +31,58 @@ export default class BingDrawClient {
|
|||
'content-type': 'application/x-www-form-urlencoded',
|
||||
referrer: 'https://www.bing.com/images/create/',
|
||||
origin: 'https://www.bing.com',
|
||||
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50',
|
||||
cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||
'x-forwarded-for': randomIp
|
||||
// 'x-forwarded-for': randomIp,
|
||||
Dnt: '1',
|
||||
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
||||
'sec-ch-ua-arch': '"x86"',
|
||||
'sec-ch-ua-bitness': '"64"',
|
||||
'sec-ch-ua-full-version': '"113.0.5672.126"',
|
||||
'sec-ch-ua-full-version-list': '"Google Chrome";v="113.0.5672.126", "Chromium";v="113.0.5672.126", "Not-A.Brand";v="24.0.0.0"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-model': '',
|
||||
'sec-ch-ua-platform': '"macOS"',
|
||||
'sec-ch-ua-platform-version': '"13.1.0"',
|
||||
'sec-fetch-dest': 'document',
|
||||
'sec-fetch-mode': 'navigate',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'sec-fetch-user': '?1',
|
||||
'Referrer-Policy': 'origin-when-cross-origin',
|
||||
'x-edge-shopping-flag': '1'
|
||||
}
|
||||
// headers['x-forwarded-for'] = '141.11.138.30'
|
||||
let body = new FormData()
|
||||
body.append('q', prompt)
|
||||
body.append('qs', 'ds')
|
||||
let fetchOptions = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
redirect: 'manual'
|
||||
headers
|
||||
}
|
||||
if (Config.proxy) {
|
||||
fetchOptions.agent = proxy(Config.proxy)
|
||||
}
|
||||
let response = await fetch(url, fetchOptions)
|
||||
let res = await response.text()
|
||||
if (res.toLowerCase().indexOf('this prompt has been blocked') > -1) {
|
||||
throw new Error('Your prompt has been blocked by Bing. Try to change any bad words and try again.')
|
||||
}
|
||||
if (response.status !== 302) {
|
||||
url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=3&FORM=GENCRE`
|
||||
let response3 = await fetch(url, fetchOptions)
|
||||
if (response3.status !== 302) {
|
||||
throw new Error('绘图失败,请检查Bing token和代理/反代配置')
|
||||
let success = false
|
||||
let retry = 5
|
||||
let response
|
||||
while (!success && retry >= 0) {
|
||||
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST' }))
|
||||
let res = await response.text()
|
||||
if (res.toLowerCase().indexOf('this prompt has been blocked') > -1) {
|
||||
throw new Error('Your prompt has been blocked by Bing. Try to change any bad words and try again.')
|
||||
}
|
||||
response = response3
|
||||
if (response.status !== 302) {
|
||||
url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=3&FORM=GENCRE`
|
||||
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST' }))
|
||||
}
|
||||
if (response.status === 302) {
|
||||
success = true
|
||||
break
|
||||
} else {
|
||||
retry--
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
throw new Error('绘图失败,请检查Bing token和代理/反代配置')
|
||||
}
|
||||
let redirectUrl = response.headers.get('Location').replace('&nfy=1', '')
|
||||
let requestId = redirectUrl.split('id=')[1]
|
||||
|
|
@ -66,26 +93,23 @@ export default class BingDrawClient {
|
|||
let pollingUrl = `${this.opts.baseUrl}/images/create/async/results/${requestId}?q=${urlEncodedPrompt}`
|
||||
logger.info({ pollingUrl })
|
||||
logger.info('waiting for bing draw results...')
|
||||
let timeoutTimes = 50
|
||||
let timeoutTimes = 30
|
||||
let found = false
|
||||
let timer = setInterval(async () => {
|
||||
if (found) {
|
||||
return
|
||||
}
|
||||
let r = await fetch(pollingUrl, {
|
||||
headers
|
||||
})
|
||||
let r = await fetch(pollingUrl, fetchOptions)
|
||||
let rText = await r.text()
|
||||
if (rText) {
|
||||
if (r.status === 200 && rText) {
|
||||
// logger.info(rText)
|
||||
logger.info('got bing draw results!')
|
||||
found = true
|
||||
let regex = /src="([^"]+)"/g
|
||||
let imageLinks = rText.match(regex)
|
||||
if (!imageLinks || imageLinks.length === 0) {
|
||||
await e.reply('绘图失败:no images', true)
|
||||
logger.error(rText)
|
||||
throw new Error('no images')
|
||||
// 很可能是微软内部error,重试即可
|
||||
return
|
||||
}
|
||||
imageLinks = imageLinks.map(link => link.split('?w=')[0]).map(link => link.replace('src="', ''))
|
||||
imageLinks = [...new Set(imageLinks)]
|
||||
|
|
@ -108,11 +132,12 @@ export default class BingDrawClient {
|
|||
if (timeoutTimes === 0) {
|
||||
await e.reply('绘图超时', true)
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
} else {
|
||||
logger.info('still waiting for bing draw results... times left: ' + timeoutTimes)
|
||||
timeoutTimes--
|
||||
}
|
||||
}
|
||||
}, 1500)
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import fetch, {
|
|||
Response
|
||||
} from 'node-fetch'
|
||||
import crypto from 'crypto'
|
||||
|
||||
import WebSocket from 'ws'
|
||||
import HttpsProxyAgent from 'https-proxy-agent'
|
||||
import { Config, pureSydneyInstruction } from './config.js'
|
||||
import { formatDate, getMasterQQ, isCN, getUserData } from './common.js'
|
||||
|
|
@ -29,15 +29,16 @@ if (Config.proxy) {
|
|||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
||||
}
|
||||
}
|
||||
async function getWebSocket () {
|
||||
let WebSocket
|
||||
try {
|
||||
WebSocket = (await import('ws')).default
|
||||
} catch (error) {
|
||||
throw new Error('ws依赖未安装,请使用pnpm install ws安装')
|
||||
}
|
||||
return WebSocket
|
||||
}
|
||||
|
||||
// async function getWebSocket () {
|
||||
// let WebSocket
|
||||
// try {
|
||||
// WebSocket = (await import('ws')).default
|
||||
// } catch (error) {
|
||||
// throw new Error('ws依赖未安装,请使用pnpm install ws安装')
|
||||
// }
|
||||
// return WebSocket
|
||||
// }
|
||||
async function getKeyv () {
|
||||
let Keyv
|
||||
try {
|
||||
|
|
@ -58,7 +59,7 @@ export default class SydneyAIClient {
|
|||
constructor (opts) {
|
||||
this.opts = {
|
||||
...opts,
|
||||
host: opts.host || Config.sydneyReverseProxy || 'https://www.bing.com'
|
||||
host: opts.host || Config.sydneyReverseProxy || 'https://edgeservices.bing.com/edgesvc'
|
||||
}
|
||||
// if (opts.proxy && !Config.sydneyForceUseReverse) {
|
||||
// this.opts.host = 'https://www.bing.com'
|
||||
|
|
@ -80,42 +81,46 @@ export default class SydneyAIClient {
|
|||
const fetchOptions = {
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'accept-language': 'en-US,en;q=0.9',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
'content-type': 'application/json',
|
||||
'sec-ch-ua': '"Chromium";v="112", "Microsoft Edge";v="112", "Not:A-Brand";v="99"',
|
||||
'sec-ch-ua-arch': '"x86"',
|
||||
'sec-ch-ua-bitness': '"64"',
|
||||
'sec-ch-ua-full-version': '"112.0.1722.7"',
|
||||
'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
||||
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
||||
// 'sec-ch-ua-arch': '"x86"',
|
||||
// 'sec-ch-ua-bitness': '"64"',
|
||||
// 'sec-ch-ua-full-version': '"112.0.1722.7"',
|
||||
// 'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-model': '',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-ch-ua-platform-version': '"15.0.0"',
|
||||
// 'sec-ch-ua-model': '',
|
||||
'sec-ch-ua-platform': '"macOS"',
|
||||
// 'sec-ch-ua-platform-version': '"15.0.0"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'x-ms-client-request-id': crypto.randomUUID(),
|
||||
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
|
||||
cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||
Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx',
|
||||
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/macOS',
|
||||
// cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||
Referer: 'https://edgeservices.bing.com/edgesvc/chat?udsframed=1&form=SHORUN&clientscopes=chat,noheader,channelstable,',
|
||||
'Referrer-Policy': 'origin-when-cross-origin',
|
||||
// Workaround for request being blocked due to geolocation
|
||||
'x-forwarded-for': '1.1.1.1'
|
||||
}
|
||||
}
|
||||
if (this.opts.cookies || this.opts.userToken) {
|
||||
// 疑似无需token了
|
||||
fetchOptions.headers.cookie = this.opts.cookies || `_U=${this.opts.userToken}`
|
||||
}
|
||||
if (this.opts.proxy) {
|
||||
fetchOptions.agent = proxy(Config.proxy)
|
||||
}
|
||||
let accessible = !(await isCN()) || this.opts.proxy
|
||||
if (accessible && !Config.sydneyForceUseReverse) {
|
||||
// 本身能访问bing.com,那就不用反代啦,重置host
|
||||
logger.info('change hosts to https://www.bing.com')
|
||||
this.opts.host = 'https://www.bing.com'
|
||||
logger.info('change hosts to https://edgeservices.bing.com')
|
||||
this.opts.host = 'https://edgeservices.bing.com/edgesvc'
|
||||
}
|
||||
logger.mark('使用host:' + this.opts.host)
|
||||
let response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
|
||||
let text = await response.text()
|
||||
let retry = 30
|
||||
let retry = 10
|
||||
while (retry >= 0 && response.status === 200 && !text) {
|
||||
await delay(400)
|
||||
response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
|
||||
|
|
@ -138,7 +143,7 @@ export default class SydneyAIClient {
|
|||
|
||||
async createWebSocketConnection () {
|
||||
await this.initCache()
|
||||
let WebSocket = await getWebSocket()
|
||||
// let WebSocket = await getWebSocket()
|
||||
return new Promise((resolve, reject) => {
|
||||
let agent
|
||||
let sydneyHost = 'wss://sydney.bing.com'
|
||||
|
|
@ -149,7 +154,7 @@ export default class SydneyAIClient {
|
|||
sydneyHost = Config.sydneyReverseProxy.replace('https://', 'wss://').replace('http://', 'ws://')
|
||||
}
|
||||
logger.mark(`use sydney websocket host: ${sydneyHost}`)
|
||||
let ws = new WebSocket(sydneyHost + '/sydney/ChatHub', { agent })
|
||||
let ws = new WebSocket(sydneyHost + '/sydney/ChatHub', undefined, { agent, origin: 'https://edgeservices.bing.com' })
|
||||
ws.on('error', (err) => {
|
||||
console.error(err)
|
||||
reject(err)
|
||||
|
|
@ -304,7 +309,8 @@ export default class SydneyAIClient {
|
|||
const text = (pureSydney ? pureSydneyInstruction : (useCast?.bing || Config.sydney)).replaceAll(namePlaceholder, botName || defaultBotName) +
|
||||
((Config.enableGroupContext && groupId) ? groupContextTip : '') +
|
||||
((Config.enforceMaster && master) ? masterTip : '') +
|
||||
(Config.sydneyMood ? moodTip : '')
|
||||
(Config.sydneyMood ? moodTip : '') +
|
||||
(Config.sydneySystemCode ? '' : '')
|
||||
// logger.info(text)
|
||||
if (pureSydney) {
|
||||
previousMessages = invocationId === 0
|
||||
|
|
@ -347,25 +353,31 @@ export default class SydneyAIClient {
|
|||
logger.mark('sydney websocket constructed successful')
|
||||
}
|
||||
const toneOption = 'h3imaginative'
|
||||
let optionsSets = [
|
||||
'nlu_direct_response_filter',
|
||||
'deepleo',
|
||||
'disable_emoji_spoken_text',
|
||||
'responsible_ai_policy_235',
|
||||
'enablemm',
|
||||
toneOption,
|
||||
'dagslnv1',
|
||||
'sportsansgnd',
|
||||
'dl_edge_desc',
|
||||
'noknowimg',
|
||||
// 'dtappid',
|
||||
// 'cricinfo',
|
||||
// 'cricinfov2',
|
||||
'dv3sugg',
|
||||
'gencontentv3'
|
||||
]
|
||||
if (Config.enableGenerateContents) {
|
||||
optionsSets.push(...['gencontentv3'])
|
||||
}
|
||||
const obj = {
|
||||
arguments: [
|
||||
{
|
||||
source: 'cib',
|
||||
optionsSets: [
|
||||
'nlu_direct_response_filter',
|
||||
'deepleo',
|
||||
'disable_emoji_spoken_text',
|
||||
'responsible_ai_policy_235',
|
||||
'enablemm',
|
||||
toneOption,
|
||||
'clgalileo',
|
||||
'gencontentv3',
|
||||
'rai267',
|
||||
'dtappid',
|
||||
'cricinfo',
|
||||
'cricinfov2',
|
||||
'dv3sugg'
|
||||
],
|
||||
optionsSets,
|
||||
sliceIds: [
|
||||
'222dtappid',
|
||||
'225cricinfo',
|
||||
|
|
|
|||
116
utils/common.js
116
utils/common.js
|
|
@ -8,6 +8,9 @@ import buffer from 'buffer'
|
|||
import yaml from 'yaml'
|
||||
import puppeteer from '../../../lib/puppeteer/puppeteer.js'
|
||||
import { Config } from './config.js'
|
||||
import { speakers as vitsRoleList } from './tts.js'
|
||||
import { supportConfigurations as voxRoleList } from './tts/voicevox.js'
|
||||
import { supportConfigurations as azureRoleList } from './tts/microsoft-azure.js'
|
||||
// export function markdownToText (markdown) {
|
||||
// return remark()
|
||||
// .use(stripMarkdown)
|
||||
|
|
@ -19,7 +22,7 @@ let _puppeteer
|
|||
try {
|
||||
const Puppeteer = (await import('../../../renderers/puppeteer/lib/puppeteer.js')).default
|
||||
let puppeteerCfg = {}
|
||||
let configFile = `./renderers/puppeteer/config.yaml`
|
||||
let configFile = './renderers/puppeteer/config.yaml'
|
||||
if (fs.existsSync(configFile)) {
|
||||
try {
|
||||
puppeteerCfg = yaml.parse(fs.readFileSync(configFile, 'utf8'))
|
||||
|
|
@ -335,7 +338,7 @@ export async function renderUrl (e, url, renderCfg = {}) {
|
|||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: url,
|
||||
url,
|
||||
option: {
|
||||
width: renderCfg.Viewport.width || 1280,
|
||||
height: renderCfg.Viewport.height || 720,
|
||||
|
|
@ -350,7 +353,7 @@ export async function renderUrl (e, url, renderCfg = {}) {
|
|||
})
|
||||
if (resultres.ok) {
|
||||
const buff = Buffer.from(await resultres.arrayBuffer())
|
||||
if(buff) {
|
||||
if (buff) {
|
||||
const base64 = segment.image(buff)
|
||||
if (renderCfg.retType === 'base64') {
|
||||
return base64
|
||||
|
|
@ -363,7 +366,7 @@ export async function renderUrl (e, url, renderCfg = {}) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await _puppeteer.browserInit()
|
||||
const page = await _puppeteer.browser.newPage()
|
||||
let base64
|
||||
|
|
@ -401,7 +404,8 @@ export function getDefaultReplySetting () {
|
|||
usePicture: Config.defaultUsePicture,
|
||||
useTTS: Config.defaultUseTTS,
|
||||
ttsRole: Config.defaultTTSRole,
|
||||
ttsRoleAzure: Config.azureTTSSpeaker
|
||||
ttsRoleAzure: Config.azureTTSSpeaker,
|
||||
ttsRoleVoiceVox: Config.voicevoxTTSSpeaker
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -679,16 +683,106 @@ export async function getUserData (user) {
|
|||
return JSON.parse(data)
|
||||
} catch (error) {
|
||||
return {
|
||||
user: user,
|
||||
user,
|
||||
passwd: '',
|
||||
chat: [],
|
||||
mode: '',
|
||||
cast: {
|
||||
api: '', //API设定
|
||||
bing: '', //必应设定
|
||||
bing_resource: '', //必应扩展资料
|
||||
slack: '', //Slack设定
|
||||
api: '', // API设定
|
||||
bing: '', // 必应设定
|
||||
bing_resource: '', // 必应扩展资料
|
||||
slack: '' // Slack设定
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getVoicevoxRoleList () {
|
||||
return voxRoleList.map(item => item.name).join('、')
|
||||
}
|
||||
|
||||
export function getAzureRoleList () {
|
||||
return azureRoleList.map(item => item.name).join('、')
|
||||
}
|
||||
|
||||
export async function getVitsRoleList (e) {
|
||||
const [firstHalf, secondHalf] = [vitsRoleList.slice(0, Math.floor(vitsRoleList.length / 2)).join('、'), vitsRoleList.slice(Math.floor(vitsRoleList.length / 2)).join('、')]
|
||||
const [chunk1, chunk2] = [firstHalf.match(/[^、]+(?:、[^、]+){0,30}/g), secondHalf.match(/[^、]+(?:、[^、]+){0,30}/g)]
|
||||
const list = [await makeForwardMsg(e, chunk1, 'vits角色列表1'), await makeForwardMsg(e, chunk2, 'vits角色列表2')]
|
||||
return await makeForwardMsg(e, list, 'vits角色列表')
|
||||
}
|
||||
|
||||
export async function getUserReplySetting (e) {
|
||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
||||
if (userSetting) {
|
||||
userSetting = JSON.parse(userSetting)
|
||||
if (Object.keys(userSetting).indexOf('useTTS') < 0) {
|
||||
userSetting.useTTS = Config.defaultUseTTS
|
||||
}
|
||||
} else {
|
||||
userSetting = getDefaultReplySetting()
|
||||
}
|
||||
return userSetting
|
||||
}
|
||||
|
||||
export async function getImg (e) {
|
||||
// 取消息中的图片、at的头像、回复的图片,放入e.img
|
||||
if (e.at && !e.source) {
|
||||
e.img = [`https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.at}`]
|
||||
}
|
||||
if (e.source) {
|
||||
let reply
|
||||
if (e.isGroup) {
|
||||
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
||||
} else {
|
||||
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
||||
}
|
||||
if (reply) {
|
||||
let i = []
|
||||
for (let val of reply) {
|
||||
if (val.type === 'image') {
|
||||
i.push(val.url)
|
||||
}
|
||||
}
|
||||
e.img = i
|
||||
}
|
||||
}
|
||||
return e.img
|
||||
}
|
||||
|
||||
export async function getImageOcrText (e) {
|
||||
const img = await getImg(e)
|
||||
if (img) {
|
||||
try {
|
||||
let resultArr = []
|
||||
let eachImgRes = ''
|
||||
for (let i in img) {
|
||||
const imgOCR = await Bot.imageOcr(img[i])
|
||||
for (let text of imgOCR.wordslist) {
|
||||
eachImgRes += (`${text?.words} \n`)
|
||||
}
|
||||
if (eachImgRes) resultArr.push(eachImgRes)
|
||||
eachImgRes = ''
|
||||
}
|
||||
// logger.warn('resultArr', resultArr)
|
||||
return resultArr
|
||||
} catch (err) {
|
||||
return false
|
||||
// logger.error(err)
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 对原始黑白名单进行去重和去除无效群号处理,并处理通过锅巴面板添加错误配置时可能导致的问题
|
||||
export function processList (whitelist, blacklist) {
|
||||
whitelist = Array.isArray(whitelist)
|
||||
? whitelist
|
||||
: String(whitelist).split(/[,,]/)
|
||||
blacklist = !Array.isArray(blacklist)
|
||||
? blacklist
|
||||
: String(blacklist).split(/[,,]/)
|
||||
whitelist = Array.from(new Set(whitelist)).filter(value => /^\^?[1-9]\d{5,9}$/.test(value))
|
||||
blacklist = Array.from(new Set(blacklist)).filter(value => /^\^?[1-9]\d{5,9}$/.test(value))
|
||||
return [whitelist, blacklist]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ const defaultConfig = {
|
|||
sydneyApologyIgnored: true,
|
||||
enforceMaster: false,
|
||||
oldview: false,
|
||||
newhelp: false,
|
||||
serverPort: 3321,
|
||||
serverHost: '',
|
||||
viewHost: '',
|
||||
|
|
@ -98,8 +99,8 @@ const defaultConfig = {
|
|||
live2dOption_rotation: 0,
|
||||
groupAdminPage: false,
|
||||
enablePrivateChat: false,
|
||||
groupWhitelist: [],
|
||||
groupBlacklist: [],
|
||||
whitelist: [],
|
||||
blacklist: [],
|
||||
ttsRegex: '/匹配规则/匹配模式',
|
||||
slackUserToken: '',
|
||||
slackBotUserToken: '',
|
||||
|
|
@ -123,7 +124,8 @@ const defaultConfig = {
|
|||
azureTTSEmotion: false,
|
||||
enhanceAzureTTSEmotion: false,
|
||||
autoJapanese: false,
|
||||
version: 'v2.6.0'
|
||||
enableGenerateContents: false,
|
||||
version: 'v2.6.2'
|
||||
}
|
||||
const _path = process.cwd()
|
||||
let config = {}
|
||||
|
|
|
|||
|
|
@ -468,12 +468,18 @@ export async function convertFaces (msg, handleAt = false, e) {
|
|||
handleAt = e?.isGroup && handleAt
|
||||
let groupMembers
|
||||
let groupCardQQMap = {}
|
||||
if (handleAt && typeof e.group.getMemberMap === 'function') {
|
||||
groupMembers = await e.group.getMemberMap()
|
||||
for (let key of groupMembers.keys()) {
|
||||
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
||||
if (handleAt) {
|
||||
try {
|
||||
groupMembers = await e.group.getMemberMap()
|
||||
} catch (err) {
|
||||
console.error(`Failed to get group members: ${err}`)
|
||||
}
|
||||
}
|
||||
if (groupMembers) {
|
||||
for (let key of groupMembers.keys()) {
|
||||
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
||||
}
|
||||
}
|
||||
}
|
||||
let tmpMsg = ''
|
||||
let tmpFace = ''
|
||||
let tmpAt = ''
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import crypto from 'crypto'
|
||||
import { getDefaultReplySetting, mkdirs } from '../common.js'
|
||||
import { Config } from '../config.js'
|
||||
import { translate } from '../translate.js'
|
||||
|
||||
let sdk
|
||||
try {
|
||||
|
|
@ -20,20 +21,29 @@ async function generateAudio (text, option = {}, ssml = '') {
|
|||
let filename = `${_path}/data/chatgpt/tts/azure/${crypto.randomUUID()}.wav`
|
||||
let audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename)
|
||||
let synthesizer
|
||||
let speaker = option?.speaker || '随机'
|
||||
let context = text
|
||||
// 打招呼用
|
||||
if (speaker === '随机') {
|
||||
speaker = supportConfigurations[Math.floor(Math.random() * supportConfigurations.length)].code
|
||||
let languagePrefix = supportConfigurations.find(config => config.code === speaker).languageDetail.charAt(0)
|
||||
languagePrefix = languagePrefix.startsWith('E') ? '英' : languagePrefix
|
||||
context = (await translate(context, languagePrefix)).replace('\n', '')
|
||||
}
|
||||
if (ssml) {
|
||||
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
||||
await speakSsmlAsync(synthesizer, ssml)
|
||||
} else {
|
||||
speechConfig.speechSynthesisLanguage = option?.language || 'zh-CN'
|
||||
logger.info('using speaker: ' + option?.speaker || 'zh-CN-YunyeNeural')
|
||||
speechConfig.speechSynthesisVoiceName = option?.speaker || 'zh-CN-YunyeNeural'
|
||||
} else { // 打招呼用
|
||||
speechConfig.speechSynthesisLanguage = option?.language || supportConfigurations.find(config => config.code === speaker).language
|
||||
speechConfig.speechSynthesisVoiceName = speaker
|
||||
logger.info('using speaker: ' + speaker)
|
||||
logger.info('using language: ' + speechConfig.speechSynthesisLanguage)
|
||||
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
||||
await speakTextAsync(synthesizer, text)
|
||||
await speakTextAsync(synthesizer, context)
|
||||
}
|
||||
|
||||
console.log('synthesis finished.')
|
||||
synthesizer.close()
|
||||
synthesizer = undefined
|
||||
return filename
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +83,27 @@ async function speakSsmlAsync (synthesizer, ssml) {
|
|||
})
|
||||
}
|
||||
async function generateSsml (text, option = {}) {
|
||||
const voiceName = option.speaker || 'zh-CN-YunyeNeural'
|
||||
const expressAs = option.emotion ? `<mstts:express-as style="${option.emotion}" styledegree="${option.emotionDegree || 1}">` : ''
|
||||
let speaker = option?.speaker || '随机'
|
||||
let emotionDegree, role, emotion
|
||||
// 打招呼用
|
||||
if (speaker === '随机') {
|
||||
role = supportConfigurations[Math.floor(Math.random() * supportConfigurations.length)]
|
||||
speaker = role.code
|
||||
if (role?.emotion) {
|
||||
const keys = Object.keys(role.emotion)
|
||||
emotion = keys[Math.floor(Math.random() * keys.length)]
|
||||
}
|
||||
logger.info('using speaker: ' + speaker)
|
||||
logger.info('using emotion: ' + emotion)
|
||||
emotionDegree = 2
|
||||
} else {
|
||||
emotion = option.emotion
|
||||
emotionDegree = option.emotionDegree
|
||||
}
|
||||
const expressAs = emotion !== undefined ? `<mstts:express-as style="${emotion}" styledegree="${emotionDegree || 1}">` : ''
|
||||
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
|
||||
xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">
|
||||
<voice name="${voiceName}">
|
||||
<voice name="${speaker}">
|
||||
${expressAs}${text}${expressAs ? '</mstts:express-as>' : ''}
|
||||
</voice>
|
||||
</speak>`
|
||||
|
|
@ -91,7 +117,7 @@ async function getEmotionPrompt (e) {
|
|||
let emotionPrompt = ''
|
||||
let ttsRoleAzure = userReplySetting.ttsRoleAzure
|
||||
const configuration = Config.ttsMode === 'azure' ? supportConfigurations.find(config => config.code === ttsRoleAzure) : ''
|
||||
if (configuration !== '' && configuration.emotion) {
|
||||
if (configuration !== '' && configuration?.emotion) {
|
||||
// 0-1 感觉没啥区别,说实话只有1和2听得出差别。。
|
||||
emotionPrompt = `\n在回复的最开始使用[]在其中表示你这次回复的情绪风格和程度(1-2),最小单位0.1
|
||||
\n例如:['angry',2]表示你极度愤怒
|
||||
|
|
@ -110,28 +136,32 @@ export const supportConfigurations = [
|
|||
name: '晓北',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(东北官话,简体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '晓北-女-中文(东北官话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-henan-YundengNeural',
|
||||
name: '云登',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(中原官话河南,简体)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '云登-男-中文(中原官话河南,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-shaanxi-XiaoniNeural',
|
||||
name: '晓妮',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(中原官话陕西,简体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '晓妮-女-中文(中原官话陕西,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-henan-YundengNeural',
|
||||
name: '云翔',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(冀鲁官话,简体)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '云翔-男-中文(冀鲁官话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoxiaoNeural',
|
||||
|
|
@ -157,7 +187,8 @@ export const supportConfigurations = [
|
|||
'poetry-reading': '读诗时带情感和节奏的语气',
|
||||
sad: '表达悲伤语气',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓晓-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunxiNeural',
|
||||
|
|
@ -178,7 +209,8 @@ export const supportConfigurations = [
|
|||
newscast: '用于新闻播报,表现出庄重、严谨的语气',
|
||||
sad: '表达悲伤、失落的语气',
|
||||
serious: '表现出认真、严肃的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '云希-男-中文 (普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunyangNeural',
|
||||
|
|
@ -190,7 +222,8 @@ export const supportConfigurations = [
|
|||
customerservice: '以亲切友好的语气为客户提供支持',
|
||||
'narration-professional': '以专业、稳重的语气讲述',
|
||||
'newscast-casual': '以轻松自然的语气播报新闻'
|
||||
}
|
||||
},
|
||||
roleInfo: '云扬-男-中文 (普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunyeNeural',
|
||||
|
|
@ -207,7 +240,8 @@ export const supportConfigurations = [
|
|||
fearful: '表达害怕和不安的语气',
|
||||
sad: '表达悲伤和失落的语气',
|
||||
serious: '以认真和严肃的态度说话'
|
||||
}
|
||||
},
|
||||
roleInfo: '云野-男-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoshuangNeural',
|
||||
|
|
@ -215,37 +249,40 @@ export const supportConfigurations = [
|
|||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '女',
|
||||
emotion: {
|
||||
chat: '表达轻松随意的语气'
|
||||
}
|
||||
emotion: { chat: '表达轻松随意的语气' },
|
||||
roleInfo: '晓双-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoyouNeural',
|
||||
name: '晓悠',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '晓悠-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoqiuNeural',
|
||||
name: '晓秋',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '晓秋-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaochenNeural',
|
||||
name: '晓辰',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '晓辰-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoyanNeural',
|
||||
name: '晓颜',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '晓颜-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaomoNeural',
|
||||
|
|
@ -266,7 +303,8 @@ export const supportConfigurations = [
|
|||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||
sad: '表达悲伤语气',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓墨-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoxuanNeural',
|
||||
|
|
@ -283,7 +321,8 @@ export const supportConfigurations = [
|
|||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓萱-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaohanNeural',
|
||||
|
|
@ -302,7 +341,8 @@ export const supportConfigurations = [
|
|||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||
sad: '表达悲伤语气',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓涵-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoruiNeural',
|
||||
|
|
@ -315,7 +355,8 @@ export const supportConfigurations = [
|
|||
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||
sad: '表达悲伤语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓睿-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaomengNeural',
|
||||
|
|
@ -323,9 +364,8 @@ export const supportConfigurations = [
|
|||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '女',
|
||||
emotion: {
|
||||
chat: '表达轻松随意的语气'
|
||||
}
|
||||
emotion: { chat: '表达轻松随意的语气' },
|
||||
roleInfo: '晓梦-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaoyiNeural',
|
||||
|
|
@ -340,7 +380,8 @@ export const supportConfigurations = [
|
|||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||
sad: '表达悲伤语气',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓伊-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-XiaozhenNeural',
|
||||
|
|
@ -355,7 +396,8 @@ export const supportConfigurations = [
|
|||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||
sad: '表达悲伤语气',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '晓甄-女-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunfengNeural',
|
||||
|
|
@ -371,14 +413,16 @@ export const supportConfigurations = [
|
|||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||
sad: '表达悲伤语气',
|
||||
serious: '严肃、命令的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '云枫-男-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunhaoNeural',
|
||||
name: '云皓',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(普通话,简体)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '云皓-男-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunjianNeural',
|
||||
|
|
@ -390,7 +434,8 @@ export const supportConfigurations = [
|
|||
'narration-relaxed': '以轻松、自然的语气进行叙述',
|
||||
'sports-commentary': '在解说体育比赛时,使用专业而自信的语气',
|
||||
'sports-commentary-excited': '在解说激动人心的体育比赛时,使用兴奋和激动的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '云健-男-中文(普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunxiaNeural',
|
||||
|
|
@ -404,7 +449,8 @@ export const supportConfigurations = [
|
|||
cheerful: '表达积极愉快的语气',
|
||||
fearful: '表达害怕、紧张的语气',
|
||||
sad: '表达悲伤和失落的语气'
|
||||
}
|
||||
},
|
||||
roleInfo: '云夏-男-中文 (普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN-YunzeNeural',
|
||||
|
|
@ -422,105 +468,120 @@ export const supportConfigurations = [
|
|||
fearful: '表达害怕、不安的情绪',
|
||||
sad: '用悲伤的语气表达悲伤和失落',
|
||||
serious: '以严肃的语气和态度表现出对事情的重视和认真对待'
|
||||
}
|
||||
},
|
||||
roleInfo: '云泽-男-中文 (普通话,简体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-HK-HiuGaaiNeural',
|
||||
name: '曉佳',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(粤语,繁体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '曉佳-女-中文(粤语,繁体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-HK-HiuMaanNeural',
|
||||
name: '曉曼',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(粤语,繁体)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '曉曼-女-中文(粤语,繁体)'
|
||||
},
|
||||
{
|
||||
code: 'zh-HK-WanLungNeural',
|
||||
name: '雲龍',
|
||||
language: 'zh-CN',
|
||||
languageDetail: '中文(粤语,繁体)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '雲龍-男-中文(粤语,繁体)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-AbbiNeural',
|
||||
name: 'Abbi',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Abbi-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-AlfieNeural',
|
||||
name: 'Alfie',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Alfie-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-BellaNeural',
|
||||
name: 'Bella',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Bella-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-ElliotNeural',
|
||||
name: 'Elliot',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Elliot-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-EthanNeural',
|
||||
name: 'Ethan',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Ethan-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-HollieNeural',
|
||||
name: 'Hollie',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Hollie-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-LibbyNeural',
|
||||
name: 'Libby',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Libby-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-MaisieNeural',
|
||||
name: 'Maisie',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Maisie-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-NoahNeural',
|
||||
name: 'Noah',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Noah-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-OliverNeural',
|
||||
name: 'Oliver',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Oliver-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-OliviaNeural',
|
||||
name: 'Olivia',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Olivia-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-RyanNeural',
|
||||
|
|
@ -528,11 +589,8 @@ export const supportConfigurations = [
|
|||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male',
|
||||
emotion: {
|
||||
chat: '表达轻松随意的语气',
|
||||
cheerful: '表达积极愉快的语气'
|
||||
|
||||
}
|
||||
emotion: { chat: '表达轻松随意的语气', cheerful: '表达积极愉快的语气' },
|
||||
roleInfo: 'Ryan-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-SoniaNeural',
|
||||
|
|
@ -540,46 +598,48 @@ export const supportConfigurations = [
|
|||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'female',
|
||||
emotion: {
|
||||
cheerful: '表达积极愉快的语气',
|
||||
sad: '表达悲伤语气'
|
||||
|
||||
}
|
||||
emotion: { cheerful: '表达积极愉快的语气', sad: '表达悲伤语气' },
|
||||
roleInfo: 'Sonia-女-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'en-GB-ThomasNeural',
|
||||
name: 'Thomas',
|
||||
language: 'en-GB',
|
||||
languageDetail: '英语(英国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Thomas-男-英语(英国)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-AoiNeural',
|
||||
name: '葵',
|
||||
language: 'ja-JP',
|
||||
languageDetail: '日语(日本)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '葵-女-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-DaichiNeural',
|
||||
name: '大地',
|
||||
language: 'ja-JP',
|
||||
languageDetail: '日语(日本)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '大地-男-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-KeitaNeural',
|
||||
name: '慶太',
|
||||
language: 'ja-JP',
|
||||
languageDetail: '日语(日本)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '慶太-男-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-MayuNeural',
|
||||
name: '真由',
|
||||
language: 'ja-JP',
|
||||
languageDetail: '日语(日本)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '真由-女-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-NanamiNeural',
|
||||
|
|
@ -591,49 +651,56 @@ export const supportConfigurations = [
|
|||
chat: '表达轻松随意的语气',
|
||||
cheerful: '表达积极愉快的语气',
|
||||
customerservice: '以友好热情的语气为客户提供支持'
|
||||
}
|
||||
},
|
||||
roleInfo: '七海-女-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-NaokiNeural',
|
||||
name: '直樹',
|
||||
language: 'ja-JP',
|
||||
languageDetail: '日语(日本)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: '直樹-男-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'ja-JP-ShioriNeural',
|
||||
name: '栞',
|
||||
language: 'ja-JP',
|
||||
languageDetail: '日语(日本)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: '栞-女-日语(日本)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-AIGenerate1Neural1',
|
||||
name: 'AI Generate 1',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: 'AI Generate 1-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-AIGenerate2Neural1',
|
||||
name: 'AI Generate 2',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: 'AI Generate 2-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-AmberNeural',
|
||||
name: 'Amber',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: 'Amber-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-AnaNeural',
|
||||
name: 'Ana',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '女性、儿童'
|
||||
gender: '女性、儿童',
|
||||
roleInfo: 'Ana-女性、儿童-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-AriaNeural',
|
||||
|
|
@ -658,35 +725,40 @@ export const supportConfigurations = [
|
|||
'narration-professional': '以专业、客观的语气朗读内容',
|
||||
'newscast-casual': '以通用、随意的语气发布一般新闻',
|
||||
'newscast-formal': '以正式、自信和权威的语气发布新闻'
|
||||
}
|
||||
},
|
||||
roleInfo: 'Aria-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-AshleyNeural',
|
||||
name: 'Ashley',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: 'Ashley-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-BrandonNeural',
|
||||
name: 'Brandon',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: 'Brandon-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-ChristopherNeural',
|
||||
name: 'Christopher',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: 'Christopher-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-CoraNeural',
|
||||
name: 'Cora',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: 'Cora-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-DavisNeural',
|
||||
|
|
@ -705,21 +777,24 @@ export const supportConfigurations = [
|
|||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||
}
|
||||
},
|
||||
roleInfo: 'Davis-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-ElizabethNeural',
|
||||
name: 'Elizabeth',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '女'
|
||||
gender: '女',
|
||||
roleInfo: 'Elizabeth-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-EricNeural',
|
||||
name: 'Eric',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: 'Eric-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-GuyNeural',
|
||||
|
|
@ -739,15 +814,16 @@ export const supportConfigurations = [
|
|||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔',
|
||||
newscast: '以正式专业的语气叙述新闻'
|
||||
|
||||
}
|
||||
},
|
||||
roleInfo: 'Guy-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-JacobNeural',
|
||||
name: 'Jacob',
|
||||
language: 'en-US',
|
||||
languageDetail: 'English (United States)',
|
||||
gender: '男'
|
||||
gender: '男',
|
||||
roleInfo: 'Jacob-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-JaneNeural',
|
||||
|
|
@ -766,7 +842,8 @@ export const supportConfigurations = [
|
|||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||
}
|
||||
},
|
||||
roleInfo: 'Jane-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-JasonNeural',
|
||||
|
|
@ -785,14 +862,8 @@ export const supportConfigurations = [
|
|||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'en-US-JennyMultilingualNeural3',
|
||||
name: 'Jenny',
|
||||
language: 'en-US',
|
||||
languageDetail: '英语(美国)',
|
||||
gender: 'female'
|
||||
},
|
||||
roleInfo: 'Jason-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-JennyNeural',
|
||||
|
|
@ -815,22 +886,24 @@ export const supportConfigurations = [
|
|||
chat: '表达轻松随意的语气',
|
||||
customerservice: '以友好热情的语气为客户提供支持',
|
||||
newscast: '以正式专业的语气叙述新闻'
|
||||
|
||||
}
|
||||
},
|
||||
roleInfo: 'Jenny-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-MichelleNeural',
|
||||
name: 'Michelle',
|
||||
language: 'en-US',
|
||||
languageDetail: '英语(美国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Michelle-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-MonicaNeural',
|
||||
name: 'Monica',
|
||||
language: 'en-US',
|
||||
languageDetail: '英语(美国)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Monica-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-NancyNeural',
|
||||
|
|
@ -849,14 +922,16 @@ export const supportConfigurations = [
|
|||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||
}
|
||||
},
|
||||
roleInfo: 'Nancy-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-RogerNeural',
|
||||
name: 'Roger',
|
||||
language: 'en-US',
|
||||
languageDetail: '英语(美国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Roger-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-SaraNeural',
|
||||
|
|
@ -875,15 +950,16 @@ export const supportConfigurations = [
|
|||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||
|
||||
}
|
||||
},
|
||||
roleInfo: 'Sara-女-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-SteffanNeural',
|
||||
name: 'Steffan',
|
||||
language: 'en-US',
|
||||
languageDetail: '英语(美国)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Steffan-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-US-TonyNeural',
|
||||
|
|
@ -902,21 +978,24 @@ export const supportConfigurations = [
|
|||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||
unfriendly: '表达一种冷淡无情的语气',
|
||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||
}
|
||||
},
|
||||
roleInfo: 'Tony-男-英语(美国)'
|
||||
},
|
||||
{
|
||||
code: 'en-IN-NeerjaNeural',
|
||||
name: 'Neerja',
|
||||
language: 'en',
|
||||
languageDetail: '英语(印度)',
|
||||
gender: 'female'
|
||||
gender: 'female',
|
||||
roleInfo: 'Neerja-女-英语(印度)'
|
||||
},
|
||||
{
|
||||
code: 'en-IN-PrabhatNeural',
|
||||
name: 'Prabhat',
|
||||
language: 'en',
|
||||
languageDetail: '英语(印度)',
|
||||
gender: 'male'
|
||||
gender: 'male',
|
||||
roleInfo: 'Prabhat-男-英语(印度)'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ try {
|
|||
}
|
||||
|
||||
export class Tokenizer {
|
||||
async getTodayHistory (groupId, date = new Date()) {
|
||||
async getHistory (groupId, date = new Date(), duration = 0) {
|
||||
if (!groupId) {
|
||||
throw new Error('no valid group id')
|
||||
}
|
||||
|
|
@ -29,11 +29,22 @@ export class Tokenizer {
|
|||
}
|
||||
return 0
|
||||
}
|
||||
// Get the current timestamp
|
||||
let currentTime = date.getTime()
|
||||
|
||||
// Step 2: Set the hours, minutes, seconds, and milliseconds to 0
|
||||
date.setHours(0, 0, 0, 0)
|
||||
|
||||
// Step 3: Calculate the timestamp representing the start of the specified date
|
||||
const startOfSpecifiedDate = date.getTime()
|
||||
// duration represents the number of hours to go back
|
||||
// if duration is 0, keeping the original date (start of today)
|
||||
let startOfSpecifiedDate = date.getTime()
|
||||
// if duration > 0, go back to the specified number of hours
|
||||
if (duration > 0) {
|
||||
// duration should be in range [0, 24]
|
||||
duration = Math.min(duration, 24)
|
||||
startOfSpecifiedDate = currentTime - (duration * 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
// Step 4: Get the end of the specified date by adding 24 hours (in milliseconds)
|
||||
const endOfSpecifiedDate = startOfSpecifiedDate + (24 * 60 * 60 * 1000)
|
||||
|
|
@ -56,12 +67,14 @@ export class Tokenizer {
|
|||
return chats
|
||||
}
|
||||
|
||||
async getTodayKeywordTopK (groupId, topK = 100) {
|
||||
async getKeywordTopK (groupId, topK = 100, duration = 0) {
|
||||
if (!nodejieba) {
|
||||
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
||||
}
|
||||
let chats = await this.getTodayHistory(groupId)
|
||||
logger.mark(`聊天记录拉去完成,获取到今日内${chats.length}条聊天记录,准备分词中`)
|
||||
// duration represents the number of hours to go back, should in range [0, 24]
|
||||
let chats = await this.getHistory(groupId, new Date(), duration)
|
||||
let duration_str = duration > 0 ? `${duration}小时` : '今日'
|
||||
logger.mark(`聊天记录拉取完成,获取到${duration_str}内${chats.length}条聊天记录,准备分词中`)
|
||||
|
||||
const _path = process.cwd()
|
||||
let stopWordsPath = `${_path}/plugins/chatgpt-plugin/utils/wordcloud/cn_stopwords.txt`
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Tokenizer } from './tokenizer.js'
|
||||
import { render } from '../common.js'
|
||||
|
||||
export async function makeWordcloud (e, groupId) {
|
||||
export async function makeWordcloud (e, groupId, duration = 0) {
|
||||
let tokenizer = new Tokenizer()
|
||||
let topK = await tokenizer.getTodayKeywordTopK(groupId, 100)
|
||||
let topK = await tokenizer.getKeywordTopK(groupId, 100, duration)
|
||||
let list = JSON.stringify(topK)
|
||||
// let list = topK
|
||||
console.log(list)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue