feat: generate high quality thumbs on link preview

This commit is contained in:
Adhiraj Singh
2022-09-15 18:40:22 +05:30
parent 864a01f9a5
commit f0bdb12e56
8 changed files with 95 additions and 21 deletions

View File

@@ -118,6 +118,11 @@ type SocketConfig = {
msgRetryCounterMap?: MessageRetryMap
/** width for link preview images */
linkPreviewImageThumbnailWidth: number
/**
* generate a high quality link preview,
* entails uploading the jpegThumbnail to WA
* */
generateHighQualityLinkPreview: boolean
/** Should Baileys ask the phone for full history, will be received async */
syncFullHistory: boolean
/**

View File

@@ -50,6 +50,7 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
syncFullHistory: false,
linkPreviewImageThumbnailWidth: 192,
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
generateHighQualityLinkPreview: false,
getMessage: async() => undefined
}

View File

@@ -10,7 +10,11 @@ import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getBinaryNodeChild,
import { makeGroupsSocket } from './groups'
export const makeMessagesSocket = (config: SocketConfig) => {
const { logger, linkPreviewImageThumbnailWidth } = config
const {
logger,
linkPreviewImageThumbnailWidth,
generateHighQualityLinkPreview
} = config
const sock = makeGroupsSocket(config)
const {
ev,
@@ -607,7 +611,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
userJid,
getUrlInfo: text => getUrlInfo(
text,
{ thumbnailWidth: linkPreviewImageThumbnailWidth, timeoutMs: 3_000 },
{
thumbnailWidth: linkPreviewImageThumbnailWidth,
timeoutMs: 3_000,
uploadImage: generateHighQualityLinkPreview
? waUploadToServer
: undefined
},
logger
),
upload: waUploadToServer,

View File

@@ -42,6 +42,7 @@ export interface WAUrlInfo {
title: string
description?: string
jpegThumbnail?: Buffer
highQualityThumbnail?: proto.Message.IImageMessage
}
// types to generate WA messages
@@ -192,6 +193,7 @@ export type WAMediaUploadFunction = (readStream: Readable, opts: { fileEncSha256
export type MediaGenerationOptions = {
logger?: Logger
mediaTypeOverride?: MediaType
upload: WAMediaUploadFunction
/** cache media so it does not have to be uploaded again */
mediaCache?: NodeCache

View File

@@ -38,6 +38,11 @@ export type SocketConfig = CommonSocketConfig & {
syncFullHistory: boolean
/** Should baileys fire init queries automatically, default true */
fireInitQueries: boolean
/**
* generate a high quality link preview,
* entails uploading the jpegThumbnail to WA
* */
generateHighQualityLinkPreview: boolean
/**
* fetch a message from your store
* implement this so that messages failed to send (solves the "this message can take a while" issue) can be retried

View File

@@ -1,5 +1,6 @@
import { Logger } from 'pino'
import { WAUrlInfo } from '../Types'
import { WAMediaUploadFunction, WAUrlInfo } from '../Types'
import { prepareWAMessageMedia } from './messages'
import { extractImageThumb, getHttpStream } from './messages-media'
const THUMBNAIL_WIDTH_PX = 192
@@ -14,6 +15,7 @@ const getCompressedJpegThumbnail = async(url: string, { thumbnailWidth, timeoutM
export type URLGenerationOptions = {
thumbnailWidth: number
timeoutMs: number
uploadImage?: WAMediaUploadFunction
}
/**
@@ -38,25 +40,37 @@ export const getUrlInfo = async(
if(info && 'title' in info) {
const [image] = info.images
let jpegThumbnail: Buffer | undefined = undefined
try {
jpegThumbnail = image
? (await getCompressedJpegThumbnail(image, opts)).buffer
: undefined
} catch(error) {
logger?.debug(
{ err: error.stack, url: previewLink },
'error in generating thumbnail'
)
}
return {
const urlInfo: WAUrlInfo = {
'canonical-url': info.url,
'matched-text': text,
title: info.title,
description: info.description,
jpegThumbnail
}
if(opts.uploadImage) {
const { imageMessage } = await prepareWAMessageMedia(
{ image: { url: image } },
{ upload: opts.uploadImage, mediaTypeOverride: 'thumbnail-link' }
)
urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail
? Buffer.from(imageMessage.jpegThumbnail)
: undefined
urlInfo.highQualityThumbnail = imageMessage || undefined
console.log(urlInfo)
} else {
try {
urlInfo.jpegThumbnail = image
? (await getCompressedJpegThumbnail(image, opts)).buffer
: undefined
} catch(error) {
logger?.debug(
{ err: error.stack, url: previewLink },
'error in generating thumbnail'
)
}
}
return urlInfo
}
} catch(error) {
if(!error.message.includes('receive a valid')) {

View File

@@ -238,9 +238,16 @@ export async function generateThumbnail(
}
) {
let thumbnail: string | undefined
let originalImageDimensions: { width: number; height: number } | undefined
if(mediaType === 'image') {
const { buffer } = await extractImageThumb(file)
const { buffer, original } = await extractImageThumb(file)
thumbnail = buffer.toString('base64')
if(original.width && original.height) {
originalImageDimensions = {
width: original.width,
height: original.height,
}
}
} else if(mediaType === 'video') {
const imgFilename = join(getTmpFilesDirectory(), generateMessageID() + '.jpg')
try {
@@ -254,7 +261,10 @@ export async function generateThumbnail(
}
}
return thumbnail
return {
thumbnail,
originalImageDimensions
}
}
export const getHttpStream = async(url: string | URL, options: AxiosRequestConfig & { isStream?: true } = {}) => {

View File

@@ -36,6 +36,8 @@ type MediaUploadData = {
fileName?: string
jpegThumbnail?: string
mimetype?: string
width?: number
height?: number
}
const MIMETYPE_MAP: { [T in MediaType]?: string } = {
@@ -144,7 +146,11 @@ export const prepareWAMessageMedia = async(
fileSha256,
fileLength,
didSaveToTmpPath
} = await encryptedStream(uploadData.media, mediaType, requiresOriginalForSomeProcessing)
} = await encryptedStream(
uploadData.media,
options.mediaTypeOverride || mediaType,
requiresOriginalForSomeProcessing
)
// url safe Base64 encode the SHA256 hash of the body
const fileEncSha256B64 = fileEncSha256.toString('base64')
const [{ mediaUrl, directPath }] = await Promise.all([
@@ -159,7 +165,17 @@ export const prepareWAMessageMedia = async(
(async() => {
try {
if(requiresThumbnailComputation) {
uploadData.jpegThumbnail = await generateThumbnail(bodyPath!, mediaType as any, options)
const {
thumbnail,
originalImageDimensions
} = await generateThumbnail(bodyPath!, mediaType as any, options)
uploadData.jpegThumbnail = thumbnail
if(!uploadData.width && originalImageDimensions) {
uploadData.width = originalImageDimensions.width
uploadData.height = originalImageDimensions.height
logger?.debug('set dimensions')
}
logger?.debug('generated thumbnail')
}
@@ -280,6 +296,17 @@ export const generateWAMessageContent = async(
extContent.description = urlInfo.description
extContent.title = urlInfo.title
extContent.previewType = 0
const img = urlInfo.highQualityThumbnail
if(img) {
extContent.thumbnailDirectPath = img.directPath
extContent.mediaKey = img.mediaKey
extContent.mediaKeyTimestamp = img.mediaKeyTimestamp
extContent.thumbnailWidth = img.width
extContent.thumbnailHeight = img.height
extContent.thumbnailSha256 = img.fileSha256
extContent.thumbnailEncSha256 = img.fileEncSha256
}
}
m.extendedTextMessage = extContent