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

Add Entitlements / SKUs #1552

Merged
merged 29 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f004123
add entitlement struct
jhoffi Aug 2, 2024
2e7eb30
add entitlements field to interaction struct
jhoffi Aug 2, 2024
4bbe559
add event handler for entitlements
jhoffi Aug 2, 2024
84ecf01
add description to Entitlement struct & type
jhoffi Aug 2, 2024
6725cb4
properly format changes in entitlement and events
jhoffi Aug 2, 2024
ccb5a90
format entitlement with gofmt
jhoffi Aug 2, 2024
7ad7e1b
move entitlement to structs
jhoffi Aug 2, 2024
87c3490
add subscription id to entitlement struct and add omitempty
jhoffi Aug 2, 2024
9620435
add endpoints
jhoffi Aug 2, 2024
38a1aff
add comments to EntitlementTest and EntitlementOwnerType
jhoffi Aug 2, 2024
efb7d7e
add sku struct
jhoffi Aug 2, 2024
351f5e9
add skus
jhoffi Aug 2, 2024
7bf00e8
fix typo
jhoffi Aug 2, 2024
d2057dc
rename skuid in entitlement struct
jhoffi Aug 2, 2024
b7ed8b8
add comments to SKUFlags
jhoffi Aug 2, 2024
5b3da13
change entitlement timestamps to time.Time
jhoffi Aug 2, 2024
34c29b0
rename skuid in entitlement-test struct
jhoffi Aug 3, 2024
6eb85e1
add documentation
jhoffi Aug 3, 2024
faf1279
remove undocumented subscriptionID field in entitlement struct
jhoffi Aug 3, 2024
887cdb3
add documentation
jhoffi Aug 3, 2024
20d5f76
update documentation
jhoffi Aug 14, 2024
54287e3
change order of EntitlementType values
jhoffi Aug 14, 2024
7cf6c73
move optional entitlement filter options into a separate struct
jhoffi Aug 15, 2024
2c97651
add premium button component
jhoffi Oct 5, 2024
e9b116e
add subscriptions
jhoffi Oct 5, 2024
d9d9f4b
optimize optional fields in entitlement struct
jhoffi Oct 5, 2024
621e4ad
Add documentation
jhoffi Oct 5, 2024
f81b609
typo
jhoffi Oct 5, 2024
347e932
remove unneccessary pointers
jhoffi Oct 15, 2024
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
4 changes: 4 additions & 0 deletions components.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ const (
DangerButton ButtonStyle = 4
// LinkButton is a special type of button which navigates to a URL. Has grey color.
LinkButton ButtonStyle = 5
// PremiumButton is a special type of button with a blurple color that links to a SKU.
PremiumButton ButtonStyle = 6
)

// ComponentEmoji represents button emoji, if it does have one.
Expand All @@ -140,6 +142,8 @@ type Button struct {
// NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID.
URL string `json:"url,omitempty"`
CustomID string `json:"custom_id,omitempty"`
// Identifier for a purchasable SKU. Only available when using premium-style buttons.
SKUID string `json:"sku_id,omitempty"`
}

// MarshalJSON is a method for marshaling Button to a JSON object.
Expand Down
22 changes: 22 additions & 0 deletions endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
EndpointWebhooks = EndpointAPI + "webhooks/"
EndpointStickers = EndpointAPI + "stickers/"
EndpointStageInstances = EndpointAPI + "stage-instances"
EndpointSKUs = EndpointAPI + "skus"

EndpointCDN = "https://cdn.discordapp.com/"
EndpointCDNAttachments = EndpointCDN + "attachments/"
Expand Down Expand Up @@ -172,6 +173,27 @@ var (
return EndpointPoll(cID, mID) + "/expire"
}

EndpointApplicationSKUs = func(aID string) string {
return EndpointApplication(aID) + "/skus"
}

EndpointEntitlements = func(aID string) string {
return EndpointApplication(aID) + "/entitlements"
}
EndpointEntitlement = func(aID, eID string) string {
return EndpointEntitlements(aID) + "/" + eID
}
EndpointEntitlementConsume = func(aID, eID string) string {
return EndpointEntitlement(aID, eID) + "/consume"
}

EndpointSubscriptions = func(skuID string) string {
return EndpointSKUs + "/" + skuID + "/subscriptions"
}
EndpointSubscription = func(skuID, subID string) string {
return EndpointSubscriptions(skuID) + "/" + subID
}

EndpointApplicationGlobalCommands = func(aID string) string {
return EndpointApplication(aID) + "/commands"
}
Expand Down
72 changes: 72 additions & 0 deletions eventhandlers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions events.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,19 @@ type MessagePollVoteRemove struct {
GuildID string `json:"guild_id,omitempty"`
AnswerID int `json:"answer_id"`
}

// EntitlementCreate is the data for an EntitlementCreate event.
type EntitlementCreate struct {
*Entitlement
}

// EntitlementUpdate is the data for an EntitlementUpdate event.
type EntitlementUpdate struct {
*Entitlement
}

// EntitlementDelete is the data for an EntitlementDelete event.
// NOTE: Entitlements are not deleted when they expire.
type EntitlementDelete struct {
*Entitlement
}
4 changes: 4 additions & 0 deletions interactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ type Interaction struct {

Token string `json:"token"`
Version int `json:"version"`

// Any entitlements for the invoking user, representing access to premium SKUs.
// NOTE: this field is only filled in monetized apps
Entitlements []*Entitlement `json:"entitlements"`
}

