Merge pull request #444 from LLOneBot/dev

release: 3.33.7
This commit is contained in:
idranme 2024-09-25 14:59:34 +08:00 committed by GitHub
commit f9270c38cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 340 additions and 301 deletions

View File

@ -4,7 +4,7 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发", "description": "实现 OneBot 11 协议,用于 QQ 机器人开发",
"version": "3.33.6", "version": "3.33.7",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {

View File

@ -38,3 +38,10 @@ export function mergeNewProperties(newObj: Dict, oldObj: Dict) {
export function filterNullable<T>(array: T[]) { export function filterNullable<T>(array: T[]) {
return array.filter(e => !isNullable(e)) as NonNullable<T>[] return array.filter(e => !isNullable(e)) as NonNullable<T>[]
} }
export function parseBool(value: string) {
if (['', 'true', '1'].includes(value)) {
return true
}
return false
}

View File

@ -30,7 +30,7 @@ export default class Store extends Service {
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'store', true) super(ctx, 'store', true)
this.cache = new LimitedHashTable<string, number>(1000) this.cache = new LimitedHashTable(1000)
this.initDatabase() this.initDatabase()
} }

View File

@ -23,7 +23,6 @@ import { fileTypeFromFile } from 'file-type'
import { copyFile, stat, unlink } from 'node:fs/promises' import { copyFile, stat, unlink } from 'node:fs/promises'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { TEMP_DIR } from '@/common/globalVars'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@ -73,7 +72,7 @@ export class NTQQFileApi extends Service {
} }
// 上传文件到QQ的文件夹 // 上传文件到QQ的文件夹
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType = 0) { async uploadFile(filePath: string, elementType = ElementType.Pic, elementSubType = 0) {
const fileMd5 = await calculateFileMD5(filePath) const fileMd5 = await calculateFileMD5(filePath)
let fileName = path.basename(filePath) let fileName = path.basename(filePath)
if (!fileName.includes('.')) { if (!fileName.includes('.')) {
@ -107,8 +106,8 @@ export class NTQQFileApi extends Service {
chatType: ChatType, chatType: ChatType,
peerUid: string, peerUid: string,
elementId: string, elementId: string,
thumbPath: string, thumbPath = '',
sourcePath: string, sourcePath = '',
timeout = 1000 * 60 * 2, timeout = 1000 * 60 * 2,
force = false force = false
) { ) {
@ -147,13 +146,7 @@ export class NTQQFileApi extends Service {
timeout timeout
} }
) )
let filePath = data.notifyInfo.filePath return data.notifyInfo.filePath
if (filePath.startsWith('\\')) {
const downloadPath = TEMP_DIR
filePath = path.join(downloadPath, filePath)
// 下载路径是下载文件夹的相对路径
}
return filePath
} }
async getImageSize(filePath: string) { async getImageSize(filePath: string) {
@ -201,6 +194,27 @@ export class NTQQFileApi extends Service {
this.ctx.logger.error('图片url获取失败', element) this.ctx.logger.error('图片url获取失败', element)
return '' return ''
} }
async downloadFileForModelId(peer: Peer, fileModelId: string, timeout = 2 * Time.minute) {
const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>(
'nodeIKernelRichMediaService/downloadFileForModelId',
[
{
peer,
fileModelIdList: [fileModelId],
save_path: ''
},
null,
],
{
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: payload => payload.notifyInfo.fileModelId === fileModelId,
timeout,
afterFirstCmd: false
}
)
return data.notifyInfo.filePath
}
} }
export class NTQQFileCacheApi extends Service { export class NTQQFileCacheApi extends Service {

View File

@ -156,12 +156,7 @@ export class NTQQGroupApi extends Service {
} }
} }
async kickMember( async kickMember(groupCode: string, kickUids: string[], refuseForever = false, kickReason = '') {
groupCode: string,
kickUids: string[],
refuseForever = false,
kickReason = '',
) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason) return session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason)

View File

