New BotApi
This commit is contained in:
parent
f9744b81d9
commit
9efc5ffd51
19 changed files with 962 additions and 513 deletions
109
README.md
109
README.md
|
@ -1,85 +1,72 @@
|
|||
# ICQ Bot API
|
||||
# ICQ Bot Api Go
|
||||
|
||||
## Installation
|
||||
[![Sourcegraph](https://sourcegraph.com/github.com/go-icq/icq/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/go-icq/icq?badge)
|
||||
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/go-icq/icq)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/go-icq/icq?style=flat-square)](https://goreportcard.com/report/github.com/go-icq/icq)
|
||||
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/go-icq/icq/master/LICENSE)
|
||||
|
||||
Go get: `go get gopkg.in/icq.v2`
|
||||
Основана на новом Bot Api (https://icq.com/botapi/)
|
||||
|
||||
Go mod / Go dep: `import "gopkg.in/icq.v2"`
|
||||
Реализованы все методы и соответствуют документации.
|
||||
|
||||
|
||||
## Working
|
||||
|
||||
Methods:
|
||||
|
||||
* SendMessage
|
||||
* UploadFile
|
||||
* FetchEvents
|
||||
|
||||
Webhooks workds but not recommends
|
||||
|
||||
## Example
|
||||
## Пример
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"gopkg.in/icq.v2"
|
||||
"github.com/go-icq/icq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// New API object
|
||||
b := icq.NewAPI(os.Getenv("ICQ_TOKEN"))
|
||||
// Инициализация
|
||||
b := icq.NewApi(os.Getenv("ICQ_TOKEN"), icq.ICQ) // or icq.Agent
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
// Получение информации о боте
|
||||
log.Println(b.Self.Get())
|
||||
|
||||
ch := make(chan interface{}) // Events channel
|
||||
osSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignal, os.Interrupt)
|
||||
signal.Notify(osSignal, os.Kill)
|
||||
// Отправка сообщения
|
||||
resultSend, err := b.Messages.SendText("429950", "Привет!", nil, "", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go b.FetchEvents(ctx, ch) // Events fetch loop
|
||||
// Отправка файла
|
||||
resultFile, err := b.Messages.SendFile("429950", "./example/example.jpg", "коржик", []string{resultSend.MsgID}, "", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-ch:
|
||||
handleEvent(b, e)
|
||||
case <-osSignal:
|
||||
cancel()
|
||||
break
|
||||
// Отправка существующего файла по ID
|
||||
_, err = b.Messages.SendExistsFile("429950", resultFile.FileID, "Существующий файл", nil, "", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Редактирование сообщения
|
||||
_, err = b.Messages.EditText("429950", "Новый текст", resultSend.MsgID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Будем слушать эвенты 5 минут. При закрытии контекста перестает работать цикл получения событий. В реальном мире контекст надо будет закрывать по сигналу ОС
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
for ev := range b.Events.Get(ctx) {
|
||||
switch ev := ev.(type) {
|
||||
case *icq.EventDataMessage:
|
||||
b.Messages.SendText(ev.Payload.Chat.ChatID, "Echo: "+ev.Payload.Text, []string{ev.Payload.MsgID}, "", "")
|
||||
default:
|
||||
log.Println(ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleEvent(b *icq.API, event interface{}) {
|
||||
switch event.(type) {
|
||||
case *icq.IMEvent:
|
||||
message := event.(*icq.IMEvent)
|
||||
if err := handleMessage(b, message); err != nil {
|
||||
b.SendMessage(icq.Message{
|
||||
To: message.Data.Source.AimID,
|
||||
Text: "Message process fail",
|
||||
})
|
||||
}
|
||||
default:
|
||||
log.Printf("%#v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(b *icq.API, message *icq.IMEvent) error {
|
||||
cmd, ok := icq.ParseCommand(message)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
_, err := b.SendMessage(icq.Message{
|
||||
To: cmd.From,
|
||||
Text: fmt.Sprintf("Command: %s, Arguments: %v", cmd.Command, cmd.Arguments),
|
||||
})
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
## Автор
|
||||
|
||||
Александр NeonXP Кирюхин <a.kiryukhin@mail.ru>
|
52
api.go
52
api.go
|
@ -1,52 +0,0 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTTP Client interface
|
||||
type Doer interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// API
|
||||
type API struct {
|
||||
token string
|
||||
baseUrl string
|
||||
client Doer
|
||||
fetchBase string
|
||||
}
|
||||
|
||||
// NewAPI constructor of API object
|
||||
func NewAPI(token string) *API {
|
||||
return &API{
|
||||
token: token,
|
||||
baseUrl: "https://botapi.icq.net",
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *API) send(path string, v url.Values) ([]byte, error) {
|
||||
req, err := http.NewRequest(http.MethodPost, a.baseUrl+path, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := a.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return b, fmt.Errorf("ICQ API error. Code=%d Message=%s", resp.StatusCode, string(b))
|
||||
}
|
||||
return b, nil
|
||||
}
|
68
chats.go
Normal file
68
chats.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type chats struct {
|
||||
client *client
|
||||
}
|
||||
|
||||
func newChats(client *client) *chats {
|
||||
return &chats{client: client}
|
||||
}
|
||||
|
||||
func (s *chats) SendActions(chatID string, actions []ChatAction) (bool, error) {
|
||||
acts := []string{}
|
||||
for _, act := range actions {
|
||||
acts = append(acts, string(act))
|
||||
}
|
||||
resp, err := s.client.request(
|
||||
http.MethodGet,
|
||||
"/chats/sendActions",
|
||||
url.Values{
|
||||
"chatId": []string{chatID},
|
||||
"actions": acts,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
result := new(OK)
|
||||
return result.OK, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (s *chats) GetInfo(chatID string) (*Chat, error) {
|
||||
resp, err := s.client.request(
|
||||
http.MethodGet,
|
||||
"/chats/getInfo",
|
||||
url.Values{
|
||||
"chatId": []string{chatID},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Chat)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (s *chats) GetAdmins(chatID string) (*Admins, error) {
|
||||
resp, err := s.client.request(
|
||||
http.MethodGet,
|
||||
"/chats/getAdmins",
|
||||
url.Values{
|
||||
"chatId": []string{chatID},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Admins)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
75
client.go
Normal file
75
client.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ApiType int
|
||||
|
||||
const (
|
||||
ICQ ApiType = iota
|
||||
Agent
|
||||
)
|
||||
|
||||
var servers = map[ApiType]string{
|
||||
ICQ: "https://api.icq.net/bot/v1/",
|
||||
Agent: "https://agent.mail.ru/bot/v1/",
|
||||
}
|
||||
|
||||
type client struct {
|
||||
token string
|
||||
apiType ApiType
|
||||
client http.Client
|
||||
}
|
||||
|
||||
func newClient(token string, apiType ApiType) *client {
|
||||
return &client{token: token, apiType: apiType, client: http.Client{Timeout: 30 * time.Second}}
|
||||
}
|
||||
|
||||
func (c *client) request(method string, methodPath string, query url.Values, body *bytes.Buffer) (io.Reader, error) {
|
||||
return c.requestWithContentType(method, methodPath, query, body, "")
|
||||
}
|
||||
|
||||
func (c *client) requestWithContentType(method string, methodPath string, query url.Values, body *bytes.Buffer, contentType string) (io.Reader, error) {
|
||||
query.Set("token", c.token)
|
||||
u, err := url.Parse(servers[c.apiType])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = path.Join(u.Path, methodPath)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), nil)
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
if body != nil {
|
||||
rc := ioutil.NopCloser(body)
|
||||
req.Body = rc
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
errObj := new(Error)
|
||||
err = json.NewDecoder(resp.Body).Decode(errObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("ok=%v message=%s", errObj.OK, errObj.Description)
|
||||
}
|
||||
return resp.Body, err
|
||||
}
|
164
events.go
164
events.go
|
@ -1,60 +1,118 @@
|
|||
package icq
|
||||
|
||||
type CommonEvent struct {
|
||||
Type string `json:"type"`
|
||||
SeqNum int `json:"seqNum"`
|
||||
}
|
||||
|
||||
type ServiceEvent struct {
|
||||
CommonEvent
|
||||
Data interface{} `json:"eventData"`
|
||||
}
|
||||
|
||||
type BuddyListEvent struct {
|
||||
CommonEvent
|
||||
Data struct {
|
||||
Groups []struct {
|
||||
Name string `json:"name"`
|
||||
ID int `json:"id"`
|
||||
Buddies []Buddy `json:"buddies"`
|
||||
} `json:"groups"`
|
||||
} `json:"eventData"`
|
||||
}
|
||||
|
||||
type MyInfoEvent struct {
|
||||
CommonEvent
|
||||
Data Buddy `json:"eventData"`
|
||||
}
|
||||
|
||||
type TypingStatus string
|
||||
|
||||
const (
|
||||
StartTyping TypingStatus = "typing"
|
||||
StopTyping = "none"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TypingEvent struct {
|
||||
CommonEvent
|
||||
Data struct {
|
||||
AimID string `json:"aimId"`
|
||||
TypingStatus TypingStatus `json:"typingStatus"`
|
||||
} `json:"eventData"`
|
||||
type events struct {
|
||||
client *client
|
||||
}
|
||||
|
||||
type IMEvent struct {
|
||||
CommonEvent
|
||||
Data struct {
|
||||
Autoresponse int `json:"autoresponse"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Notification string `json:"notification"`
|
||||
MsgID string `json:"msgId"`
|
||||
IMF string `json:"imf"`
|
||||
Message string `json:"message"`
|
||||
RawMessage struct {
|
||||
IPCountry string `json:"ipCountry"`
|
||||
ClientCountry string `json:"clientCountry"`
|
||||
Base64Msg string `json:"base64Msg"`
|
||||
} `json:"rawMsg"`
|
||||
Source Buddy `json:"source"`
|
||||
} `json:"eventData"`
|
||||
func newEvents(client *client) *events {
|
||||
return &events{client: client}
|
||||
}
|
||||
|
||||
func (e *events) Get(ctx context.Context) <-chan EventInterface {
|
||||
ch := make(chan EventInterface)
|
||||
go func() {
|
||||
lastEvent := 0
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
close(ch)
|
||||
return
|
||||
}
|
||||
events, err := e.getEvents(lastEvent)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
<-time.After(5 * time.Second) // Retry after 5 seconds
|
||||
continue
|
||||
}
|
||||
for _, e := range events.Events {
|
||||
ch <- e
|
||||
lastEvent = e.GetEventID()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (e *events) getEvents(lastEvent int) (*Events, error) {
|
||||
resp, err := e.client.request(
|
||||
http.MethodGet,
|
||||
"/events/get",
|
||||
url.Values{
|
||||
"lastEventId": []string{strconv.Itoa(lastEvent)},
|
||||
"pollTime": []string{"30"},
|
||||
},
|
||||
nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tempResult := new(RawEvents)
|
||||
if err := json.NewDecoder(resp).Decode(tempResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Events)
|
||||
for _, e := range tempResult.Events {
|
||||
tempEvent := new(Event)
|
||||
if err := json.Unmarshal(e, tempEvent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ev EventInterface
|
||||
switch tempEvent.GetType() {
|
||||
case EventTypeDataMessage:
|
||||
ev = new(EventDataMessage)
|
||||
case EventTypeEditedMessage:
|
||||
ev = new(EventEditedMessage)
|
||||
case EventTypeDeletedMessage:
|
||||
ev = new(EventDeletedMessage)
|
||||
case EventTypePinnedMessage:
|
||||
ev = new(EventPinnedMessage)
|
||||
case EventTypeUnpinnedMessage:
|
||||
ev = new(EventUnpinnedMessage)
|
||||
case EventTypeNewChatMembers:
|
||||
ev = new(EventNewChatMembers)
|
||||
case EventTypeLeftChatMembers:
|
||||
ev = new(EventLeftChatMembers)
|
||||
}
|
||||
if err := json.Unmarshal(e, ev); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch ev := ev.(type) {
|
||||
case *EventDataMessage:
|
||||
for _, ea := range ev.Payload.RawParts {
|
||||
tempAttachment := new(Attachment)
|
||||
if err := json.Unmarshal(ea, tempAttachment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var eav AttachmentInterface
|
||||
switch tempAttachment.Type {
|
||||
case AttachmentTypeSticker:
|
||||
eav = new(AttachmentSticker)
|
||||
case AttachmentTypeMention:
|
||||
eav = new(AttachmentMention)
|
||||
case AttachmentTypeVoice:
|
||||
eav = new(AttachmentVoice)
|
||||
case AttachmentTypeFile:
|
||||
eav = new(AttachmentFile)
|
||||
case AttachmentTypeForward:
|
||||
eav = new(AttachmentForward)
|
||||
case AttachmentTypeReply:
|
||||
eav = new(AttachmentReply)
|
||||
}
|
||||
if err := json.Unmarshal(ea, eav); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ev.Payload.Parts = append(ev.Payload.Parts, eav)
|
||||
}
|
||||
}
|
||||
result.Events = append(result.Events, ev)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -2,60 +2,52 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-icq/icq"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/go-icq/icq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// New API object
|
||||
b := icq.NewAPI(os.Getenv("ICQ_TOKEN"))
|
||||
// Инициализация
|
||||
b := icq.NewApi(os.Getenv("ICQ_TOKEN"), icq.ICQ) // or icq.Agent
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
// Получение информации о боте
|
||||
log.Println(b.Self.Get())
|
||||
|
||||
ch := make(chan interface{}) // Events channel
|
||||
osSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignal, os.Interrupt)
|
||||
signal.Notify(osSignal, os.Kill)
|
||||
// Отправка сообщения
|
||||
resultSend, err := b.Messages.SendText("429950", "Привет!", nil, "", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go b.FetchEvents(ctx, ch) // Events fetch loop
|
||||
// Отправка файла
|
||||
resultFile, err := b.Messages.SendFile("429950", "./example/example.jpg", "коржик", []string{resultSend.MsgID}, "", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-ch:
|
||||
handleEvent(b, e)
|
||||
case <-osSignal:
|
||||
cancel()
|
||||
break
|
||||
// Отправка существующего файла по ID
|
||||
_, err = b.Messages.SendExistsFile("429950", resultFile.FileID, "Существующий файл", nil, "", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Редактирование сообщения
|
||||
_, err = b.Messages.EditText("429950", "Новый текст", resultSend.MsgID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Будем слушать эвенты 5 минут. При закрытии контекста перестает работать цикл получения событий. В реальном мире контекст надо будет закрывать по сигналу ОС
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
for ev := range b.Events.Get(ctx) {
|
||||
switch ev := ev.(type) {
|
||||
case *icq.EventDataMessage:
|
||||
b.Messages.SendText(ev.Payload.Chat.ChatID, "Echo: "+ev.Payload.Text, []string{ev.Payload.MsgID}, "", "")
|
||||
default:
|
||||
log.Println(ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleEvent(b *icq.API, event interface{}) {
|
||||
switch event.(type) {
|
||||
case *icq.IMEvent:
|
||||
message := event.(*icq.IMEvent)
|
||||
if err := handleMessage(b, message); err != nil {
|
||||
b.SendMessage(icq.Message{
|
||||
To: message.Data.Source.AimID,
|
||||
Text: "Message process fail",
|
||||
})
|
||||
}
|
||||
default:
|
||||
log.Printf("%#v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(b *icq.API, message *icq.IMEvent) error {
|
||||
cmd, ok := icq.ParseCommand(message)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
_, err := b.SendMessage(icq.Message{
|
||||
To: cmd.From,
|
||||
Text: fmt.Sprintf("Command: %s, Arguments: %v", cmd.Command, cmd.Arguments),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
BIN
example/example.jpg
Normal file
BIN
example/example.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
|
@ -1,88 +0,0 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (a *API) FetchEvents(ctx context.Context, ch chan interface{}) error {
|
||||
fetchResp := &struct {
|
||||
Response struct {
|
||||
Data struct {
|
||||
FetchBase string `json:"fetchBaseURL"`
|
||||
PollTime int `json:"pollTime"`
|
||||
Events []json.RawMessage `json:"events"`
|
||||
} `json:"data"`
|
||||
} `json:"response"`
|
||||
}{}
|
||||
for {
|
||||
b := []byte{}
|
||||
u := a.fetchBase
|
||||
if u == "" {
|
||||
v := url.Values{}
|
||||
v.Set("aimsid", a.token)
|
||||
v.Set("first", "1")
|
||||
u = a.baseUrl + "/fetchEvents?" + v.Encode()
|
||||
}
|
||||
req, err := http.Get(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err = ioutil.ReadAll(req.Body)
|
||||
req.Body.Close()
|
||||
if err := json.Unmarshal(b, fetchResp); err != nil {
|
||||
return err
|
||||
}
|
||||
a.fetchBase = fetchResp.Response.Data.FetchBase
|
||||
for _, e := range fetchResp.Response.Data.Events {
|
||||
ce := &CommonEvent{}
|
||||
if err := json.Unmarshal(e, ce); err != nil {
|
||||
return err
|
||||
}
|
||||
switch ce.Type {
|
||||
case "service":
|
||||
ev := &ServiceEvent{}
|
||||
if err := json.Unmarshal(e, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- ev
|
||||
case "buddylist":
|
||||
ev := &BuddyListEvent{}
|
||||
if err := json.Unmarshal(e, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- ev
|
||||
case "myInfo":
|
||||
ev := &MyInfoEvent{}
|
||||
if err := json.Unmarshal(e, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- ev
|
||||
case "typing":
|
||||
ev := &TypingEvent{}
|
||||
if err := json.Unmarshal(e, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- ev
|
||||
case "im":
|
||||
ev := &IMEvent{}
|
||||
if err := json.Unmarshal(e, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- ev
|
||||
default:
|
||||
ch <- ce
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Duration(fetchResp.Response.Data.PollTime)):
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
31
files.go
Normal file
31
files.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type files struct {
|
||||
client *client
|
||||
}
|
||||
|
||||
func newFiles(client *client) *files {
|
||||
return &files{client: client}
|
||||
}
|
||||
|
||||
func (f *files) GetInfo(fileID string) (*FileInfo, error) {
|
||||
resp, err := f.client.request(
|
||||
http.MethodGet,
|
||||
"/chats/getInfo",
|
||||
url.Values{
|
||||
"fileId": []string{fileID},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(FileInfo)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
2
go.mod
2
go.mod
|
@ -1 +1,3 @@
|
|||
module github.com/go-icq/icq
|
||||
|
||||
go 1.12
|
||||
|
|
20
icq.go
Normal file
20
icq.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package icq
|
||||
|
||||
type Api struct {
|
||||
Self *self
|
||||
Chats *chats
|
||||
Files *files
|
||||
Messages *messages
|
||||
Events *events
|
||||
}
|
||||
|
||||
func NewApi(token string, apiType ApiType) *Api {
|
||||
client := newClient(token, apiType)
|
||||
return &Api{
|
||||
Self: newSelf(client),
|
||||
Chats: newChats(client),
|
||||
Files: newFiles(client),
|
||||
Messages: newMessages(client),
|
||||
Events: newEvents(client),
|
||||
}
|
||||
}
|
264
messages.go
Normal file
264
messages.go
Normal file
|
@ -0,0 +1,264 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
type messages struct {
|
||||
client *client
|
||||
}
|
||||
|
||||
func newMessages(client *client) *messages {
|
||||
return &messages{client: client}
|
||||
}
|
||||
|
||||
func (f *messages) SendText(chatID string, text string, replyMsgID []string, forwardChatID string, forwardMsgID string) (*Msg, error) {
|
||||
params := url.Values{
|
||||
"chatId": []string{chatID},
|
||||
"text": []string{text},
|
||||
}
|
||||
if replyMsgID != nil && len(replyMsgID) > 0 {
|
||||
for _, msgID := range replyMsgID {
|
||||
params.Add("replyMsgId", msgID)
|
||||
}
|
||||
}
|
||||
if forwardChatID != "" {
|
||||
params.Set("forwardChatId", forwardChatID)
|
||||
}
|
||||
if forwardMsgID != "" {
|
||||
params.Set("forwardMsgId", forwardMsgID)
|
||||
}
|
||||
resp, err := f.client.request(
|
||||
http.MethodGet,
|
||||
"/messages/sendText",
|
||||
params,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Msg)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (f *messages) SendExistsFile(chatID string, fileID string, caption string, replyMsgID []string, forwardChatID string, forwardMsgID string) (*Msg, error) {
|
||||
params := url.Values{
|
||||
"chatId": []string{chatID},
|
||||
"fileId": []string{fileID},
|
||||
"caption": []string{caption},
|
||||
}
|
||||
if replyMsgID != nil && len(replyMsgID) > 0 {
|
||||
for _, msgID := range replyMsgID {
|
||||
params.Add("replyMsgId", msgID)
|
||||
}
|
||||
}
|
||||
if forwardChatID != "" {
|
||||
params.Set("forwardChatId", forwardChatID)
|
||||
}
|
||||
if forwardMsgID != "" {
|
||||
params.Set("forwardMsgId", forwardMsgID)
|
||||
}
|
||||
resp, err := f.client.request(
|
||||
http.MethodGet,
|
||||
"/messages/sendFile",
|
||||
params,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Msg)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (f *messages) SendFile(chatID string, fileName string, caption string, replyMsgID []string, forwardChatID string, forwardMsgID string) (*MsgLoadFile, error) {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
|
||||
fileWriter, err := bodyWriter.CreateFormFile("file", fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fh, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := fh.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bodyWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
|
||||
params := url.Values{
|
||||
"chatId": []string{chatID},
|
||||
"caption": []string{caption},
|
||||
}
|
||||
if replyMsgID != nil && len(replyMsgID) > 0 {
|
||||
for _, msgID := range replyMsgID {
|
||||
params.Add("replyMsgId", msgID)
|
||||
}
|
||||
}
|
||||
if forwardChatID != "" {
|
||||
params.Set("forwardChatId", forwardChatID)
|
||||
}
|
||||
if forwardMsgID != "" {
|
||||
params.Set("forwardMsgId", forwardMsgID)
|
||||
}
|
||||
resp, err := f.client.requestWithContentType(
|
||||
http.MethodPost,
|
||||
"/messages/sendFile",
|
||||
params,
|
||||
bodyBuf,
|
||||
contentType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(MsgLoadFile)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (f *messages) SendExistsVoice(chatID string, fileID string, replyMsgID []string, forwardChatID string, forwardMsgID string) (*Msg, error) {
|
||||
params := url.Values{
|
||||
"chatId": []string{chatID},
|
||||
"fileId": []string{fileID},
|
||||
}
|
||||
if replyMsgID != nil && len(replyMsgID) > 0 {
|
||||
for _, msgID := range replyMsgID {
|
||||
params.Add("replyMsgId", msgID)
|
||||
}
|
||||
}
|
||||
if forwardChatID != "" {
|
||||
params.Set("forwardChatId", forwardChatID)
|
||||
}
|
||||
if forwardMsgID != "" {
|
||||
params.Set("forwardMsgId", forwardMsgID)
|
||||
}
|
||||
resp, err := f.client.request(
|
||||
http.MethodGet,
|
||||
"/messages/sendVoice",
|
||||
params,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Msg)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (f *messages) SendVoice(chatID string, fileName string, replyMsgID []string, forwardChatID string, forwardMsgID string) (*MsgLoadFile, error) {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
|
||||
fileWriter, err := bodyWriter.CreateFormFile("file", fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fh, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := fh.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bodyWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
|
||||
params := url.Values{
|
||||
"chatId": []string{chatID},
|
||||
}
|
||||
if replyMsgID != nil && len(replyMsgID) > 0 {
|
||||
for _, msgID := range replyMsgID {
|
||||
params.Add("replyMsgId", msgID)
|
||||
}
|
||||
}
|
||||
if forwardChatID != "" {
|
||||
params.Set("forwardChatId", forwardChatID)
|
||||
}
|
||||
if forwardMsgID != "" {
|
||||
params.Set("forwardMsgId", forwardMsgID)
|
||||
}
|
||||
resp, err := f.client.requestWithContentType(
|
||||
http.MethodPost,
|
||||
"/messages/sendVoice",
|
||||
params,
|
||||
bodyBuf,
|
||||
contentType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(MsgLoadFile)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (f *messages) EditText(chatID string, text string, msgID string) (bool, error) {
|
||||
params := url.Values{
|
||||
"msgId": []string{msgID},
|
||||
"chatId": []string{chatID},
|
||||
"text": []string{text},
|
||||
}
|
||||
resp, err := f.client.request(
|
||||
http.MethodGet,
|
||||
"/messages/editText",
|
||||
params,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
result := new(OK)
|
||||
return result.OK, json.NewDecoder(resp).Decode(result)
|
||||
}
|
||||
|
||||
func (f *messages) DeleteMessages(chatID string, msgIDs []string) (bool, error) {
|
||||
params := url.Values{
|
||||
"chatId": []string{chatID},
|
||||
}
|
||||
if msgIDs != nil && len(msgIDs) > 0 {
|
||||
for _, msgID := range msgIDs {
|
||||
params.Add("msgId", msgID)
|
||||
}
|
||||
}
|
||||
resp, err := f.client.request(
|
||||
http.MethodGet,
|
||||
"/messages/deleteMessages",
|
||||
params,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
result := new(OK)
|
||||
return result.OK, json.NewDecoder(resp).Decode(result)
|
||||
}
|
282
schemas.go
Normal file
282
schemas.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
package icq
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Bot struct {
|
||||
UserID string `json:"userId"` // уникальный идентификатор
|
||||
Nick string `json:"nick"` // уникальный ник
|
||||
FirstName string `json:"firstName"` // имя
|
||||
About string `json:"about"` // описание бота
|
||||
Photo []struct {
|
||||
URL string `json:"url"` // url
|
||||
} `json:"photo"` // аватар бота
|
||||
OK bool `json:"ok"` // статус запроса
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
InviteLink string `json:"inviteLink"`
|
||||
Public bool `json:"public"`
|
||||
Title string `json:"title"`
|
||||
Group string `json:"group"`
|
||||
OK bool `json:"ok"` // статус запроса
|
||||
}
|
||||
|
||||
type Admin struct {
|
||||
UserID string `json:"user_id"`
|
||||
Creator bool `json:"creator"`
|
||||
}
|
||||
|
||||
type Admins struct {
|
||||
Admins []Admin `json:"admins"`
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Type string `json:"type"`
|
||||
Size int `json:"size"`
|
||||
Filename string `json:"filename"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Msg struct {
|
||||
MsgID string `json:"msgId"`
|
||||
OK bool `json:"ok"` // статус запроса
|
||||
}
|
||||
|
||||
type MsgLoadFile struct {
|
||||
FileID string `json:"fileId"`
|
||||
MsgID string `json:"msgId"`
|
||||
OK bool `json:"ok"` // статус запроса
|
||||
}
|
||||
|
||||
type User struct {
|
||||
UserID string `json:"userId"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
FileID string `json:"fileId"`
|
||||
}
|
||||
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventTypeDataMessage EventType = "newMessage"
|
||||
EventTypeEditedMessage EventType = "editedMessage"
|
||||
EventTypeDeletedMessage EventType = "deletedMessage"
|
||||
EventTypePinnedMessage EventType = "pinnedMessage"
|
||||
EventTypeUnpinnedMessage EventType = "unpinnedMessage"
|
||||
EventTypeNewChatMembers EventType = "newChatMembers"
|
||||
EventTypeLeftChatMembers EventType = "leftChatMembers"
|
||||
)
|
||||
|
||||
type EventInterface interface {
|
||||
GetEventID() int
|
||||
GetType() EventType
|
||||
}
|
||||
|
||||
type Events struct {
|
||||
Events []EventInterface `json:"events"`
|
||||
}
|
||||
|
||||
type RawEvents struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
EventID int `json:"eventId"`
|
||||
Type EventType `json:"type"`
|
||||
}
|
||||
|
||||
func (e Event) GetEventID() int {
|
||||
return e.EventID
|
||||
}
|
||||
|
||||
func (e Event) GetType() EventType {
|
||||
return e.Type
|
||||
}
|
||||
|
||||
type EventDataMessage struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
} `json:"chat"`
|
||||
From User `json:"from"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Text string `json:"text"`
|
||||
Parts []AttachmentInterface
|
||||
RawParts []json.RawMessage `json:"parts"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type EventEditedMessage struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
} `json:"chat"`
|
||||
From User `json:"from"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Text string `json:"text"`
|
||||
EditedTimestamp string `json:"editedTimestamp"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type EventDeletedMessage struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
} `json:"chat"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type EventPinnedMessage struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
} `json:"chat"`
|
||||
From User `json:"from"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Text string `json:"text"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type EventUnpinnedMessage struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
} `json:"chat"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type EventNewChatMembers struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
NewMembers []User `json:"newMembers"`
|
||||
AddedBy User `json:"addedBy"`
|
||||
} `json:"chat"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type EventLeftChatMembers struct {
|
||||
Event
|
||||
Payload struct {
|
||||
MsgID string `json:"msgId"`
|
||||
Chat struct {
|
||||
ChatID string `json:"chatId"`
|
||||
LeftMembers []User `json:"leftMembers"`
|
||||
RemovedBy User `json:"removedBy"`
|
||||
} `json:"chat"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type AttachmentType string
|
||||
|
||||
const (
|
||||
AttachmentTypeSticker AttachmentType = "sticker"
|
||||
AttachmentTypeMention AttachmentType = "mention"
|
||||
AttachmentTypeVoice AttachmentType = "voice"
|
||||
AttachmentTypeFile AttachmentType = "file"
|
||||
AttachmentTypeForward AttachmentType = "forward"
|
||||
AttachmentTypeReply AttachmentType = "reply"
|
||||
)
|
||||
|
||||
type AttachmentInterface interface {
|
||||
GetType() AttachmentType
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Type AttachmentType `json:"type"`
|
||||
}
|
||||
|
||||
func (a Attachment) GetType() AttachmentType {
|
||||
return a.Type
|
||||
}
|
||||
|
||||
type AttachmentSticker struct {
|
||||
Attachment
|
||||
Payload File `json:"payload"`
|
||||
}
|
||||
|
||||
type AttachmentMention struct {
|
||||
Attachment
|
||||
Payload User `json:"payload"`
|
||||
}
|
||||
|
||||
type AttachmentVoice struct {
|
||||
Attachment
|
||||
Payload File `json:"payload"`
|
||||
}
|
||||
|
||||
type AttachmentFile struct {
|
||||
Attachment
|
||||
Payload struct {
|
||||
FileID string `json:"fileId"`
|
||||
Type AttachmentFileType `json:"type"`
|
||||
Caption string `json:"caption"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type AttachmentFileType string
|
||||
|
||||
const (
|
||||
AttachmentFileTypeImage AttachmentFileType = "image"
|
||||
AttachmentFileTypeAudio AttachmentFileType = "audio"
|
||||
AttachmentFileTypeVideo AttachmentFileType = "video"
|
||||
)
|
||||
|
||||
type AttachmentForward struct {
|
||||
Attachment
|
||||
Payload struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type AttachmentReply struct {
|
||||
Attachment
|
||||
Payload struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
OK bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type OK struct {
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
type ChatAction string
|
||||
|
||||
const (
|
||||
ChatActionLooking ChatAction = "looking"
|
||||
ChatActionTyping ChatAction = "typing"
|
||||
)
|
24
self.go
Normal file
24
self.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type self struct {
|
||||
client *client
|
||||
}
|
||||
|
||||
func newSelf(client *client) *self {
|
||||
return &self{client: client}
|
||||
}
|
||||
|
||||
func (s *self) Get() (*Bot, error) {
|
||||
resp, err := s.client.request(http.MethodGet, "/self/get", url.Values{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(Bot)
|
||||
return result, json.NewDecoder(resp).Decode(result)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SendMessage with `message` text to `to` participant
|
||||
func (a *API) SendMessage(message Message) (*MessageResponse, error) {
|
||||
parse, _ := json.Marshal(message.Parse)
|
||||
v := url.Values{}
|
||||
v.Set("aimsid", a.token)
|
||||
v.Set("r", strconv.FormatInt(time.Now().Unix(), 10))
|
||||
v.Set("t", message.To)
|
||||
v.Set("message", message.Text)
|
||||
v.Set("mentions", strings.Join(message.Mentions, ","))
|
||||
if len(message.Parse) > 0 {
|
||||
v.Set("parse", string(parse))
|
||||
}
|
||||
b, err := a.send("/im/sendIM", v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Response{}
|
||||
if err := json.Unmarshal(b, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("failed to send message: %s", r.Response.StatusText)
|
||||
}
|
||||
return r.Response.Data, nil
|
||||
}
|
81
types.go
81
types.go
|
@ -1,81 +0,0 @@
|
|||
package icq
|
||||
|
||||
type Response struct {
|
||||
Response struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
StatusText string `json:"statusText"`
|
||||
RequestId string `json:"requestId"`
|
||||
Data *MessageResponse `json:"data"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
type ParseType string
|
||||
|
||||
const (
|
||||
ParseURL ParseType = "url"
|
||||
ParseFilesharing = "filesharing"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
To string
|
||||
Text string
|
||||
Mentions []string
|
||||
Parse []ParseType
|
||||
}
|
||||
|
||||
type MessageResponse struct {
|
||||
SubCode struct {
|
||||
Error int `json:"error"`
|
||||
} `json:"subCode"`
|
||||
MessageID string `json:"msgId"`
|
||||
HistoryMessageID int64 `json:"histMsgId"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type FileResponse struct {
|
||||
StaticUrl string `json:"static_url"`
|
||||
MimeType string `json:"mime"`
|
||||
SnapID string `json:"snapId"`
|
||||
TtlID string `json:"ttl_id"`
|
||||
IsPreviewable int `json:"is_previewable"`
|
||||
FileID string `json:"fileid"`
|
||||
FileSize int `json:"filesize"`
|
||||
FileName string `json:"filename"`
|
||||
ContentID string `json:"content_id"`
|
||||
}
|
||||
|
||||
type WebhookRequest struct {
|
||||
Token string `json:"aimsid"`
|
||||
Updates []Update `json:"update"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Update struct {
|
||||
Chat Chat `json:"chat"`
|
||||
Date int `json:"date"`
|
||||
From User `json:"from"`
|
||||
Text string `json:"text"`
|
||||
} `json:"update"`
|
||||
UpdateID int `json:"update_id"`
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
LanguageCode string `json:"language_code"`
|
||||
}
|
||||
|
||||
type Buddy struct {
|
||||
AimID string `json:"aimId"`
|
||||
DisplayID string `json:"displayId"`
|
||||
FriendlyName string `json:"friendly"`
|
||||
State string `json:"state"`
|
||||
UserType string `json:"userType"`
|
||||
UserAgreement []string `json:"userAgreement"`
|
||||
Nick string `json:"nick"`
|
||||
GlobalFlags int `json:"globalFlags"`
|
||||
BuddyIcon string `json:"buddyIcon"`
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// UploadFile to ICQ servers and returns URL to file
|
||||
func (a *API) UploadFile(fileName string, r io.Reader) (*FileResponse, error) {
|
||||
v := url.Values{}
|
||||
v.Set("aimsid", a.token)
|
||||
v.Set("filename", fileName)
|
||||
req, err := http.NewRequest(http.MethodPost, a.baseUrl+"/im/sendFile?"+v.Encode(), r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := a.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
file := struct {
|
||||
Data FileResponse `json:"data"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &file.Data, nil
|
||||
}
|
31
utils.go
31
utils.go
|
@ -1,31 +0,0 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command is sugar on top of IMEvent that represented standard ICQ bot commands
|
||||
type Command struct {
|
||||
From string
|
||||
Command string
|
||||
Arguments []string
|
||||
}
|
||||
|
||||
// ParseCommand from IMEvent
|
||||
// Command must starts from '.' or '/'. Arguments separated by space (' ')
|
||||
func ParseCommand(event *IMEvent) (*Command, bool) {
|
||||
message := event.Data.Message
|
||||
parts := strings.Split(message, " ")
|
||||
if len(parts) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if parts[0][0] != '.' && parts[0][0] != '/' {
|
||||
return nil, false
|
||||
}
|
||||
cmd := string(parts[0][1:])
|
||||
return &Command{
|
||||
From: event.Data.Source.AimID,
|
||||
Command: strings.ToLower(cmd),
|
||||
Arguments: parts[1:],
|
||||
}, true
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package icq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GetWebhookHandler returns http.HandleFunc that parses webhooks
|
||||
// Warning! Not fully functional at ICQ now!
|
||||
func (a *API) GetWebhookHandler(cu chan<- Update, e chan<- error) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if r.Method != http.MethodPost {
|
||||
e <- fmt.Errorf("incorrect method: %s", r.Method)
|
||||
return
|
||||
}
|
||||
wr := &WebhookRequest{}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
e <- err
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(b, wr); err != nil {
|
||||
e <- err
|
||||
return
|
||||
}
|
||||
for _, u := range wr.Updates {
|
||||
cu <- u
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue