This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
//go:build plus
package mediasenders
// TextFormat 内容文本格式类型定义
type TextFormat = string
// 常用的内容文本格式
const (
FormatText = "text"
FormatMarkdown = "markdown"
)

View File

@@ -0,0 +1,86 @@
//go:build plus
package mediasenders
import (
"encoding/json"
"errors"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/utils/string"
"strings"
)
// AliyunSmsMedia 阿里云短信
type AliyunSmsMedia struct {
Sign string `yaml:"sign" json:"sign"` // 签名名称
TemplateCode string `yaml:"templateCode" json:"templateCode"` // 模板CODE
Variables []*Variable `yaml:"variables" json:"variables"` // 变量
AccessKeyId string `yaml:"accessKeyId" json:"accessKeyId"` // AccessKeyId
AccessKeySecret string `yaml:"accessKeySecret" json:"accessKeySecret"` // AccessKeySecret
}
// NewAliyunSmsMedia 获取新对象
func NewAliyunSmsMedia() *AliyunSmsMedia {
return &AliyunSmsMedia{}
}
func (this *AliyunSmsMedia) Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error) {
// {"Message":"OK","RequestId":"xxxx","BizId":"xxxx","Code":"OK"}
client, err := sdk.NewClientWithAccessKey("cn-hangzhou", this.AccessKeyId, this.AccessKeySecret)
if err != nil {
panic(err)
}
request := requests.NewCommonRequest()
request.Method = "POST"
request.Scheme = "https" // https | http
request.Domain = "dysmsapi.aliyuncs.com"
request.Version = "2017-05-25"
request.ApiName = "SendSms"
request.QueryParams["RegionId"] = "cn-hangzhou"
request.QueryParams["PhoneNumbers"] = user
request.QueryParams["SignName"] = this.Sign
request.QueryParams["TemplateCode"] = this.TemplateCode
varMap := maps.Map{}
for _, v := range this.Variables {
value := v.Value
value = strings.Replace(value, "${MessageUser}", user, -1)
value = strings.Replace(value, "${MessageSubject}", subject, -1)
value = strings.Replace(value, "${MessageBody}", body, -1)
// 阿里云的限制参数长度不能超过20
var maxLen = 20
if len([]rune(value)) > maxLen {
value = string([]rune(value)[:maxLen-4]) + "..."
}
varMap[v.Name] = value
}
request.QueryParams["TemplateParam"] = stringutil.JSONEncode(varMap)
response, err := client.ProcessCommonRequest(request)
if err != nil {
return nil, err
}
data := response.GetHttpContentBytes()
m := maps.Map{}
err = json.Unmarshal(data, &m)
if err != nil {
return data, err
}
if m.GetString("Code") == "OK" {
return data, nil
}
return data, errors.New("fail to send sms" + string(data))
}
// RequireUser 是否需要用户标识
func (this *AliyunSmsMedia) RequireUser() bool {
return true
}

View File

@@ -0,0 +1,72 @@
//go:build plus
package mediasenders
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/utils/string"
"io"
"net/http"
"strings"
"time"
)
// DingTalkMedia 钉钉群机器人媒介
type DingTalkMedia struct {
WebHookURL string `yaml:"webHookURL" json:"webHookURL"`
}
// 获取新对象
func NewDingTalkMedia() *DingTalkMedia {
return &DingTalkMedia{}
}
func (this *DingTalkMedia) Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error) {
if len(this.WebHookURL) == 0 {
return nil, errors.New("webHook url should not be empty")
}
content := maps.Map{
"msgtype": "text",
"text": maps.Map{
"content": "标题:" + subject + "\n内容" + body,
},
}
if len(user) > 0 {
mobiles := []string{}
for _, u := range strings.Split(user, ",") {
u = strings.TrimSpace(u)
if len(u) > 0 {
mobiles = append(mobiles, u)
}
}
content["at"] = maps.Map{
"atMobiles": mobiles,
}
}
reader := bytes.NewBufferString(stringutil.JSONEncode(content))
req, err := http.NewRequest(http.MethodPost, this.WebHookURL, reader)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := utils.SharedHttpClient(5 * time.Second)
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
resp, err = io.ReadAll(response.Body)
return
}
// RequireUser 是否需要用户标识
func (this *DingTalkMedia) RequireUser() bool {
return false
}

View File

