├── .gitignore ├── LICENSE ├── README.md ├── client.go ├── client_test.go ├── errors.go ├── example └── example.go ├── go.mod ├── go.sum ├── httputil ├── helpers.go └── requestdoer.go ├── longpoll └── user │ ├── errors.go │ ├── longpoll.go │ ├── longpoll_test.go │ ├── options.go │ ├── stream.go │ ├── types.go │ ├── update.go │ └── update_test.go ├── options.go ├── requestparams.go └── requestparams_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | example 4 | !example/example.go 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 urShadow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-vk-api 2 | [![GoDoc](https://godoc.org/github.com/go-vk-api/vk?status.svg)](https://godoc.org/github.com/go-vk-api/vk) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/go-vk-api/vk)](https://goreportcard.com/report/github.com/go-vk-api/vk) 4 | 5 | Golang bindings for the VK API 6 | 7 | ## Install 8 | 9 | Install the package with: 10 | 11 | ```bash 12 | go get github.com/go-vk-api/vk 13 | ``` 14 | 15 | Import it with: 16 | 17 | ```go 18 | import "github.com/go-vk-api/vk" 19 | ``` 20 | 21 | and use `vk` as the package name inside the code. 22 | 23 | ## Example 24 | 25 | [Full example with errors handling](https://github.com/go-vk-api/vk/blob/master/example/example.go) 26 | 27 | ```go 28 | package main 29 | 30 | import ( 31 | "log" 32 | "net/http" 33 | "os" 34 | 35 | "github.com/go-vk-api/vk" 36 | lp "github.com/go-vk-api/vk/longpoll/user" 37 | ) 38 | 39 | func main() { 40 | client, _ := vk.NewClientWithOptions( 41 | vk.WithToken(os.Getenv("VK_ACCESS_TOKEN")), 42 | ) 43 | 44 | _ = printMe(client) 45 | 46 | longpoll, _ := lp.NewWithOptions(client, lp.WithMode(lp.ReceiveAttachments)) 47 | 48 | stream, _ := longpoll.GetUpdatesStream(0) 49 | 50 | for update := range stream.Updates { 51 | switch data := update.Data.(type) { 52 | case *lp.NewMessage: 53 | if data.Text == "/hello" { 54 | _ = client.CallMethod("messages.send", vk.RequestParams{ 55 | "peer_id": data.PeerID, 56 | "message": "Hello!", 57 | "forward_messages": data.ID, 58 | "random_id": 0, 59 | }, nil) 60 | } 61 | } 62 | } 63 | } 64 | 65 | func printMe(api *vk.Client) error { 66 | var users []struct { 67 | ID int64 `json:"id"` 68 | FirstName string `json:"first_name"` 69 | LastName string `json:"last_name"` 70 | } 71 | 72 | _ = api.CallMethod("users.get", vk.RequestParams{}, &users) 73 | 74 | me := users[0] 75 | 76 | log.Println(me.ID, me.FirstName, me.LastName) 77 | 78 | return nil 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package vk 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "reflect" 7 | 8 | "github.com/go-vk-api/vk/httputil" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | const ( 13 | DefaultBaseURL = "https://api.vk.com/method" 14 | DefaultLang = "en" 15 | DefaultVersion = "5.103" 16 | ) 17 | 18 | // Client manages communication with VK API. 19 | type Client struct { 20 | BaseURL string 21 | Lang string 22 | Version string 23 | 24 | Token string 25 | 26 | HTTPClient httputil.RequestDoer 27 | } 28 | 29 | // CallMethod invokes the named method and stores the result in the value pointed to by response. 30 | func (c *Client) CallMethod(method string, params RequestParams, response interface{}) error { 31 | queryParams, err := params.URLValues() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | setIfEmpty := func(param, value string) { 37 | if queryParams.Get(param) == "" { 38 | queryParams.Set(param, value) 39 | } 40 | } 41 | 42 | setIfEmpty("v", c.Version) 43 | setIfEmpty("lang", c.Lang) 44 | if c.Token != "" { 45 | setIfEmpty("access_token", c.Token) 46 | } 47 | 48 | rawBody, err := httputil.Post(c.HTTPClient, c.BaseURL+"/"+method, queryParams) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | var body struct { 54 | Response interface{} `json:"response"` 55 | Error *MethodError `json:"error"` 56 | } 57 | 58 | if response != nil { 59 | valueOfResponse := reflect.ValueOf(response) 60 | if valueOfResponse.Kind() != reflect.Ptr || valueOfResponse.IsNil() { 61 | return errors.New("response must be a valid pointer") 62 | } 63 | 64 | body.Response = response 65 | } 66 | 67 | if err = json.Unmarshal(rawBody, &body); err != nil { 68 | return err 69 | } 70 | 71 | if body.Error != nil { 72 | return body.Error 73 | } 74 | 75 | return nil 76 | } 77 | 78 | // NewClient initializes a new VK API client with default values. 79 | func NewClient() (*Client, error) { 80 | return NewClientWithOptions() 81 | } 82 | 83 | // NewClientWithOptions initializes a new VK API client with default values. It takes functors 84 | // to modify values when creating it, like `NewClientWithOptions(WithToken(…))`. 85 | func NewClientWithOptions(options ...Option) (*Client, error) { 86 | client := &Client{ 87 | BaseURL: DefaultBaseURL, 88 | Lang: DefaultLang, 89 | Version: DefaultVersion, 90 | } 91 | 92 | for _, option := range options { 93 | if err := option(client); err != nil { 94 | return nil, err 95 | } 96 | } 97 | 98 | if client.HTTPClient == nil { 99 | client.HTTPClient = http.DefaultClient 100 | } 101 | 102 | return client, nil 103 | } 104 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package vk 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestNewClient(t *testing.T) { 9 | client, err := NewClient() 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | 14 | testDefaultClient(client, t) 15 | } 16 | 17 | func TestNewClientWithOptions(t *testing.T) { 18 | token := "TOKEN" 19 | 20 | client, err := NewClientWithOptions( 21 | WithToken(token), 22 | WithHTTPClient(http.DefaultClient), 23 | ) 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | 28 | testDefaultClient(client, t) 29 | 30 | if client.Token != token { 31 | t.Errorf("client.Token == %q, want %q", client.Token, token) 32 | } 33 | 34 | if client.HTTPClient != http.DefaultClient { 35 | t.Errorf("client.HTTPClient == %v, want %v (http.DefaultClient)", client.HTTPClient, http.DefaultClient) 36 | } 37 | } 38 | 39 | func testDefaultClient(client *Client, t *testing.T) { 40 | if client.Lang != DefaultLang { 41 | t.Errorf("client.Lang == %q, want %q", client.Lang, DefaultLang) 42 | } 43 | 44 | if client.Version != DefaultVersion { 45 | t.Errorf("client.Version == %q, want %q", client.Version, DefaultVersion) 46 | } 47 | 48 | if client.BaseURL != DefaultBaseURL { 49 | t.Errorf("client.BaseURL == %q, want %q", client.BaseURL, DefaultBaseURL) 50 | } 51 | 52 | if client.HTTPClient == nil { 53 | t.Error("client.HTTPClient == nil") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package vk 2 | 3 | // MethodError represents a VK API method call error. 4 | type MethodError struct { 5 | Code int64 `json:"error_code"` 6 | Message string `json:"error_msg"` 7 | RequestParams []struct { 8 | Key string `json:"key"` 9 | Value string `json:"value"` 10 | } `json:"request_params"` 11 | } 12 | 13 | func (err *MethodError) Error() string { 14 | return "vk: " + err.Message 15 | } 16 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/go-vk-api/vk" 10 | lp "github.com/go-vk-api/vk/longpoll/user" 11 | ) 12 | 13 | func main() { 14 | client, err := vk.NewClientWithOptions( 15 | vk.WithToken(os.Getenv("VK_ACCESS_TOKEN")), 16 | vk.WithHTTPClient(&http.Client{Timeout: time.Second * 30}), 17 | ) 18 | if err != nil { 19 | log.Panic(err) 20 | } 21 | 22 | err = printMe(client) 23 | if err != nil { 24 | log.Panic(err) 25 | } 26 | 27 | longpoll, err := lp.NewWithOptions(client, lp.WithMode(lp.ReceiveAttachments)) 28 | if err != nil { 29 | log.Panic(err) 30 | } 31 | 32 | stream, err := longpoll.GetUpdatesStream(0) 33 | if err != nil { 34 | log.Panic(err) 35 | } 36 | 37 | for { 38 | select { 39 | case update, ok := <-stream.Updates: 40 | if !ok { 41 | return 42 | } 43 | 44 | switch data := update.Data.(type) { 45 | case *lp.NewMessage: 46 | if data.Text == "/hello" { 47 | var sentMessageID int64 48 | 49 | if err = client.CallMethod("messages.send", vk.RequestParams{ 50 | "peer_id": data.PeerID, 51 | "message": "Hello!", 52 | "forward_messages": data.ID, 53 | "random_id": 0, 54 | }, &sentMessageID); err != nil { 55 | log.Panic(err) 56 | } 57 | 58 | log.Println(sentMessageID) 59 | } 60 | } 61 | case err, ok := <-stream.Errors: 62 | if ok { 63 | // stream.Stop() 64 | log.Panic(err) 65 | } 66 | } 67 | } 68 | } 69 | 70 | func printMe(api *vk.Client) error { 71 | var users []struct { 72 | ID int64 `json:"id"` 73 | FirstName string `json:"first_name"` 74 | LastName string `json:"last_name"` 75 | } 76 | 77 | if err := api.CallMethod("users.get", vk.RequestParams{}, &users); err != nil { 78 | return err 79 | } 80 | 81 | me := users[0] 82 | 83 | log.Println(me.ID, me.FirstName, me.LastName) 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-vk-api/vk 2 | 3 | require github.com/pkg/errors v0.9.1 4 | 5 | go 1.13 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 2 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 3 | -------------------------------------------------------------------------------- /httputil/helpers.go: -------------------------------------------------------------------------------- 1 | package httputil 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/url" 7 | "strings" 8 | ) 9 | 10 | // Post issues a POST to the specified URL. 11 | func Post(rd RequestDoer, url string, params url.Values) ([]byte, error) { 12 | req, err := http.NewRequest("POST", url, strings.NewReader(params.Encode())) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 18 | 19 | resp, err := rd.Do(req) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | defer resp.Body.Close() 25 | 26 | return ioutil.ReadAll(resp.Body) 27 | } 28 | -------------------------------------------------------------------------------- /httputil/requestdoer.go: -------------------------------------------------------------------------------- 1 | package httputil 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // RequestDoer is the interface implemented by types that 8 | // can Do an HTTP request. 9 | type RequestDoer interface { 10 | Do(req *http.Request) (*http.Response, error) 11 | } 12 | -------------------------------------------------------------------------------- /longpoll/user/errors.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrEventHistoryOutdated = errors.New("event history outdated") 9 | ErrKeyExpired = errors.New("key expired") 10 | ErrUserInformationLost = errors.New("user information lost") 11 | ErrInvalidVersion = errors.New("invalid version") 12 | ) 13 | -------------------------------------------------------------------------------- /longpoll/user/longpoll.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/go-vk-api/vk" 8 | "github.com/go-vk-api/vk/httputil" 9 | ) 10 | 11 | const ( 12 | // DefaultVersion is a default version of the VK Long Poll API. 13 | DefaultVersion = 2 14 | // DefaultWait is a waiting period. Maximum: 90. 15 | DefaultWait = 25 16 | // DefaultMode is an additional answer options. 17 | DefaultMode = ReceiveAttachments 18 | ) 19 | 20 | // Mode represents the additional answer options. 21 | type Mode int64 22 | 23 | const ( 24 | ReceiveAttachments Mode = 2 25 | ReturnExpandedSetOfEvents Mode = 8 26 | ReturnPts Mode = 32 27 | ReturnFriendOnlineExtraField Mode = 64 28 | ReturnRandomID Mode = 128 29 | ) 30 | 31 | const ( 32 | eventHistoryOutdated = iota + 1 33 | keyExpired 34 | userInformationLost 35 | invalidVersion 36 | ) 37 | 38 | // Longpoll manages communication with VK User Long Poll API. 39 | type Longpoll struct { 40 | client *vk.Client 41 | 42 | Key string 43 | Server string 44 | Wait int64 45 | Mode Mode 46 | Version int64 47 | 48 | NeedPts int64 49 | GroupID int64 50 | } 51 | 52 | // UpdateServer updates the longpoll server and returns a new ts. 53 | func (lp *Longpoll) UpdateServer() (newTs int64, err error) { 54 | params := vk.RequestParams{ 55 | "need_pts": lp.NeedPts, 56 | "lp_version": lp.Version, 57 | } 58 | 59 | if lp.GroupID > 0 { 60 | params["group_id"] = lp.GroupID 61 | } 62 | 63 | var body struct { 64 | Key string `json:"key"` 65 | Server string `json:"server"` 66 | Ts int64 `json:"ts"` 67 | } 68 | 69 | if err = lp.client.CallMethod("messages.getLongPollServer", params, &body); err != nil { 70 | return 71 | } 72 | 73 | lp.Key = body.Key 74 | lp.Server = body.Server 75 | 76 | return body.Ts, nil 77 | } 78 | 79 | // Poll requests updates starting with the ts and returns a new updates and ts. 80 | func (lp *Longpoll) Poll(ts int64) (updates []*Update, newTS int64, err error) { 81 | params, err := vk.RequestParams{ 82 | "act": "a_check", 83 | "key": lp.Key, 84 | "ts": ts, 85 | "wait": lp.Wait, 86 | "mode": lp.Mode, 87 | "version": lp.Version, 88 | }.URLValues() 89 | if err != nil { 90 | return 91 | } 92 | 93 | rawBody, err := httputil.Post(lp.client.HTTPClient, "https://"+lp.Server, params) 94 | if err != nil { 95 | return 96 | } 97 | 98 | var body struct { 99 | TS int64 `json:"ts"` 100 | Updates []*Update `json:"updates"` 101 | Failed *int64 `json:"failed"` 102 | } 103 | 104 | if err = json.Unmarshal(rawBody, &body); err != nil { 105 | return 106 | } 107 | 108 | if body.Failed != nil { 109 | return nil, body.TS, lp.failedToError(*body.Failed) 110 | } 111 | 112 | return body.Updates, body.TS, nil 113 | } 114 | 115 | func (lp *Longpoll) failedToError(failed int64) error { 116 | switch failed { 117 | case eventHistoryOutdated: 118 | return ErrEventHistoryOutdated 119 | case keyExpired: 120 | return ErrKeyExpired 121 | case userInformationLost: 122 | return ErrUserInformationLost 123 | case invalidVersion: 124 | return ErrInvalidVersion 125 | } 126 | 127 | return fmt.Errorf("unexpected failed value (%d)", failed) 128 | } 129 | 130 | // GetUpdatesStream starts and returns a stream of updates. 131 | func (lp *Longpoll) GetUpdatesStream(ts int64) (*Stream, error) { 132 | newTs, err := lp.UpdateServer() 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | if ts == 0 { 138 | ts = newTs 139 | } 140 | 141 | stream := &Stream{ 142 | lp: lp, 143 | TS: ts, 144 | } 145 | 146 | if err := stream.Start(); err != nil { 147 | return nil, err 148 | } 149 | 150 | return stream, nil 151 | } 152 | 153 | // New initializes a new longpoll client with default values. 154 | func New(client *vk.Client) (*Longpoll, error) { 155 | return NewWithOptions(client) 156 | } 157 | 158 | // NewWithOptions initializes a new longpoll client with default values. It takes functors 159 | // to modify values when creating it, like `NewWithOptions(WithMode(…))`. 160 | func NewWithOptions(client *vk.Client, options ...Option) (*Longpoll, error) { 161 | longpoll := &Longpoll{ 162 | client: client, 163 | 164 | Wait: DefaultWait, 165 | Mode: DefaultMode, 166 | Version: DefaultVersion, 167 | } 168 | 169 | for _, option := range options { 170 | if err := option(longpoll); err != nil { 171 | return nil, err 172 | } 173 | } 174 | 175 | return longpoll, nil 176 | } 177 | -------------------------------------------------------------------------------- /longpoll/user/longpoll_test.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewWithOptions(t *testing.T) { 8 | wantMode := ReceiveAttachments + ReturnFriendOnlineExtraField 9 | 10 | lp, err := NewWithOptions( 11 | nil, 12 | WithMode(wantMode), 13 | ) 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | 18 | if lp.Wait != DefaultWait { 19 | t.Errorf("lp.Wait == %d, want %d", lp.Wait, DefaultWait) 20 | } 21 | 22 | if lp.Mode != wantMode { 23 | t.Errorf("lp.Mode == %d, want %d", lp.Mode, wantMode) 24 | } 25 | 26 | if lp.Version != DefaultVersion { 27 | t.Errorf("lp.Version == %d, want %d", lp.Version, DefaultVersion) 28 | } 29 | 30 | if lp.client != nil { 31 | t.Errorf("lp.client = %v, want nil", lp.client) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /longpoll/user/options.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | // Option is a configuration option to initialize a longpoll. 4 | type Option func(*Longpoll) error 5 | 6 | // WithMode overrides the longpoll mode with the specified one. 7 | func WithMode(m Mode) Option { 8 | return func(lp *Longpoll) error { 9 | lp.Mode = m 10 | 11 | return nil 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /longpoll/user/stream.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | // Stream represents a stream of events the VK User Longpoll API. 4 | type Stream struct { 5 | lp *Longpoll 6 | 7 | TS int64 8 | 9 | Updates <-chan *Update 10 | Errors <-chan error 11 | 12 | stop chan struct{} 13 | } 14 | 15 | // Start starts a channel for getting updates. 16 | func (s *Stream) Start() error { 17 | updatesCh := make(chan *Update) 18 | errorsCh := make(chan error, 1) 19 | s.stop = make(chan struct{}, 1) 20 | 21 | s.Updates = updatesCh 22 | s.Errors = errorsCh 23 | 24 | go func() { 25 | defer func() { 26 | close(updatesCh) 27 | close(errorsCh) 28 | close(s.stop) 29 | }() 30 | 31 | for { 32 | select { 33 | case <-s.stop: 34 | return 35 | default: 36 | } 37 | 38 | updates, newTS, err := s.lp.Poll(s.TS) 39 | if err != nil { 40 | switch err { 41 | case ErrEventHistoryOutdated: 42 | s.TS = newTS 43 | continue 44 | case ErrKeyExpired, ErrUserInformationLost: 45 | newTS, err = s.lp.UpdateServer() 46 | if err != nil { 47 | errorsCh <- err 48 | return 49 | } 50 | s.TS = newTS 51 | continue 52 | default: 53 | errorsCh <- err 54 | return 55 | } 56 | } 57 | s.TS = newTS 58 | 59 | for _, update := range updates { 60 | select { 61 | case <-s.stop: 62 | return 63 | default: 64 | } 65 | 66 | updatesCh <- update 67 | } 68 | } 69 | }() 70 | 71 | return nil 72 | } 73 | 74 | // Stop stops the stream. 75 | func (s *Stream) Stop() { 76 | s.stop <- struct{}{} 77 | } 78 | -------------------------------------------------------------------------------- /longpoll/user/types.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | // Event codes. 4 | const ( 5 | EventNewMessage = 4 6 | ) 7 | 8 | // NewMessage represents the information an event EventNewMessage contains. 9 | type NewMessage struct { 10 | ID int64 11 | Flags int64 12 | PeerID int64 13 | Timestamp int64 14 | Text string 15 | Attachments map[string]string 16 | RandomID int64 17 | } 18 | -------------------------------------------------------------------------------- /longpoll/user/update.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // Update represents the information an event contains. 11 | type Update struct { 12 | Type int64 13 | Data interface{} 14 | RawData json.RawMessage 15 | } 16 | 17 | // UnmarshalJSON implements the json.Unmarshaler interface. 18 | func (update *Update) UnmarshalJSON(data []byte) error { 19 | if err := json.Unmarshal(data, &update.Data); err != nil { 20 | return err 21 | } 22 | 23 | update.Type = int64(update.Data.([]interface{})[0].(float64)) 24 | update.RawData = data 25 | 26 | switch update.Type { 27 | case EventNewMessage: 28 | update.Data = &NewMessage{} 29 | default: 30 | return nil // []interface{} remains the type of update.Data 31 | } 32 | 33 | return update.Unmarshal(update.Data) 34 | } 35 | 36 | // Unmarshal parses JSON-encoded update.RawData and stores the result 37 | // in the struct value pointed to by outputStruct. 38 | func (update *Update) Unmarshal(outputStruct interface{}) error { 39 | structPtr := reflect.ValueOf(outputStruct) 40 | 41 | if structPtr.Kind() != reflect.Ptr || structPtr.IsNil() || structPtr.Elem().Kind() != reflect.Struct { 42 | return errors.New("outputStruct must be a valid pointer to a struct") 43 | } 44 | 45 | structValue := structPtr.Elem() 46 | 47 | arr := make([]interface{}, structValue.NumField()+1) 48 | 49 | for n := 0; n < structValue.NumField(); n++ { 50 | arr[n+1] = structValue.Field(n).Addr().Interface() // skip event code 51 | } 52 | 53 | return json.Unmarshal(update.RawData, &arr) 54 | } 55 | -------------------------------------------------------------------------------- /longpoll/user/update_test.go: -------------------------------------------------------------------------------- 1 | package longpoll 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestUpdate_UnmarshalJSON(t *testing.T) { 10 | update := Update{} 11 | 12 | wantMessage := &NewMessage{ 13 | ID: 1, 14 | Flags: 2, 15 | PeerID: 3, 16 | Timestamp: 4, 17 | Text: "567", 18 | Attachments: map[string]string{"from": "89"}, 19 | RandomID: 10, 20 | } 21 | 22 | json := fmt.Sprintf( 23 | `[%d,%d,%d,%d,%d,"%s",{"from":"%s"},%d]`, 24 | EventNewMessage, 25 | wantMessage.ID, 26 | wantMessage.Flags, 27 | wantMessage.PeerID, 28 | wantMessage.Timestamp, 29 | wantMessage.Text, 30 | wantMessage.Attachments["from"], 31 | wantMessage.RandomID, 32 | ) 33 | 34 | err := update.UnmarshalJSON([]byte(json)) 35 | if err != nil { 36 | t.Error(err) 37 | } 38 | 39 | if update.Type != EventNewMessage { 40 | t.Errorf("update.Type == %d, want %d", update.Type, EventNewMessage) 41 | } 42 | 43 | message, ok := update.Data.(*NewMessage) 44 | if !ok { 45 | t.Errorf("reflect.TypeOf(update.Data) == %v, want %v", reflect.TypeOf(update.Data), reflect.TypeOf(wantMessage)) 46 | } 47 | 48 | if message.ID != wantMessage.ID { 49 | t.Errorf("message.ID == %d, want %d", message.ID, wantMessage.ID) 50 | } 51 | 52 | if message.Flags != wantMessage.Flags { 53 | t.Errorf("message.Flags == %d, want %d", message.Flags, wantMessage.Flags) 54 | } 55 | 56 | if message.PeerID != wantMessage.PeerID { 57 | t.Errorf("message.PeerID == %d, want %d", message.PeerID, wantMessage.PeerID) 58 | } 59 | 60 | if message.Timestamp != wantMessage.Timestamp { 61 | t.Errorf("message.Timestamp == %d, want %d", message.Timestamp, wantMessage.Timestamp) 62 | } 63 | 64 | if message.Text != wantMessage.Text { 65 | t.Errorf("message.Text == %q, want %q", message.Text, wantMessage.Text) 66 | } 67 | 68 | if message.Attachments["from"] != wantMessage.Attachments["from"] { 69 | t.Errorf("message.Attachments[\"from\"] == %q, want %q", message.Attachments["from"], wantMessage.Attachments["from"]) 70 | } 71 | 72 | if message.RandomID != wantMessage.RandomID { 73 | t.Errorf("message.RandomID == %d, want %d", message.RandomID, wantMessage.RandomID) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package vk 2 | 3 | import ( 4 | "github.com/go-vk-api/vk/httputil" 5 | ) 6 | 7 | // Option is a configuration option to initialize a client. 8 | type Option func(*Client) error 9 | 10 | // WithToken overrides the client token with the specified one. 11 | func WithToken(token string) Option { 12 | return func(c *Client) error { 13 | c.Token = token 14 | 15 | return nil 16 | } 17 | } 18 | 19 | // WithHTTPClient overrides the client http client with the specified one. 20 | func WithHTTPClient(doer httputil.RequestDoer) Option { 21 | return func(c *Client) error { 22 | c.HTTPClient = doer 23 | 24 | return nil 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /requestparams.go: -------------------------------------------------------------------------------- 1 | package vk 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | // RequestParams are the params for invoking methods. 9 | type RequestParams map[string]interface{} 10 | 11 | // URLValues translates the params to url.Values. 12 | func (params RequestParams) URLValues() (url.Values, error) { 13 | values := url.Values{} 14 | 15 | for k, v := range params { 16 | values.Add(k, fmt.Sprint(v)) 17 | } 18 | 19 | return values, nil 20 | } 21 | -------------------------------------------------------------------------------- /requestparams_test.go: -------------------------------------------------------------------------------- 1 | package vk 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRequestParams_UrlValues(t *testing.T) { 8 | cases := []struct { 9 | in RequestParams 10 | want string 11 | }{ 12 | { 13 | RequestParams{ 14 | "boolean": true, 15 | "int": 108, 16 | "string": "4 8 15 16 23 42", 17 | }, 18 | "boolean=true&int=108&string=4+8+15+16+23+42", 19 | }, 20 | } 21 | 22 | for _, c := range cases { 23 | urlValues, err := c.in.URLValues() 24 | 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | 29 | if len(urlValues) != len(c.in) { 30 | t.Errorf("len(urlValues) == %d, want %d", len(urlValues), len(c.in)) 31 | } 32 | 33 | if urlValues.Encode() != c.want { 34 | t.Errorf("urlValues.Encode() == %q, want %q", urlValues.Encode(), c.want) 35 | } 36 | } 37 | } 38 | --------------------------------------------------------------------------------