mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: generate high quality thumbs on link preview
This commit is contained in:
@@ -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
|
||||
/**
|
||||
|
||||
@@ -50,6 +50,7 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
||||
syncFullHistory: false,
|
||||
linkPreviewImageThumbnailWidth: 192,
|
||||
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
|
||||
generateHighQualityLinkPreview: false,
|
||||
getMessage: async() => undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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 } = {}) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user