├── 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 |
19 |
20 |
WHASTAPP API
21 |
OTP-WA versio 1.0
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
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 |
23 |

24 |
25 |
WHASTAPP API -
26 | Disconect
27 |
OTP-WA versio 1.0
28 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
303 |
304 |
--------------------------------------------------------------------------------