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,
WA_MESSAGE_STUB_TYPES,
ReconnectMode,
ProxyAgent,
} from '../src/WAConnection/WAConnection'
import * as fs from 'fs'
@@ -16,6 +17,7 @@ async function example() {
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
// loads the auth file credentials if present
fs.existsSync('./auth_info.json') && conn.loadAuthInfo ('./auth_info.json')
@@ -23,6 +25,8 @@ async function example() {
conn.connectOptions.timeoutMs = 60*1000
// attempt to reconnect at most 10 times
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()
const unread = await conn.loadAllUnreadMessages ()

View File

@@ -51,7 +51,6 @@ async function connectToWhatsApp () {
const unread = await conn.loadAllUnreadMessages ()
console.log ("you have " + unread.length + " unread messages")
}
// run in main file
connectToWhatsApp ()
.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 **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
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",
"curve25519-js": "^0.0.4",
"futoin-hkdf": "^1.3.2",
"https-proxy-agent": "^5.0.0",
"jimp": "^0.16.1",
"node-fetch": "^2.6.0",
"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 * as assert from 'assert'
import fetch from 'node-fetch'
@@ -51,7 +51,7 @@ WAConnectionTest('Misc', (conn) => {
await delay (5000)
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 newPP = await fs.readFile ('./Media/cat.jpeg')

View File

@@ -3,6 +3,7 @@ import WS from 'ws'
import * as Utils from './Utils'
import Encoder from '../Binary/Encoder'
import Decoder from '../Binary/Decoder'
import fetch from 'node-fetch'
import {
AuthenticationCredentials,
WAUser,
@@ -21,6 +22,7 @@ import {
ReconnectMode,
WAConnectOptions,
MediaConnInfo,
DEFAULT_ORIGIN,
} from './Constants'
import { EventEmitter } from 'events'
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 () {
return `${Utils.unixTimestampSeconds(this.referenceDate)}.--${this.msgCount}`
}

View File

@@ -1,5 +1,5 @@
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 Decoder from '../Binary/Decoder'
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
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 ('open', () => {

View File

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

View File

@@ -1,8 +1,13 @@
import { WA } from '../Binary/Constants'
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 the WAMessage Prototypes
export { proto as WAMessageProto }
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_STATUS_TYPE = proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS
export interface WALocationMessage {
degreesLatitude: number
degreesLongitude: number
@@ -67,6 +73,8 @@ export type WAConnectOptions = {
waitForChats?: boolean
connectCooldownMs?: number
/** agent which can be used for proxying connections */
agent?: Agent
}
export type WAConnectionState = 'open' | 'connecting' | 'close'

View File

@@ -2,12 +2,14 @@ import * as Crypto from 'crypto'
import HKDF from 'futoin-hkdf'
import Jimp from 'jimp'
import {promises as fs} from 'fs'
import fetch from 'node-fetch'
import { exec } from 'child_process'
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 { 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 = {
'aix': 'AIX',
@@ -220,6 +222,8 @@ export const generateProfilePicture = async (buffer: Buffer) => {
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 */
export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, info: MessageOptions) {
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
* @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
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
const headers = { Origin: 'https://web.whatsapp.com' }
const fetched = await fetch(messageContent.url, { headers })
const fetched = await fetchRequest(messageContent.url, 'GET')
const buffer = await fetched.buffer()
if (buffer.length <= 10) {

View File

@@ -399,6 +399,13 @@
dependencies:
"@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:
version "4.1.1"
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"
meow "^3.3.0"
debug@4.1.1:
debug@4, debug@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
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"
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:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"