-
Notifications
You must be signed in to change notification settings - Fork 218
/
wechat.go
145 lines (124 loc) · 3.87 KB
/
wechat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package wechat
import (
"context"
"fmt"
"net/http"
"sync"
"github.com/pkg/errors"
"github.com/silenceper/wechat/v2"
"github.com/silenceper/wechat/v2/cache"
"github.com/silenceper/wechat/v2/officialaccount/config"
"github.com/silenceper/wechat/v2/officialaccount/message"
"github.com/silenceper/wechat/v2/util"
)
type verificationCallbackFunc func(r *http.Request, verified bool)
// Config is the Service configuration.
type Config struct {
AppID string
AppSecret string
Token string
EncodingAESKey string
Cache cache.Cache
}
// wechatMessageManager abstracts go-wechat's message.Manager for writing unit tests
type wechatMessageManager interface {
Send(msg *message.CustomerMessage) error
}
// Service encapsulates the WeChat client along with internal state for storing users.
type Service struct {
config *Config
messageManager wechatMessageManager
userIDs []string
}
// New returns a new instance of a WeChat notification service.
func New(cfg *Config) *Service {
wc := wechat.NewWechat()
wcCfg := &config.Config{
AppID: cfg.AppID,
AppSecret: cfg.AppSecret,
Token: cfg.Token,
EncodingAESKey: cfg.EncodingAESKey,
Cache: cfg.Cache,
}
oa := wc.GetOfficialAccount(wcCfg)
return &Service{
config: cfg,
messageManager: oa.GetCustomerMessageManager(),
}
}
// WaitForOneOffVerification waits for the verification call from the WeChat backend.
//
// Should be running when (re-)applying settings in wechat configuration.
//
// Set devMode to true when using the sandbox.
//
// See https://developers.weixin.qq.com/doc/offiaccount/en/Basic_Information/Access_Overview.html
func (s *Service) WaitForOneOffVerification(serverURL string, devMode bool, callback verificationCallbackFunc) error {
srv := &http.Server{Addr: serverURL}
verificationDone := &sync.WaitGroup{}
verificationDone.Add(1)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
echoStr := query.Get("echostr")
if devMode {
if callback != nil {
callback(r, true)
}
_, _ = w.Write([]byte(echoStr))
// verification done; dev mode
verificationDone.Done()
return
}
// perform signature check
timestamp := query.Get("timestamp")
nonce := query.Get("nonce")
suppliedSignature := query.Get("signature")
computedSignature := util.Signature(s.config.Token, timestamp, nonce)
if suppliedSignature == computedSignature {
if callback != nil {
callback(r, true)
}
_, _ = w.Write([]byte(echoStr))
// verification done; prod mode
verificationDone.Done()
return
}
// verification not done (keep waiting)
if callback != nil {
callback(r, false)
}
})
var err error
go func() {
if innerErr := srv.ListenAndServe(); innerErr != http.ErrServerClosed {
err = errors.Wrapf(innerErr, "failed to start verification listener '%s'", serverURL)
}
}()
// wait until verification is done and shutdown the server
verificationDone.Wait()
if innerErr := srv.Shutdown(context.TODO()); innerErr != nil {
err = errors.Wrap(innerErr, "failed to shutdown verification listerer")
}
return err
}
// AddReceivers takes user ids and adds them to the internal users list. The Send method will send
// a given message to all those users.
func (s *Service) AddReceivers(userIDs ...string) {
s.userIDs = append(s.userIDs, userIDs...)
}
// Send takes a message subject and a message content and sends them to all previously set users.
func (s *Service) Send(ctx context.Context, subject, content string) error {
for _, userID := range s.userIDs {
select {
case <-ctx.Done():
return ctx.Err()
default:
text := fmt.Sprintf("%s\n%s", subject, content)
err := s.messageManager.Send(message.NewCustomerTextMessage(userID, text))
if err != nil {
return errors.Wrapf(err, "failed to send message to WeChat user '%s'", userID)
}
}
}
return nil
}