Skip to content

Commit

Permalink
Merge pull request ookamiiixd#9 from ookamiiixd/beta-md
Browse files Browse the repository at this point in the history
merge legacy and md
  • Loading branch information
ookamiiixd authored Feb 15, 2022
2 parents 50679d6 + 6a5d554 commit 5b0a176
Show file tree
Hide file tree
Showing 20 changed files with 7,483 additions and 303 deletions.
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HOST=127.0.0.1
PORT=8000
MAX_RETRIES=5
RECONNECT_INTERVAL=5000
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"no-unused-expressions": ["error", { "allowTernary": true }],
"curly": "error",
"new-cap": "off",
"no-return-assign": "off"
"no-return-assign": "off",
"no-await-in-loop": "off"
},
"plugins": ["prettier"]
}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
node_modules/
package-lock.json
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package.json
package-lock.json
2 changes: 2 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"endOfLine": "lf",
"semi": false,
"singleQuote": true
}
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Baileys API

An implementation of [@adiwajshing/Baileys](https://github.com/adiwajshing/Baileys) as a simple RESTful API service with multiple device support.
An implementation of [@adiwajshing/Baileys](https://github.com/adiwajshing/Baileys) as a simple RESTful API service with multiple device support. This project implement both **Legacy** (Normal WhatsApp Web) and **Beta Multi-Device** client so that you can choose and use one of them easily.

---

Expand All @@ -12,25 +12,50 @@ Note: this branch is intended for the **normal WhatsApp Web** user. If you're us
2. Enter to the project directory.
3. Execute `npm i` to install the dependencies.

## `.env` Configurations

```env
# Listening Host
HOST=127.0.0.1
# Listening Port
PORT=8000
# Maximum Reconnect Attempts
MAX_RETRIES=5
# Reconnect Interval (in Milliseconds)
RECONNECT_INTERVAL=5000
```

## Usage

1. You can start the app by executing `npm run start` or `node .`.
2. Now the endpoint should be available according to your environment variable settings. Default is at `http://localhost:8000`.
2. Now the endpoint should be available according to your environment variable configurations. Default is at `http://localhost:8000`.

## API Docs

The API documentation is available online at [here](https://documenter.getpostman.com/view/18988925/UVRHiNne). You can also import the **Postman Collection File** `(postman_collection.json)` into your Postman App alternatively.
The API documentation is available online [here](https://documenter.getpostman.com/view/18988925/UVeNni36). You can also import the **Postman Collection File** `(postman_collection.json)` into your Postman App alternatively.

The server will respond in JSON format:
The server will respond in following JSON format:

```javascript
{
success: true|false, // bool
message: "", // string
data: {} // object
data: {}|[] // object or array of object
}
```

## Known Issue
- Logging out from your phone manually when the session is still active **will kill the entire app** after a few minutes. As for now you should only destroy a session by using the **delete session endpoint** to avoid this issue. This issue only occurs for **Beta Multi-Device** users.

## Notes
- The app only provide a very simple validation, you may want to implement your own.
- There's no authentication, you may want to implement your own.
- The **Beta Multi-Device** client use provided baileys's `makeInMemoryStore` method which will store your data in memory and a json file, you may want to use a better data management.
- **There's no reading message occured before sending message**. The reading message only occurs when the client received message from someone, it will read them immediately. You should always read messages before starting the app and start sending messages to avoid abnormal detection.

## Notice

This project is intended for learning purpose only, don't use this for spam or any activities that is prohibited by **WhatsApp**.
This project is intended for learning purpose only, don't use it for spamming or any activities that's' prohibited by **WhatsApp**.
8 changes: 6 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'dotenv/config'
import express from 'express'
import nodeCleanup from 'node-cleanup'
import routes from './routes.js'
import { init } from './whatsapp.js'
import { init, cleanup } from './whatsapp.js'

const app = express()
const host = process.env.HOST ?? '127.0.0.1'
const port = process.env.PORT ?? 8000
const port = parseInt(process.env.PORT ?? 8000)

app.use(express.urlencoded({ extended: true }))
app.use(express.json())
Expand All @@ -15,4 +17,6 @@ app.listen(port, host, () => {
console.log(`Server is listening on http://${host}:${port}`)
})

nodeCleanup(cleanup)

export default app
84 changes: 59 additions & 25 deletions controllers/chatController.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,71 @@
import { MessageType } from '@adiwajshing/baileys'
import { getChatList, formatPhone } from './../whatsapp.js'
import { getSession, getChatList, isExists, sendMessage, formatPhone } from './../whatsapp.js'
import response from './../response.js'

const getList = (req, res) => {
const { session } = res.locals

return response(res, 200, true, '', getChatList(session))
return response(res, 200, true, '', getChatList(res.locals.sessionId))
}

const send = (req, res) => {
const { session } = res.locals
const send = async (req, res) => {
const session = getSession(res.locals.sessionId)
const receiver = formatPhone(req.body.receiver)
const { message } = req.body

session
.isOnWhatsApp(receiver)
.then((data) => {
if (!data.exists) {
return response(res, 400, false, 'The receiver number cannot be found.')
try {
const exists = await isExists(session, receiver)

if (!exists) {
return response(res, 400, false, 'The receiver number is not exists.')
}

await sendMessage(session, receiver, { text: message })

response(res, 200, true, 'The message has been successfully sent.')
} catch {
response(res, 500, false, 'Failed to send the message.')
}
}

const sendBulk = async (req, res) => {
const session = getSession(res.locals.sessionId)
const errors = []

for (const [key, data] of req.body.entries()) {
if (!data.receiver || !data.message) {
errors.push(key)

continue
}

data.receiver = formatPhone(data.receiver)

try {
const exists = await isExists(session, data.receiver)

if (!exists) {
errors.push(key)

continue
}

session
.sendMessage(receiver, message, MessageType.text)
.then(() => {
return response(res, 200, true, 'The message has been successfully sent.')
})
.catch(() => {
return response(res, 500, false, 'Failed to send the message.')
})
})
.catch(() => {
return response(res, 500, false, 'Cannot validate receiver number.')
})
await sendMessage(session, data.receiver, { text: data.message })
} catch {
errors.push(key)
}
}

if (errors.length === 0) {
return response(res, 200, true, 'All messages has been successfully sent.')
}

const isAllFailed = errors.length === req.body.length

response(
res,
isAllFailed ? 500 : 200,
!isAllFailed,
isAllFailed ? 'Failed to send all messages.' : 'Some messages has been successfully sent.',
{ errors }
)
}

export { getList, send }
export { getList, send, sendBulk }
39 changes: 26 additions & 13 deletions controllers/getMessages.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import { getSession } from '../whatsapp.js'
import response from './../response.js'

const getMessages = (req, res) => {
const getMessages = async (req, res) => {
const session = getSession(res.locals.sessionId)

/* eslint-disable camelcase */
const { session } = res.locals
const { jid } = req.params
const { limit = 25, cursor_id = null, cursor_fromMe = null } = req.query
const cursor = {
id: cursor_id,
fromMe: cursor_fromMe === null ? cursor_fromMe : cursor_fromMe === 'true',

const cursor = {}

if (cursor_id) {
cursor.before = {
id: cursor_id,
fromMe: Boolean(cursor_fromMe && cursor_fromMe === 'true'),
}
}
/* eslint-enable camelcase */

session
.loadMessages(jid, limit, cursor)
.then((messages) => {
return response(res, 200, true, '', messages)
})
.catch(() => {
return response(res, 500, false, 'Failed to load messages.')
})
try {
let messages
const useCursor = 'before' in cursor ? cursor : null

if (session.isLegacy) {
messages = await session.fetchMessagesFromWA(jid, limit, useCursor)
} else {
messages = await session.store.loadMessages(jid, limit, useCursor)
}

response(res, 200, true, '', messages)
} catch {
response(res, 500, false, 'Failed to load messages.')
}
}

export default getMessages
41 changes: 16 additions & 25 deletions controllers/groupController.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,28 @@
import { MessageType } from '@adiwajshing/baileys'
import { getChatList, formatGroup } from './../whatsapp.js'
import { getSession, getChatList, isExists, sendMessage, formatGroup } from './../whatsapp.js'
import response from './../response.js'

const getList = (req, res) => {
const { session } = res.locals

return response(res, 200, true, '', getChatList(session, true))
return response(res, 200, true, '', getChatList(res.locals.sessionId, true))
}

const send = (req, res) => {
const { session } = res.locals
const send = async (req, res) => {
const session = getSession(res.locals.sessionId)
const receiver = formatGroup(req.body.receiver)
const { message } = req.body

session
.fetchGroupMetadataFromWA(receiver)
.then((data) => {
if (!data.id) {
return response(res, 400, false, 'The group cannot be found.')
}
try {
const exists = await isExists(session, receiver, true)

if (!exists) {
return response(res, 400, false, 'The group is not exists.')
}

await sendMessage(session, receiver, { text: message })

session
.sendMessage(receiver, message, MessageType.text)
.then(() => {
return response(res, 200, true, 'The message has been successfully sent.')
})
.catch(() => {
return response(res, 500, false, 'Failed to send the message.')
})
})
.catch(() => {
return response(res, 500, false, 'Cannot validate group.')
})
response(res, 200, true, 'The message has been successfully sent.')
} catch {
response(res, 500, false, 'Failed to send the message.')
}
}

export { getList, send }
28 changes: 19 additions & 9 deletions controllers/sessionController.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import { isSessionExists, createSession, triggerDeleteSession } from './../whatsapp.js'
import { isSessionExists, createSession, getSession, deleteSession } from './../whatsapp.js'
import response from './../response.js'

const find = (req, res) => {
if (isSessionExists(req.params.id)) {
return response(res, 200, true, 'Session found.')
}

return response(res, 404, false, 'Session not found.')
response(res, 404, false, 'Session not found.')
}

const add = (req, res) => {
const sessionId = req.body.id
const { id, isLegacy } = req.body

if (isSessionExists(sessionId)) {
return response(res, 409, false, 'Session already exists, please use other id.')
if (isSessionExists(id)) {
return response(res, 409, false, 'Session already exists, please use another id.')
}

createSession(sessionId, res)
createSession(id, isLegacy === 'true', res)
}

const del = (req, res) => {
triggerDeleteSession(req.params.id)
const del = async (req, res) => {
const { id } = req.params
const session = getSession(id)

if (session) {
try {
await session.logout()
} catch {
} finally {
deleteSession(id, session.isLegacy)
}
}

return response(res, 200, true, 'The session has been successfully deleted.')
response(res, 200, true, 'The session has been successfully deleted.')
}

export { find, add, del }
6 changes: 6 additions & 0 deletions dirname.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { dirname } from 'path'
import { fileURLToPath } from 'url'

const __dirname = dirname(fileURLToPath(import.meta.url))

export default __dirname
8 changes: 4 additions & 4 deletions middleware/sessionValidator.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { getSession } from '../whatsapp.js'
import { isSessionExists } from '../whatsapp.js'
import response from './../response.js'

const validate = (req, res, next) => {
const session = getSession(req.query.id)
const sessionId = req.query.id

if (!session) {
if (!isSessionExists(sessionId)) {
return response(res, 404, false, 'Session not found.')
}

res.locals.session = session
res.locals.sessionId = sessionId
next()
}

Expand Down
Loading

0 comments on commit 5b0a176

Please sign in to comment.