Compare commits

...

11 Commits

Author SHA1 Message Date
idranme
a58fb31f8e Merge pull request #448 from LLOneBot/dev
release: 3.33.8
2024-09-26 12:57:16 +08:00
idranme
fe85e277f1 chore: v3.33.8 2024-09-26 12:54:30 +08:00
idranme
5217638b46 feat 2024-09-26 01:52:47 +08:00
idranme
f68b707e1c optimize 2024-09-25 22:34:59 +08:00
idranme
c24ce6ec65 adjustment of get_friends_with_category API returns 2024-09-25 22:04:52 +08:00
idranme
f9270c38cf Merge pull request #444 from LLOneBot/dev
release: 3.33.7
2024-09-25 14:59:34 +08:00
idranme
fd478cdaed chore: v3.33.7 2024-09-25 14:55:05 +08:00
idranme
517b233496 fix 2024-09-25 14:52:04 +08:00
idranme
1045c94a91 feat: get_group_file_url API 2024-09-25 12:13:28 +08:00
idranme
032ac85c04 refactor 2024-09-24 19:59:07 +08:00
idranme
1e35ffd7e6 optimize 2024-09-24 14:18:44 +08:00
32 changed files with 476 additions and 380 deletions

View File

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

View File

@@ -38,3 +38,10 @@ export function mergeNewProperties(newObj: Dict, oldObj: Dict) {
export function filterNullable<T>(array: 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) {
super(ctx, 'store', true)
this.cache = new LimitedHashTable<string, number>(1000)
this.cache = new LimitedHashTable(1000)
this.initDatabase()
}

View File

@@ -23,7 +23,6 @@ import { fileTypeFromFile } from 'file-type'
import { copyFile, stat, unlink } from 'node:fs/promises'
import { Time } from 'cosmokit'
import { Service, Context } from 'cordis'
import { TEMP_DIR } from '@/common/globalVars'
declare module 'cordis' {
interface Context {
@@ -73,7 +72,7 @@ export class NTQQFileApi extends Service {
}
// 上传文件到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)
let fileName = path.basename(filePath)
if (!fileName.includes('.')) {
@@ -107,8 +106,8 @@ export class NTQQFileApi extends Service {
chatType: ChatType,
peerUid: string,
elementId: string,
thumbPath: string,
sourcePath: string,
thumbPath = '',
sourcePath = '',
timeout = 1000 * 60 * 2,
force = false
) {
@@ -147,13 +146,7 @@ export class NTQQFileApi extends Service {
timeout
}
)
let filePath = data.notifyInfo.filePath
if (filePath.startsWith('\\')) {
const downloadPath = TEMP_DIR
filePath = path.join(downloadPath, filePath)
// 下载路径是下载文件夹的相对路径
}
return filePath
return data.notifyInfo.filePath
}
async getImageSize(filePath: string) {
@@ -201,6 +194,27 @@ export class NTQQFileApi extends Service {
this.ctx.logger.error('图片url获取失败', element)
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 {
@@ -222,7 +236,7 @@ export class NTQQFileCacheApi extends Service {
}
scanCache() {
invoke<GeneralCallResult>(ReceiveCmdS.CACHE_SCAN_FINISH, [], { classNameIsRegister: true })
invoke<GeneralCallResult>(ReceiveCmdS.CACHE_SCAN_FINISH, [], { registerEvent: true })
return invoke<CacheScanResult>(NTMethod.CACHE_SCAN, [null, null], { timeout: 300 * Time.second })
}

View File

@@ -101,52 +101,20 @@ export class NTQQFriendApi extends Service {
return retMap
}
async getBuddyV2ExWithCate(refresh = false) {
const session = getSession()
if (session) {
const uids: string[] = []
const categoryMap: Map<string, Dict> = new Map()
const buddyService = session.getBuddyService()
const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data
uids.push(
...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName })
})
return item.buddyUids
}))
const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key)
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value
})
} else {
const data = await invoke<{
buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo>
}>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API,
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}
)
const category: Map<number, Pick<CategoryFriend, 'buddyUids' | 'categroyName'>> = new Map()
for (const item of data.buddyCategory) {
category.set(item.categoryId, pick(item, ['buddyUids', 'categroyName']))
async getBuddyV2WithCate(refresh = false) {
const data = await invoke<{
buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo>
}>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API,
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}
return Object.values(data.userSimpleInfos)
.filter(v => v.baseInfo && category.get(v.baseInfo.categoryId)?.buddyUids.includes(v.uid!))
.map(value => {
return {
...value,
categoryId: value.baseInfo.categoryId,
categroyName: category.get(value.baseInfo.categoryId)?.categroyName
}
})
}
)
return data
}
async isBuddy(uid: string): Promise<boolean> {

View File

@@ -7,7 +7,8 @@ import {
GroupRequestOperateTypes,
GetFileListParam,
OnGroupFileInfoUpdateParams,
PublishGroupBulletinReq
PublishGroupBulletinReq,
GroupAllInfo
} from '../types'
import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services'
@@ -104,7 +105,7 @@ export class NTQQGroupApi extends Service {
}
async getSingleScreenNotifies(num: number) {
invoke(ReceiveCmdS.GROUP_NOTIFY, [], { classNameIsRegister: true })
invoke(ReceiveCmdS.GROUP_NOTIFY, [], { registerEvent: true })
return (await invoke<GroupNotifies>(
'nodeIKernelGroupService/getSingleScreenNotifies',
[{ doubt: false, startSeq: '', number: num }, null],
@@ -156,12 +157,7 @@ export class NTQQGroupApi extends Service {
}
}
async kickMember(
groupCode: string,
kickUids: string[],
refuseForever = false,
kickReason = '',
) {
async kickMember(groupCode: string, kickUids: string[], refuseForever = false, kickReason = '') {
const session = getSession()
if (session) {
return session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason)
@@ -277,7 +273,7 @@ export class NTQQGroupApi extends Service {
}
async getGroupFileList(groupId: string, fileListForm: GetFileListParam) {
invoke('nodeIKernelMsgListener/onGroupFileInfoUpdate', [], { classNameIsRegister: true })
invoke('nodeIKernelMsgListener/onGroupFileInfoUpdate', [], { registerEvent: true })
const data = await invoke<{ fileInfo: OnGroupFileInfoUpdateParams }>(
'nodeIKernelRichMediaService/getGroupFileList',
[
@@ -331,4 +327,24 @@ export class NTQQGroupApi extends Service {
}
}, null])
}
async getGroupAllInfo(groupCode: string, timeout = 1000) {
invoke('nodeIKernelGroupListener/onGroupAllInfoChange', [], { registerEvent: true })
return await invoke<{ groupAll: GroupAllInfo }>(
'nodeIKernelGroupService/getGroupAllInfo',
[
{
groupCode,
source: 4
},
null
],
{
cbCmd: 'nodeIKernelGroupListener/onGroupAllInfoChange',
afterFirstCmd: false,
cmdCB: payload => payload.groupAll.groupCode === groupCode,
timeout
}
)
}
}

View File

@@ -63,7 +63,7 @@ class Core extends Service {
uids = payload.data.flatMap(item => item.buddyList.map(e => e.uid))
}
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)
})
@@ -116,7 +116,7 @@ class Core extends Service {
if (activatedPeerUids.includes(contact.id)) continue
activatedPeerUids.push(contact.id)
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.getMsgHistory(peer, '', 20).then(({ msgList }) => {
const lastTempMsg = msgList.at(-1)
@@ -136,12 +136,12 @@ class Core extends Service {
registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => {
const peerUid = payload[0] as string
this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid)
let chatType = ChatType.friend
let chatType = ChatType.C2C
if (isNumeric(peerUid)) {
chatType = ChatType.group
chatType = ChatType.Group
}
else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) {
chatType = ChatType.temp
chatType = ChatType.TempC2CFromGroup
}
const peer = { peerUid, chatType }
await this.ctx.sleep(1000)
@@ -215,7 +215,7 @@ class Core extends Service {
this.ctx.parallel('nt/friend-request', payload.data.buddyReqs)
})
invoke('nodeIKernelMsgListener/onRecvSysMsg', [], { classNameIsRegister: true })
invoke('nodeIKernelMsgListener/onRecvSysMsg', [], { registerEvent: true })
registerReceiveHook<{
msgBuf: number[]

View File

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

View File

@@ -98,7 +98,7 @@ interface NTService {
interface InvokeOptions<ReturnType> {
className?: NTClass
channel?: NTChannel
classNameIsRegister?: boolean
registerEvent?: boolean
cbCmd?: string | string[]
cmdCB?: (payload: ReturnType, result: unknown) => boolean
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
@@ -115,7 +115,7 @@ export function invoke<
const timeout = options.timeout ?? 5000
const afterFirstCmd = options.afterFirstCmd ?? true
let eventName = className + '-' + channel[channel.length - 1]
if (options.classNameIsRegister) {
if (options.registerEvent) {
eventName += '-register'
}
return new Promise<R>((resolve, reject) => {

View File

@@ -78,3 +78,45 @@ export interface PublishGroupBulletinReq {
pinned: number
confirmRequired: number
}
export interface GroupAllInfo {
groupCode: string
ownerUid: string
groupFlag: number
groupFlagExt: number
maxMemberNum: number
memberNum: number
groupOption: number
classExt: number
groupName: string
fingerMemo: string
groupQuestion: string
certType: number
shutUpAllTimestamp: number
shutUpMeTimestamp: number //解除禁言时间
groupTypeFlag: number
privilegeFlag: number
groupSecLevel: number
groupFlagExt3: number
isConfGroup: number
isModifyConfGroupFace: number
isModifyConfGroupName: number
noFigerOpenFlag: number
noCodeFingerOpenFlag: number
groupFlagExt4: number
groupMemo: string
cmdUinMsgSeq: number
cmdUinJoinTime: number
cmdUinUinFlag: number
cmdUinMsgMask: number
groupSecLevelInfo: number
cmdUinPrivilege: number
cmdUinFlagEx2: number
appealDeadline: number
remarkName: number
isTop: boolean
richFingerMemo: string
groupAnswer: string
joinGroupAuth: string
isAllowModifyConfGroupName: number
}

View File

@@ -1,126 +1,108 @@
import { GroupMemberRole } from './group'
import { GeneralCallResult } from '../services'
export interface GetFileListParam {
sortType: number
fileCount: number
startIndex: number
sortOrder: number
showOnlinedocFolder: number
folderId?: string
}
export enum ElementType {
UNKNOWN = 0,
TEXT = 1,
PIC = 2,
FILE = 3,
PTT = 4,
VIDEO = 5,
FACE = 6,
REPLY = 7,
WALLET = 9,
GreyTip = 8, //Poke别叫戳一搓了 官方名字拍一拍 戳一戳是另一个名字
ARK = 10,
MFACE = 11,
LIVEGIFT = 12,
STRUCTLONGMSG = 13,
MARKDOWN = 14,
GIPHY = 15,
MULTIFORWARD = 16,
INLINEKEYBOARD = 17,
INTEXTGIFT = 18,
CALENDAR = 19,
YOLOGAMERESULT = 20,
AVRECORD = 21,
FEED = 22,
TOFURECORD = 23,
ACEBUBBLE = 24,
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
Text = 1,
Pic = 2,
File = 3,
Ptt = 4,
Video = 5,
Face = 6,
Reply = 7,
GrayTip = 8,
Ark = 10,
MarketFace = 11,
LiveGift = 12,
StructLongMsg = 13,
Markdown = 14,
Giphy = 15,
MultiForward = 16,
InlineKeyboard = 17,
Calendar = 19,
YoloGameResult = 20,
AvRecord = 21,
TofuRecord = 23,
FaceBubble = 27,
ShareLocation = 28,
TaskTopMsg = 29,
RecommendedMsg = 43,
ActionBar = 44
}
export interface SendTextElement {
elementType: ElementType.TEXT
elementType: ElementType.Text
elementId: ''
textElement: TextElement
}
export interface SendPttElement {
elementType: ElementType.PTT
elementType: ElementType.Ptt
elementId: ''
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, // 表情包小图
pttElement: Partial<PttElement>
}
export interface SendPicElement {
elementType: ElementType.PIC
elementType: ElementType.Pic
elementId: ''
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
}
picElement: Partial<PicElement>
}
export interface SendReplyElement {
elementType: ElementType.REPLY
elementType: ElementType.Reply
elementId: ''
replyElement: Partial<ReplyElement>
}
export interface SendFaceElement {
elementType: ElementType.FACE
elementType: ElementType.Face
elementId: ''
faceElement: FaceElement
}
export interface SendMarketFaceElement {
elementType: ElementType.MFACE
elementType: ElementType.MarketFace
elementId: ''
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 {
content: string
atType: number
atType: AtType
atUid: string
atTinyId: string
atNtUid: string
@@ -157,47 +139,6 @@ export interface FileElement {
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 {
canConvert2Text: boolean
duration: number // 秒数
@@ -208,7 +149,7 @@ export interface PttElement {
fileSize: string // "4261"
fileSubId: string // "0"
fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
formatType: string // 1
formatType: number // 1
invalidState: number // 0
md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6"
playState: number // 0
@@ -219,6 +160,7 @@ export interface PttElement {
voiceChangeType: number // 0
voiceType: number // 0
waveAmplitudes: number[]
autoConvertText: number
}
export interface ArkElement {
@@ -230,6 +172,16 @@ export interface ArkElement {
export const IMAGE_HTTP_HOST = 'https://gchat.qpic.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 {
picSubType: PicSubType
picType: PicType // 有这玩意儿吗
@@ -246,22 +198,22 @@ export interface PicElement {
}
export enum GrayTipElementSubType {
REVOKE = 1,
PROCLAMATION = 2,
EMOJIREPLY = 3,
GROUP = 4,
BUDDY = 5,
FEED = 6,
ESSENCE = 7,
GROUPNOTIFY = 8,
BUDDYNOTIFY = 9,
FILE = 10,
FEEDCHANNELMSG = 11,
XMLMSG = 12,
LOCALMSG = 13,
BLOCK = 14,
AIOOP = 15,
WALLET = 16,
Revoke = 1,
Proclamation = 2,
EmojiReply = 3,
Group = 4,
Buddy = 5,
Feed = 6,
Essence = 7,
GroupNotify = 8,
BuddyNotify = 9,
File = 10,
FeedChannelMsg = 11,
XmlMsg = 12,
LocalMsg = 13,
Block = 14,
AioOp = 15,
Wallet = 16,
JSON = 17,
}
@@ -291,7 +243,7 @@ export interface GrayTipElement {
export enum FaceIndex {
dice = 358,
Dice = 358,
RPS = 359, // 石头剪刀布
}
@@ -365,6 +317,7 @@ export interface InlineKeyboardElementRowButton {
enter: false
subscribeDataTemplateIds: []
}
export interface InlineKeyboardElement {
rows: [
{
@@ -381,9 +334,9 @@ export interface TipAioOpGrayTipElement {
}
export enum TipGroupElementType {
memberIncrease = 1,
kicked = 3, // 被移出群
ban = 8,
MemberIncrease = 1,
Kicked = 3, // 被移出群
Ban = 8,
}
export interface TipGroupElement {
@@ -425,17 +378,27 @@ export interface TipGroupElement {
}
}
export interface StructLongMsgElement {
xmlContent: string
resId: string
}
export interface MultiForwardMsgElement {
xmlContent: string // xml格式的消息内容
resId: string
fileName: string
}
export enum ChatType {
C2C = 1,
Group = 2,
TempC2CFromGroup = 100,
}
export interface RawMessage {
msgId: string
msgType: number
subMsgType: number
msgShortId?: number // 自己维护的消息id
msgTime: string // 时间戳,秒
msgSeq: string
msgRandom: string
@@ -475,12 +438,11 @@ export interface MessageElement {
fileElement?: FileElement
liveGiftElement?: unknown
markdownElement?: MarkdownElement
structLongMsgElement?: unknown
structLongMsgElement?: StructLongMsgElement
multiForwardMsgElement?: MultiForwardMsgElement
giphyElement?: unknown
walletElement?: unknown
inlineKeyboardElement?: InlineKeyboardElement
textGiftElement?: unknown //????
textGiftElement?: unknown
calendarElement?: unknown
yoloGameResultElement?: unknown
avRecordElement?: unknown
@@ -588,3 +550,12 @@ export interface TmpChatInfoApi extends GeneralCallResult {
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,
guildId: ''
}
if (fileCache[0].elementType === ElementType.PIC) {
if (fileCache[0].elementType === ElementType.Pic) {
const msgList = await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [fileCache[0].msgId])
if (msgList.msgList.length === 0) {
throw new Error('msg not found')
@@ -59,7 +59,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
throw new Error('element not found')
}
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)
}
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 peer = {
guildId: '',
chatType: ChatType.group,
chatType: ChatType.Group,
peerUid: 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 { OB11Entities } from '../../entities'
import { RawMessage } from '@/ntqqapi/types'
import { filterNullable } from '@/common/utils/misc'
import { filterNullable, parseBool } from '@/common/utils/misc'
interface Payload {
group_id: number | string
@@ -23,12 +23,12 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
group_id: Schema.union([Number, String]).required(),
message_seq: Schema.union([Number, String]),
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> {
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[]
if (!payload.message_seq || payload.message_seq === '0') {
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 (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)))
return { messages: filterNullable(ob11MsgList) }
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { BaseAction, Schema } from '../BaseAction'
import { GroupMemberRole } from '@/ntqqapi/types'
import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload {
group_id: number | string
@@ -13,7 +14,7 @@ export default class SetGroupAdmin extends BaseAction<Payload, null> {
payloadSchema = Schema.object({
group_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> {

View File

@@ -1,5 +1,6 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload {
group_id: number | string
@@ -12,7 +13,7 @@ export default class SetGroupKick extends BaseAction<Payload, null> {
payloadSchema = Schema.object({
group_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> {

View File

@@ -1,5 +1,6 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload {
group_id: number | string
@@ -10,7 +11,7 @@ export default class SetGroupWholeBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupWholeBan
payloadSchema = Schema.object({
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> {

View File

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

View File

@@ -3,7 +3,7 @@ import { OB11Message } from '@/onebot11/types'
import { ActionName } from '../types'
import { ChatType, RawMessage } from '@/ntqqapi/types'
import { OB11Entities } from '@/onebot11/entities'
import { filterNullable } from '@/common/utils/misc'
import { filterNullable, parseBool } from '@/common/utils/misc'
interface Payload {
user_id: number | string
@@ -24,7 +24,7 @@ export class GetFriendMsgHistory extends BaseAction<Payload, Response> {
message_seq: Schema.union([Number, String]),
message_id: Schema.union([Number, String]),
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> {
@@ -38,14 +38,11 @@ export class GetFriendMsgHistory extends BaseAction<Payload, Response> {
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!uid) throw new Error(`记录${payload.user_id}不存在`)
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
}
if (msgList.length === 0) throw new Error('未找到消息')
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)))
return { messages: filterNullable(ob11MsgList) }
}

View File

@@ -4,14 +4,35 @@ import { OB11Entities } from '../../entities'
import { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils'
export class GetFriendWithCategory extends BaseAction<void, OB11User[]> {
interface Category {
categoryId: number
categorySortId: number
categoryName: string
categoryMbCount: number
onlineCount: number
buddyList: OB11User[]
}
export class GetFriendWithCategory extends BaseAction<void, Category[]> {
actionName = ActionName.GetFriendsWithCategory
protected async _handle() {
if (getBuildVersion() >= 26702) {
return OB11Entities.friendsV2(await this.ctx.ntFriendApi.getBuddyV2ExWithCate(true))
} else {
if (getBuildVersion() < 26702) {
throw new Error('this ntqq version not support, must be 26702 or later')
}
const data = await this.ctx.ntFriendApi.getBuddyV2WithCate(true)
return data.buddyCategory.map(item => {
return {
categoryId: item.categoryId,
categorySortId: item.categorySortId,
categoryName: item.categroyName,
categoryMbCount: item.categroyMbCount,
onlineCount: item.onlineCount,
buddyList: item.buddyUids.map(uid => {
const info = data.userSimpleInfos[uid]
return OB11Entities.friendV2(info)
})
}
})
}
}

View File

@@ -1,7 +1,6 @@
import { BaseAction } from '../BaseAction'
import { ChatType } from '@/ntqqapi/types'
import { ActionName } from '../types'
import { Peer } from '@/ntqqapi/types'
import { createPeer } from '@/onebot11/helper/createMessage'
interface Payload {
message_id: number | string
@@ -10,17 +9,6 @@ interface Payload {
}
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> {
if (!payload.message_id) {
throw Error('message_id不能为空')
@@ -29,7 +17,7 @@ abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
if (!msg) {
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])
if (ret.result !== 0) {
throw new Error(`转发消息失败 ${ret.errMsg}`)

View File

@@ -93,7 +93,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnData> {
if (!returnMsg) {
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 {

View File

@@ -81,5 +81,6 @@ export enum ActionName {
GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
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 { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils'
import { parseBool } from '@/common/utils/misc'
interface Payload {
no_cache: boolean
@@ -11,7 +12,7 @@ interface Payload {
export class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList
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) {

View File

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

View File

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

View File

@@ -53,14 +53,15 @@ export namespace OB11Entities {
messagePostFormat,
} = ctx.config as OneBot11Adapter.Config
const selfUin = selfInfo.uin
const msgShortId = ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
const resMsg: OB11Message = {
self_id: parseInt(selfUin),
user_id: parseInt(msg.senderUin),
time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.msgShortId!,
real_id: msg.msgShortId!,
message_seq: msg.msgShortId!,
message_type: msg.chatType === ChatType.group ? 'group' : 'private',
message_id: msgShortId,
real_id: msgShortId,
message_seq: msgShortId,
message_type: msg.chatType === ChatType.Group ? 'group' : 'private',
sender: {
user_id: parseInt(msg.senderUin),
nickname: msg.sendNickName,
@@ -76,7 +77,7 @@ export namespace OB11Entities {
if (debug) {
resMsg.raw = msg
}
if (msg.chatType === ChatType.group) {
if (msg.chatType === ChatType.Group) {
resMsg.sub_type = 'normal'
resMsg.group_id = parseInt(msg.peerUin)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUin)
@@ -86,15 +87,15 @@ export namespace OB11Entities {
resMsg.sender.title = member.memberSpecialTitle ?? ''
}
}
else if (msg.chatType === ChatType.friend) {
else if (msg.chatType === ChatType.C2C) {
resMsg.sub_type = 'friend'
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.temp_source = 0 //群聊
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) {
resMsg.sender.group_id = Number(ret.tmpChatInfo?.groupCode)
} else {
@@ -104,10 +105,10 @@ export namespace OB11Entities {
for (const element of msg.elements) {
let messageSegment: OB11MessageData | undefined
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
if (element.textElement && element.textElement?.atType !== AtType.Unknown) {
let qq: string
let name: string | undefined
if (element.textElement.atType == AtType.atAll) {
if (element.textElement.atType === AtType.All) {
qq = 'all'
} else {
const { atNtUid, atUid, content } = element.textElement
@@ -128,7 +129,7 @@ export namespace OB11Entities {
}
else if (element.textElement) {
const text = element.textElement.content
if (!text.trim()) {
if (!text) {
continue
}
messageSegment = {
@@ -290,28 +291,31 @@ export namespace OB11Entities {
}
else if (element.faceElement) {
const { faceElement } = element
const faceId = faceElement.faceIndex
if (faceId === FaceIndex.dice) {
const { faceIndex, pokeType } = faceElement
if (faceIndex === FaceIndex.Dice) {
messageSegment = {
type: OB11MessageDataType.dice,
data: {
result: faceElement.resultId!
}
}
}
else if (faceId === FaceIndex.RPS) {
} else if (faceIndex === FaceIndex.RPS) {
messageSegment = {
type: OB11MessageDataType.RPS,
data: {
result: faceElement.resultId!
}
}
}
else {
/*} else if (faceIndex === 1 && pokeType === 1) {
messageSegment = {
type: OB11MessageDataType.shake,
data: {}
}*/
} else {
messageSegment = {
type: OB11MessageDataType.face,
data: {
id: faceId.toString()
id: faceIndex.toString()
}
}
}
@@ -355,20 +359,20 @@ export namespace OB11Entities {
}
if (messageSegment) {
const cqCode = encodeCQCode(messageSegment)
if (messagePostFormat === 'string') {
(resMsg.message as string) += cqCode
} else {
if (messagePostFormat === 'array') {
(resMsg.message as OB11MessageData[]).push(messageSegment)
}
resMsg.raw_message += cqCode
}
}
resMsg.raw_message = resMsg.raw_message.trim()
if (messagePostFormat === 'string') {
resMsg.message = resMsg.raw_message
}
return resMsg
}
export async function privateEvent(ctx: Context, msg: RawMessage): Promise<OB11BaseNoticeEvent | void> {
if (msg.chatType !== ChatType.friend) {
if (msg.chatType !== ChatType.C2C) {
return
}
for (const element of msg.elements) {
@@ -397,7 +401,7 @@ export namespace OB11Entities {
}
export async function groupEvent(ctx: Context, msg: RawMessage): Promise<OB11GroupNoticeEvent | void> {
if (msg.chatType !== ChatType.group) {
if (msg.chatType !== ChatType.Group) {
return
}
if (msg.senderUin) {
@@ -417,7 +421,7 @@ export namespace OB11Entities {
const grayTipElement = element.grayTipElement
const groupElement = grayTipElement?.groupElement
if (groupElement) {
if (groupElement.type === TipGroupElementType.memberIncrease) {
if (groupElement.type === TipGroupElementType.MemberIncrease) {
ctx.logger.info('收到群成员增加消息', groupElement)
await ctx.sleep(1000)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.memberUid)
@@ -431,8 +435,8 @@ export namespace OB11Entities {
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin))
}
}
else if (groupElement.type === TipGroupElementType.ban) {
ctx.logger.info('收到群员禁言提示', groupElement)
else if (groupElement.type === TipGroupElementType.Ban) {
ctx.logger.info('收到群员禁言提示', groupElement)
const memberUid = groupElement.shutUp?.member.uid
const adminUid = groupElement.shutUp?.admin.uid
let memberUin: string = ''
@@ -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.ntGroupApi.quitGroup(msg.peerUid)
try {
@@ -507,7 +511,7 @@ export namespace OB11Entities {
const msgSeq: string = emojiLikeData.gtip.url.msgseq
const emojiId: string = emojiLikeData.gtip.face.id
const peer = {
chatType: ChatType.group,
chatType: ChatType.Group,
guildId: '',
peerUid: msg.peerUid,
}
@@ -531,7 +535,7 @@ export namespace OB11Entities {
}
if (
grayTipElement.subElementType == GrayTipElementSubType.XMLMSG &&
grayTipElement.subElementType == GrayTipElementSubType.XmlMsg &&
xmlElement?.templId == '10179'
) {
ctx.logger.info('收到新人被邀请进群消息', grayTipElement)
@@ -575,7 +579,7 @@ export namespace OB11Entities {
if (!groupCode || !msgSeq || !msgRandom) return
const peer = {
guildId: '',
chatType: ChatType.group,
chatType: ChatType.Group,
peerUid: groupCode
}
const essence = await ctx.ntGroupApi.queryCachedEssenceMsg(groupCode, msgSeq, msgRandom)
@@ -611,13 +615,13 @@ export namespace OB11Entities {
shortId: number
): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> {
const msgElement = msg.elements.find(
(element) => element.grayTipElement?.subElementType === GrayTipElementSubType.REVOKE,
(element) => element.grayTipElement?.subElementType === GrayTipElementSubType.Revoke,
)
if (!msgElement) {
return
}
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)
return new OB11GroupRecallNoticeEvent(
parseInt(msg.peerUid),
@@ -645,23 +649,20 @@ export namespace OB11Entities {
return friends.map(friend)
}
export function friendsV2(friends: FriendV2[]): OB11User[] {
const data: OB11User[] = []
for (const friend of friends) {
const sexValue = sex(friend.baseInfo.sex!)
data.push({
...omit(friend.baseInfo, ['richBuffer']),
...friend.coreInfo,
user_id: parseInt(friend.coreInfo.uin),
nickname: friend.coreInfo.nick,
remark: friend.coreInfo.nick,
sex: sexValue,
level: 0,
categroyName: friend.categroyName,
categoryId: friend.categoryId
})
export function friendV2(raw: FriendV2): OB11User {
return {
...omit(raw.baseInfo, ['richBuffer', 'phoneNum']),
...omit(raw.coreInfo, ['nick']),
user_id: parseInt(raw.coreInfo.uin),
nickname: raw.coreInfo.nick,
remark: raw.coreInfo.remark || raw.coreInfo.nick,
sex: sex(raw.baseInfo.sex),
level: 0
}
return data
}
export function friendsV2(raw: FriendV2[]): OB11User[] {
return raw.map(friendV2)
}
export function groupMemberRole(role: number): OB11GroupMemberRole | undefined {

View File

@@ -62,22 +62,22 @@ export async function createSendElements(
}
}
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)
if (atMember) {
const display = `@${atMember.cardName || atMember.nick}`
sendElements.push(
SendElementEntities.at(atQQ, atMember.uid, AtType.atUser, display),
SendElementEntities.at(atQQ, atMember.uid, AtType.One, display),
)
} else {
const atNmae = sendMsg.data?.name
const uid = await ctx.ntUserApi.getUidByUin(atQQ) || ''
const display = atNmae ? `@${atNmae}` : ''
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.type === 'flash'
)
deleteAfterSentFiles.push(res.picElement.sourcePath)
deleteAfterSentFiles.push(res.picElement.sourcePath!)
sendElements.push(res)
}
break
@@ -248,24 +248,32 @@ export async function sendMsg(
sendElements: SendMessageElement[],
deleteAfterSentFiles: string[]
) {
if (peer.chatType === ChatType.Group) {
const info = await ctx.ntGroupApi.getGroupAllInfo(peer.peerUid)
.catch(() => undefined)
const shutUpMeTimestamp = info?.groupAll.shutUpMeTimestamp
if (shutUpMeTimestamp && shutUpMeTimestamp * 1000 > Date.now()) {
throw new Error('当前处于被禁言状态')
}
}
if (!sendElements.length) {
throw '消息体无法解析,请检查是否发送了不支持的消息类型'
throw new Error('消息体无法解析,请检查是否发送了不支持的消息类型')
}
// 计算发送的文件大小
let totalSize = 0
for (const fileElement of sendElements) {
try {
if (fileElement.elementType === ElementType.PTT) {
totalSize += fs.statSync(fileElement.pttElement.filePath).size
if (fileElement.elementType === ElementType.Ptt) {
totalSize += fs.statSync(fileElement.pttElement.filePath!).size
}
if (fileElement.elementType === ElementType.FILE) {
if (fileElement.elementType === ElementType.File) {
totalSize += fs.statSync(fileElement.fileElement.filePath).size
}
if (fileElement.elementType === ElementType.VIDEO) {
if (fileElement.elementType === ElementType.Video) {
totalSize += fs.statSync(fileElement.videoElement.filePath).size
}
if (fileElement.elementType === ElementType.PIC) {
totalSize += fs.statSync(fileElement.picElement.sourcePath).size
if (fileElement.elementType === ElementType.Pic) {
totalSize += fs.statSync(fileElement.picElement.sourcePath!).size
}
} catch (e) {
ctx.logger.warn('文件大小计算失败', e, fileElement)
@@ -276,8 +284,7 @@ export async function sendMsg(
//log('设置消息超时时间', timeout)
const returnMsg = await ctx.ntMsgApi.sendMsg(peer, sendElements, timeout)
if (returnMsg) {
returnMsg.msgShortId = ctx.store.createMsgShortId(peer, returnMsg.msgId)
ctx.logger.info('消息发送', returnMsg.msgShortId)
ctx.logger.info('消息发送', peer)
deleteAfterSentFiles.map(path => fsPromise.unlink(path))
return returnMsg
}
@@ -294,10 +301,10 @@ export enum CreatePeerMode {
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) {
return {
chatType: ChatType.group,
chatType: ChatType.Group,
peerUid: payload.group_id.toString(),
}
}
@@ -306,7 +313,7 @@ export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode:
if (!uid) throw new Error('无法获取用户信息')
const isBuddy = await ctx.ntFriendApi.isBuddy(uid)
return {
chatType: isBuddy ? ChatType.friend : ChatType.temp,
chatType: isBuddy ? ChatType.C2C : ChatType.TempC2CFromGroup,
peerUid: uid,
}
}

View File

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