Skip to content
This repository was archived by the owner on Mar 5, 2023. It is now read-only.

Commit 4eef78e

Browse files
authored
Role connections (bwmarrin#1295)
* feat: role connection metadata * feat: add role connection endpoints Add User Application Role Connection endpoints: * Get User Application Role Connection * Update User Application Role Connection * feat: add example Add basic example to showcase linked roles flow. * refactor(endpoints): move role connection metadata Move Application Role Connection Metadata endpoint to other Application endpoints.
1 parent 4074561 commit 4eef78e

File tree

6 files changed

+298
-7
lines changed

6 files changed

+298
-7
lines changed

endpoints.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ var (
6060
return EndpointCDNBanners + uID + "/" + cID + ".gif"
6161
}
6262

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

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

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

203205
EndpointOAuth2 = EndpointAPI + "oauth2/"
204206
EndpointOAuth2Applications = EndpointOAuth2 + "applications"

examples/linked_roles/go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/bwmarrin/discordgo/examples/linked_roles
2+
3+
go 1.13
4+
5+
replace github.com/bwmarrin/discordgo v0.26.1 => ../../
6+
7+
require (
8+
github.com/bwmarrin/discordgo v0.26.1
9+
github.com/joho/godotenv v1.4.0
10+
golang.org/x/oauth2 v0.3.0
11+
)

examples/linked_roles/go.sum

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

examples/linked_roles/main.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"flag"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
10+
"github.com/bwmarrin/discordgo"
11+
"github.com/joho/godotenv"
12+
"golang.org/x/oauth2"
13+
)
14+
15+
var oauthConfig = oauth2.Config{
16+
Endpoint: oauth2.Endpoint{
17+
AuthURL: "https://discord.com/oauth2/authorize",
18+
TokenURL: "https://discord.com/api/oauth2/token",
19+
},
20+
Scopes: []string{"identify", "role_connections.write"},
21+
}
22+
23+
var (
24+
appID = flag.String("app", "", "Application ID")
25+
token = flag.String("token", "", "Application token")
26+
clientSecret = flag.String("secret", "", "OAuth2 secret")
27+
redirectURL = flag.String("redirect", "", "OAuth2 Redirect URL")
28+
)
29+
30+
func init() {
31+
flag.Parse()
32+
godotenv.Load()
33+
oauthConfig.ClientID = *appID
34+
oauthConfig.ClientSecret = *clientSecret
35+
oauthConfig.RedirectURL, _ = url.JoinPath(*redirectURL, "/linked-roles-callback")
36+
}
37+
38+
func main() {
39+
s, _ := discordgo.New("Bot " + *token)
40+
41+
_, err := s.ApplicationRoleConnectionMetadataUpdate(*appID, []*discordgo.ApplicationRoleConnectionMetadata{
42+
{
43+
Type: discordgo.ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual,
44+
Key: "loc",
45+
Name: "Lines of Code",
46+
NameLocalizations: map[discordgo.Locale]string{},
47+
Description: "Total lines of code written",
48+
DescriptionLocalizations: map[discordgo.Locale]string{},
49+
},
50+
{
51+
Type: discordgo.ApplicationRoleConnectionMetadataBooleanEqual,
52+
Key: "gopher",
53+
Name: "Gopher",
54+
NameLocalizations: map[discordgo.Locale]string{},
55+
Description: "Writes in Go",
56+
DescriptionLocalizations: map[discordgo.Locale]string{},
57+
},
58+
{
59+
Type: discordgo.ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual,
60+
Key: "first_line",
61+
Name: "First line written",
62+
NameLocalizations: map[discordgo.Locale]string{},
63+
Description: "Days since the first line of code",
64+
DescriptionLocalizations: map[discordgo.Locale]string{},
65+
},
66+
})
67+
if err != nil {
68+
panic(err)
69+
}
70+
71+
fmt.Println("Updated application metadata")
72+
http.HandleFunc("/linked-roles", func(w http.ResponseWriter, r *http.Request) {
73+
w.Header().Set("Cache-Control", "no-cache")
74+
// Redirect the user to Discord OAuth2 page.
75+
http.Redirect(w, r, oauthConfig.AuthCodeURL("random-state"), http.StatusMovedPermanently)
76+
})
77+
http.HandleFunc("/linked-roles-callback", func(w http.ResponseWriter, r *http.Request) {
78+
q := r.URL.Query()
79+
// A safeguard against CSRF attacks.
80+
// Usually tied to requesting user or random.
81+
// NOTE: Hardcoded for the sake of the example.
82+
if q["state"][0] != "random-state" {
83+
return
84+
}
85+
86+
// Fetch the tokens with code we've received.
87+
tokens, err := oauthConfig.Exchange(r.Context(), q["code"][0])
88+
if err != nil {
89+
w.Write([]byte(err.Error()))
90+
return
91+
}
92+
93+
// Construct a temporary session with user's OAuth2 access_token.
94+
ts, _ := discordgo.New("Bearer " + tokens.AccessToken)
95+
96+
// Retrive the user data.
97+
u, err := ts.User("@me")
98+
if err != nil {
99+
w.Write([]byte(err.Error()))
100+
return
101+
}
102+
103+
// Fetch external metadata...
104+
// NOTE: Hardcoded for the sake of the example.
105+
metadata := map[string]string{
106+
"gopher": "1", // 1 for true, 0 for false
107+
"loc": "10000",
108+
"first_line": "1970-01-01", // YYYY-MM-DD
109+
}
110+
111+
// And submit it back to discord.
112+
_, err = ts.UserApplicationRoleConnectionUpdate(*appID, &discordgo.ApplicationRoleConnection{
113+
PlatformName: "Discord Gophers",
114+
PlatformUsername: u.Username,
115+
Metadata: metadata,
116+
})
117+
if err != nil {
118+
w.Write([]byte(err.Error()))
119+
return
120+
}
121+
122+
// Retrieve it to check if everything is ok.
123+
info, err := ts.UserApplicationRoleConnection(*appID)
124+
if err != nil {
125+
w.Write([]byte(err.Error()))
126+
return
127+
}
128+
jsonMetadata, _ := json.Marshal(info.Metadata)
129+
// And show it to the user.
130+
w.Write([]byte(fmt.Sprintf("Your updated metadata is: %s", jsonMetadata)))
131+
})
132+
http.ListenAndServe(":8010", nil)
133+
}

