restapi.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /******************************************************************************
  2. * A Discord API for Golang.
  3. * See discord.go for more information.
  4. *
  5. * This file contains functions for interacting with the Discord HTTP REST API
  6. * at the lowest level.
  7. */
  8. package discordgo
  9. import (
  10. "bytes"
  11. "encoding/json"
  12. "errors"
  13. "fmt"
  14. "io/ioutil"
  15. "net/http"
  16. "time"
  17. )
  18. // Constants of known Discord API Endpoints
  19. // Please let me know if you know of any others.
  20. const (
  21. // Base URLS
  22. DISCORD = "http://discordapp.com"
  23. API = DISCORD + "/api"
  24. SERVERS = API + "/guilds"
  25. CHANNELS = API + "/channels"
  26. USERS = API + "/users"
  27. LOGIN = API + "/auth/login"
  28. LOGOUT = API + "/auth/logout"
  29. GATEWAY = API + "/gateway"
  30. // Constants not implemented below yet. TODO tracker :)
  31. REGISTER = API + "/auth/register"
  32. INVITE = API + "/invite"
  33. TRACK = API + "/track"
  34. SSO = API + "/sso"
  35. VERIFY = API + "/auth/verify"
  36. VERIFY_RESEND = API + "/auth/verify/resend"
  37. FORGOT_PASSWORD = API + "/auth/forgot"
  38. RESET_PASSWORD = API + "/auth/reset"
  39. REGIONS = API + "/voice/regions"
  40. ICE = API + "/voice/ice"
  41. REPORT = API + "/report"
  42. INTEGRATIONS = API + "/integrations"
  43. // Authenticated User Info
  44. AU = USERS + "/@me"
  45. AU_DEVICES = AU + "/devices"
  46. AU_SETTINGS = AU + "/settings"
  47. AU_CONNECTIONS = AU + "/connections"
  48. AU_CHANNELS = AU + "/channels"
  49. AU_SERVERS = AU + "/guilds"
  50. // Need a way to handle these here so the variables can be inserted.
  51. // Maybe defined as functions?
  52. /*
  53. INTEGRATIONS_JOIN: integrationId => `/integrations/${integrationId}/join`,
  54. AVATAR: (userId, hash) => `/users/${userId}/avatars/${hash}.jpg`,
  55. MESSAGES: channelId => `/channels/${channelId}/messages`,
  56. INSTANT_INVITES: channelId => `/channels/${channelId}/invites`,
  57. TYPING: channelId => `/channels/${channelId}/typing`,
  58. CHANNEL_PERMISSIONS: channelId => `/channels/${channelId}/permissions`,
  59. TUTORIAL: `/tutorial`,
  60. TUTORIAL_INDICATORS: `/tutorial/indicators`,
  61. USER_CHANNELS: userId => `/users/${userId}/channels`,
  62. GUILD_CHANNELS: guildId => `/guilds/${guildId}/channels`,
  63. GUILD_MEMBERS: guildId => `/guilds/${guildId}/members`,
  64. GUILD_INTEGRATIONS: guildId => `/guilds/${guildId}/integrations`,
  65. GUILD_BANS: guildId => `/guilds/${guildId}/bans`,
  66. GUILD_ROLES: guildId => `/guilds/${guildId}/roles`,
  67. GUILD_INSTANT_INVITES: guildId => `/guilds/${guildId}/invites`,
  68. GUILD_EMBED: guildId => `/guilds/${guildId}/embed`,
  69. GUILD_PRUNE: guildId => `/guilds/${guildId}/prune`,
  70. GUILD_ICON: (guildId, hash) => `/guilds/${guildId}/icons/${hash}.jpg`,
  71. */
  72. )
  73. // Request makes a (GET/POST/?) Requests to Discord REST API.
  74. // All the other functions in this file use this function.
  75. func (s *Session) Request(method, urlStr, body string) (response []byte, err error) {
  76. if s.Debug {
  77. fmt.Println("REQUEST :: " + method + " " + urlStr + "\n" + body)
  78. }
  79. req, err := http.NewRequest(method, urlStr, bytes.NewBuffer([]byte(body)))
  80. if err != nil {
  81. return
  82. }
  83. // Not used on initial login..
  84. if s.Token != "" {
  85. req.Header.Set("authorization", s.Token)
  86. }
  87. req.Header.Set("Content-Type", "application/json")
  88. client := &http.Client{Timeout: (20 * time.Second)}
  89. resp, err := client.Do(req)
  90. if err != nil {
  91. return
  92. }
  93. response, err = ioutil.ReadAll(resp.Body)
  94. if err != nil {
  95. return
  96. }
  97. resp.Body.Close()
  98. if resp.StatusCode != 204 && resp.StatusCode != 200 {
  99. err = errors.New(fmt.Sprintf("StatusCode: %d, %s", resp.StatusCode, string(response)))
  100. return
  101. }
  102. if s.Debug {
  103. var prettyJSON bytes.Buffer
  104. error := json.Indent(&prettyJSON, response, "", "\t")
  105. if error != nil {
  106. fmt.Print("JSON parse error: ", error)
  107. return
  108. }
  109. fmt.Println("RESPONSE ::\n" + string(prettyJSON.Bytes()))
  110. }
  111. return
  112. }
  113. // Login asks the Discord server for an authentication token
  114. func (s *Session) Login(email string, password string) (token string, err error) {
  115. response, err := s.Request("POST", LOGIN, fmt.Sprintf(`{"email":"%s", "password":"%s"}`, email, password))
  116. var temp map[string]interface{}
  117. err = json.Unmarshal(response, &temp)
  118. token = temp["token"].(string)
  119. return
  120. }
  121. // Returns the user details of the given userId
  122. // session : An active session connection to Discord
  123. // user : A user Id or name
  124. func (s *Session) Users(userId string) (user User, err error) {
  125. body, err := s.Request("GET", fmt.Sprintf("%s/%s", USERS, userId), ``)
  126. err = json.Unmarshal(body, &user)
  127. return
  128. }
  129. // USERS could pull users channels, servers, settings and so forth too?
  130. // you know, pull all the data for the user. update the user strut
  131. // to house that data. Seems reasonable.
  132. // PrivateChannels returns an array of Channel structures for all private
  133. // channels for a user
  134. func (s *Session) PrivateChannels(userId string) (channels []Channel, err error) {
  135. body, err := s.Request("GET", fmt.Sprintf("%s/%s/channels", USERS, userId), ``)
  136. err = json.Unmarshal(body, &channels)
  137. return
  138. }
  139. // Guilds returns an array of Guild structures for all servers for a user
  140. func (s *Session) Guilds(userId string) (servers []Guild, err error) {
  141. body, err := s.Request("GET", fmt.Sprintf("%s/%s/guilds", USERS, userId), ``)
  142. err = json.Unmarshal(body, &servers)
  143. return
  144. }
  145. // add one to get specific server by ID, or enhance the above with an ID field.
  146. // GET http://discordapp.com/api/guilds/ID#
  147. // Members returns an array of Member structures for all members of a given
  148. // server.
  149. func (s *Session) Members(serverId int) (members []Member, err error) {
  150. body, err := s.Request("GET", fmt.Sprintf("%s/%d/members", SERVERS, serverId), ``)
  151. err = json.Unmarshal(body, &members)
  152. return
  153. }
  154. // Channels returns an array of Channel structures for all channels of a given
  155. // server.
  156. func (s *Session) Channels(serverId int) (channels []Channel, err error) {
  157. body, err := s.Request("GET", fmt.Sprintf("%s/%d/channels", SERVERS, serverId), ``)
  158. err = json.Unmarshal(body, &channels)
  159. return
  160. }
  161. // update above or add a way to get channel by ID. ChannelByName could be handy
  162. // too you know.
  163. // http://discordapp.com/api/channels/ID#
  164. // Messages returns an array of Message structures for messaages within a given
  165. // channel. limit, beforeId, and afterId can be used to control what messages
  166. // are returned.
  167. func (s *Session) Messages(channelId int, limit int, beforeId int, afterId int) (messages []Message, err error) {
  168. var urlStr string
  169. if limit > 0 {
  170. urlStr = fmt.Sprintf("%s/%d/messages?limit=%d", CHANNELS, channelId, limit)
  171. }
  172. if afterId > 0 {
  173. if urlStr != "" {
  174. urlStr = urlStr + fmt.Sprintf("&after=%d", afterId)
  175. } else {
  176. urlStr = fmt.Sprintf("%s/%d/messages?after=%d", CHANNELS, channelId, afterId)
  177. }
  178. }
  179. if beforeId > 0 {
  180. if urlStr != "" {
  181. urlStr = urlStr + fmt.Sprintf("&before=%d", beforeId)
  182. } else {
  183. urlStr = fmt.Sprintf("%s/%d/messages?after=%d", CHANNELS, channelId, beforeId)
  184. }
  185. }
  186. if urlStr == "" {
  187. urlStr = fmt.Sprintf("%s/%d/messages", CHANNELS, channelId)
  188. }
  189. body, err := s.Request("GET", urlStr, ``)
  190. err = json.Unmarshal(body, &messages)
  191. return
  192. }
  193. // SendMessage sends a message to the given channel.
  194. func (s *Session) SendMessage(channelId int, content string) (message Message, err error) {
  195. var urlStr string = fmt.Sprintf("%s/%d/messages", CHANNELS, channelId)
  196. response, err := s.Request("POST", urlStr, fmt.Sprintf(`{"content":"%s"}`, content))
  197. err = json.Unmarshal(response, &message)
  198. return
  199. }
  200. // Returns the a websocket Gateway address
  201. // session : An active session connection to Discord
  202. func (s *Session) Gateway() (gateway string, err error) {
  203. response, err := s.Request("GET", GATEWAY, ``)
  204. var temp map[string]interface{}
  205. err = json.Unmarshal(response, &temp)
  206. gateway = temp["url"].(string)
  207. return
  208. }
  209. // Close ends a session and logs out from the Discord REST API.
  210. // This does not seem to actually invalidate the token. So you can still
  211. // make API calls even after a Logout. So, it seems almost pointless to
  212. // even use.
  213. func (s *Session) Logout() (err error) {
  214. _, err = s.Request("POST", LOGOUT, fmt.Sprintf(`{"token": "%s"}`, s.Token))
  215. return
  216. }