Skip to content
This repository has been archived by the owner on Aug 29, 2019. It is now read-only.

Commit

Permalink
+ Long polling to get events
Browse files Browse the repository at this point in the history
+ ParseCommand sugar
  • Loading branch information
Alexander Kiryukhin committed Dec 22, 2018
1 parent 98399ba commit 11849c8
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 169 deletions.
82 changes: 38 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ Methods:

* SendMessage
* UploadFile
* FetchEvents

Webhooks to get updates
Webhooks workds but not recommends

## Example

Expand All @@ -25,67 +26,60 @@ import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"

"gopkg.in/icq.v1"
"gopkg.in/icq.v2"
)

func main() {
// New API object
b := icq.NewAPI(os.Getenv("ICQ_TOKEN"))

// Send message
r, err := b.SendMessage(icq.Message{To: "429950", Text: "Hello, world!"})
if err != nil {
log.Fatalln(err)
}
log.Println(r.State)

// Send file
f, err := os.Open("./example/icq.png")
defer f.Close()
if err != nil {
log.Fatalln(err)
}
file, err := b.UploadFile("icq.png", f)
if err != nil {
log.Fatalln(err)
}
b.SendMessage(icq.Message{To: "429950", Text: file.StaticUrl})
ctx, cancel := context.WithCancel(context.Background())

// Webhook usage
updates := make(chan icq.Update)
errors := make(chan error)
ch := make(chan interface{}) // Events channel
osSignal := make(chan os.Signal, 1)

m := http.NewServeMux()
m.HandleFunc("/webhook", b.GetWebhookHandler(updates, errors)) // Webhook sets here

h := &http.Server{Addr: ":8080", Handler: m}
go func() {
log.Fatalln(h.ListenAndServe())
}()
signal.Notify(osSignal, os.Interrupt)
signal.Notify(osSignal, os.Kill)

go b.FetchEvents(ctx, ch) // Events fetch loop

for {
select {
case u := <-updates:
log.Println("Incomming message", u)
case e := <-ch:
handleEvent(b, e)
case <-osSignal:
cancel()
break
}
}
}

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: u.Update.Chat.ID,
Text: fmt.Sprintf("You sent me: %s", u.Update.Text),
To: message.Data.Source.AimID,
Text: "Message process fail",
})
// ... process ICQ updates ...
case err := <-errors:
log.Fatalln(err)
case sig := <-osSignal:
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
h.Shutdown(ctx)
log.Fatalln("OS signal:", sig.String())
}
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
}
```
85 changes: 4 additions & 81 deletions api.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package icq

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)

// HTTP Client interface
Expand All @@ -19,9 +15,10 @@ type Doer interface {

// API
type API struct {
token string
baseUrl string
client Doer
token string
baseUrl string
client Doer
fetchBase string
}

// NewAPI constructor of API object
Expand All @@ -33,80 +30,6 @@ func NewAPI(token string) *API {
}
}

// 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
}

// 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
}

// GetWebhookHandler returns http.HandleFunc that parses webhooks
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
}
}
}

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 {
Expand Down
60 changes: 60 additions & 0 deletions events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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"
)

type TypingEvent struct {
CommonEvent
Data struct {
AimID string `json:"aimId"`
TypingStatus TypingStatus `json:"typingStatus"`
} `json:"eventData"`
}

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"`
}
80 changes: 36 additions & 44 deletions example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,59 @@ package main
import (
"context"
"fmt"
"github.com/go-icq/icq"
"log"
"net/http"
"os"
"os/signal"
"time"

"github.com/go-icq/icq"
)

func main() {
// New API object
b := icq.NewAPI(os.Getenv("ICQ_TOKEN"))

// Send message
r, err := b.SendMessage(icq.Message{To: "429950", Text: "Hello, world!"})
if err != nil {
log.Fatalln(err)
}
log.Println(r.State)
ctx, cancel := context.WithCancel(context.Background())

// Send file
f, err := os.Open("./example/icq.png")
defer f.Close()
if err != nil {
log.Fatalln(err)
}
file, err := b.UploadFile("icq.png", f)
if err != nil {
log.Fatalln(err)
}
b.SendMessage(icq.Message{To: "429950", Text: file.StaticUrl})

// Webhook usage
updates := make(chan icq.Update)
errors := make(chan error)
ch := make(chan interface{}) // Events channel
osSignal := make(chan os.Signal, 1)

m := http.NewServeMux()
m.HandleFunc("/webhook", b.GetWebhookHandler(updates, errors)) // Webhook sets here

h := &http.Server{Addr: ":8080", Handler: m}
go func() {
log.Fatalln(h.ListenAndServe())
}()
signal.Notify(osSignal, os.Interrupt)
signal.Notify(osSignal, os.Kill)

go b.FetchEvents(ctx, ch) // Events fetch loop

for {
select {
case u := <-updates:
log.Printf("Incomming message %#v", u)
case e := <-ch:
handleEvent(b, e)
case <-osSignal:
cancel()
break
}
}
}

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: u.Update.Chat.ID,
Text: fmt.Sprintf("You sent me: %s", u.Update.Text),
To: message.Data.Source.AimID,
Text: "Message process fail",
})
// ... process ICQ updates ...
case err := <-errors:
log.Fatalln(err)
case sig := <-osSignal:
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
h.Shutdown(ctx)
log.Fatalln("OS signal:", sig.String())
}
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
}
Binary file removed example/icq.png
Binary file not shown.
Loading

0 comments on commit 11849c8

Please sign in to comment.