Merge pull request #206 from LLOneBot/feat/music-card

feat: music card sign
This commit is contained in:
手瓜一十雪 2024-04-30 13:45:15 +08:00 committed by GitHub
commit 25c3d51d69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 133 additions and 59 deletions

View File

@ -52,6 +52,7 @@ export class ConfigUtil {
autoDeleteFile: false, autoDeleteFile: false,
autoDeleteFileSecond: 60, autoDeleteFileSecond: 60,
enablePoke: false, enablePoke: false,
musicSignUrl: '',
} }
if (!fs.existsSync(this.configPath)) { if (!fs.existsSync(this.configPath)) {

View File

@ -28,6 +28,7 @@ export interface Config {
autoDeleteFileSecond?: number autoDeleteFileSecond?: number
ffmpeg?: string // ffmpeg路径 ffmpeg?: string // ffmpeg路径
enablePoke?: boolean enablePoke?: boolean
musicSignUrl?: string
} }
export interface LLOneBotError { export interface LLOneBotError {

37
src/common/utils/sign.ts Normal file
View File

@ -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<string> {
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
}
}

View File

@ -310,7 +310,7 @@ export class SendMsgElementConstructor {
} }
} }
static ark(data: any): SendArkElement { static ark(data: string): SendArkElement {
return { return {
elementType: ElementType.ARK, elementType: ElementType.ARK,
elementId: '', elementId: '',

View File

@ -15,7 +15,9 @@ import {
OB11MessageCustomMusic, OB11MessageCustomMusic,
OB11MessageData, OB11MessageData,
OB11MessageDataType, OB11MessageDataType,
OB11MessageJson,
OB11MessageMixType, OB11MessageMixType,
OB11MessageMusic,
OB11MessageNode, OB11MessageNode,
OB11PostSendMsg, OB11PostSendMsg,
} from '../../types' } from '../../types'
@ -26,12 +28,13 @@ import { ActionName, BaseCheckResult } from '../types'
import * as fs from 'node:fs' import * as fs from 'node:fs'
import { decodeCQCode } from '../../cqcode' import { decodeCQCode } from '../../cqcode'
import { dbUtil } from '../../../common/db' 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 { log } from '../../../common/utils/log'
import { sleep } from '../../../common/utils/helper' import { sleep } from '../../../common/utils/helper'
import { uri2local } from '../../../common/utils' import { uri2local } from '../../../common/utils'
import { crychic } from '../../../ntqqapi/external/crychic' import { crychic } from '../../../ntqqapi/external/crychic'
import { NTQQGroupApi } from '../../../ntqqapi/api' import { NTQQGroupApi } from '../../../ntqqapi/api'
import { CustomMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
function checkSendMessage(sendMsgList: OB11MessageData[]) { function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean { function checkUri(uri: string): boolean {
@ -302,6 +305,13 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', 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))) { if (payload.message_type !== 'private' && payload.group_id && !(await getGroup(payload.group_id))) {
return { return {
valid: false, valid: false,
@ -374,19 +384,32 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} catch (e) { } catch (e) {
throw '发送转发消息失败 ' + e.toString() throw '发送转发消息失败 ' + e.toString()
} }
} else { } else if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) {
if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) { const music = messages[0] as OB11MessageMusic
const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic if (music) {
if (music) { const { musicSignUrl } = getConfigUtil().getConfig()
const { url, audio, title, content, image } = music.data if (!musicSignUrl) {
const selfPeer: Peer = { peerUid: selfInfo.uid, chatType: ChatType.friend } throw '音乐签名地址未配置'
// 搞不定!
// 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}
} }
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) // log("send msg:", peer, sendElements)
@ -555,41 +578,41 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
} }
private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement { // private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement {
const musicJson = { // const musicJson = {
app: 'com.tencent.structmsg', // app: 'com.tencent.structmsg',
config: { // config: {
ctime: 1709689928, // ctime: 1709689928,
forward: 1, // forward: 1,
token: '5c1e4905f926dd3a64a4bd3841460351', // token: '5c1e4905f926dd3a64a4bd3841460351',
type: 'normal', // type: 'normal',
}, // },
extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin }, // extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin },
meta: { // meta: {
news: { // news: {
action: '', // action: '',
android_pkg_name: '', // android_pkg_name: '',
app_type: 1, // app_type: 1,
appid: 100497308, // appid: 100497308,
ctime: 1709689928, // ctime: 1709689928,
desc: content || title, // desc: content || title,
jumpUrl: url, // jumpUrl: url,
musicUrl: audio, // musicUrl: audio,
preview: image, // preview: image,
source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0', // source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0',
source_url: '', // source_url: '',
tag: 'QQ音乐', // tag: 'QQ音乐',
title: title, // title: title,
uin: selfInfo.uin, // uin: selfInfo.uin,
}, // },
}, // },
prompt: content || title, // prompt: content || title,
ver: '0.0.0.1', // ver: '0.0.0.1',
view: 'news', // view: 'news',
} // }
return SendMsgElementConstructor.ark(musicJson) // return SendMsgElementConstructor.ark(musicJson)
} // }
} }
export default SendMsg export default SendMsg

View File

@ -1,5 +1,6 @@
import { PicSubType, RawMessage } from '../ntqqapi/types' import { PicSubType, RawMessage } from '../ntqqapi/types'
import { EventType } from './event/OB11BaseEvent' import { EventType } from './event/OB11BaseEvent'
import { IdMusicSignPostData, CustomMusicSignPostData } from '../common/utils/sign'
export interface OB11User { export interface OB11User {
user_id: number user_id: number
@ -219,21 +220,21 @@ export interface OB11MessageNode {
} }
} }
export interface OB11MessageIdMusic {
type: OB11MessageDataType.music
data: IdMusicSignPostData
}
export interface OB11MessageCustomMusic { export interface OB11MessageCustomMusic {
type: OB11MessageDataType.music type: OB11MessageDataType.music
data: { data: Omit<CustomMusicSignPostData, 'singer'> & { content?: string }
type: 'custom'
url: string
audio: string
title: string
content?: string
image?: string
}
} }
export type OB11MessageMusic = OB11MessageIdMusic | OB11MessageCustomMusic
export interface OB11MessageJson { export interface OB11MessageJson {
type: OB11MessageDataType.json type: OB11MessageDataType.json
data: { config: { token: string } } & any data: { data: string /* , config: { token: string } */ }
} }
export type OB11MessageData = export type OB11MessageData =
@ -247,6 +248,7 @@ export type OB11MessageData =
| OB11MessageFile | OB11MessageFile
| OB11MessageVideo | OB11MessageVideo
| OB11MessageNode | OB11MessageNode
| OB11MessageIdMusic
| OB11MessageCustomMusic | OB11MessageCustomMusic
| OB11MessageJson | OB11MessageJson
| OB11MessagePoke | OB11MessagePoke

