Init project
This commit is contained in:
commit
b01f418c07
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
.git/
|
||||||
|
.env
|
||||||
|
.wwebjs_auth/
|
||||||
|
.wwebjs_cache/
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
22
.env.example
Normal file
22
.env.example
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# WhatsApp groups to monitor (comma-separated)
|
||||||
|
GROUP_NAMES=Group1,Group2
|
||||||
|
|
||||||
|
# TextBee SMS Gateway
|
||||||
|
TEXTBEE_DEVICE_ID=your_device_id
|
||||||
|
TEXTBEE_API_KEY=your_api_key
|
||||||
|
SMS_RECIPIENT=+972501234567
|
||||||
|
|
||||||
|
# Telegram (for QR re-auth notifications)
|
||||||
|
TELEGRAM_BOT_TOKEN=your_bot_token
|
||||||
|
TELEGRAM_CHAT_ID=your_chat_id
|
||||||
|
|
||||||
|
# Batch settings
|
||||||
|
BATCH_INTERVAL_MS=900000
|
||||||
|
BATCH_MAX_CHARS=700
|
||||||
|
INCLUDE_OWN_MESSAGES=true
|
||||||
|
OWN_NAME=Me
|
||||||
|
OWN_LAST_NAME=
|
||||||
|
|
||||||
|
# Keep-alive (ping a URL to prevent hosting sleep)
|
||||||
|
KEEP_ALIVE_URL=
|
||||||
|
KEEP_ALIVE_INTERVAL_MS=300000
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.env
|
||||||
|
node_modules/
|
||||||
|
.wwebjs_auth/
|
||||||
|
.wwebjs_cache/
|
||||||
|
*.session
|
||||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
FROM node:22-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
chromium \
|
||||||
|
chromium-sandbox \
|
||||||
|
libnss3 \
|
||||||
|
libnspr4 \
|
||||||
|
libatk1.0-0 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libcups2 \
|
||||||
|
libdrm2 \
|
||||||
|
libxkbcommon0 \
|
||||||
|
libxcomposite1 \
|
||||||
|
libxdamage1 \
|
||||||
|
libxrandr2 \
|
||||||
|
libgbm1 \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libcairo2 \
|
||||||
|
libasound2 \
|
||||||
|
--no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV CHROME_PATH=/usr/bin/chromium \
|
||||||
|
PUPPETEER_SKIP_DOWNLOAD=true \
|
||||||
|
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV PORT=3000
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "index.js"]
|
||||||
107
README.md
Normal file
107
README.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# OmegaBaSMS
|
||||||
|
|
||||||
|
Forward WhatsApp group messages to SMS — batched and free.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
- Monitors specified WhatsApp groups via WhatsApp Web
|
||||||
|
- Queues incoming messages for a configurable interval (default 30s)
|
||||||
|
- Flushes all queued messages as a single SMS via **TextBee** (uses your Android phone's mobile plan)
|
||||||
|
- If the WhatsApp session expires, sends the QR code to **Telegram** for easy re-auth
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- Android phone with mobile plan (for TextBee)
|
||||||
|
- Telegram account (for QR re-auth notifications)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. TextBee (SMS Gateway)
|
||||||
|
|
||||||
|
1. Create a free account at [textbee.dev](https://textbee.dev)
|
||||||
|
2. Install the TextBee app on your Android phone
|
||||||
|
3. In the dashboard, register your device (scan QR with the app)
|
||||||
|
4. Copy your **Device ID** and **API key**
|
||||||
|
|
||||||
|
### 2. Telegram Bot (QR re-auth)
|
||||||
|
|
||||||
|
1. Open Telegram, search for `@BotFather`, send `/newbot`
|
||||||
|
2. Choose a name and username, get the **bot token**
|
||||||
|
3. Message your bot once, then visit:
|
||||||
|
`https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates`
|
||||||
|
4. Copy your **chat ID** from the response
|
||||||
|
|
||||||
|
### 3. Configuration
|
||||||
|
|
||||||
|
Copy the example env file and fill in your details:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
```env
|
||||||
|
# WhatsApp groups to monitor (comma-separated)
|
||||||
|
GROUP_NAMES=MyGroup,AnotherGroup
|
||||||
|
|
||||||
|
# TextBee
|
||||||
|
TEXTBEE_DEVICE_ID=your_device_id
|
||||||
|
TEXTBEE_API_KEY=your_api_key
|
||||||
|
SMS_RECIPIENT=+972501234567
|
||||||
|
|
||||||
|
# Telegram
|
||||||
|
TELEGRAM_BOT_TOKEN=your_bot_token
|
||||||
|
TELEGRAM_CHAT_ID=your_chat_id
|
||||||
|
|
||||||
|
# Batch settings (30s for testing, 900000 = 15min for production)
|
||||||
|
BATCH_INTERVAL_MS=30000
|
||||||
|
BATCH_MAX_CHARS=700
|
||||||
|
|
||||||
|
# Your name shown in forwarded messages
|
||||||
|
INCLUDE_OWN_MESSAGES=true
|
||||||
|
OWN_NAME=Dvir
|
||||||
|
OWN_LAST_NAME=
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
On first run, scan the QR code with WhatsApp on your phone. The session is saved for next time.
|
||||||
|
|
||||||
|
## Log format
|
||||||
|
|
||||||
|
```
|
||||||
|
[12:34:56] [INIT] Starting OmegaBaSMS...
|
||||||
|
[12:34:56] [INIT] Launching WhatsApp Web...
|
||||||
|
[12:34:58] [QR] New QR code received — scan with WhatsApp
|
||||||
|
[12:35:10] [READY] WhatsApp connected successfully
|
||||||
|
[13:01:23] [QUEUE] Queue #1 - Message from Dvir, flushed at 13:16
|
||||||
|
[13:16:23] [INFO] Flushed 2 messages
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch messaging
|
||||||
|
|
||||||
|
Messages are queued and flushed together to avoid SMS spam:
|
||||||
|
|
||||||
|
```
|
||||||
|
[GroupName]
|
||||||
|
Dvir: Hey everyone
|
||||||
|
Dudi: What's up?
|
||||||
|
Dvir: Meeting at 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
OmegaBaSMS/
|
||||||
|
├── index.js # Main — WhatsApp client, queue, logging
|
||||||
|
├── config.js # Reads from .env
|
||||||
|
├── sms.js # TextBee API
|
||||||
|
├── telegram.js # Telegram notification
|
||||||
|
├── .env # Your secrets (git-ignored)
|
||||||
|
├── .env.example # Template
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
31
config.js
Normal file
31
config.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
groupNames: (process.env.GROUP_NAMES || '').split(',').map(s => s.trim()).filter(Boolean),
|
||||||
|
|
||||||
|
smsGateway: {
|
||||||
|
deviceId: process.env.TEXTBEE_DEVICE_ID,
|
||||||
|
apiKey: process.env.TEXTBEE_API_KEY,
|
||||||
|
recipientNumber: process.env.SMS_RECIPIENT,
|
||||||
|
},
|
||||||
|
|
||||||
|
telegram: {
|
||||||
|
botToken: process.env.TELEGRAM_BOT_TOKEN,
|
||||||
|
chatId: process.env.TELEGRAM_CHAT_ID,
|
||||||
|
},
|
||||||
|
|
||||||
|
ownerNumber: process.env.SMS_RECIPIENT,
|
||||||
|
|
||||||
|
batch: {
|
||||||
|
intervalMs: parseInt(process.env.BATCH_INTERVAL_MS, 10) || 900000,
|
||||||
|
maxChars: parseInt(process.env.BATCH_MAX_CHARS, 10) || 700,
|
||||||
|
},
|
||||||
|
|
||||||
|
includeOwnMessages: process.env.INCLUDE_OWN_MESSAGES !== 'false',
|
||||||
|
ownName: [process.env.OWN_NAME, process.env.OWN_LAST_NAME].filter(Boolean).join(' ') || 'Me',
|
||||||
|
|
||||||
|
keepAlive: {
|
||||||
|
url: process.env.KEEP_ALIVE_URL || '',
|
||||||
|
intervalMs: parseInt(process.env.KEEP_ALIVE_INTERVAL_MS, 10) || 300000,
|
||||||
|
},
|
||||||
|
};
|
||||||
294
index.js
Normal file
294
index.js
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
const { Client, LocalAuth } = require('whatsapp-web.js');
|
||||||
|
const qrcode = require('qrcode');
|
||||||
|
const qrcodeTerminal = require('qrcode-terminal');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const config = require('./config');
|
||||||
|
const { sendSMS } = require('./sms');
|
||||||
|
const { sendTelegramPhoto } = require('./telegram');
|
||||||
|
|
||||||
|
let messageQueue = [];
|
||||||
|
let flushTimer = null;
|
||||||
|
let flushTimerStart = null;
|
||||||
|
let client = null;
|
||||||
|
let restartDelay = 1000;
|
||||||
|
let starting = false;
|
||||||
|
let restarting = false;
|
||||||
|
let msgCounter = 0;
|
||||||
|
|
||||||
|
function ts() {
|
||||||
|
return new Date().toLocaleString('he-IL', { hour12: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(level, msg) {
|
||||||
|
console.log(`[${ts()}] [${level}] ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushTime() {
|
||||||
|
if (!flushTimerStart) return '--:--';
|
||||||
|
const t = new Date(Date.now() + Math.max(0, config.batch.intervalMs - (Date.now() - flushTimerStart)));
|
||||||
|
return t.toLocaleTimeString('he-IL', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBatch(queue) {
|
||||||
|
const groups = {};
|
||||||
|
for (const m of queue) {
|
||||||
|
if (!groups[m.group]) groups[m.group] = [];
|
||||||
|
groups[m.group].push(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
for (const [group, msgs] of Object.entries(groups)) {
|
||||||
|
parts.push(`[${group}]`);
|
||||||
|
for (const m of msgs) {
|
||||||
|
parts.push(`${m.sender}: ${m.text}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueSize() {
|
||||||
|
return messageQueue.reduce(
|
||||||
|
(sum, m) => sum + m.group.length + m.sender.length + m.text.length + 6,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleFlush() {
|
||||||
|
if (flushTimer) clearTimeout(flushTimer);
|
||||||
|
if (!flushTimerStart) flushTimerStart = Date.now();
|
||||||
|
flushTimer = setTimeout(flushQueue, config.batch.intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushQueue() {
|
||||||
|
if (restarting) return;
|
||||||
|
if (messageQueue.length === 0) return;
|
||||||
|
|
||||||
|
flushTimer = null;
|
||||||
|
flushTimerStart = null;
|
||||||
|
|
||||||
|
const batch = messageQueue;
|
||||||
|
messageQueue = [];
|
||||||
|
msgCounter = 0;
|
||||||
|
|
||||||
|
const text = formatBatch(batch);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendSMS(text);
|
||||||
|
log('INFO', `Flushed ${batch.length} messages`);
|
||||||
|
} catch (err) {
|
||||||
|
log('ERROR', `Flush failed: ${err.message}`);
|
||||||
|
messageQueue = batch.concat(messageQueue);
|
||||||
|
msgCounter = messageQueue.length;
|
||||||
|
scheduleFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enqueue(group, sender, text) {
|
||||||
|
msgCounter++;
|
||||||
|
messageQueue.push({ group, sender, text });
|
||||||
|
|
||||||
|
scheduleFlush();
|
||||||
|
log('QUEUE', `Queue #${msgCounter} - Message from ${sender}, flushed at ${flushTime()}`);
|
||||||
|
|
||||||
|
if (queueSize() >= config.batch.maxChars) {
|
||||||
|
clearTimeout(flushTimer);
|
||||||
|
flushTimer = null;
|
||||||
|
flushTimerStart = null;
|
||||||
|
flushQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function killClient() {
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.removeAllListeners();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.destroy();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT:
|
||||||
|
* Give Chromium time to release Windows file locks
|
||||||
|
*/
|
||||||
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
|
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let keepAliveTimer = null;
|
||||||
|
|
||||||
|
function startKeepAlive() {
|
||||||
|
if (keepAliveTimer) clearInterval(keepAliveTimer);
|
||||||
|
const ka = config.keepAlive;
|
||||||
|
if (!ka || !ka.url) return;
|
||||||
|
|
||||||
|
log('INIT', `Keep-alive ping every ${ka.intervalMs / 1000}s to ${ka.url}`);
|
||||||
|
const ping = () => {
|
||||||
|
fetch(ka.url).catch(() => {});
|
||||||
|
};
|
||||||
|
ping();
|
||||||
|
keepAliveTimer = setInterval(ping, ka.intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startClient() {
|
||||||
|
if (starting) {
|
||||||
|
log('WARN', 'Already starting — skipping duplicate call');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
starting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
restarting = true;
|
||||||
|
|
||||||
|
log('INIT', 'Starting OmegaBaSMS...');
|
||||||
|
await killClient();
|
||||||
|
|
||||||
|
log('INIT', `Groups to monitor: ${config.groupNames.join(', ')}`);
|
||||||
|
log('INIT', `Batch interval: ${config.batch.intervalMs / 1000}s / max ${config.batch.maxChars} chars`);
|
||||||
|
log('INIT', `Forwarding SMS to: ${config.smsGateway.recipientNumber}`);
|
||||||
|
|
||||||
|
if (config.telegram.botToken) {
|
||||||
|
log('INIT', 'Telegram notifications enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
startKeepAlive();
|
||||||
|
|
||||||
|
log('INIT', 'Launching WhatsApp Web...');
|
||||||
|
|
||||||
|
client = new Client({
|
||||||
|
authStrategy: new LocalAuth(),
|
||||||
|
puppeteer: {
|
||||||
|
headless: true,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('qr', async (qr) => {
|
||||||
|
log('QR', 'New QR code received — scan with WhatsApp on your phone');
|
||||||
|
qrcodeTerminal.generate(qr, { small: true });
|
||||||
|
|
||||||
|
if (config.telegram.botToken) {
|
||||||
|
try {
|
||||||
|
const buf = await qrcode.toBuffer(qr, { width: 400 });
|
||||||
|
await sendTelegramPhoto(buf, 'WhatsApp re-auth needed - scan this QR\nhttps://wa.me/settings/linked_devices');
|
||||||
|
log('QR', 'QR photo sent to Telegram');
|
||||||
|
} catch (err) {
|
||||||
|
log('ERROR', `Failed to send QR photo: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('ready', () => {
|
||||||
|
restarting = false;
|
||||||
|
restartDelay = 1000;
|
||||||
|
log('READY', 'WhatsApp connected successfully');
|
||||||
|
log('READY', `Monitoring ${config.groupNames.length} group(s): ${config.groupNames.join(', ')}`);
|
||||||
|
log('READY', `Forwarding to ${config.smsGateway.recipientNumber}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('auth_failure', (msg) => {
|
||||||
|
log('ERROR', `Auth failure: ${msg}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('disconnected', async (reason) => {
|
||||||
|
if (starting) return;
|
||||||
|
|
||||||
|
log('WARN', `Disconnected: ${reason}. Restarting in ${restartDelay / 1000}s...`);
|
||||||
|
|
||||||
|
restarting = true;
|
||||||
|
|
||||||
|
if (reason === 'LOGOUT') {
|
||||||
|
const authDir = path.join(__dirname, '.wwebjs_auth');
|
||||||
|
if (fs.existsSync(authDir)) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(authDir, {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
maxRetries: 10,
|
||||||
|
retryDelay: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
log('WARN', 'Cleared old session data');
|
||||||
|
} catch (err) {
|
||||||
|
log('ERROR', `Failed clearing auth data: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, restartDelay));
|
||||||
|
restartDelay = Math.min(restartDelay * 2, 30000);
|
||||||
|
startClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('message_create', async (message) => {
|
||||||
|
try {
|
||||||
|
if (restarting) return;
|
||||||
|
if (message.type !== 'chat') return;
|
||||||
|
|
||||||
|
const chat = await message.getChat();
|
||||||
|
if (!chat.isGroup) return;
|
||||||
|
if (!config.groupNames.includes(chat.name)) return;
|
||||||
|
if (message.fromMe && !config.includeOwnMessages) return;
|
||||||
|
|
||||||
|
const contact = await message.getContact();
|
||||||
|
const sender = message.fromMe
|
||||||
|
? config.ownName
|
||||||
|
: (contact.name || contact.pushname || contact.shortName || contact.number || 'Unknown');
|
||||||
|
|
||||||
|
const body = message.body || (message.hasMedia ? '[Media]' : '');
|
||||||
|
if (!body) return;
|
||||||
|
|
||||||
|
enqueue(chat.name, sender, body);
|
||||||
|
} catch (err) {
|
||||||
|
log('ERROR', `Message handler: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.initialize();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
starting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔥 FIX: detect detached frame / WhatsApp Web crashes
|
||||||
|
*/
|
||||||
|
function shouldRestart(err) {
|
||||||
|
const msg = (err && err.message) || String(err);
|
||||||
|
return msg.includes('detached Frame') ||
|
||||||
|
msg.includes('Execution context was destroyed') ||
|
||||||
|
msg.includes('Target closed') ||
|
||||||
|
msg.includes('Session closed') ||
|
||||||
|
msg.includes('Navigation failed') ||
|
||||||
|
msg.includes('Protocol error');
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('uncaughtException', (err) => {
|
||||||
|
if (shouldRestart(err)) {
|
||||||
|
log('WARN', `Recoverable: ${err.message}. Restarting...`);
|
||||||
|
startClient();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('FATAL', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (err) => {
|
||||||
|
if (shouldRestart(err)) {
|
||||||
|
log('WARN', `Recoverable: ${err.message}. Restarting...`);
|
||||||
|
startClient();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('FATAL', `Unhandled rejection: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
startClient();
|
||||||
2576
package-lock.json
generated
Normal file
2576
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
package.json
Normal file
15
package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "omegabasms",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Forward WhatsApp group messages via SMS",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
|
"whatsapp-web.js": "^1.25.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
sms.js
Normal file
42
sms.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const https = require('https');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
async function sendSMS(text, recipientOverride) {
|
||||||
|
const gw = config.smsGateway;
|
||||||
|
const recipients = recipientOverride ? [recipientOverride] : [gw.recipientNumber];
|
||||||
|
const data = JSON.stringify({
|
||||||
|
recipients,
|
||||||
|
message: text,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = https.request(
|
||||||
|
{
|
||||||
|
hostname: 'api.textbee.dev',
|
||||||
|
path: `/api/v1/gateway/devices/${gw.deviceId}/send-sms`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': gw.apiKey,
|
||||||
|
'Content-Length': Buffer.byteLength(data),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
let body = '';
|
||||||
|
res.on('data', (chunk) => (body += chunk));
|
||||||
|
res.on('end', () => {
|
||||||
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
resolve(body);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`TextBee ${res.statusCode}: ${body}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.on('error', reject);
|
||||||
|
req.write(data);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { sendSMS };
|
||||||
26
telegram.js
Normal file
26
telegram.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
const tg = config.telegram;
|
||||||
|
if (!tg || !tg.botToken) {
|
||||||
|
module.exports = { sendTelegramMessage: async () => {}, sendTelegramPhoto: async () => {} };
|
||||||
|
} else {
|
||||||
|
const base = `https://api.telegram.org/bot${tg.botToken}`;
|
||||||
|
|
||||||
|
async function sendTelegramMessage(text) {
|
||||||
|
await fetch(`${base}/sendMessage`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ chat_id: tg.chatId, text }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendTelegramPhoto(buffer, caption) {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('chat_id', tg.chatId);
|
||||||
|
form.append('photo', new Blob([buffer]), 'qr.png');
|
||||||
|
if (caption) form.append('caption', caption);
|
||||||
|
await fetch(`${base}/sendPhoto`, { method: 'POST', body: form });
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { sendTelegramMessage, sendTelegramPhoto };
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user