diff --git a/.gitignore b/.gitignore index 611c2c4..7baf07c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ output.csv package-lock.json */.DS_Store .DS_Store +.env diff --git a/Binary/Encoder.ts b/Binary/Encoder.ts index b365fca..fdaf25f 100644 --- a/Binary/Encoder.ts +++ b/Binary/Encoder.ts @@ -17,7 +17,7 @@ export default class Encoder { this.pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff]) } pushBytes(bytes: Uint8Array | Array) { - this.data.push(...bytes) + this.data.push.apply(this.data, bytes) } pushString(str: string) { const bytes = new TextEncoder().encode(str) diff --git a/Example/example.ts b/Example/example.ts index 93fe9db..93dc95e 100644 --- a/Example/example.ts +++ b/Example/example.ts @@ -1,53 +1,40 @@ import { WAClient, - AuthenticationCredentialsBase64, getNotificationType, MessageType, decodeMediaMessage, Presence, MessageOptions, Mimetype, + WALocationMessage, + MessageLogLevel, } from '../WAClient/WAClient' -import fs from 'fs' +import * as fs from 'fs' async function example() { const client = new WAClient() // instantiate client.autoReconnect = true // auto reconnect on disconnect - client.logUnhandledMessages = false // set to true to see what kind of stuff you can implement + client.logLevel = MessageLogLevel.none // set to unhandled to see what kind of stuff you can implement - let authInfo: AuthenticationCredentialsBase64 = null - try { - const file = fs.readFileSync('auth_info.json') // load a closed session back if it exists - authInfo = JSON.parse(file) - } catch {} - - // connect or timeout in 20 seconds - const [user, chats, contacts, unread] = await client.connect(authInfo, 20 * 1000) + // connect or timeout in 20 seconds (loads the auth file credentials if present) + const [user, chats, contacts, unread] = await client.connect('./auth_info.json', 20 * 1000) console.log('oh hello ' + user.name + ' (' + user.id + ')') console.log('you have ' + unread.length + ' unread messages') console.log('you have ' + chats.length + ' chats & ' + contacts.length + ' contacts') - authInfo = client.base64EncodedAuthInfo() // get all the auth info we need to restore this session + const authInfo = client.base64EncodedAuthInfo() // get all the auth info we need to restore this session fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file /* Note: one can take this auth_info.json file and login again from any computer without having to scan the QR code, and get full access to one's WhatsApp. Despite the convenience, be careful with this file */ - client.setOnPresenceUpdate((json) => console.log(json.id + ' presence is ' + json.type)) - client.setOnMessageStatusChange((json) => { + client.setOnPresenceUpdate(json => console.log(json.id + ' presence is ' + json.type)) + client.setOnMessageStatusChange(json => { const participant = json.participant ? ' (' + json.participant + ')' : '' // participant exists when the message is from a group - console.log( - json.to + - participant + - ' acknowledged message(s) ' + - json.ids + - ' as ' + - json.type + - ' at ' + - json.timestamp, - ) + console.log(`${json.to}${participant} acknlowledged message(s) ${json.ids} as ${json.type}`) }) - client.setOnUnreadMessage(async (m) => { + // set to false to NOT relay your own sent messages + client.setOnUnreadMessage(true, async (m) => { const [notificationType, messageType] = getNotificationType(m) // get what type of notification it is -- message, group add notification etc. console.log('got notification of type: ' + notificationType) @@ -74,32 +61,20 @@ async function example() { const contact = m.message.contactMessage console.log(sender + ' sent contact (' + contact.displayName + '): ' + contact.vcard) } else if (messageType === MessageType.location || messageType === MessageType.liveLocation) { - const locMessage = m.message[messageType] - console.log( - sender + - ' sent location (lat: ' + - locMessage.degreesLatitude + - ', long: ' + - locMessage.degreesLongitude + - '), saving thumbnail...', - ) - decodeMediaMessage(m.message, 'loc_thumb_in_' + m.key.id) + const locMessage = m.message[messageType] as WALocationMessage + console.log(`${sender} sent location (lat: ${locMessage.degreesLatitude}, long: ${locMessage.degreesLongitude})`) + + decodeMediaMessage(m.message, './Media/loc_thumb_in_' + m.key.id) // save location thumbnail if (messageType === MessageType.liveLocation) { - console.log( - sender + - ' sent live location for duration: ' + - m.duration / 60 + - ' minutes, seq number: ' + - locMessage.sequenceNumber, - ) + console.log(`${sender} sent live location for duration: ${m.duration/60}`) } } else { // if it is a media (audio, image, video) message // decode, decrypt & save the media. // The extension to the is applied automatically based on the media type try { - const savedFile = await decodeMediaMessage(m.message, 'media_in_' + m.key.id) + const savedFile = await decodeMediaMessage(m.message, './Media/media_in_' + m.key.id) console.log(sender + ' sent media, saved at: ' + savedFile) } catch (err) { console.log('error in decoding message: ' + err) @@ -124,21 +99,22 @@ async function example() { content = { degreesLatitude: 32.123123, degreesLongitude: 12.12123123 } type = MessageType.location } else { - content = fs.readFileSync('example/ma_gif.mp4') // load the gif + content = fs.readFileSync('./Media/ma_gif.mp4') // load the gif options.mimetype = Mimetype.gif + type = MessageType.video } const response = await client.sendMessage(m.key.remoteJid, content, type, options) console.log("sent message with ID '" + response.messageID + "' successfully: " + (response.status === 200)) }, 3 * 1000) - }, true) // set to false to not relay your own sent messages + }) /* example of custom functionality for tracking battery */ - client.registerCallback(['action', null, 'battery'], (json) => { + client.registerCallback(['action', null, 'battery'], json => { const batteryLevelStr = json[2][0][1].value const batterylevel = parseInt(batteryLevelStr) console.log('battery level: ' + batterylevel) }) - client.setOnUnexpectedDisconnect((err) => console.log('disconnected unexpectedly: ' + err)) + client.setOnUnexpectedDisconnect(err => console.log('disconnected unexpectedly: ' + err)) } example().catch((err) => console.log(`encountered error: ${err}`)) diff --git a/Media/.gitkeep b/Media/.gitkeep new file mode 100644 index 0000000..14158e0 --- /dev/null +++ b/Media/.gitkeep @@ -0,0 +1,2 @@ +ma_gif.mp4 +meme.jpeg \ No newline at end of file diff --git a/WAClient/Base.ts b/WAClient/Base.ts index 8eabd4b..7880ba5 100644 --- a/WAClient/Base.ts +++ b/WAClient/Base.ts @@ -8,6 +8,7 @@ import { WAGroupCreateResponse, WAGroupMetadata, WAGroupModification, + MessageLogLevel, } from '../WAConnection/Constants' import { generateMessageTag } from '../WAConnection/Utils' @@ -51,7 +52,7 @@ export default class WhatsAppWebBase extends WAConnection { if (!message.key.fromMe || callbackOnMyMessages) { // if this message was sent to us, notify callback(message as WAMessage) - } else if (this.logUnhandledMessages) { + } else if (this.logLevel >= MessageLogLevel.unhandled) { this.log(`[Unhandled] message - ${JSON.stringify(message)}`) } }) diff --git a/WAClient/Tests.ts b/WAClient/Tests.ts index 51dfa06..194809a 100644 --- a/WAClient/Tests.ts +++ b/WAClient/Tests.ts @@ -1,24 +1,27 @@ import { WAClient } from './WAClient' import { MessageType, MessageOptions, Mimetype, Presence } from './Constants' -import fs from 'fs' +import * as fs from 'fs' +import * as assert from 'assert' -import assert from 'assert' import { decodeMediaMessage } from './Utils' import { promiseTimeout } from '../WAConnection/Utils' -const testJid = '919646328797@s.whatsapp.net' -const createTimeout = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout)) +require ('dotenv').config () // dotenv to load test jid +const testJid = process.env.TEST_JID // set TEST_JID=xyz@s.whatsapp.net in a .env file in the root directory + +const createTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout)) async function sendAndRetreiveMessage(client: WAClient, content, type: MessageType, options: MessageOptions = {}) { const response = await client.sendMessage(testJid, content, type, options) - assert.equal(response.status, 200) + assert.strictEqual(response.status, 200) const messages = await client.loadConversation(testJid, 1, null, true) - assert.equal(messages[0].key.id, response.messageID) + assert.strictEqual(messages[0].key.id, response.messageID) return messages[0] } function WAClientTest(name: string, func: (client: WAClient) => void) { describe(name, () => { const client = new WAClient() + console.log (`test jid: ${testJid}`) before(async () => { const file = './auth_info.json' await client.connectSlim(file) @@ -31,14 +34,14 @@ function WAClientTest(name: string, func: (client: WAClient) => void) { WAClientTest('Messages', (client) => { it('should send a text message', async () => { const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text) - assert.equal(message.message.conversation, 'hello fren') + assert.strictEqual(message.message.conversation, 'hello fren') }) it('should quote a message', async () => { const messages = await client.loadConversation(testJid, 2) const message = await sendAndRetreiveMessage(client, 'hello fren 2', MessageType.extendedText, { quoted: messages[0], }) - assert.equal(message.message.extendedTextMessage.contextInfo.stanzaId, messages[0].key.id) + assert.strictEqual(message.message.extendedTextMessage.contextInfo.stanzaId, messages[0].key.id) }) it('should send a gif', async () => { const content = fs.readFileSync('./Media/ma_gif.mp4') @@ -56,7 +59,7 @@ WAClientTest('Messages', (client) => { const content = fs.readFileSync('./Media/meme.jpeg') const message = await sendAndRetreiveMessage(client, content, MessageType.image, { quoted: messages[0] }) const file = await decodeMediaMessage(message.message, './Media/received_img') - assert.equal(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id) + assert.strictEqual(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id) }) }) WAClientTest('Presence', (client) => { @@ -64,7 +67,7 @@ WAClientTest('Presence', (client) => { const presences = Object.values(Presence) for (const i in presences) { const response = await client.updatePresence(testJid, presences[i]) - assert.equal(response.status, 200) + assert.strictEqual(response.status, 200) await createTimeout(1500) } @@ -73,15 +76,15 @@ WAClientTest('Presence', (client) => { WAClientTest('Misc', (client) => { it('should tell if someone has an account on WhatsApp', async () => { const response = await client.isOnWhatsApp(testJid) - assert.equal(response, true) + assert.strictEqual(response, true) const responseFail = await client.isOnWhatsApp('abcd@s.whatsapp.net') - assert.equal(responseFail, false) + assert.strictEqual(responseFail, false) }) it('should return the status', async () => { const response = await client.getStatus(testJid) assert.ok(response.status) - assert.equal(typeof response.status, 'string') + assert.strictEqual(typeof response.status, 'string') }) it('should return the profile picture', async () => { const response = await client.getProfilePicture(testJid) @@ -93,38 +96,38 @@ WAClientTest('Groups', (client) => { let gid: string it('should create a group', async () => { const response = await client.groupCreate('Cool Test Group', [testJid]) - assert.equal(response.status, 200) + assert.strictEqual(response.status, 200) gid = response.gid console.log('created group: ' + gid) }) it('should retreive group invite code', async () => { const code = await client.groupInviteCode(gid) assert.ok(code) - assert.equal(typeof code, 'string') + assert.strictEqual(typeof code, 'string') }) it('should retreive group metadata', async () => { const metadata = await client.groupMetadata(gid) - assert.equal(metadata.id, gid) - assert.equal(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1) + assert.strictEqual(metadata.id, gid) + assert.strictEqual(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1) }) it('should send a message on the group', async () => { const r = await client.sendMessage(gid, 'hello', MessageType.text) - assert.equal(r.status, 200) + assert.strictEqual(r.status, 200) }) it('should update the subject', async () => { const subject = 'V Cool Title' const r = await client.groupUpdateSubject(gid, subject) - assert.equal(r.status, 200) + assert.strictEqual(r.status, 200) const metadata = await client.groupMetadata(gid) - assert.equal(metadata.subject, subject) + assert.strictEqual(metadata.subject, subject) }) it('should remove someone from a group', async () => { await client.groupRemove(gid, [testJid]) }) it('should leave the group', async () => { const response = await client.groupLeave(gid) - assert.equal(response.status, 200) + assert.strictEqual(response.status, 200) }) }) WAClientTest('Events', (client) => { @@ -146,7 +149,12 @@ WAClientTest('Events', (client) => { console.log (presence) }) const response = await client.requestPresenceUpdate (client.userMetaData) - assert.equal (response.status, 200) + assert.strictEqual (response.status, 200) await createTimeout (25000) })*/ }) +/*WAClientTest ('Testz', client => { + it ('should work', async () => { + + }) +})*/ \ No newline at end of file diff --git a/WAClient/Utils.ts b/WAClient/Utils.ts index 9dc6d1d..fedccfd 100644 --- a/WAClient/Utils.ts +++ b/WAClient/Utils.ts @@ -1,6 +1,6 @@ import { MessageType, HKDFInfoKeys, MessageOptions, MessageStubTypes } from './Constants' -import sharp from 'sharp' -import fs from 'fs' +import * as sharp from 'sharp' +import * as fs from 'fs' import fetch from 'node-fetch' import { WAMessage, WAMessageContent } from '../WAConnection/Constants' import { hmacSign, aesDecryptWithIV, hkdf } from '../WAConnection/Utils' diff --git a/WAConnection/Base.ts b/WAConnection/Base.ts index 5a70c3e..46b6d8a 100644 --- a/WAConnection/Base.ts +++ b/WAConnection/Base.ts @@ -1,10 +1,11 @@ -import WS from 'ws' -import QR from 'qrcode-terminal' -import fs from 'fs' +import * as QR from 'qrcode-terminal' +import * as fs from 'fs' +import * as WS from 'ws' import * as Utils from './Utils' import Encoder from '../Binary/Encoder' import Decoder from '../Binary/Decoder' -import { AuthenticationCredentials, UserMetaData, WANode, AuthenticationCredentialsBase64, WATag } from './Constants' +import { AuthenticationCredentials, UserMetaData, WANode, AuthenticationCredentialsBase64, WATag, MessageLogLevel } from './Constants' + /** Generate a QR code from the ref & the curve public key. This is scanned by the phone */ const generateQRCode = function ([ref, publicKey, clientID]) { @@ -23,7 +24,7 @@ export default class WAConnectionBase { autoReconnect = true lastSeen: Date = null /** Log messages that are not handled, so you can debug & see what custom stuff you can implement */ - logUnhandledMessages = false + logLevel: MessageLogLevel = MessageLogLevel.none /** Data structure of tokens & IDs used to establish one's identiy to WhatsApp Web */ protected authInfo: AuthenticationCredentials = { clientID: null, @@ -259,6 +260,6 @@ export default class WAConnectionBase { } } protected log(text) { - console.log(`[Baileys] ${text}`) + console.log(`[Baileys][${new Date().toLocaleString()}] ${text}`) } } diff --git a/WAConnection/Connect.ts b/WAConnection/Connect.ts index 031779a..899c1b7 100644 --- a/WAConnection/Connect.ts +++ b/WAConnection/Connect.ts @@ -1,7 +1,6 @@ -import WS from 'ws' - +import * as WS from 'ws' import * as Utils from './Utils' -import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact } from './Constants' +import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact, MessageLogLevel } from './Constants' import WAConnectionValidator from './Validation' export default class WAConnectionConnector extends WAConnectionValidator { @@ -13,7 +12,7 @@ export default class WAConnectionConnector extends WAConnectionValidator { */ async connect(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) { const userInfo = await this.connectSlim(authInfo, timeoutMs) - const chats = await this.receiveChatsAndContacts() + const chats = await this.receiveChatsAndContacts(timeoutMs) return [userInfo, ...chats] as [UserMetaData, WAChat[], WAContact[], WAMessage[]] } /** @@ -31,7 +30,7 @@ export default class WAConnectionConnector extends WAConnectionValidator { try { this.loadAuthInfoFromBase64(authInfo) } catch {} - + this.conn = new WS('wss://web.whatsapp.com/ws', null, { origin: 'https://web.whatsapp.com' }) let promise: Promise = new Promise((resolve, reject) => { @@ -52,8 +51,8 @@ export default class WAConnectionConnector extends WAConnectionValidator { reject(error) }) }) - promise = timeoutMs ? Utils.promiseTimeout(timeoutMs, promise) : promise - return promise.catch((err) => { + promise = Utils.promiseTimeout(timeoutMs, promise) + return promise.catch(err => { this.close() throw err }) @@ -61,25 +60,31 @@ export default class WAConnectionConnector extends WAConnectionValidator { /** * Sets up callbacks to receive chats, contacts & unread messages. * Must be called immediately after connect - * [chats, contacts, unreadMessages] + * @returns [chats, contacts, unreadMessages] */ - receiveChatsAndContacts() { - const chats: Array = [] + async receiveChatsAndContacts(timeoutMs: number = null) { + let chats: Array = [] let contacts: Array = [] - const unreadMessages: Array = [] - const unreadMap = {} + let unreadMessages: Array = [] + let unreadMap: Record = {} - let encounteredAddBefore = false + let receivedContacts = false + let receivedMessages = false let convoResolve this.log('waiting for chats & contacts') // wait for the message with chats const waitForConvos = () => - new Promise((resolve, _) => { - convoResolve = resolve - const chatUpdate = (json) => { + new Promise(resolve => { + convoResolve = () => { + // de-register the callbacks, so that they don't get called again + this.deregisterCallback(['action', 'add:last']) + this.deregisterCallback(['action', 'add:before']) + this.deregisterCallback(['action', 'add:unread']) + resolve() + } + const chatUpdate = json => { + receivedMessages = true const isLast = json[1].last - encounteredAddBefore = json[1].add === 'before' ? true : encounteredAddBefore - json = json[2] if (json) { for (let k = json.length - 1; k >= 0; k--) { @@ -92,12 +97,8 @@ export default class WAConnectionConnector extends WAConnectionValidator { } } } - if (isLast) { - // de-register the callbacks, so that they don't get called again - this.deregisterCallback(['action', 'add:last']) - this.deregisterCallback(['action', 'add:before']) - this.deregisterCallback(['action', 'add:unread']) - resolve() + if (isLast && receivedContacts) { // if received contacts before messages + convoResolve () } } // wait for actual messages to load, "last" is the most recent message, "before" contains prior messages @@ -105,22 +106,27 @@ export default class WAConnectionConnector extends WAConnectionValidator { this.registerCallback(['action', 'add:before'], chatUpdate) this.registerCallback(['action', 'add:unread'], chatUpdate) }) - const waitForChats = this.registerCallbackOneTime(['response', 'type:chat']).then((json) => { - json[2].forEach((chat) => { + const waitForChats = async () => { + const json = await this.registerCallbackOneTime(['response', 'type:chat']) + json[2].forEach(chat => { chats.push(chat[1]) // chats data (log json to see what it looks like) // store the number of unread messages for each sender unreadMap[chat[1].jid] = chat[1].count }) - if (chats && chats.length > 0) return waitForConvos() - }) - const waitForContacts = this.registerCallbackOneTime(['response', 'type:contacts']).then((json) => { - contacts = json[2].map((item) => item[1]) - // if no add:before messages are sent, and you receive contacts + if (chats.length > 0) return waitForConvos() + } + const waitForContacts = async () => { + const json = await this.registerCallbackOneTime(['response', 'type:contacts']) + contacts = json[2].map(item => item[1]) + receivedContacts = true + // if you receive contacts after messages // should probably resolve the promise - if (!encounteredAddBefore && convoResolve) convoResolve() - }) + if (receivedMessages) convoResolve() + } // wait for the chats & contacts to load - return Promise.all([waitForChats, waitForContacts]).then(() => [chats, contacts, unreadMessages]) + const promise = Promise.all([waitForChats(), waitForContacts()]) + await Utils.promiseTimeout (timeoutMs, promise) + return [chats, contacts, unreadMessages] as [WAChat[], WAContact[], WAMessage[]] } private onMessageRecieved(message) { if (message[0] === '!') { @@ -170,6 +176,9 @@ export default class WAConnectionConnector extends WAConnectionValidator { // if we recieved a message that was encrypted but we don't have the keys, then there must be an error throw [3, 'recieved encrypted message when auth creds not available', message] } + if (this.logLevel === MessageLogLevel.all) { + this.log(messageTag + ', ' + JSON.stringify(json)) + } /* Check if this is a response to a message we sent */ @@ -214,7 +223,7 @@ export default class WAConnectionConnector extends WAConnectionValidator { return } } - if (this.logUnhandledMessages) { + if (this.logLevel === MessageLogLevel.unhandled) { this.log('[Unhandled] ' + messageTag + ', ' + JSON.stringify(json)) } } diff --git a/WAConnection/Constants.ts b/WAConnection/Constants.ts index 336c883..f3cd5e0 100644 --- a/WAConnection/Constants.ts +++ b/WAConnection/Constants.ts @@ -1,6 +1,11 @@ import { WA } from '../Binary/Constants' import { proto } from '../Binary/WAMessage' +export enum MessageLogLevel { + none=0, + unhandled=1, + all=2 +} export interface AuthenticationCredentials { clientID: string serverToken: string diff --git a/WAConnection/Tests.ts b/WAConnection/Tests.ts index 3c23359..d16d4b8 100644 --- a/WAConnection/Tests.ts +++ b/WAConnection/Tests.ts @@ -1,4 +1,4 @@ -import assert from 'assert' +import * as assert from 'assert' import WAConnection from './WAConnection' import { AuthenticationCredentialsBase64 } from './Constants' @@ -22,17 +22,16 @@ describe('Test Connect', () => { it('should connect', async () => { console.log('please be ready to scan with your phone') const conn = new WAConnection() - await assert.doesNotReject(async () => conn.connectSlim(null), 'initial connection failed') - assert.ok(conn.userMetaData) - assert.ok(conn.userMetaData.id) + const user = await conn.connectSlim(null) + assert.ok(user) + assert.ok(user.id) conn.close() auth = conn.base64EncodedAuthInfo() }) it('should reconnect', async () => { const conn = new WAConnection() - - const [user, chats, contacts, unread] = await conn.connect('./auth_info.json', 20 * 1000) + const [user, chats, contacts, unread] = await conn.connect(auth, 20*1000) assert.ok(user) assert.ok(user.id) @@ -55,4 +54,4 @@ describe('Test Connect', () => { await assert.rejects(async () => conn.connectSlim(auth), 'reconnect should have failed') }) -}) +}) \ No newline at end of file diff --git a/WAConnection/Utils.ts b/WAConnection/Utils.ts index c6f4c08..6ece841 100644 --- a/WAConnection/Utils.ts +++ b/WAConnection/Utils.ts @@ -1,5 +1,5 @@ -import Crypto from 'crypto' -import HKDF from 'futoin-hkdf' +import * as Crypto from 'crypto' +import * as HKDF from 'futoin-hkdf' /** decrypt AES 256 CBC; where the IV is prefixed to the buffer */ @@ -38,6 +38,7 @@ export function randomBytes(length) { return Crypto.randomBytes(length) } export function promiseTimeout(ms: number, promise: Promise) { + if (!ms) { return promise } // Create a promise that rejects in milliseconds const timeout = new Promise((_, reject) => { const id = setTimeout(() => { diff --git a/package-lock.json b/package-lock.json index eaf43ae..dda86a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -140,6 +140,15 @@ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", "dev": true }, + "@types/ws": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz", + "integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.5.0.tgz", @@ -722,6 +731,12 @@ "esutils": "^2.0.2" } }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true + }, "dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", diff --git a/package.json b/package.json index 0c921e2..1502b24 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint": "eslint '*/*.ts' --quiet --fix", "tsc": "tsc", "dev": "ts-node-dev --respawn --transpileOnly ./app/app.ts", - "prod": "tsc && node ./build/app.js" + "example": "npx ts-node Example/example.ts" }, "author": "Adhiraj Singh", "license": "MIT", @@ -40,9 +40,11 @@ "@types/mocha": "^7.0.2", "@types/node": "^14.0.14", "@types/sharp": "^0.25.0", + "@types/ws": "^7.2.6", "@typescript-eslint/eslint-plugin": "^3.5.0", "@typescript-eslint/parser": "^3.5.0", "assert": "^2.0.0", + "dotenv": "^8.2.0", "eslint": "^7.3.1", "eslint-config-prettier": "^6.11.0", "eslint-plugin-prettier": "^3.1.4",