View File

@ -95,7 +95,9 @@ async function onSettingWindowCreated(view: Element) {
<setting-text>HTTP </setting-text> <setting-text>HTTP </setting-text>
</div> </div>
<div class="q-input"> <div class="q-input">
<input id="config-ob11-httpSecret" class="q-input__inner" data-config-key="ob11.httpSecret" type="text" value="${config.ob11.httpSecret}" placeholder="未设置" /> <input id="config-ob11-httpSecret" class="q-input__inner" data-config-key="ob11.httpSecret" type="text" value="${
config.ob11.httpSecret
}" placeholder="" />
</div> </div>
</setting-item> </setting-item>
<setting-item data-direction="row"> <setting-item data-direction="row">
@ -158,9 +160,17 @@ async function onSettingWindowCreated(view: Element) {
), ),
SettingItem( SettingItem(
'ffmpeg 路径发送语音、视频需要同时保证ffprobe和ffmpeg在一起', 'ffmpeg 路径发送语音、视频需要同时保证ffprobe和ffmpeg在一起',
` <a href="javascript:LiteLoader.api.openExternal(\'https://llonebot.github.io/zh-CN/guide/ffmpeg\');">下载地址</a> <span id="config-ffmpeg-path-text">, 路径:${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'}</span>`, ` <a href="javascript:LiteLoader.api.openExternal(\'https://llonebot.github.io/zh-CN/guide/ffmpeg\');">下载地址</a> <span id="config-ffmpeg-path-text">, 路径:${
!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'
}</span>`,
SettingButton('选择ffmpeg', 'config-ffmpeg-select'), SettingButton('选择ffmpeg', 'config-ffmpeg-select'),
), ),
SettingItem(
'音乐卡片签名地址',
null,
`<div class="q-input" style="width:210px;"><input class="q-input__inner" data-config-key="musicSignUrl" type="text" value="${config.musicSignUrl}" placeholder="未设置" /></div>`,
'config-musicSignUrl',
),
SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')),
]), ]),
SettingList([ SettingList([