diff --git a/examples/ding-dong-bot.go b/examples/ding-dong-bot.go index cd0c778..c662e4f 100644 --- a/examples/ding-dong-bot.go +++ b/examples/ding-dong-bot.go @@ -1,17 +1,22 @@ package main import ( - "fmt" - - "github.com/wechaty/go-wechaty/wechaty" + "fmt" + "github.com/wechaty/go-wechaty/wechaty" + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty/user" ) func main() { - _ = wechaty.NewWechaty(). - OnScan(func(qrCode, status string) { - fmt.Printf("Scan QR Code to login: %s\nhttps://api.qrserver.com/v1/create-qr-code/?data=%s\n", status, qrCode) - }). - OnLogin(func(user string) { fmt.Printf("User %s logined\n", user) }). - OnMessage(func(message string) { fmt.Printf("Message: %s\n", message) }). - Start() + _ = wechaty.NewWechaty(). + OnScan(func(qrCode string, status schemas.ScanStatus, data string) { + fmt.Printf("Scan QR Code to login: %v\nhttps://api.qrserver.com/v1/create-qr-code/?data=%s\n", status, qrCode) + }). + OnLogin(func(user string) { + fmt.Printf("User %s logined\n", user) + }). + OnMessage(func(message *user.Message) { + fmt.Println(fmt.Printf("Message: %v\n", message)) + }). + Start() } diff --git a/go.mod b/go.mod index e092880..8a858c8 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,14 @@ module github.com/wechaty/go-wechaty go 1.14 require ( + github.com/bitly/go-simplejson v0.5.0 + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/hashicorp/golang-lru v0.5.4 github.com/otiai10/opengraph v1.1.1 - golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/tools v0.0.0-20200402223321-bcf690261a44 // indirect + github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 + github.com/tuotoo/qrcode v0.0.0-20190222102259-ac9c44189bf2 + github.com/willf/bitset v1.1.10 // indirect + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect + k8s.io/apimachinery v0.18.0 + k8s.io/client-go v11.0.0+incompatible ) diff --git a/wechaty-puppet/file-box/file_box.go b/wechaty-puppet/file-box/file_box.go new file mode 100644 index 0000000..70f5839 --- /dev/null +++ b/wechaty-puppet/file-box/file_box.go @@ -0,0 +1,159 @@ +package file_box + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/bitly/go-simplejson" + "github.com/tuotoo/qrcode" + helper_functions "github.com/wechaty/go-wechaty/wechaty-puppet/helper-functions" + "io/ioutil" + "mime" + "os" + "path/filepath" +) + +type fileImplInterface interface { + toJSONMap() map[string]interface{} + toBytes() ([]byte, error) +} + +// FileBox struct +type FileBox struct { + fileImpl fileImplInterface + name string + metadata map[string]interface{} + boxType FileBoxType + fileBytes []byte + mimeType string +} + +func newFileBox(common *FileBoxJsonObjectCommon, fileImpl fileImplInterface) *FileBox { + return &FileBox{ + fileImpl: fileImpl, + name: common.Name, + metadata: common.Metadata, + boxType: common.BoxType, + mimeType: mime.TypeByExtension(filepath.Ext(common.Name)), + } +} + +func NewFileBoxFromJSONString(s string) (*FileBox, error) { + newJson, err := simplejson.NewJson([]byte(s)) + if err != nil { + return nil, err + } + boxType, err := newJson.Get("boxType").Int64() + if err != nil { + return nil, err + } + switch boxType { + case FileBoxTypeBase64: + fileBoxStruct := new(FileBoxJsonObjectBase64) + if err := json.Unmarshal([]byte(s), fileBoxStruct); err != nil { + return nil, err + } + return NewFileBoxFromJSONObjectBase64(fileBoxStruct), nil + case FileBoxTypeQRCode: + fileBoxStruct := new(FileBoxJsonObjectQRCode) + if err := json.Unmarshal([]byte(s), fileBoxStruct); err != nil { + return nil, err + } + return NewFileBoxFromJSONObjectQRCode(fileBoxStruct), nil + case FileBoxTypeUrl: + fileBoxStruct := new(FileBoxJsonObjectUrl) + if err := json.Unmarshal([]byte(s), fileBoxStruct); err != nil { + return nil, err + } + return NewFileBoxFromJSONObjectUrl(fileBoxStruct), nil + default: + return nil, errors.New("invalid value boxType") + } +} + +func NewFileBoxFromJSONObjectBase64(data *FileBoxJsonObjectBase64) *FileBox { + return newFileBox(data.FileBoxJsonObjectCommon, newFileBoxBase64(data.Base64)) +} + +func NewFileBoxFromJSONObjectUrl(data *FileBoxJsonObjectUrl) *FileBox { + return newFileBox(data.FileBoxJsonObjectCommon, NewFileBoxUrl(data.RemoteUrl, data.Headers)) +} + +func NewFileBoxFromJSONObjectQRCode(data *FileBoxJsonObjectQRCode) *FileBox { + return newFileBox(data.FileBoxJsonObjectCommon, NewFileBoxQRCode(data.QrCode)) +} + +func (fb *FileBox) ToJSONString() (string, error) { + jsonMap := map[string]interface{}{ + "name": fb.name, + "metadata": fb.metadata, + "boxType": fb.boxType, + } + implJsonMap := fb.fileImpl.toJSONMap() + for k, v := range implJsonMap { + jsonMap[k] = v + } + marshal, err := json.Marshal(jsonMap) + return string(marshal), err +} + +func (fb *FileBox) ToFile(filePath string, overwrite bool) error { + if filePath == "" { + filePath = fb.name + } + path, err := os.Getwd() + if err != nil { + return err + } + fullPath := filepath.Join(path, filePath) + if !overwrite && helper_functions.FileExists(fullPath) { + return os.ErrExist + } + fileBytes, err := fb.ToBytes() + if err != nil { + return err + } + return ioutil.WriteFile(filePath, fileBytes, os.ModePerm) +} + +func (fb *FileBox) ToBytes() ([]byte, error) { + if fb.fileBytes != nil { + return fb.fileBytes, nil + } + toBytes, err := fb.fileImpl.toBytes() + if err != nil { + return nil, err + } + fb.fileBytes = toBytes + return fb.fileBytes, nil +} + +func (fb *FileBox) ToBase64() (string, error) { + fileBytes, err := fb.ToBytes() + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(fileBytes), nil +} + +func (fb *FileBox) ToDataURL() (string, error) { + toBase64, err := fb.ToBase64() + if err != nil { + return "", nil + } + return fmt.Sprintf("data:%s;base64,%s", fb.mimeType, toBase64), nil +} + +func (fb *FileBox) ToQrCode() (string, error) { + fileBytes, err := fb.ToBytes() + if err != nil { + return "", err + } + decode, err := qrcode.Decode(bytes.NewReader(fileBytes)) + if err != nil { + return "", nil + } + return decode.Content, nil +} diff --git a/wechaty-puppet/file-box/file_box_base64.go b/wechaty-puppet/file-box/file_box_base64.go new file mode 100644 index 0000000..e5959b0 --- /dev/null +++ b/wechaty-puppet/file-box/file_box_base64.go @@ -0,0 +1,27 @@ +package file_box + +import ( + "encoding/base64" +) + +type fileBoxBase64 struct { + base64Data string +} + +func newFileBoxBase64(base64Data string) *fileBoxBase64 { + return &fileBoxBase64{base64Data: base64Data} +} + +func (fb *fileBoxBase64) toJSONMap() map[string]interface{} { + return map[string]interface{}{ + "base64": fb.base64Data, + } +} + +func (fb *fileBoxBase64) toBytes() ([]byte, error) { + dec, err := base64.StdEncoding.DecodeString(fb.base64Data) + if err != nil { + return nil, err + } + return dec, nil +} diff --git a/wechaty-puppet/file-box/file_box_qrcode.go b/wechaty-puppet/file-box/file_box_qrcode.go new file mode 100644 index 0000000..b8c6874 --- /dev/null +++ b/wechaty-puppet/file-box/file_box_qrcode.go @@ -0,0 +1,27 @@ +package file_box + +import ( + "github.com/skip2/go-qrcode" +) + +type fileBoxQRCode struct { + qrCode string +} + +func NewFileBoxQRCode(qrCode string) *fileBoxQRCode { + return &fileBoxQRCode{qrCode: qrCode} +} + +func (fb *fileBoxQRCode) toJSONMap() map[string]interface{} { + return map[string]interface{}{ + "qrCode": fb.qrCode, + } +} + +func (fb *fileBoxQRCode) toBytes() ([]byte, error) { + qr, err := qrcode.New(fb.qrCode, qrcode.Medium) + if err != nil { + return nil, err + } + return qr.PNG(256) +} diff --git a/wechaty-puppet/file-box/file_box_test.go b/wechaty-puppet/file-box/file_box_test.go new file mode 100644 index 0000000..f744aef --- /dev/null +++ b/wechaty-puppet/file-box/file_box_test.go @@ -0,0 +1,92 @@ +package file_box + +import ( + "encoding/base64" + "io/ioutil" + "log" + "os" + "reflect" + "testing" +) + +func TestFileBox_ToFile(t *testing.T) { + expect := "test content" + fileBox := NewFileBoxFromJSONObjectBase64(&FileBoxJsonObjectBase64{ + FileBoxJsonObjectCommon: &FileBoxJsonObjectCommon{ + Name: "test.text", + Metadata: nil, + BoxType: FileBoxTypeBase64, + }, + Base64: base64.StdEncoding.EncodeToString([]byte(expect)), + }) + const filename = "testdata/test.text" + t.Run("toFile success", func(t *testing.T) { + err := fileBox.ToFile(filename, true) + if err != nil { + log.Fatal(err) + } + file, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + got, err := ioutil.ReadAll(file) + if err != nil { + log.Fatal(err) + } + if expect != string(got) { + log.Fatalf("got %s expect %s", got, expect) + } + }) + t.Run("file exists", func(t *testing.T) { + err := fileBox.ToFile(filename, false) + if err != os.ErrExist { + log.Fatalf("got %s expect %s", err, os.ErrExist) + } + }) +} + +func TestNewFileBoxFromJSONString(t *testing.T) { + tests := []struct { + jsonString string + expectFileImpl reflect.Type + }{ + { + jsonString: `{ +"name":"test.png", +"metadata": null, +"boxType":1, +"base64":"dGVzdCBjb250ZW50" +}`, + expectFileImpl: reflect.TypeOf(new(fileBoxBase64)), + }, + { + jsonString: `{ +"name":"test.png", +"metadata": null, +"boxType":2, +"remoteUrl":"http://www.example.com", +"header":null +}`, + expectFileImpl: reflect.TypeOf(new(fileBoxUrl)), + }, + { + jsonString: `{ +"name":"test.png", +"metadata": null, +"boxType":3, +"qrCode":"test content" +}`, + expectFileImpl: reflect.TypeOf(new(fileBoxQRCode)), + }, + } + for _, t := range tests { + fileBox, err := NewFileBoxFromJSONString(t.jsonString) + if err != nil { + log.Fatal(err) + } + gotReflectType := reflect.TypeOf(fileBox.fileImpl) + if gotReflectType != t.expectFileImpl { + log.Fatalf("got %v expect %v", gotReflectType, t.expectFileImpl) + } + } +} diff --git a/wechaty-puppet/file-box/file_box_url.go b/wechaty-puppet/file-box/file_box_url.go new file mode 100644 index 0000000..62cfb9a --- /dev/null +++ b/wechaty-puppet/file-box/file_box_url.go @@ -0,0 +1,37 @@ +package file_box + +import ( + helper_functions "github.com/wechaty/go-wechaty/wechaty-puppet/helper-functions" + "io/ioutil" + "net/http" +) + +type fileBoxUrl struct { + remoteUrl string + headers http.Header +} + +func NewFileBoxUrl(remoteUrl string, headers http.Header) *fileBoxUrl { + return &fileBoxUrl{remoteUrl: remoteUrl, headers: headers} +} + +func (fb *fileBoxUrl) toJSONMap() map[string]interface{} { + return map[string]interface{}{ + "headers": fb.headers, + "remoteUrl": fb.remoteUrl, + } +} + +func (fb *fileBoxUrl) toBytes() ([]byte, error) { + request, err := http.NewRequest(http.MethodGet, fb.remoteUrl, nil) + if err != nil { + return nil, err + } + request.Header = fb.headers + response, err := helper_functions.HttpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + return ioutil.ReadAll(response.Body) +} diff --git a/wechaty-puppet/file-box/testdata/.gitignore b/wechaty-puppet/file-box/testdata/.gitignore new file mode 100644 index 0000000..0790484 --- /dev/null +++ b/wechaty-puppet/file-box/testdata/.gitignore @@ -0,0 +1 @@ +test.text diff --git a/wechaty-puppet/file-box/type.go b/wechaty-puppet/file-box/type.go new file mode 100644 index 0000000..f46ff5c --- /dev/null +++ b/wechaty-puppet/file-box/type.go @@ -0,0 +1,38 @@ +package file_box + +import "net/http" + +type FileBoxJsonObjectCommon struct { + Name string `json:"name"` + Metadata map[string]interface{} `json:"metadata"` + BoxType FileBoxType `json:"boxType"` +} + +type FileBoxJsonObjectBase64 struct { + *FileBoxJsonObjectCommon + Base64 string `json:"base64"` +} + +type FileBoxJsonObjectUrl struct { + *FileBoxJsonObjectCommon + RemoteUrl string `json:"remoteUrl"` + Headers http.Header `json:"headers"` +} + +type FileBoxJsonObjectQRCode struct { + *FileBoxJsonObjectCommon + QrCode string `json:"qrCode"` +} + +type FileBoxType uint8 + +const ( + FileBoxTypeUnknown FileBoxType = 0 + + FileBoxTypeBase64 = 1 + FileBoxTypeUrl = 2 + FileBoxTypeQRCode = 3 + FileBoxTypeBuffer = 4 + FileBoxTypeFile = 5 + FileBoxTypeStream = 6 +) diff --git a/wechaty-puppet/file_box.go b/wechaty-puppet/file_box.go deleted file mode 100644 index 0043a7f..0000000 --- a/wechaty-puppet/file_box.go +++ /dev/null @@ -1,20 +0,0 @@ -package wechatypuppet - -// FileBox file struct -type FileBox struct { -} - -// ToJSON struct to map -func (f *FileBox) ToJSON() map[string]interface{} { - return nil -} - -// ToFile save to file -func (f *FileBox) ToFile(path string) { - return -} - -// FromQrCode from qr code -func (f *FileBox) FromQrCode(path string) { - return -} diff --git a/wechaty-puppet/helper-functions/file.go b/wechaty-puppet/helper-functions/file.go new file mode 100644 index 0000000..0d1c00b --- /dev/null +++ b/wechaty-puppet/helper-functions/file.go @@ -0,0 +1,14 @@ +package helper_functions + +import "os" + +func FileExists(path string) bool { + _, err := os.Stat(path) + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} diff --git a/wechaty-puppet/helper-functions/http_client.go b/wechaty-puppet/helper-functions/http_client.go new file mode 100644 index 0000000..a3346a0 --- /dev/null +++ b/wechaty-puppet/helper-functions/http_client.go @@ -0,0 +1,13 @@ +package helper_functions + +import ( + "net/http" + "time" +) + +var HttpClient = http.Client{ + Transport: nil, + CheckRedirect: nil, + Jar: nil, + Timeout: 30 * time.Second, +} diff --git a/wechaty-puppet/helper-functions/try_wait.go b/wechaty-puppet/helper-functions/try_wait.go new file mode 100644 index 0000000..e860dc9 --- /dev/null +++ b/wechaty-puppet/helper-functions/try_wait.go @@ -0,0 +1,18 @@ +package helper_functions + +import ( + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/retry" + "time" +) + +var defaultRetry = wait.Backoff{ + Steps: 9, + Duration: 20 * time.Millisecond, + Factor: 3.0, + Jitter: 0.1, +} + +func TryWait(f func() error) error { + return retry.RetryOnConflict(defaultRetry, f) +} diff --git a/wechaty-puppet/memory-card/storage/file.go b/wechaty-puppet/memory-card/storage/file.go index 373186c..b872776 100644 --- a/wechaty-puppet/memory-card/storage/file.go +++ b/wechaty-puppet/memory-card/storage/file.go @@ -2,6 +2,7 @@ package storage import ( "encoding/json" + helper_functions "github.com/wechaty/go-wechaty/wechaty-puppet/helper-functions" "io/ioutil" "os" "path/filepath" @@ -29,7 +30,7 @@ func (f *FileStorage) Save(payload map[string]interface{}) error { } func (f *FileStorage) Load() (map[string]interface{}, error) { - if !exists(f.absFileName) { + if !helper_functions.FileExists(f.absFileName) { return map[string]interface{}{}, nil } file, err := os.Open(f.absFileName) @@ -63,14 +64,3 @@ func handleAbsFileName(absFileName string) (string, error) { } return absFileName, nil } - -func exists(path string) bool { - _, err := os.Stat(path) //os.Stat获取文件信息 - if err != nil { - if os.IsExist(err) { - return true - } - return false - } - return true -} diff --git a/wechaty-puppet/puppet.go b/wechaty-puppet/puppet.go index e30833d..39bfd29 100644 --- a/wechaty-puppet/puppet.go +++ b/wechaty-puppet/puppet.go @@ -1,63 +1,102 @@ package wechatypuppet import ( - lru "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru" + "github.com/wechaty/go-wechaty/wechaty-puppet/file-box" - "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" ) -// PuppetInterface puppet interface -type PuppetInterface interface { - MessageImage(messageID string, imageType schemas.ImageType) FileBox +// puppetInterface puppet interface +type puppetInterface interface { + MessageImage(messageID string, imageType schemas.ImageType) file_box.FileBox + FriendshipPayloadReceive(friendshipID string) schemas.FriendshipPayloadReceive + FriendshipPayloadConfirm(friendshipID string) schemas.FriendshipPayloadConfirm + FriendshipPayloadVerify(friendshipID string) schemas.FriendshipPayloadVerify + FriendshipAccept(friendshipID string) } -// Puppet puppet struce +// Puppet puppet struct type Puppet struct { - PuppetInterface + puppetInterface - CacheMessagePayload *lru.Cache + CacheMessagePayload *lru.Cache + CacheFriendshipPayload *lru.Cache } // MessageList message list func (p *Puppet) MessageList() (ks []string) { - keys := p.CacheMessagePayload.Keys() - for _, v := range keys { - if k, ok := v.(string); ok { - ks = append(ks, k) - } - } - return + keys := p.CacheMessagePayload.Keys() + for _, v := range keys { + if k, ok := v.(string); ok { + ks = append(ks, k) + } + } + return } // MessageSearch search message func (p *Puppet) MessageSearch(query schemas.MessageUserQueryFilter) []string { - allMessageIDList := p.MessageList() - if len(allMessageIDList) <= 0 { - return allMessageIDList - } - - // load - var messagePayloadList []schemas.MessagePayload - for _, v := range allMessageIDList { - messagePayloadList = append(messagePayloadList, p.MessagePayload(v)) - } - // Filter todo:: messageQueryFilterFactory - var messageIDList []string - for _, message := range messagePayloadList { - if message.FromId == query.FromId || message.RoomId == query.RoomId || message.ToId == query.ToId { - messageIDList = append(messageIDList, message.Id) - } - } - - return messageIDList + allMessageIDList := p.MessageList() + if len(allMessageIDList) <= 0 { + return allMessageIDList + } + + // load + var messagePayloadList []schemas.MessagePayload + for _, v := range allMessageIDList { + messagePayloadList = append(messagePayloadList, p.MessagePayload(v)) + } + // Filter todo:: messageQueryFilterFactory + var messageIDList []string + for _, message := range messagePayloadList { + if message.FromId == query.FromId || message.RoomId == query.RoomId || message.ToId == query.ToId { + messageIDList = append(messageIDList, message.Id) + } + } + + return messageIDList } // messageQueryFilterFactory 实现正则和直接匹配 func (p *Puppet) messageQueryFilterFactory(query string) schemas.MessagePayloadFilterFunction { - return nil + return nil } // MessagePayload message payload todo:: no finish func (p *Puppet) MessagePayload(messageID string) (payload schemas.MessagePayload) { - return payload + return payload +} + +// FriendshipPayloadReceive ... +func (p *Puppet) FriendshipPayloadReceive(friendshipID string) schemas.FriendshipPayloadReceive { + cachePayload, ok := p.CacheFriendshipPayload.Get(friendshipID) + if ok { + return cachePayload.(schemas.FriendshipPayloadReceive) + } + payload := p.puppetInterface.FriendshipPayloadReceive(friendshipID) + p.CacheFriendshipPayload.Add(friendshipID, payload) + return payload +} + +// FriendshipPayloadConfirm ... +func (p *Puppet) FriendshipPayloadConfirm(friendshipID string) schemas.FriendshipPayloadConfirm { + cachePayload, ok := p.CacheFriendshipPayload.Get(friendshipID) + if ok { + return cachePayload.(schemas.FriendshipPayloadConfirm) + } + payload := p.puppetInterface.FriendshipPayloadConfirm(friendshipID) + p.CacheFriendshipPayload.Add(friendshipID, payload) + return payload +} + +// FriendshipPayloadVerify ... +func (p *Puppet) FriendshipPayloadVerify(friendshipID string) schemas.FriendshipPayloadVerify { + cachePayload, ok := p.CacheFriendshipPayload.Get(friendshipID) + if ok { + return cachePayload.(schemas.FriendshipPayloadVerify) + } + payload := p.puppetInterface.FriendshipPayloadVerify(friendshipID) + p.CacheFriendshipPayload.Add(friendshipID, payload) + return payload } diff --git a/wechaty-puppet/schemas/events.go b/wechaty-puppet/schemas/events.go index 5b965d2..a7912ca 100644 --- a/wechaty-puppet/schemas/events.go +++ b/wechaty-puppet/schemas/events.go @@ -1,5 +1,6 @@ package schemas +//go:generate stringer -type=ScanStatus type ScanStatus uint8 const ( @@ -12,7 +13,7 @@ const ( ) type EventFriendshipPayload struct { - FriendshipId string + friendshipID string } type EventLoginPayload struct { diff --git a/wechaty-puppet/schemas/friendship.go b/wechaty-puppet/schemas/friendship.go index af9195b..c0299f5 100644 --- a/wechaty-puppet/schemas/friendship.go +++ b/wechaty-puppet/schemas/friendship.go @@ -1,5 +1,6 @@ package schemas +//go:generate stringer -type=FriendshipType type FriendshipType uint8 const ( diff --git a/wechaty-puppet/schemas/friendshiptype_string.go b/wechaty-puppet/schemas/friendshiptype_string.go new file mode 100644 index 0000000..f95ce23 --- /dev/null +++ b/wechaty-puppet/schemas/friendshiptype_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=FriendshipType"; DO NOT EDIT. + +package schemas + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[FriendshipTypeUnknown-0] + _ = x[FriendshipTypeConfirm-1] + _ = x[FriendshipTypeReceive-2] + _ = x[FriendshipTypeVerify-3] +} + +const _FriendshipType_name = "FriendshipTypeUnknownFriendshipTypeConfirmFriendshipTypeReceiveFriendshipTypeVerify" + +var _FriendshipType_index = [...]uint8{0, 21, 42, 63, 83} + +func (i FriendshipType) String() string { + if i >= FriendshipType(len(_FriendshipType_index)-1) { + return "FriendshipType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _FriendshipType_name[_FriendshipType_index[i]:_FriendshipType_index[i+1]] +} diff --git a/wechaty-puppet/schemas/puppet.go b/wechaty-puppet/schemas/puppet.go index 7a11956..5d28bb4 100644 --- a/wechaty-puppet/schemas/puppet.go +++ b/wechaty-puppet/schemas/puppet.go @@ -5,3 +5,29 @@ type PuppetOptions struct { timeout int64 token string } + +type PuppetEventName uint8 + +const ( + PuppetEventNameUnknown PuppetEventName = iota + PuppetEventNameFriendShipConfirm + PuppetEventNameFriendShipReceive + PuppetEventNameFriendShipVerify + PuppetEventNameLogin + PuppetEventNameLogout + PuppetEventNameMessage + PuppetEventNameRoomInvite + PuppetEventNameRoomJoin + PuppetEventNameRoomLeave + PuppetEventNameRoomTopic + PuppetEventNameScan + + PuppetEventNameDong + PuppetEventNameError + PuppetEventNameHeartbeat + PuppetEventNameReady + PuppetEventNameReset + + PuppetEventNameStop + PuppetEventNameStart +) diff --git a/wechaty-puppet/schemas/scanstatus_string.go b/wechaty-puppet/schemas/scanstatus_string.go new file mode 100644 index 0000000..906d790 --- /dev/null +++ b/wechaty-puppet/schemas/scanstatus_string.go @@ -0,0 +1,28 @@ +// Code generated by "stringer -type=ScanStatus"; DO NOT EDIT. + +package schemas + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ScanStatusUnknown-0] + _ = x[ScanStatusCancel-1] + _ = x[ScanStatusWaiting-2] + _ = x[ScanStatusScanned-3] + _ = x[ScanStatusConfirmed-4] + _ = x[ScanStatusTimeout-5] +} + +const _ScanStatus_name = "ScanStatusUnknownScanStatusCancelScanStatusWaitingScanStatusScannedScanStatusConfirmedScanStatusTimeout" + +var _ScanStatus_index = [...]uint8{0, 17, 33, 50, 67, 86, 103} + +func (i ScanStatus) String() string { + if i >= ScanStatus(len(_ScanStatus_index)-1) { + return "ScanStatus(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ScanStatus_name[_ScanStatus_index[i]:_ScanStatus_index[i+1]] +} diff --git a/wechaty/accessory.go b/wechaty/accessory.go deleted file mode 100644 index 791a04f..0000000 --- a/wechaty/accessory.go +++ /dev/null @@ -1,14 +0,0 @@ -package wechaty - -import ( - wechatyPuppet "github.com/wechaty/go-wechaty/wechaty-puppet" -) - -// Accessory accessory interface -type Accessory interface { - SetPuppet(puppet wechatyPuppet.Puppet) - GetPuppet() *wechatyPuppet.Puppet - - SetWechaty(wechaty Wechaty) - GetWechaty() *Wechaty -} diff --git a/wechaty/event.go b/wechaty/event.go new file mode 100644 index 0000000..600a505 --- /dev/null +++ b/wechaty/event.go @@ -0,0 +1,44 @@ +package wechaty + +import ( + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty/user" + "time" +) + +type ( + // EventDong ... + EventDong func(data string) + // EventError ... + EventError func(err error) + // EventFriendshipConfirm ... + EventFriendshipConfirm func(friendship *user.Friendship) + // EventFriendshipVerify ... + EventFriendshipVerify func(friendship *user.Friendship) + // EventFriendshipReceive ... + EventFriendshipReceive func(friendshipReceive *user.FriendshipReceive) + // EventHeartbeat ... + EventHeartbeat func(data string) + // EventLogin ... + EventLogin func(user string) // TODO(dchaofei): ContactSelf struct + // EventLogout ... + EventLogout func(user string, reason string) // TODO(dchaofei): ContactSelf struct + // EventMessage ... + EventMessage func(message *user.Message) + // EventReady ... + EventReady func() + // EventRoomInvite ... + EventRoomInvite func(roomInvitation string) // TODO(dchaofei): RoomInvitation struct + // EventRoomJoin ... + EventRoomJoin func(room *user.Room, inviteeList []*user.Contact, inviter *user.Contact, date time.Time) + // EventRoomLeave ... + EventRoomLeave func(room *user.Room, leaverList []*user.Contact, remover *user.Contact, date time.Time) + // EventRoomTopic ... + EventRoomTopic func(room *user.Room, newTopic string, oldTopic string, changer *user.Contact, date time.Time) + // EventScan ... + EventScan func(qrCode string, status schemas.ScanStatus, data string) + // EventStart ... + EventStart func() + // EventStop ... + EventStop func() +) diff --git a/wechaty/interface/accessory.go b/wechaty/interface/accessory.go new file mode 100644 index 0000000..d782b1f --- /dev/null +++ b/wechaty/interface/accessory.go @@ -0,0 +1,14 @@ +package _interface + +import ( + wechatyPuppet "github.com/wechaty/go-wechaty/wechaty-puppet" +) + +// Accessory accessory interface +type Accessory interface { + SetPuppet(puppet wechatyPuppet.Puppet) + GetPuppet() *wechatyPuppet.Puppet + + SetWechaty(wechaty Wechaty) + GetWechaty() Wechaty +} diff --git a/wechaty/interface/wechaty.go b/wechaty/interface/wechaty.go new file mode 100644 index 0000000..beb6e76 --- /dev/null +++ b/wechaty/interface/wechaty.go @@ -0,0 +1,5 @@ +package _interface + +// Wechaty interface +type Wechaty interface { +} diff --git a/wechaty/user/contact.go b/wechaty/user/contact.go index a651dc1..f5194eb 100644 --- a/wechaty/user/contact.go +++ b/wechaty/user/contact.go @@ -21,18 +21,31 @@ package user -import "github.com/wechaty/go-wechaty/wechaty" +import ( + "github.com/wechaty/go-wechaty/wechaty/interface" +) type Contact struct { - wechaty.Accessory + _interface.Accessory - Id string + Id string } -func (r *Contact) Load(id string) Contact { - return Contact{} +func NewContact(accessory _interface.Accessory, id string) *Contact { + return &Contact{ + Accessory: accessory, + Id: id, + } } func (r *Contact) Ready(forceSync bool) bool { - return true + return true +} + +func (r *Contact) isReady() bool { + return true +} + +func (r *Contact) Sync() { + r.Ready(true) } diff --git a/wechaty/user/friendship.go b/wechaty/user/friendship.go new file mode 100644 index 0000000..ae9e95a --- /dev/null +++ b/wechaty/user/friendship.go @@ -0,0 +1,30 @@ +package user + +import ( + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty/interface" +) + +type friendship interface { + Contact() *Contact +} + +type Friendship struct { + _interface.Accessory + friendshipPayloadBase schemas.FriendshipPayloadBase +} + +func NewFriendship(accessory _interface.Accessory, friendshipPayloadBase schemas.FriendshipPayloadBase) *Friendship { + return &Friendship{ + Accessory: accessory, + friendshipPayloadBase: friendshipPayloadBase, + } +} + +func (f *Friendship) Contact() *Contact { + return NewContact(f.Accessory, f.friendshipPayloadBase.ContactId) +} + +func (f *Friendship) Hello() string { + return f.friendshipPayloadBase.Hello +} diff --git a/wechaty/user/friendship_receive.go b/wechaty/user/friendship_receive.go new file mode 100644 index 0000000..af55186 --- /dev/null +++ b/wechaty/user/friendship_receive.go @@ -0,0 +1,35 @@ +package user + +import ( + "errors" + helper_functions "github.com/wechaty/go-wechaty/wechaty-puppet/helper-functions" + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty/interface" +) + +type FriendshipReceive struct { + _interface.Accessory + payload *schemas.FriendshipPayloadReceive + *Friendship +} + +func NewFriendshipReceive(accessory _interface.Accessory, payload *schemas.FriendshipPayloadReceive) *FriendshipReceive { + return &FriendshipReceive{ + Accessory: accessory, + payload: payload, + Friendship: NewFriendship(accessory, payload.FriendshipPayloadBase), + } +} + +func (f *FriendshipReceive) Accept() { + f.GetPuppet().FriendshipAccept(f.payload.Id) + contact := f.Contact() + _ = helper_functions.TryWait(func() error { + contact.Ready(false) + if contact.isReady() { + return nil + } + return errors.New("friendshipReceive.accept() contact.ready() not ready") + }) + contact.Sync() +} diff --git a/wechaty/user/image.go b/wechaty/user/image.go index 86fc2e9..dd0e204 100644 --- a/wechaty/user/image.go +++ b/wechaty/user/image.go @@ -1,18 +1,18 @@ package user import ( - "github.com/wechaty/go-wechaty/wechaty" - wechatyPuppet "github.com/wechaty/go-wechaty/wechaty-puppet" - "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty-puppet/file-box" + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty/interface" ) type Images struct { - wechaty.Accessory + _interface.Accessory ImageId string } // NewImages create image struct -func NewImages(id string, accessory wechaty.Accessory) *Images { +func NewImages(id string, accessory _interface.Accessory) *Images { if accessory.GetPuppet() == nil { panic("Image class can not be instantiated without a puppet!") } @@ -20,16 +20,16 @@ func NewImages(id string, accessory wechaty.Accessory) *Images { } // Thumbnail message thumbnail images -func (img *Images) Thumbnail() wechatyPuppet.FileBox { +func (img *Images) Thumbnail() file_box.FileBox { return img.Accessory.GetPuppet().MessageImage(img.ImageId, schemas.ImageTypeThumbnail) } // HD message hd images -func (img *Images) HD() wechatyPuppet.FileBox { +func (img *Images) HD() file_box.FileBox { return img.Accessory.GetPuppet().MessageImage(img.ImageId, schemas.ImageTypeHD) } // Artwork message artwork images -func (img *Images) Artwork() wechatyPuppet.FileBox { +func (img *Images) Artwork() file_box.FileBox { return img.Accessory.GetPuppet().MessageImage(img.ImageId, schemas.ImageTypeArtwork) } diff --git a/wechaty/user/message.go b/wechaty/user/message.go index 41de28c..e7adc76 100644 --- a/wechaty/user/message.go +++ b/wechaty/user/message.go @@ -1,10 +1,10 @@ package user import ( - "fmt" + "fmt" + "github.com/wechaty/go-wechaty/wechaty/interface" - "github.com/wechaty/go-wechaty/wechaty" - "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" ) type MessageUserQueryFilter struct { @@ -16,7 +16,7 @@ type MessageUserQueryFilter struct { } type Message struct { - wechaty.Accessory + _interface.Accessory Type schemas.MessageType diff --git a/wechaty/user/tag.go b/wechaty/user/tag.go index f8982b3..ddeeb46 100644 --- a/wechaty/user/tag.go +++ b/wechaty/user/tag.go @@ -22,15 +22,15 @@ package user import ( - "github.com/wechaty/go-wechaty/wechaty" + "github.com/wechaty/go-wechaty/wechaty/interface" ) type Tag struct { - wechaty.Accessory + _interface.Accessory TagId string } -func NewTag(id string, accessory wechaty.Accessory) *Tag { +func NewTag(id string, accessory _interface.Accessory) *Tag { if accessory.GetPuppet() == nil { panic("Tag class can not be instantiated without a puppet!") } diff --git a/wechaty/wechaty.go b/wechaty/wechaty.go index cf98f4a..fd1b210 100644 --- a/wechaty/wechaty.go +++ b/wechaty/wechaty.go @@ -22,34 +22,143 @@ // Package wechaty ... package wechaty +import ( + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "reflect" +) + // Wechaty ... type Wechaty struct { + eventMap map[schemas.PuppetEventName]interface{} } // NewWechaty ... // instance by golang. func NewWechaty() *Wechaty { - return &Wechaty{} + return &Wechaty{ + eventMap: map[schemas.PuppetEventName]interface{}{}, + } +} + +func (w *Wechaty) registerEvent(name schemas.PuppetEventName, event interface{}) { + w.eventMap[name] = event } // OnScan ... -func (w *Wechaty) OnScan(f func(qrCode, status string)) *Wechaty { - return w +func (w *Wechaty) OnScan(f EventScan) *Wechaty { + w.registerEvent(schemas.PuppetEventNameScan, f) + return w } // OnLogin ... -// todo:: fake code. user should be struct -func (w *Wechaty) OnLogin(func(user string)) *Wechaty { - return w +func (w *Wechaty) OnLogin(f EventLogin) *Wechaty { + w.registerEvent(schemas.PuppetEventNameLogin, f) + return w } // OnMessage ... -// todo:: fake code. message should be struct -func (w *Wechaty) OnMessage(func(message string)) *Wechaty { - return w +func (w *Wechaty) OnMessage(f EventMessage) *Wechaty { + w.registerEvent(schemas.PuppetEventNameMessage, f) + return w +} + +// OnDong ... +func (w *Wechaty) OnDong(f EventDong) *Wechaty { + w.registerEvent(schemas.PuppetEventNameDong, f) + return w +} + +// OnError ... +func (w *Wechaty) OnError(f EventError) *Wechaty { + w.registerEvent(schemas.PuppetEventNameError, f) + return w +} + +// OnFriendshipConfirm ... +func (w *Wechaty) OnFriendshipConfirm(f EventFriendshipConfirm) *Wechaty { + w.registerEvent(schemas.PuppetEventNameFriendShipConfirm, f) + return w +} + +// OnFriendshipVerify ... +func (w *Wechaty) OnFriendshipVerify(f EventFriendshipVerify) *Wechaty { + w.registerEvent(schemas.PuppetEventNameFriendShipVerify, f) + return w +} + +// OnFriendshipReceive ... +func (w *Wechaty) OnFriendshipReceive(f EventFriendshipReceive) *Wechaty { + w.registerEvent(schemas.PuppetEventNameFriendShipReceive, f) + return w +} + +// OnHeartbeat ... +func (w *Wechaty) OnHeartbeat(f EventHeartbeat) *Wechaty { + w.registerEvent(schemas.PuppetEventNameHeartbeat, f) + return w +} + +// OnLogout ... +func (w *Wechaty) OnLogout(f EventLogout) *Wechaty { + w.registerEvent(schemas.PuppetEventNameLogout, f) + return w +} + +// OnReady ... +func (w *Wechaty) OnReady(f EventReady) *Wechaty { + w.registerEvent(schemas.PuppetEventNameReady, f) + return w +} + +// OnRoomInvite ... +func (w *Wechaty) OnRoomInvite(f EventRoomInvite) *Wechaty { + w.registerEvent(schemas.PuppetEventNameRoomInvite, f) + return w +} + +// OnRoomJoin ... +func (w *Wechaty) OnRoomJoin(f EventRoomJoin) *Wechaty { + w.registerEvent(schemas.PuppetEventNameRoomJoin, f) + return w +} + +// OnRoomLeave ... +func (w *Wechaty) OnRoomLeave(f EventRoomLeave) *Wechaty { + w.registerEvent(schemas.PuppetEventNameRoomLeave, f) + return w +} + +// OnRoomTopic ... +func (w *Wechaty) OnRoomTopic(f EventRoomTopic) *Wechaty { + w.registerEvent(schemas.PuppetEventNameRoomTopic, f) + return w +} + +// OnStart ... +func (w *Wechaty) OnStart(f EventStart) *Wechaty { + w.registerEvent(schemas.PuppetEventNameStart, f) + return w +} + +// OnStop ... +func (w *Wechaty) OnStop(f EventStop) *Wechaty { + w.registerEvent(schemas.PuppetEventNameStop, f) + return w +} + +// Emit ... +func (w *Wechaty) Emit(name schemas.PuppetEventName, data ...interface{}) { + f, ok := w.eventMap[name] + if ok { + values := make([]reflect.Value, 0, len(data)) + for _, v := range data { + values = append(values, reflect.ValueOf(v)) + } + _ = reflect.ValueOf(f).Call(values) + } } // Start ... func (w *Wechaty) Start() *Wechaty { - return w + return w } diff --git a/wechaty/wechaty_test.go b/wechaty/wechaty_test.go index 70f3d90..c69303b 100644 --- a/wechaty/wechaty_test.go +++ b/wechaty/wechaty_test.go @@ -1,22 +1,50 @@ package wechaty import ( - "reflect" - "testing" + "github.com/wechaty/go-wechaty/wechaty-puppet/schemas" + "log" + "reflect" + "testing" ) func TestNewWechaty(t *testing.T) { - tests := []struct { - name string - want *Wechaty - }{ - {name: "new", want: NewWechaty()}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewWechaty(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewWechaty() = %v, want %v", got, tt.want) - } - }) - } + tests := []struct { + name string + want *Wechaty + }{ + {name: "new", want: NewWechaty()}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewWechaty(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewWechaty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestWechaty_Emit(t *testing.T) { + wechaty := NewWechaty() + got := "" + expect := "test" + wechaty.OnHeartbeat(func(data string) { + got = data + }) + wechaty.Emit(schemas.PuppetEventNameHeartbeat, expect) + if got != expect { + log.Fatalf("got %s expect %s", got, expect) + } +} + +func TestWechaty_On(t *testing.T) { + wechaty := NewWechaty() + got := "" + expect := "ding" + wechaty.OnDong(func(data string) { + got = data + }) + wechaty.Emit(schemas.PuppetEventNameDong, expect) + if got != expect { + log.Fatalf("got %s expect %s", got, expect) + } }