type interaction Interaction
Expand Down
134 changes: 134 additions & 0 deletions restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3500,3 +3500,137 @@ func (s *Session) PollExpire(channelID, messageID string) (msg *Message, err err
err = unmarshal(body, &msg)
return
}

// ----------------------------------------------------------------------
// Functions specific to monetization
// ----------------------------------------------------------------------

// SKUs returns all SKUs for a given application.
// appID : The ID of the application.
func (s *Session) SKUs(appID string) (skus []*SKU, err error) {
endpoint := EndpointApplicationSKUs(appID)

body, err := s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}

err = unmarshal(body, &skus)
return
}

// Entitlements returns all Entitlements for a given app, active and expired.
// appID : The ID of the application.
// filterOptions : Optional filter options; otherwise set it to nil.
func (s *Session) Entitlements(appID string, filterOptions *EntitlementFilterOptions, options ...RequestOption) (entitlements []*Entitlement, err error) {
endpoint := EndpointEntitlements(appID)

queryParams := url.Values{}
if filterOptions != nil {
if filterOptions.UserID != "" {
queryParams.Set("user_id", filterOptions.UserID)
}
if filterOptions.SkuIDs != nil && len(filterOptions.SkuIDs) > 0 {
queryParams.Set("sku_ids", strings.Join(filterOptions.SkuIDs, ","))
}
if filterOptions.Before != nil {
queryParams.Set("before", filterOptions.Before.Format(time.RFC3339))
}
if filterOptions.After != nil {
queryParams.Set("after", filterOptions.After.Format(time.RFC3339))
}
if filterOptions.Limit > 0 {
queryParams.Set("limit", strconv.Itoa(filterOptions.Limit))
}
if filterOptions.GuildID != "" {
queryParams.Set("guild_id", filterOptions.GuildID)
}
if filterOptions.ExcludeEnded {
queryParams.Set("exclude_ended", "true")
}
}

body, err := s.RequestWithBucketID("GET", endpoint+"?"+queryParams.Encode(), nil, endpoint, options...)
if err != nil {
return
}

err = unmarshal(body, &entitlements)
return
}

// EntitlementConsume marks a given One-Time Purchase for the user as consumed.
func (s *Session) EntitlementConsume(appID, entitlementID string, options ...RequestOption) (err error) {
_, err = s.RequestWithBucketID("POST", EndpointEntitlementConsume(appID, entitlementID), nil, EndpointEntitlementConsume(appID, ""), options...)
return
}

// EntitlementTestCreate creates a test entitlement to a given SKU for a given guild or user.
// Discord will act as though that user or guild has entitlement to your premium offering.
func (s *Session) EntitlementTestCreate(appID string, data *EntitlementTest, options ...RequestOption) (err error) {
endpoint := EndpointEntitlements(appID)

_, err = s.RequestWithBucketID("POST", endpoint, data, endpoint, options...)
return
}

// EntitlementTestDelete deletes a currently-active test entitlement. Discord will act as though
// that user or guild no longer has entitlement to your premium offering.
func (s *Session) EntitlementTestDelete(appID, entitlementID string, options ...RequestOption) (err error) {
_, err = s.RequestWithBucketID("DELETE", EndpointEntitlement(appID, entitlementID), nil, EndpointEntitlement(appID, ""), options...)
return
}

// Subscriptions returns all subscriptions containing the SKU.
// skuID : The ID of the SKU.
// userID : User ID for which to return subscriptions. Required except for OAuth queries.
// before : Optional timestamp to retrieve subscriptions before this time.
// after : Optional timestamp to retrieve subscriptions after this time.
// limit : Optional maximum number of subscriptions to return (1-100, default 50).
func (s *Session) Subscriptions(skuID string, userID string, before, after *time.Time, limit int, options ...RequestOption) (subscriptions []*Subscription, err error) {
endpoint := EndpointSubscriptions(skuID)

queryParams := url.Values{}
if before != nil {
queryParams.Set("before", before.Format(time.RFC3339))
}
if after != nil {
queryParams.Set("after", after.Format(time.RFC3339))
}
if userID != "" {
queryParams.Set("user_id", userID)
}
if limit > 0 {
queryParams.Set("limit", strconv.Itoa(limit))
}

body, err := s.RequestWithBucketID("GET", endpoint+"?"+queryParams.Encode(), nil, endpoint, options...)
if err != nil {
return
}

err = unmarshal(body, &subscriptions)
return
}

// Subscription returns a subscription by its SKU and subscription ID.
// skuID : The ID of the SKU.
// subscriptionID : The ID of the subscription.
// userID : User ID for which to return the subscription. Required except for OAuth queries.
func (s *Session) Subscription(skuID, subscriptionID, userID string, options ...RequestOption) (subscription *Subscription, err error) {
endpoint := EndpointSubscription(skuID, subscriptionID)

queryParams := url.Values{}
if userID != "" {
// Unlike stated in the documentation, the user_id parameter is required here.
queryParams.Set("user_id", userID)
}

body, err := s.RequestWithBucketID("GET", endpoint+"?"+queryParams.Encode(), nil, endpoint, options...)
if err != nil {
return
}

err = unmarshal(body, &subscription)
return
}
Loading
Loading