Browse Source

Inital addition of Websocket handling. Lots of moving things around.

Bruce Marriner 9 years ago
parent
commit
adac11495a
4 changed files with 263 additions and 0 deletions
  1. 0 0
      restapi.go
  2. 17 0
      users.go
  3. 16 0
      util.go
  4. 230 0
      wsapi.go

client.go → restapi.go


+ 17 - 0
users.go

@@ -0,0 +1,17 @@
+package discordgo
+
+type User struct {
+	Id            int    `json:"id,string"`
+	Email         string `json:"email"`
+	Username      string `json:"username"`
+	Avatar        string `json:"Avatar"`
+	Verified      bool   `json:"verified"`
+	Discriminator string `json:"discriminator"`
+}
+
+type PrivateChannel struct {
+	Id            int  `json:"id,string"`
+	IsPrivate     bool `json:"is_private"`
+	LastMessageId int  `json:"last_message_id,string"`
+	Recipient     User `json:"recipient"`
+}

+ 16 - 0
util.go

@@ -0,0 +1,16 @@
+package discordgo
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+)
+
+func printJSON(body []byte) {
+	var prettyJSON bytes.Buffer
+	error := json.Indent(&prettyJSON, body, "", "\t")
+	if error != nil {
+		fmt.Print("JSON parse error: ", error)
+	}
+	fmt.Println("RESPONSE ::\n" + string(prettyJSON.Bytes()))
+}

+ 230 - 0
wsapi.go

@@ -0,0 +1,230 @@
+/******************************************************************************
+ * Discordgo by Bruce Marriner <bruce@sqls.net>
+ * A Discord API for Golang.
+ * See discord.go for more information.
+ *
+ * This file contains functions low level functions for interacting
+ * with the Discord Websocket interface.
+ */
+
+package discordgo
+
+import (
+	"encoding/json"
+	"fmt"
+	"time"
+
+	"github.com/gorilla/websocket"
+)
+
+// Basic struct for all Websocket Event messages
+type Event struct {
+	Type      string `json:"t"`
+	State     int    `json:"s"`
+	Operation int    `json:"o"`
+	Direction int    `json:"dir"`
+	//Direction of command, 0-received, 1-sent -- thanks Xackery/discord
+
+	RawData json.RawMessage `json:"d"`
+}
+
+// The Ready Event given after initial connection
+type Ready struct {
+	Version           int           `json:"v"`
+	SessionID         string        `json:"session_id"`
+	HeartbeatInterval time.Duration `json:"heartbeat_interval"`
+	User              User          `json:"user"`
+	ReadState         []ReadState
+	PrivateChannels   []PrivateChannel
+	Servers           []Server
+}
+
+// ReadState might need to move? Gives me the read status
+// of all my channels when first connecting. I think :)
+type ReadState struct {
+	MentionCount  int
+	LastMessageID int `json:"last_message_id,string"`
+	ID            int `json:"id,string"`
+}
+
+// Returns the a websocket Gateway address
+// session : An active session connection to Discord
+// put this here instead of restapi because it is used soley
+// for the websocket stuff - but maybe I should move it back
+// because it's part of the restapi...
+func Gateway(session *Session) (gateway string, err error) {
+
+	response, err := Request(session, "GET", fmt.Sprintf("%s/gateway", discordApi), ``)
+
+	var temp map[string]interface{}
+	err = json.Unmarshal(response, &temp)
+	gateway = temp["url"].(string)
+	return
+}
+
+// Open a websocket connection to Discord
+func Open(session *Session) (conn *websocket.Conn, err error) {
+
+	// TODO: See if there's a use for the http response.
+	//conn, response, err := websocket.DefaultDialer.Dial(session.Gateway, nil)
+	conn, _, err = websocket.DefaultDialer.Dial(session.Gateway, nil)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// maybe this is SendOrigin? not sure the right name here
+// also bson.M vs string interface map?  Read about
+// how to send JSON the right way.
+func Handshake(conn *websocket.Conn, token string) (err error) {
+
+	err = conn.WriteJSON(map[string]interface{}{
+		"op": 2,
+		"d": map[string]interface{}{
+			"v":     3,
+			"token": token,
+			"properties": map[string]string{
+				"$os":               "linux", // get from os package
+				"$browser":          "Discordgo",
+				"$device":           "Discordgo",
+				"$referer":          "",
+				"$referring_domain": "",
+			},
+		},
+	})
+
+	return
+}
+
+func UpdateStatus(conn *websocket.Conn, idleSince, gameId string) (err error) {
+
+	err = conn.WriteJSON(map[string]interface{}{
+		"op": 2,
+		"d": map[string]interface{}{
+			"idle_since": idleSince,
+			"game_id":    gameId,
+		},
+	})
+
+	return
+}
+
+// TODO: need a channel or something to communicate
+// to this so I can tell it to stop listening
+func Listen(conn *websocket.Conn) (err error) {
+	for {
+		messageType, message, err := conn.ReadMessage()
+		if err != nil {
+			fmt.Println(err)
+			break
+		}
+		go event(conn, messageType, message)
+	}
+
+	return
+}
+
+// Not sure how needed this is and where it would be best to call it.
+// somewhere.
+func Close(conn *websocket.Conn) {
+	conn.Close()
+}
+
+// Front line handler for all Websocket Events.  Determines the
+// event type and passes the message along to the next handler.
+func event(conn *websocket.Conn, messageType int, message []byte) {
+
+	//printJSON(message) // TODO: wrap in debug if statement
+
+	var event Event
+	err := json.Unmarshal(message, &event)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	switch event.Type {
+
+	case "READY":
+		ready(conn, &event)
+	case "TYPING_START":
+		// do stuff
+	case "MESSAGE_CREATE":
+		// do stuff
+	case "MESSAGE_ACK":
+		// do stuff
+	case "MESSAGE_UPDATE":
+		// do stuff
+	case "MESSAGE_DELETE":
+		// do stuff
+	case "PRESENCE_UPDATE":
+		// do stuff
+	case "CHANNEL_CREATE":
+		// do stuff
+	case "CHANNEL_UPDATE":
+		// do stuff
+	case "CHANNEL_DELETE":
+		// do stuff
+	case "GUILD_CREATE":
+		// do stuff
+	case "GUILD_DELETE":
+		// do stuff
+	case "GUILD_MEMBER_ADD":
+		// do stuff
+	case "GUILD_MEMBER_REMOVE": // which is it.
+		// do stuff
+	case "GUILD_MEMBER_DELETE":
+		// do stuff
+	case "GUILD_MEMBER_UPDATE":
+		// do stuff
+	case "GUILD_ROLE_CREATE":
+		// do stuff
+	case "GUILD_ROLE_DELETE":
+		// do stuff
+	case "GUILD_INTEGRATIONS_UPDATE":
+		// do stuff
+
+	default:
+		fmt.Println("UNKNOWN EVENT: ", event.Type)
+		// learn the log package
+		// log.print type and JSON data
+	}
+
+}
+
+// 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
+	}
+
+	fmt.Println(ready)
+
+	go heartbeat(conn, ready.HeartbeatInterval)
+
+	// Start KeepAlive based on .
+
+}
+
+// This heartbeat is sent to keep the Websocket conenction
+// to Discord alive. If not sent, Discord will close the
+// connection.
+func heartbeat(conn *websocket.Conn, interval time.Duration) {
+
+	ticker := time.NewTicker(interval * time.Millisecond)
+	for range ticker.C {
+		timestamp := int(time.Now().Unix())
+		conn.WriteJSON(map[string]int{
+			"op": 1,
+			"d":  timestamp,
+		})
+	}
+}