@@ -0,0 +1,279 @@
//go:build plus
package mediasenders
import (
"crypto/tls"
"encoding/base64"
"errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"net"
"net/mail"
"net/smtp"
"strconv"
"time"
)
type MailInfo struct {
To string
Subject string
Body string
}
// EmailMedia 邮件媒介
type EmailMedia struct {
SMTP string `yaml:"smtp" json:"smtp"` // SMTP地址host:port
Username string `yaml:"username" json:"username"` // 用户名
Password string `yaml:"password" json:"password"` // 密码
From string `yaml:"from" json:"from"` // 发件人
FromName string `yaml:"fromName" json:"fromName"` // 发件人名称
Protocol string `yaml:"protocol" json:"protocol"` // 协议tcp/tls
}
// NewEmailMedia 获取新对象
func NewEmailMedia() *EmailMedia {
return &EmailMedia{}
}
func (this *EmailMedia) Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error) {
if len(this.SMTP) == 0 {
return nil, errors.New("host address should be specified")
}
// 自动加端口
if _, _, err := net.SplitHostPort(this.SMTP); err != nil {
this.SMTP += ":587"
}
if len(this.From) == 0 {
this.From = this.Username
}
var contentType = "Content-Type: text/html; charset=UTF-8"
var senderName = this.FromName
if len(senderName) == 0 {
if len(productName) > 0 {
senderName = productName
} else {
senderName = teaconst.GlobalProductName
}
subject = "[" + senderName + "]" + subject
}
var msg = []byte("To: " + user + "\r\nFrom: " + strconv.Quote(senderName) + " <" + this.From + ">\r\nSubject: " + "=?utf-8?B?" + base64.StdEncoding.EncodeToString([]byte(subject)) + "?=" + "\r\n" + contentType + "\r\n\r\n" + body)
return nil, this.SendMail(this.From, []string{user}, msg)
}
// RequireUser 是否需要用户标识
func (this *EmailMedia) RequireUser() bool {
return true
}
// SendMail 发送邮件
func (this *EmailMedia) SendMail(from string, to []string, message []byte) error {
_, err := mail.ParseAddress(from)
if err != nil {
return err
}
if len(to) == 0 {
return errors.New("recipients should not be empty")
}
for _, to1 := range to {
_, err := mail.ParseAddress(to1)
if err != nil {
return err
}
}
client, err := this.Connect()
if err != nil {
return err
}
defer func() {
_ = client.Quit()
_ = client.Close()
}()
// To && From
if err := client.Mail(from); err != nil {
return err
}
for _, to1 := range to {
if err := client.Rcpt(to1); err != nil {
return err
}
}
// Data
w, err := client.Data()
if err != nil {
return err
}
_, err = w.Write(message)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return nil
}
// SendMails 发送邮件
func (this *EmailMedia) SendMails(mails []*MailInfo, productName string) error {
if len(mails) == 0 {
return nil
}
_, err := mail.ParseAddress(this.From)
if err != nil {
return err
}
client, err := this.Connect()
if err != nil {
return err
}
defer func() {
_ = client.Quit()
_ = client.Close()
}()
// 发送者
var senderName = this.FromName
if len(senderName) == 0 {
if len(productName) > 0 {
senderName = productName
} else {
senderName = teaconst.GlobalProductName
}
}
// 创建邮件
for _, mailInfo := range mails {
if len(mailInfo.To) == 0 {
continue
}
_, err = mail.ParseAddress(mailInfo.To)
if err != nil {
return err
}
if err := client.Mail(this.From); err != nil {
return err
}
if err := client.Rcpt(mailInfo.To); err != nil {
return err
}
// Data
w, err := client.Data()
if err != nil {
return err
}
_, err = w.Write([]byte("To: " + mailInfo.To + "\r\nFrom: " + strconv.Quote(senderName) + " <" + this.From + ">\r\nSubject: " + mailInfo.Subject + "\r\n" + "Content-Type: text/html; charset=UTF-8" + "\r\n\r\n" + mailInfo.Body))
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
}
return nil
}
func (this *EmailMedia) Connect() (*smtp.Client, error) {
var serverName = this.SMTP
var username = this.Username
var password = this.Password
host, port, _ := net.SplitHostPort(serverName)
var client *smtp.Client
// TLS config
var tlsConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}
if this.Protocol == "tcp" || (len(this.Protocol) == 0 && port == "25") { // 25 port: default tcp port
conn, err := net.DialTimeout("tcp", serverName, 10*time.Second)
if err != nil {
return nil, err
}
client, err = smtp.NewClient(conn, host)
if err != nil {
return nil, err
}
} else if port == "587" { // 587 port: prefer START_TLS
conn, err := net.DialTimeout("tcp", serverName, 10*time.Second)
if err != nil {
conn, err := tls.Dial("tcp", serverName, tlsConfig)
if err != nil {
return nil, err
}
client, err = smtp.NewClient(conn, host)
if err != nil {
return nil, err
}
} else {
client, err = smtp.NewClient(conn, host)
if err != nil {
return nil, err
}
err = client.StartTLS(tlsConfig)
if err != nil {
return nil, err
}
}
} else {
conn, err := tls.Dial("tcp", serverName, tlsConfig)
if err != nil {
conn, err := net.DialTimeout("tcp", serverName, 10*time.Second)
if err != nil {
return nil, err
}
client, err = smtp.NewClient(conn, host)
if err != nil {
return nil, err
}
err = client.StartTLS(tlsConfig)
if err != nil {
return nil, err
}
} else {
client, err = smtp.NewClient(conn, host)
if err != nil {
return nil, err
}
}
}
// 认证
var auth = smtp.PlainAuth("", username, password, host)
if err := client.Auth(auth); err != nil {
_ = client.Quit()
_ = client.Close()
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,78 @@
//go:build plus
package mediasenders_test
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/senders/mediasenders"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
)
func TestEmailMedia_Send(t *testing.T) {
dbs.NotifyReady()
var media = mediasenders.NewEmailMedia()
media.SMTP = "smtp.qq.com:587"
media.Username = "19644627@qq.com"
media.Password = "123456" // 换成你的邮件密码或者授权码
media.From = "19644627@qq.com"
//media.FromName = "测试员"
//media.FromName = "\"测试员\""
//var subject = "This is test subject"
var subject = "这是中文标题"
_, err := media.Send("iwind.liu@gmail.com", subject, "This is a test body <strong>粗体哦</strong><br/>换行哦", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Fatal(err)
}
}
func TestEmailMedia_SendMails(t *testing.T) {
dbs.NotifyReady()
var media = mediasenders.NewEmailMedia()
media.SMTP = "smtp.qq.com:587"
media.Username = "19644627@qq.com"
media.Password = "123456" // 换成你的邮件密码或者授权码
media.From = "19644627@qq.com"
//media.FromName = "测试员"
//media.FromName = "\"测试员\""
err := media.SendMails([]*mediasenders.MailInfo{
{
To: "iwind.liu@gmail.com",
Subject: "This is test subject",
Body: "This is a test body <strong>粗体哦</strong><br/>换行哦",
},
{
To: "iwind.liu@gmail.com",
Subject: "This is test subject 2",
Body: "This is a test body 2 <strong>粗体哦</strong><br/>换行哦",
},
{
To: "q@yun4s.cn",
Subject: "This is test subject 3",
Body: "This is a test body 3 <strong>粗体哦</strong><br/>换行哦",
},
{
To: "root@teaos.cn",
Subject: "This is test subject 4",
Body: "This is a test body 4 <strong>粗体哦</strong><br/>换行哦",
},
}, "")
if err != nil {
t.Fatal(err)
}
}
func TestEmailMedia_Send_163(t *testing.T) {
var media = mediasenders.NewEmailMedia()
media.SMTP = "smtp.163.com:465"
media.Username = "iwind_php@163.com"
media.Password = "123456" // 换成你的邮件密码或者授权码
media.From = "iwind_php@163.com"
_, err := media.Send("iwind_php@163.com", "This is test subject", "This is a test body <strong>粗体哦</strong><br/>换行哦", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,137 @@
//go:build plus
package mediasenders
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/iwind/TeaGo/maps"
"io"
"net/http"
"net/url"
"time"
)
// QyWeixinMedia 企业微信媒介
type QyWeixinMedia struct {
CorporateId string `yaml:"corporateId" json:"corporateId"`
AgentId string `yaml:"agentId" json:"agentId"`
AppSecret string `yaml:"appSecret" json:"appSecret"`
TextFormat TextFormat `yaml:"textFormat" json:"textFormat"`
}
// NewQyWeixinMedia 获取新对象
func NewQyWeixinMedia() *QyWeixinMedia {
return &QyWeixinMedia{}
}
func (this *QyWeixinMedia) Send(user string, subject string, body string, productName string, datetime string) (respData []byte, err error) {
// 获取Token
u := "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + url.QueryEscape(this.CorporateId) + "&corpsecret=" + url.QueryEscape(this.AppSecret)
req1, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return nil, err
}
client := utils.SharedHttpClient(5 * time.Second)
resp1, err := client.Do(req1)
if err != nil {
return nil, err
}
defer resp1.Body.Close()
if resp1.StatusCode != http.StatusOK {
return nil, errors.New("status code not 200")
}
data, err := io.ReadAll(resp1.Body)
if err != nil {
return nil, err
}
m := maps.Map{}
err = json.Unmarshal(data, &m)
if err != nil {
return data, err
}
errCode := m.GetInt("errcode")
if errCode > 0 {
return data, errors.New("error code:" + fmt.Sprintf("%d", errCode))
}
accessToken := m.GetString("access_token")
if len(user) == 0 {
user = "@all"
}
// 发送消息
u = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + url.QueryEscape(accessToken)
msg := maps.Map{
"touser": user,
"toparty": "",
"totag": "",
"toall": 0,
"agentid": this.AgentId,
"safe": 0,
}
if this.TextFormat == FormatMarkdown {
msg["msgtype"] = "markdown"
msg["markdown"] = maps.Map{
"content": subject + "\n" + body,
}
} else {
msg["msgtype"] = "text"
msg["text"] = maps.Map{
"content": subject + "\n" + body,
}
}
data, err = json.Marshal(msg)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, u, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New("status not 200")
}
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
m = maps.Map{}
err = json.Unmarshal(data, &m)
if err != nil {
return nil, err
}
errCode = m.GetInt("errcode")
if errCode != 0 {
return data, errors.New("errcode " + fmt.Sprintf("%d", errCode))
}
invalidUser := m.GetString("invaliduser")
if len(invalidUser) > 0 {
return data, errors.New("invalid users:" + invalidUser)
}
return data, nil
}
// RequireUser 是否需要用户标识
func (this *QyWeixinMedia) RequireUser() bool {
return false
}

