feat: implement fetching product catalog + order details on MD

This commit is contained in:
Adhiraj Singh
2022-03-06 13:30:11 +05:30
parent 8a52eeb310
commit c4edcef5da
5 changed files with 280 additions and 1 deletions

150
src/Socket/business.ts Normal file
View File

@@ -0,0 +1,150 @@
import { SocketConfig } from '../Types'
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode } from '../Utils/business'
import { jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
import { makeMessagesRecvSocket } from './messages-recv'
export const makeBusinessSocket = (config: SocketConfig) => {
const { logger } = config
const sock = makeMessagesRecvSocket(config)
const {
authState,
query
} = sock
const getCatalog = async(jid?: string, limit = 10) => {
jid = jidNormalizedUser(jid || authState.creds.me?.id)
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: [
{
tag: 'limit',
attrs: { },
content: Buffer.from([ 49, 48 ])
},
{
tag: 'width',
attrs: { },
content: Buffer.from([ 49, 48, 48 ])
},
{
tag: 'height',
attrs: { },
content: Buffer.from([ 49, 48, 48 ])
}
]
}
]
})
return parseCatalogNode(result)
}
const getCollections = async(jid?: string, limit = 51) => {
jid = jidNormalizedUser(jid || authState.creds.me?.id)
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([ 49, 48 ])
},
{
tag: 'item_limit',
attrs: { },
content: Buffer.from([ limit ])
},
{
tag: 'width',
attrs: { },
content: Buffer.from([ 49, 48, 48 ])
},
{
tag: 'height',
attrs: { },
content: Buffer.from([ 49, 48, 48 ])
}
]
}
]
})
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([ 49, 48, 48 ])
},
{
tag: 'height',
attrs: { },
content: Buffer.from([ 49, 48, 48 ])
}
]
},
{
tag: 'token',
attrs: { },
content: Buffer.from(tokenBase64, 'base64')
}
]
}
]
})
return parseOrderDetailsNode(result)
}
return {
...sock,
getCatalog,
getCollections,
getOrderDetails,
}
}

View File

@@ -1,6 +1,6 @@
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults'
import { SocketConfig } from '../Types'
import { makeMessagesRecvSocket as _makeSocket } from './messages-recv'
import { makeBusinessSocket as _makeSocket } from './business'
// export the last socket layer
const makeWASocket = (config: Partial<SocketConfig>) => (

View File

@@ -10,6 +10,19 @@ export type ProductCreateResult = {
data: { product: any }
}
export type CatalogStatus = {
status: string
canAppeal: boolean
}
export type CatalogCollection = {
id: string
name: string
products: Product[]
status: CatalogStatus
}
export type ProductAvailability = 'in stock'
export type ProductBase = {

107
src/Utils/business.ts Normal file
View File

@@ -0,0 +1,107 @@
import { CatalogCollection, CatalogStatus, OrderDetails, OrderProduct, Product } from '../Types'
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'
export const parseCatalogNode = (node: BinaryNode) => {
const catalogNode = getBinaryNodeChild(node, 'product_catalog')
const products = getBinaryNodeChildren(catalogNode, 'product').map(parseProductNode)
return { products }
}
export const parseCollectionsNode = (node: BinaryNode) => {
const collectionsNode = getBinaryNodeChild(node, 'collections')
const collections = getBinaryNodeChildren(collectionsNode, 'collection').map<CatalogCollection>(
collectionNode => {
const id = parseCatalogId(collectionNode)
const name = getBinaryNodeChildString(collectionNode, 'name')
const products = getBinaryNodeChildren(collectionNode, 'product').map(parseProductNode)
return {
id,
name,
products,
status: parseStatusInfo(collectionNode)
}
}
)
return {
collections
}
}
export const parseOrderDetailsNode = (node: BinaryNode) => {
const orderNode = getBinaryNodeChild(node, 'order')
const products = getBinaryNodeChildren(orderNode, 'product').map<OrderProduct>(
productNode => {
const imageNode = getBinaryNodeChild(productNode, 'image')
return {
id: parseCatalogId(productNode),
name: getBinaryNodeChildString(productNode, 'name'),
imageUrl: getBinaryNodeChildString(imageNode, 'url'),
price: +getBinaryNodeChildString(productNode, 'price'),
currency: getBinaryNodeChildString(productNode, 'currency'),
quantity: +getBinaryNodeChildString(productNode, 'quantity')
}
}
)
const priceNode = getBinaryNodeChild(orderNode, 'price')
const orderDetails: OrderDetails = {
price: {
total: +getBinaryNodeChildString(priceNode, 'total'),
currency: getBinaryNodeChildString(priceNode, 'currency'),
},
products
}
return orderDetails
}
const parseProductNode = (productNode: BinaryNode) => {
const isHidden = productNode.attrs.is_hidden === 'true'
const id = parseCatalogId(productNode)
const mediaNode = getBinaryNodeChild(productNode, 'media')
const statusInfoNode = getBinaryNodeChild(productNode, 'status_info')
const product: Product = {
id,
imageUrls: parseImageUrls(mediaNode),
reviewStatus: {
whatsapp: getBinaryNodeChildString(statusInfoNode, 'status'),
},
availability: 'in stock',
name: getBinaryNodeChildString(productNode, 'name'),
retailerId: getBinaryNodeChildString(productNode, 'retailer_id'),
url: getBinaryNodeChildString(productNode, 'url'),
description: getBinaryNodeChildString(productNode, 'description'),
price: +getBinaryNodeChildString(productNode, 'price'),
currency: getBinaryNodeChildString(productNode, 'currency'),
isHidden,
}
return product
}
const parseImageUrls = (mediaNode: BinaryNode) => {
const imgNode = getBinaryNodeChild(mediaNode, 'image')
return {
requested: getBinaryNodeChildString(imgNode, 'request_image_url'),
original: getBinaryNodeChildString(imgNode, 'original_image_url')
}
}
const parseStatusInfo = (mediaNode: BinaryNode): CatalogStatus => {
const node = getBinaryNodeChild(mediaNode, 'status_info')
return {
status: getBinaryNodeChildString(node, 'status'),
canAppeal: getBinaryNodeChildString(node, 'can_appeal') === 'true',
}
}
const parseCatalogId = (node: BinaryNode) => {
const idNode = getBinaryNodeChildBuffer(node, 'id')
const id = Buffer.from(idNode).readBigUInt64LE().toString()
return id
}

View File

@@ -33,6 +33,15 @@ export const getBinaryNodeChildBuffer = (node: BinaryNode, childTag: string) =>
}
}
export const getBinaryNodeChildString = (node: BinaryNode, childTag: string) => {
const child = getBinaryNodeChild(node, childTag)?.content
if(Buffer.isBuffer(child) || child instanceof Uint8Array) {
return Buffer.from(child).toString('utf-8')
} else if(typeof child === 'string') {
return child
}
}
export const getBinaryNodeChildUInt = (node: BinaryNode, childTag: string, length: number) => {
const buff = getBinaryNodeChildBuffer(node, childTag)
if(buff) {