added proxy support

This commit is contained in:
Adhiraj
2020-09-06 15:48:01 +05:30
parent 7cc35d6e84
commit f12f6fd90d
10 changed files with 70 additions and 21 deletions

View File

@@ -8,6 +8,7 @@ import {
MessageLogLevel, MessageLogLevel,
WA_MESSAGE_STUB_TYPES, WA_MESSAGE_STUB_TYPES,
ReconnectMode, ReconnectMode,
ProxyAgent,
} from '../src/WAConnection/WAConnection' } from '../src/WAConnection/WAConnection'
import * as fs from 'fs' import * as fs from 'fs'
@@ -16,6 +17,7 @@ async function example() {
conn.autoReconnect = ReconnectMode.onConnectionLost // only automatically reconnect when the connection breaks conn.autoReconnect = ReconnectMode.onConnectionLost // only automatically reconnect when the connection breaks
conn.logLevel = MessageLogLevel.info // set to unhandled to see what kind of stuff you can implement conn.logLevel = MessageLogLevel.info // set to unhandled to see what kind of stuff you can implement
// loads the auth file credentials if present // loads the auth file credentials if present
fs.existsSync('./auth_info.json') && conn.loadAuthInfo ('./auth_info.json') fs.existsSync('./auth_info.json') && conn.loadAuthInfo ('./auth_info.json')
@@ -23,6 +25,8 @@ async function example() {
conn.connectOptions.timeoutMs = 60*1000 conn.connectOptions.timeoutMs = 60*1000
// attempt to reconnect at most 10 times // attempt to reconnect at most 10 times
conn.connectOptions.maxRetries = 10 conn.connectOptions.maxRetries = 10
// uncomment the following line to proxy the connection; some random proxy I got off of: https://proxyscrape.com/free-proxy-list
//conn.connectOptions.agent = ProxyAgent ('http://1.0.180.120:8080')
await conn.connect() await conn.connect()
const unread = await conn.loadAllUnreadMessages () const unread = await conn.loadAllUnreadMessages ()

View File

@@ -51,7 +51,6 @@ async function connectToWhatsApp () {
const unread = await conn.loadAllUnreadMessages () const unread = await conn.loadAllUnreadMessages ()
console.log ("you have " + unread.length + " unread messages") console.log ("you have " + unread.length + " unread messages")
} }
// run in main file // run in main file
connectToWhatsApp () connectToWhatsApp ()
.catch (err => console.log("unexpected error: " + err) ) // catch any errors .catch (err => console.log("unexpected error: " + err) ) // catch any errors
@@ -69,6 +68,18 @@ Do note, the `chats` object returned is now a [KeyedDB](https://github.com/adiwa
- Most applications require pagination of chats (Use `chats.paginated()`) - Most applications require pagination of chats (Use `chats.paginated()`)
- Most applications require **O(1)** access to chats via the chat ID. (Use `chats.get(jid)` with `KeyedDB`) - Most applications require **O(1)** access to chats via the chat ID. (Use `chats.get(jid)` with `KeyedDB`)
## Connecting via an HTTPS proxy
``` ts
import { WAConnection, ProxyAgent } from '@adiwajshing/baileys'
const conn = new WAConnecion ()
conn.connectOptions.agent = ProxyAgent ('http://some-host:1234')
await conn.connect ()
console.log ("oh hello " + conn.user.name + "! You connected via a proxy")
```
## Saving & Restoring Sessions ## Saving & Restoring Sessions
You obviously don't want to keep scanning the QR code every time you want to connect. You obviously don't want to keep scanning the QR code every time you want to connect.

View File

@@ -34,6 +34,7 @@
"@adiwajshing/keyed-db": "^0.1.2", "@adiwajshing/keyed-db": "^0.1.2",
"curve25519-js": "^0.0.4", "curve25519-js": "^0.0.4",
"futoin-hkdf": "^1.3.2", "futoin-hkdf": "^1.3.2",
"https-proxy-agent": "^5.0.0",
"jimp": "^0.16.1", "jimp": "^0.16.1",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"protobufjs": "^6.10.1", "protobufjs": "^6.10.1",

View File

@@ -1,4 +1,4 @@
import { Presence, ChatModification, delay } from '../WAConnection/WAConnection' import { Presence, ChatModification, delay, DEFAULT_ORIGIN } from '../WAConnection/WAConnection'
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import * as assert from 'assert' import * as assert from 'assert'
import fetch from 'node-fetch' import fetch from 'node-fetch'
@@ -51,7 +51,7 @@ WAConnectionTest('Misc', (conn) => {
await delay (5000) await delay (5000)
const ppUrl = await conn.getProfilePicture(conn.user.jid) const ppUrl = await conn.getProfilePicture(conn.user.jid)
const fetched = await fetch(ppUrl, { headers: { Origin: 'https://web.whatsapp.com' } }) const fetched = await fetch(ppUrl)
const buff = await fetched.buffer () const buff = await fetched.buffer ()
const newPP = await fs.readFile ('./Media/cat.jpeg') const newPP = await fs.readFile ('./Media/cat.jpeg')

View File

@@ -3,6 +3,7 @@ import WS from 'ws'
import * as Utils from './Utils' import * as Utils from './Utils'
import Encoder from '../Binary/Encoder' import Encoder from '../Binary/Encoder'
import Decoder from '../Binary/Decoder' import Decoder from '../Binary/Decoder'
import fetch from 'node-fetch'
import { import {
AuthenticationCredentials, AuthenticationCredentials,
WAUser, WAUser,
@@ -21,6 +22,7 @@ import {
ReconnectMode, ReconnectMode,
WAConnectOptions, WAConnectOptions,
MediaConnInfo, MediaConnInfo,
DEFAULT_ORIGIN,
} from './Constants' } from './Constants'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import KeyedDB from '@adiwajshing/keyed-db' import KeyedDB from '@adiwajshing/keyed-db'
@@ -340,6 +342,17 @@ export class WAConnection extends EventEmitter {
} }
}) })
} }
/**
* Does a fetch request with the configuration of the connection
*/
protected fetchRequest = (endpoint: string, method: string = 'GET', body?: any) => (
fetch(endpoint, {
method,
body,
headers: { Origin: DEFAULT_ORIGIN },
agent: this.connectOptions.agent
})
)
generateMessageTag () { generateMessageTag () {
return `${Utils.unixTimestampSeconds(this.referenceDate)}.--${this.msgCount}` return `${Utils.unixTimestampSeconds(this.referenceDate)}.--${this.msgCount}`
} }

