feat: FriendAddNotice

This commit is contained in:
linyuchen 2024-04-30 23:06:50 +08:00
parent bcb6b51241
commit 59cd28a2fd
17 changed files with 124 additions and 34 deletions

14
CHANGELOG Normal file
View File

@ -0,0 +1,14 @@
# 3.24.0
## 修复
* 修复图片rkey导致链接失效的问题
* 修复/get_image, /get_file 无法获取图片的问题
* 修复上报他人管理员被取消通知
## 新增
* 新增表情回应发送和上报
* 新增商城表情发送,和上报 url
* 新增转发单条消息接口 `forward_friend_single_msg`, `forward_group_single_msg`
* 新增新增好友事件

View File

@ -1,10 +1,10 @@
{ {
"manifest_version": 4, "manifest_version": 4,
"type": "extension", "type": "extension",
"name": "LLOneBot v3.23.0", "name": "LLOneBot v3.24.0",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新", "description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
"version": "3.23.0", "version": "3.24.0",
"icon": "./icon.jpg", "icon": "./icon.jpg",
"authors": [ "authors": [
{ {
@ -20,7 +20,11 @@
"name": "LLOneBot.zip" "name": "LLOneBot.zip"
} }
}, },
"platform": ["win32", "linux", "darwin"], "platform": [
"win32",
"linux",
"darwin"
],
"injects": { "injects": {
"renderer": "./renderer/index.js", "renderer": "./renderer/index.js",
"main": "./main/main.cjs", "main": "./main/main.cjs",

View File

@ -3,6 +3,7 @@ import { type FileCache, type LLOneBotError } from './types'
import { NTQQGroupApi } from '../ntqqapi/api/group' import { NTQQGroupApi } from '../ntqqapi/api/group'
import { log } from './utils/log' import { log } from './utils/log'
import { isNumeric } from './utils/helper' import { isNumeric } from './utils/helper'
import { NTQQFriendApi } from '../ntqqapi/api'
export const selfInfo: SelfInfo = { export const selfInfo: SelfInfo = {
uid: '', uid: '',
@ -24,14 +25,17 @@ export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
let filterKey = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid' let filterKey = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid'
let filterValue = uinOrUid let filterValue = uinOrUid
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString()) let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
// if (!friend) { if (!friend) {
// try { try {
// friends = (await NTQQApi.getFriends(true)) const _friends = (await NTQQFriendApi.getFriends(true))
// friend = friends.find(friend => friend[filterKey] === filterValue.toString()) friend = _friends.find(friend => friend[filterKey] === filterValue.toString())
// } catch (e) { if (friend){
// // log("刷新好友列表失败", e.stack.toString()) friends.push(friend)
// } }
// } } catch (e) {
log("刷新好友列表失败", e.stack.toString())
}
}
return friend return friend
} }
@ -44,7 +48,8 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
if (group) { if (group) {
groups.push(group) groups.push(group)
} }
} catch (e) {} } catch (e) {
}
} }
return group return group
} }

View File

