Skip to content

Commit

Permalink
fix: Fixed image rendering issue in Chatwoot
Browse files Browse the repository at this point in the history
* refactor: wrap the buffer to stream convert using promise for async control

* refactor: add error reject in promise wrapper at buffer to stream method

* refactor: refact stream reader

* chore: add mocha for tests

* Revert "chore: add mocha for tests"

This reverts commit 90dee67.

* chore: add mocha for tests and add test script

* chore: add mocha types

* chore: config mocha types in tscondfig

* chore!: enable isolatedModules for test modules in isolation

* chore: transpile only test files with ts-node for no type checks

* chore: setup jest

* feat: implement buffer utils to work with buffers

* test: ensure that buffer utils will work successfully

* refactor: implement AsyncBufferToStream into chatwoot attachments field

* refactor: remove test logs

* chore!: remove no used dep buffer-to-stream

* refactor: Change the bufferUtils filename

* fix: unable isolated modules

* refactor: change the buffer utils to default exportation

* fix: change the file import path for buffer utils.

* fix: add event listen to monitore buffer to stream conversion

* refactor: change the stream aproach in buffer conversion

* refactor: add test to webhook

* chore: add pre-push command to husky

* fix: remove debug url

* refactor: refact the send message attachments to chatwoot

* refactor: remove no used variables

* refactor: remove debug stuff

* chore: add lint check in pre-push hook

---------

Co-authored-by: netoeymard <eymard@sysautomacao.com.br>
Co-authored-by: Cleiton Carvalho <cleitoncosta83@gmail.com>
  • Loading branch information
3 people committed Aug 16, 2023
1 parent f3ca926 commit ff6df6c
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 43 deletions.
4 changes: 4 additions & 0 deletions .husky/pre-push
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn test && yarn lint
5 changes: 5 additions & 0 deletions jest.config.js
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
9 changes: 6 additions & 3 deletions package.json
Expand Up @@ -24,7 +24,8 @@
"swagger:copy-assets": "shx cp -R ./node_modules/swagger-ui-dist ./swagger-docs",
"swagger:copy-json": "shx cp ./src/swagger.json ./swagger-docs",
"swagger:fix-url": "sed -i \"s|https://petstore.swagger.io/v2/swagger.json|./swagger.json|g\" ./swagger-docs/index.html",
"swagger:generate": "yarn swagger:copy-assets && yarn swagger:copy-json && yarn swagger:fix-url"
"swagger:generate": "yarn swagger:copy-assets && yarn swagger:copy-json && yarn swagger:fix-url",
"test": "jest"
},
"dependencies": {
"@wppconnect-team/wppconnect": "^1.28.0",
Expand Down Expand Up @@ -64,9 +65,9 @@
"@commitlint/cz-commitlint": "^17.7.1",
"@types/archiver": "^5.3.2",
"@types/bcrypt": "^5.0.0",
"@types/buffer-to-stream": "^1.0.0",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.3",
"@types/merge-deep": "^3.0.0",
"@types/mime-types": "^2.1.1",
"@types/multer": "^1.4.7",
Expand All @@ -87,18 +88,20 @@
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"husky": "^8.0.3",
"jest": "^29.6.2",
"license-check-and-add": "^4.0.5",
"mongoose": "^7.4.3",
"nodemon": "^3.0.1",
"prettier": "^2.8.8",
"pretty-quick": "^3.1.3",
"prom-client": "^14.2.0",
"redis": "^4.6.7",
"release-it": "^16.1.5",
"rimraf": "^5.0.1",
"shx": "^0.3.4",
"ts-jest": "^29.1.1",
"swagger-ui-dist": "^5.3.2",
"ts-loader": "^9.4.4",
"prom-client": "^14.2.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"webpack-cli": "^5.1.4"
Expand Down
53 changes: 53 additions & 0 deletions src/tests/util/bufferUtils.test.ts
@@ -0,0 +1,53 @@
import { Readable } from 'stream';

import bufferUtils from '../../util/bufferutils';

function generateRandomData(length: number): string {
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let randomData = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
randomData += characters.charAt(randomIndex);
}
return randomData;
}

