Skip to content

Instantly share code, notes, and snippets.

@adi-li
Last active August 6, 2019 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adi-li/5f3196ecb7705360fb6c1c160e44e65f to your computer and use it in GitHub Desktop.
Save adi-li/5f3196ecb7705360fb6c1c160e44e65f to your computer and use it in GitHub Desktop.
Unofficial Meteorsis API for node.js (https://www.meteorsis.com)
const rp = require('request-promise-native');
const https = require('https');
const querystring = require('querystring');
const iconv = require('iconv-lite');
const baseUrl = 'https://www.meteorsis.com/misweb/';
const MeteorsisApiErrorCodeMap = {
1: '非預期系統錯誤',
2: '用戶名稱與密碼不符',
3: '戶口己被停用',
5: '短訊的排程日期只限於 5 年之內',
6: '短訊內容必須存在,同時英文短訊的字數只限於 459;或統一碼短訊的字數 只限於 201',
8: '顯示名稱必須存在,並只限於 11 個位的英文及數字組合或 16 個位的數字',
9: 'SMSD ID 不存在',
16: '餘額不足',
17: '電話號碼格式錯誤',
18: '中國短訊的內容包含非法詞語',
20: '連接逾時',
240: '網址格式錯誤:參數數目不符',
241: '網址格式錯誤:content',
242: '網址格式錯誤:langeng',
243: '網址格式錯誤:recipient',
244: '網址格式錯誤:dos',
245: '網址格式錯誤:senderid',
246: '網址格式錯誤:smsdid',
247: '網址格式錯誤:flash',
248: '網址格式錯誤:dncr',
249: '網址格式錯誤:URL 包含危險性的字元',
};
const MeteorsisSmsStatusMap = {
1: '上傳失敗',
2: '上傳成功',
3: '排程',
4: '處理',
19: '重發',
20: '過期',
21: '無法傳送',
22: '確認傳送',
};
const MeteorsisSmsErrorCode = {
300: '不適用',
301: '回條未接收',
302: '號碼格式錯誤',
303: '號碼不存在、己停用',
304: '手機已關、接收不良、飛線',
305: '短訊匣已滿',
306: '短訊匣已滿',
307: '拒收名冊號碼',
308: '餘額不足',
309: '中國短訊非法詞語',
312: '訊息被封鎖,例如客戶欠款、濫發訊息、客戶拒收、程式攔截',
315: '本地電訊商系統故障',
326: '短訊收費超出上限',
};
const chunk = (array, chunkSize) => {
const result = [];
for (let index = 0; index < array.length; index += chunkSize) {
result.push(array.slice(index, index + chunkSize))
}
return result;
};
class MeteorsisApiError extends Error {
static fromCode(code) {
return new MeteorsisApiError(`API error: ${code} ${MeteorsisApiErrorCodeMap[code] || 'Unknown Error'}`);
}
}
class MeteorsisApi {
constructor(username, password) {
this.username = username;
this.password = password;
this.defaultSendingOptions = {
dos: 'now', // schedule time, 'now' / 'yyyy-MM-dd hh:mm'
dncr: 0, // 1 for skipping the numbers in Do-not-call Registers
senderid: 'RANDOMID', // specify sender id
};
this.client = rp.defaults({
// dangerous server, but better than no SSL at all
agent: new https.Agent({
host: 'www.meteorsis.com',
port: 443,
ciphers: 'DES-CBC3-SHA',
rejectUnauthorized: false,
}),
});
}
async sendRequest({ action, ...options }) {
const resp = await this.client({
uri: `${baseUrl}${action}`,
method: 'GET',
...options,
});
const result = querystring.parse(resp, ';', ':');
if ('ERROR' in result) {
throw MeteorsisApiError.fromCode(result.ERROR);
}
return result;
}
async sendSms(recipient, content, options = {}) {
const recipients = (
recipient instanceof Array
? recipient
: [recipient]
)
.map(rpt => rpt.replace(/[\D]/g, ''));
// eslint-disable-next-line no-control-regex
const langeng = /^[\x00-\x7F]*$/g.test(content) ? 1 : 0;
const encodedContent = langeng
? content
: iconv
.encode(content, 'UTF-16BE')
.toString('hex')
.toUpperCase();
const params = {
username: this.username,
password: this.password,
langeng,
...this.defaultSendingOptions,
...options,
content: encodedContent,
};
const result = [];
const chunkedRecipients = chunk(recipients, 20);
for (let index = 0; index < chunkedRecipients.length; index++) {
const chunkedRecipient = chunkedRecipients[index];
// eslint-disable-next-line no-await-in-loop
const resp = await this.sendRequest({
action: 'f_sendsms.aspx',
qs: {
...params,
recipient: chunkedRecipient.join(','),
},
});
const smsdids = resp.SMSDID.split(',');
result.push(...smsdids);
}
return { SMSDID: result };
}
async getSmsStatus(smsdid) {
return this.sendRequest({
action: 'f_getsmsdstatus.aspx',
qs: {
smsdid,
username: this.username,
password: this.password,
},
});
}
async getUserCredit() {
return this.sendRequest({
action: 'f_getusercredit.aspx',
qs: {
username: this.username,
password: this.password,
},
});
}
async getServerQueue() {
return this.sendRequest({
action: 'f_getserverqueue.aspx',
qs: {
username: this.username,
password: this.password,
},
});
}
static webhook(req, res) {
console.log(req.query);
if (req.query.smsdstatus !== '22') {
// no send receipt was replied
// try resend or do something else
console.log(
'SMS not sent',
MeteorsisSmsStatusMap[req.query.smsdstatus],
MeteorsisSmsErrorCode[req.query.smsderrorcode],
);
}
res.send('OK');
}
}
module.exports = MeteorsisApi;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment