Inital implementation for callback funcs handling Websocket events

This commit is contained in:
Bruce Marriner 2015-11-12 09:59:37 -06:00
parent a5d3e9378e
commit e8b77083df
2 changed files with 132 additions and 76 deletions

View file

@ -7,14 +7,42 @@ package discordgo
import "github.com/gorilla/websocket" import "github.com/gorilla/websocket"
// A Session represents a connection to the Discord REST API. // A Session represents a connection to the Discord REST API.
// Token : The authentication token returned from Discord // token : The authentication token returned from Discord
// Debug : If set to ture debug logging will be displayed. // Debug : If set to ture debug logging will be displayed.
type Session struct { type Session struct {
Token string // Authentication token for this session Token string // Authentication token for this session
Gateway string // Websocket Gateway for this session Gateway string // Websocket Gateway for this session
Debug bool // Debug for printing JSON request/responses Debug bool // Debug for printing JSON request/responses
Cache int // number in X to cache some responses Cache int // number in X to cache some responses
Websocket *websocket.Conn // TODO: use this
// Settable Callback functions for Websocket Events
OnEvent func(*Session, Event) // should Event be *Event?
OnReady func(*Session, Ready)
OnMessageCreate func(*Session, Message)
OnTypingStart func(*Session, Event)
OnMessageAck func(*Session, Event)
OnMessageUpdate func(*Session, Event)
OnMessageDelete func(*Session, Event)
OnPresenceUpdate func(*Session, Event)
OnChannelCreate func(*Session, Event)
OnChannelUpdate func(*Session, Event)
OnChannelDelete func(*Session, Event)
OnGuildCreate func(*Session, Event)
OnGuildDelete func(*Session, Event)
OnGuildMemberAdd func(*Session, Event)
OnGuildMemberRemove func(*Session, Event)
OnGuildMemberDelete func(*Session, Event) // which is it?
OnGuildMemberUpdate func(*Session, Event)
OnGuildRoleCreate func(*Session, Event)
OnGuildRoleDelete func(*Session, Event)
OnGuildIntegrationsUpdate func(*Session, Event)
// OnMessageCreate func(Session, Event, Message)
// ^^ Any value to passing session, event, message?
// probably just the Message is all one would need.
// but having the sessin could be handy?
wsConn *websocket.Conn
//TODO, add bools for like. //TODO, add bools for like.
// are we connnected to websocket? // are we connnected to websocket?
// have we authenticated to login? // have we authenticated to login?

164
wsapi.go
View file

@ -11,6 +11,7 @@ package discordgo
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"runtime"
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -25,6 +26,7 @@ type Event struct {
//Direction of command, 0-received, 1-sent -- thanks Xackery/discord //Direction of command, 0-received, 1-sent -- thanks Xackery/discord
RawData json.RawMessage `json:"d"` RawData json.RawMessage `json:"d"`
Session Session
} }
// The Ready Event given after initial connection // The Ready Event given after initial connection
@ -47,30 +49,26 @@ type ReadState struct {
} }
// Open a websocket connection to Discord // Open a websocket connection to Discord
func Open(session *Session) (conn *websocket.Conn, err error) { func Open(s *Session) (err error) {
// TODO: See if there's a use for the http response. // TODO: See if there's a use for the http response.
// conn, response, err := websocket.DefaultDialer.Dial(session.Gateway, nil) // conn, response, err := websocket.DefaultDialer.Dial(session.Gateway, nil)
conn, _, err = websocket.DefaultDialer.Dial(session.Gateway, nil) s.wsConn, _, err = websocket.DefaultDialer.Dial(s.Gateway, nil)
if err != nil {
return
}
return return
} }
// maybe this is SendOrigin? not sure the right name here // maybe this is SendOrigin? not sure the right name here
// also bson.M vs string interface map? Read about // also bson.M vs string interface map? Read about
// how to send JSON the right way. // how to send JSON the right way.
func Handshake(conn *websocket.Conn, token string) (err error) { func Handshake(s *Session) (err error) {
err = conn.WriteJSON(map[string]interface{}{ err = s.wsConn.WriteJSON(map[string]interface{}{
"op": 2, "op": 2,
"d": map[string]interface{}{ "d": map[string]interface{}{
"v": 3, "v": 3,
"token": token, "token": s.Token,
"properties": map[string]string{ "properties": map[string]string{
"$os": "linux", // get from os package "$os": runtime.GOOS,
"$browser": "Discordgo", "$browser": "Discordgo",
"$device": "Discordgo", "$device": "Discordgo",
"$referer": "", "$referer": "",
@ -82,9 +80,9 @@ func Handshake(conn *websocket.Conn, token string) (err error) {
return return
} }
func UpdateStatus(conn *websocket.Conn, idleSince, gameId string) (err error) { func UpdateStatus(s *Session, idleSince, gameId string) (err error) {
err = conn.WriteJSON(map[string]interface{}{ err = s.wsConn.WriteJSON(map[string]interface{}{
"op": 2, "op": 2,
"d": map[string]interface{}{ "d": map[string]interface{}{
"idle_since": idleSince, "idle_since": idleSince,
@ -97,14 +95,20 @@ func UpdateStatus(conn *websocket.Conn, idleSince, gameId string) (err error) {
// TODO: need a channel or something to communicate // TODO: need a channel or something to communicate
// to this so I can tell it to stop listening // to this so I can tell it to stop listening
func Listen(conn *websocket.Conn) (err error) { func Listen(s *Session) (err error) {
for {
messageType, message, err := conn.ReadMessage() if s.wsConn == nil {
fmt.Println("No websocket connection exists.")
return // need to return an error.
}
for { // s.wsConn != nil { // need a cleaner way to exit? this doesn't acheive anything.
messageType, message, err := s.wsConn.ReadMessage()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
break break
} }
go event(conn, messageType, message) go event(s, messageType, message)
} }
return return
@ -112,103 +116,127 @@ func Listen(conn *websocket.Conn) (err error) {
// Not sure how needed this is and where it would be best to call it. // Not sure how needed this is and where it would be best to call it.
// somewhere. // somewhere.
func Close(conn *websocket.Conn) { func Close(s *Session) {
conn.Close() s.wsConn.Close()
} }
// Front line handler for all Websocket Events. Determines the // Front line handler for all Websocket Events. Determines the
// event type and passes the message along to the next handler. // event type and passes the message along to the next handler.
func event(conn *websocket.Conn, messageType int, message []byte) { func event(s *Session, messageType int, message []byte) (err error) {
//printJSON(message) // TODO: wrap in debug if statement if s.Debug {
printJSON(message)
var event Event
err := json.Unmarshal(message, &event)
if err != nil {
fmt.Println(err)
return
} }
switch event.Type { var e Event
if err := json.Unmarshal(message, &e); err != nil {
return err
}
switch e.Type {
case "READY": case "READY":
ready(conn, &event) if s.OnReady != nil {
var st Ready
if err := json.Unmarshal(e.RawData, &st); err != nil {
return err
}
s.OnReady(s, st)
return
}
case "TYPING_START": case "TYPING_START":
// do stuff if s.OnTypingStart != nil {
}
case "MESSAGE_CREATE": case "MESSAGE_CREATE":
// do stuff if s.OnMessageCreate != nil {
var st Message
if err := json.Unmarshal(e.RawData, &st); err != nil {
return err
}
s.OnMessageCreate(s, st)
return
}
case "MESSAGE_ACK": case "MESSAGE_ACK":
// do stuff if s.OnMessageAck != nil {
}
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
// do stuff if s.OnMessageUpdate != nil {
}
case "MESSAGE_DELETE": case "MESSAGE_DELETE":
// do stuff if s.OnMessageDelete != nil {
}
case "PRESENCE_UPDATE": case "PRESENCE_UPDATE":
// do stuff if s.OnPresenceUpdate != nil {
}
case "CHANNEL_CREATE": case "CHANNEL_CREATE":
// do stuff if s.OnChannelCreate != nil {
}
case "CHANNEL_UPDATE": case "CHANNEL_UPDATE":
// do stuff if s.OnChannelUpdate != nil {
}
case "CHANNEL_DELETE": case "CHANNEL_DELETE":
// do stuff if s.OnChannelDelete != nil {
}
case "GUILD_CREATE": case "GUILD_CREATE":
// do stuff if s.OnGuildCreate != nil {
}
case "GUILD_DELETE": case "GUILD_DELETE":
// do stuff if s.OnGuildDelete != nil {
}
case "GUILD_MEMBER_ADD": case "GUILD_MEMBER_ADD":
// do stuff if s.OnGuildMemberAdd != nil {
}
case "GUILD_MEMBER_REMOVE": // which is it. case "GUILD_MEMBER_REMOVE": // which is it.
// do stuff if s.OnGuildMemberRemove != nil {
}
case "GUILD_MEMBER_DELETE": case "GUILD_MEMBER_DELETE":
// do stuff if s.OnGuildMemberDelete != nil {
}
case "GUILD_MEMBER_UPDATE": case "GUILD_MEMBER_UPDATE":
// do stuff if s.OnGuildMemberUpdate != nil {
}
case "GUILD_ROLE_CREATE": case "GUILD_ROLE_CREATE":
// do stuff if s.OnGuildRoleCreate != nil {
}
case "GUILD_ROLE_DELETE": case "GUILD_ROLE_DELETE":
// do stuff if s.OnGuildRoleDelete != nil {
}
case "GUILD_INTEGRATIONS_UPDATE": case "GUILD_INTEGRATIONS_UPDATE":
// do stuff if s.OnGuildIntegrationsUpdate != nil {
}
default: default:
fmt.Println("UNKNOWN EVENT: ", event.Type) fmt.Println("UNKNOWN EVENT: ", e.Type)
// learn the log package // learn the log package
// log.print type and JSON data // log.print type and JSON data
} }
// if still here, send to generic OnEvent
if s.OnEvent != nil {
s.OnEvent(s, e)
} }
// handles the READY Websocket Event from Discord
// this is the motherload of detail provided at
// initial connection to the Websocket.
func ready(conn *websocket.Conn, event *Event) {
var ready Ready
err := json.Unmarshal(event.RawData, &ready)
if err != nil {
fmt.Println(err)
return return
} }
fmt.Println(ready)
go heartbeat(conn, ready.HeartbeatInterval)
// Start KeepAlive based on .
}
// This heartbeat is sent to keep the Websocket conenction // This heartbeat is sent to keep the Websocket conenction
// to Discord alive. If not sent, Discord will close the // to Discord alive. If not sent, Discord will close the
// connection. // connection.
func heartbeat(conn *websocket.Conn, interval time.Duration) { func Heartbeat(s *Session, i time.Duration) {
ticker := time.NewTicker(interval * time.Millisecond) if s.wsConn == nil {
fmt.Println("No websocket connection exists.")
return // need to return an error.
}
ticker := time.NewTicker(i * time.Millisecond)
for range ticker.C { for range ticker.C {
timestamp := int(time.Now().Unix()) timestamp := int(time.Now().Unix())
conn.WriteJSON(map[string]int{ err := s.wsConn.WriteJSON(map[string]int{
"op": 1, "op": 1,
"d": timestamp, "d": timestamp,
}) })
if err != nil {
return // log error?
}
} }
} }