View File

@@ -1,5 +1,5 @@
import * as Utils from './Utils' import * as Utils from './Utils'
import { WAMessage, WAChat, MessageLogLevel, WANode, KEEP_ALIVE_INTERVAL_MS, BaileysError, WAConnectOptions, DisconnectReason, UNAUTHORIZED_CODES, WAContact, TimedOutError, CancelledError, WAOpenResult } from './Constants' import { WAMessage, WAChat, MessageLogLevel, WANode, KEEP_ALIVE_INTERVAL_MS, BaileysError, WAConnectOptions, DisconnectReason, UNAUTHORIZED_CODES, WAContact, TimedOutError, CancelledError, WAOpenResult, DEFAULT_ORIGIN, WS_URL } from './Constants'
import {WAConnection as Base} from './1.Validation' import {WAConnection as Base} from './1.Validation'
import Decoder from '../Binary/Decoder' import Decoder from '../Binary/Decoder'
import WS from 'ws' import WS from 'ws'
@@ -73,7 +73,7 @@ export class WAConnection extends Base {
const reconnectID = shouldUseReconnect ? this.user.jid.replace ('@s.whatsapp.net', '@c.us') : null const reconnectID = shouldUseReconnect ? this.user.jid.replace ('@s.whatsapp.net', '@c.us') : null
this.conn = new WS('wss://web.whatsapp.com/ws', null, { origin: 'https://web.whatsapp.com', timeout: timeoutMs }) this.conn = new WS(WS_URL, null, { origin: DEFAULT_ORIGIN, timeout: timeoutMs, agent: options.agent })
this.conn.on('message', data => this.onMessageRecieved(data as any)) this.conn.on('message', data => this.onMessageRecieved(data as any))
this.conn.on ('open', () => { this.conn.on ('open', () => {

View File

@@ -1,5 +1,4 @@
import {WAConnection as Base} from './5.User' import {WAConnection as Base} from './5.User'
import fetch from 'node-fetch'
import {promises as fs} from 'fs' import {promises as fs} from 'fs'
import { import {
MessageOptions, MessageOptions,
@@ -114,18 +113,14 @@ export class WAConnection extends Base {
for (let host of json.hosts) { for (let host of json.hosts) {
const hostname = `https://${host.hostname}${MediaPathMap[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}` const hostname = `https://${host.hostname}${MediaPathMap[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
try { try {
const urlFetch = await fetch(hostname, { const urlFetch = await this.fetchRequest(hostname, 'POST', body)
method: 'POST',
body: body,
headers: { Origin: 'https://web.whatsapp.com' },
})
mediaUrl = (await urlFetch.json())?.url mediaUrl = (await urlFetch.json())?.url
if (mediaUrl) break if (mediaUrl) break
else throw new Error (`upload failed`) else throw new Error (`upload failed`)
} catch (error) { } catch (error) {
const isLast = host.hostname === json.hosts[json.hosts.length-1].hostname const isLast = host.hostname === json.hosts[json.hosts.length-1].hostname
this.log (`Error in uploading to ${host}${isLast ? '' : ', retrying...'}`, MessageLogLevel.info) this.log (`Error in uploading to ${host.hostname}${isLast ? '' : ', retrying...'}`, MessageLogLevel.info)
} }
} }
if (!mediaUrl) throw new Error('Media upload failed on all hosts') if (!mediaUrl) throw new Error('Media upload failed on all hosts')
@@ -212,15 +207,14 @@ export class WAConnection extends Base {
* Renews the download url automatically, if necessary. * Renews the download url automatically, if necessary.
*/ */
async downloadMediaMessage (message: WAMessage) { async downloadMediaMessage (message: WAMessage) {
const fetchHeaders = { }
try { try {
const buff = await decodeMediaMessageBuffer (message.message, fetchHeaders) const buff = await decodeMediaMessageBuffer (message.message, this.fetchRequest)
return buff return buff
} catch (error) { } catch (error) {
if (error instanceof BaileysError && error.status === 404) { // media needs to be updated if (error instanceof BaileysError && error.status === 404) { // media needs to be updated
this.log (`updating media of message: ${message.key.id}`, MessageLogLevel.info) this.log (`updating media of message: ${message.key.id}`, MessageLogLevel.info)
await this.updateMediaMessage (message) await this.updateMediaMessage (message)
const buff = await decodeMediaMessageBuffer (message.message, fetchHeaders) const buff = await decodeMediaMessageBuffer (message.message, this.fetchRequest)
return buff return buff
} }
throw error throw error

View File

@@ -1,8 +1,13 @@
import { WA } from '../Binary/Constants' import { WA } from '../Binary/Constants'
import { proto } from '../../WAMessage/WAMessage' import { proto } from '../../WAMessage/WAMessage'
import { Agent } from 'https'
export const WS_URL = 'wss://web.whatsapp.com/ws'
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com'
export const KEEP_ALIVE_INTERVAL_MS = 20*1000 export const KEEP_ALIVE_INTERVAL_MS = 20*1000
// export the WAMessage Prototypes // export the WAMessage Prototypes
export { proto as WAMessageProto } export { proto as WAMessageProto }
export type WANode = WA.Node export type WANode = WA.Node
@@ -15,6 +20,7 @@ export type WAContextInfo = proto.IContextInfo
export import WA_MESSAGE_STUB_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STUBTYPE export import WA_MESSAGE_STUB_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STUBTYPE
export import WA_MESSAGE_STATUS_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS export import WA_MESSAGE_STATUS_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS
export interface WALocationMessage { export interface WALocationMessage {
degreesLatitude: number degreesLatitude: number
degreesLongitude: number degreesLongitude: number
@@ -67,6 +73,8 @@ export type WAConnectOptions = {
waitForChats?: boolean waitForChats?: boolean
connectCooldownMs?: number connectCooldownMs?: number
/** agent which can be used for proxying connections */
agent?: Agent
} }
export type WAConnectionState = 'open' | 'connecting' | 'close' export type WAConnectionState = 'open' | 'connecting' | 'close'

View File

@@ -2,12 +2,14 @@ import * as Crypto from 'crypto'
import HKDF from 'futoin-hkdf' import HKDF from 'futoin-hkdf'
import Jimp from 'jimp' import Jimp from 'jimp'
import {promises as fs} from 'fs' import {promises as fs} from 'fs'
import fetch from 'node-fetch'
import { exec } from 'child_process' import { exec } from 'child_process'
import {platform, release} from 'os' import {platform, release} from 'os'
import HttpsProxyAgent from 'https-proxy-agent'
import { URL } from 'url'
import { Agent } from 'https'
import Decoder from '../Binary/Decoder' import Decoder from '../Binary/Decoder'
import { MessageType, HKDFInfoKeys, MessageOptions, WAChat, WAMessageContent, BaileysError, WAMessageProto, TimedOutError, CancelledError } from './Constants' import { MessageType, HKDFInfoKeys, MessageOptions, WAChat, WAMessageContent, BaileysError, WAMessageProto, TimedOutError, CancelledError, DEFAULT_ORIGIN } from './Constants'
const platformMap = { const platformMap = {
'aix': 'AIX', 'aix': 'AIX',
@@ -220,6 +222,8 @@ export const generateProfilePicture = async (buffer: Buffer) => {
preview: await cropped.resize(96, 96).getBufferAsync (Jimp.MIME_JPEG) preview: await cropped.resize(96, 96).getBufferAsync (Jimp.MIME_JPEG)
} }
} }
export const ProxyAgent = (host: string | URL) => HttpsProxyAgent(host) as any as Agent
/** generates a thumbnail for a given media, if required */ /** generates a thumbnail for a given media, if required */
export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, info: MessageOptions) { export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, info: MessageOptions) {
if (info.thumbnail === null || info.thumbnail) { if (info.thumbnail === null || info.thumbnail) {
@@ -249,7 +253,7 @@ export async function generateThumbnail(buffer: Buffer, mediaType: MessageType,
* Decode a media message (video, image, document, audio) & return decrypted buffer * Decode a media message (video, image, document, audio) & return decrypted buffer
* @param message the media message you want to decode * @param message the media message you want to decode
*/ */
export async function decodeMediaMessageBuffer(message: WAMessageContent, fetchHeaders: {[k: string]: string} = {}) { export async function decodeMediaMessageBuffer(message: WAMessageContent, fetchRequest: (host: string, method: string) => any) {
/* /*
One can infer media type from the key in the message One can infer media type from the key in the message
it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc. it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc.
@@ -274,8 +278,7 @@ export async function decodeMediaMessageBuffer(message: WAMessageContent, fetchH
} }
// download the message // download the message
const headers = { Origin: 'https://web.whatsapp.com' } const fetched = await fetchRequest(messageContent.url, 'GET')
const fetched = await fetch(messageContent.url, { headers })
const buffer = await fetched.buffer() const buffer = await fetched.buffer()
if (buffer.length <= 10) { if (buffer.length <= 10) {

View File

@@ -399,6 +399,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
agent-base@6:
version "6.0.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4"
integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==
dependencies:
debug "4"
ansi-colors@4.1.1: ansi-colors@4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -653,7 +660,7 @@ dateformat@~1.0.4-1.2.3:
get-stdin "^4.0.1" get-stdin "^4.0.1"
meow "^3.3.0" meow "^3.3.0"
debug@4.1.1: debug@4, debug@4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -946,6 +953,14 @@ hosted-git-info@^2.1.4:
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
ieee754@^1.1.4: ieee754@^1.1.4:
version "1.1.13" version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"