@ -157,6 +157,7 @@ function onLoad() {
const { debug, reportSelfMessage } = getConfigUtil().getConfig() const { debug, reportSelfMessage } = getConfigUtil().getConfig()
for (let message of msgList) { for (let message of msgList) {
// 过滤启动之前的消息 // 过滤启动之前的消息
// log('收到新消息', message);
if (parseInt(message.msgTime) < startTime / 1000) { if (parseInt(message.msgTime) < startTime / 1000) {
continue continue
} }
@ -191,6 +192,12 @@ function onLoad() {
postOB11Event(groupEvent) postOB11Event(groupEvent)
} }
}) })
OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
if (friendAddEvent) {
// log("post friend add event", friendAddEvent);
postOB11Event(friendAddEvent)
}
})
} }
} }
@ -219,7 +226,7 @@ function onLoad() {
}) })
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
for (const message of payload.msgList) { for (const message of payload.msgList) {
// log("message update", message.sendStatus, message.msgId, message.msgSeq) // log("message update", message)
if (message.recallTime != '0') { if (message.recallTime != '0') {
//todo: 这个判断方法不太好,应该使用灰色消息元素来判断 //todo: 这个判断方法不太好,应该使用灰色消息元素来判断
// 撤回消息上报 // 撤回消息上报
@ -304,7 +311,7 @@ function onLoad() {
// if (notify.user2.uid) { // if (notify.user2.uid) {
// member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid); // member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
// } // }
if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET].includes(notify.type)) { if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type)) {
const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid) const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid)
log('有管理员变动通知') log('有管理员变动通知')
refreshGroupMembers(notify.group.groupCode).then() refreshGroupMembers(notify.group.groupCode).then()
@ -314,7 +321,7 @@ function onLoad() {
if (member1) { if (member1) {
log('变动管理员获取成功') log('变动管理员获取成功')
groupAdminNoticeEvent.user_id = parseInt(member1.uin) groupAdminNoticeEvent.user_id = parseInt(member1.uin)
groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? 'unset' : 'set' groupAdminNoticeEvent.sub_type = [GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type) ? 'unset' : 'set'
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal; // member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
postOB11Event(groupAdminNoticeEvent, true) postOB11Event(groupAdminNoticeEvent, true)
} else { } else {

View File

@ -2,6 +2,7 @@ import { Friend, FriendRequest } from '../types'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
import { friendRequests } from '../../common/data' import { friendRequests } from '../../common/data'
import { log } from '../../common/utils'
export class NTQQFriendApi { export class NTQQFriendApi {
static async getFriends(forced = false) { static async getFriends(forced = false) {
@ -16,7 +17,9 @@ export class NTQQFriendApi {
methodName: NTQQApiMethod.FRIENDS, methodName: NTQQApiMethod.FRIENDS,
args: [{ force_update: forced }, undefined], args: [{ force_update: forced }, undefined],
cbCmd: ReceiveCmdS.FRIENDS, cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}) })
// log('获取好友列表', data)
let _friends: Friend[] = [] let _friends: Friend[] = []
for (const fData of data.data) { for (const fData of data.data) {
_friends.push(...fData.buddyList) _friends.push(...fData.buddyList)

View File

@ -23,6 +23,8 @@ import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
import { encodeSilk } from '../common/utils/audio' import { encodeSilk } from '../common/utils/audio'
import { isNull } from '../common/utils' import { isNull } from '../common/utils'
export const mFaceCache = new Map<string, string>(); // emojiId -> faceName
export class SendMsgElementConstructor { export class SendMsgElementConstructor {
static poke(groupCode: string, uin: string) { static poke(groupCode: string, uin: string) {
return null return null
@ -117,7 +119,15 @@ export class SendMsgElementConstructor {
} }
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> { static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
try{
await fs.stat(filePath)
}catch (e) {
throw `文件${filePath}异常,不存在`
}
log("复制视频到QQ目录", filePath)
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO) let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO)
log("复制视频到QQ目录完成", path)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
@ -184,7 +194,7 @@ export class SendMsgElementConstructor {
}) })
let thumbPath = new Map() let thumbPath = new Map()
const _thumbPath = await createThumb const _thumbPath = await createThumb
log('生成缩略图', _thumbPath) log('生成视频缩略图', _thumbPath)
const thumbSize = (await fs.stat(_thumbPath)).size const thumbSize = (await fs.stat(_thumbPath)).size
// log("生成缩略图", _thumbPath) // log("生成缩略图", _thumbPath)
thumbPath.set(0, _thumbPath) thumbPath.set(0, _thumbPath)
@ -273,7 +283,7 @@ export class SendMsgElementConstructor {
emojiPackageId, emojiPackageId,
emojiId, emojiId,
key, key,
faceName faceName: faceName || mFaceCache.get(emojiId) || '[商城表情]',
}, },
} }
} }

View File

@ -372,6 +372,8 @@ export interface MultiForwardMsgElement {
export interface RawMessage { export interface RawMessage {
msgId: string msgId: string
msgType: number
subMsgType: number
msgShortId?: number // 自己维护的消息id msgShortId?: number // 自己维护的消息id
msgTime: string // 时间戳,秒 msgTime: string // 时间戳,秒
msgSeq: string msgSeq: string

View File

@ -5,7 +5,8 @@ export enum GroupNotifyTypes {
ADMIN_SET = 8, ADMIN_SET = 8,
KICK_MEMBER = 9, KICK_MEMBER = 9,
MEMBER_EXIT = 11, // 主动退出 MEMBER_EXIT = 11, // 主动退出
ADMIN_UNSET = 12, ADMIN_UNSET = 12, // 我被取消管理员
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
} }
export interface GroupNotifies { export interface GroupNotifies {

View File

@ -7,7 +7,7 @@ import { NTQQGroupApi } from '../../../ntqqapi/api'
import { log } from '../../../common/utils' import { log } from '../../../common/utils'
interface Payload { interface Payload {
no_cache: boolean no_cache: boolean | string
} }
class GetGroupList extends BaseAction<Payload, OB11Group[]> { class GetGroupList extends BaseAction<Payload, OB11Group[]> {
@ -16,11 +16,11 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if ( if (
groups.length === 0 groups.length === 0
// || payload.no_cache === true || payload?.no_cache === true || payload?.no_cache === 'true'
) { ) {
try { try {
const groups = await NTQQGroupApi.getGroups(true) const groups = await NTQQGroupApi.getGroups(true)
// log("get groups", groups) log("强制刷新群列表, 数量:", groups.length)
return OB11Constructor.groups(groups) return OB11Constructor.groups(groups)
} catch (e) {} } catch (e) {}
} }

View File

@ -4,9 +4,11 @@ import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group' import { NTQQGroupApi } from '../../../ntqqapi/api/group'
import { log } from '../../../common/utils'
export interface PayloadType { export interface PayloadType {
group_id: number group_id: number,
no_cache: boolean | string
} }
class GetGroupMemberList extends BaseAction<PayloadType, OB11GroupMember[]> { class GetGroupMemberList extends BaseAction<PayloadType, OB11GroupMember[]> {
@ -15,8 +17,9 @@ class GetGroupMemberList extends BaseAction<PayloadType, OB11GroupMember[]> {
protected async _handle(payload: PayloadType) { protected async _handle(payload: PayloadType) {
const group = await getGroup(payload.group_id.toString()) const group = await getGroup(payload.group_id.toString())
if (group) { if (group) {
if (!group.members?.length) { if (!group.members?.length || payload.no_cache === true || payload.no_cache === 'true') {
group.members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) group.members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString())
log('强制刷新群成员列表, 数量: ', group.members.length)
} }
return OB11Constructor.groupMembers(group) return OB11Constructor.groupMembers(group)
} else { } else {

View File

@ -12,6 +12,9 @@ class DeleteMsg extends BaseAction<Payload, void> {
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
let msg = await dbUtil.getMsgByShortId(payload.message_id) let msg = await dbUtil.getMsgByShortId(payload.message_id)
if (!msg) {
throw `消息${payload.message_id}不存在`
}
await NTQQMsgApi.recallMsg( await NTQQMsgApi.recallMsg(
{ {
chatType: msg.chatType, chatType: msg.chatType,

View File

@ -179,7 +179,7 @@ export async function createSendElements(
break break
case OB11MessageDataType.mface: { case OB11MessageDataType.mface: {
sendElements.push( sendElements.push(
SendMsgElementConstructor.mface(sendMsg.data.emoji_package_id, sendMsg.data.emoji_id, sendMsg.data.key, sendMsg.data.summary || ""), SendMsgElementConstructor.mface(sendMsg.data.emoji_package_id, sendMsg.data.emoji_id, sendMsg.data.key, sendMsg.data.summary),
) )
} }
case OB11MessageDataType.image: case OB11MessageDataType.image:

View File

@ -3,11 +3,25 @@ import { OB11Constructor } from '../../constructor'
import { friends } from '../../../common/data' import { friends } from '../../../common/data'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQFriendApi } from '../../../ntqqapi/api'
import { log } from '../../../common/utils'
class GetFriendList extends BaseAction<null, OB11User[]> { interface Payload{
no_cache: boolean | string
}
class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList actionName = ActionName.GetFriendList
protected async _handle(payload: null) { protected async _handle(payload: Payload) {
if (friends.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
const _friends = await NTQQFriendApi.getFriends(true)
// log('强制刷新好友列表,结果: ', _friends)
if (_friends.length > 0) {
friends.length = 0
friends.push(..._friends)
}
}
return OB11Constructor.friends(friends) return OB11Constructor.friends(friends)
} }
} }

View File

@ -44,6 +44,8 @@ import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent' import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent'
import { NTQQGroupApi } from '../ntqqapi/api' import { NTQQGroupApi } from '../ntqqapi/api'
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent' import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
import { mFaceCache } from '../ntqqapi/constructor'
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
let lastRKeyUpdateTime = 0 let lastRKeyUpdateTime = 0
@ -247,6 +249,7 @@ export class OB11Constructor {
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId message_data['data']['emoji_id'] = element.marketFaceElement.emojiId
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId) message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId)
message_data['data']['key'] = element.marketFaceElement.key message_data['data']['key'] = element.marketFaceElement.key
mFaceCache.set(md5, element.marketFaceElement.faceName);
} else if (element.markdownElement) { } else if (element.markdownElement) {
message_data['type'] = OB11MessageDataType.markdown message_data['type'] = OB11MessageDataType.markdown
message_data['data']['data'] = element.markdownElement.content message_data['data']['data'] = element.markdownElement.content
@ -458,6 +461,16 @@ export class OB11Constructor {
} }
} }
static async FriendAddEvent(msg: RawMessage): Promise<OB11FriendAddNoticeEvent | undefined> {
if (msg.chatType !== ChatType.friend) {
return;
}
if (msg.msgType === 5 && msg.subMsgType === 12) {
const event = new OB11FriendAddNoticeEvent(parseInt(msg.peerUin));
return event;
}
return;
}
static friend(friend: User): OB11User { static friend(friend: User): OB11User {
return { return {
user_id: parseInt(friend.uin), user_id: parseInt(friend.uin),

View File

@ -0,0 +1,11 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
notice_type = 'friend_add';
user_id: number;
public constructor(userId: number) {
super();
this.user_id = userId;
}
}

View File

@ -165,12 +165,12 @@ async function onSettingWindowCreated(view: Element) {
}</span>`, }</span>`,
SettingButton('选择ffmpeg', 'config-ffmpeg-select'), SettingButton('选择ffmpeg', 'config-ffmpeg-select'),
), ),
// SettingItem( SettingItem(
// '音乐卡片签名地址', '音乐卡片签名地址',
// null, null,
// `<div class="q-input" style="width:210px;"><input class="q-input__inner" data-config-key="musicSignUrl" type="text" value="${config.musicSignUrl}" placeholder="未设置" /></div>`, `<div class="q-input" style="width:210px;"><input class="q-input__inner" data-config-key="musicSignUrl" type="text" value="${config.musicSignUrl}" placeholder="未设置" /></div>`,
// 'config-musicSignUrl', 'config-musicSignUrl',
// ), ),
SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')),
]), ]),
SettingList([ SettingList([

View File

@ -1 +1 @@
export const version = '3.23.0' export const version = '3.24.0'