View File

@@ -0,0 +1,82 @@
//go:build plus
package mediasenders
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/utils/string"
"io"
"net/http"
"strings"
"time"
)
// QyWeixinRobotMedia 企业微信群机器人媒介
type QyWeixinRobotMedia struct {
WebHookURL string `yaml:"webHookURL" json:"webHookURL"`
TextFormat TextFormat `yaml:"textFormat" json:"textFormat"`
}
// NewQyWeixinRobotMedia 获取新对象
func NewQyWeixinRobotMedia() *QyWeixinRobotMedia {
return &QyWeixinRobotMedia{}
}
func (this *QyWeixinRobotMedia) Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error) {
if len(this.WebHookURL) == 0 {
return nil, errors.New("webHook url should not be empty")
}
mobiles := []string{}
if len(user) > 0 {
for _, u := range strings.Split(user, ",") {
u = strings.TrimSpace(u)
if len(u) > 0 {
mobiles = append(mobiles, u)
}
}
}
var content maps.Map
if this.TextFormat == FormatMarkdown { // markdown
content = maps.Map{
"msgtype": "markdown",
"markdown": maps.Map{
"content": subject + "\n" + body,
"mentioned_mobile_list": mobiles,
},
}
} else {
content = maps.Map{
"msgtype": "text",
"text": maps.Map{
"content": subject + "\n" + body,
"mentioned_mobile_list": mobiles,
},
}
}
reader := bytes.NewBufferString(stringutil.JSONEncode(content))
req, err := http.NewRequest(http.MethodPost, this.WebHookURL, reader)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := utils.SharedHttpClient(5 * time.Second)
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
resp, err = io.ReadAll(response.Body)
return
}
// RequireUser 是否需要用户标识
func (this *QyWeixinRobotMedia) RequireUser() bool {
return false
}

