salman0ansari / whatsapp-api-nodejs

RESTful WhatsApp API with Multiple Device Support
GNU General Public License v3.0
1.27k stars 606 forks source link

Added makeInMemoryStore #82

Closed Zarinia closed 2 years ago

Zarinia commented 2 years ago

Hi again and thanks @dasmond1 @devalexanderdaza @salman0ansari I added this function Please check if I have bugs or errors in it change directory to sessions/Data and add sessions/Store

/* eslint-disable no-unsafe-optional-chaining */
const QRCode = require('qrcode');
const pino = require('pino');
const {
    default: makeWASocket,
    useSingleFileAuthState,
    DisconnectReason, makeInMemoryStore
} = require('@adiwajshing/baileys');
const {unlinkSync} = require('fs');
const {v4: uuidv4} = require('uuid');
const path = require('path');
const processButton = require('../helper/processbtn');
const generateVC = require('../helper/genVc');
// const Chat = require("../models/chat.model")
const axios = require('axios');
const config = require('../../config/config');
const downloadMessage = require('../helper/downloadMsg');

class WhatsAppInstance {
    store;
    socketConfig = {
        printQRInTerminal: false,
        browser: ['Whatsapp MD', '', '3.0'],
        logger: pino({
            level: 'silent'
        })
    };
    key = '';
    authState;
    allowWebhook = false;
    instance = {
        key: this.key,
        chats: [],
        qr: '',
        messages: []
    };

    axiosInstance = axios.create({
        baseURL: config.webhookUrl
    });

    constructor(key, allowWebhook = false, webhook = null) {
        this.key = key ? key : uuidv4();
        this.allowWebhook = allowWebhook;
        if (this.allowWebhook && webhook !== null) {
            this.axiosInstance = axios.create({
                baseURL: webhook
            });
        }
        this.authState = useSingleFileAuthState(
            path.join(__dirname, `../sessions/Data/${this.key}.json`)
        );
    }

    async SendWebhook(data) {
        if (!this.allowWebhook) {
            return;
        }
        this.axiosInstance.post('', data).catch((error) => {
            return;
        });
    }

    async init() {
        this.store = makeInMemoryStore({});
        this.store.readFromFile(path.join(__dirname, `../sessions/Store/${this.key}.json`));
        setInterval(() => {
            this.store.writeToFile(path.join(__dirname, `../sessions/Store/${this.key}.json`));
        }, 10_000);
        this.socketConfig.auth = this.authState.state;
        this.instance.sock = makeWASocket(this.socketConfig);
        this.setHandler();
        return this;
    }

