├── helper └── formatPhone.js ├── package.json ├── model └── index.js ├── LICENSE ├── readme.md ├── README.md ├── log.html ├── server.js └── apps.html /helper/formatPhone.js: -------------------------------------------------------------------------------- 1 | const phoneNumberFormatter = function(number) { 2 | // 1. Menghilangkan karakter selain angka 3 | let formatted = number.replace(/\D/g, ''); 4 | 5 | // 2. Menghilangkan angka 0 di depan (prefix) 6 | // Kemudian diganti dengan 62 7 | if (formatted.startsWith('0')) { 8 | formatted = '62' + formatted.substr(1); 9 | } 10 | 11 | if (!formatted.endsWith('@c.us')) { 12 | formatted += '@c.us'; 13 | } 14 | 15 | return formatted; 16 | } 17 | 18 | module.exports = { 19 | phoneNumberFormatter 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@supabase/supabase-js": "^1.32.2", 4 | "body-parser": "^1.19.2", 5 | "cookie-parser": "^1.4.6", 6 | "dotenv": "^16.0.0", 7 | "express": "^4.17.3", 8 | "express-session": "^1.17.2", 9 | "express-validator": "^6.14.0", 10 | "http": "^0.0.1-security", 11 | "nodemon": "^2.0.15", 12 | "qrcode": "^1.5.0", 13 | "socket.io": "^4.4.1", 14 | "whatsapp-web.js": "^1.16.5" 15 | }, 16 | "scripts": { 17 | "start": "nodemon server.js" 18 | }, 19 | "name": "wa-apijs", 20 | "version": "1.0.0", 21 | "main": "server.js", 22 | "keywords": [ 23 | "whatsapp-web.js", 24 | "wa-apijs", 25 | "OPT whatsapp" 26 | ], 27 | "author": "Bagus Tilas H", 28 | "license": "ISC", 29 | "description": "aplikasi untuk memudahkan membuat otp via whatsapp" 30 | } 31 | -------------------------------------------------------------------------------- /model/index.js: -------------------------------------------------------------------------------- 1 | const { createClient } = require("@supabase/supabase-js"); 2 | require("dotenv").config(); 3 | 4 | const SUPABASE_URL = process.env.API_URL; 5 | const SUPABASE_KEY = process.env.API_KEY; 6 | const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); 7 | 8 | const getUser = async (user) => { 9 | const { data, err } = await supabase 10 | .from("api_users") 11 | .select("api_key,created_at") 12 | .eq("nohp", user); 13 | return data; 14 | }; 15 | 16 | const UpdateUser = async (api, user) => { 17 | const { data, error } = await supabase 18 | .from("api_users") 19 | .update({ api_key: api }) 20 | .eq("nohp", user); 21 | }; 22 | 23 | const cekApiKey = async (key) => { 24 | const { data, err } = await supabase 25 | .from("api_users") 26 | .select("api_key,created_at") 27 | .eq("api_key", key); 28 | return data; 29 | }; 30 | 31 | module.exports = { 32 | getUser, 33 | UpdateUser, 34 | cekApiKey 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DenBagusTilas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | OTP-WhatsApp V.1.0 2 | 3 | Tools ini di buat untuk memudahkan mengirim otp via whatsapp menggunakan API nodejs (whatsapp-web.js 4 | ),sudah di lengkapi dengan API-KEY yang di generate di backend dan disimpan di 5 | supabase sebagi databse untuk menampung dan menyimpan api key yang di gunakan 6 | 7 | bisa di jalankan di HEROKU 8 | 9 | ******| HEROKU |****** 10 | 11 | - Deploy Apikasi ke heroku 12 | - Tambahkan puppeter di build pack https://buildpack-registry.s3.amazonaws.com/buildpacks/jontewks/puppeteer.tgz 13 | - build empty commit git commit --allow-empty -am "Redeploy to heroku: add buildpack for puppeter" 14 | 15 | ******| SUPABSE |****** 16 | 17 | - Edit .env file di folder root 18 | API_URL = "YOUR_API_URL_SUPABASE" 19 | API_KEY = "YOUR_API_KEY_SUPABSE" 20 | 21 | 22 | ******| DOCS |****** 23 | 24 | - Send Message tanpa api key 25 | POST http://localhost:3000/api/v1/send-msg HTTP/1.1 26 | Content-Type: application/json 27 | 28 | { 29 | "number": "088xxxxxxxx", 30 | "message": "test otp api v1" 31 | } 32 | 33 | - send Message dengan api key 34 | POST http://localhost:3000/api/v2/send-msg HTTP/1.1 35 | Content-Type: application/json 36 | 37 | { 38 | "number": "088xxxxxxxx", 39 | "message": "test otp api v2", 40 | "api_key": "1e857623-52a7-47f8-b3f3-161802e3bd42" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OTP-WhatsApp V.1.0 2 | 3 | Tools ini di buat untuk memudahkan mengirim otp via whatsapp menggunakan API nodejs (whatsapp-web.js 4 | ),sudah di lengkapi dengan API-KEY yang di generate di backend dan disimpan di 5 | supabase sebagi databse untuk menampung dan menyimpan api key yang di gunakan 6 | 7 | Tools yang harus disiapkan : 8 | - nodejs, git, supabse, express.js, 9 | 10 | bisa di jalankan di HEROKU 11 | ---------------------------------------------------------------------------------------------------- 12 | demo : OTP_WhatsApp 13 | 14 | ******| HEROKU |****** 15 | 16 | - Deploy Apikasi ke heroku 17 | - Tambahkan puppeter di build pack https://buildpack-registry.s3.amazonaws.com/buildpacks/jontewks/puppeteer.tgz 18 | - build empty commit git commit --allow-empty -am "Redeploy to heroku: add buildpack for puppeter" 19 | 20 | ******| LOCAL |****** 21 | - Donwload Repo ini 22 | - Masuk keterminal cd /path/OTP-WhatsApp 23 | - Ketikan `npm install` 24 | - Buka Browser `localhost:3000` 25 | 26 | ******| SUPABSE |****** 27 | 28 | Buat Database https://supabase.com/ 29 | 30 | `create table api_users ( 31 | id bigint generated by default as identity primary key, 32 | nohp varchar null, 33 | api_key varchar null, 34 | inserted_at timestamp with time zone default timezone('utc'::text, now()) not null, 35 | updated_at timestamp with time zone default timezone('utc'::text, now()) not null, 36 | data jsonb, 37 | name text 38 | );` 39 | 40 | Buat file .env di root folder 41 | 42 | `API_URL = "YOUR_API_URL_SUPABASE" 43 | API_KEY = "YOUR_API_KEY_SUPABSE"` 44 | 45 | 46 | ******| SEND MESSAGE |****** 47 | 48 | - Send Message tanpa api key 49 | POST http://localhost:3000/api/v1/send-msg HTTP/1.1 50 | Content-Type: application/json 51 | { 52 | "number": "088xxxxxxxx", 53 | "message": "test otp api v1" 54 | }` 55 | 56 | - send Message dengan api key 57 | POST http://localhost:3000/api/v2/send-msg HTTP/1.1 58 | Content-Type: application/json 59 | { 60 | "number": "088xxxxxxxx", 61 | "message": "test otp api v2", 62 | "api_key": "1e857623-52a7-47f8-b3f3-161802e3bd42" 63 | } 64 | 65 | -------------------------------------------------------------------------------- /log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OTP WHASTAPP 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 | 36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 72 | 73 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const { Client, LocalAuth } = require("whatsapp-web.js"); 2 | const qrcode = require("qrcode"); 3 | const express = require("express"); 4 | const sessions = require("express-session"); 5 | const cookieParser = require("cookie-parser"); 6 | const socketIO = require("socket.io"); 7 | const http = require("http"); 8 | const bodyParser = require("body-parser"); 9 | const path = require("path"); 10 | 11 | const { body, validationResult } = require("express-validator"); 12 | const { phoneNumberFormatter } = require("./helper/formatPhone.js"); 13 | const { getUser, UpdateUser, cekApiKey } = require("./model/index"); 14 | 15 | //server connection 16 | const app = express(); 17 | const server = http.createServer(app); 18 | const port = process.env.PORT || 3000; 19 | const io = socketIO(server, { 20 | cors: { 21 | origin: "http://localhost:3000", 22 | methods: ["GET", "POST"], 23 | credentials: true, 24 | transports: ["websocket", "polling"], 25 | }, 26 | allowEIO3: true, 27 | }); 28 | 29 | const oneDay = 1000 * 60 * 60 * 24; 30 | const username = "8888"; 31 | const passowrd = "otpwa" 32 | var session; 33 | 34 | app.use( 35 | sessions({ 36 | secret: "thisismysecrctekeyfhrgfgrfrty84fwir767", 37 | saveUninitialized: true, 38 | cookie: { maxAge: oneDay }, 39 | resave: false, 40 | }) 41 | ); 42 | 43 | app.use(cookieParser()); 44 | app.use(express.json()); 45 | app.use(bodyParser.json()); 46 | app.use( 47 | express.urlencoded({ 48 | extended: true, 49 | }) 50 | ); 51 | app.use(express.static(path.join(__dirname, "static"))); 52 | 53 | app.get("/", (req, res) => { 54 | res.sendFile("log.html", { 55 | root: __dirname, 56 | }); 57 | }); 58 | 59 | app.post("/auth", (req, res) => { 60 | let user = req.body.username; 61 | let pass = req.body.password; 62 | 63 | if (user == username && pass == password) { 64 | req.session.waconect == true ? (cw = req.session.waconect) : (cw = false); 65 | 66 | session = req.session; 67 | session.userid = user; 68 | session.login = true; 69 | session.waconect = cw; 70 | console.log(req.session); 71 | 72 | res.json({ 73 | status: 200, 74 | login: true, 75 | users: session.userid, 76 | }); 77 | 78 | } else { 79 | res.json({ 80 | status: 500, 81 | login: false, 82 | }); 83 | } 84 | }); 85 | 86 | app.get("/logout", (req, res) => { 87 | req.session.destroy((err) => { 88 | if (err) { 89 | return console.log(err); 90 | } 91 | res.redirect("/"); 92 | }); 93 | }); 94 | 95 | app.get("/apps", (req, res) => { 96 | if (req.session.login == true) { 97 | res.sendFile("apps.html", { 98 | root: __dirname, 99 | }); 100 | } else { 101 | res.redirect("/"); 102 | } 103 | }); 104 | 105 | app.post("/connect", (req, res) => { 106 | session = req.session; 107 | session.waconect = true; 108 | console.log(req.session); 109 | 110 | res.json({ 111 | status: 200, 112 | waconect: true, 113 | }); 114 | }); 115 | 116 | app.post("/generet", (req, res) => { 117 | const apikey = req.body.message; 118 | const user = req.body.user; 119 | 120 | UpdateUser(apikey, user) 121 | .then((response) => { 122 | res.json({ 123 | status: true, 124 | response: response, 125 | }); 126 | }) 127 | .catch((err) => { 128 | res.status(500).json({ 129 | status: false, 130 | response: err, 131 | }); 132 | }); 133 | }); 134 | 135 | app.post("/disconect", (req, res) => { 136 | session = req.session; 137 | session.waconect = false; 138 | console.log(req.session); 139 | 140 | res.json({ 141 | status: 200, 142 | waconect: false, 143 | }); 144 | }); 145 | 146 | const client = new Client({ 147 | restartOnAuthFail: true, 148 | puppeteer: { 149 | headless: true, 150 | args: [ 151 | "--no-sandbox", 152 | "--disable-setuid-sandbox", 153 | "--disable-dev-shm-usage", 154 | "--disable-accelerated-2d-canvas", 155 | "--no-first-run", 156 | "--no-zygote", 157 | "--single-process", // <- this one doesn't works in Windows 158 | "--disable-gpu", 159 | ], 160 | }, 161 | authStrategy: new LocalAuth(), 162 | }); 163 | 164 | client.on("message", (message) => { 165 | console.log(message.body); 166 | }); 167 | 168 | client.initialize(); 169 | 170 | // Socket IO 171 | io.on("connection", function (socket) { 172 | socket.on("logout", () => { 173 | socket.emit("message", "Logout Success, Lets Scan Again"); 174 | client.logout(); 175 | }); 176 | 177 | client.on("ready", () => { 178 | console.log("Client is ready!"); 179 | socket.emit("ready", "Whatsapp is ready!"); 180 | socket.emit("message", "Whatsapp is ready!"); 181 | }); 182 | 183 | client.on("qr", (qr) => { 184 | var count = 0; 185 | var interval = setInterval(function () { 186 | count++; 187 | console.log("count->", count); 188 | qrcode.toDataURL(qr, (err, url) => { 189 | socket.emit("qr", url); 190 | socket.emit("message", "QR Code received, scan please!"); 191 | console.log("QR RECEIVED", qr); 192 | }); 193 | if (count == 10) { 194 | socket.emit("message", "QR Code Refress"); 195 | } 196 | clearInterval(interval); 197 | }, 1000); 198 | }); 199 | 200 | client.on("authenticated", () => { 201 | socket.emit("authenticated", "Whastapp is authenticated!"); 202 | socket.emit("message", "Whatsapp is authenticated!"); 203 | socket.emit("waconect", true); 204 | console.log("AUTHENTICATED"); 205 | }); 206 | 207 | client.on("message_ack", (msg) => { 208 | socket.emit("message", "Message Success"); 209 | console.error("MESSAGE SEND", msg); 210 | }); 211 | 212 | client.on("auth_failure", (msg) => { 213 | // Fired if session restore was unsuccessful 214 | socket.emit("message", "Auth failure, restarting..."); 215 | console.error("AUTHENTICATION FAILURE", msg); 216 | socket.emit("waconect", true); 217 | }); 218 | 219 | client.on("disconnected", (msg) => { 220 | socket.emit("message", "Whatsapp is disconnected!"); 221 | socket.emit("waconect", false); 222 | client.destroy(); 223 | client.initialize(); 224 | console.log("Client Disconnected"); 225 | }); 226 | }); 227 | 228 | const checkRegisteredNumber = async function (number) { 229 | const isRegistered = await client.isRegisteredUser(number); 230 | return isRegistered; 231 | }; 232 | 233 | app.post( 234 | "/api/v2/send-msg", 235 | [ 236 | body("number").notEmpty(), 237 | body("message").notEmpty(), 238 | body("api_key").notEmpty(), 239 | ], 240 | (req, res) => { 241 | const number = phoneNumberFormatter(req.body.number); 242 | const message = req.body.message; 243 | const api_key = req.body.api_key; 244 | const errors = validationResult(req).formatWith(({ msg }) => { 245 | return msg; 246 | }); 247 | 248 | if (!errors.isEmpty()) { 249 | return res.json({ 250 | status: false, 251 | message: "can't empty", 252 | }); 253 | } else { 254 | cekApiKey(api_key).then((result) => { 255 | var api = result[0].api_key; 256 | if (api !== "") { 257 | client 258 | .sendMessage(number, message) 259 | .then(() => { 260 | res.json({ 261 | status: true, 262 | response: 200, 263 | }); 264 | }) 265 | .catch(() => { 266 | res.json({ 267 | status: false, 268 | response: 500, 269 | }); 270 | }); 271 | } else { 272 | res.json({ 273 | status: 400, 274 | api_sts: false, 275 | }); 276 | } 277 | }); 278 | } 279 | } 280 | ); 281 | 282 | app.post( 283 | "/api/v1/send-msg", 284 | [body("number").notEmpty(), body("message").notEmpty()], 285 | async (req, res) => { 286 | const errors = validationResult(req).formatWith(({ msg }) => { 287 | return msg; 288 | }); 289 | 290 | if (!errors.isEmpty()) { 291 | return res.status(422).json({ 292 | status: false, 293 | message: errors.mapped(), 294 | }); 295 | } 296 | 297 | const number = phoneNumberFormatter(req.body.number); 298 | const message = req.body.message; 299 | 300 | const isRegisteredNumber = await checkRegisteredNumber(number); 301 | if (!isRegisteredNumber) { 302 | return res.status(422).json({ 303 | status: false, 304 | message: "The number is not registered", 305 | }); 306 | } 307 | 308 | client 309 | .sendMessage(number, message) 310 | .then((response) => { 311 | res.status(200).json({ 312 | status: true, 313 | response: response, 314 | }); 315 | }) 316 | .catch((err) => { 317 | res.status(500).json({ 318 | status: false, 319 | response: err, 320 | }); 321 | }); 322 | } 323 | ); 324 | 325 | server.listen(port, () => { 326 | console.log(`cli-nodejs-api listening at ${port}`); 327 | }); 328 | -------------------------------------------------------------------------------- /apps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OTP WHASTAPP 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | loli 24 |
25 |
WHASTAPP API - 26 | Disconect
27 |

OTP-WA versio 1.0

28 | 31 | 32 |
    33 |
34 |
35 | 36 | 37 |
38 |
39 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 69 | 70 | 71 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 303 | 304 | --------------------------------------------------------------------------------