restapi.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,3 +3245,62 @@ func (s *Session) AutoModerationRuleDelete(guildID, ruleID string) (err error) {
32453245
_, err = s.RequestWithBucketID("DELETE", endpoint, nil, endpoint)
32463246
return
32473247
}
3248+
3249+
// ApplicationRoleConnectionMetadata returns application role connection metadata.
3250+
// appID : ID of the application
3251+
func (s *Session) ApplicationRoleConnectionMetadata(appID string) (st []*ApplicationRoleConnectionMetadata, err error) {
3252+
endpoint := EndpointApplicationRoleConnectionMetadata(appID)
3253+
var body []byte
3254+
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
3255+
if err != nil {
3256+
return
3257+
}
3258+
3259+
err = unmarshal(body, &st)
3260+
return
3261+
}
3262+
3263+
// ApplicationRoleConnectionMetadataUpdate updates and returns application role connection metadata.
3264+
// appID : ID of the application
3265+
// metadata : New metadata
3266+
func (s *Session) ApplicationRoleConnectionMetadataUpdate(appID string, metadata []*ApplicationRoleConnectionMetadata) (st []*ApplicationRoleConnectionMetadata, err error) {
3267+
endpoint := EndpointApplicationRoleConnectionMetadata(appID)
3268+
var body []byte
3269+
body, err = s.RequestWithBucketID("PUT", endpoint, metadata, endpoint)
3270+
if err != nil {
3271+
return
3272+
}
3273+
3274+
err = unmarshal(body, &st)
3275+
return
3276+
}
3277+
3278+
// UserApplicationRoleConnection returns user role connection to the specified application.
3279+
// appID : ID of the application
3280+
func (s *Session) UserApplicationRoleConnection(appID string) (st *ApplicationRoleConnection, err error) {
3281+
endpoint := EndpointUserApplicationRoleConnection(appID)
3282+
var body []byte
3283+
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
3284+
if err != nil {
3285+
return
3286+
}
3287+
3288+
err = unmarshal(body, &st)
3289+
return
3290+
3291+
}
3292+
3293+
// UserApplicationRoleConnectionUpdate updates and returns user role connection to the specified application.
3294+
// appID : ID of the application
3295+
// connection : New ApplicationRoleConnection data
3296+
func (s *Session) UserApplicationRoleConnectionUpdate(appID string, rconn *ApplicationRoleConnection) (st *ApplicationRoleConnection, err error) {
3297+
endpoint := EndpointUserApplicationRoleConnection(appID)
3298+
var body []byte
3299+
body, err = s.RequestWithBucketID("PUT", endpoint, rconn, endpoint)
3300+
if err != nil {
3301+
return
3302+
}
3303+
3304+
err = unmarshal(body, &st)
3305+
return
3306+
}

structs.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,38 @@ type Application struct {
156156
Flags int `json:"flags,omitempty"`
157157
}
158158

159+
// ApplicationRoleConnectionMetadataType represents the type of application role connection metadata.
160+
type ApplicationRoleConnectionMetadataType int
161+
162+
// Application role connection metadata types.
163+
const (
164+
ApplicationRoleConnectionMetadataIntegerLessThanOrEqual ApplicationRoleConnectionMetadataType = 1
165+
ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 2
166+
ApplicationRoleConnectionMetadataIntegerEqual ApplicationRoleConnectionMetadataType = 3
167+
ApplicationRoleConnectionMetadataIntegerNotEqual ApplicationRoleConnectionMetadataType = 4
168+
ApplicationRoleConnectionMetadataDatetimeLessThanOrEqual ApplicationRoleConnectionMetadataType = 5
169+
ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 6
170+
ApplicationRoleConnectionMetadataBooleanEqual ApplicationRoleConnectionMetadataType = 7
171+
ApplicationRoleConnectionMetadataBooleanNotEqual ApplicationRoleConnectionMetadataType = 8
172+
)
173+
174+
// ApplicationRoleConnectionMetadata stores application role connection metadata.
175+
type ApplicationRoleConnectionMetadata struct {
176+
Type ApplicationRoleConnectionMetadataType `json:"type"`
177+
Key string `json:"key"`
178+
Name string `json:"name"`
179+
NameLocalizations map[Locale]string `json:"name_localizations"`
180+
Description string `json:"description"`
181+
DescriptionLocalizations map[Locale]string `json:"description_localizations"`
182+
}
183+
184+
// ApplicationRoleConnection represents the role connection that an application has attached to a user.
185+
type ApplicationRoleConnection struct {
186+
PlatformName string `json:"platform_name"`
187+
PlatformUsername string `json:"platform_username"`
188+
Metadata map[string]string `json:"metadata"`
189+
}
190+
159191
// UserConnection is a Connection returned from the UserConnections endpoint
160192
type UserConnection struct {
161193
ID string `json:"id"`

0 commit comments

Comments
 (0)