package discord import ( "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "github.com/pkg/errors" ) type Token struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token"` Scope string `json:"scope"` } const oauthurl string = "https://discord.com/oauth2/authorize" const apiurl string = "https://discord.com/api/v10" func GetOAuthLink(cfg *Config, state, trustedHost string) (string, error) { if cfg == nil { return "", errors.New("cfg cannot be nil") } if state == "" { return "", errors.New("state cannot be empty") } if trustedHost == "" { return "", errors.New("trustedHost cannot be empty") } values := url.Values{} values.Add("response_type", "code") values.Add("client_id", cfg.ClientID) values.Add("scope", cfg.OAuthScopes) values.Add("state", state) values.Add("redirect_uri", fmt.Sprintf("%s/%s", trustedHost, cfg.RedirectPath)) values.Add("prompt", "none") return fmt.Sprintf("%s?%s", oauthurl, values.Encode()), nil } func AuthorizeWithCode(cfg *Config, code, trustedHost string) (*Token, error) { if code == "" { return nil, errors.New("code cannot be empty") } if cfg == nil { return nil, errors.New("config cannot be nil") } if trustedHost == "" { return nil, errors.New("trustedHost cannot be empty") } // Prepare form data data := url.Values{} data.Set("grant_type", "authorization_code") data.Set("code", code) data.Set("redirect_uri", fmt.Sprintf("%s/%s", trustedHost, cfg.RedirectPath)) // Create request req, err := http.NewRequest( "POST", apiurl+"/oauth2/token", strings.NewReader(data.Encode()), ) if err != nil { return nil, errors.Wrap(err, "failed to create request") } // Set headers req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // Set basic auth (client_id and client_secret) req.SetBasicAuth(cfg.ClientID, cfg.ClientSecret) // Execute request client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, errors.Wrap(err, "failed to execute request") } defer resp.Body.Close() // Read response body body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "failed to read response body") } // Check status code if resp.StatusCode != http.StatusOK { return nil, errors.Errorf("discord API returned status %d: %s", resp.StatusCode, string(body)) } // Parse JSON response var tokenResp Token if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, errors.Wrap(err, "failed to parse token response") } return &tokenResp, nil } func RefreshToken(cfg *Config, token *Token) (*Token, error) { if token == nil { return nil, errors.New("token cannot be nil") } if cfg == nil { return nil, errors.New("config cannot be nil") } // Prepare form data data := url.Values{} data.Set("grant_type", "refresh_token") data.Set("refresh_token", token.RefreshToken) // Create request req, err := http.NewRequest( "POST", apiurl+"/oauth2/token", strings.NewReader(data.Encode()), ) if err != nil { return nil, errors.Wrap(err, "failed to create request") } // Set headers req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // Set basic auth (client_id and client_secret) req.SetBasicAuth(cfg.ClientID, cfg.ClientSecret) // Execute request client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, errors.Wrap(err, "failed to execute request") } defer resp.Body.Close() // Read response body body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "failed to read response body") } // Check status code if resp.StatusCode != http.StatusOK { return nil, errors.Errorf("discord API returned status %d: %s", resp.StatusCode, string(body)) } // Parse JSON response var tokenResp Token if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, errors.Wrap(err, "failed to parse token response") } return &tokenResp, nil } func RevokeToken(cfg *Config, token *Token) error { if token == nil { return errors.New("token cannot be nil") } if cfg == nil { return errors.New("config cannot be nil") } // Prepare form data data := url.Values{} data.Set("token", token.AccessToken) data.Set("token_type_hint", "access_token") // Create request req, err := http.NewRequest( "POST", apiurl+"/oauth2/token/revoke", strings.NewReader(data.Encode()), ) if err != nil { return errors.Wrap(err, "failed to create request") } // Set headers req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // Set basic auth (client_id and client_secret) req.SetBasicAuth(cfg.ClientID, cfg.ClientSecret) // Execute request client := &http.Client{} resp, err := client.Do(req) if err != nil { return errors.Wrap(err, "failed to execute request") } defer resp.Body.Close() // Check status code if resp.StatusCode != http.StatusOK { return errors.Errorf("discord API returned status %d", resp.StatusCode) } return nil }