wo-base-messenger/messenger.js
2025-03-15 14:14:24 +08:00

205 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//const util = require('util')
const http = require('http')
const NodeMailer = require('nodemailer')
// 注意unicloud 不支持,本文件里只好不用 ?. 操作符
const my = {}
const sender = {}
module.exports = {
initMy (envar) {
my.envar = envar
},
// 或者如果smtp参数已经确定就可以直接定义 sendMail: Bluebird.promisify(Smtp.sendMail).bind(Smtp)
async sendMail (messageObject, smtp = my.envar?.SMTP || wo?.envar?.SMTP) {
if (!smtp?.host) {
globalThis.wo?.ccerror?.({ _state: 'SMTP_CONFIG_MALFORMED', smtp })
return { _state: 'SMTP_CONFIG_MALFORMED', smtp }
}
// messageObject: { from, to, cc, bcc, subject, text, html, sender, replyTo, inReplyTo }
sender.smtpTransporter = sender.smtpTransporter || NodeMailer.createTransport(smtp)
return await sender.smtpTransporter
.sendMail(messageObject)
.then((result) => {
/*
{
accepted: [ 'anolaxy+1@outlook.com' ],
rejected: [],
envelopeTime: 159,
messageTime: 160,
messageSize: 1055,
response: '250 Ok',
envelope: { from: 'hi@babely.cc', to: [ 'anolaxy+1@outlook.com' ] },
messageId: '<f78681c8-f5d7-4149-4abf-4b940497535f@babely.cc>'
}
*/
if (result.messageId && result.response === '250 Ok') {
globalThis.wo?.cclog?.({ _state: 'MSG_SENT_SUCCESS', subject: messageObject.subject, ...result.envelope })
return { _state: 'MSG_SENT_SUCCESS' }
} else {
globalThis.wo?.cclog?.({ _state: 'MSG_SEND_FAIL', subject: messageObject.subject, ...result })
return { _state: 'MSG_SEND_FAIL' }
}
})
.catch((error) => {
globalThis.wo?.ccerror?.({ _state: 'MSG_SEND_ERROR', subject: messageObject.subject, error })
return { _state: 'MSG_SEND_ERROR' }
})
},
async sendSms ({
phone,
config = my.envar?.SMS || wo.envar?.SMS || {}, // ['ALIYUN','UNICLOUD','TENCENT'].includes(config.vendor)
msg, // for 'DXTON'
msgParam,
// 以下参数可在 config 内部,或者在这里再次覆盖
msgTemplate,
signName, // for 'ALIYUN', 'TENCENT'
appid, // for 'UNICLOUD', 'TENCENT'
} = {}) {
let result
if (/^\+\d+-\d+$/.test(phone)) {
if (!config?.vendor) {
result = { _state: 'SMS_CONFIG_MALFORMED', phone, config }
} else if (config.vendor === 'DXTON') {
result = await this.sendSmsDxton({ phone, config, msg })
} else if (config.vendor === 'ALIYUN') {
result = await this.sendSmsAliyun({ phone, config, msgParam, msgTemplate, signName })
} else if (config.vendor === 'TENCENT') {
result = await this.sendSmsTencent({ phone, config, msgParam, msgTemplate, appid, signName })
} else if (config.vendor === 'UNICLOUD') {
result = await this.sendSmsUnicloud({ phone, config, msgParam, msgTemplate, appid })
} else {
result = { _state: 'SMS_UNKNOWN_VENDOR', error: { unknownVendor: config.vendor } }
}
} else {
result = { _state: 'SMS_INVALID_PHONE', error: {} }
}
globalThis.wo?.cclog?.({ phone, signName, msgParam, msgTemplate, ...result })
return result
},
/* 使用 dxton.com 的短信接口 http://www.dxton.com/help_detail/38.html
- 测试接口:
- 国内: http://sms.106jiekou.com/utf8/sms.aspx?account=9999&password=接口密码&mobile=13900008888&content=您的订单编码888888。如需帮助请联系客服。
- 国外: http://sms.106jiekou.com/utf8/worldapi.aspx?account=9999&password=接口密码&mobile=手机号码&content=尊敬的用户您已经注册成功,用户名:{0} 密码:{1} 感谢您的注册!
- response 的 content-type 为 text/html
*/
async sendSmsDxton ({ phone, config, msg }) {
var matches = phone.match(/\d+/g)
var smsNumber, smsUrl
if (matches[0] === '86') {
smsUrl = config.urlChina
smsNumber = matches[1]
} else {
smsUrl = config.urlWorld // 国际短信不需要签名、模板,可发送任意内容。
smsNumber = matches[0] + matches[1]
}
// let returnCode = await RequestPromise.get(smsUrl + '&mobile=' + smsNumber + '&content=' + encodeURIComponent(msg))
// let returnCode = await axios.get(smsUrl + '&mobile=' + smsNumber + '&content=' + encodeURIComponent(msg))
return await new Promise((resolve, reject) => {
http
.get(smsUrl + '&mobile=' + smsNumber + '&content=' + encodeURIComponent(msg), (resp) => {
let returnCode = ''
resp.on('returnCode', (chunk) => {
returnCode += chunk
})
resp.on('end', () => {
if (parseInt(returnCode) === 100) {
resolve({ _state: 'MSG_SENT_SUCCESS' }) // 100: 发送成功 (表示已和我们接口连通)
} else {
resolve({ _state: 'MSG_SEND_FAIL', error: { returnCode } }) // 短信接口错误代码http://www.dxton.com/help_detail/2.html
}
})
})
.on('error', (error) => {
reject({ _state: 'MSG_SEND_ERROR', error })
})
})
},
async sendSmsAliyun ({ phone, config, msgParam, msgTemplate, signName }) {
sender.smsClientAliyun = sender.smsClientAliyun || new (require('@alicloud/sms-sdk'))(config) // https://www.npmjs.com/package/@alicloud/sms-sdk
const [countryCode, callNumber] = phone.match(/\d+/g)
const smsNumber = countryCode === '86' ? callNumber : `00${countryCode}${callNumber}`
return await sender.smsClientAliyun
.sendSMS({
PhoneNumbers: smsNumber, //必填:待发送手机号。支持以逗号分隔的形式进行批量调用批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式;发送国际/港澳台消息时接收号码格式为00+国际区号+号码如“0085200000000”
SignName: signName || config.signName, //必填:短信签名-可在短信控制台中找到
TemplateCode: msgTemplate || config.msgTemplate, //必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
TemplateParam: JSON.stringify(msgParam), //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时。
})
.then(
function (result) {
let { Code } = result
if (Code === 'OK') {
return { _state: 'MSG_SENT_SUCCESS' }
} else {
return { _state: 'MSG_SEND_FAIL', error: result }
}
},
function (error) {
return { _state: 'MSG_SEND_ERROR', error }
}
)
},
async sendSmsTencent ({ phone, config, msgTemplate, msgParam, appid, signName } = {}) {
sender.smsClientTencent = sender.smsClientTencent || new (require('tencentcloud-sdk-nodejs').sms.v20210111.Client)(config) // https://cloud.tencent.com/document/product/382/43197
return await sender.smsClientTencent
.SendSms({
// API: https://cloud.tencent.com/document/product/382/55981
PhoneNumberSet: [phone.replace('-', '')],
SmsSdkAppId: appid || config.appid,
SignName: typeof signName !== 'undefined' ? signName : config.signName, // 腾讯云的国际短信可以没有签名,因此允许传入参数 signName:'' 来覆盖默认的 config.signName
TemplateId: msgTemplate || config.msgTemplate,
TemplateParamSet: Object.values(msgParam),
})
.then(
function ({ SendStatusSet, RequestId } = {}) {
let { SerialNo, PhoneNumber, Fee, Code, Message, IsoCode } = SendStatusSet[0]
if (Code === 'Ok') {
return { _state: 'MSG_SENT_SUCCESS' }
} else {
return { _state: 'MSG_SEND_FAIL', error: { SendStatusSet, RequestId } }
}
},
function (error) {
return { _state: 'MSG_SEND_ERROR', error }
}
)
},
async sendSmsUnicloud ({ phone, config, msgTemplate, msgParam, appid } = {}) {
try {
const result = await uniCloud.sendSms({
appid: appid || config.smsAppid,
smsKey: config.smsKey,
smsSecret: config.smsSecret,
phone: phone.match(/\d+/g)[1],
templateId: msgTemplate || config.msgTemplate || 'uni_sms_test', // for test only, max 100 messages for 10 phones at most per day.
data: msgParam, // 模版中的变量的值,例如 { passcode: '234345', purpose: '注册' }
})
if (result?.errorCode === 0) {
return { _state: 'MSG_SENT_SUCCESS', result } // { code:0, errCode:0, success:true }
} else {
// 错误码参见 https://doc.dcloud.net.cn/uniCloud/sms/dev.html
return { _state: 'MSG_SEND_FAIL', result }
}
} catch (error) {
// 调用失败 例如 {"code":undefined,"msg":"短信发送失败:账户余额不足"}
return {
_state: 'MSG_SEND_ERROR',
error, // { errCode, errMsg }
}
}
},
}