Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Role connections #1295

Merged
merged 4 commits into from
Dec 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ var (
return EndpointCDNBanners + uID + "/" + cID + ".gif"
}

EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" }
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" }
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
EndpointUserApplicationRoleConnection = func(aID string) string { return EndpointUsers + "@me/applications/" + aID + "/role-connection" }
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }

EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" }
Expand Down Expand Up @@ -197,8 +198,9 @@ var (
EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" }
EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" }

EndpointApplications = EndpointAPI + "applications"
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
EndpointApplications = EndpointAPI + "applications"
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
EndpointApplicationRoleConnectionMetadata = func(aID string) string { return EndpointApplication(aID) + "/role-connections/metadata" }

EndpointOAuth2 = EndpointAPI + "oauth2/"
EndpointOAuth2Applications = EndpointOAuth2 + "applications"
Expand Down
11 changes: 11 additions & 0 deletions examples/linked_roles/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/bwmarrin/discordgo/examples/linked_roles

go 1.13

replace github.com/bwmarrin/discordgo v0.26.1 => ../../

require (
github.com/bwmarrin/discordgo v0.26.1
github.com/joho/godotenv v1.4.0
golang.org/x/oauth2 v0.3.0
)
54 changes: 54 additions & 0 deletions examples/linked_roles/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
133 changes: 133 additions & 0 deletions examples/linked_roles/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"

"github.com/bwmarrin/discordgo"
"github.com/joho/godotenv"
"golang.org/x/oauth2"
)

var oauthConfig = oauth2.Config{
Endpoint: oauth2.Endpoint{
AuthURL: "https://discord.com/oauth2/authorize",
TokenURL: "https://discord.com/api/oauth2/token",
},
Scopes: []string{"identify", "role_connections.write"},
}

var (
appID = flag.String("app", "", "Application ID")
token = flag.String("token", "", "Application token")
clientSecret = flag.String("secret", "", "OAuth2 secret")
redirectURL = flag.String("redirect", "", "OAuth2 Redirect URL")
)

func init() {
flag.Parse()
godotenv.Load()
oauthConfig.ClientID = *appID
oauthConfig.ClientSecret = *clientSecret
oauthConfig.RedirectURL, _ = url.JoinPath(*redirectURL, "/linked-roles-callback")
}

func main() {
s, _ := discordgo.New("Bot " + *token)

_, err := s.ApplicationRoleConnectionMetadataUpdate(*appID, []*discordgo.ApplicationRoleConnectionMetadata{
{
Type: discordgo.ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual,
Key: "loc",
Name: "Lines of Code",
NameLocalizations: map[discordgo.Locale]string{},
Description: "Total lines of code written",
DescriptionLocalizations: map[discordgo.Locale]string{},
},
{
Type: discordgo.ApplicationRoleConnectionMetadataBooleanEqual,
Key: "gopher",
Name: "Gopher",
NameLocalizations: map[discordgo.Locale]string{},
Description: "Writes in Go",
DescriptionLocalizations: map[discordgo.Locale]string{},
},
{
Type: discordgo.ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual,
Key: "first_line",
Name: "First line written",
NameLocalizations: map[discordgo.Locale]string{},
Description: "Days since the first line of code",
DescriptionLocalizations: map[discordgo.Locale]string{},
},
})
if err != nil {
panic(err)
}

fmt.Println("Updated application metadata")
http.HandleFunc("/linked-roles", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache")
// Redirect the user to Discord OAuth2 page.
http.Redirect(w, r, oauthConfig.AuthCodeURL("random-state"), http.StatusMovedPermanently)
})
http.HandleFunc("/linked-roles-callback", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
// A safeguard against CSRF attacks.
// Usually tied to requesting user or random.
// NOTE: Hardcoded for the sake of the example.
if q["state"][0] != "random-state" {
return
}

// Fetch the tokens with code we've received.
tokens, err := oauthConfig.Exchange(r.Context(), q["code"][0])
if err != nil {
w.Write([]byte(err.Error()))
return
}

// Construct a temporary session with user's OAuth2 access_token.
ts, _ := discordgo.New("Bearer " + tokens.AccessToken)

// Retrive the user data.
u, err := ts.User("@me")
if err != nil {
w.Write([]byte(err.Error()))
return
}

// Fetch external metadata...
// NOTE: Hardcoded for the sake of the example.
metadata := map[string]string{
"gopher": "1", // 1 for true, 0 for false
"loc": "10000",
"first_line": "1970-01-01", // YYYY-MM-DD
}

// And submit it back to discord.
_, err = ts.UserApplicationRoleConnectionUpdate(*appID, &discordgo.ApplicationRoleConnection{
PlatformName: "Discord Gophers",
PlatformUsername: u.Username,
Metadata: metadata,
})
if err != nil {
w.Write([]byte(err.Error()))
return
}

// Retrieve it to check if everything is ok.
info, err := ts.UserApplicationRoleConnection(*appID)
if err != nil {
w.Write([]byte(err.Error()))
return
}
jsonMetadata, _ := json.Marshal(info.Metadata)
// And show it to the user.
w.Write([]byte(fmt.Sprintf("Your updated metadata is: %s", jsonMetadata)))
})
http.ListenAndServe(":8010", nil)
}
59 changes: 59 additions & 0 deletions restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3245,3 +3245,62 @@ func (s *Session) AutoModerationRuleDelete(guildID, ruleID string) (err error) {
_, err = s.RequestWithBucketID("DELETE", endpoint, nil, endpoint)
return
}

