wsapi.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  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. // An Event provides a basic initial struct for all websocket event.
  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. RawData json.RawMessage `json:"d"`
  23. }
  24. // A Ready stores all data for the websocket READY event.
  25. type Ready struct {
  26. Version int `json:"v"`
  27. SessionID string `json:"session_id"`
  28. HeartbeatInterval time.Duration `json:"heartbeat_interval"`
  29. User User `json:"user"`
  30. ReadState []ReadState
  31. PrivateChannels []PrivateChannel
  32. Guilds []Guild
  33. }
  34. // ReadState might need to move? Gives me the read status
  35. // of all my channels when first connecting. I think :)
  36. // A ReadState stores data on the read state of channels.
  37. type ReadState struct {
  38. MentionCount int
  39. LastMessageID string `json:"last_message_id"`
  40. ID string `json:"id"`
  41. }
  42. // A TypingStart stores data for the typing start websocket event.
  43. type TypingStart struct {
  44. UserID string `json:"user_id"`
  45. ChannelID string `json:"channel_id"`
  46. Timestamp int `json:"timestamp"`
  47. }
  48. // A PresenceUpdate stores data for the pressence update websocket event.
  49. type PresenceUpdate struct {
  50. User User `json:"user"`
  51. Status string `json:"status"`
  52. Roles []string `json:"roles"`
  53. GuildID string `json:"guild_id"`
  54. GameID int `json:"game_id"`
  55. }
  56. // A MessageAck stores data for the message ack websocket event.
  57. type MessageAck struct {
  58. MessageID string `json:"message_id"`
  59. ChannelID string `json:"channel_id"`
  60. }
  61. // A MessageDelete stores data for the message delete websocket event.
  62. type MessageDelete struct {
  63. ID string `json:"id"`
  64. ChannelID string `json:"channel_id"`
  65. } // so much like MessageAck..
  66. // A GuildIntegrationsUpdate stores data for the guild integrations update
  67. // websocket event.
  68. type GuildIntegrationsUpdate struct {
  69. GuildID string `json:"guild_id"`
  70. }
  71. // A GuildRole stores data for guild role websocket events.
  72. type GuildRole struct {
  73. Role Role `json:"role"`
  74. GuildID string `json:"guild_id"`
  75. }
  76. // A GuildRoleDelete stores data for the guild role delete websocket event.
  77. type GuildRoleDelete struct {
  78. RoleID string `json:"role_id"`
  79. GuildID string `json:"guild_id"`
  80. }
  81. // Open opens a websocket connection to Discord.
  82. func (s *Session) Open() (err error) {
  83. // Get the gateway to use for the Websocket connection
  84. g, err := s.Gateway()
  85. // TODO: See if there's a use for the http response.
  86. // conn, response, err := websocket.DefaultDialer.Dial(session.Gateway, nil)
  87. s.wsConn, _, err = websocket.DefaultDialer.Dial(g, nil)
  88. return
  89. }
  90. // maybe this is SendOrigin? not sure the right name here
  91. // also bson.M vs string interface map? Read about
  92. // how to send JSON the right way.
  93. // Handshake sends the client data to Discord during websocket initial connection.
  94. func (s *Session) Handshake() (err error) {
  95. err = s.wsConn.WriteJSON(map[string]interface{}{
  96. "op": 2,
  97. "d": map[string]interface{}{
  98. "v": 3,
  99. "token": s.Token,
  100. "properties": map[string]string{
  101. "$os": runtime.GOOS,
  102. "$browser": "Discordgo",
  103. "$device": "Discordgo",
  104. "$referer": "",
  105. "$referring_domain": "",
  106. },
  107. },
  108. })
  109. return
  110. }
  111. // UpdateStatus is used to update the authenticated user's status.
  112. func (s *Session) UpdateStatus(idleSince, gameID string) (err error) {
  113. err = s.wsConn.WriteJSON(map[string]interface{}{
  114. "op": 2,
  115. "d": map[string]interface{}{
  116. "idle_since": idleSince,
  117. "game_id": gameID,
  118. },
  119. })
  120. return
  121. }
  122. // TODO: need a channel or something to communicate
  123. // to this so I can tell it to stop listening
  124. // Listen starts listening to the websocket connection for events.
  125. func (s *Session) Listen() (err error) {
  126. if s.wsConn == nil {
  127. fmt.Println("No websocket connection exists.")
  128. return // need to return an error.
  129. }
  130. for {
  131. messageType, message, err := s.wsConn.ReadMessage()
  132. if err != nil {
  133. fmt.Println("Websocket Listen Error", err)
  134. break
  135. }
  136. go s.event(messageType, message)
  137. }
  138. return
  139. }
  140. // Not sure how needed this is and where it would be best to call it.
  141. // somewhere.
  142. // Close closes the connection to the websocket.
  143. func (s *Session) Close() {
  144. s.wsConn.Close()
  145. }
  146. // Front line handler for all Websocket Events. Determines the
  147. // event type and passes the message along to the next handler.
  148. // event is the front line handler for all events. This needs to be
  149. // broken up into smaller functions to be more idiomatic Go.
  150. func (s *Session) event(messageType int, message []byte) (err error) {
  151. if s.Debug {
  152. printJSON(message)
  153. }
  154. var e Event
  155. if err := json.Unmarshal(message, &e); err != nil {
  156. return err
  157. }
  158. switch e.Type {
  159. case "READY":
  160. if s.OnReady != nil {
  161. var st Ready
  162. if err := json.Unmarshal(e.RawData, &st); err != nil {
  163. fmt.Println(e.Type, err)
  164. printJSON(e.RawData) // TODO: Better error logging
  165. return err
  166. }
  167. s.OnReady(s, st)
  168. return
  169. }
  170. case "VOICE_SERVER_UPDATE":
  171. // TEMP CODE FOR TESTING VOICE
  172. var st VoiceServerUpdate
  173. if err := json.Unmarshal(e.RawData, &st); err != nil {
  174. fmt.Println(e.Type, err)
  175. printJSON(e.RawData) // TODO: Better error logging
  176. return err
  177. }
  178. s.onVoiceServerUpdate(st)
  179. return
  180. case "VOICE_STATE_UPDATE":
  181. // TEMP CODE FOR TESTING VOICE
  182. var st VoiceState
  183. if err := json.Unmarshal(e.RawData, &st); err != nil {
  184. fmt.Println(e.Type, err)
  185. printJSON(e.RawData) // TODO: Better error logging
  186. return err
  187. }
  188. s.onVoiceStateUpdate(st)
  189. case "PRESENCE_UPDATE":
  190. if s.OnPresenceUpdate != nil {
  191. var st PresenceUpdate
  192. if err := json.Unmarshal(e.RawData, &st); err != nil {
  193. fmt.Println(e.Type, err)
  194. printJSON(e.RawData) // TODO: Better error logging
  195. return err
  196. }
  197. s.OnPresenceUpdate(s, st)
  198. return
  199. }
  200. case "TYPING_START":
  201. if s.OnTypingStart != nil {
  202. var st TypingStart
  203. if err := json.Unmarshal(e.RawData, &st); err != nil {
  204. fmt.Println(e.Type, err)
  205. printJSON(e.RawData) // TODO: Better error logging
  206. return err
  207. }
  208. s.OnTypingStart(s, st)
  209. return
  210. }
  211. /* // Never seen this come in but saw it in another Library.
  212. case "MESSAGE_ACK":
  213. if s.OnMessageAck != nil {
  214. }
  215. */
  216. case "MESSAGE_CREATE":
  217. if s.OnMessageCreate != nil {
  218. var st Message
  219. if err := json.Unmarshal(e.RawData, &st); err != nil {
  220. fmt.Println(e.Type, err)
  221. printJSON(e.RawData) // TODO: Better error logging
  222. return err
  223. }
  224. s.OnMessageCreate(s, st)
  225. return
  226. }
  227. case "MESSAGE_UPDATE":
  228. if s.OnMessageUpdate != nil {
  229. var st Message
  230. if err := json.Unmarshal(e.RawData, &st); err != nil {
  231. fmt.Println(e.Type, err)
  232. printJSON(e.RawData) // TODO: Better error logging
  233. return err
  234. }
  235. s.OnMessageUpdate(s, st)
  236. return
  237. }
  238. case "MESSAGE_DELETE":
  239. if s.OnMessageDelete != nil {
  240. var st MessageDelete
  241. if err := json.Unmarshal(e.RawData, &st); err != nil {
  242. fmt.Println(e.Type, err)
  243. printJSON(e.RawData) // TODO: Better error logging
  244. return err
  245. }
  246. s.OnMessageDelete(s, st)
  247. return
  248. }
  249. case "MESSAGE_ACK":
  250. if s.OnMessageAck != nil {
  251. var st MessageAck
  252. if err := json.Unmarshal(e.RawData, &st); err != nil {
  253. fmt.Println(e.Type, err)
  254. printJSON(e.RawData) // TODO: Better error logging
  255. return err
  256. }
  257. s.OnMessageAck(s, st)
  258. return
  259. }
  260. case "CHANNEL_CREATE":
  261. if s.OnChannelCreate != nil {
  262. var st Channel
  263. if err := json.Unmarshal(e.RawData, &st); err != nil {
  264. fmt.Println(e.Type, err)
  265. printJSON(e.RawData) // TODO: Better error logginEventg
  266. return err
  267. }
  268. s.OnChannelCreate(s, st)
  269. return
  270. }
  271. case "CHANNEL_UPDATE":
  272. if s.OnChannelUpdate != nil {
  273. var st Channel
  274. if err := json.Unmarshal(e.RawData, &st); err != nil {
  275. fmt.Println(e.Type, err)
  276. printJSON(e.RawData) // TODO: Better error logginEventg
  277. return err
  278. }
  279. s.OnChannelUpdate(s, st)
  280. return
  281. }
  282. case "CHANNEL_DELETE":
  283. if s.OnChannelDelete != nil {
  284. var st Channel
  285. if err := json.Unmarshal(e.RawData, &st); err != nil {
  286. fmt.Println(e.Type, err)
  287. printJSON(e.RawData) // TODO: Better error logginEventg
  288. return err
  289. }
  290. s.OnChannelDelete(s, st)
  291. return
  292. }
  293. case "GUILD_CREATE":
  294. if s.OnGuildCreate != nil {
  295. var st Guild
  296. if err := json.Unmarshal(e.RawData, &st); err != nil {
  297. fmt.Println(e.Type, err)
  298. printJSON(e.RawData) // TODO: Better error logginEventg
  299. return err
  300. }
  301. s.OnGuildCreate(s, st)
  302. return
  303. }
  304. case "GUILD_UPDATE":
  305. if s.OnGuildCreate != nil {
  306. var st Guild
  307. if err := json.Unmarshal(e.RawData, &st); err != nil {
  308. fmt.Println(e.Type, err)
  309. printJSON(e.RawData) // TODO: Better error logginEventg
  310. return err
  311. }
  312. s.OnGuildUpdate(s, st)
  313. return
  314. }
  315. case "GUILD_DELETE":
  316. if s.OnGuildDelete != nil {
  317. var st Guild
  318. if err := json.Unmarshal(e.RawData, &st); err != nil {
  319. fmt.Println(e.Type, err)
  320. printJSON(e.RawData) // TODO: Better error logginEventg
  321. return err
  322. }
  323. s.OnGuildDelete(s, st)
  324. return
  325. }
  326. case "GUILD_MEMBER_ADD":
  327. if s.OnGuildMemberAdd != nil {
  328. var st Member
  329. if err := json.Unmarshal(e.RawData, &st); err != nil {
  330. fmt.Println(e.Type, err)
  331. printJSON(e.RawData) // TODO: Better error logginEventg
  332. return err
  333. }
  334. s.OnGuildMemberAdd(s, st)
  335. return
  336. }
  337. case "GUILD_MEMBER_REMOVE":
  338. if s.OnGuildMemberRemove != nil {
  339. var st Member
  340. if err := json.Unmarshal(e.RawData, &st); err != nil {
  341. fmt.Println(e.Type, err)
  342. printJSON(e.RawData) // TODO: Better error logginEventg
  343. return err
  344. }
  345. s.OnGuildMemberRemove(s, st)
  346. return
  347. }
  348. case "GUILD_MEMBER_UPDATE":
  349. if s.OnGuildMemberUpdate != nil {
  350. var st Member
  351. if err := json.Unmarshal(e.RawData, &st); err != nil {
  352. fmt.Println(e.Type, err)
  353. printJSON(e.RawData) // TODO: Better error logginEventg
  354. return err
  355. }
  356. s.OnGuildMemberUpdate(s, st)
  357. return
  358. }
  359. case "GUILD_ROLE_CREATE":
  360. if s.OnGuildRoleCreate != nil {
  361. var st GuildRole
  362. if err := json.Unmarshal(e.RawData, &st); err != nil {
  363. fmt.Println(e.Type, err)
  364. printJSON(e.RawData) // TODO: Better error logginEventg
  365. return err
  366. }
  367. s.OnGuildRoleCreate(s, st)
  368. return
  369. }
  370. case "GUILD_ROLE_UPDATE":
  371. if s.OnGuildRoleUpdate != nil {
  372. var st GuildRole
  373. if err := json.Unmarshal(e.RawData, &st); err != nil {
  374. fmt.Println(e.Type, err)
  375. printJSON(e.RawData) // TODO: Better error logginEventg
  376. return err
  377. }
  378. s.OnGuildRoleUpdate(s, st)
  379. return
  380. }
  381. case "GUILD_ROLE_DELETE":
  382. if s.OnGuildRoleDelete != nil {
  383. var st GuildRoleDelete
  384. if err := json.Unmarshal(e.RawData, &st); err != nil {
  385. fmt.Println(e.Type, err)
  386. printJSON(e.RawData) // TODO: Better error logginEventg
  387. return err
  388. }
  389. s.OnGuildRoleDelete(s, st)
  390. return
  391. }
  392. case "GUILD_INTEGRATIONS_UPDATE":
  393. if s.OnGuildIntegrationsUpdate != nil {
  394. var st GuildIntegrationsUpdate
  395. if err := json.Unmarshal(e.RawData, &st); err != nil {
  396. fmt.Println(e.Type, err)
  397. printJSON(e.RawData) // TODO: Better error logginEventg
  398. return err
  399. }
  400. s.OnGuildIntegrationsUpdate(s, st)
  401. return
  402. }
  403. default:
  404. fmt.Println("UNKNOWN EVENT: ", e.Type)
  405. // learn the log package
  406. // log.print type and JSON data
  407. }
  408. // if still here, send to generic OnEvent
  409. if s.OnEvent != nil {
  410. s.OnEvent(s, e)
  411. }
  412. return
  413. }
  414. // This heartbeat is sent to keep the Websocket conenction
  415. // to Discord alive. If not sent, Discord will close the
  416. // connection.
  417. // Heartbeat sends regular heartbeats to Discord so it knows the client
  418. // is still connected. If you do not send these heartbeats Discord will
  419. // disconnect the websocket connection after a few seconds.
  420. func (s *Session) Heartbeat(i time.Duration) {
  421. if s.wsConn == nil {
  422. fmt.Println("No websocket connection exists.")
  423. return // need to return an error.
  424. }
  425. ticker := time.NewTicker(i * time.Millisecond)
  426. for range ticker.C {
  427. timestamp := int(time.Now().Unix())
  428. err := s.wsConn.WriteJSON(map[string]int{
  429. "op": 1,
  430. "d": timestamp,
  431. })
  432. if err != nil {
  433. return // log error?
  434. }
  435. }
  436. }
  437. // Everything below is experimental Voice support code
  438. // all of it will get changed and moved around.
  439. // A VoiceServerUpdate stores the data received during the Voice Server Update
  440. // data websocket event. This data is used during the initial Voice Channel
  441. // join handshaking.
  442. type VoiceServerUpdate struct {
  443. Token string `json:"token"`
  444. GuildID string `json:"guild_id"`
  445. Endpoint string `json:"endpoint"`
  446. }
  447. // VoiceChannelJoin joins the authenticated session user to
  448. // a voice channel. All the voice magic starts with this.
  449. func (s *Session) VoiceChannelJoin(guildID, channelID string) {
  450. if s.wsConn == nil {
  451. fmt.Println("error: no websocket connection exists.")
  452. return
  453. }
  454. // Odd, but.. it works. map interface caused odd unknown opcode error
  455. // Later I'll test with a struct
  456. json := []byte(fmt.Sprintf(`{"op":4,"d":{"guild_id":"%s","channel_id":"%s","self_mute":false,"self_deaf":false}}`,
  457. guildID, channelID))
  458. err := s.wsConn.WriteMessage(websocket.TextMessage, json)
  459. if err != nil {
  460. fmt.Println("error:", err)
  461. return
  462. }
  463. // Probably will be removed later.
  464. s.VGuildID = guildID
  465. s.VChannelID = channelID
  466. }
  467. // onVoiceStateUpdate handles Voice State Update events on the data
  468. // websocket. This comes immediately after the call to VoiceChannelJoin
  469. // for the authenticated session user. This block is experimental
  470. // code and will be chaned in the future.
  471. func (s *Session) onVoiceStateUpdate(st VoiceState) {
  472. // Need to have this happen at login and store it in the Session
  473. self, err := s.User("@me")
  474. if err != nil {
  475. fmt.Println(err)
  476. return
  477. }
  478. // This event comes for all users, if it's not for the session
  479. // user just ignore it.
  480. if st.UserID != self.ID {
  481. return
  482. }
  483. // Store the SessionID. Used later.
  484. s.VSessionID = st.SessionID
  485. }
  486. // onVoiceServerUpdate handles the Voice Server Update data websocket event.
  487. // This will later be exposed but is only for experimental use now.
  488. func (s *Session) onVoiceServerUpdate(st VoiceServerUpdate) {
  489. // Store all the values. They are used later.
  490. // GuildID is probably not needed and may be dropped.
  491. s.VToken = st.Token
  492. s.VEndpoint = st.Endpoint
  493. s.VGuildID = st.GuildID
  494. // We now have enough information to open a voice websocket conenction
  495. // so, that's what the next call does.
  496. s.VoiceOpenWS()
  497. }