View File

@@ -0,0 +1,20 @@
//go:build plus
package mediasenders
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
)
func TestQyWeixinRobotMedia_Send(t *testing.T) {
media := NewQyWeixinRobotMedia()
media.WebHookURL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=123456" //需要换成你自己的webhook
media.TextFormat = FormatText
resp, err := media.Send("", "这是标题", "*这是内容*", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Fatal(err)
}
t.Log("resp:", string(resp))
}

View File

@@ -0,0 +1,22 @@
//go:build plus
package mediasenders
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
)
func TestNewQyWeixinMedia(t *testing.T) {
m := NewQyWeixinMedia()
m.CorporateId = "xxx"
m.AppSecret = "xxx"
m.AgentId = "1000003"
resp, err := m.Send("", "标题:报警标题", "内容:报警内容/全员都有", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Log(string(resp))
t.Fatal(err)
}
t.Log(string(resp))
}

View File

@@ -0,0 +1,136 @@
//go:build plus
package mediasenders
import (
"bytes"
"errors"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/string"
"os"
"os/exec"
"runtime"
"strings"
)
// ScriptMedia 脚本媒介
type ScriptMedia struct {
Path string `yaml:"path" json:"path"`
ScriptType string `yaml:"scriptType" json:"scriptType"` // 脚本类型可以为path, code
ScriptLang string `yaml:"scriptLang" json:"scriptLang"` // 脚本语言
Script string `yaml:"script" json:"script"` // 脚本代码
Cwd string `yaml:"cwd" json:"cwd"`
Env []*Variable `yaml:"env" json:"env"`
}
// NewScriptMedia 获取新对象
func NewScriptMedia() *ScriptMedia {
return &ScriptMedia{}
}
// AddEnv 添加环境变量
func (this *ScriptMedia) AddEnv(name, value string) {
this.Env = append(this.Env, &Variable{
Name: name,
Value: value,
})
}
// FormattedScript 格式化脚本
func (this *ScriptMedia) FormattedScript() string {
script := this.Script
script = strings.Replace(script, "\r", "", -1)
return script
}
// Generate 保存到本地
func (this *ScriptMedia) Generate(id string) (path string, err error) {
var tmpDir = os.TempDir()
if runtime.GOOS == "windows" {
path = tmpDir + Tea.DS + "edge.script." + id + ".bat"
} else {
path = tmpDir + "/edge.script." + id + ".script"
}
shFile := files.NewFile(path)
if !shFile.Exists() {
err = shFile.WriteString(this.FormattedScript())
if err != nil {
return
}
err = shFile.Chmod(0777)
if err != nil {
return
}
}
return
}
// Send 发送
func (this *ScriptMedia) Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error) {
// 脚本
if this.ScriptType == "code" {
path, err := this.Generate(stringutil.Rand(16))
if err != nil {
return nil, err
}
this.Path = path
defer func() {
f := files.NewFile(this.Path)
if f.Exists() {
err := f.Delete()
if err != nil {
logs.Error(err)
}
}
}()
}
if len(this.Path) == 0 {
return nil, errors.New("'path' should be specified")
}
cmd := exec.Command(this.Path)
if len(this.Env) > 0 {
for _, env := range this.Env {
cmd.Env = append(cmd.Env, env.Name+"="+env.Value)
}
}
cmd.Env = append(cmd.Env, "MessageUser="+user)
cmd.Env = append(cmd.Env, "MessageSubject="+subject)
cmd.Env = append(cmd.Env, "MessageBody="+body)
if len(this.Cwd) > 0 {
cmd.Dir = this.Cwd
}
stdout := bytes.NewBuffer([]byte{})
stderr := bytes.NewBuffer([]byte{})
cmd.Stdout = stdout
cmd.Stderr = stderr
err = cmd.Start()
if err != nil {
return nil, err
}
err = cmd.Wait()
if err != nil {
// do nothing
}
if stderr.Len() > 0 {
return stdout.Bytes(), errors.New(stderr.String())
}
return stdout.Bytes(), nil
}
// RequireUser 是否需要用户标识
func (this *ScriptMedia) RequireUser() bool {
return false
}

View File

