feat: automatic upload detection for product images

This commit is contained in:
Adhiraj Singh
2022-03-07 15:33:52 +05:30
parent 6967e53164
commit 238cde23b7
5 changed files with 94 additions and 27 deletions

View File

@@ -1,5 +1,6 @@
import { LegacySocketConfig, OrderDetails } from '../Types'
import { CatalogResult, Product, ProductCreate, ProductCreateResult, ProductUpdate } from '../Types'
import { uploadingNecessaryImages } from '../Utils/business'
import makeGroupsSocket from './groups'
const makeBusinessSocket = (config: LegacySocketConfig) => {
@@ -7,6 +8,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
const {
query,
generateMessageTag,
waUploadToServer,
state
} = sock
@@ -46,7 +48,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
json: [
'action',
'addProduct_reh',
mapProductCreate(product)
await mapProductCreate(product)
]
})
@@ -72,6 +74,10 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
}
const productUpdate = async(productId: string, update: ProductUpdate) => {
const productCreate = await mapProductCreate(
{ ...update, originCountryCode: undefined },
false
)
const result: ProductCreateResult = await query({
expect200: true,
json: [
@@ -79,10 +85,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
'editProduct_reh',
{
product_id: productId,
...mapProductCreate(
{ ...update, originCountryCode: undefined },
false
)
...productCreate
}
]
})
@@ -128,13 +131,17 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
}
// maps product create to send to WA
const mapProductCreate = (product: ProductCreate, mapCompliance = true) => {
const mapProductCreate = async(product: ProductCreate, mapCompliance = true) => {
const imgs = (
await uploadingNecessaryImages(product.images, waUploadToServer)
).map(img => img.url)
const result: any = {
name: product.name,
description: product.description,
image_url: product.imageUrls[0],
image_url: imgs[0],
url: product.url || '',
additional_image_urls: product.imageUrls.slice(1),
additional_image_urls: imgs.slice(1),
retailer_id: product.retailerId || '',
width: '100',
height: '100',

View File

@@ -407,6 +407,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
return {
...sock,
relayMessage,
waUploadToServer,
generateUrlInfo,
messageInfo: async(jid: string, messageID: string) => {
const { content }: BinaryNode = await query({

View File

@@ -1,15 +1,15 @@
import { ProductCreate, ProductUpdate, SocketConfig } from '../Types'
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode } from '../Utils/business'
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business'
import { jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
import { getBinaryNodeChild } from '../WABinary/generic-utils'
import { makeMessagesRecvSocket } from './messages-recv'
export const makeBusinessSocket = (config: SocketConfig) => {
const { logger } = config
const sock = makeMessagesRecvSocket(config)
const {
authState,
query
query,
waUploadToServer
} = sock
const getCatalog = async(jid?: string, limit = 10) => {
@@ -143,6 +143,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
}
const productUpdate = async(productId: string, update: ProductUpdate) => {
update = await uploadingNecessaryImagesOfProduct(update, waUploadToServer)
const editNode = toProductNode(productId, update)
const result = await query({
@@ -168,6 +169,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
}
const productCreate = async(create: ProductCreate) => {
create = await uploadingNecessaryImagesOfProduct(create, waUploadToServer)
const createNode = toProductNode(undefined, create)
const result = await query({

View File

@@ -1,3 +1,4 @@
import { WAMediaUpload } from './Message'
export type CatalogResult = {
data: {
@@ -38,8 +39,8 @@ export type ProductBase = {
export type ProductCreate = ProductBase & {
/** ISO country code for product origin. Set to undefined for no country */
originCountryCode: string | undefined
imageUrls: string[]
/** images of the product */
images: WAMediaUpload[]
}
export type ProductUpdate = Omit<ProductCreate, 'originCountryCode'>

View File

@@ -1,5 +1,8 @@
import { CatalogCollection, CatalogStatus, OrderDetails, OrderProduct, Product, ProductCreate, ProductUpdate } from '../Types'
import { Boom } from '@hapi/boom'
import { createHash } from 'crypto'
import { CatalogCollection, CatalogStatus, OrderDetails, OrderProduct, Product, ProductCreate, ProductUpdate, WAMediaUpload, WAMediaUploadFunction } from '../Types'
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'
import { getStream, toReadable } from './messages-media'
export const parseCatalogNode = (node: BinaryNode) => {
const catalogNode = getBinaryNodeChild(node, 'product_catalog')
@@ -94,22 +97,28 @@ export const toProductNode = (productId: string | undefined, product: ProductCre
})
}
if(product.imageUrls.length) {
if(product.images.length) {
content.push({
tag: 'media',
attrs: { },
content: product.imageUrls.map(
url => ({
tag: 'image',
attrs: { },
content: [
{
tag: 'url',
attrs: { },
content: Buffer.from(url)
}
]
})
content: product.images.map(
img => {
if(!('url' in img)) {
throw new Boom('Expected img for product to already be uploaded', { statusCode: 400 })
}
return {
tag: 'image',
attrs: { },
content: [
{
tag: 'url',
attrs: { },
content: Buffer.from(img.url.toString())
}
]
}
}
)
})
}
@@ -187,6 +196,53 @@ export const parseProductNode = (productNode: BinaryNode) => {
return product
}
/**
* Uploads images not already uploaded to WA's servers
*/
export async function uploadingNecessaryImagesOfProduct<T extends ProductUpdate | ProductCreate>(product: T, waUploadToServer: WAMediaUploadFunction, timeoutMs = 30_000) {
product = {
...product,
images: product.images ? await uploadingNecessaryImages(product.images, waUploadToServer, timeoutMs) : product.images
}
return product
}
/**
* Uploads images not already uploaded to WA's servers
*/
export const uploadingNecessaryImages = async(images: WAMediaUpload[], waUploadToServer: WAMediaUploadFunction, timeoutMs = 30_000) => {
const results = await Promise.all(
images.map<Promise<{ url: string }>>(
async img => {
if('url' in img) {
const url = img.url.toString()
if(url.includes('.whatsapp.net')) {
return { url }
}
}
const { stream } = await getStream(img)
const hasher = createHash('sha256')
const contentBlocks: Buffer[] = []
for await (const block of stream) {
hasher.update(block)
contentBlocks.push(block)
}
const sha = hasher.digest('base64')
const { mediaUrl } = await waUploadToServer(
toReadable(Buffer.concat(contentBlocks)),
{ mediaType: 'image', fileEncSha256B64: sha, timeoutMs }
)
return { url: mediaUrl }
}
)
)
return results
}
const parseImageUrls = (mediaNode: BinaryNode) => {
const imgNode = getBinaryNodeChild(mediaNode, 'image')
return {