2021-01-28 23:25:14 +00:00
|
|
|
/*
|
|
|
|
Package longpoll implements Bots Long Poll API.
|
|
|
|
|
|
|
|
See more https://vk.com/dev/bots_longpoll
|
|
|
|
*/
|
|
|
|
package longpoll // import "github.com/SevereCloud/vksdk/v2/longpoll-bot"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2022-01-28 22:48:40 +00:00
|
|
|
"errors"
|
2021-01-28 23:25:14 +00:00
|
|
|
"fmt"
|
2022-01-28 22:48:40 +00:00
|
|
|
"io"
|
2021-01-28 23:25:14 +00:00
|
|
|
"net/http"
|
2022-01-28 22:48:40 +00:00
|
|
|
"strconv"
|
2021-01-28 23:25:14 +00:00
|
|
|
|
|
|
|
"github.com/SevereCloud/vksdk/v2"
|
|
|
|
"github.com/SevereCloud/vksdk/v2/api"
|
|
|
|
"github.com/SevereCloud/vksdk/v2/events"
|
|
|
|
"github.com/SevereCloud/vksdk/v2/internal"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Response struct.
|
|
|
|
type Response struct {
|
|
|
|
Ts string `json:"ts"`
|
|
|
|
Updates []events.GroupEvent `json:"updates"`
|
|
|
|
Failed int `json:"failed"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// LongPoll struct.
|
|
|
|
type LongPoll struct {
|
|
|
|
GroupID int
|
|
|
|
Server string
|
|
|
|
Key string
|
|
|
|
Ts string
|
|
|
|
Wait int
|
|
|
|
VK *api.VK
|
|
|
|
Client *http.Client
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
|
|
|
funcFullResponseList []func(Response)
|
|
|
|
|
|
|
|
events.FuncList
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewLongPoll returns a new LongPoll.
|
|
|
|
//
|
|
|
|
// The LongPoll 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.
|
|
|
|
func NewLongPoll(vk *api.VK, groupID int) (*LongPoll, error) {
|
|
|
|
lp := &LongPoll{
|
|
|
|
VK: vk,
|
|
|
|
GroupID: groupID,
|
|
|
|
Wait: 25,
|
|
|
|
Client: http.DefaultClient,
|
|
|
|
}
|
|
|
|
lp.FuncList = *events.NewFuncList()
|
|
|
|
|
|
|
|
err := lp.updateServer(true)
|
|
|
|
|
|
|
|
return lp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewLongPollCommunity returns a new LongPoll for community token.
|
|
|
|
//
|
|
|
|
// The LongPoll 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.
|
|
|
|
func NewLongPollCommunity(vk *api.VK) (*LongPoll, error) {
|
|
|
|
resp, err := vk.GroupsGetByID(nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
lp := &LongPoll{
|
|
|
|
VK: vk,
|
|
|
|
GroupID: resp[0].ID,
|
|
|
|
Wait: 25,
|
|
|
|
Client: http.DefaultClient,
|
|
|
|
}
|
|
|
|
lp.FuncList = *events.NewFuncList()
|
|
|
|
|
|
|
|
err = lp.updateServer(true)
|
|
|
|
|
|
|
|
return lp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lp *LongPoll) updateServer(updateTs bool) error {
|
|
|
|
params := api.Params{
|
|
|
|
"group_id": lp.GroupID,
|
|
|
|
}
|
|
|
|
|
|
|
|
serverSetting, err := lp.VK.GroupsGetLongPollServer(params)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
lp.Key = serverSetting.Key
|
|
|
|
lp.Server = serverSetting.Server
|
|
|
|
|
|
|
|
if updateTs {
|
|
|
|
lp.Ts = serverSetting.Ts
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lp *LongPoll) check(ctx context.Context) (response Response, err error) {
|
|
|
|
u := fmt.Sprintf("%s?act=a_check&key=%s&ts=%s&wait=%d", lp.Server, lp.Key, lp.Ts, lp.Wait)
|
|
|
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := lp.Client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2022-01-28 22:48:40 +00:00
|
|
|
response, err = parseResponse(resp.Body)
|
2021-01-28 23:25:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = lp.checkResponse(response)
|
|
|
|
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
2022-01-28 22:48:40 +00:00
|
|
|
func parseResponse(reader io.Reader) (response Response, err error) {
|
|
|
|
decoder := json.NewDecoder(reader)
|
|
|
|
for decoder.More() {
|
|
|
|
token, err := decoder.Token()
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
t, ok := token.(string)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t {
|
|
|
|
case "failed":
|
|
|
|
raw, err := decoder.Token()
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Failed = int(raw.(float64))
|
|
|
|
case "updates":
|
|
|
|
var updates []events.GroupEvent
|
|
|
|
|
|
|
|
err = decoder.Decode(&updates)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Updates = updates
|
|
|
|
case "ts":
|
|
|
|
// can be a number in the response with "failed" field: {"ts":8,"failed":1}
|
|
|
|
// or string, e.g. {"ts":"8","updates":[]}
|
|
|
|
rawTs, err := decoder.Token()
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if ts, isNumber := rawTs.(float64); isNumber {
|
|
|
|
response.Ts = strconv.Itoa(int(ts))
|
|
|
|
} else {
|
|
|
|
response.Ts = rawTs.(string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
2021-01-28 23:25:14 +00:00
|
|
|
func (lp *LongPoll) checkResponse(response Response) (err error) {
|
|
|
|
switch response.Failed {
|
|
|
|
case 0:
|
|
|
|
lp.Ts = response.Ts
|
|
|
|
case 1:
|
|
|
|
lp.Ts = response.Ts
|
|
|
|
case 2:
|
|
|
|
err = lp.updateServer(false)
|
|
|
|
case 3:
|
|
|
|
err = lp.updateServer(true)
|
|
|
|
default:
|
|
|
|
err = &Failed{response.Failed}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lp *LongPoll) autoSetting(ctx context.Context) error {
|
|
|
|
params := api.Params{
|
|
|
|
"group_id": lp.GroupID,
|
|
|
|
"enabled": true,
|
|
|
|
"api_version": vksdk.API,
|
|
|
|
}.WithContext(ctx)
|
|
|
|
for _, event := range lp.ListEvents() {
|
|
|
|
params[string(event)] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Updating LongPoll settings
|
|
|
|
_, err := lp.VK.GroupsSetLongPollSettings(params)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run handler.
|
|
|
|
func (lp *LongPoll) Run() error {
|
|
|
|
return lp.RunWithContext(context.Background())
|
|
|
|
}
|
|
|
|
|
|
|
|
// RunWithContext handler.
|
|
|
|
func (lp *LongPoll) RunWithContext(ctx context.Context) error {
|
|
|
|
return lp.run(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lp *LongPoll) run(ctx context.Context) error {
|
|
|
|
ctx, lp.cancel = context.WithCancel(ctx)
|
|
|
|
|
2021-05-29 22:25:30 +00:00
|
|
|
if err := lp.autoSetting(ctx); err != nil {
|
2021-01-28 23:25:14 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case _, ok := <-ctx.Done():
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
resp, err := lp.check(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = context.WithValue(ctx, internal.LongPollTsKey, resp.Ts)
|
|
|
|
|
|
|
|
for _, event := range resp.Updates {
|
|
|
|
err = lp.Handler(ctx, event)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range lp.funcFullResponseList {
|
|
|
|
f(resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown gracefully shuts down the longpoll without interrupting any active connections.
|
|
|
|
func (lp *LongPoll) Shutdown() {
|
|
|
|
if lp.cancel != nil {
|
|
|
|
lp.cancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FullResponse handler.
|
|
|
|
func (lp *LongPoll) FullResponse(f func(Response)) {
|
|
|
|
lp.funcFullResponseList = append(lp.funcFullResponseList, f)
|
|
|
|
}
|