@@ -0,0 +1,50 @@
//go:build plus
package mediasenders
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
)
func TestScriptMedia_Send(t *testing.T) {
script := `#!/usr/bin/env bash
echo "subject:${MessageSubject}"
echo "body:${MessageBody}"
`
tmp := files.NewFile(Tea.Root + "/web/tmp/media_test.sh")
err := tmp.WriteString(script)
if err != nil {
t.Fatal(err)
}
_ = tmp.Chmod(0777)
defer func() {
_ = tmp.Delete()
}()
media := NewScriptMedia()
media.Path = tmp.Path()
_, err = media.Send("zhangsan", "this is subject", "this is body", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Fatal(err)
}
}
func TestScriptMedia_Send2(t *testing.T) {
media := NewScriptMedia()
media.ScriptType = "code"
media.Script = `#!/usr/bin/env bash
echo "subject:${MessageSubject}"
echo "body:${MessageBody}"
`
_, err := media.Send("zhangsan", "this is subject", "this is body", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,75 @@
//go:build plus
package mediasenders
import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/iwind/TeaGo/types"
"net/http"
"net/url"
"time"
)
// TelegramMedia Telegram媒介
type TelegramMedia struct {
Token string `yaml:"token" json:"token"`
ProxyURL string `yaml:"proxyURL" json:"proxyURL"`
}
// NewTelegramMedia 获取新对象
func NewTelegramMedia() *TelegramMedia {
return &TelegramMedia{}
}
// Send 发送消息
func (this *TelegramMedia) Send(user string, subject string, body string, productName string, datetime string) (respBytes []byte, err error) {
var proxyFunc func(req *http.Request) (*url.URL, error)
if len(this.ProxyURL) > 0 {
proxyFunc = func(req *http.Request) (*url.URL, error) {
return url.Parse(this.ProxyURL)
}
}
var httpClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 4096,
MaxIdleConnsPerHost: 32,
MaxConnsPerHost: 32,
IdleConnTimeout: 2 * time.Minute,
ExpectContinueTimeout: 1 * time.Second,
TLSHandshakeTimeout: 0,
Proxy: proxyFunc,
},
}
defer httpClient.CloseIdleConnections()
bot, err := tgbotapi.NewBotAPIWithClient(this.Token, httpClient)
if err != nil {
return nil, err
}
bot.Client = httpClient
var text string
if len(productName) > 0 {
text += productName
if len(datetime) > 0 {
text += " AT " + datetime
}
text += "\n----\n"
} else if len(datetime) > 0 {
text += "AT " + datetime
text += "\n----\n"
}
text += subject + "\n" + body
var msg = tgbotapi.NewMessage(types.Int64(user), text)
_, err = bot.Send(msg)
return nil, err
}
// RequireUser 是否需要用户标识
func (this *TelegramMedia) RequireUser() bool {
return true
}

View File

@@ -0,0 +1,21 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package mediasenders_test
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/senders/mediasenders"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
)
func TestTelegramMedia_Send(t *testing.T) {
var media = mediasenders.NewTelegramMedia()
media.Token = "YOUR BOT TOKEN" // from @BotFather
//media.ProxyURL = "sock5://192.168.2.41:7890"
_, err := media.Send("YOUR ID" /** from @get_id_bot **/, "test subject", "test body", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,156 @@
//go:build plus
package mediasenders
import (
"encoding/json"
"errors"
"reflect"
)
// MediaType 通知媒介类型
type MediaType = string
const (
MediaTypeEmail MediaType = "email"
MediaTypeWebHook MediaType = "webHook"
MediaTypeScript MediaType = "script"
MediaTypeDingTalk MediaType = "dingTalk"
MediaTypeQyWeixin MediaType = "qyWeixin"
MediaTypeQyWeixinRobot MediaType = "qyWeixinRobot"
MediaTypeAliyunSms MediaType = "aliyunSms"
MediaTypeTelegram MediaType = "telegram"
)
type MediaDefinition struct {
Name string `json:"name"`
Code MediaType `json:"code"`
SupportsHTML bool `json:"supportsHtml"`
Instance MediaInterface `json:"-"`
Description string `json:"description"`
User string `json:"user"`
IsOn bool `json:"isOn"`
}
// AllMediaTypes 所有媒介
func AllMediaTypes() []*MediaDefinition {
return []*MediaDefinition{
{
Name: "邮件",
Code: MediaTypeEmail,
SupportsHTML: true,
Instance: new(EmailMedia),
Description: "通过邮件发送通知。",
User: "接收人邮箱地址。",
IsOn: true,
},
{
Name: "WebHook",
Code: MediaTypeWebHook,
SupportsHTML: false,
Instance: new(WebHookMedia),
Description: "通过HTTP请求发送通知。",
User: "通过${MessageUser}参数传递到URL上。",
IsOn: true,
},
{
Name: "脚本",
Code: MediaTypeScript,
SupportsHTML: false,
Instance: new(ScriptMedia),
Description: "通过运行脚本发送通知。",
User: "可以在脚本中使用${MessageUser}来获取这个标识。",
IsOn: true,
},
{
Name: "钉钉群机器人",
Code: MediaTypeDingTalk,
SupportsHTML: false,
Instance: new(DingTalkMedia),
Description: "通过钉钉群机器人发送通知消息。",
User: "要At@)的群成员的手机号,多个手机号用英文逗号隔开,也可以为空。",
IsOn: true,
},
{
Name: "企业微信应用",
Code: MediaTypeQyWeixin,
SupportsHTML: false,
Instance: new(QyWeixinMedia),
Description: "通过企业微信应用发送通知消息。",
User: "接收消息的成员的用户账号,多个成员用竖线(|)分隔,如果所有成员使用@all。留空表示所有成员。",
IsOn: true,
},
{
Name: "企业微信群机器人",
Code: MediaTypeQyWeixinRobot,
SupportsHTML: false,
Instance: new(QyWeixinRobotMedia),
Description: "通过微信群机器人发送通知消息。",
User: "要At@)的群成员的手机号,多个手机号用英文逗号隔开,也可以为空。",
IsOn: true,
},
{
Name: "阿里云短信",
Code: MediaTypeAliyunSms,
SupportsHTML: false,
Instance: new(AliyunSmsMedia),
Description: "通过<a href=\"https://www.aliyun.com/product/sms\" target=\"_blank\">阿里云短信服务</a>发送短信。",
User: "接收消息的手机号。",
IsOn: true,
},
{
Name: "Telegram机器人",
Code: MediaTypeTelegram,
SupportsHTML: false,
Instance: new(TelegramMedia),
Description: "通过机器人向群或者某个用户发送消息需要确保所在网络能够访问Telegram API服务。",
User: "群或用户的Chat ID通常是一个数字可以通过和 @get_id_bot 建立对话并发送任意消息获得。",
IsOn: true,
},
}
}
// FindMediaType 查找媒介类型
func FindMediaType(mediaType string) *MediaDefinition {
for _, m := range AllMediaTypes() {
if m.Code == mediaType {
return m
}
}
return nil
}
// NewMediaInstance 查找媒介实例
func NewMediaInstance(mediaType string, optionsJSON []byte) (MediaInterface, error) {
for _, m := range AllMediaTypes() {
if m.Code == mediaType {
var media = reflect.New(reflect.TypeOf(m.Instance).Elem()).Interface().(MediaInterface)
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, media)
if err != nil {
return nil, errors.New("decode media options failed: " + err.Error())
}
}
return media, nil
}
}
return nil, errors.New("can not find media with type '" + mediaType + "'")
}
// FindMediaTypeName 查找媒介类型名称
func FindMediaTypeName(mediaType string) string {
var m = FindMediaType(mediaType)
if m == nil {
return ""
}
return m.Name
}
// MediaInterface 媒介接口
type MediaInterface interface {
// Send 发送
Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error)
// RequireUser 是否可以需要用户标识
RequireUser() bool
}

View File

@@ -0,0 +1,148 @@
//go:build plus
package mediasenders
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/iwind/TeaGo/types"
"github.com/iwind/TeaGo/utils/string"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// WebHookMedia WebHook媒介
type WebHookMedia struct {
URL string `yaml:"url" json:"url"` // URL中可以使用${MessageSubject}, ${MessageBody}两个变量
Method string `yaml:"method" json:"method"`
ContentType string `yaml:"contentType" json:"contentType"` // 内容类型params|body
Headers []*Variable `yaml:"headers" json:"headers"`
Params []*Variable `yaml:"params" json:"params"`
Body string `yaml:"body" json:"body"`
}
// NewWebHookMedia 获取新对象
func NewWebHookMedia() *WebHookMedia {
return &WebHookMedia{}
}
// Send 发送
func (this *WebHookMedia) Send(user string, subject string, body string, productName string, datetime string) (resp []byte, err error) {
if len(this.URL) == 0 {
return nil, errors.New("'url' should be specified")
}
var timeout = 10 * time.Second
if len(this.Method) == 0 {
this.Method = http.MethodGet
}
this.URL = strings.Replace(this.URL, "${MessageUser}", url.QueryEscape(user), -1)
this.URL = strings.Replace(this.URL, "${MessageSubject}", url.QueryEscape(subject), -1)
this.URL = strings.Replace(this.URL, "${MessageBody}", url.QueryEscape(body), -1)
var req *http.Request
if this.Method == http.MethodGet {
req, err = http.NewRequest(this.Method, this.URL, nil)
} else {
var params = url.Values{
"MessageUser": []string{user},
"MessageSubject": []string{subject},
"MessageBody": []string{body},
}
var postBody = ""
if this.ContentType == "params" {
for _, param := range this.Params {
param.Value = strings.Replace(param.Value, "${MessageUser}", user, -1)
param.Value = strings.Replace(param.Value, "${MessageSubject}", subject, -1)
param.Value = strings.Replace(param.Value, "$MessageBody}", body, -1)
params.Add(param.Name, param.Value)
}
postBody = params.Encode()
} else if this.ContentType == "body" {
userJSON := stringutil.JSONEncode(user)
subjectJSON := stringutil.JSONEncode(subject)
bodyJSON := stringutil.JSONEncode(body)
if len(userJSON) > 0 {
userJSON = userJSON[1 : len(userJSON)-1]
}
if len(subjectJSON) > 0 {
subjectJSON = subjectJSON[1 : len(subjectJSON)-1]
}
if len(bodyJSON) > 0 {
bodyJSON = bodyJSON[1 : len(bodyJSON)-1]
}
postBody = strings.Replace(this.Body, "${MessageUser}", userJSON, -1)
postBody = strings.Replace(postBody, "${MessageSubject}", subjectJSON, -1)
postBody = strings.Replace(postBody, "${MessageBody}", bodyJSON, -1)
} else {
postBody = params.Encode()
}
req, err = http.NewRequest(this.Method, this.URL, strings.NewReader(postBody))
}
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
if this.Method == http.MethodPost {
if this.ContentType == "params" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}
if len(this.Headers) > 0 {
for _, h := range this.Headers {
req.Header.Set(h.Name, h.Value)
}
}
var client = utils.SharedHttpClient(timeout)
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() {
_ = response.Body.Close()
}()
data, err := io.ReadAll(response.Body)
if err != nil {
return data, err
}
// check response code
if response.StatusCode >= 400 {
err = errors.New("invalid response code '" + types.String(response.StatusCode) + "'")
}
return data, err
}
// RequireUser 是否需要用户标识
func (this *WebHookMedia) RequireUser() bool {
return false
}
// AddHeader 添加Header
func (this *WebHookMedia) AddHeader(name string, value string) {
this.Headers = append(this.Headers, &Variable{
Name: name,
Value: value,
})
}
// AddParam 添加参数
func (this *WebHookMedia) AddParam(name string, value string) {
this.Params = append(this.Params, &Variable{
Name: name,
Value: value,
})
}

View File

@@ -0,0 +1,16 @@
//go:build plus
package mediasenders
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
)
func TestMediaWebHook_Send(t *testing.T) {
media := NewWebHookMedia()
media.URL = "http://127.0.0.1:9991/webhook?subject=${MessageSubject}&body=${MessageBody}"
resp, err := media.Send("zhangsan", "this is subject", "this is body", teaconst.GlobalProductName, timeutil.Format("Y-m-d H:i:s"))
t.Log(string(resp), err)
}

View File