// ApplicationRoleConnectionMetadata returns application role connection metadata.
// appID : ID of the application
func (s *Session) ApplicationRoleConnectionMetadata(appID string) (st []*ApplicationRoleConnectionMetadata, err error) {
endpoint := EndpointApplicationRoleConnectionMetadata(appID)
var body []byte
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}

err = unmarshal(body, &st)
return
}

// ApplicationRoleConnectionMetadataUpdate updates and returns application role connection metadata.
// appID : ID of the application
// metadata : New metadata
func (s *Session) ApplicationRoleConnectionMetadataUpdate(appID string, metadata []*ApplicationRoleConnectionMetadata) (st []*ApplicationRoleConnectionMetadata, err error) {
endpoint := EndpointApplicationRoleConnectionMetadata(appID)
var body []byte
body, err = s.RequestWithBucketID("PUT", endpoint, metadata, endpoint)
if err != nil {
return
}

err = unmarshal(body, &st)
return
}

// UserApplicationRoleConnection returns user role connection to the specified application.
// appID : ID of the application
func (s *Session) UserApplicationRoleConnection(appID string) (st *ApplicationRoleConnection, err error) {
endpoint := EndpointUserApplicationRoleConnection(appID)
var body []byte
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}

err = unmarshal(body, &st)
return

}

// UserApplicationRoleConnectionUpdate updates and returns user role connection to the specified application.
// appID : ID of the application
// connection : New ApplicationRoleConnection data
func (s *Session) UserApplicationRoleConnectionUpdate(appID string, rconn *ApplicationRoleConnection) (st *ApplicationRoleConnection, err error) {
endpoint := EndpointUserApplicationRoleConnection(appID)
var body []byte
body, err = s.RequestWithBucketID("PUT", endpoint, rconn, endpoint)
if err != nil {
return
}

err = unmarshal(body, &st)
return
}
32 changes: 32 additions & 0 deletions structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,38 @@ type Application struct {
Flags int `json:"flags,omitempty"`
}

// ApplicationRoleConnectionMetadataType represents the type of application role connection metadata.
type ApplicationRoleConnectionMetadataType int

// Application role connection metadata types.
const (
ApplicationRoleConnectionMetadataIntegerLessThanOrEqual ApplicationRoleConnectionMetadataType = 1
ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 2
ApplicationRoleConnectionMetadataIntegerEqual ApplicationRoleConnectionMetadataType = 3
ApplicationRoleConnectionMetadataIntegerNotEqual ApplicationRoleConnectionMetadataType = 4
ApplicationRoleConnectionMetadataDatetimeLessThanOrEqual ApplicationRoleConnectionMetadataType = 5
ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 6
ApplicationRoleConnectionMetadataBooleanEqual ApplicationRoleConnectionMetadataType = 7
ApplicationRoleConnectionMetadataBooleanNotEqual ApplicationRoleConnectionMetadataType = 8
)

// ApplicationRoleConnectionMetadata stores application role connection metadata.
type ApplicationRoleConnectionMetadata struct {
Type ApplicationRoleConnectionMetadataType `json:"type"`
Key string `json:"key"`
Name string `json:"name"`
NameLocalizations map[Locale]string `json:"name_localizations"`
Description string `json:"description"`
DescriptionLocalizations map[Locale]string `json:"description_localizations"`
}

// ApplicationRoleConnection represents the role connection that an application has attached to a user.
type ApplicationRoleConnection struct {
PlatformName string `json:"platform_name"`
PlatformUsername string `json:"platform_username"`
Metadata map[string]string `json:"metadata"`
}

// UserConnection is a Connection returned from the UserConnections endpoint
type UserConnection struct {
ID string `json:"id"`
Expand Down