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 { LegacySocketConfig, OrderDetails } from '../Types'
|
||||||
import { CatalogResult, Product, ProductCreate, ProductCreateResult, ProductUpdate } from '../Types'
|
import { CatalogResult, Product, ProductCreate, ProductCreateResult, ProductUpdate } from '../Types'
|
||||||
|
import { uploadingNecessaryImages } from '../Utils/business'
|
||||||
import makeGroupsSocket from './groups'
|
import makeGroupsSocket from './groups'
|
||||||
|
|
||||||
const makeBusinessSocket = (config: LegacySocketConfig) => {
|
const makeBusinessSocket = (config: LegacySocketConfig) => {
|
||||||
@@ -7,6 +8,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
|
|||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
generateMessageTag,
|
generateMessageTag,
|
||||||
|
waUploadToServer,
|
||||||
state
|
state
|
||||||
} = sock
|
} = sock
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
|
|||||||
json: [
|
json: [
|
||||||
'action',
|
'action',
|
||||||
'addProduct_reh',
|
'addProduct_reh',
|
||||||
mapProductCreate(product)
|
await mapProductCreate(product)
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,6 +74,10 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productUpdate = async(productId: string, update: ProductUpdate) => {
|
const productUpdate = async(productId: string, update: ProductUpdate) => {
|
||||||
|
const productCreate = await mapProductCreate(
|
||||||
|
{ ...update, originCountryCode: undefined },
|
||||||
|
false
|
||||||
|
)
|
||||||
const result: ProductCreateResult = await query({
|
const result: ProductCreateResult = await query({
|
||||||
expect200: true,
|
expect200: true,
|
||||||
json: [
|
json: [
|
||||||
@@ -79,10 +85,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
|
|||||||
'editProduct_reh',
|
'editProduct_reh',
|
||||||
{
|
{
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
...mapProductCreate(
|
...productCreate
|
||||||
{ ...update, originCountryCode: undefined },
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -128,13 +131,17 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps product create to send to WA
|
// 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 = {
|
const result: any = {
|
||||||
name: product.name,
|
name: product.name,
|
||||||
description: product.description,
|
description: product.description,
|
||||||
image_url: product.imageUrls[0],
|
image_url: imgs[0],
|
||||||
url: product.url || '',
|
url: product.url || '',
|
||||||
additional_image_urls: product.imageUrls.slice(1),
|
additional_image_urls: imgs.slice(1),
|
||||||
retailer_id: product.retailerId || '',
|
retailer_id: product.retailerId || '',
|
||||||
width: '100',
|
width: '100',
|
||||||
height: '100',
|
height: '100',
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
|||||||
return {
|
return {
|
||||||
...sock,
|
...sock,
|
||||||
relayMessage,
|
relayMessage,
|
||||||
|
waUploadToServer,
|
||||||
generateUrlInfo,
|
generateUrlInfo,
|
||||||
messageInfo: async(jid: string, messageID: string) => {
|
messageInfo: async(jid: string, messageID: string) => {
|
||||||
const { content }: BinaryNode = await query({
|
const { content }: BinaryNode = await query({
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { ProductCreate, ProductUpdate, SocketConfig } from '../Types'
|
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 { jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
||||||
import { getBinaryNodeChild } from '../WABinary/generic-utils'
|
import { getBinaryNodeChild } from '../WABinary/generic-utils'
|
||||||
import { makeMessagesRecvSocket } from './messages-recv'
|
import { makeMessagesRecvSocket } from './messages-recv'
|
||||||
|
|
||||||
export const makeBusinessSocket = (config: SocketConfig) => {
|
export const makeBusinessSocket = (config: SocketConfig) => {
|
||||||
const { logger } = config
|
|
||||||
const sock = makeMessagesRecvSocket(config)
|
const sock = makeMessagesRecvSocket(config)
|
||||||
const {
|
const {
|
||||||
authState,
|
authState,
|
||||||
query
|
query,
|
||||||
|
waUploadToServer
|
||||||
} = sock
|
} = sock
|
||||||
|
|
||||||
const getCatalog = async(jid?: string, limit = 10) => {
|
const getCatalog = async(jid?: string, limit = 10) => {
|
||||||
@@ -143,6 +143,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productUpdate = async(productId: string, update: ProductUpdate) => {
|
const productUpdate = async(productId: string, update: ProductUpdate) => {
|
||||||
|
update = await uploadingNecessaryImagesOfProduct(update, waUploadToServer)
|
||||||
const editNode = toProductNode(productId, update)
|
const editNode = toProductNode(productId, update)
|
||||||
|
|
||||||
const result = await query({
|
const result = await query({
|
||||||
@@ -168,6 +169,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productCreate = async(create: ProductCreate) => {
|
const productCreate = async(create: ProductCreate) => {
|
||||||
|
create = await uploadingNecessaryImagesOfProduct(create, waUploadToServer)
|
||||||
const createNode = toProductNode(undefined, create)
|
const createNode = toProductNode(undefined, create)
|
||||||
|
|
||||||
const result = await query({
|
const result = await query({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { WAMediaUpload } from './Message'
|
||||||
|
|
||||||
export type CatalogResult = {
|
export type CatalogResult = {
|
||||||
data: {
|
data: {
|
||||||
@@ -38,8 +39,8 @@ export type ProductBase = {
|
|||||||
export type ProductCreate = ProductBase & {
|
export type ProductCreate = ProductBase & {
|
||||||
/** ISO country code for product origin. Set to undefined for no country */
|
/** ISO country code for product origin. Set to undefined for no country */
|
||||||
originCountryCode: string | undefined
|
originCountryCode: string | undefined
|
||||||
|
/** images of the product */
|
||||||
imageUrls: string[]
|
images: WAMediaUpload[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProductUpdate = Omit<ProductCreate, 'originCountryCode'>
|
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 { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'
|
||||||
|
import { getStream, toReadable } from './messages-media'
|
||||||
|
|
||||||
export const parseCatalogNode = (node: BinaryNode) => {
|
export const parseCatalogNode = (node: BinaryNode) => {
|
||||||
const catalogNode = getBinaryNodeChild(node, 'product_catalog')
|
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({
|
content.push({
|
||||||
tag: 'media',
|
tag: 'media',
|
||||||
attrs: { },
|
attrs: { },
|
||||||
content: product.imageUrls.map(
|
content: product.images.map(
|
||||||
url => ({
|
img => {
|
||||||
tag: 'image',
|
if(!('url' in img)) {
|
||||||
attrs: { },
|
throw new Boom('Expected img for product to already be uploaded', { statusCode: 400 })
|
||||||
content: [
|
}
|
||||||
{
|
|
||||||
tag: 'url',
|
return {
|
||||||
attrs: { },
|
tag: 'image',
|
||||||
content: Buffer.from(url)
|
attrs: { },
|
||||||
}
|
content: [
|
||||||
]
|
{
|
||||||
})
|
tag: 'url',
|
||||||
|
attrs: { },
|
||||||
|
content: Buffer.from(img.url.toString())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -187,6 +196,53 @@ export const parseProductNode = (productNode: BinaryNode) => {
|
|||||||
return product
|
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 parseImageUrls = (mediaNode: BinaryNode) => {
|
||||||
const imgNode = getBinaryNodeChild(mediaNode, 'image')
|
const imgNode = getBinaryNodeChild(mediaNode, 'image')
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user