@@ -0,0 +1,17 @@
//go:build plus
package mediasenders
// Variable 变量
type Variable struct {
Name string `yaml:"name" json:"name"` // 变量名
Value string `yaml:"value" json:"value"` // 变量值
}
// 创建新变量
func NewVariable(name string, value string) *Variable {
return &Variable{
Name: name,
Value: value,
}
}

View File

@@ -0,0 +1,86 @@
//go:build plus
package smssenders
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/iwind/TeaGo/maps"
)
// AliyunSMSSender 阿里云短信
type AliyunSMSSender struct {
params *userconfigs.SMSSenderAliyunSMSParams
}
// NewAliyunSMSSender 获取新对象
func NewAliyunSMSSender() *AliyunSMSSender {
return &AliyunSMSSender{}
}
// Init 初始化
func (this *AliyunSMSSender) Init(params any) error {
if params == nil {
return errors.New("'params' should not be nil")
}
var ok bool
this.params, ok = params.(*userconfigs.SMSSenderAliyunSMSParams)
if !ok {
return errors.New("invalid params type")
}
return nil
}
// Send 发送短信
func (this *AliyunSMSSender) Send(mobile string, body string, code string) (resp string, isOk bool, err error) {
// {"Message":"OK","RequestId":"xxxx","BizId":"xxxx","Code":"OK"}
client, err := sdk.NewClientWithAccessKey("cn-hangzhou", this.params.AccessKeyId, this.params.AccessKeySecret)
if err != nil {
panic(err)
}
var request = requests.NewCommonRequest()
request.Method = "POST"
request.Scheme = "https" // https | http
request.Domain = "dysmsapi.aliyuncs.com"
request.Version = "2017-05-25"
request.ApiName = "SendSms"
request.QueryParams["RegionId"] = "cn-hangzhou"
request.QueryParams["PhoneNumbers"] = mobile // TODO 支持海外手机号?
request.QueryParams["SignName"] = this.params.Sign
request.QueryParams["TemplateCode"] = this.params.TemplateCode
var codeVarName = this.params.CodeVarName
if len(codeVarName) == 0 {
codeVarName = "code"
}
templateParamJSON, err := json.Marshal(map[string]string{
codeVarName: code,
})
if err != nil {
return "", false, err
}
request.QueryParams["TemplateParam"] = string(templateParamJSON)
response, err := client.ProcessCommonRequest(request)
if err != nil {
return "", false, err
}
var data = response.GetHttpContentBytes()
m := maps.Map{}
err = json.Unmarshal(data, &m)
if err != nil {
return string(data), false, err
}
if m.GetString("Code") == "OK" {
return "", true, nil
}
return string(data), false, errors.New("fail to send sms" + string(data))
}

View File

@@ -0,0 +1,29 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package smssenders_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/senders/smssenders"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"testing"
)
func TestAliyunSMSSender_Send(t *testing.T) {
var sender = smssenders.NewAliyunSMSSender()
err := sender.Init(&userconfigs.SMSSenderAliyunSMSParams{
Sign: "SIGN",
TemplateCode: "TEMPLATE CODE",
CodeVarName: "code1",
AccessKeyId: "ACCESS KEY ID",
AccessKeySecret: "ACCESS KEY SECRET",
})
if err != nil {
t.Fatal(err)
}
_, _, err = sender.Send("13888888888", "", "1234")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,12 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package smssenders
type SenderInterface interface {
// Init 初始化
Init(params any) error
// Send 发送短信
Send(mobile string, body string, code string) (resp string, isOk bool, err error)
}

View File

@@ -0,0 +1,69 @@
//go:build plus
package smssenders
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms
)
// 相关文档https://cloud.tencent.com/document/product/382/43199
// TencentSMSSender 腾讯云短信
type TencentSMSSender struct {
params *userconfigs.SMSSenderTencentSMSParams
}
// NewTencentSMSSender 获取新对象
func NewTencentSMSSender() *TencentSMSSender {
return &TencentSMSSender{}
}
// Init 初始化
func (this *TencentSMSSender) Init(params any) error {
if params == nil {
return errors.New("'params' should not be nil")
}
var ok bool
this.params, ok = params.(*userconfigs.SMSSenderTencentSMSParams)
if !ok {
return errors.New("invalid params type")
}
return nil
}
// Send 发送短信
func (this *TencentSMSSender) Send(mobile string, body string, code string) (resp string, isOk bool, err error) {
var credential = common.NewCredential(this.params.AccessKeyId, this.params.AccessKeySecret)
var cpf = profile.NewClientProfile()
cpf.HttpProfile.ReqMethod = "POST"
// cpf.HttpProfile.ReqTimeout = 5
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
cpf.SignMethod = "HmacSHA1"
client, err := sms.NewClient(credential, "ap-guangzhou", cpf)
if err != nil {
return "", false, err
}
var request = sms.NewSendSmsRequest()
request.SmsSdkAppId = common.StringPtr(this.params.SDKAppId)
request.SignName = common.StringPtr(this.params.Sign)
request.TemplateId = common.StringPtr(this.params.TemplateId)
request.TemplateParamSet = common.StringPtrs([]string{code})
request.PhoneNumberSet = common.StringPtrs([]string{"+86" + mobile}) // TODO 支持海外手机号?
request.SessionContext = common.StringPtr("")
request.ExtendCode = common.StringPtr("")
request.SenderId = common.StringPtr("")
// 通过client对象调用想要访问的接口需要传入请求对象
_, err = client.SendSms(request)
if err != nil {
return "", false, err
}
return "", true, nil
}

View File