describe('Utils Functions', function () {
describe('Buffer to Stream', function () {
const bodyToBuffer = generateRandomData(100);
const buffer = Buffer.from(bodyToBuffer, 'utf-8');

it('Should transform the Buffer in a Readable Stream', function () {
const bufferStream = bufferUtils.bufferToReadableStream(buffer);

// Assert that the bufferStream is a Readable stream
expect(bufferStream).toBeInstanceOf(Readable);
});

it('Should, on data end, checks if the Stream are correct', function () {
const bufferStream = bufferUtils.bufferToReadableStream(buffer);

let data = '';

bufferStream.on('data', (chunck) => {
data += chunck.toString('utf-8');
});

bufferStream.on('end', () => {
expect(data).toStrictEqual(bodyToBuffer);
});
});
});

describe('Async Buffer to Stream', function () {
const bodyToBuffer = generateRandomData(10000000);
const buffer = Buffer.from(bodyToBuffer, 'utf-8');

it('Should await the Buffer convertion and return a instance of readable', async function () {
const bufferStream = await bufferUtils.AsyncBufferToStream(buffer);

expect(bufferStream).toBeInstanceOf(Readable);
});
});
});
49 changes: 49 additions & 0 deletions src/util/bufferutils.ts
@@ -0,0 +1,49 @@
/*
* Copyright 2021 WPPConnect Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Readable } from 'stream';

// type AsyncBufferToStream

function bufferToReadableStream(buffer: Buffer): Readable {
const readableInstanceStream = new Readable({
read() {
this.push(buffer);
this.push(null);
},
});

return readableInstanceStream;
}

async function AsyncBufferToStream(buffer: Buffer): Promise<Readable> {
return new Promise((resolve, reject) => {
const bufferStream = bufferToReadableStream(buffer);
bufferStream.on('data', () => {
// data = chunck;
});

bufferStream.on('end', () => {
resolve(bufferStream);
});

bufferStream.on('error', (error) => {
reject(error);
});
});
}

export default { bufferToReadableStream, AsyncBufferToStream };
158 changes: 122 additions & 36 deletions src/util/chatWootClient.ts
Expand Up @@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import axios from 'axios';
import toStream from 'buffer-to-stream';
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { default as FormData } from 'form-data';
import mime from 'mime-types';

import bufferutils from './bufferutils';
// import bufferUtils from './bufferutils';
import { eventEmitter } from './sessionUtil';

export default class chatWootClient {
Expand All @@ -28,7 +29,7 @@ export default class chatWootClient {
declare sender: any;
declare account_id: any;
declare inbox_id: any;
declare api: any;
declare api: AxiosInstance;

constructor(config: any, session: string) {
this.config = config;
Expand Down Expand Up @@ -86,8 +87,94 @@ export default class chatWootClient {
});
}

// async sendMessage(client: any, message: any) {
// if (message.isGroupMsg || message.chatId.indexOf('@broadcast') > 0) return;
// const contact = await this.createContact(message);
// const conversation = await this.createConversation(
// contact,
// message.chatId.split('@')[0]
// );

// try {
// if (
// message.type == 'image' ||
// message.type == 'video' ||
// message.type == 'in' ||
// message.type == 'document' ||
// message.type == 'ptt' ||
// message.type == 'audio' ||
// message.type == 'sticker'
// ) {
// if (message.mimetype == 'image/webp') message.mimetype = 'image/jpeg';
// const extension = mime.extension(message.mimetype);
// const filename = `${message.timestamp}.${extension}`;
// let b64;

// if (message.qrCode) b64 = message.qrCode;
// else {
// const buffer = await client.decryptFile(message);
// b64 = await buffer.toString('base64');
// }

// const mediaData = Buffer.from(b64, 'base64');

// // Create a readable stream from the Buffer
// const stream = new Readable();
// stream.push(mediaData);
// stream.push(null); // Signaling the end of the stream

// const data = new FormData();
// if (message.caption) {
// data.append('content', message.caption);
// }

// data.append('attachments[]', stream, {
// filename: filename,
// contentType: message.mimetype,
// });

// data.append('message_type', 'incoming');
// data.append('private', 'false');

// const configPost = Object.assign(
// {},
// {
// baseURL: this.config.baseURL,
// headers: {
// 'Content-Type': 'application/json;charset=utf-8',
// api_access_token: this.config.token,
// },
// }
// );

// configPost.headers = { ...configPost.headers, ...data.getHeaders() };
// console.log('PRÉ-REQUEST');
// const result = await axios.post(
// `api/v1/accounts/${this.account_id}/conversations/${conversation.id}/messages`,
// data,
// configPost
// );
// console.log('POS-REQUEST');
// return result;
// } else {
// const body = {
// content: message.body,
// message_type: 'incoming',
// };
// const { data } = await this.api.post(
// `api/v1/accounts/${this.account_id}/conversations/${conversation.id}/messages`,
// body
// );
// return data;
// }
// } catch (e) {
// return null;
// }
// }

async sendMessage(client: any, message: any) {
if (message.isGroupMsg || message.chatId.indexOf('@broadcast') > 0) return;

const contact = await this.createContact(message);
const conversation = await this.createConversation(
contact,
Expand All @@ -96,69 +183,68 @@ export default class chatWootClient {

try {
if (
message.type == 'image' ||
message.type == 'video' ||
message.type == 'in' ||
message.type == 'document' ||
message.type == 'ptt' ||
message.type == 'audio' ||
message.type == 'sticker'
[
'image',
'video',
'in',
'document',
'ptt',
'audio',
'sticker',
].includes(message.type)
) {
if (message.mimetype == 'image/webp') message.mimetype = 'image/jpeg';
if (message.mimetype === 'image/webp') message.mimetype = 'image/jpeg';
const extension = mime.extension(message.mimetype);
const filename = `${message.timestamp}.${extension}`;
let b64;

if (message.qrCode) b64 = message.qrCode;
else {
if (message.qrCode) {
b64 = message.qrCode;
} else {
const buffer = await client.decryptFile(message);
b64 = await buffer.toString('base64');
b64 = buffer.toString('base64');
}

const mediaData = Buffer.from(b64, 'base64');
const stream = bufferutils.bufferToReadableStream(mediaData);

const data = new FormData();
if (message.caption) {
data.append('content', message.caption);
}
data.append('attachments[]', toStream(mediaData), {

data.append('attachments[]', stream, {
filename: filename,
contentType: message.mimetype,
});

data.append('message_type', 'incoming');
data.append('private', 'false');

const configPost = Object.assign(
{},
{
baseURL: this.config.baseURL,
headers: {
'Content-Type': 'application/json;charset=utf-8',
api_access_token: this.config.token,
},
}
);
configPost.headers = { ...configPost.headers, ...data.getHeaders() };

const result = await axios.post(
`api/v1/accounts/${this.account_id}/conversations/${conversation.id}/messages`,
data,
configPost
);
const configPost: AxiosRequestConfig = {
baseURL: this.config.baseURL,
headers: {
api_access_token: this.config.token,
...data.getHeaders(),
},
};
const endpoint = `api/v1/accounts/${this.account_id}/conversations/${conversation.id}/messages`;

const result = await axios.post(endpoint, data, configPost);

return result;
} else {
const body = {
content: message.body,
message_type: 'incoming',
};
const { data } = await this.api.post(
`api/v1/accounts/${this.account_id}/conversations/${conversation.id}/messages`,
body
);
const endpoint = `api/v1/accounts/${this.account_id}/conversations/${conversation.id}/messages`;

const { data } = await this.api.post(endpoint, body);
return data;
}
} catch (e) {
console.error('Error sending message:', e);
return null;
}
}
Expand Down

0 comments on commit ff6df6c

Please sign in to comment.