2026-05-24 18:27:49 +03:30
|
|
|
import 'dotenv/config';
|
|
|
|
|
|
|
|
|
|
const username = process.env.SMS_USERNAME;
|
2026-05-25 13:22:13 +03:30
|
|
|
const password = process.env.SMS_PASSWORD ?? process.env.SMS_API_KEY;
|
2026-05-24 18:27:49 +03:30
|
|
|
const sender = process.env.SMS_SENDER;
|
2026-05-30 11:13:52 +03:30
|
|
|
const provider = (process.env.SMS_PROVIDER ?? 'trez').toLowerCase();
|
2026-05-24 18:27:49 +03:30
|
|
|
|
|
|
|
|
if (!username || !password || !sender) {
|
2026-05-25 13:22:13 +03:30
|
|
|
console.error('Set SMS_USERNAME, SMS_PASSWORD (or SMS_API_KEY), and SMS_SENDER in .env');
|
2026-05-24 18:27:49 +03:30
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const targets = process.argv.slice(2).length
|
|
|
|
|
? process.argv.slice(2)
|
2026-05-30 11:13:52 +03:30
|
|
|
: ['09128081392'];
|
2026-05-24 18:27:49 +03:30
|
|
|
|
2026-05-25 13:22:13 +03:30
|
|
|
const text = 'کد ورود منتایم: 123456';
|
2026-05-30 11:13:52 +03:30
|
|
|
const timeoutMs = Number.parseInt(process.env.SMS_TIMEOUT_MS ?? '20000', 10);
|
|
|
|
|
|
|
|
|
|
function parseTrezResponse(body) {
|
|
|
|
|
const trimmed = body.trim();
|
|
|
|
|
if (!trimmed) return { ok: true, reason: 'empty' };
|
|
|
|
|
const lower = trimmed.toLowerCase();
|
|
|
|
|
if (lower.includes('error') || lower.includes('fail') || lower.includes('خطا')) {
|
|
|
|
|
return { ok: false, reason: trimmed };
|
|
|
|
|
}
|
|
|
|
|
if (/^-?\d+$/.test(trimmed)) {
|
|
|
|
|
const n = Number(trimmed);
|
|
|
|
|
if (n === 1 || n > 1) return { ok: true, reason: trimmed };
|
|
|
|
|
return { ok: false, reason: `code ${trimmed}` };
|
|
|
|
|
}
|
|
|
|
|
return { ok: true, reason: trimmed };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sendViaTrez(mobile) {
|
|
|
|
|
const baseUrl = (process.env.SMS_BASE_URL ?? 'http://smspanel.Trez.ir').replace(/\/$/, '');
|
|
|
|
|
const path = process.env.SMS_SEND_PATH ?? '/SendMessageWithCode.ashx';
|
|
|
|
|
const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
|
|
|
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
|
|
|
body: new URLSearchParams({
|
|
|
|
|
PhoneNumber: sender,
|
|
|
|
|
Username: username,
|
|
|
|
|
Password: password,
|
|
|
|
|
Mobile: mobile,
|
|
|
|
|
Message: text,
|
|
|
|
|
}).toString(),
|
|
|
|
|
signal: AbortSignal.timeout(timeoutMs),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const body = await res.text();
|
|
|
|
|
const parsed = parseTrezResponse(body);
|
|
|
|
|
console.log(
|
|
|
|
|
`mobile=${mobile} provider=trez http=${res.status} body=${JSON.stringify(body.trim())} ${parsed.ok ? 'OK' : 'FAILED'} (${parsed.reason})`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sendVia0098(mobile) {
|
|
|
|
|
const escapeXml = (value) =>
|
|
|
|
|
String(value)
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
.replace(/'/g, ''');
|
2026-05-25 13:22:13 +03:30
|
|
|
|
2026-05-24 18:27:49 +03:30
|
|
|
const soapBody = `<?xml version="1.0" encoding="utf-8"?>
|
2026-05-25 13:22:13 +03:30
|
|
|
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
2026-05-24 18:27:49 +03:30
|
|
|
<soap:Body>
|
|
|
|
|
<SendSMS xmlns="http://tempuri.org/">
|
|
|
|
|
<username>${escapeXml(username)}</username>
|
|
|
|
|
<password>${escapeXml(password)}</password>
|
|
|
|
|
<text>${escapeXml(text)}</text>
|
|
|
|
|
<mobileno>${escapeXml(mobile)}</mobileno>
|
|
|
|
|
<pnlno>${escapeXml(sender)}</pnlno>
|
|
|
|
|
</SendSMS>
|
|
|
|
|
</soap:Body>
|
|
|
|
|
</soap:Envelope>`;
|
|
|
|
|
|
|
|
|
|
const res = await fetch('https://webservice.0098sms.com/service.asmx', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'text/xml; charset=utf-8',
|
|
|
|
|
SOAPAction: 'http://tempuri.org/SendSMS',
|
|
|
|
|
},
|
|
|
|
|
body: soapBody,
|
2026-05-30 11:13:52 +03:30
|
|
|
signal: AbortSignal.timeout(timeoutMs),
|
2026-05-24 18:27:49 +03:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const body = await res.text();
|
2026-05-25 13:22:13 +03:30
|
|
|
const match = body.match(/<SendSMSResult[^>]*>([^<]*)<\/SendSMSResult>/i);
|
|
|
|
|
const code = match?.[1]?.trim() ?? '?';
|
2026-05-25 15:50:32 +03:30
|
|
|
const n = Number(code);
|
|
|
|
|
const ok = Number.isFinite(n) ? n >= 0 : false;
|
2026-05-30 11:13:52 +03:30
|
|
|
console.log(
|
|
|
|
|
`mobile=${mobile} provider=0098 http=${res.status} RESULT=${code} ${ok ? 'OK' : 'FAILED'}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-05-25 13:22:13 +03:30
|
|
|
|
2026-05-30 11:13:52 +03:30
|
|
|
console.log(`SMS_PROVIDER=${provider}`);
|
|
|
|
|
for (const mobile of targets) {
|
|
|
|
|
if (provider === '0098') {
|
|
|
|
|
await sendVia0098(mobile);
|
|
|
|
|
} else {
|
|
|
|
|
await sendViaTrez(mobile);
|
2026-05-25 13:22:13 +03:30
|
|
|
}
|
2026-05-24 18:27:49 +03:30
|
|
|
}
|