LLOneBot/src/onebot11/entities.ts
2024-09-07 02:56:59 +08:00

777 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { XMLParser } from 'fast-xml-parser'
import {
OB11Group,
OB11GroupMember,
OB11GroupMemberRole,
OB11Message,
OB11MessageData,
OB11MessageDataType,
OB11User,
OB11UserSex,
} from './types'
import {
AtType,
ChatType,
FaceIndex,
GrayTipElementSubType,
Group,
Peer,
GroupMember,
RawMessage,
Sex,
TipGroupElementType,
User,
FriendV2,
ChatType2
} from '../ntqqapi/types'
import { EventType } from './event/OB11BaseEvent'
import { encodeCQCode } from './cqcode'
import { MessageUnique } from '../common/utils/messageUnique'
import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent'
import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent'
import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent'
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent'
import { calcQQLevel } from '../common/utils/misc'
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent'
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent'
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent'
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'
import { omit, isNullable, pick, Dict } from 'cosmokit'
import { Context } from 'cordis'
import { selfInfo } from '@/common/globalVars'
import { pathToFileURL } from 'node:url'
import OneBot11Adapter from './adapter'
export namespace OB11Entities {
export async function message(ctx: Context, msg: RawMessage): Promise<OB11Message> {
const {
debug,
messagePostFormat,
} = ctx.config as OneBot11Adapter.Config
const selfUin = selfInfo.uin
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',
sender: {
user_id: parseInt(msg.senderUin!),
nickname: msg.sendNickName,
card: msg.sendMemberName ?? '',
},
raw_message: '',
font: 14,
sub_type: 'friend',
message: messagePostFormat === 'string' ? '' : [],
message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: selfUin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
}
if (debug) {
resMsg.raw = msg
}
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!)
if (member) {
resMsg.sender.role = groupMemberRole(member.role)
resMsg.sender.nickname = member.nick
resMsg.sender.title = member.memberSpecialTitle ?? ''
}
}
else if (msg.chatType === ChatType.friend) {
resMsg.sub_type = 'friend'
resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick
}
else if (msg.chatType as unknown as ChatType2 === ChatType2.KCHATTYPETEMPC2CFROMGROUP) {
resMsg.sub_type = 'group'
resMsg.temp_source = 0 //群聊
resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick
const ret = await ctx.ntMsgApi.getTempChatInfo(ChatType2.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid)
if (ret?.result === 0) {
resMsg.sender.group_id = Number(ret.tmpChatInfo?.groupCode)
} else {
resMsg.sender.group_id = 284840486 //兜底数据
}
}
for (const element of msg.elements) {
let messageSegment: OB11MessageData | undefined
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
let qq: string
let name: string | undefined
if (element.textElement.atType == AtType.atAll) {
qq = 'all'
}
else {
const { atNtUid, content } = element.textElement
let atQQ = element.textElement.atUid
if (!atQQ || atQQ === '0') {
const atMember = await ctx.ntGroupApi.getGroupMember(msg.peerUin, atNtUid)
if (atMember) {
atQQ = atMember.uin
}
}
if (atQQ) {
qq = atQQ
name = content.replace('@', '')
}
}
messageSegment = {
type: OB11MessageDataType.at,
data: {
qq: qq!,
name
}
}
}
else if (element.textElement) {
const text = element.textElement.content
if (!text.trim()) {
continue
}
messageSegment = {
type: OB11MessageDataType.text,
data: {
text
}
}
}
else if (element.replyElement) {
const { replyElement } = element
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: ''
}
try {
const { replayMsgSeq, replyMsgTime, senderUidStr } = replyElement
const records = msg.records.find(msgRecord => msgRecord.msgId === replyElement.sourceMsgIdInRecords)
if (!records || !replyMsgTime || !senderUidStr) {
throw new Error('找不到回复消息')
}
const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUidStr])
const replyMsg = msgList.find(msg => msg.msgRandom === records.msgRandom)
// 284840486: 合并消息内侧 消息具体定位不到
if (!replyMsg && msg.peerUin !== '284840486') {
ctx.logger.info('queryMsgs', msgList.map(e => pick(e, ['msgSeq', 'msgRandom'])))
throw new Error('回复消息验证失败')
}
messageSegment = {
type: OB11MessageDataType.reply,
data: {
id: MessageUnique.createMsg(peer, replyMsg ? replyMsg.msgId : records.msgId).toString()
}
}
} catch (e) {
ctx.logger.error('获取不到引用的消息', replyElement, (e as Error).stack)
continue
}
}
else if (element.picElement) {
const { picElement } = element
const fileSize = picElement.fileSize ?? '0'
messageSegment = {
type: OB11MessageDataType.image,
data: {
file: picElement.fileName,
subType: picElement.picSubType,
url: await ctx.ntFileApi.getImageUrl(picElement),
file_size: fileSize,
}
}
MessageUnique.addFileCache({
peerUid: msg.peerUid,
msgId: msg.msgId,
msgTime: +msg.msgTime,
chatType: msg.chatType,
elementId: element.elementId,
elementType: element.elementType,
fileName: picElement.fileName,
fileUuid: picElement.fileUuid,
fileSize,
})
}
else if (element.videoElement) {
const { videoElement } = element
const videoUrl = await ctx.ntFileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
}, msg.msgId, element.elementId)
const fileSize = videoElement.fileSize ?? '0'
messageSegment = {
type: OB11MessageDataType.video,
data: {
file: videoElement.fileName,
url: videoUrl || pathToFileURL(videoElement.filePath).href,
path: videoElement.filePath,
file_size: fileSize,
}
}
MessageUnique.addFileCache({
peerUid: msg.peerUid,
msgId: msg.msgId,
msgTime: +msg.msgTime,
chatType: msg.chatType,
elementId: element.elementId,
elementType: element.elementType,
fileName: videoElement.fileName,
fileUuid: videoElement.fileUuid!,
fileSize,
})
}
else if (element.fileElement) {
const { fileElement } = element
const fileSize = fileElement.fileSize ?? '0'
messageSegment = {
type: OB11MessageDataType.file,
data: {
file: fileElement.fileName,
url: pathToFileURL(fileElement.filePath).href,
file_id: fileElement.fileUuid,
path: fileElement.filePath,
file_size: fileSize,
}
}
MessageUnique.addFileCache({
peerUid: msg.peerUid,
msgId: msg.msgId,
msgTime: +msg.msgTime,
chatType: msg.chatType,
elementId: element.elementId,
elementType: element.elementType,
fileName: fileElement.fileName,
fileUuid: fileElement.fileUuid!,
fileSize,
})
}
else if (element.pttElement) {
const { pttElement } = element
const fileSize = pttElement.fileSize ?? '0'
messageSegment = {
type: OB11MessageDataType.voice,
data: {
file: pttElement.fileName,
url: pathToFileURL(pttElement.filePath).href,
path: pttElement.filePath,
file_size: fileSize,
}
}
MessageUnique.addFileCache({
peerUid: msg.peerUid,
msgId: msg.msgId,
msgTime: +msg.msgTime,
chatType: msg.chatType,
elementId: element.elementId,
elementType: element.elementType,
fileName: pttElement.fileName,
fileUuid: pttElement.fileUuid,
fileSize,
})
}
else if (element.arkElement) {
const { arkElement } = element
messageSegment = {
type: OB11MessageDataType.json,
data: {
data: arkElement.bytesData
}
}
}
else if (element.faceElement) {
const { faceElement } = element
const faceId = faceElement.faceIndex
if (faceId === FaceIndex.dice) {
messageSegment = {
type: OB11MessageDataType.dice,
data: {
result: faceElement.resultId!
}
}
}
else if (faceId === FaceIndex.RPS) {
messageSegment = {
type: OB11MessageDataType.RPS,
data: {
result: faceElement.resultId!
}
}
}
else {
messageSegment = {
type: OB11MessageDataType.face,
data: {
id: faceId.toString()
}
}
}
}
else if (element.marketFaceElement) {
const { marketFaceElement } = element
const { emojiId } = marketFaceElement
// 取md5的前两位
const dir = emojiId.substring(0, 2)
// 获取组装url
// const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000`
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`
messageSegment = {
type: OB11MessageDataType.mface,
data: {
summary: marketFaceElement.faceName!,
url,
emoji_id: emojiId,
emoji_package_id: marketFaceElement.emojiPackageId,
key: marketFaceElement.key
}
}
//mFaceCache.set(emojiId, element.marketFaceElement.faceName!)
}
else if (element.markdownElement) {
const { markdownElement } = element
messageSegment = {
type: OB11MessageDataType.markdown,
data: {
data: markdownElement.content
}
}
}
else if (element.multiForwardMsgElement) {
messageSegment = {
type: OB11MessageDataType.forward,
data: {
id: msg.msgId
}
}
}
if (messageSegment) {
const cqCode = encodeCQCode(messageSegment)
if (messagePostFormat === 'string') {
(resMsg.message as string) += cqCode
} else {
(resMsg.message as OB11MessageData[]).push(messageSegment)
}
resMsg.raw_message += cqCode
}
}
resMsg.raw_message = resMsg.raw_message.trim()
return resMsg
}
export async function privateEvent(ctx: Context, msg: RawMessage): Promise<OB11BaseNoticeEvent | void> {
if (msg.chatType !== ChatType.friend) {
return
}
for (const element of msg.elements) {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr)
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
const pokedetail: Dict[] = json.items
//筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid)
if (poke_uid.length == 2) {
return new OB11FriendPokeEvent(
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid)),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid)),
pokedetail
)
}
}
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
}
}
}
// 好友增加事件
if (msg.msgType === 5 && msg.subMsgType === 12) {
const event = new OB11FriendAddNoticeEvent(parseInt(msg.peerUin))
return event
}
}
export async function groupEvent(ctx: Context, msg: RawMessage): Promise<OB11GroupNoticeEvent | void> {
if (msg.chatType !== ChatType.group) {
return
}
if (msg.senderUin) {
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, msg.senderUin)
if (member && member.cardName !== msg.sendMemberName) {
const event = new OB11GroupCardEvent(
parseInt(msg.peerUid),
parseInt(msg.senderUin),
msg.sendMemberName!,
member.cardName,
)
member.cardName = msg.sendMemberName!
return event
}
}
for (const element of msg.elements) {
const grayTipElement = element.grayTipElement
const groupElement = grayTipElement?.groupElement
if (groupElement) {
if (groupElement.type === TipGroupElementType.memberIncrease) {
ctx.logger.info('收到群成员增加消息', groupElement)
await ctx.sleep(1000)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.memberUid)
let memberUin = member?.uin
if (!memberUin) {
memberUin = (await ctx.ntUserApi.getUserDetailInfo(groupElement.memberUid)).uin
}
const adminMember = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.adminUid)
if (memberUin) {
const operatorUin = adminMember?.uin || memberUin
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin))
}
}
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 = ''
let duration = Number(groupElement.shutUp?.duration)
const subType = duration > 0 ? 'ban' : 'lift_ban'
if (memberUid) {
memberUin =
(await ctx.ntGroupApi.getGroupMember(msg.peerUid, memberUid))?.uin ||
(await ctx.ntUserApi.getUserDetailInfo(memberUid))?.uin
}
else {
memberUin = '0' // 0表示全员禁言
if (duration > 0) {
duration = -1
}
}
const adminUin =
(await ctx.ntGroupApi.getGroupMember(msg.peerUid, adminUid!))?.uin || (await ctx.ntUserApi.getUserDetailInfo(adminUid!))?.uin
if (memberUin && adminUin) {
return new OB11GroupBanEvent(
parseInt(msg.peerUid),
parseInt(memberUin),
parseInt(adminUin),
duration,
subType,
)
}
}
else if (groupElement.type === TipGroupElementType.kicked) {
ctx.logger.info(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
ctx.ntGroupApi.quitGroup(msg.peerUid)
try {
const adminUin = (await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await ctx.ntUserApi.getUinByUid(groupElement.adminUid))
if (adminUin) {
return new OB11GroupDecreaseEvent(
parseInt(msg.peerUid),
parseInt(selfInfo.uin),
parseInt(adminUin),
'kick_me'
)
}
} catch (e) {
return new OB11GroupDecreaseEvent(
parseInt(msg.peerUid),
parseInt(selfInfo.uin),
0,
'leave'
)
}
}
}
else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin!), {
id: element.fileElement.fileUuid!,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,
})
}
if (grayTipElement) {
const xmlElement = grayTipElement.xmlElement
if (xmlElement?.templId === '10382') {
// 表情回应消息
// "content":
// "<gtip align=\"center\">
// <qq uin=\"u_snYxnEfja-Po_\" col=\"3\" jp=\"3794\"/>
// <nor txt=\"回应了你的\"/>
// <url jp= \"\" msgseq=\"74711\" col=\"3\" txt=\"消息:\"/>
// <face type=\"1\" id=\"76\"> </face>
// </gtip>",
const emojiLikeData = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
}).parse(xmlElement.content)
ctx.logger.info('收到表情回应我的消息', emojiLikeData)
try {
const senderUin: string = emojiLikeData.gtip.qq.jp
const msgSeq: string = emojiLikeData.gtip.url.msgseq
const emojiId: string = emojiLikeData.gtip.face.id
const peer = {
chatType: ChatType.group,
guildId: '',
peerUid: msg.peerUid,
}
const replyMsgList = (await ctx.ntMsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList
if (!replyMsgList?.length) {
return
}
const shortId = MessageUnique.getShortIdByMsgId(replyMsgList[0].msgId)
return new OB11GroupMsgEmojiLikeEvent(
parseInt(msg.peerUid),
parseInt(senderUin),
shortId!,
[{
emoji_id: emojiId,
count: 1,
}]
)
} catch (e) {
ctx.logger.error('解析表情回应消息失败', (e as Error).stack)
}
}
if (
grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER &&
xmlElement?.templId == '10179'
) {
ctx.logger.info('收到新人被邀请进群消息', grayTipElement)
if (xmlElement?.content) {
const regex = /jp="(\d+)"/g
const matches: string[] = []
let match: RegExpExecArray | null = null
while ((match = regex.exec(xmlElement.content)) !== null) {
matches.push(match[1])
}
// log("新人进群匹配到的QQ号", matches)
if (matches.length === 2) {
const [inviter, invitee] = matches
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite')
}
}
}
else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
/*
{
align: 'center',
items: [
{ txt: '恭喜', type: 'nor' },
{
col: '3',
jp: '5',
param: ["QQ号"],
txt: '林雨辰',
type: 'url'
},
{ txt: '获得群主授予的', type: 'nor' },
{
col: '3',
jp: '',
txt: '好好好',
type: 'url'
},
{ txt: '头衔', type: 'nor' }
]
}
* */
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
const pokedetail: Dict[] = json.items
//筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid)
if (poke_uid.length == 2) {
return new OB11GroupPokeEvent(
parseInt(msg.peerUid),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid) ?? 0),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid) ?? 0),
pokedetail
)
}
}
if (grayTipElement.jsonGrayTipElement.busiId == 2401) {
ctx.logger.info('收到群精华消息', json)
const searchParams = new URL(json.items[0].jp).searchParams
const msgSeq = searchParams.get('msgSeq')!
const Group = searchParams.get('groupCode')
const Peer: Peer = {
guildId: '',
chatType: ChatType.group,
peerUid: Group!
}
const msgList = (await ctx.ntMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true))?.msgList
if (!msgList?.length) {
return
}
//const origMsg = await dbUtil.getMsgByLongId(msgList[0].msgId)
//const postMsg = await dbUtil.getMsgBySeqId(origMsg?.msgSeq!) ?? origMsg
// 如果 senderUin 为 0可能是 历史消息 或 自身消息
//if (msgList[0].senderUin === '0') {
//msgList[0].senderUin = postMsg?.senderUin ?? getSelfUin()
//}
return new OB11GroupEssenceEvent(
parseInt(msg.peerUid),
MessageUnique.getShortIdByMsgId(msgList[0].msgId)!,
parseInt(msgList[0].senderUin!)
)
// 获取MsgSeq+Peer可获取具体消息
}
if (grayTipElement.jsonGrayTipElement.busiId == 2407) {
const memberUin = json.items[1].param[0]
const title = json.items[3].txt
ctx.logger.info('收到群成员新头衔消息', json)
ctx.ntGroupApi.getGroupMember(msg.peerUid, memberUin).then(member => {
if (!isNullable(member)) {
member.memberSpecialTitle = title
}
})
return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title)
}
}
}
}
}
export async function recallEvent(
ctx: Context,
msg: RawMessage,
shortId: number
): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> {
const msgElement = msg.elements.find(
(element) => element.grayTipElement?.subElementType === GrayTipElementSubType.RECALL,
)
if (!msgElement) {
return
}
const revokeElement = msgElement.grayTipElement.revokeElement
if (msg.chatType === ChatType.group) {
const operator = await ctx.ntGroupApi.getGroupMember(msg.peerUid, revokeElement.operatorUid)
return new OB11GroupRecallNoticeEvent(
parseInt(msg.peerUid),
parseInt(msg.senderUin!),
parseInt(operator?.uin || msg.senderUin!),
shortId,
)
}
else {
return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin!), shortId)
}
}
export function friend(friend: User): OB11User {
return {
user_id: parseInt(friend.uin),
nickname: friend.nick,
remark: friend.remark,
sex: sex(friend.sex!),
level: (friend.qqLevel && calcQQLevel(friend.qqLevel)) || 0,
}
}
export function friends(friends: User[]): OB11User[] {
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
})
}
return data
}
export function groupMemberRole(role: number): OB11GroupMemberRole | undefined {
return {
4: OB11GroupMemberRole.owner,
3: OB11GroupMemberRole.admin,
2: OB11GroupMemberRole.member,
}[role]
}
export function sex(sex: Sex): OB11UserSex {
const sexMap = {
[Sex.male]: OB11UserSex.male,
[Sex.female]: OB11UserSex.female,
[Sex.unknown]: OB11UserSex.unknown,
}
return sexMap[sex] || OB11UserSex.unknown
}
export function groupMember(group_id: string, member: GroupMember): OB11GroupMember {
return {
group_id: parseInt(group_id),
user_id: parseInt(member.uin),
nickname: member.nick,
card: member.cardName,
sex: sex(member.sex!),
age: 0,
area: '',
level: '0',
qq_level: (member.qqLevel && calcQQLevel(member.qqLevel)) || 0,
join_time: 0, // 暂时没法获取
last_sent_time: 0, // 暂时没法获取
title_expire_time: 0,
unfriendly: false,
card_changeable: true,
is_robot: member.isRobot,
shut_up_timestamp: member.shutUpTime,
role: groupMemberRole(member.role),
title: member.memberSpecialTitle || '',
}
}
export function stranger(user: User): OB11User {
return {
...user,
user_id: parseInt(user.uin),
nickname: user.nick,
sex: sex(user.sex!),
age: 0,
qid: user.qid,
login_days: 0,
level: (user.qqLevel && calcQQLevel(user.qqLevel)) || 0,
}
}
export function group(group: Group): OB11Group {
return {
group_id: parseInt(group.groupCode),
group_name: group.groupName,
group_memo: group.remarkName,
group_create_time: +group.createTime,
member_count: group.memberCount,
max_member_count: group.maxMember,
}
}
export function groups(groups: Group[]): OB11Group[] {
return groups.map(group)
}
}