import type { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig, WAMediaUpload } from '../Types' import type { UpdateBussinesProfileProps } from '../Types/Bussines' import { getRawMediaUploadData } from '../Utils' import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business' import { type BinaryNode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary' import { getBinaryNodeChild } from '../WABinary/generic-utils' import { makeMessagesRecvSocket } from './messages-recv' 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] !== undefined && args[key] !== null) .map(key => ({ tag: key, attrs: {}, content: args[key] as string })) ) if (args.websites !== undefined) { node.push( ...args.websites.map(website => ({ tag: 'website', attrs: {}, content: website })) ) } if (args.hours !== undefined) { node.push({ tag: 'business_hours', attrs: { timezone: args.hours.timezone }, content: args.hours.days.map(dayConfig => { const base = { tag: 'business_hours_config', attrs: { day_of_week: dayConfig.day, mode: dayConfig.mode } } as const if (dayConfig.mode === 'specific_hours') { return { ...base, attrs: { ...base.attrs, open_time: dayConfig.openTimeInMinutes, close_time: dayConfig.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) const queryParamNodes: BinaryNode[] = [ { tag: 'limit', attrs: {}, content: Buffer.from((limit || 10).toString()) }, { tag: 'width', attrs: {}, content: Buffer.from('100') }, { tag: 'height', attrs: {}, content: Buffer.from('100') } ] if (cursor) { queryParamNodes.push({ tag: 'after', attrs: {}, content: cursor }) } const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:biz:catalog' }, content: [ { tag: 'product_catalog', attrs: { jid, allow_shop_source: 'true' }, content: queryParamNodes } ] }) return parseCatalogNode(result) } const getCollections = async (jid?: string, limit = 51) => { jid = jid || authState.creds.me?.id jid = jidNormalizedUser(jid) const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:biz:catalog', smax_id: '35' }, content: [ { tag: 'collections', attrs: { biz_jid: jid }, content: [ { tag: 'collection_limit', attrs: {}, content: Buffer.from(limit.toString()) }, { tag: 'item_limit', attrs: {}, content: Buffer.from(limit.toString()) }, { tag: 'width', attrs: {}, content: Buffer.from('100') }, { tag: 'height', attrs: {}, content: Buffer.from('100') } ] } ] }) return parseCollectionsNode(result) } const getOrderDetails = async (orderId: string, tokenBase64: string) => { const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'fb:thrift_iq', smax_id: '5' }, content: [ { tag: 'order', attrs: { op: 'get', id: orderId }, content: [ { tag: 'image_dimensions', attrs: {}, content: [ { tag: 'width', attrs: {}, content: Buffer.from('100') }, { tag: 'height', attrs: {}, content: Buffer.from('100') } ] }, { tag: 'token', attrs: {}, content: Buffer.from(tokenBase64) } ] } ] }) return parseOrderDetailsNode(result) } const productUpdate = async (productId: string, update: ProductUpdate) => { update = await uploadingNecessaryImagesOfProduct(update, waUploadToServer) const editNode = toProductNode(productId, update) const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:biz:catalog' }, content: [ { tag: 'product_catalog_edit', attrs: { v: '1' }, content: [ editNode, { tag: 'width', attrs: {}, content: '100' }, { tag: 'height', attrs: {}, content: '100' } ] } ] }) const productCatalogEditNode = getBinaryNodeChild(result, 'product_catalog_edit') const productNode = getBinaryNodeChild(productCatalogEditNode, 'product') return parseProductNode(productNode!) } const productCreate = async (create: ProductCreate) => { // ensure isHidden is defined create.isHidden = !!create.isHidden create = await uploadingNecessaryImagesOfProduct(create, waUploadToServer) const createNode = toProductNode(undefined, create) const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:biz:catalog' }, content: [ { tag: 'product_catalog_add', attrs: { v: '1' }, content: [ createNode, { tag: 'width', attrs: {}, content: '100' }, { tag: 'height', attrs: {}, content: '100' } ] } ] }) const productCatalogAddNode = getBinaryNodeChild(result, 'product_catalog_add') const productNode = getBinaryNodeChild(productCatalogAddNode, 'product') return parseProductNode(productNode!) } const productDelete = async (productIds: string[]) => { const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:biz:catalog' }, content: [ { tag: 'product_catalog_delete', attrs: { v: '1' }, content: productIds.map(id => ({ tag: 'product', attrs: {}, content: [ { tag: 'id', attrs: {}, content: Buffer.from(id) } ] })) } ] }) const productCatalogDelNode = getBinaryNodeChild(result, 'product_catalog_delete') return { deleted: +(productCatalogDelNode?.attrs.deleted_count || 0) } } return { ...sock, logger: config.logger, getOrderDetails, getCatalog, getCollections, productCreate, productDelete, productUpdate, updateBussinesProfile, updateCoverPhoto, removeCoverPhoto } }