mirror of
https://github.com/cheveguerra/whatsapp-web.js.git
synced 2026-04-18 03:29:14 +00:00
Compare commits
18 Commits
v1.17.1
...
v1.18.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd4df4cf75 | ||
|
|
7fe3574730 | ||
|
|
09a81d0e1e | ||
|
|
c09a22c533 | ||
|
|
bb09bb74e7 | ||
|
|
694a52bf26 | ||
|
|
c459eca799 | ||
|
|
b74246d69a | ||
|
|
ab7ee0eb4f | ||
|
|
537e843a49 | ||
|
|
76f7a6e279 | ||
|
|
f2ec77f969 | ||
|
|
fd368361df | ||
|
|
bd553f75d3 | ||
|
|
c5c705a553 | ||
|
|
6e047cb9be | ||
|
|
56343497e9 | ||
|
|
6a4fca0a77 |
32
.github/workflows/release.yml
vendored
Normal file
32
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_type:
|
||||
description: "`alpha`, `alpha-minor`, `alpha-major` release?"
|
||||
required: true
|
||||
default: "alpha"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'pedroslopez/whatsapp-web.js' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
- run: git config --global user.email "hello@wwebjs.dev"
|
||||
- run: git config --global user.name "WWebJS Bot"
|
||||
- name: Bump version and publish to NPM
|
||||
id: publish
|
||||
run: ./tools/publish ${{ github.event.inputs.release_type }}
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.RELEASE_NPM_TOKEN }}
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
prerelease: ${{ steps.publish.outputs.PRERELEASE }}
|
||||
generateReleaseNotes: true
|
||||
tag: v${{ steps.publish.outputs.NEW_VERSION }}
|
||||
@@ -480,7 +480,7 @@ class Message extends Base {
|
||||
let msg = window.Store.Msg.get(msgId);
|
||||
|
||||
if (msg.canStar()) {
|
||||
return msg.chat.sendStarMsgs([msg], true);
|
||||
return window.Store.Cmd.sendStarMsgs(msg.chat, [msg], false);
|
||||
}
|
||||
}, this.id._serialized);
|
||||
}
|
||||
@@ -493,7 +493,7 @@ class Message extends Base {
|
||||
let msg = window.Store.Msg.get(msgId);
|
||||
|
||||
if (msg.canStar()) {
|
||||
return msg.chat.sendStarMsgs([msg], false);
|
||||
return window.Store.Cmd.sendUnstarMsgs(msg.chat, [msg], false);
|
||||
}
|
||||
}, this.id._serialized);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ const client = new Client({
|
||||
|
||||
client.initialize();
|
||||
|
||||
client.on('loading_screen', (percent, message) => {
|
||||
console.log('LOADING SCREEN', percent, message);
|
||||
});
|
||||
|
||||
client.on('qr', (qr) => {
|
||||
// NOTE: This event will not be fired if a session is specified.
|
||||
console.log('QR RECEIVED', qr);
|
||||
|
||||
65
index.d.ts
vendored
65
index.d.ts
vendored
@@ -241,6 +241,15 @@ declare namespace WAWebJS {
|
||||
message: Message
|
||||
) => void): this
|
||||
|
||||
/** Emitted when a reaction is sent, received, updated or removed */
|
||||
on(event: 'message_reaction', listener: (
|
||||
/** The reaction object */
|
||||
reaction: Reaction
|
||||
) => void): this
|
||||
|
||||
/** Emitted when loading screen is appearing */
|
||||
on(event: 'loading_screen', listener: (percent: string, message: string) => void): this
|
||||
|
||||
/** Emitted when the QR code is received */
|
||||
on(event: 'qr', listener: (
|
||||
/** qr code string
|
||||
@@ -256,6 +265,9 @@ declare namespace WAWebJS {
|
||||
|
||||
/** Emitted when the client has initialized and is ready to receive messages */
|
||||
on(event: 'ready', listener: () => void): this
|
||||
|
||||
/** Emitted when the RemoteAuth session is saved successfully on the external Database */
|
||||
on(event: 'remote_session_saved', listener: () => void): this
|
||||
}
|
||||
|
||||
/** Current connection information */
|
||||
@@ -345,6 +357,9 @@ declare namespace WAWebJS {
|
||||
failureEventPayload?: any
|
||||
}>;
|
||||
getAuthEventPayload: () => Promise<any>;
|
||||
afterAuthReady: () => Promise<void>;
|
||||
disconnect: () => Promise<void>;
|
||||
destroy: () => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -365,6 +380,30 @@ declare namespace WAWebJS {
|
||||
dataPath?: string
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote-based authentication
|
||||
*/
|
||||
export class RemoteAuth extends AuthStrategy {
|
||||
public clientId?: string;
|
||||
public dataPath?: string;
|
||||
constructor(options?: {
|
||||
store: Store,
|
||||
clientId?: string,
|
||||
dataPath?: string,
|
||||
backupSyncIntervalMs: number
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote store interface
|
||||
*/
|
||||
export interface Store {
|
||||
sessionExists: ({session: string}) => Promise<boolean> | boolean,
|
||||
delete: ({session: string}) => Promise<any> | any,
|
||||
save: ({session: string}) => Promise<any> | any,
|
||||
extract: ({session: string, path: string}) => Promise<any> | any,
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy session auth strategy
|
||||
@@ -463,9 +502,11 @@ declare namespace WAWebJS {
|
||||
GROUP_LEAVE = 'group_leave',
|
||||
GROUP_UPDATE = 'group_update',
|
||||
QR_RECEIVED = 'qr',
|
||||
LOADING_SCREEN = 'loading_screen',
|
||||
DISCONNECTED = 'disconnected',
|
||||
STATE_CHANGED = 'change_state',
|
||||
BATTERY_CHANGED = 'change_battery',
|
||||
REMOTE_SESSION_SAVED = 'remote_session_saved'
|
||||
}
|
||||
|
||||
/** Group notification types */
|
||||
@@ -708,7 +749,7 @@ declare namespace WAWebJS {
|
||||
/** React to this message with an emoji*/
|
||||
react: (reaction: string) => Promise<void>,
|
||||
/**
|
||||
* Forwards this message to another chat
|
||||
* Forwards this message to another chat (that you chatted before, otherwise it will fail)
|
||||
*/
|
||||
forward: (chat: Chat | string) => Promise<void>,
|
||||
/** Star this message */
|
||||
@@ -805,13 +846,16 @@ declare namespace WAWebJS {
|
||||
data: string
|
||||
/** Document file name. Value can be null */
|
||||
filename?: string | null
|
||||
/** Document file size in bytes. Value can be null. */
|
||||
filesize?: number | null
|
||||
|
||||
/**
|
||||
* @param {string} mimetype MIME type of the attachment
|
||||
* @param {string} data Base64-encoded data of the file
|
||||
* @param {?string} filename Document file name. Value can be null
|
||||
* @param {?number} filesize Document file size in bytes. Value can be null.
|
||||
*/
|
||||
constructor(mimetype: string, data: string, filename?: string | null)
|
||||
constructor(mimetype: string, data: string, filename?: string | null, filesize?: number | null)
|
||||
|
||||
/** Creates a MessageMedia instance from a local file path */
|
||||
static fromFilePath: (filePath: string) => MessageMedia
|
||||
@@ -1018,6 +1062,10 @@ declare namespace WAWebJS {
|
||||
* Set this to Infinity to load all messages.
|
||||
*/
|
||||
limit?: number
|
||||
/**
|
||||
* Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
|
||||
*/
|
||||
fromMe?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1300,6 +1348,19 @@ declare namespace WAWebJS {
|
||||
|
||||
constructor(body: string, buttons: Array<{ id?: string; body: string }>, title?: string | null, footer?: string | null)
|
||||
}
|
||||
|
||||
/** Message type Reaction */
|
||||
export class Reaction {
|
||||
id: MessageId
|
||||
orphan: number
|
||||
orphanReason?: string
|
||||
timestamp: number
|
||||
reaction: string
|
||||
read: boolean
|
||||
msgId: MessageId
|
||||
senderId: string
|
||||
ack?: number
|
||||
}
|
||||
}
|
||||
|
||||
export = WAWebJS
|
||||
|
||||
1
index.js
1
index.js
@@ -25,6 +25,7 @@ module.exports = {
|
||||
// Auth Strategies
|
||||
NoAuth: require('./src/authStrategies/NoAuth'),
|
||||
LocalAuth: require('./src/authStrategies/LocalAuth'),
|
||||
RemoteAuth: require('./src/authStrategies/RemoteAuth'),
|
||||
LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'),
|
||||
|
||||
...Constants
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "whatsapp-web.js",
|
||||
"version": "1.17.1",
|
||||
"version": "1.18.0-alpha.1",
|
||||
"description": "Library for interacting with the WhatsApp Web API ",
|
||||
"main": "./index.js",
|
||||
"typings": "./index.d.ts",
|
||||
@@ -51,5 +51,10 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"archiver": "^5.3.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"unzipper": "^0.10.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constan
|
||||
const { ExposeStore, LoadUtils } = require('./util/Injected');
|
||||
const ChatFactory = require('./factories/ChatFactory');
|
||||
const ContactFactory = require('./factories/ContactFactory');
|
||||
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List} = require('./structures');
|
||||
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
|
||||
const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
|
||||
const NoAuth = require('./authStrategies/NoAuth');
|
||||
|
||||
@@ -115,6 +115,52 @@ class Client extends EventEmitter {
|
||||
referer: 'https://whatsapp.com/'
|
||||
});
|
||||
|
||||
await page.evaluate(`function getElementByXpath(path) {
|
||||
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
}`);
|
||||
|
||||
let lastPercent = null,
|
||||
lastPercentMessage = null;
|
||||
|
||||
await page.exposeFunction('loadingScreen', async (percent, message) => {
|
||||
if (lastPercent !== percent || lastPercentMessage !== message) {
|
||||
this.emit(Events.LOADING_SCREEN, percent, message);
|
||||
lastPercent = percent;
|
||||
lastPercentMessage = message;
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(
|
||||
async function (selectors) {
|
||||
var observer = new MutationObserver(function () {
|
||||
let progressBar = window.getElementByXpath(
|
||||
selectors.PROGRESS
|
||||
);
|
||||
let progressMessage = window.getElementByXpath(
|
||||
selectors.PROGRESS_MESSAGE
|
||||
);
|
||||
|
||||
if (progressBar) {
|
||||
window.loadingScreen(
|
||||
progressBar.value,
|
||||
progressMessage.innerText
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
});
|
||||
},
|
||||
{
|
||||
PROGRESS: '//*[@id=\'app\']/div/div/div[2]/progress',
|
||||
PROGRESS_MESSAGE: '//*[@id=\'app\']/div/div/div[3]',
|
||||
}
|
||||
);
|
||||
|
||||
const INTRO_IMG_SELECTOR = '[data-testid="intro-md-beta-logo-dark"], [data-testid="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]';
|
||||
const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas';
|
||||
|
||||
@@ -373,7 +419,7 @@ class Client extends EventEmitter {
|
||||
this.emit(Events.MEDIA_UPLOADED, message);
|
||||
});
|
||||
|
||||
await page.exposeFunction('onAppStateChangedEvent', (state) => {
|
||||
await page.exposeFunction('onAppStateChangedEvent', async (state) => {
|
||||
|
||||
/**
|
||||
* Emitted when the connection state changes
|
||||
@@ -400,6 +446,7 @@ class Client extends EventEmitter {
|
||||
* @event Client#disconnected
|
||||
* @param {WAState|"NAVIGATION"} reason reason that caused the disconnect
|
||||
*/
|
||||
await this.authStrategy.disconnect();
|
||||
this.emit(Events.DISCONNECTED, state);
|
||||
this.destroy();
|
||||
}
|
||||
@@ -439,6 +486,27 @@ class Client extends EventEmitter {
|
||||
this.emit(Events.INCOMING_CALL, cll);
|
||||
});
|
||||
|
||||
await page.exposeFunction('onReaction', (reactions) => {
|
||||
for (const reaction of reactions) {
|
||||
/**
|
||||
* Emitted when a reaction is sent, received, updated or removed
|
||||
* @event Client#message_reaction
|
||||
* @param {object} reaction
|
||||
* @param {object} reaction.id - Reaction id
|
||||
* @param {number} reaction.orphan - Orphan
|
||||
* @param {?string} reaction.orphanReason - Orphan reason
|
||||
* @param {number} reaction.timestamp - Timestamp
|
||||
* @param {string} reaction.reaction - Reaction
|
||||
* @param {boolean} reaction.read - Read
|
||||
* @param {object} reaction.msgId - Parent message id
|
||||
* @param {string} reaction.senderId - Sender id
|
||||
* @param {?number} reaction.ack - Ack
|
||||
*/
|
||||
|
||||
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
|
||||
window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
|
||||
@@ -458,6 +526,22 @@ class Client extends EventEmitter {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
const module = window.Store.createOrUpdateReactionsModule;
|
||||
const ogMethod = module.createOrUpdateReactions;
|
||||
module.createOrUpdateReactions = ((...args) => {
|
||||
window.onReaction(args[0].map(reaction => {
|
||||
const msgKey = window.Store.MsgKey.fromString(reaction.msgKey);
|
||||
const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey);
|
||||
const timestamp = reaction.timestamp / 1000;
|
||||
|
||||
return {...reaction, msgKey, parentMsgKey, timestamp };
|
||||
}));
|
||||
|
||||
return ogMethod(...args);
|
||||
}).bind(module);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -465,11 +549,13 @@ class Client extends EventEmitter {
|
||||
* @event Client#ready
|
||||
*/
|
||||
this.emit(Events.READY);
|
||||
this.authStrategy.afterAuthReady();
|
||||
|
||||
// Disconnect when navigating away when in PAIRING state (detect logout)
|
||||
this.pupPage.on('framenavigated', async () => {
|
||||
const appState = await this.getState();
|
||||
if(!appState || appState === WAState.PAIRING) {
|
||||
await this.authStrategy.disconnect();
|
||||
this.emit(Events.DISCONNECTED, 'NAVIGATION');
|
||||
await this.destroy();
|
||||
}
|
||||
@@ -481,6 +567,7 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
async destroy() {
|
||||
await this.pupBrowser.close();
|
||||
await this.authStrategy.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,9 @@ class BaseAuthStrategy {
|
||||
};
|
||||
}
|
||||
async getAuthEventPayload() {}
|
||||
async afterAuthReady() {}
|
||||
async disconnect() {}
|
||||
async destroy() {}
|
||||
async logout() {}
|
||||
}
|
||||
|
||||
|
||||
204
src/authStrategies/RemoteAuth.js
Normal file
204
src/authStrategies/RemoteAuth.js
Normal file
@@ -0,0 +1,204 @@
|
||||
'use strict';
|
||||
|
||||
/* Require Optional Dependencies */
|
||||
try {
|
||||
var fs = require('fs-extra');
|
||||
var unzipper = require('unzipper');
|
||||
var archiver = require('archiver');
|
||||
} catch {
|
||||
fs = undefined;
|
||||
unzipper = undefined;
|
||||
archiver = undefined;
|
||||
}
|
||||
|
||||
const path = require('path');
|
||||
const { Events } = require('./../util/Constants');
|
||||
const BaseAuthStrategy = require('./BaseAuthStrategy');
|
||||
|
||||
/**
|
||||
* Remote-based authentication
|
||||
* @param {object} options - options
|
||||
* @param {object} options.store - Remote database store instance
|
||||
* @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
|
||||
* @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
|
||||
* @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute}
|
||||
*/
|
||||
class RemoteAuth extends BaseAuthStrategy {
|
||||
constructor({ clientId, dataPath, store, backupSyncIntervalMs } = {}) {
|
||||
if (!fs && !unzipper && !archiver) throw new Error('Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag');
|
||||
super();
|
||||
|
||||
const idRegex = /^[-_\w]+$/i;
|
||||
if (clientId && !idRegex.test(clientId)) {
|
||||
throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
|
||||
}
|
||||
if (!backupSyncIntervalMs || backupSyncIntervalMs < 60000) {
|
||||
throw new Error('Invalid backupSyncIntervalMs. Accepts values starting from 60000ms {1 minute}.');
|
||||
}
|
||||
if(!store) throw new Error('Remote database store is required.');
|
||||
|
||||
this.store = store;
|
||||
this.clientId = clientId;
|
||||
this.backupSyncIntervalMs = backupSyncIntervalMs;
|
||||
this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
|
||||
this.tempDir = `${this.dataPath}/wwebjs_temp_session`;
|
||||
this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */
|
||||
}
|
||||
|
||||
async beforeBrowserInitialized() {
|
||||
const puppeteerOpts = this.client.options.puppeteer;
|
||||
const sessionDirName = this.clientId ? `RemoteAuth-${this.clientId}` : 'RemoteAuth';
|
||||
const dirPath = path.join(this.dataPath, sessionDirName);
|
||||
|
||||
if (puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) {
|
||||
throw new Error('RemoteAuth is not compatible with a user-supplied userDataDir.');
|
||||
}
|
||||
|
||||
this.userDataDir = dirPath;
|
||||
this.sessionName = sessionDirName;
|
||||
|
||||
await this.extractRemoteSession();
|
||||
|
||||
this.client.options.puppeteer = {
|
||||
...puppeteerOpts,
|
||||
userDataDir: dirPath
|
||||
};
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.disconnect();
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
clearInterval(this.backupSync);
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
await this.deleteRemoteSession();
|
||||
|
||||
let pathExists = await this.isValidPath(this.userDataDir);
|
||||
if (pathExists) {
|
||||
await fs.promises.rm(this.userDataDir, {
|
||||
recursive: true,
|
||||
force: true
|
||||
}).catch(() => {});
|
||||
}
|
||||
clearInterval(this.backupSync);
|
||||
}
|
||||
|
||||
async afterAuthReady() {
|
||||
const sessionExists = await this.store.sessionExists({session: this.sessionName});
|
||||
if(!sessionExists) {
|
||||
await this.delay(60000); /* Initial delay sync required for session to be stable enough to recover */
|
||||
await this.storeRemoteSession({emit: true});
|
||||
}
|
||||
var self = this;
|
||||
this.backupSync = setInterval(async function () {
|
||||
await self.storeRemoteSession();
|
||||
}, this.backupSyncIntervalMs);
|
||||
}
|
||||
|
||||
async storeRemoteSession(options) {
|
||||
/* Compress & Store Session */
|
||||
const pathExists = await this.isValidPath(this.userDataDir);
|
||||
if (pathExists) {
|
||||
await this.compressSession();
|
||||
await this.store.save({session: this.sessionName});
|
||||
await fs.promises.unlink(`${this.sessionName}.zip`);
|
||||
await fs.promises.rm(`${this.tempDir}`, {
|
||||
recursive: true,
|
||||
force: true
|
||||
}).catch(() => {});
|
||||
if(options && options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED);
|
||||
}
|
||||
}
|
||||
|
||||
async extractRemoteSession() {
|
||||
const pathExists = await this.isValidPath(this.userDataDir);
|
||||
const compressedSessionPath = `${this.sessionName}.zip`;
|
||||
const sessionExists = await this.store.sessionExists({session: this.sessionName});
|
||||
if (pathExists) {
|
||||
await fs.promises.rm(this.userDataDir, {
|
||||
recursive: true,
|
||||
force: true
|
||||
}).catch(() => {});
|
||||
}
|
||||
if (sessionExists) {
|
||||
await this.store.extract({session: this.sessionName, path: compressedSessionPath});
|
||||
await this.unCompressSession(compressedSessionPath);
|
||||
} else {
|
||||
fs.mkdirSync(this.userDataDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteRemoteSession() {
|
||||
const sessionExists = await this.store.sessionExists({session: this.sessionName});
|
||||
if (sessionExists) await this.store.delete({session: this.sessionName});
|
||||
}
|
||||
|
||||
async compressSession() {
|
||||
const archive = archiver('zip');
|
||||
const stream = fs.createWriteStream(`${this.sessionName}.zip`);
|
||||
|
||||
await fs.copy(this.userDataDir, this.tempDir).catch(() => {});
|
||||
await this.deleteMetadata();
|
||||
return new Promise((resolve, reject) => {
|
||||
archive
|
||||
.directory(this.tempDir, false)
|
||||
.on('error', err => reject(err))
|
||||
.pipe(stream);
|
||||
|
||||
stream.on('close', () => resolve());
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
async unCompressSession(compressedSessionPath) {
|
||||
var stream = fs.createReadStream(compressedSessionPath);
|
||||
await new Promise((resolve, reject) => {
|
||||
stream.pipe(unzipper.Extract({
|
||||
path: this.userDataDir
|
||||
}))
|
||||
.on('error', err => reject(err))
|
||||
.on('finish', () => resolve());
|
||||
});
|
||||
await fs.promises.unlink(compressedSessionPath);
|
||||
}
|
||||
|
||||
async deleteMetadata() {
|
||||
const sessionDirs = [this.tempDir, path.join(this.tempDir, 'Default')];
|
||||
for (const dir of sessionDirs) {
|
||||
const sessionFiles = await fs.promises.readdir(dir);
|
||||
for (const element of sessionFiles) {
|
||||
if (!this.requiredDirs.includes(element)) {
|
||||
const dirElement = path.join(dir, element);
|
||||
const stats = await fs.promises.lstat(dirElement);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await fs.promises.rm(dirElement, {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
} else {
|
||||
await fs.promises.unlink(dirElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async isValidPath(path) {
|
||||
try {
|
||||
await fs.promises.access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RemoteAuth;
|
||||
@@ -170,13 +170,22 @@ class Chat extends Base {
|
||||
|
||||
/**
|
||||
* Loads chat messages, sorted from earliest to latest.
|
||||
* @param {Object} searchOptions Options for searching messages. Right now only limit is supported.
|
||||
* @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported.
|
||||
* @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.
|
||||
* @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
|
||||
* @returns {Promise<Array<Message>>}
|
||||
*/
|
||||
async fetchMessages(searchOptions) {
|
||||
let messages = await this.client.pupPage.evaluate(async (chatId, searchOptions) => {
|
||||
const msgFilter = m => !m.isNotification; // dont include notification messages
|
||||
const msgFilter = (m) => {
|
||||
if (m.isNotification) {
|
||||
return false; // dont include notification messages
|
||||
}
|
||||
if (searchOptions && searchOptions.fromMe && m.id.fromMe !== searchOptions.fromMe) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const chat = window.Store.Chat.get(chatId);
|
||||
let msgs = chat.msgs.getModelsArray().filter(msgFilter);
|
||||
|
||||
@@ -342,6 +342,8 @@ class Message extends Base {
|
||||
*/
|
||||
async react(reaction){
|
||||
await this.client.pupPage.evaluate(async (messageId, reaction) => {
|
||||
if (!messageId) { return undefined; }
|
||||
|
||||
const msg = await window.Store.Msg.get(messageId);
|
||||
await window.Store.sendReactionToMsg(msg, reaction);
|
||||
}, this.id._serialized, reaction);
|
||||
@@ -356,7 +358,7 @@ class Message extends Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards this message to another chat
|
||||
* Forwards this message to another chat (that you chatted before, otherwise it will fail)
|
||||
*
|
||||
* @param {string|Chat} chat Chat model or chat ID to which the message will be forwarded
|
||||
* @returns {Promise}
|
||||
@@ -408,12 +410,13 @@ class Message extends Base {
|
||||
signal: (new AbortController).signal
|
||||
});
|
||||
|
||||
const data = window.WWebJS.arrayBufferToBase64(decryptedMedia);
|
||||
const data = await window.WWebJS.arrayBufferToBase64Async(decryptedMedia);
|
||||
|
||||
return {
|
||||
data,
|
||||
mimetype: msg.mimetype,
|
||||
filename: msg.filename
|
||||
filename: msg.filename,
|
||||
filesize: msg.size
|
||||
};
|
||||
} catch (e) {
|
||||
if(e.status && e.status === 404) return undefined;
|
||||
@@ -422,7 +425,7 @@ class Message extends Base {
|
||||
}, this.id._serialized);
|
||||
|
||||
if (!result) return undefined;
|
||||
return new MessageMedia(result.mimetype, result.data, result.filename);
|
||||
return new MessageMedia(result.mimetype, result.data, result.filename, result.filesize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,7 +452,7 @@ class Message extends Base {
|
||||
let msg = window.Store.Msg.get(msgId);
|
||||
|
||||
if (msg.canStar()) {
|
||||
return msg.chat.sendStarMsgs([msg], true);
|
||||
return window.Store.Cmd.sendStarMsgs(msg.chat, [msg], false);
|
||||
}
|
||||
}, this.id._serialized);
|
||||
}
|
||||
@@ -462,7 +465,7 @@ class Message extends Base {
|
||||
let msg = window.Store.Msg.get(msgId);
|
||||
|
||||
if (msg.canStar()) {
|
||||
return msg.chat.sendStarMsgs([msg], false);
|
||||
return window.Store.Cmd.sendUnstarMsgs(msg.chat, [msg], false);
|
||||
}
|
||||
}, this.id._serialized);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ const { URL } = require('url');
|
||||
* Media attached to a message
|
||||
* @param {string} mimetype MIME type of the attachment
|
||||
* @param {string} data Base64-encoded data of the file
|
||||
* @param {?string} filename Document file name
|
||||
* @param {?string} filename Document file name. Value can be null
|
||||
* @param {?number} filesize Document file size in bytes. Value can be null
|
||||
*/
|
||||
class MessageMedia {
|
||||
constructor(mimetype, data, filename) {
|
||||
constructor(mimetype, data, filename, filesize) {
|
||||
/**
|
||||
* MIME type of the attachment
|
||||
* @type {string}
|
||||
@@ -27,10 +28,16 @@ class MessageMedia {
|
||||
this.data = data;
|
||||
|
||||
/**
|
||||
* Name of the file (for documents)
|
||||
* Document file name. Value can be null
|
||||
* @type {?string}
|
||||
*/
|
||||
this.filename = filename;
|
||||
|
||||
/**
|
||||
* Document file size in bytes. Value can be null
|
||||
* @type {?number}
|
||||
*/
|
||||
this.filesize = filesize;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +75,7 @@ class MessageMedia {
|
||||
const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options);
|
||||
const response = await fetch(url, reqOptions);
|
||||
const mime = response.headers.get('Content-Type');
|
||||
const size = response.headers.get('Content-Length');
|
||||
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null;
|
||||
@@ -83,7 +91,7 @@ class MessageMedia {
|
||||
data = btoa(data);
|
||||
}
|
||||
|
||||
return { data, mime, name };
|
||||
return { data, mime, name, size };
|
||||
}
|
||||
|
||||
const res = options.client
|
||||
@@ -96,7 +104,7 @@ class MessageMedia {
|
||||
if (!mimetype)
|
||||
mimetype = res.mime;
|
||||
|
||||
return new MessageMedia(mimetype, res.data, filename);
|
||||
return new MessageMedia(mimetype, res.data, filename, res.size || null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
69
src/structures/Reaction.js
Normal file
69
src/structures/Reaction.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const Base = require('./Base');
|
||||
|
||||
/**
|
||||
* Represents a Reaction on WhatsApp
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Reaction extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
if (data) this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
/**
|
||||
* Reaction ID
|
||||
* @type {object}
|
||||
*/
|
||||
this.id = data.msgKey;
|
||||
/**
|
||||
* Orphan
|
||||
* @type {number}
|
||||
*/
|
||||
this.orphan = data.orphan;
|
||||
/**
|
||||
* Orphan reason
|
||||
* @type {?string}
|
||||
*/
|
||||
this.orphanReason = data.orphanReason;
|
||||
/**
|
||||
* Unix timestamp for when the reaction was created
|
||||
* @type {number}
|
||||
*/
|
||||
this.timestamp = data.timestamp;
|
||||
/**
|
||||
* Reaction
|
||||
* @type {string}
|
||||
*/
|
||||
this.reaction = data.reactionText;
|
||||
/**
|
||||
* Read
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.read = data.read;
|
||||
/**
|
||||
* Message ID
|
||||
* @type {object}
|
||||
*/
|
||||
this.msgId = data.parentMsgKey;
|
||||
/**
|
||||
* Sender ID
|
||||
* @type {string}
|
||||
*/
|
||||
this.senderId = data.senderUserJid;
|
||||
/**
|
||||
* ACK
|
||||
* @type {?number}
|
||||
*/
|
||||
this.ack = data.ack;
|
||||
|
||||
|
||||
return super._patch(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Reaction;
|
||||
@@ -18,4 +18,5 @@ module.exports = {
|
||||
Buttons: require('./Buttons'),
|
||||
List: require('./List'),
|
||||
Payment: require('./Payment'),
|
||||
Reaction: require('./Reaction'),
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ exports.DefaultOptions = {
|
||||
qrMaxRetries: 0,
|
||||
takeoverOnConflict: false,
|
||||
takeoverTimeoutMs: 0,
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
|
||||
ffmpegPath: 'ffmpeg',
|
||||
bypassCSP: false
|
||||
};
|
||||
@@ -41,15 +41,18 @@ exports.Events = {
|
||||
MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone',
|
||||
MESSAGE_REVOKED_ME: 'message_revoke_me',
|
||||
MESSAGE_ACK: 'message_ack',
|
||||
MESSAGE_REACTION: 'message_reaction',
|
||||
MEDIA_UPLOADED: 'media_uploaded',
|
||||
GROUP_JOIN: 'group_join',
|
||||
GROUP_LEAVE: 'group_leave',
|
||||
GROUP_UPDATE: 'group_update',
|
||||
QR_RECEIVED: 'qr',
|
||||
LOADING_SCREEN: 'loading_screen',
|
||||
DISCONNECTED: 'disconnected',
|
||||
STATE_CHANGED: 'change_state',
|
||||
BATTERY_CHANGED: 'change_battery',
|
||||
INCOMING_CALL: 'incoming_call'
|
||||
INCOMING_CALL: 'incoming_call',
|
||||
REMOTE_SESSION_SAVED: 'remote_session_saved'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,6 +50,7 @@ exports.ExposeStore = (moduleRaidStr) => {
|
||||
window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0];
|
||||
window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0];
|
||||
window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg;
|
||||
window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0];
|
||||
window.Store.StickerTools = {
|
||||
...window.mR.findModule('toWebpSticker')[0],
|
||||
...window.mR.findModule('addWebpMetadata')[0]
|
||||
@@ -479,6 +480,20 @@ exports.LoadUtils = () => {
|
||||
return window.btoa(binary);
|
||||
};
|
||||
|
||||
window.WWebJS.arrayBufferToBase64Async = (arrayBuffer) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const blob = new Blob([arrayBuffer], {
|
||||
type: 'application/octet-stream',
|
||||
});
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
const [, data] = fileReader.result.split(',');
|
||||
resolve(data);
|
||||
};
|
||||
fileReader.onerror = (e) => reject(e);
|
||||
fileReader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
window.WWebJS.getFileHash = async (data) => {
|
||||
let buffer = await data.arrayBuffer();
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|
||||
|
||||
80
tools/publish
Executable file
80
tools/publish
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd '..'
|
||||
|
||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
RELEASE_MODE=$1
|
||||
|
||||
echo ""
|
||||
echo "-----> CHECK INPUTS"
|
||||
echo ""
|
||||
|
||||
if [[ "$RELEASE_MODE" == "alpha" ]]
|
||||
then
|
||||
PRERELEASE='true'
|
||||
VERSION_ARGS="prerelease --preid alpha"
|
||||
DIST_TAG="next"
|
||||
elif [[ "$RELEASE_MODE" == "alpha-minor" ]]
|
||||
then
|
||||
PRERELEASE='true'
|
||||
VERSION_ARGS="preminor --preid alpha"
|
||||
DIST_TAG="next"
|
||||
elif [[ "$RELEASE_MODE" == "alpha-major" ]]
|
||||
then
|
||||
PRERELEASE='true'
|
||||
VERSION_ARGS="premajor --preid alpha"
|
||||
DIST_TAG="next"
|
||||
else
|
||||
echo 'Release Mode required'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f ~/.npmrc ]; then
|
||||
echo "Found existing .npmrc"
|
||||
else
|
||||
if [[ -z "$NPM_TOKEN" ]];then
|
||||
echo "No NPM_TOKEN or ~/.npmrc, exiting.."
|
||||
exit 1;
|
||||
else
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
fi
|
||||
fi
|
||||
echo "Publishing as NPM user: `npm whoami`"
|
||||
|
||||
if [[ $BRANCH != 'main' ]]; then
|
||||
echo "Not on 'main' branch. Exiting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
echo "There are uncommitted changes on this branch. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "-----> BUMP VERSION"
|
||||
echo ""
|
||||
|
||||
npm version $VERSION_ARGS || exit 1
|
||||
git push && git push --tags || exit 1
|
||||
|
||||
NEW_VERSION=`cat package.json | jq -r .version`
|
||||
echo "New Version: $NEW_VERSION"
|
||||
|
||||
echo ""
|
||||
echo "-----> PUSH TO NPM"
|
||||
echo ""
|
||||
|
||||
npm publish --tag $DIST_TAG
|
||||
|
||||
|
||||
echo ""
|
||||
echo "-----> Done!"
|
||||
echo "Version $NEW_VERSION published to $DIST_TAG tag"
|
||||
echo ""
|
||||
|
||||
echo "::set-output name=NEW_VERSION::$NEW_VERSION"
|
||||
echo "::set-output name=PRERELEASE::$PRERELEASE"
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user