diff options
Diffstat (limited to 'vendor/github.com/SevereCloud/vksdk/v2/api/api.go')
-rw-r--r-- | vendor/github.com/SevereCloud/vksdk/v2/api/api.go | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/vendor/github.com/SevereCloud/vksdk/v2/api/api.go b/vendor/github.com/SevereCloud/vksdk/v2/api/api.go new file mode 100644 index 00000000..56da7784 --- /dev/null +++ b/vendor/github.com/SevereCloud/vksdk/v2/api/api.go @@ -0,0 +1,359 @@ +/* +Package api implements VK API. + +See more https://vk.com/dev/api_requests +*/ +package api // import "github.com/SevereCloud/vksdk/v2/api" + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "mime" + "net/http" + "net/url" + "reflect" + "sync" + "sync/atomic" + "time" + + "github.com/SevereCloud/vksdk/v2" + "github.com/SevereCloud/vksdk/v2/internal" + "github.com/SevereCloud/vksdk/v2/object" +) + +// Api constants. +const ( + Version = vksdk.API + MethodURL = "https://api.vk.com/method/" +) + +// VKontakte API methods (except for methods from secure and ads sections) +// with user access key or service access key can be accessed +// no more than 3 times per second. The community access key is limited +// to 20 requests per second. +// +// Maximum amount of calls to the secure section methods depends +// on the app's users amount. If an app has less than +// 10 000 users, 5 requests per second, +// up to 100 000 – 8 requests, +// up to 1 000 000 – 20 requests, +// 1 000 000+ – 35 requests. +// +// The ads section methods are subject to their own limitations, +// you can read them on this page - https://vk.com/dev/ads_limits +// +// If one of this limits is exceeded, the server will return following error: +// "Too many requests per second". (errors.TooMany). +// +// If your app's logic implies many requests in a row, check the execute method. +// It allows for up to 25 requests for different methods in a single request. +// +// In addition to restrictions on the frequency of calls, there are also +// quantitative restrictions on calling the same type of methods. +// +// After exceeding the quantitative limit, access to a particular method may +// require entering a captcha (see https://vk.com/dev/captcha_error), +// and may also be temporarily restricted (in this case, the server does +// not return a response to the call of a particular method, but handles +// any other requests without problems). +// +// If this error occurs, the following parameters are also passed in +// the error message: +// +// CaptchaSID - identifier captcha. +// +// CaptchaImg - a link to the image that you want to show the user +// to enter text from that image. +// +// In this case, you should ask the user to enter text from +// the CaptchaImg image and repeat the request by adding parameters to it: +// +// captcha_sid - the obtained identifier; +// +// captcha_key - text entered by the user. +// +// More info: https://vk.com/dev/api_requests +const ( + LimitUserToken = 3 + LimitGroupToken = 20 +) + +// VK struct. +type VK struct { + accessTokens []string + lastToken uint32 + MethodURL string + Version string + Client *http.Client + Limit int + UserAgent string + Handler func(method string, params ...Params) (Response, error) + + mux sync.Mutex + lastTime time.Time + rps int +} + +// Response struct. +type Response struct { + Response json.RawMessage `json:"response"` + Error Error `json:"error"` + ExecuteErrors ExecuteErrors `json:"execute_errors"` +} + +// NewVK returns a new VK. +// +// The VKSDK will use the http.DefaultClient. +// This means that if the http.DefaultClient is modified by other components +// of your application the modifications will be picked up by the SDK as well. +// +// In some cases this might be intended, but it is a better practice +// to create a custom HTTP Client to share explicitly through +// your application. You can configure the VKSDK to use the custom +// HTTP Client by setting the VK.Client value. +// +// This set limit 20 requests per second for one token. +func NewVK(tokens ...string) *VK { + var vk VK + + vk.accessTokens = tokens + vk.Version = Version + + vk.Handler = vk.defaultHandler + + vk.MethodURL = MethodURL + vk.Client = http.DefaultClient + vk.Limit = LimitGroupToken + vk.UserAgent = internal.UserAgent + + return &vk +} + +// getToken return next token (simple round-robin). +func (vk *VK) getToken() string { + i := atomic.AddUint32(&vk.lastToken, 1) + return vk.accessTokens[(int(i)-1)%len(vk.accessTokens)] +} + +// Params type. +type Params map[string]interface{} + +// Lang - determines the language for the data to be displayed on. For +// example country and city names. If you use a non-cyrillic language, +// cyrillic symbols will be transliterated automatically. +// Numeric format from account.getInfo is supported as well. +// +// p.Lang(object.LangRU) +// +// See all language code in module object. +func (p Params) Lang(v int) Params { + p["lang"] = v + return p +} + +// TestMode allows to send requests from a native app without switching it on +// for all users. +func (p Params) TestMode(v bool) Params { + p["test_mode"] = v + return p +} + +// CaptchaSID received ID. +// +// See https://vk.com/dev/captcha_error +func (p Params) CaptchaSID(v string) Params { + p["captcha_sid"] = v + return p +} + +// CaptchaKey text input. +// +// See https://vk.com/dev/captcha_error +func (p Params) CaptchaKey(v string) Params { + p["captcha_key"] = v + return p +} + +// Confirm parameter. +// +// See https://vk.com/dev/need_confirmation +func (p Params) Confirm(v bool) Params { + p["confirm"] = v + return p +} + +// WithContext parameter. +func (p Params) WithContext(ctx context.Context) Params { + p[":context"] = ctx + return p +} + +func buildQuery(sliceParams ...Params) (context.Context, url.Values) { + query := url.Values{} + ctx := context.Background() + + for _, params := range sliceParams { + for key, value := range params { + if key != ":context" { + query.Set(key, FmtValue(value, 0)) + } else { + ctx = value.(context.Context) + } + } + } + + return ctx, query +} + +// defaultHandler provides access to VK API methods. +func (vk *VK) defaultHandler(method string, sliceParams ...Params) (Response, error) { + u := vk.MethodURL + method + ctx, query := buildQuery(sliceParams...) + attempt := 0 + + for { + var response Response + + attempt++ + + // Rate limiting + if vk.Limit > 0 { + vk.mux.Lock() + + sleepTime := time.Second - time.Since(vk.lastTime) + if sleepTime < 0 { + vk.lastTime = time.Now() + vk.rps = 0 + } else if vk.rps == vk.Limit*len(vk.accessTokens) { + time.Sleep(sleepTime) + vk.lastTime = time.Now() + vk.rps = 0 + } + vk.rps++ + + vk.mux.Unlock() + } + + rawBody := bytes.NewBufferString(query.Encode()) + + req, err := http.NewRequestWithContext(ctx, "POST", u, rawBody) + if err != nil { + return response, err + } + + req.Header.Set("User-Agent", vk.UserAgent) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := vk.Client.Do(req) + if err != nil { + return response, err + } + + mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if mediatype != "application/json" { + _ = resp.Body.Close() + return response, &InvalidContentType{mediatype} + } + + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + _ = resp.Body.Close() + return response, err + } + + _ = resp.Body.Close() + + switch response.Error.Code { + case ErrNoType: + return response, nil + case ErrTooMany: + if attempt < vk.Limit { + continue + } + + return response, &response.Error + } + + return response, &response.Error + } +} + +// Request provides access to VK API methods. +func (vk *VK) Request(method string, sliceParams ...Params) ([]byte, error) { + token := vk.getToken() + + reqParams := Params{ + "access_token": token, + "v": vk.Version, + } + + sliceParams = append(sliceParams, reqParams) + + resp, err := vk.Handler(method, sliceParams...) + + return resp.Response, err +} + +// RequestUnmarshal provides access to VK API methods. +func (vk *VK) RequestUnmarshal(method string, obj interface{}, sliceParams ...Params) error { + rawResponse, err := vk.Request(method, sliceParams...) + if err != nil { + return err + } + + return json.Unmarshal(rawResponse, &obj) +} + +func fmtReflectValue(value reflect.Value, depth int) string { + switch f := value; value.Kind() { + case reflect.Invalid: + return "" + case reflect.Bool: + return fmtBool(f.Bool()) + case reflect.Array, reflect.Slice: + s := "" + + for i := 0; i < f.Len(); i++ { + if i > 0 { + s += "," + } + + s += FmtValue(f.Index(i).Interface(), depth) + } + + return s + case reflect.Ptr: + // pointer to array or slice or struct? ok at top level + // but not embedded (avoid loops) + if depth == 0 && f.Pointer() != 0 { + switch a := f.Elem(); a.Kind() { + case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: + return FmtValue(a.Interface(), depth+1) + } + } + } + + return fmt.Sprint(value) +} + +// FmtValue return vk format string. +func FmtValue(value interface{}, depth int) string { + if value == nil { + return "" + } + + switch f := value.(type) { + case bool: + return fmtBool(f) + case object.Attachment: + return f.ToAttachment() + case object.JSONObject: + return f.ToJSON() + case reflect.Value: + return fmtReflectValue(f, depth) + } + + return fmtReflectValue(reflect.ValueOf(value), depth) +} |