package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"net/http"
"os"
"time"
// Load and register the drivers.
_ "github.com/xgfone/go-msgnotice/driver/drivers/email"
_ "github.com/xgfone/go-msgnotice/driver/drivers/feishu"
"github.com/xgfone/go-msgnotice/channel"
"github.com/xgfone/go-msgnotice/channel/manager"
"github.com/xgfone/go-msgnotice/driver"
"github.com/xgfone/go-msgnotice/driver/builder"
"github.com/xgfone/go-msgnotice/driver/middleware"
"github.com/xgfone/go-msgnotice/driver/middleware/logger"
"github.com/xgfone/go-msgnotice/driver/middleware/timeout"
)
var (
listenaddr = flag.String("listenaddr", "127.0.0.1:80", "The address to listen on.")
channelsfile = flag.String("channelsfile", "channels.json", "The file path storing the channels.")
)
func _loadFromFile(filepath string, dst any, cb func() error) (err error) {
data, err := os.ReadFile(filepath)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return
} else if len(data) == 0 {
return
}
if err = json.Unmarshal(data, dst); err != nil {
return
}
return cb()
}
func initChannels() {
type Channel struct {
ChannelName string
DriverName string
DriverConf map[string]any
}
var channels []Channel
var _channels []channel.Channel
err := _loadFromFile(*channelsfile, &channels, func() (err error) {
for _, c := range channels {
if c.ChannelName == "" || c.DriverName == "" {
return errors.New("missing the channel or driver name")
}
driver, err := builder.Build(c.DriverName, c.DriverConf)
if err != nil {
return err
}
_channels = append(_channels, channel.New(c.ChannelName, driver))
}
return
})
if err != nil {
fmt.Println(err)
return
}
manager.Default.UpsertChannels(_channels...)
}
func initMiddlewares() {
middleware.DefaultManager.Use(timeout.New(100, time.Second*10, nil))
middleware.DefaultManager.Use(logger.New(90, nil))
}
func httpHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost)
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
var req struct {
Content any
Channel string
Receiver string
Metadata map[string]any
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if req.Content == nil || req.Channel == "" || req.Receiver == "" {
http.Error(w, "missing the required arguments", 400)
return
}
msg := driver.NewMessage(req.Channel, "", req.Receiver, req.Content, req.Metadata)
err := channel.Send(context.Background(), msg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
flag.Parse()
initMiddlewares()
initChannels()
http.HandleFunc("/message", httpHandler)
_ = http.ListenAndServe(*listenaddr, nil)
}
// Channels configuration file: channels.json
[
{
"ChannelName": "email",
"DriverName": "email",
"DriverConf": {
"addr": "smtp.example.com",
"from": "username@example.com",
"username": "username@example.com",
"password": "password",
"forcetls": true
}
},
{
"ChannelName": "feishu.webhook",
"DriverName": "feishu.webhook"
}
]
# Run the message notice process.
$ msgnotice &
# Client sends the message.
## Send the message by the email
$ curl http://localhost/message -X POST -H 'content-type: application/json' \
-d '{"Content": {"Subject": "subject", "Content": "test"}, "Channel": "email", "Receiver": "xiegaofeng@bimoai.com"}'
## Send the general text message to feishu by webhook.
$ curl http://localhost/message -X POST -H 'content-type: application/json' \
-d '{"Channel": "feishu.webhook", "Receiver": "9c473cb6-1234-5678-bbf1-147b5e83ab8e", "Content": "<at user_id=\"all\">All</at>"}'
## Send the rich text message to feishu by webhook.
$ curl http://localhost/message -X POST -H 'content-type: application/json' -d '{
"Channel": "feishu.webhook",
"Receiver": "9c473cb6-1234-5678-bbf1-147b5e83ab8e",
"Metadata": {"MsgType": "post"},
"Content": {
"zh_cn": {
"title": "test",
"content": [
[
{"tag": "text", "text": "first paragraph"},
{"tag": "at", "user_id": "all"},
{"tag": "a", "text": "URL", "href": "https://www.example.com"}
],
[
{"tag": "text", "text": "second paragraph"}
]
]
}
}
}'