    setHandler() {
        const sock = this.instance.sock;
        this.store.bind(sock?.ev);
        // on credentials update save state
        sock?.ev.on('creds.update', this.authState.saveState);

        // on socket closed, opened, connecting
        sock?.ev.on('connection.update', async (update) => {
            const {connection, lastDisconnect, qr} = update;

            if (connection === 'connecting') {
                return;
            }

            if (connection === 'close') {
                // reconnect if not logged out
                if (
                    lastDisconnect?.error?.output?.statusCode !==
                    DisconnectReason.loggedOut
                ) {
                    await this.init();
                } else {
                    unlinkSync(
                        path.join(__dirname, `../sessions/Data/${this.key}.json`)
                    );
                    this.instance.online = false;
                }
            } else if (connection === 'open') {
                this.instance.online = true;
            }

            if (qr) {
                QRCode.toDataURL(qr).then((url) => {
                    this.instance.qr = url;
                    this.SendWebhook({
                        type: 'update',
                        message: 'Received QR Code',
                        key: this.key,
                        qrcode: url
                    });
                });
            }
        });

        // on receive all chats
        sock?.ev.on('chats.set', async ({chats}) => {
            console.log('got chats', this.store.chats.all());
            const recivedChats = chats.map((chat) => {
                return {
                    ...chat,
                    messages: []
                };
            });
            this.instance.chats.push(...recivedChats);
            //    const db = await Chat({key: this.key, chat: this.instance.chats})
            //    await db.save()
            //    console.log(db)
        });

        sock?.ev.on('contacts.set', () => {
            console.log('got contacts', Object.values(this.store.contacts));
        });

        // on recive new chat
        sock?.ev.on('chats.upsert', (newChat) => {
            // console.log("Received new chat")
            const chats = newChat.map((chat) => {
                return {
                    ...chat,
                    messages: []
                };
            });
            this.instance.chats.push(...chats);
        });

        // on chat change
        sock?.ev.on('chats.update', (changedChat) => {
            changedChat.map((chat) => {
                const index = this.instance.chats.findIndex(
                    (pc) => pc.id === chat.id
                );
                const PrevChat = this.instance.chats[index];
                this.instance.chats[index] = {
                    ...PrevChat,
                    ...chat
                };
            });
        });

        // on chat delete
        sock?.ev.on('chats.delete', (deletedChats) => {
            deletedChats.map((chat) => {
                const index = this.instance.chats.findIndex(
                    (c) => c.id === chat
                );
                this.instance.chats.splice(index, 1);
            });
        });

        // on new mssage
        sock?.ev.on('messages.upsert', (m) => {
            if (m.type === 'prepend') {
                this.instance.messages.unshift(...m.messages);
            }

            if (m.type !== 'notify') {
                return;
            }

            this.instance.messages.unshift(...m.messages);

            m.messages.map(async (msg) => {
                if (!msg.message) {
                    return;
                }
                if (msg.key.fromMe) {
                    return;
                }

                const messageType = Object.keys(msg.message)[0];
                if (
                    [
                        'protocolMessage',
                        'senderKeyDistributionMessage'
                    ].includes(messageType)
                ) {
                    return;
                }

                const webhookData = {
                    key: this.key,
                    ...msg
                };

                if (messageType === 'conversation') {
                    webhookData['text'] = m;
                }
                if (config.webhookBase64) {
                    switch (messageType) {
                        case 'imageMessage':
                            webhookData['msgContent'] = await downloadMessage(
                                msg.message.imageMessage,
                                'image'
                            );
                            break;
                        case 'videoMessage':
                            webhookData['msgContent'] = await downloadMessage(
                                msg.message.videoMessage,
                                'video'
                            );
                            break;
                        case 'audioMessage':
                            webhookData['msgContent'] = await downloadMessage(
                                msg.message.audioMessage,
                                'audio'
                            );
                            break;
                        default:
                            webhookData['msgContent'] = '';
                            break;
                    }
                }

                this.SendWebhook(webhookData);
            });
        });
    }

    async getInstanceDetail(key) {
        return {
            instance_key: key,
            phone_connected: this.instance?.online,
            user: this.instance?.online ? this.instance.sock?.user : {}
        };
    }

    getWhatsAppId(id) {
        if (id.includes('@g.us') || id.includes('@s.whatsapp.net')) {
            return id;
        }
        return id.includes('-') ? `${id}@g.us` : `${id}@s.whatsapp.net`;
    }

    async verifyId(id) {
        if (id.includes('@g.us')) {
            return true;
        }
        const [result] = await this.instance.sock?.onWhatsApp(id);
        if (result?.exists) {
            return true;
        }
        throw new Error('no account exists');
    }

    async sendTextMessage(to, message) {
        await this.verifyId(this.getWhatsAppId(to));
        const data = await this.instance.sock?.sendMessage(
            this.getWhatsAppId(to),
            {text: message}
        );
        return data;
    }

    async sendMediaFile(to, file, type, caption = '', filename) {
        await this.verifyId(this.getWhatsAppId(to));
        const data = await this.instance.sock?.sendMessage(
            this.getWhatsAppId(to),
            {
                mimetype: file.mimetype,
                [type]: file.buffer,
                caption: caption,
                ptt: type === 'audio' ? true : false,
                fileName: filename ? filename : file.originalname
            }
        );
        return data;
    }

    async sendUrlMediaFile(to, url, type, mimeType, caption = '') {
        await this.verifyId(this.getWhatsAppId(to));

        const data = await this.instance.sock?.sendMessage(
            this.getWhatsAppId(to),
            {
                [type]: {
                    url: url
                },
                caption: caption,
                mimetype: mimeType
            }
        );
        return data;
    }

    async DownloadProfile(of) {
        await this.verifyId(this.getWhatsAppId(of));
        const ppUrl = await this.instance.sock?.profilePictureUrl(
            this.getWhatsAppId(of),
            'image'
        );
        return ppUrl;
    }

    async getUserStatus(of) {
        await this.verifyId(this.getWhatsAppId(of));
        const status = await this.instance.sock?.fetchStatus(
            this.getWhatsAppId(of)
        );
        return status;
    }

