diff --git a/src/common/config.ts b/src/common/config.ts index a4d73fd..b6e1f4c 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -52,6 +52,7 @@ export class ConfigUtil { autoDeleteFile: false, autoDeleteFileSecond: 60, enablePoke: false, + musicSignUrl: '', } if (!fs.existsSync(this.configPath)) { diff --git a/src/common/types.ts b/src/common/types.ts index 58fd136..82e3a32 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -28,6 +28,7 @@ export interface Config { autoDeleteFileSecond?: number ffmpeg?: string // ffmpeg路径 enablePoke?: boolean + musicSignUrl?: string } export interface LLOneBotError { diff --git a/src/common/utils/sign.ts b/src/common/utils/sign.ts new file mode 100644 index 0000000..5fd3032 --- /dev/null +++ b/src/common/utils/sign.ts @@ -0,0 +1,37 @@ +import { log } from './log' + +export interface IdMusicSignPostData { + type: 'qq' | '163' + id: string | number +} + +export interface CustomMusicSignPostData { + type: 'custom' + url: string + audio: string + title: string + image?: string + singer?: string +} + +export type MusicSignPostData = IdMusicSignPostData | CustomMusicSignPostData + +export class MusicSign { + private readonly url: string + + constructor(url: string) { + this.url = url + } + + async sign(postData: MusicSignPostData): Promise { + const resp = await fetch(this.url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(postData), + }) + if (!resp.ok) throw new Error(resp.statusText) + const data = await resp.text() + log('音乐消息生成成功', data) + return data + } +} diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index 134b5d6..0e03dd0 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -310,7 +310,7 @@ export class SendMsgElementConstructor { } } - static ark(data: any): SendArkElement { + static ark(data: string): SendArkElement { return { elementType: ElementType.ARK, elementId: '', diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 8ec7f4d..c516b08 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -15,7 +15,9 @@ import { OB11MessageCustomMusic, OB11MessageData, OB11MessageDataType, + OB11MessageJson, OB11MessageMixType, + OB11MessageMusic, OB11MessageNode, OB11PostSendMsg, } from '../../types' @@ -26,12 +28,13 @@ import { ActionName, BaseCheckResult } from '../types' import * as fs from 'node:fs' import { decodeCQCode } from '../../cqcode' import { dbUtil } from '../../../common/db' -import { ALLOW_SEND_TEMP_MSG } from '../../../common/config' +import { ALLOW_SEND_TEMP_MSG, getConfigUtil } from '../../../common/config' import { log } from '../../../common/utils/log' import { sleep } from '../../../common/utils/helper' import { uri2local } from '../../../common/utils' import { crychic } from '../../../ntqqapi/external/crychic' import { NTQQGroupApi } from '../../../ntqqapi/api' +import { CustomMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign' function checkSendMessage(sendMsgList: OB11MessageData[]) { function checkUri(uri: string): boolean { @@ -302,6 +305,13 @@ export class SendMsg extends BaseAction { message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', } } + const musicNum = this.getSpecialMsgNum(payload, OB11MessageDataType.music) + if (musicNum && messages.length > 1) { + return { + valid: false, + message: '音乐消息不可以和其他消息混在一起发送', + } + } if (payload.message_type !== 'private' && payload.group_id && !(await getGroup(payload.group_id))) { return { valid: false, @@ -374,19 +384,32 @@ export class SendMsg extends BaseAction { } catch (e) { throw '发送转发消息失败 ' + e.toString() } - } else { - if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) { - const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic - if (music) { - const { url, audio, title, content, image } = music.data - const selfPeer: Peer = { peerUid: selfInfo.uid, chatType: ChatType.friend } - // 搞不定! - // const musicMsg = await this.send(selfPeer, [this.genMusicElement(url, audio, title, content, image)], [], false) - // 转发 - // const res = await NTQQApi.forwardMsg(selfPeer, peer, [musicMsg.msgId]) - // log("转发音乐消息成功", res); - // return {message_id: musicMsg.msgShortId} + } else if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) { + const music = messages[0] as OB11MessageMusic + if (music) { + const { musicSignUrl } = getConfigUtil().getConfig() + if (!musicSignUrl) { + throw '音乐签名地址未配置' } + const { type } = music.data + if (!['qq', '163', 'custom'].includes(type)) { + throw `不支持的音乐类型 ${type}` + } + const postData: MusicSignPostData = { ...music.data } + if (type === 'custom' && music.data.content) { + ;(postData as CustomMusicSignPostData).singer = music.data.content + delete (postData as OB11MessageCustomMusic['data']).content + } + let jsonContent: string + try { + jsonContent = await new MusicSign(musicSignUrl).sign(postData) + } catch (e) { + throw `签名音乐消息失败:${e}` + } + messages[0] = { + type: OB11MessageDataType.json, + data: { data: jsonContent }, + } as OB11MessageJson } } // log("send msg:", peer, sendElements) @@ -555,41 +578,41 @@ export class SendMsg extends BaseAction { } } - private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement { - const musicJson = { - app: 'com.tencent.structmsg', - config: { - ctime: 1709689928, - forward: 1, - token: '5c1e4905f926dd3a64a4bd3841460351', - type: 'normal', - }, - extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin }, - meta: { - news: { - action: '', - android_pkg_name: '', - app_type: 1, - appid: 100497308, - ctime: 1709689928, - desc: content || title, - jumpUrl: url, - musicUrl: audio, - preview: image, - source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0', - source_url: '', - tag: 'QQ音乐', - title: title, - uin: selfInfo.uin, - }, - }, - prompt: content || title, - ver: '0.0.0.1', - view: 'news', - } + // private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement { + // const musicJson = { + // app: 'com.tencent.structmsg', + // config: { + // ctime: 1709689928, + // forward: 1, + // token: '5c1e4905f926dd3a64a4bd3841460351', + // type: 'normal', + // }, + // extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin }, + // meta: { + // news: { + // action: '', + // android_pkg_name: '', + // app_type: 1, + // appid: 100497308, + // ctime: 1709689928, + // desc: content || title, + // jumpUrl: url, + // musicUrl: audio, + // preview: image, + // source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0', + // source_url: '', + // tag: 'QQ音乐', + // title: title, + // uin: selfInfo.uin, + // }, + // }, + // prompt: content || title, + // ver: '0.0.0.1', + // view: 'news', + // } - return SendMsgElementConstructor.ark(musicJson) - } + // return SendMsgElementConstructor.ark(musicJson) + // } } export default SendMsg diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 8b9040c..c3393a9 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -1,5 +1,6 @@ import { PicSubType, RawMessage } from '../ntqqapi/types' import { EventType } from './event/OB11BaseEvent' +import { IdMusicSignPostData, CustomMusicSignPostData } from '../common/utils/sign' export interface OB11User { user_id: number @@ -219,21 +220,21 @@ export interface OB11MessageNode { } } +export interface OB11MessageIdMusic { + type: OB11MessageDataType.music + data: IdMusicSignPostData +} + export interface OB11MessageCustomMusic { type: OB11MessageDataType.music - data: { - type: 'custom' - url: string - audio: string - title: string - content?: string - image?: string - } + data: Omit & { content?: string } } +export type OB11MessageMusic = OB11MessageIdMusic | OB11MessageCustomMusic + export interface OB11MessageJson { type: OB11MessageDataType.json - data: { config: { token: string } } & any + data: { data: string /* , config: { token: string } */ } } export type OB11MessageData = @@ -247,6 +248,7 @@ export type OB11MessageData = | OB11MessageFile | OB11MessageVideo | OB11MessageNode + | OB11MessageIdMusic | OB11MessageCustomMusic | OB11MessageJson | OB11MessagePoke diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 557d120..259941f 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -95,7 +95,9 @@ async function onSettingWindowCreated(view: Element) { HTTP 事件上报密钥
- +
@@ -158,9 +160,17 @@ async function onSettingWindowCreated(view: Element) { ), SettingItem( 'ffmpeg 路径,发送语音、视频需要,同时保证ffprobe和ffmpeg在一起', - ` 下载地址 , 路径:${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'}`, + ` 下载地址 , 路径:${ + !isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定' + }`, SettingButton('选择ffmpeg', 'config-ffmpeg-select'), ), + SettingItem( + '音乐卡片签名地址', + null, + `
`, + 'config-musicSignUrl', + ), SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), ]), SettingList([