feat(business): change profile/cover photo and manage quick replies (#1724)
This commit is contained in:
@@ -79,7 +79,8 @@ export const MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
|
||||
'thumbnail-link': '/mms/image',
|
||||
'product-catalog-image': '/product/image',
|
||||
'md-app-state': '',
|
||||
'md-msg-hist': '/mms/md-app-state'
|
||||
'md-msg-hist': '/mms/md-app-state',
|
||||
'biz-cover-photo': '/pps/biz-cover-photo'
|
||||
}
|
||||
|
||||
export const MEDIA_HKDF_KEY_MAPPING = {
|
||||
@@ -100,7 +101,8 @@ export const MEDIA_HKDF_KEY_MAPPING = {
|
||||
'md-app-state': 'App State',
|
||||
'product-catalog-image': '',
|
||||
'payment-bg-image': 'Payment Background',
|
||||
ptv: 'Video'
|
||||
ptv: 'Video',
|
||||
'biz-cover-photo': 'Image'
|
||||
}
|
||||
|
||||
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
|
||||
|
||||
+141
-2
@@ -1,4 +1,6 @@
|
||||
import type { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig } from '../Types'
|
||||
import type { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig, WAMediaUpload } from '../Types'
|
||||
import type { UpdateBussinesProfileProps } from '../Types/Bussines'
|
||||
import { getRawMediaUploadData } from '../Utils'
|
||||
import {
|
||||
parseCatalogNode,
|
||||
parseCollectionsNode,
|
||||
@@ -15,6 +17,140 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
||||
const sock = makeMessagesRecvSocket(config)
|
||||
const { authState, query, waUploadToServer } = sock
|
||||
|
||||
const updateBussinesProfile = async (args: UpdateBussinesProfileProps) => {
|
||||
const node: BinaryNode[] = []
|
||||
const simpleFields: (keyof UpdateBussinesProfileProps)[] = ['address', 'email', 'description']
|
||||
|
||||
node.push(
|
||||
...simpleFields
|
||||
.filter(key => args[key])
|
||||
.map(key => ({
|
||||
tag: key,
|
||||
attrs: {},
|
||||
content: args[key] as string
|
||||
}))
|
||||
)
|
||||
|
||||
if (args.websites) {
|
||||
node.push(
|
||||
...args.websites.map(website => ({
|
||||
tag: 'website',
|
||||
attrs: {},
|
||||
content: website
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
if (args.hours) {
|
||||
node.push({
|
||||
tag: 'business_hours',
|
||||
attrs: { timezone: args.hours.timezone },
|
||||
content: args.hours.days.map(config => {
|
||||
const base = {
|
||||
tag: 'business_hours_config',
|
||||
attrs: { day_of_week: config.day, mode: config.mode }
|
||||
}
|
||||
|
||||
if (config.mode === 'specific_hours') {
|
||||
return {
|
||||
...base,
|
||||
attrs: {
|
||||
...base.attrs,
|
||||
open_time: config.openTimeInMinutes,
|
||||
close_time: config.closeTimeInMinutes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'set',
|
||||
xmlns: 'w:biz'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'business_profile',
|
||||
attrs: {
|
||||
v: '3',
|
||||
mutation_type: 'delta'
|
||||
},
|
||||
content: node
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const updateCoverPhoto = async (photo: WAMediaUpload) => {
|
||||
const { fileSha256, filePath } = await getRawMediaUploadData(photo, 'biz-cover-photo')
|
||||
const fileSha256B64 = fileSha256.toString('base64')
|
||||
|
||||
const { meta_hmac, fbid, ts } = await waUploadToServer(filePath, {
|
||||
fileEncSha256B64: fileSha256B64,
|
||||
mediaType: 'biz-cover-photo'
|
||||
})
|
||||
|
||||
await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'set',
|
||||
xmlns: 'w:biz'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'business_profile',
|
||||
attrs: {
|
||||
v: '3',
|
||||
mutation_type: 'delta'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'cover_photo',
|
||||
attrs: { id: String(fbid), op: 'update', token: meta_hmac!, ts: String(ts) }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return fbid!
|
||||
}
|
||||
|
||||
const removeCoverPhoto = async (id: string) => {
|
||||
return await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'set',
|
||||
xmlns: 'w:biz'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'business_profile',
|
||||
attrs: {
|
||||
v: '3',
|
||||
mutation_type: 'delta'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'cover_photo',
|
||||
attrs: { op: 'delete', id }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const getCatalog = async ({ jid, limit, cursor }: GetCatalogOptions) => {
|
||||
jid = jid || authState.creds.me?.id
|
||||
jid = jidNormalizedUser(jid)
|
||||
@@ -277,6 +413,9 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
||||
getCollections,
|
||||
productCreate,
|
||||
productDelete,
|
||||
productUpdate
|
||||
productUpdate,
|
||||
updateBussinesProfile,
|
||||
updateCoverPhoto,
|
||||
removeCoverPhoto
|
||||
}
|
||||
}
|
||||
|
||||
+28
-1
@@ -26,6 +26,7 @@ import type {
|
||||
WAReadReceiptsValue
|
||||
} from '../Types'
|
||||
import { ALL_WA_PATCH_NAMES } from '../Types'
|
||||
import type { QuickReplyAction } from '../Types/Bussines.js'
|
||||
import type { LabelActionBody } from '../Types/Label'
|
||||
import { SyncState } from '../Types/State'
|
||||
import {
|
||||
@@ -997,6 +998,30 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or Edit Quick Reply
|
||||
*/
|
||||
const addOrEditQuickReply = (quickReply: QuickReplyAction) => {
|
||||
return chatModify(
|
||||
{
|
||||
quickReply
|
||||
},
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Quick Reply
|
||||
*/
|
||||
const removeQuickReply = (timestamp: string) => {
|
||||
return chatModify(
|
||||
{
|
||||
quickReply: { timestamp, deleted: true }
|
||||
},
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* queries need to be fired on connection open
|
||||
* help ensure parity with WA Web
|
||||
@@ -1200,6 +1225,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
removeChatLabel,
|
||||
addMessageLabel,
|
||||
removeMessageLabel,
|
||||
star
|
||||
star,
|
||||
addOrEditQuickReply,
|
||||
removeQuickReply
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { proto } from '../../WAProto'
|
||||
|
||||
export type DayOfWeekBussines = 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
|
||||
|
||||
export type HoursDay =
|
||||
| { day: DayOfWeekBussines; mode: 'specific_hours'; openTimeInMinutes: string; closeTimeInMinutes: string }
|
||||
| { day: DayOfWeekBussines; mode: 'open_24h' | 'appointment_only' }
|
||||
|
||||
export type UpdateBussinesProfileProps = {
|
||||
address?: string
|
||||
websites?: string[]
|
||||
email?: string
|
||||
description?: string
|
||||
hours?: {
|
||||
timezone: string
|
||||
days: HoursDay[]
|
||||
}
|
||||
}
|
||||
|
||||
export type QuickReplyAction = proto.SyncActionValue.IQuickReplyAction & { timestamp?: string }
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { proto } from '../../WAProto/index.js'
|
||||
import type { AccountSettings } from './Auth'
|
||||
import type { QuickReplyAction } from './Bussines.js'
|
||||
import type { BufferedEventData } from './Events'
|
||||
import type { LabelActionBody } from './Label'
|
||||
import type { ChatLabelAssociationActionBody } from './LabelAssociation'
|
||||
@@ -119,6 +120,7 @@ export type ChatModification =
|
||||
| { removeChatLabel: ChatLabelAssociationActionBody }
|
||||
| { addMessageLabel: MessageLabelAssociationActionBody }
|
||||
| { removeMessageLabel: MessageLabelAssociationActionBody }
|
||||
| { quickReply: QuickReplyAction }
|
||||
|
||||
export type InitialReceivedChatsState = {
|
||||
[jid: string]: {
|
||||
|
||||
@@ -321,7 +321,7 @@ export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions &
|
||||
export type WAMediaUploadFunction = (
|
||||
encFilePath: string,
|
||||
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number }
|
||||
) => Promise<{ mediaUrl: string; directPath: string }>
|
||||
) => Promise<{ mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number }>
|
||||
|
||||
export type MediaGenerationOptions = {
|
||||
logger?: ILogger
|
||||
|
||||
@@ -666,6 +666,22 @@ export const chatModificationToAppPatch = (mod: ChatModification, jid: string) =
|
||||
apiVersion: 1,
|
||||
operation: OP.SET
|
||||
}
|
||||
} else if ('quickReply' in mod) {
|
||||
patch = {
|
||||
syncAction: {
|
||||
quickReplyAction: {
|
||||
count: 0,
|
||||
deleted: mod.quickReply.deleted || false,
|
||||
keywords: [],
|
||||
message: mod.quickReply.message || '',
|
||||
shortcut: mod.quickReply.shortcut || ''
|
||||
}
|
||||
},
|
||||
index: ['quick_reply', mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000))],
|
||||
type: 'regular',
|
||||
apiVersion: 2,
|
||||
operation: OP.SET
|
||||
}
|
||||
} else if ('addLabel' in mod) {
|
||||
patch = {
|
||||
syncAction: {
|
||||
|
||||
@@ -632,7 +632,7 @@ export const getWAUploadToServer = (
|
||||
// send a query JSON to obtain the url & auth token to upload our media
|
||||
let uploadInfo = await refreshMediaConn(false)
|
||||
|
||||
let urls: { mediaUrl: string; directPath: string } | undefined
|
||||
let urls: { mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number } | undefined
|
||||
const hosts = [...customUploadHosts, ...uploadInfo.hosts]
|
||||
|
||||
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
|
||||
@@ -664,7 +664,10 @@ export const getWAUploadToServer = (
|
||||
if (result?.url || result?.directPath) {
|
||||
urls = {
|
||||
mediaUrl: result.url,
|
||||
directPath: result.direct_path
|
||||
directPath: result.direct_path,
|
||||
meta_hmac: result.meta_hmac,
|
||||
fbid: result.fbid,
|
||||
ts: result.ts
|
||||
}
|
||||
break
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user