    async blockUnblock(to, data) {
        await this.verifyId(this.getWhatsAppId(to));
        const status = await this.instance.sock?.updateBlockStatus(
            this.getWhatsAppId(to),
            data
        );
        return status;
    }

    async sendButtonMessage(to, data) {
        await this.verifyId(this.getWhatsAppId(to));
        const result = await this.instance.sock?.sendMessage(
            this.getWhatsAppId(to),
            {
                templateButtons: processButton(data.buttons),
                text: data.text ?? '',
                footer: data.footerText ?? ''
            }
        );
        return result;
    }

    async sendContactMessage(to, data) {
        await this.verifyId(this.getWhatsAppId(to));
        const vcard = generateVC(data);
        const result = await this.instance.sock?.sendMessage(
            await this.getWhatsAppId(to),
            {
                contacts: {
                    displayName: data.fullName,
                    contacts: [{displayName: data.fullName, vcard}]
                }
            }
        );
        return result;
    }

    async sendListMessage(to, data) {
        await this.verifyId(this.getWhatsAppId(to));
        const result = await this.instance.sock?.sendMessage(
            this.getWhatsAppId(to),
            {
                text: data.text,
                sections: data.sections,
                buttonText: data.buttonText,
                footer: data.description,
                title: data.title
            }
        );
        return result;
    }

    async sendMediaButtonMessage(to, data) {
        await this.verifyId(this.getWhatsAppId(to));

        const result = await this.instance.sock?.sendMessage(
            this.getWhatsAppId(to),
            {
                [data.mediaType]: {
                    url: data.image
                },
                footer: data.footerText ?? '',
                caption: data.text,
                templateButtons: processButton(data.buttons),
                mimetype: data.mimeType
            }
        );
        return result;
    }

    // Group Methods
    parseParticipants(users) {
        return users.map((users) => this.getWhatsAppId(users));
    }

    async createNewGroup(name, users) {
        const group = await this.instance.sock?.groupCreate(
            name,
            users.map(this.getWhatsAppId)
        );
        return group;
    }

    async addNewParticipant(id, users) {
        try {
            const res = await this.instance.sock?.groupAdd(
                this.getWhatsAppId(id),
                this.parseParticipants(users)
            );
            return res;
        } catch {
            return {
                error: true,
                message:
                    'Unable to add participant, you must be an admin in this group'
            };
        }
    }

    async makeAdmin(id, users) {
        try {
            const res = await this.instance.sock?.groupMakeAdmin(
                this.getWhatsAppId(id),
                this.parseParticipants(users)
            );
            return res;
        } catch {
            return {
                error: true,
                message:
                    'unable to promote some participants, check if you are admin in group or participants exists'
            };
        }
    }

    async demoteAdmin(id, users) {
        try {
            const res = await this.instance.sock?.groupDemoteAdmin(
                this.getWhatsAppId(id),
                this.parseParticipants(users)
            );
            return res;
        } catch {
            return {
                error: true,
                message:
                    'unable to demote some participants, check if you are admin in group or participants exists'
            };
        }
    }

    async getAllGroups() {
        // let AllChat = await Chat.findOne({key: key}).exec();
        return this.instance.chats.filter((c) => c.id.includes('@g.us'));
    }

    async leaveGroup(id) {
        const group = this.instance.chats.find((c) => c.id === id);
        if (!group) {
            throw new Error('no group exists');
        }
        return await this.instance.sock?.groupLeave(id);
    }

    async getInviteCodeGroup(id) {
        const group = this.instance.chats.find((c) => c.id === id);
        if (!group) {
            throw new Error(
                'unable to get invite code, check if the group exists'
            );
        }
        return await this.instance.sock?.groupInviteCode(id);
    }
}

exports.WhatsAppInstance = WhatsAppInstance;
salman0ansari commented 2 years ago

sorry but that's not how you contribute

salman0ansari commented 2 years ago

and i don't want to use MemoryStore because it store every data in RAM which is not efficient.

Zarinia commented 2 years ago

True, I know that it is stored in RAM, and RAM consumption increases, but it is a good way to temporarily store information for a small number of clients. Better this way is the database that needs to be implemented

Zarinia commented 2 years ago

I wrote this section to use the message loader "loadMessages" store.loadMessages() I really need this part, thank you for telling me my problems so that it can be fixed

salman0ansari commented 2 years ago

not relevant to this project