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) #1

Merged
merged 1 commit into from
Dec 29, 2024
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
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