@juzi/wechaty-puppet-whatsapp
Version:
Wechaty Puppet for WhatsApp
345 lines (310 loc) • 12.4 kB
text/typescript
/* eslint-disable no-redeclare */
import * as PUPPET from '@juzi/wechaty-puppet'
import { FileBox, type FileBoxInterface, log } from '../config.js'
import { WA_ERROR_TYPE } from '../exception/error-type.js'
import WAError from '../exception/whatsapp-error.js'
import { contactRawPayload } from './contact.js'
import type PuppetWhatsApp from '../puppet-whatsapp.js'
import type {
WhatsAppContactPayload as RoomPayload,
InviteV4Data,
} from '../schema/whatsapp-type.js'
import { isRoomId } from '../helper/miscellaneous.js'
import { parserRoomRawPayload } from '../helper/pure-function/room-raw-payload-parser.js'
import { getMessageMediaFromFilebox } from '../helper/pure-function/messageMedia.js'
const PRE = 'MIXIN_ROOM'
export async function roomList (this: PuppetWhatsApp): Promise<string[]> {
log.verbose(PRE, 'roomList()')
const cacheManager = await this.manager.getCacheManager()
const roomIdList = await cacheManager.getRoomIdList()
return roomIdList
}
/**
* Filter friend list and non-friend list from member id list
* @param { PuppetWhatsApp } this
* @param { string[] } memberIdList
* @returns { friendsList: string[]; nonFriendsList: string[]; }
*/
async function checkRoomMember (this: PuppetWhatsApp, memberIdList: string[]) {
const friendsList = []
const nonFriendsList = []
for (const memberId of memberIdList) {
const memberPayload = await this.manager.getContactById(memberId)
if (memberPayload.isMyContact) {
friendsList.push(memberId)
} else {
nonFriendsList.push(memberId)
}
}
const botId = this.manager.whatsAppManager.getBotId()
if (!friendsList.includes(botId)) {
friendsList.push(botId)
}
return {
friendsList,
nonFriendsList,
}
}
async function updateRoomRawPayloadToCache (
this: PuppetWhatsApp,
roomId: string,
params: {
name?: string,
avatar?: string,
memberIdList?: string[],
},
): Promise<RoomPayload | undefined> {
const { name, avatar, memberIdList } = params
const cacheManager = await this.manager.getCacheManager()
const roomInCache = await cacheManager.getContactOrRoomRawPayload(roomId)
if (roomInCache) {
if (name) {
roomInCache.name = name
}
if (avatar) {
roomInCache.avatar = avatar
}
if (memberIdList && memberIdList.length > 0) {
await cacheManager.setRoomMemberIdList(roomId, memberIdList)
}
await cacheManager.setContactOrRoomRawPayload(roomId, roomInCache)
}
return roomInCache
}
export async function roomCreate (
this: PuppetWhatsApp,
contactIdList: string[],
topic: string,
): Promise<string> {
log.verbose(PRE, 'roomCreate(%s, %s)', contactIdList, topic)
const { friendsList, nonFriendsList } = await checkRoomMember.call(this, contactIdList)
const group = await this.manager.createRoom(topic, friendsList)
const roomId = group.gid._serialized
if (roomId) {
if (nonFriendsList.length > 0) {
await addMemberListToRoom.call(this, roomId, nonFriendsList)
}
await updateRoomRawPayloadToCache.call(this, roomId, {
memberIdList: contactIdList,
name: topic,
})
return roomId
} else {
throw WAError(WA_ERROR_TYPE.ERR_CREATE_ROOM, `An error occurred while creating the group, detail: ${contactIdList}, topic: ${topic}`)
}
}
export async function roomAdd (
this: PuppetWhatsApp,
roomId: string,
contactId: string,
): Promise<void> {
log.verbose(PRE, 'roomAdd(%s, %s)', roomId, contactId)
await addMemberListToRoom.call(this, roomId, contactId)
}
async function addMemberListToRoom (
this: PuppetWhatsApp,
roomId: string,
contactIds: string | string[],
) {
const roomChat = await this.manager.getRoomChatById(roomId)
const contactIdList = Array.isArray(contactIds) ? contactIds : [contactIds]
const result = await roomChat.addParticipants(contactIdList)
const cacheManager = await this.manager.getCacheManager()
const successContactIdList = []
if (typeof result === 'string') {
throw WAError(WA_ERROR_TYPE.ERR_ADD_ROOM, `cannot add contact: ${contactIdList.join(', ')} to room ${roomId}, ${result}`)
}
for (const key in result) {
const item = result[key]
for (const contactId in item) {
const contactResult = item[contactId]
if (Number(contactResult?.code) === 200) {
log.silly(PRE, `add member ${contactId} to room ${roomId} succeded`)
successContactIdList.push(contactId)
} else {
switch (Number(contactResult?.code)) {
case 409:
log.warn(PRE, `add member ${contactId} to room ${roomId} failed, contact is already in room`)
break
default:
log.warn(PRE, `add member ${contactId} to room ${roomId} failed for unknown reason`)
}
}
}
}
await cacheManager.addRoomMemberToList(roomId, successContactIdList)
}
export async function roomDel (
this: PuppetWhatsApp,
roomId: string,
contactIds: string | string[],
): Promise<void> {
log.verbose(PRE, 'roomDel(%s, %s)', roomId, contactIds)
const roomChat = await this.manager.getRoomChatById(roomId)
const contactIdList = Array.isArray(contactIds) ? contactIds : [contactIds]
await roomChat.removeParticipants(contactIdList)
const cacheManager = await this.manager.getCacheManager()
await cacheManager.removeRoomMemberFromList(roomId, contactIdList)
}
export async function roomQuit (this: PuppetWhatsApp, roomId: string): Promise<void> {
log.verbose(PRE, 'roomQuit(%s)', roomId)
const roomChat = await this.manager.getRoomChatById(roomId)
await roomChat.leave()
const cacheManager = await this.manager.getCacheManager()
await cacheManager.deleteContactOrRoom(roomId)
await cacheManager.deleteRoomMemberIdList(roomId)
}
export async function roomAvatar (this: PuppetWhatsApp, roomId: string, avatar?: FileBoxInterface): Promise<FileBox | void> {
log.verbose(PRE, 'roomAvatar(%s)', roomId)
if (avatar) {
const media = await getMessageMediaFromFilebox(avatar)
const roomChat = await this.manager.getRoomChatById(roomId)
const result = await roomChat.setPicture(media)
if (!result) {
throw WAError(WA_ERROR_TYPE.ERR_ROOM_AVATAR_SET_FAILED, `can not set room avatar, room id: ${roomId}`)
}
} else {
const payload = await this.roomPayload(roomId)
if (payload.avatar) {
return FileBox.fromUrl(payload.avatar)
}
throw WAError(WA_ERROR_TYPE.ERR_ROOM_AVATAR_NOT_FOUND, `can not find this room avatar, room id: ${roomId}`)
}
}
export async function roomTopic(this: PuppetWhatsApp, roomId: string): Promise<string>
export async function roomTopic(this: PuppetWhatsApp, roomId: string, topic: string): Promise<void>
export async function roomTopic (
this: PuppetWhatsApp,
roomId: string,
topic?: string,
): Promise<void | string> {
log.verbose(PRE, 'roomTopic(%s, %s)', roomId, topic)
if (typeof topic === 'undefined') {
const payload = await this.roomPayload(roomId)
return payload.topic
}
const roomChat = await this.manager.getRoomChatById(roomId)
if (roomChat.isGroup) {
await roomChat.setSubject(topic)
}
await this.dirtyPayload(PUPPET.types.Payload.Room, roomId)
}
export async function roomQRCode (this: PuppetWhatsApp, roomId: string): Promise<string> {
log.verbose(PRE, 'roomQRCode(%s)', roomId)
const roomChat = await this.manager.getRoomChatById(roomId)
const code = await roomChat.getInviteCode()
const url = `https://chat.whatsapp.com/${code}`
return url
}
/**
* Get member id list from cache
* @param { PuppetWhatsApp } this whatsapp client
* @param { string } roomId roomId
* @returns { string[] } member id list
*/
export async function roomMemberList (this: PuppetWhatsApp, roomId: string): Promise<string[]> {
log.verbose(PRE, 'roomMemberList(%s)', roomId)
const cacheManager = await this.manager.getCacheManager()
const memberList = await cacheManager.getRoomMemberIdList(roomId)
if (memberList.length === 0) {
return this.manager.syncRoomMemberList(roomId)
}
return memberList
}
export async function roomMemberRawPayload (this: PuppetWhatsApp, roomId: string, contactId: string): Promise<PUPPET.payloads.RoomMember> {
log.verbose(PRE, 'roomMemberRawPayload(%s, %s)', roomId, contactId)
const member = await contactRawPayload.call(this, contactId)
return {
avatar: member.avatar,
id: member.id._serialized,
name: member.pushname || member.name || '',
// roomAlias : contact.name,
}
}
export async function roomMemberRawPayloadParser (this: PuppetWhatsApp, rawPayload: PUPPET.payloads.RoomMember): Promise<PUPPET.payloads.RoomMember> {
log.verbose(PRE, 'roomMemberRawPayloadParser(%s)', JSON.stringify(rawPayload))
return rawPayload
}
export async function roomAnnounce(this: PuppetWhatsApp, roomId: string): Promise<string>
export async function roomAnnounce(this: PuppetWhatsApp, roomId: string, text: string): Promise<void>
export async function roomAnnounce (this: PuppetWhatsApp, roomId: string, text?: string): Promise<void | string> {
if (typeof text === 'undefined') {
const roomChat = await this.manager.getRoomChatById(roomId)
return roomChat.description
}
const roomChat = await this.manager.getRoomChatById(roomId)
await roomChat.setDescription((text || undefined) as any)
await this.dirtyPayload(PUPPET.types.Payload.Room, roomId)
}
/**
*
* Room Invitation
*
*/
export async function roomInvitationAccept (this: PuppetWhatsApp, roomInvitationId: string): Promise<void> {
log.verbose(PRE, 'roomInvitationAccept(%s)', roomInvitationId)
const info = await roomInvitationRawPayload.call(this, roomInvitationId)
if (Object.keys(info).length === 1) {
await this.manager.acceptRoomInvite(info.inviteCode!)
} else {
await this.manager.acceptPrivateRoomInvite(info as InviteV4Data)
}
}
export async function roomInvitationRawPayload (this: PuppetWhatsApp, roomInvitationId: string): Promise<Partial<InviteV4Data>> {
log.verbose(PRE, 'roomInvitationRawPayload(%s)', roomInvitationId)
const cacheManager = await this.manager.getCacheManager()
const info = await cacheManager.getRoomInvitationRawPayload(roomInvitationId)
if (info) {
return info
} else {
return {
inviteCode: roomInvitationId,
}
}
}
/**
*
* @param this PuppetWhatsapp
* @param rawPayload Partial<InviteV4Data>
* @returns Partial<InviteV4Data>
* TODO: Here we return Partial<InviteV4Data> for roomInvitationAccept usage, We may need other fields required by RoomInvitationPayload
*/
export async function roomInvitationRawPayloadParser (this: PuppetWhatsApp, rawPayload: any): Promise<PUPPET.payloads.RoomInvitation> {
log.verbose(PRE, 'roomInvitationRawPayloadParser(%s)', JSON.stringify(rawPayload))
return rawPayload
}
export async function roomRawPayload (this: PuppetWhatsApp, id: string): Promise<RoomPayload> {
log.verbose(PRE, 'roomRawPayload(%s)', id)
if (!isRoomId(id)) {
throw WAError(WA_ERROR_TYPE.ERR_ROOM_NOT_FOUND, `please check room id: ${id} again.`)
}
const cacheManager = await this.manager.getCacheManager()
const room = await cacheManager.getContactOrRoomRawPayload(id)
const roomAnnounce = await this.roomAnnounce(id)
if (room) {
room.announce = roomAnnounce
return room
} else {
try {
const rawRoom = await this.manager.getContactById(id)
const avatar = await rawRoom.getProfilePicUrl() || ''
const room = Object.assign(rawRoom, { avatar, announce: roomAnnounce })
await cacheManager.setContactOrRoomRawPayload(id, room)
return room
} catch (error) {
throw WAError(WA_ERROR_TYPE.ERR_ROOM_NOT_FOUND, `roomRawPayload(${id}) not found.`)
}
}
}
export async function roomRawPayloadParser (this: PuppetWhatsApp, roomPayload: RoomPayload): Promise<PUPPET.payloads.Room> {
const roomId = roomPayload.id._serialized
try {
const roomChat = await this.manager.getRoomChatById(roomId)
const result = parserRoomRawPayload(roomPayload, roomChat)
log.verbose(PRE, 'roomRawPayloadParser roomPayload(%s) roomChat(%s) result(%s)', JSON.stringify(roomPayload), JSON.stringify(roomChat), JSON.stringify(result))
return result
} catch (error) {
log.error(PRE, `roomRawPayloadParser(${roomId}) failed, error message: ${(error as Error).message}`)
throw WAError(WA_ERROR_TYPE.ERR_ROOM_NOT_FOUND, `roomRawPayloadParser(${roomId}) failed, error message: ${(error as Error).message}`)
}
}