wsapi.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /******************************************************************************
  2. * A Discord API for Golang.
  3. * See discord.go for more information.
  4. *
  5. * This file contains low level functions for interacting
  6. * with the Discord Websocket interface.
  7. */
  8. package discordgo
  9. import (
  10. "encoding/json"
  11. "fmt"
  12. "runtime"
  13. "time"
  14. "github.com/gorilla/websocket"
  15. )
  16. // Basic struct for all Websocket Event messages
  17. type Event struct {
  18. Type string `json:"t"`
  19. State int `json:"s"`
  20. Operation int `json:"o"`
  21. Direction int `json:"dir"`
  22. //Direction of command, 0-received, 1-sent -- thanks Xackery/discord
  23. RawData json.RawMessage `json:"d"`
  24. Session Session
  25. }
  26. // The Ready Event given after initial connection
  27. type Ready struct {
  28. Version int `json:"v"`
  29. SessionID string `json:"session_id"`
  30. HeartbeatInterval time.Duration `json:"heartbeat_interval"`
  31. User User `json:"user"`
  32. ReadState []ReadState
  33. PrivateChannels []PrivateChannel
  34. Guilds []Guild
  35. }
  36. // ReadState might need to move? Gives me the read status
  37. // of all my channels when first connecting. I think :)
  38. type ReadState struct {
  39. MentionCount int
  40. LastMessageID int `json:"last_message_id,string"`
  41. ID int `json:"id,string"`
  42. }
  43. type TypingStart struct {
  44. UserId int `json:"user_id,string"`
  45. ChannelId int `json:"channel_id,string"`
  46. Timestamp int `json:"timestamp"`
  47. }
  48. type PresenceUpdate struct {
  49. User User `json:"user"`
  50. Status string `json:"status"`
  51. Roles []Role `json:"roles"`
  52. GuildId int `json:"guild_id,string"`
  53. GameId int `json:"game_id"`
  54. }
  55. type MessageAck struct {
  56. MessageId int `json:"message_id,string"`
  57. ChannelId int `json:"channel_id,string"`
  58. }
  59. type MessageDelete struct {
  60. Id int `json:"id,string"`
  61. ChannelId int `json:"channel_id,string"`
  62. } // so much like MessageAck..
  63. type GuildIntegrationsUpdate struct {
  64. GuildId int `json:"guild_id,string"`
  65. }
  66. // Open a websocket connection to Discord
  67. func Open(s *Session) (err error) {
  68. // TODO: See if there's a use for the http response.
  69. // conn, response, err := websocket.DefaultDialer.Dial(session.Gateway, nil)
  70. s.wsConn, _, err = websocket.DefaultDialer.Dial(s.Gateway, nil)
  71. return
  72. }
  73. // maybe this is SendOrigin? not sure the right name here
  74. // also bson.M vs string interface map? Read about
  75. // how to send JSON the right way.
  76. func Handshake(s *Session) (err error) {
  77. err = s.wsConn.WriteJSON(map[string]interface{}{
  78. "op": 2,
  79. "d": map[string]interface{}{
  80. "v": 3,
  81. "token": s.Token,
  82. "properties": map[string]string{
  83. "$os": runtime.GOOS,
  84. "$browser": "Discordgo",
  85. "$device": "Discordgo",
  86. "$referer": "",
  87. "$referring_domain": "",
  88. },
  89. },
  90. })
  91. return
  92. }
  93. func UpdateStatus(s *Session, idleSince, gameId string) (err error) {
  94. err = s.wsConn.WriteJSON(map[string]interface{}{
  95. "op": 2,
  96. "d": map[string]interface{}{
  97. "idle_since": idleSince,
  98. "game_id": gameId,
  99. },
  100. })
  101. return
  102. }
  103. // TODO: need a channel or something to communicate
  104. // to this so I can tell it to stop listening
  105. func Listen(s *Session) (err error) {
  106. if s.wsConn == nil {
  107. fmt.Println("No websocket connection exists.")
  108. return // need to return an error.
  109. }
  110. for { // s.wsConn != nil { // need a cleaner way to exit? this doesn't acheive anything.
  111. messageType, message, err := s.wsConn.ReadMessage()
  112. if err != nil {
  113. fmt.Println(err)
  114. break
  115. }
  116. go event(s, messageType, message)
  117. }
  118. return
  119. }
  120. // Not sure how needed this is and where it would be best to call it.
  121. // somewhere.
  122. func Close(s *Session) {
  123. s.wsConn.Close()
  124. }
  125. // Front line handler for all Websocket Events. Determines the
  126. // event type and passes the message along to the next handler.
  127. func event(s *Session, messageType int, message []byte) (err error) {
  128. if s.Debug {
  129. printJSON(message)
  130. }
  131. var e Event
  132. if err := json.Unmarshal(message, &e); err != nil {
  133. return err
  134. }
  135. switch e.Type {
  136. case "READY":
  137. if s.OnReady != nil {
  138. var st Ready
  139. if err := json.Unmarshal(e.RawData, &st); err != nil {
  140. printJSON(e.RawData) // TODO: Better error logging
  141. return err
  142. }
  143. s.OnReady(s, st)
  144. return
  145. }
  146. case "VOICE_STATE_UPDATE":
  147. if s.OnVoiceStateUpdate != nil {
  148. var st VoiceState
  149. if err := json.Unmarshal(e.RawData, &st); err != nil {
  150. printJSON(e.RawData) // TODO: Better error logging
  151. return err
  152. }
  153. s.OnVoiceStateUpdate(s, st)
  154. return
  155. }
  156. case "PRESENCE_UPDATE":
  157. if s.OnPresenceUpdate != nil {
  158. var st PresenceUpdate
  159. if err := json.Unmarshal(e.RawData, &st); err != nil {
  160. printJSON(e.RawData) // TODO: Better error logging
  161. return err
  162. }
  163. s.OnPresenceUpdate(s, st)
  164. return
  165. }
  166. case "TYPING_START":
  167. if s.OnTypingStart != nil {
  168. var st TypingStart
  169. if err := json.Unmarshal(e.RawData, &st); err != nil {
  170. printJSON(e.RawData) // TODO: Better error logging
  171. return err
  172. }
  173. s.OnTypingStart(s, st)
  174. return
  175. }
  176. case "MESSAGE_CREATE":
  177. if s.OnMessageCreate != nil {
  178. var st Message
  179. if err := json.Unmarshal(e.RawData, &st); err != nil {
  180. printJSON(e.RawData) // TODO: Better error logging
  181. return err
  182. }
  183. s.OnMessageCreate(s, st)
  184. return
  185. }
  186. case "MESSAGE_UPDATE":
  187. if s.OnMessageUpdate != nil {
  188. var st Message
  189. if err := json.Unmarshal(e.RawData, &st); err != nil {
  190. printJSON(e.RawData) // TODO: Better error logging
  191. return err
  192. }
  193. s.OnMessageUpdate(s, st)
  194. return
  195. }
  196. case "MESSAGE_DELETE":
  197. if s.OnMessageDelete != nil {
  198. var st MessageDelete
  199. if err := json.Unmarshal(e.RawData, &st); err != nil {
  200. printJSON(e.RawData) // TODO: Better error logging
  201. return err
  202. }
  203. s.OnMessageDelete(s, st)
  204. }
  205. case "MESSAGE_ACK":
  206. if s.OnMessageAck != nil {
  207. var st MessageAck
  208. if err := json.Unmarshal(e.RawData, &st); err != nil {
  209. printJSON(e.RawData) // TODO: Better error logging
  210. return err
  211. }
  212. s.OnMessageAck(s, st)
  213. return
  214. }
  215. case "CHANNEL_CREATE":
  216. if s.OnChannelCreate != nil {
  217. var st Channel
  218. if err := json.Unmarshal(e.RawData, &st); err != nil {
  219. printJSON(e.RawData) // TODO: Better error logginEventg
  220. return err
  221. }
  222. s.OnChannelCreate(s, st)
  223. return
  224. }
  225. case "CHANNEL_UPDATE":
  226. if s.OnChannelUpdate != nil {
  227. var st Channel
  228. if err := json.Unmarshal(e.RawData, &st); err != nil {
  229. printJSON(e.RawData) // TODO: Better error logginEventg
  230. return err
  231. }
  232. s.OnChannelUpdate(s, st)
  233. return
  234. }
  235. case "CHANNEL_DELETE":
  236. if s.OnChannelDelete != nil {
  237. var st Channel
  238. if err := json.Unmarshal(e.RawData, &st); err != nil {
  239. printJSON(e.RawData) // TODO: Better error logginEventg
  240. return err
  241. }
  242. s.OnChannelDelete(s, st)
  243. return
  244. }
  245. case "GUILD_CREATE":
  246. if s.OnGuildCreate != nil {
  247. var st Guild
  248. if err := json.Unmarshal(e.RawData, &st); err != nil {
  249. printJSON(e.RawData) // TODO: Better error logginEventg
  250. return err
  251. }
  252. s.OnGuildCreate(s, st)
  253. return
  254. }
  255. case "GUILD_DELETE":
  256. if s.OnGuildDelete != nil {
  257. var st Guild
  258. if err := json.Unmarshal(e.RawData, &st); err != nil {
  259. printJSON(e.RawData) // TODO: Better error logginEventg
  260. return err
  261. }
  262. s.OnGuildDelete(s, st)
  263. return
  264. }
  265. case "GUILD_MEMBER_ADD":
  266. if s.OnGuildMemberAdd != nil {
  267. var st Member
  268. if err := json.Unmarshal(e.RawData, &st); err != nil {
  269. printJSON(e.RawData) // TODO: Better error logginEventg
  270. return err
  271. }
  272. s.OnGuildMemberAdd(s, st)
  273. return
  274. }
  275. case "GUILD_MEMBER_REMOVE":
  276. if s.OnGuildMemberRemove != nil {
  277. var st Member
  278. if err := json.Unmarshal(e.RawData, &st); err != nil {
  279. printJSON(e.RawData) // TODO: Better error logginEventg
  280. return err
  281. }
  282. s.OnGuildMemberRemove(s, st)
  283. return
  284. }
  285. case "GUILD_MEMBER_UPDATE":
  286. if s.OnGuildMemberUpdate != nil {
  287. var st Member
  288. if err := json.Unmarshal(e.RawData, &st); err != nil {
  289. printJSON(e.RawData) // TODO: Better error logginEventg
  290. return err
  291. }
  292. s.OnGuildMemberUpdate(s, st)
  293. return
  294. }
  295. /*
  296. case "GUILD_ROLE_CREATE":
  297. if s.OnGuildRoleCreate != nil {
  298. }
  299. case "GUILD_ROLE_DELETE":
  300. if s.OnGuildRoleDelete != nil {
  301. }
  302. */
  303. case "GUILD_INTEGRATIONS_UPDATE":
  304. if s.OnGuildIntegrationsUpdate != nil {
  305. var st GuildIntegrationsUpdate
  306. if err := json.Unmarshal(e.RawData, &st); err != nil {
  307. printJSON(e.RawData) // TODO: Better error logginEventg
  308. return err
  309. }
  310. s.OnGuildIntegrationsUpdate(s, st)
  311. return
  312. }
  313. default:
  314. fmt.Println("UNKNOWN EVENT: ", e.Type)
  315. // learn the log package
  316. // log.print type and JSON data
  317. }
  318. // if still here, send to generic OnEvent
  319. if s.OnEvent != nil {
  320. s.OnEvent(s, e)
  321. }
  322. return
  323. }
  324. // This heartbeat is sent to keep the Websocket conenction
  325. // to Discord alive. If not sent, Discord will close the
  326. // connection.
  327. func Heartbeat(s *Session, i time.Duration) {
  328. if s.wsConn == nil {
  329. fmt.Println("No websocket connection exists.")
  330. return // need to return an error.
  331. }
  332. ticker := time.NewTicker(i * time.Millisecond)
  333. for range ticker.C {
  334. timestamp := int(time.Now().Unix())
  335. err := s.wsConn.WriteJSON(map[string]int{
  336. "op": 1,
  337. "d": timestamp,
  338. })
  339. if err != nil {
  340. return // log error?
  341. }
  342. }
  343. }