mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: automatic upload detection for product images
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -407,6 +407,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
return {
|
||||
...sock,
|
||||
relayMessage,
|
||||
waUploadToServer,
|
||||
generateUrlInfo,
|
||||
messageInfo: async(jid: string, messageID: string) => {
|
||||
const { content }: BinaryNode = await query({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user