@ -63,7 +63,7 @@ class Core extends Service {
uids = payload.data.flatMap(item => item.buddyList.map(e => e.uid)) uids = payload.data.flatMap(item => item.buddyList.map(e => e.uid))
} }
for (const uid of uids) { for (const uid of uids) {
this.ctx.ntMsgApi.activateChat({ peerUid: uid, chatType: ChatType.friend }) this.ctx.ntMsgApi.activateChat({ peerUid: uid, chatType: ChatType.C2C })
} }
this.ctx.logger.info('好友列表变动', uids.length) this.ctx.logger.info('好友列表变动', uids.length)
}) })
@ -116,7 +116,7 @@ class Core extends Service {
if (activatedPeerUids.includes(contact.id)) continue if (activatedPeerUids.includes(contact.id)) continue
activatedPeerUids.push(contact.id) activatedPeerUids.push(contact.id)
const peer = { peerUid: contact.id, chatType: contact.chatType } const peer = { peerUid: contact.id, chatType: contact.chatType }
if (contact.chatType === ChatType.temp) { if (contact.chatType === ChatType.TempC2CFromGroup) {
this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => { this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => {
this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => { this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
const lastTempMsg = msgList.at(-1) const lastTempMsg = msgList.at(-1)
@ -136,12 +136,12 @@ class Core extends Service {
registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => { registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => {
const peerUid = payload[0] as string const peerUid = payload[0] as string
this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid) this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid)
let chatType = ChatType.friend let chatType = ChatType.C2C
if (isNumeric(peerUid)) { if (isNumeric(peerUid)) {
chatType = ChatType.group chatType = ChatType.Group
} }
else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) { else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) {
chatType = ChatType.temp chatType = ChatType.TempC2CFromGroup
} }
const peer = { peerUid, chatType } const peer = { peerUid, chatType }
await this.ctx.sleep(1000) await this.ctx.sleep(1000)

View File

@ -28,11 +28,11 @@ import { isNullable } from 'cosmokit'
export namespace SendElementEntities { export namespace SendElementEntities {
export function text(content: string): SendTextElement { export function text(content: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.Text,
elementId: '', elementId: '',
textElement: { textElement: {
content, content,
atType: AtType.notAt, atType: AtType.Unknown,
atUid: '', atUid: '',
atTinyId: '', atTinyId: '',
atNtUid: '', atNtUid: '',
@ -42,7 +42,7 @@ export namespace SendElementEntities {
export function at(atUid: string, atNtUid: string, atType: AtType, display: string): SendTextElement { export function at(atUid: string, atNtUid: string, atType: AtType, display: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.Text,
elementId: '', elementId: '',
textElement: { textElement: {
content: display, content: display,
@ -56,7 +56,7 @@ export namespace SendElementEntities {
export function reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { export function reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement {
return { return {
elementType: ElementType.REPLY, elementType: ElementType.Reply,
elementId: '', elementId: '',
replyElement: { replyElement: {
replayMsgSeq: msgSeq, // raw.msgSeq replayMsgSeq: msgSeq, // raw.msgSeq
@ -68,7 +68,7 @@ export namespace SendElementEntities {
} }
export async function pic(ctx: Context, picPath: string, summary = '', subType: 0 | 1 = 0, isFlashPic?: boolean): Promise<SendPicElement> { export async function pic(ctx: Context, picPath: string, summary = '', subType: 0 | 1 = 0, isFlashPic?: boolean): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(picPath, ElementType.PIC, subType) const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(picPath, ElementType.Pic, subType)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常,大小为 0' throw '文件异常,大小为 0'
} }
@ -81,7 +81,7 @@ export namespace SendElementEntities {
fileName: fileName, fileName: fileName,
sourcePath: path, sourcePath: path,
original: true, original: true,
picType: imageSize.type === 'gif' ? PicType.gif : PicType.jpg, picType: imageSize.type === 'gif' ? PicType.GIF : PicType.JPEG,
picSubType: subType, picSubType: subType,
fileUuid: '', fileUuid: '',
fileSubId: '', fileSubId: '',
@ -91,7 +91,7 @@ export namespace SendElementEntities {
} }
ctx.logger.info('图片信息', picElement) ctx.logger.info('图片信息', picElement)
return { return {
elementType: ElementType.PIC, elementType: ElementType.Pic,
elementId: '', elementId: '',
picElement, picElement,
} }
@ -104,7 +104,7 @@ export namespace SendElementEntities {
throw new Error('文件异常,大小为 0') throw new Error('文件异常,大小为 0')
} }
const element: SendFileElement = { const element: SendFileElement = {
elementType: ElementType.FILE, elementType: ElementType.File,
elementId: '', elementId: '',
fileElement: { fileElement: {
fileName, fileName,
@ -123,7 +123,7 @@ export namespace SendElementEntities {
throw `文件${filePath}异常,不存在` throw `文件${filePath}异常,不存在`
} }
ctx.logger.info('复制视频到QQ目录', filePath) ctx.logger.info('复制视频到QQ目录', filePath)
const { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO) const { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.Video)
ctx.logger.info('复制视频到QQ目录完成', path) ctx.logger.info('复制视频到QQ目录完成', path)
if (fileSize === 0) { if (fileSize === 0) {
@ -200,7 +200,7 @@ export namespace SendElementEntities {
thumbPath.set(0, _thumbPath) thumbPath.set(0, _thumbPath)
const thumbMd5 = await calculateFileMD5(_thumbPath) const thumbMd5 = await calculateFileMD5(_thumbPath)
const element: SendVideoElement = { const element: SendVideoElement = {
elementType: ElementType.VIDEO, elementType: ElementType.Video,
elementId: '', elementId: '',
videoElement: { videoElement: {
fileName: fileName || _fileName, fileName: fileName || _fileName,
@ -212,17 +212,7 @@ export namespace SendElementEntities {
thumbSize, thumbSize,
thumbWidth: videoInfo.width, thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height, thumbHeight: videoInfo.height,
fileSize: '' + fileSize, fileSize: String(fileSize),
// fileUuid: "",
// transferStatus: 0,
// progress: 0,
// invalidState: 0,
// fileSubId: "",
// fileBizId: null,
// originVideoMd5: "",
// fileFormat: 2,
// import_rich_media_context: null,
// sourceVideoCodecFormat: 2
}, },
} }
ctx.logger.info('videoElement', element) ctx.logger.info('videoElement', element)
@ -235,7 +225,7 @@ export namespace SendElementEntities {
throw '语音转换失败, 请检查语音文件是否正常' throw '语音转换失败, 请检查语音文件是否正常'
} }
// log("生成语音", silkPath, duration); // log("生成语音", silkPath, duration);
const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(silkPath, ElementType.PTT) const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(silkPath, ElementType.Ptt)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
@ -243,14 +233,13 @@ export namespace SendElementEntities {
unlink(silkPath) unlink(silkPath)
} }
return { return {
elementType: ElementType.PTT, elementType: ElementType.Ptt,
elementId: '', elementId: '',
pttElement: { pttElement: {
fileName: fileName, fileName: fileName,
filePath: path, filePath: path,
md5HexStr: md5, md5HexStr: md5,
fileSize: fileSize, fileSize: String(fileSize),
// duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算
duration: duration, duration: duration,
formatType: 1, formatType: 1,
voiceType: 1, voiceType: 1,
@ -279,7 +268,7 @@ export namespace SendElementEntities {
faceType = 3; faceType = 3;
} }
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: faceId, faceIndex: faceId,
@ -295,7 +284,8 @@ export namespace SendElementEntities {
export function mface(emojiPackageId: number, emojiId: string, key: string, summary?: string): SendMarketFaceElement { export function mface(emojiPackageId: number, emojiId: string, key: string, summary?: string): SendMarketFaceElement {
return { return {
elementType: ElementType.MFACE, elementType: ElementType.MarketFace,
elementId: '',
marketFaceElement: { marketFaceElement: {
imageWidth: 300, imageWidth: 300,
imageHeight: 300, imageHeight: 300,
@ -312,10 +302,10 @@ export namespace SendElementEntities {
// 随机1到6 // 随机1到6
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: FaceIndex.dice, faceIndex: FaceIndex.Dice,
faceType: 3, faceType: 3,
faceText: '[骰子]', faceText: '[骰子]',
packId: '1', packId: '1',
@ -334,7 +324,7 @@ export namespace SendElementEntities {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: FaceIndex.RPS, faceIndex: FaceIndex.RPS,
@ -353,7 +343,7 @@ export namespace SendElementEntities {
export function ark(data: string): SendArkElement { export function ark(data: string): SendArkElement {
return { return {
elementType: ElementType.ARK, elementType: ElementType.Ark,
elementId: '', elementId: '',
arkElement: { arkElement: {
bytesData: data, bytesData: data,
@ -365,7 +355,7 @@ export namespace SendElementEntities {
export function shake(): SendFaceElement { export function shake(): SendFaceElement {
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: 1, faceIndex: 1,

View File

@ -1,126 +1,108 @@
import { GroupMemberRole } from './group' import { GroupMemberRole } from './group'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
export interface GetFileListParam {
sortType: number
fileCount: number
startIndex: number
sortOrder: number
showOnlinedocFolder: number
folderId?: string
}
export enum ElementType { export enum ElementType {
UNKNOWN = 0, Text = 1,
TEXT = 1, Pic = 2,
PIC = 2, File = 3,
FILE = 3, Ptt = 4,
PTT = 4, Video = 5,
VIDEO = 5, Face = 6,
FACE = 6, Reply = 7,
REPLY = 7, GrayTip = 8,
WALLET = 9, Ark = 10,
GreyTip = 8, //Poke别叫戳一搓了 官方名字拍一拍 戳一戳是另一个名字 MarketFace = 11,
ARK = 10, LiveGift = 12,
MFACE = 11, StructLongMsg = 13,
LIVEGIFT = 12, Markdown = 14,
STRUCTLONGMSG = 13, Giphy = 15,
MARKDOWN = 14, MultiForward = 16,
GIPHY = 15, InlineKeyboard = 17,
MULTIFORWARD = 16, Calendar = 19,
INLINEKEYBOARD = 17, YoloGameResult = 20,
INTEXTGIFT = 18, AvRecord = 21,
CALENDAR = 19, TofuRecord = 23,
YOLOGAMERESULT = 20, FaceBubble = 27,
AVRECORD = 21, ShareLocation = 28,
FEED = 22, TaskTopMsg = 29,
TOFURECORD = 23, RecommendedMsg = 43,
ACEBUBBLE = 24, ActionBar = 44
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
} }
export interface SendTextElement { export interface SendTextElement {
elementType: ElementType.TEXT elementType: ElementType.Text
elementId: '' elementId: ''
textElement: TextElement textElement: TextElement
} }
export interface SendPttElement { export interface SendPttElement {
elementType: ElementType.PTT elementType: ElementType.Ptt
elementId: '' elementId: ''
pttElement: { pttElement: Partial<PttElement>
fileName: string
filePath: string
md5HexStr: string
fileSize: number
duration: number // 单位是秒
formatType: number
voiceType: number
voiceChangeType: number
canConvert2Text: boolean
waveAmplitudes: number[]
fileSubId: ''
playState: number
autoConvertText: number
}
}
export enum PicType {
gif = 2000,
jpg = 1000,
}
export enum PicSubType {
normal = 0, // 普通图片,大图
face = 1, // 表情包小图
} }
export interface SendPicElement { export interface SendPicElement {
elementType: ElementType.PIC elementType: ElementType.Pic
elementId: '' elementId: ''
picElement: { picElement: Partial<PicElement>
md5HexStr: string
fileSize: number | string
picWidth: number
picHeight: number
fileName: string
sourcePath: string
original: boolean
picType: PicType
picSubType: PicSubType
fileUuid: string
fileSubId: string
thumbFileSize: number
summary: string
}
} }
export interface SendReplyElement { export interface SendReplyElement {
elementType: ElementType.REPLY elementType: ElementType.Reply
elementId: '' elementId: ''
replyElement: Partial<ReplyElement> replyElement: Partial<ReplyElement>
} }
export interface SendFaceElement { export interface SendFaceElement {
elementType: ElementType.FACE elementType: ElementType.Face
elementId: '' elementId: ''
faceElement: FaceElement faceElement: FaceElement
} }
export interface SendMarketFaceElement { export interface SendMarketFaceElement {
elementType: ElementType.MFACE elementType: ElementType.MarketFace
elementId: ''
marketFaceElement: MarketFaceElement marketFaceElement: MarketFaceElement
} }
export interface SendFileElement {
elementType: ElementType.File
elementId: ''
fileElement: FileElement
}
export interface SendVideoElement {
elementType: ElementType.Video
elementId: ''
videoElement: VideoElement
}
export interface SendArkElement {
elementType: ElementType.Ark
elementId: ''
arkElement: ArkElement
}
export type SendMessageElement =
| SendTextElement
| SendPttElement
| SendPicElement
| SendReplyElement
| SendFaceElement
| SendMarketFaceElement
| SendFileElement
| SendVideoElement
| SendArkElement
export enum AtType {
Unknown,
All,
One,
}
export interface TextElement { export interface TextElement {
content: string content: string
atType: number atType: AtType
atUid: string atUid: string
atTinyId: string atTinyId: string
atNtUid: string atNtUid: string
@ -157,47 +139,6 @@ export interface FileElement {
fileBizId?: number fileBizId?: number
} }
export interface SendFileElement {
elementType: ElementType.FILE
elementId: ''
fileElement: FileElement
}
export interface SendVideoElement {
elementType: ElementType.VIDEO
elementId: ''
videoElement: VideoElement
}
export interface SendArkElement {
elementType: ElementType.ARK
elementId: ''
arkElement: ArkElement
}
export type SendMessageElement =
| SendTextElement
| SendPttElement
| SendPicElement
| SendReplyElement
| SendFaceElement
| SendMarketFaceElement
| SendFileElement
| SendVideoElement
| SendArkElement
export enum AtType {
notAt = 0,
atAll = 1,
atUser = 2,
}
export enum ChatType {
friend = 1,
group = 2,
temp = 100,
}
export interface PttElement { export interface PttElement {
canConvert2Text: boolean canConvert2Text: boolean
duration: number // 秒数 duration: number // 秒数
@ -208,7 +149,7 @@ export interface PttElement {
fileSize: string // "4261" fileSize: string // "4261"
fileSubId: string // "0" fileSubId: string // "0"
fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
formatType: string // 1 formatType: number // 1
invalidState: number // 0 invalidState: number // 0
md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6" md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6"
playState: number // 0 playState: number // 0
@ -219,6 +160,7 @@ export interface PttElement {
voiceChangeType: number // 0 voiceChangeType: number // 0
voiceType: number // 0 voiceType: number // 0
waveAmplitudes: number[] waveAmplitudes: number[]
autoConvertText: number
} }
export interface ArkElement { export interface ArkElement {
@ -230,6 +172,16 @@ export interface ArkElement {
export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn' export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn' export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
export enum PicType {
GIF = 2000,
JPEG = 1000,
}
export enum PicSubType {
Normal = 0, // 普通图片,大图
Face = 1, // 表情包小图
}
export interface PicElement { export interface PicElement {
picSubType: PicSubType picSubType: PicSubType
picType: PicType // 有这玩意儿吗 picType: PicType // 有这玩意儿吗
@ -246,22 +198,22 @@ export interface PicElement {
} }
export enum GrayTipElementSubType { export enum GrayTipElementSubType {
REVOKE = 1, Revoke = 1,
PROCLAMATION = 2, Proclamation = 2,
EMOJIREPLY = 3, EmojiReply = 3,
GROUP = 4, Group = 4,
BUDDY = 5, Buddy = 5,
FEED = 6, Feed = 6,
ESSENCE = 7, Essence = 7,
GROUPNOTIFY = 8, GroupNotify = 8,
BUDDYNOTIFY = 9, BuddyNotify = 9,
FILE = 10, File = 10,
FEEDCHANNELMSG = 11, FeedChannelMsg = 11,
XMLMSG = 12, XmlMsg = 12,
LOCALMSG = 13, LocalMsg = 13,
BLOCK = 14, Block = 14,
AIOOP = 15, AioOp = 15,
WALLET = 16, Wallet = 16,
JSON = 17, JSON = 17,
} }
@ -291,7 +243,7 @@ export interface GrayTipElement {
export enum FaceIndex { export enum FaceIndex {
dice = 358, Dice = 358,
RPS = 359, // 石头剪刀布 RPS = 359, // 石头剪刀布
} }
@ -365,6 +317,7 @@ export interface InlineKeyboardElementRowButton {
enter: false enter: false
subscribeDataTemplateIds: [] subscribeDataTemplateIds: []
} }
export interface InlineKeyboardElement { export interface InlineKeyboardElement {
rows: [ rows: [
{ {
@ -381,9 +334,9 @@ export interface TipAioOpGrayTipElement {
} }
export enum TipGroupElementType { export enum TipGroupElementType {
memberIncrease = 1, MemberIncrease = 1,
kicked = 3, // 被移出群 Kicked = 3, // 被移出群
ban = 8, Ban = 8,
} }
export interface TipGroupElement { export interface TipGroupElement {
@ -425,17 +378,27 @@ export interface TipGroupElement {
} }
} }
export interface StructLongMsgElement {
xmlContent: string
resId: string
}
export interface MultiForwardMsgElement { export interface MultiForwardMsgElement {
xmlContent: string // xml格式的消息内容 xmlContent: string // xml格式的消息内容
resId: string resId: string
fileName: string fileName: string
} }
export enum ChatType {
C2C = 1,
Group = 2,
TempC2CFromGroup = 100,
}
export interface RawMessage { export interface RawMessage {
msgId: string msgId: string
msgType: number msgType: number
subMsgType: number subMsgType: number
msgShortId?: number // 自己维护的消息id
msgTime: string // 时间戳,秒 msgTime: string // 时间戳,秒
msgSeq: string msgSeq: string
msgRandom: string msgRandom: string
@ -475,12 +438,11 @@ export interface MessageElement {
fileElement?: FileElement fileElement?: FileElement
liveGiftElement?: unknown liveGiftElement?: unknown
markdownElement?: MarkdownElement markdownElement?: MarkdownElement
structLongMsgElement?: unknown structLongMsgElement?: StructLongMsgElement
multiForwardMsgElement?: MultiForwardMsgElement multiForwardMsgElement?: MultiForwardMsgElement
giphyElement?: unknown giphyElement?: unknown
walletElement?: unknown
inlineKeyboardElement?: InlineKeyboardElement inlineKeyboardElement?: InlineKeyboardElement
textGiftElement?: unknown //???? textGiftElement?: unknown
calendarElement?: unknown calendarElement?: unknown
yoloGameResultElement?: unknown yoloGameResultElement?: unknown
avRecordElement?: unknown avRecordElement?: unknown
@ -588,3 +550,12 @@ export interface TmpChatInfoApi extends GeneralCallResult {
sig: string sig: string
} }
} }
export interface GetFileListParam {
sortType: number
fileCount: number
startIndex: number
sortOrder: number
showOnlinedocFolder: number
folderId?: string
}

View File

@ -48,7 +48,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
peerUid: fileCache[0].peerUid, peerUid: fileCache[0].peerUid,
guildId: '' guildId: ''
} }
if (fileCache[0].elementType === ElementType.PIC) { if (fileCache[0].elementType === ElementType.Pic) {
const msgList = await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [fileCache[0].msgId]) const msgList = await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [fileCache[0].msgId])
if (msgList.msgList.length === 0) { if (msgList.msgList.length === 0) {
throw new Error('msg not found') throw new Error('msg not found')
@ -59,7 +59,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
throw new Error('element not found') throw new Error('element not found')
} }
res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement!) res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement!)
} else if (fileCache[0].elementType === ElementType.VIDEO) { } else if (fileCache[0].elementType === ElementType.Video) {
res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId) res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId)
} }
if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) { if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) {

View File

@ -26,7 +26,7 @@ export class GetEssenceMsgList extends BaseAction<Payload, EssenceMsg[]> {
const groupCode = payload.group_id.toString() const groupCode = payload.group_id.toString()
const peer = { const peer = {
guildId: '', guildId: '',
chatType: ChatType.group, chatType: ChatType.Group,
peerUid: groupCode peerUid: groupCode
} }
const essence = await this.ctx.ntGroupApi.queryCachedEssenceMsg(groupCode) const essence = await this.ctx.ntGroupApi.queryCachedEssenceMsg(groupCode)

View File

@ -0,0 +1,64 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { pathToFileURL } from 'node:url'
import { ChatType } from '@/ntqqapi/types'
export interface Payload {
group_id: number | string
file_id: string
busid?: number
}
export interface Response {
url: string
}
export class GetGroupFileUrl extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupFileUrl
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file_id: Schema.string().required()
})
protected async _handle(payload: Payload) {
const file = await this.ctx.store.getFileCacheById(payload.file_id)
if (file.length > 0) {
const { msgId, chatType, peerUid, elementId } = file[0]
const path = await this.ctx.ntFileApi.downloadMedia(msgId, chatType, peerUid, elementId)
return {
url: pathToFileURL(path).href
}
} else {
const groupId = payload.group_id.toString()
let modelId: string | undefined
let nextIndex: number | undefined
while (nextIndex !== 0) {
const res = await this.ctx.ntGroupApi.getGroupFileList(groupId, {
sortType: 1,
fileCount: 50,
startIndex: nextIndex ?? 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
const file = res.item.find(item => item.fileInfo?.fileId === payload.file_id)
if (file) {
modelId = file.fileInfo?.fileModelId
break
}
nextIndex = res.nextIndex
}
if (modelId) {
const peer = {
chatType: ChatType.Group,
peerUid: groupId,
guildId: ''
}
const path = await this.ctx.ntFileApi.downloadFileForModelId(peer, modelId)
return {
url: pathToFileURL(path).href
}
}
throw new Error('file not found')
}
}
}

View File

@ -4,7 +4,7 @@ import { ActionName } from '../types'
import { ChatType } from '@/ntqqapi/types' import { ChatType } from '@/ntqqapi/types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { RawMessage } from '@/ntqqapi/types' import { RawMessage } from '@/ntqqapi/types'
import { filterNullable } from '@/common/utils/misc' import { filterNullable, parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@ -23,12 +23,12 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
group_id: Schema.union([Number, String]).required(), group_id: Schema.union([Number, String]).required(),
message_seq: Schema.union([Number, String]), message_seq: Schema.union([Number, String]),
count: Schema.union([Number, String]).default(20), count: Schema.union([Number, String]).default(20),
reverseOrder: Schema.boolean().default(false), reverseOrder: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(false)
}) })
protected async _handle(payload: Payload): Promise<Response> { protected async _handle(payload: Payload): Promise<Response> {
const { count, reverseOrder } = payload const { count, reverseOrder } = payload
const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() } const peer = { chatType: ChatType.Group, peerUid: payload.group_id.toString() }
let msgList: RawMessage[] let msgList: RawMessage[]
if (!payload.message_seq || payload.message_seq === '0') { if (!payload.message_seq || payload.message_seq === '0') {
msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList
@ -39,9 +39,6 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
} }
if (!msgList?.length) throw new Error('未找到消息') if (!msgList?.length) throw new Error('未找到消息')
if (reverseOrder) msgList.reverse() if (reverseOrder) msgList.reverse()
for (const msg of msgList) {
msg.msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
}
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg))) const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg)))
return { messages: filterNullable(ob11MsgList) } return { messages: filterNullable(ob11MsgList) }
} }

View File

@ -36,8 +36,9 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
contextMode = CreatePeerMode.Private contextMode = CreatePeerMode.Private
} }
const peer = await createPeer(this.ctx, payload, contextMode) const peer = await createPeer(this.ctx, payload, contextMode)
const returnMsg = await this.handleForwardNode(peer, payload.messages) const msg = await this.handleForwardNode(peer, payload.messages)
return { message_id: returnMsg.msgShortId! } const msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
return { message_id: msgShortId }
} }
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> { private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
@ -52,7 +53,7 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
this.ctx.logger.info('克隆消息', sendElements) this.ctx.logger.info('克隆消息', sendElements)
try { try {
const peer = { const peer = {
chatType: ChatType.friend, chatType: ChatType.C2C,
peerUid: selfInfo.uid peerUid: selfInfo.uid
} }
const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements) const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements)
@ -66,7 +67,7 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
// 返回一个合并转发的消息id // 返回一个合并转发的消息id
private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) { private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) {
const selfPeer = { const selfPeer = {
chatType: ChatType.friend, chatType: ChatType.C2C,
peerUid: selfInfo.uid, peerUid: selfInfo.uid,
} }
const nodeMsgIds: { msgId: string, peer: Peer }[] = [] const nodeMsgIds: { msgId: string, peer: Peer }[] = []
@ -100,7 +101,7 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
sendElementsSplit[splitIndex] = [] sendElementsSplit[splitIndex] = []
} }
if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) { if (ele.elementType === ElementType.File || ele.elementType === ElementType.Video) {
if (sendElementsSplit[splitIndex].length > 0) { if (sendElementsSplit[splitIndex].length > 0) {
splitIndex++ splitIndex++
} }
@ -157,7 +158,6 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
throw Error('转发消息失败,节点为空') throw Error('转发消息失败,节点为空')
} }
const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds) const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds)
returnMsg.msgShortId = this.ctx.store.createMsgShortId(destPeer, returnMsg.msgId)
return returnMsg return returnMsg
} }
} }

View File

@ -1,6 +1,7 @@
import { BaseAction, Schema } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { GroupRequestOperateTypes } from '@/ntqqapi/types' import { GroupRequestOperateTypes } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
flag: string flag: string
@ -12,7 +13,7 @@ export default class SetGroupAddRequest extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupAddRequest actionName = ActionName.SetGroupAddRequest
payloadSchema = Schema.object({ payloadSchema = Schema.object({
flag: Schema.string().required(), flag: Schema.string().required(),
approve: Schema.boolean().default(true), approve: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(true),
reason: Schema.string() reason: Schema.string()
}) })

View File

@ -1,6 +1,7 @@
import { BaseAction, Schema } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { GroupMemberRole } from '@/ntqqapi/types' import { GroupMemberRole } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@ -13,7 +14,7 @@ export default class SetGroupAdmin extends BaseAction<Payload, null> {
payloadSchema = Schema.object({ payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(), group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(), user_id: Schema.union([Number, String]).required(),
enable: Schema.boolean().default(true) enable: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(true)
}) })
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {

View File

@ -1,5 +1,6 @@
import { BaseAction, Schema } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@ -12,7 +13,7 @@ export default class SetGroupKick extends BaseAction<Payload, null> {
payloadSchema = Schema.object({ payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(), group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(), user_id: Schema.union([Number, String]).required(),
reject_add_request: Schema.boolean().default(false) reject_add_request: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(false)
}) })
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {

View File

@ -1,5 +1,6 @@
import { BaseAction, Schema } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@ -10,7 +11,7 @@ export default class SetGroupWholeBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupWholeBan actionName = ActionName.SetGroupWholeBan
payloadSchema = Schema.object({ payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(), group_id: Schema.union([Number, String]).required(),
enable: Schema.boolean().default(true) enable: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(true)
}) })
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {

View File

@ -69,6 +69,7 @@ import { GetGroupFilesByFolder } from './go-cqhttp/GetGroupFilesByFolder'
import { GetFriendWithCategory } from './llonebot/GetFriendWithCategory' import { GetFriendWithCategory } from './llonebot/GetFriendWithCategory'
import { UploadGroupFile } from './go-cqhttp/UploadGroupFile' import { UploadGroupFile } from './go-cqhttp/UploadGroupFile'
import { UploadPrivateFile } from './go-cqhttp/UploadPrivateFile' import { UploadPrivateFile } from './go-cqhttp/UploadPrivateFile'
import { GetGroupFileUrl } from './go-cqhttp/GetGroupFileUrl'
export function initActionMap(adapter: Adapter) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@ -143,6 +144,7 @@ export function initActionMap(adapter: Adapter) {
new GetGroupRootFiles(adapter), new GetGroupRootFiles(adapter),
new SendGroupNotice(adapter), new SendGroupNotice(adapter),
new GetGroupFilesByFolder(adapter), new GetGroupFilesByFolder(adapter),
new GetGroupFileUrl(adapter)
] ]
const actionMap = new Map<string, BaseAction<any, unknown>>() const actionMap = new Map<string, BaseAction<any, unknown>>()
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

@ -3,7 +3,7 @@ import { OB11Message } from '@/onebot11/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { ChatType, RawMessage } from '@/ntqqapi/types' import { ChatType, RawMessage } from '@/ntqqapi/types'
import { OB11Entities } from '@/onebot11/entities' import { OB11Entities } from '@/onebot11/entities'
import { filterNullable } from '@/common/utils/misc' import { filterNullable, parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
user_id: number | string user_id: number | string
@ -24,7 +24,7 @@ export class GetFriendMsgHistory extends BaseAction<Payload, Response> {
message_seq: Schema.union([Number, String]), message_seq: Schema.union([Number, String]),
message_id: Schema.union([Number, String]), message_id: Schema.union([Number, String]),
count: Schema.union([Number, String]).default(20), count: Schema.union([Number, String]).default(20),
reverseOrder: Schema.boolean().default(false) reverseOrder: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(false)
}) })
async _handle(payload: Payload): Promise<Response> { async _handle(payload: Payload): Promise<Response> {
@ -38,14 +38,11 @@ export class GetFriendMsgHistory extends BaseAction<Payload, Response> {
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString()) const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!uid) throw new Error(`记录${payload.user_id}不存在`) if (!uid) throw new Error(`记录${payload.user_id}不存在`)
const isBuddy = await this.ctx.ntFriendApi.isBuddy(uid) const isBuddy = await this.ctx.ntFriendApi.isBuddy(uid)
const peer = { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid: uid } const peer = { chatType: isBuddy ? ChatType.C2C : ChatType.TempC2CFromGroup, peerUid: uid }
msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList
} }
if (msgList.length === 0) throw new Error('未找到消息') if (msgList.length === 0) throw new Error('未找到消息')
if (payload.reverseOrder) msgList.reverse() if (payload.reverseOrder) msgList.reverse()
for (const msg of msgList) {
msg.msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
}
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg))) const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg)))
return { messages: filterNullable(ob11MsgList) } return { messages: filterNullable(ob11MsgList) }
} }

View File

@ -1,7 +1,6 @@
import { BaseAction } from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ChatType } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { Peer } from '@/ntqqapi/types' import { createPeer } from '@/onebot11/helper/createMessage'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
@ -10,17 +9,6 @@ interface Payload {
} }
abstract class ForwardSingleMsg extends BaseAction<Payload, null> { abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
protected async getTargetPeer(payload: Payload): Promise<Peer> {
if (payload.user_id) {
const peerUid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!peerUid) {
throw new Error(`无法找到私聊对象${payload.user_id}`)
}
return { chatType: ChatType.friend, peerUid }
}
return { chatType: ChatType.group, peerUid: payload.group_id!.toString() }
}
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
if (!payload.message_id) { if (!payload.message_id) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
@ -29,7 +17,7 @@ abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
if (!msg) { if (!msg) {
throw new Error(`无法找到消息${payload.message_id}`) throw new Error(`无法找到消息${payload.message_id}`)
} }
const peer = await this.getTargetPeer(payload) const peer = await createPeer(this.ctx, payload)
const ret = await this.ctx.ntMsgApi.forwardMsg(msg.peer, peer, [msg.msgId]) const ret = await this.ctx.ntMsgApi.forwardMsg(msg.peer, peer, [msg.msgId])
if (ret.result !== 0) { if (ret.result !== 0) {
throw new Error(`转发消息失败 ${ret.errMsg}`) throw new Error(`转发消息失败 ${ret.errMsg}`)

View File

@ -93,7 +93,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnData> {
if (!returnMsg) { if (!returnMsg) {
throw new Error('消息发送失败') throw new Error('消息发送失败')
} }
return { message_id: returnMsg.msgShortId! } const msgShortId = this.ctx.store.createMsgShortId({
chatType: returnMsg.chatType,
peerUid: returnMsg.peerUid
}, returnMsg.msgId)
return { message_id: msgShortId }
} }
private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number { private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number {

View File

@ -81,5 +81,6 @@ export enum ActionName {
GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain', GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files', GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
GoCQHTTP_SendGroupNotice = '_send_group_notice', GoCQHTTP_SendGroupNotice = '_send_group_notice',
GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder' GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder',
GoCQHTTP_GetGroupFileUrl = 'get_group_file_url'
} }

View File

@ -3,6 +3,7 @@ import { OB11User } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils' import { getBuildVersion } from '@/common/utils'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
no_cache: boolean no_cache: boolean
@ -11,7 +12,7 @@ interface Payload {
export class GetFriendList extends BaseAction<Payload, OB11User[]> { export class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList actionName = ActionName.GetFriendList
payloadSchema = Schema.object({ payloadSchema = Schema.object({
no_cache: Schema.boolean().default(false) no_cache: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(false)
}) })
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {

View File

@ -1,5 +1,6 @@
import { BaseAction } from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { ChatType } from '@/ntqqapi/types'
interface Payload { interface Payload {
flag: string flag: string
@ -22,6 +23,11 @@ export default class SetFriendAddRequest extends BaseAction<Payload, null> {
if (payload.remark) { if (payload.remark) {
await this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark) await this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark)
} }
await this.ctx.ntMsgApi.activateChat({
peerUid: uid,
chatType: ChatType.C2C,
guildId: ''
})
return null return null
} }
} }

View File

@ -185,11 +185,6 @@ class OneBot11Adapter extends Service {
if (parseInt(message.msgTime) < this.startTime / 1000) { if (parseInt(message.msgTime) < this.startTime / 1000) {
continue continue
} }
const peer: Peer = {
chatType: message.chatType,
peerUid: message.peerUid
}
message.msgShortId = this.ctx.store.createMsgShortId(peer, message.msgId)
this.addMsgCache(message) this.addMsgCache(message)
OB11Entities.message(this.ctx, message) OB11Entities.message(this.ctx, message)

View File

@ -53,14 +53,15 @@ export namespace OB11Entities {
messagePostFormat, messagePostFormat,
} = ctx.config as OneBot11Adapter.Config } = ctx.config as OneBot11Adapter.Config
const selfUin = selfInfo.uin const selfUin = selfInfo.uin
const msgShortId = ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
const resMsg: OB11Message = { const resMsg: OB11Message = {
self_id: parseInt(selfUin), self_id: parseInt(selfUin),
user_id: parseInt(msg.senderUin), user_id: parseInt(msg.senderUin),
time: parseInt(msg.msgTime) || Date.now(), time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.msgShortId!, message_id: msgShortId,
real_id: msg.msgShortId!, real_id: msgShortId,
message_seq: msg.msgShortId!, message_seq: msgShortId,
message_type: msg.chatType === ChatType.group ? 'group' : 'private', message_type: msg.chatType === ChatType.Group ? 'group' : 'private',
sender: { sender: {
user_id: parseInt(msg.senderUin), user_id: parseInt(msg.senderUin),
nickname: msg.sendNickName, nickname: msg.sendNickName,
@ -76,7 +77,7 @@ export namespace OB11Entities {
if (debug) { if (debug) {
resMsg.raw = msg resMsg.raw = msg
} }
if (msg.chatType === ChatType.group) { if (msg.chatType === ChatType.Group) {
resMsg.sub_type = 'normal' resMsg.sub_type = 'normal'
resMsg.group_id = parseInt(msg.peerUin) resMsg.group_id = parseInt(msg.peerUin)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUin) const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUin)
@ -86,15 +87,15 @@ export namespace OB11Entities {
resMsg.sender.title = member.memberSpecialTitle ?? '' resMsg.sender.title = member.memberSpecialTitle ?? ''
} }
} }
else if (msg.chatType === ChatType.friend) { else if (msg.chatType === ChatType.C2C) {
resMsg.sub_type = 'friend' resMsg.sub_type = 'friend'
resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick
} }
else if (msg.chatType === ChatType.temp) { else if (msg.chatType === ChatType.TempC2CFromGroup) {
resMsg.sub_type = 'group' resMsg.sub_type = 'group'
resMsg.temp_source = 0 //群聊 resMsg.temp_source = 0 //群聊
resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick
const ret = await ctx.ntMsgApi.getTempChatInfo(ChatType.temp, msg.senderUid) const ret = await ctx.ntMsgApi.getTempChatInfo(ChatType.TempC2CFromGroup, msg.senderUid)
if (ret?.result === 0) { if (ret?.result === 0) {
resMsg.sender.group_id = Number(ret.tmpChatInfo?.groupCode) resMsg.sender.group_id = Number(ret.tmpChatInfo?.groupCode)
} else { } else {
@ -104,10 +105,10 @@ export namespace OB11Entities {
for (const element of msg.elements) { for (const element of msg.elements) {
let messageSegment: OB11MessageData | undefined let messageSegment: OB11MessageData | undefined
if (element.textElement && element.textElement?.atType !== AtType.notAt) { if (element.textElement && element.textElement?.atType !== AtType.Unknown) {
let qq: string let qq: string
let name: string | undefined let name: string | undefined
if (element.textElement.atType == AtType.atAll) { if (element.textElement.atType === AtType.All) {
qq = 'all' qq = 'all'
} else { } else {
const { atNtUid, atUid, content } = element.textElement const { atNtUid, atUid, content } = element.textElement
@ -290,28 +291,31 @@ export namespace OB11Entities {
} }
else if (element.faceElement) { else if (element.faceElement) {
const { faceElement } = element const { faceElement } = element
const faceId = faceElement.faceIndex const { faceIndex, pokeType } = faceElement
if (faceId === FaceIndex.dice) { if (faceIndex === FaceIndex.Dice) {
messageSegment = { messageSegment = {
type: OB11MessageDataType.dice, type: OB11MessageDataType.dice,
data: { data: {
result: faceElement.resultId! result: faceElement.resultId!
} }
} }
} } else if (faceIndex === FaceIndex.RPS) {
else if (faceId === FaceIndex.RPS) {
messageSegment = { messageSegment = {
type: OB11MessageDataType.RPS, type: OB11MessageDataType.RPS,
data: { data: {
result: faceElement.resultId! result: faceElement.resultId!
} }
} }
} /*} else if (faceIndex === 1 && pokeType === 1) {
else { messageSegment = {
type: OB11MessageDataType.shake,
data: {}
}*/
} else {
messageSegment = { messageSegment = {
type: OB11MessageDataType.face, type: OB11MessageDataType.face,
data: { data: {
id: faceId.toString() id: faceIndex.toString()
} }
} }
} }
@ -368,7 +372,7 @@ export namespace OB11Entities {
} }
export async function privateEvent(ctx: Context, msg: RawMessage): Promise<OB11BaseNoticeEvent | void> { export async function privateEvent(ctx: Context, msg: RawMessage): Promise<OB11BaseNoticeEvent | void> {
if (msg.chatType !== ChatType.friend) { if (msg.chatType !== ChatType.C2C) {
return return
} }
for (const element of msg.elements) { for (const element of msg.elements) {
@ -397,7 +401,7 @@ export namespace OB11Entities {
} }
export async function groupEvent(ctx: Context, msg: RawMessage): Promise<OB11GroupNoticeEvent | void> { export async function groupEvent(ctx: Context, msg: RawMessage): Promise<OB11GroupNoticeEvent | void> {
if (msg.chatType !== ChatType.group) { if (msg.chatType !== ChatType.Group) {
return return
} }
if (msg.senderUin) { if (msg.senderUin) {
@ -417,7 +421,7 @@ export namespace OB11Entities {
const grayTipElement = element.grayTipElement const grayTipElement = element.grayTipElement
const groupElement = grayTipElement?.groupElement const groupElement = grayTipElement?.groupElement
if (groupElement) { if (groupElement) {
if (groupElement.type === TipGroupElementType.memberIncrease) { if (groupElement.type === TipGroupElementType.MemberIncrease) {
ctx.logger.info('收到群成员增加消息', groupElement) ctx.logger.info('收到群成员增加消息', groupElement)
await ctx.sleep(1000) await ctx.sleep(1000)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.memberUid) const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.memberUid)
@ -431,7 +435,7 @@ export namespace OB11Entities {
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin)) return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin))
} }
} }
else if (groupElement.type === TipGroupElementType.ban) { else if (groupElement.type === TipGroupElementType.Ban) {
ctx.logger.info('收到群群员禁言提示', groupElement) ctx.logger.info('收到群群员禁言提示', groupElement)
const memberUid = groupElement.shutUp?.member.uid const memberUid = groupElement.shutUp?.member.uid
const adminUid = groupElement.shutUp?.admin.uid const adminUid = groupElement.shutUp?.admin.uid
@ -461,7 +465,7 @@ export namespace OB11Entities {
) )
} }
} }
else if (groupElement.type === TipGroupElementType.kicked) { else if (groupElement.type === TipGroupElementType.Kicked) {
ctx.logger.info(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement) ctx.logger.info(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
ctx.ntGroupApi.quitGroup(msg.peerUid) ctx.ntGroupApi.quitGroup(msg.peerUid)
try { try {
@ -507,7 +511,7 @@ export namespace OB11Entities {
const msgSeq: string = emojiLikeData.gtip.url.msgseq const msgSeq: string = emojiLikeData.gtip.url.msgseq
const emojiId: string = emojiLikeData.gtip.face.id const emojiId: string = emojiLikeData.gtip.face.id
const peer = { const peer = {
chatType: ChatType.group, chatType: ChatType.Group,
guildId: '', guildId: '',
peerUid: msg.peerUid, peerUid: msg.peerUid,
} }
@ -531,7 +535,7 @@ export namespace OB11Entities {
} }
if ( if (
grayTipElement.subElementType == GrayTipElementSubType.XMLMSG && grayTipElement.subElementType == GrayTipElementSubType.XmlMsg &&
xmlElement?.templId == '10179' xmlElement?.templId == '10179'
) { ) {
ctx.logger.info('收到新人被邀请进群消息', grayTipElement) ctx.logger.info('收到新人被邀请进群消息', grayTipElement)
@ -575,7 +579,7 @@ export namespace OB11Entities {
if (!groupCode || !msgSeq || !msgRandom) return if (!groupCode || !msgSeq || !msgRandom) return
const peer = { const peer = {
guildId: '', guildId: '',
chatType: ChatType.group, chatType: ChatType.Group,
peerUid: groupCode peerUid: groupCode
} }
const essence = await ctx.ntGroupApi.queryCachedEssenceMsg(groupCode, msgSeq, msgRandom) const essence = await ctx.ntGroupApi.queryCachedEssenceMsg(groupCode, msgSeq, msgRandom)
@ -611,13 +615,13 @@ export namespace OB11Entities {
shortId: number shortId: number
): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> { ): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> {
const msgElement = msg.elements.find( const msgElement = msg.elements.find(
(element) => element.grayTipElement?.subElementType === GrayTipElementSubType.REVOKE, (element) => element.grayTipElement?.subElementType === GrayTipElementSubType.Revoke,
) )
if (!msgElement) { if (!msgElement) {
return return
} }
const revokeElement = msgElement.grayTipElement!.revokeElement const revokeElement = msgElement.grayTipElement!.revokeElement
if (msg.chatType === ChatType.group) { if (msg.chatType === ChatType.Group) {
const operator = await ctx.ntGroupApi.getGroupMember(msg.peerUid, revokeElement!.operatorUid) const operator = await ctx.ntGroupApi.getGroupMember(msg.peerUid, revokeElement!.operatorUid)
return new OB11GroupRecallNoticeEvent( return new OB11GroupRecallNoticeEvent(
parseInt(msg.peerUid), parseInt(msg.peerUid),

View File

@ -62,22 +62,22 @@ export async function createSendElements(
} }
} }
if (isAdmin && remainAtAllCount > 0) { if (isAdmin && remainAtAllCount > 0) {
sendElements.push(SendElementEntities.at(atQQ, atQQ, AtType.atAll, '@全体成员')) sendElements.push(SendElementEntities.at(atQQ, atQQ, AtType.All, '@全体成员'))
} }
} }
else if (peer.chatType === ChatType.group) { else if (peer.chatType === ChatType.Group) {
const atMember = await ctx.ntGroupApi.getGroupMember(peer.peerUid, atQQ) const atMember = await ctx.ntGroupApi.getGroupMember(peer.peerUid, atQQ)
if (atMember) { if (atMember) {
const display = `@${atMember.cardName || atMember.nick}` const display = `@${atMember.cardName || atMember.nick}`
sendElements.push( sendElements.push(
SendElementEntities.at(atQQ, atMember.uid, AtType.atUser, display), SendElementEntities.at(atQQ, atMember.uid, AtType.One, display),
) )
} else { } else {
const atNmae = sendMsg.data?.name const atNmae = sendMsg.data?.name
const uid = await ctx.ntUserApi.getUidByUin(atQQ) || '' const uid = await ctx.ntUserApi.getUidByUin(atQQ) || ''
const display = atNmae ? `@${atNmae}` : '' const display = atNmae ? `@${atNmae}` : ''
sendElements.push( sendElements.push(
SendElementEntities.at(atQQ, uid, AtType.atUser, display), SendElementEntities.at(atQQ, uid, AtType.One, display),
) )
} }
} }
@ -134,7 +134,7 @@ export async function createSendElements(
sendMsg.data.subType || 0, sendMsg.data.subType || 0,
sendMsg.data.type === 'flash' sendMsg.data.type === 'flash'
) )
deleteAfterSentFiles.push(res.picElement.sourcePath) deleteAfterSentFiles.push(res.picElement.sourcePath!)
sendElements.push(res) sendElements.push(res)
} }
break break
@ -255,17 +255,17 @@ export async function sendMsg(
let totalSize = 0 let totalSize = 0
for (const fileElement of sendElements) { for (const fileElement of sendElements) {
try { try {
if (fileElement.elementType === ElementType.PTT) { if (fileElement.elementType === ElementType.Ptt) {
totalSize += fs.statSync(fileElement.pttElement.filePath).size totalSize += fs.statSync(fileElement.pttElement.filePath!).size
} }
if (fileElement.elementType === ElementType.FILE) { if (fileElement.elementType === ElementType.File) {
totalSize += fs.statSync(fileElement.fileElement.filePath).size totalSize += fs.statSync(fileElement.fileElement.filePath).size
} }
if (fileElement.elementType === ElementType.VIDEO) { if (fileElement.elementType === ElementType.Video) {
totalSize += fs.statSync(fileElement.videoElement.filePath).size totalSize += fs.statSync(fileElement.videoElement.filePath).size
} }
if (fileElement.elementType === ElementType.PIC) { if (fileElement.elementType === ElementType.Pic) {
totalSize += fs.statSync(fileElement.picElement.sourcePath).size totalSize += fs.statSync(fileElement.picElement.sourcePath!).size
} }
} catch (e) { } catch (e) {
ctx.logger.warn('文件大小计算失败', e, fileElement) ctx.logger.warn('文件大小计算失败', e, fileElement)
@ -276,8 +276,7 @@ export async function sendMsg(
//log('设置消息超时时间', timeout) //log('设置消息超时时间', timeout)
const returnMsg = await ctx.ntMsgApi.sendMsg(peer, sendElements, timeout) const returnMsg = await ctx.ntMsgApi.sendMsg(peer, sendElements, timeout)
if (returnMsg) { if (returnMsg) {
returnMsg.msgShortId = ctx.store.createMsgShortId(peer, returnMsg.msgId) ctx.logger.info('消息发送', peer)
ctx.logger.info('消息发送', returnMsg.msgShortId)
deleteAfterSentFiles.map(path => fsPromise.unlink(path)) deleteAfterSentFiles.map(path => fsPromise.unlink(path))
return returnMsg return returnMsg
} }
@ -294,10 +293,10 @@ export enum CreatePeerMode {
Group = 2 Group = 2
} }
export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode: CreatePeerMode): Promise<Peer> { export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode = CreatePeerMode.Normal): Promise<Peer> {
if ((mode === CreatePeerMode.Group || mode === CreatePeerMode.Normal) && payload.group_id) { if ((mode === CreatePeerMode.Group || mode === CreatePeerMode.Normal) && payload.group_id) {
return { return {
chatType: ChatType.group, chatType: ChatType.Group,
peerUid: payload.group_id.toString(), peerUid: payload.group_id.toString(),
} }
} }
@ -306,7 +305,7 @@ export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode:
if (!uid) throw new Error('无法获取用户信息') if (!uid) throw new Error('无法获取用户信息')
const isBuddy = await ctx.ntFriendApi.isBuddy(uid) const isBuddy = await ctx.ntFriendApi.isBuddy(uid)
return { return {
chatType: isBuddy ? ChatType.friend : ChatType.temp, chatType: isBuddy ? ChatType.C2C : ChatType.TempC2CFromGroup,
peerUid: uid, peerUid: uid,
} }
} }

View File

@ -1 +1 @@
export const version = '3.33.6' export const version = '3.33.7'