Skip to content

Commit

Permalink
Add Entitlements / SKUs (#1552)
Browse files Browse the repository at this point in the history
Pull Request: #1552

Copied Message from Commits:

* add entitlement struct

* add entitlements field to interaction struct

* add event handler for entitlements

* add description to Entitlement struct & type

* properly format changes in entitlement and events

* format entitlement with gofmt

* move entitlement to structs

* add subscription id to entitlement struct and add omitempty

* add endpoints

* add comments to EntitlementTest and EntitlementOwnerType

* add sku struct

* add skus

* fix typo

* rename skuid in entitlement struct

* add comments to SKUFlags

* change entitlement timestamps to time.Time

* rename skuid in entitlement-test struct

* add documentation

* remove undocumented subscriptionID field in entitlement struct

* add documentation

* update documentation

* change order of EntitlementType values

* move optional entitlement filter options into a separate struct

* add premium button component

* add subscriptions

* optimize optional fields in entitlement struct

* Add documentation

* typo

* remove unneccessary pointers
  • Loading branch information
jhoffi authored Dec 8, 2024
1 parent 247b6f7 commit 33ffff2
Show file tree
Hide file tree
Showing 7 changed files with 446 additions and 0 deletions.
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 @@ -3570,3 +3570,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

0 comments on commit 33ffff2

Please sign in to comment.