@@ -0,0 +1,28 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package smssenders_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/senders/smssenders"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"testing"
)
func TestTencentSMSSender_Send(t *testing.T) {
var sender = smssenders.NewAliyunSMSSender()
err := sender.Init(&userconfigs.SMSSenderTencentSMSParams{
SDKAppId: "123456",
Sign: "SIGN",
TemplateId: "123456",
AccessKeyId: "ACCESS KEY ID",
AccessKeySecret: "ACCESS KEY SECRET",
})
if err != nil {
t.Fatal(err)
}
_, _, err = sender.Send("13888888888", "", "1234")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package smssenders
import (
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"net/url"
"strings"
"time"
)
type WebHookSender struct {
params *userconfigs.SMSSenderWebHookParams
}
func NewWebHookSender() *WebHookSender {
return &WebHookSender{}
}
func (this *WebHookSender) Init(params any) error {
if params == nil {
return errors.New("'params' should not be nil")
}
var ok bool
this.params, ok = params.(*userconfigs.SMSSenderWebHookParams)
if !ok {
return errors.New("invalid params type")
}
return nil
}
// Send 发送短信
func (this *WebHookSender) Send(mobile string, body string, code string) (result string, isOk bool, err error) {
var encodedQuery = (&url.Values{
"mobile": []string{mobile},
"body": []string{body},
"code": []string{code},
}).Encode()
var webHookURL = this.params.URL
var reader io.Reader
if this.params.Method == "POST" {
reader = strings.NewReader(encodedQuery)
} else {
if strings.Contains(webHookURL, "?") {
webHookURL += "&" + encodedQuery
} else {
webHookURL += "?" + encodedQuery
}
}
req, err := http.NewRequest(this.params.Method, webHookURL, reader)
if err != nil {
return "", false, fmt.Errorf("create request failed: %w", err)
}
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
if this.params.Method == "POST" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
resp, err := utils.SharedHttpClient(10 * time.Second).Do(req)
if err != nil {
return "", false, err
}
if resp == nil || resp.Body == nil {
return "", false, errors.New("invalid response")
}
defer func() {
_ = resp.Body.Close()
}()
respData, err := io.ReadAll(resp.Body)
if err != nil {
return "", false, err
}
if resp.StatusCode != http.StatusOK {
return "StatusCode: " + types.String(resp.StatusCode) + ": " + string(respData), false, nil
}
return string(respData), true, nil
}

View File

@@ -0,0 +1,62 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package smssenders_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/senders/smssenders"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"net/http"
"testing"
)
func TestWebHookSender_Send_Get(t *testing.T) {
var sender = smssenders.NewWebHookSender()
err := sender.Init(&userconfigs.SMSSenderWebHookParams{
URL: "http://192.168.2.41:2345/sms/get",
Method: http.MethodGet,
})
if err != nil {
t.Fatal(err)
}
resp, isOk, err := sender.Send("13888888888", "THIS IS BODY", "1234")
if err != nil {
t.Fatal(err)
}
t.Log(isOk)
t.Log("response:", resp)
}
func TestWebHookSender_Send_Get_Fail(t *testing.T) {
var sender = smssenders.NewWebHookSender()
err := sender.Init(&userconfigs.SMSSenderWebHookParams{
URL: "http://192.168.2.41:2345/404",
Method: http.MethodGet,
})
if err != nil {
t.Fatal(err)
}
resp, isOk, err := sender.Send("13888888888", "THIS IS BODY", "1234")
if err != nil {
t.Fatal(err)
}
t.Log(isOk)
t.Log("response:", resp)
}
func TestWebHookSender_Send_POST(t *testing.T) {
var sender = smssenders.NewWebHookSender()
err := sender.Init(&userconfigs.SMSSenderWebHookParams{
URL: "http://192.168.2.41:2345/sms/post",
Method: http.MethodPost,
})
if err != nil {
t.Fatal(err)
}
resp, isOk, err := sender.Send("13888888888", "THIS IS BODY", "1234")
if err != nil {
t.Fatal(err)
}
t.Log(isOk)
t.Log("response:", resp)
}

View File

@@ -0,0 +1,69 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package smssenders
import (
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
)
type SenderDefinition struct {
Name string `json:"name"`
Code string `json:"code"`
}
func FindAllTypes() []*SenderDefinition {
return []*SenderDefinition{
{
Name: "自定义HTTP接口",
Code: userconfigs.SMSSenderWebHook,
},
{
Name: "阿里云短信",
Code: userconfigs.SMSSenderAliyunSMS,
},
{
Name: "腾讯云短信",
Code: userconfigs.SMSSenderTencentSMS,
},
}
}
func CreateSender(senderType userconfigs.SMSSenderType, paramsJSON []byte) (sender SenderInterface, err error) {
var params any
switch senderType {
case userconfigs.SMSSenderWebHook:
sender = NewWebHookSender()
params = &userconfigs.SMSSenderWebHookParams{}
case userconfigs.SMSSenderAliyunSMS:
sender = NewAliyunSMSSender()
params = &userconfigs.SMSSenderAliyunSMSParams{}
case userconfigs.SMSSenderTencentSMS:
sender = NewTencentSMSSender()
params = &userconfigs.SMSSenderTencentSMSParams{}
default:
return nil, errors.New("could not find sender with type '" + senderType + "'")
}
if sender == nil {
return nil, errors.New("could not find sender with type '" + senderType + "'")
}
err = json.Unmarshal(paramsJSON, params)
if err != nil {
err = fmt.Errorf("decode params failed: %w", err)
return
}
err = sender.Init(params)
if err != nil {
err = fmt.Errorf("initialize sender failed: %w", err)
return
}
return
}