Skip to content
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

## [1.2.0] - 2026-02-10 ([#59](https://github.com/Its-donkey/kappopher/pull/59))

### Added
- `GetCharityCampaignParams` struct with pagination support for `GetCharityCampaign`
- Custom `Pagination.UnmarshalJSON` to handle Twitch endpoints that return pagination as a string instead of an object (e.g. Get Extension Live Channels)

### Changed
- **BREAKING:** Renamed `GetCustomRewards` → `GetCustomReward` (aligns with Twitch API reference)
- **BREAKING:** Renamed `GetCustomRewardRedemptions` → `GetCustomRewardRedemption` (aligns with Twitch API reference)
- **BREAKING:** Renamed `GetCharityDonations` → `GetCharityCampaignDonations` (aligns with Twitch API reference)
- **BREAKING:** Renamed `AddSuspiciousUserStatus` → `AddSuspiciousStatusToChatUser` (aligns with Twitch API reference)
- **BREAKING:** Renamed `RemoveSuspiciousUserStatus` → `RemoveSuspiciousStatusFromChatUser` (aligns with Twitch API reference)
- **BREAKING:** `GetCharityCampaign` now takes `*GetCharityCampaignParams` instead of a bare `string`, and returns `*Response[CharityCampaign]` instead of `*CharityCampaign`
- All params types renamed to match their function names (`GetCustomRewardsParams` → `GetCustomRewardParams`, etc.)

### Fixed
- Pagination deserialization for endpoints where Twitch returns `"pagination": ""` instead of `{"cursor": "..."}`

## [1.1.1] - 2026-02-06 ([#56](https://github.com/Its-donkey/kappopher/pull/56))

### Added
Expand Down
18 changes: 9 additions & 9 deletions docs/channel-points.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ title: Channel Points API
description: Manage custom Channel Points rewards and redemptions.
---

## GetCustomRewards
## GetCustomReward

Get custom Channel Points rewards for a broadcaster.

**Requires:** `channel:read:redemptions` or `channel:manage:redemptions`

```go
// Get all rewards
resp, err := client.GetCustomRewards(ctx, &helix.GetCustomRewardsParams{
resp, err := client.GetCustomReward(ctx, &helix.GetCustomRewardParams{
BroadcasterID: "12345",
})

// Get specific rewards by ID (max 50)
resp, err = client.GetCustomRewards(ctx, &helix.GetCustomRewardsParams{
resp, err = client.GetCustomReward(ctx, &helix.GetCustomRewardParams{
BroadcasterID: "12345",
IDs: []string{"reward-id-1", "reward-id-2"},
})

// Get only manageable rewards
resp, err = client.GetCustomRewards(ctx, &helix.GetCustomRewardsParams{
resp, err = client.GetCustomReward(ctx, &helix.GetCustomRewardParams{
BroadcasterID: "12345",
OnlyManageableRewards: true,
})
Expand Down Expand Up @@ -225,29 +225,29 @@ err := client.DeleteCustomReward(ctx, &helix.DeleteCustomRewardParams{

**Note:** This endpoint returns no data on success (HTTP 204 No Content).

## GetCustomRewardRedemptions
## GetCustomRewardRedemption

Get redemptions for a custom Channel Points reward.

**Requires:** `channel:read:redemptions` or `channel:manage:redemptions`

```go
// Get all unfulfilled redemptions for a reward
resp, err := client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedemptionsParams{
resp, err := client.GetCustomRewardRedemption(ctx, &helix.GetCustomRewardRedemptionParams{
BroadcasterID: "12345",
RewardID: "reward-id",
Status: "UNFULFILLED",
})

// Get specific redemptions by ID
resp, err = client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedemptionsParams{
resp, err = client.GetCustomRewardRedemption(ctx, &helix.GetCustomRewardRedemptionParams{
BroadcasterID: "12345",
RewardID: "reward-id",
IDs: []string{"redemption-id-1", "redemption-id-2"},
})

// Get fulfilled redemptions sorted by newest first
resp, err = client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedemptionsParams{
resp, err = client.GetCustomRewardRedemption(ctx, &helix.GetCustomRewardRedemptionParams{
BroadcasterID: "12345",
RewardID: "reward-id",
Status: "FULFILLED",
Expand All @@ -258,7 +258,7 @@ resp, err = client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedempt
})

// Get canceled redemptions sorted by oldest first
resp, err = client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedemptionsParams{
resp, err = client.GetCustomRewardRedemption(ctx, &helix.GetCustomRewardRedemptionParams{
BroadcasterID: "12345",
RewardID: "reward-id",
Status: "CANCELED",
Expand Down
24 changes: 14 additions & 10 deletions docs/charity.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ for _, campaign := range resp.Data {
}
```

**Parameters:**
- `BroadcasterID` (string, required): The ID of the broadcaster who is running the charity campaign
- Pagination parameters (`First`, `After`)

**Sample Response:**
```json
{
Expand Down Expand Up @@ -54,31 +58,31 @@ for _, campaign := range resp.Data {
}
```

## GetCharityDonations
## GetCharityCampaignDonations

Get the list of donations that users have made to the broadcaster's charity campaign.

**Requires:** channel:read:charity scope

```go
resp, err := client.GetCharityDonations(ctx, &helix.GetCharityDonationsParams{
BroadcasterID: "12345",
First: 20,
resp, err := client.GetCharityCampaignDonations(ctx, &helix.GetCharityCampaignDonationsParams{
BroadcasterID: "12345",
PaginationParams: &helix.PaginationParams{First: 20},
})
if err != nil {
log.Fatal(err)
}
for _, donation := range resp.Data {
fmt.Printf("%s donated %s %s (Campaign: %s)\n",
fmt.Printf("%s donated %d %s (Campaign: %s)\n",
donation.UserName, donation.Amount.Value,
donation.Amount.DecimalPlaces, donation.CampaignID)
donation.Amount.Currency, donation.CampaignID)
}

// Paginate through more results
if resp.Pagination.Cursor != "" {
resp, err = client.GetCharityDonations(ctx, &helix.GetCharityDonationsParams{
BroadcasterID: "12345",
After: resp.Pagination.Cursor,
if resp.Pagination != nil && resp.Pagination.Cursor != "" {
resp, err = client.GetCharityCampaignDonations(ctx, &helix.GetCharityCampaignDonationsParams{
BroadcasterID: "12345",
PaginationParams: &helix.PaginationParams{After: resp.Pagination.Cursor},
})
}
```
Expand Down
8 changes: 5 additions & 3 deletions docs/examples/analytics-charity.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ func main() {
broadcasterID := "12345"

// Get current charity campaign
campaign, err := client.GetCharityCampaign(ctx, broadcasterID)
campaign, err := client.GetCharityCampaign(ctx, &helix.GetCharityCampaignParams{
BroadcasterID: broadcasterID,
})
if err != nil {
log.Fatal(err)
}
Expand All @@ -193,8 +195,8 @@ func main() {

// Get charity donations
donations, err := client.GetCharityCampaignDonations(ctx, &helix.GetCharityCampaignDonationsParams{
BroadcasterID: broadcasterID,
First: 20,
BroadcasterID: broadcasterID,
PaginationParams: &helix.PaginationParams{First: 20},
})
if err != nil {
log.Fatal(err)
Expand Down
10 changes: 5 additions & 5 deletions docs/examples/channel-points.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func boolPtr(b bool) *bool { return &b }

```go
// Get all custom rewards
rewards, err := client.GetCustomRewards(ctx, &helix.GetCustomRewardsParams{
rewards, err := client.GetCustomReward(ctx, &helix.GetCustomRewardParams{
BroadcasterID: broadcasterID,
})
if err != nil {
Expand All @@ -110,13 +110,13 @@ for _, reward := range rewards.Data {
}

// Get specific rewards by ID
specificRewards, err := client.GetCustomRewards(ctx, &helix.GetCustomRewardsParams{
specificRewards, err := client.GetCustomReward(ctx, &helix.GetCustomRewardParams{
BroadcasterID: broadcasterID,
IDs: []string{"reward-id-1", "reward-id-2"},
})

// Get only manageable rewards (created by your app)
manageableRewards, err := client.GetCustomRewards(ctx, &helix.GetCustomRewardsParams{
manageableRewards, err := client.GetCustomReward(ctx, &helix.GetCustomRewardParams{
BroadcasterID: broadcasterID,
OnlyManageableRewards: true,
})
Expand Down Expand Up @@ -162,7 +162,7 @@ if err != nil {

```go
// Get unfulfilled redemptions
redemptions, err := client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedemptionsParams{
redemptions, err := client.GetCustomRewardRedemption(ctx, &helix.GetCustomRewardRedemptionParams{
BroadcasterID: broadcasterID,
RewardID: "reward-id",
Status: "UNFULFILLED",
Expand All @@ -180,7 +180,7 @@ for _, redemption := range redemptions.Data {
}

// Get redemptions with pagination
allRedemptions, err := client.GetCustomRewardRedemptions(ctx, &helix.GetCustomRewardRedemptionsParams{
allRedemptions, err := client.GetCustomRewardRedemption(ctx, &helix.GetCustomRewardRedemptionParams{
BroadcasterID: broadcasterID,
RewardID: "reward-id",
Status: "UNFULFILLED",
Expand Down
6 changes: 3 additions & 3 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ for _, channel := range resp.Data {
}
```

**Note:** This endpoint returns `"pagination": ""` (a string) instead of the usual `{"cursor": "..."}` object. The library handles this automatically.

**Sample Response:**
```json
{
Expand All @@ -149,9 +151,7 @@ for _, channel := range resp.Data {
"title": "Testing Extensions with Chat"
}
],
"pagination": {
"cursor": "eyJiIjpudWxsLCJhIjp7Ik9mZnNldCI6MjB9fQ"
}
"pagination": ""
}
```

Expand Down
10 changes: 5 additions & 5 deletions docs/moderation.md
Original file line number Diff line number Diff line change
Expand Up @@ -785,23 +785,23 @@ for _, channel := range resp.Data {
}
```

## AddSuspiciousUserStatus
## AddSuspiciousStatusToChatUser

Add a suspicious status to a chat user. Suspicious users can be marked as "restricted" (cannot chat) or "monitored" (messages are flagged for review).

**Requires:** `moderator:manage:suspicious_users`

```go
// Mark a user as restricted (cannot send messages)
err := client.AddSuspiciousUserStatus(ctx, &helix.AddSuspiciousUserStatusParams{
err := client.AddSuspiciousStatusToChatUser(ctx, &helix.AddSuspiciousStatusToChatUserParams{
BroadcasterID: "12345",
ModeratorID: "67890",
UserID: "11111",
Status: helix.SuspiciousUserStatusRestricted,
})

// Mark a user as monitored (messages flagged for review)
err = client.AddSuspiciousUserStatus(ctx, &helix.AddSuspiciousUserStatusParams{
err = client.AddSuspiciousStatusToChatUser(ctx, &helix.AddSuspiciousStatusToChatUserParams{
BroadcasterID: "12345",
ModeratorID: "67890",
UserID: "22222",
Expand All @@ -819,14 +819,14 @@ if err != nil {

**Response:** This endpoint returns 204 No Content on success.

## RemoveSuspiciousUserStatus
## RemoveSuspiciousStatusFromChatUser

Remove a suspicious status from a chat user, allowing them to chat normally again.

**Requires:** `moderator:manage:suspicious_users`

```go
err := client.RemoveSuspiciousUserStatus(ctx, &helix.RemoveSuspiciousUserStatusParams{
err := client.RemoveSuspiciousStatusFromChatUser(ctx, &helix.RemoveSuspiciousStatusFromChatUserParams{
BroadcasterID: "12345",
ModeratorID: "67890",
UserID: "11111",
Expand Down
16 changes: 8 additions & 8 deletions helix/channel_points.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ type GlobalCooldown struct {
GlobalCooldownSeconds int `json:"global_cooldown_seconds"`
}

// GetCustomRewardsParams contains parameters for GetCustomRewards.
type GetCustomRewardsParams struct {
// GetCustomRewardParams contains parameters for GetCustomReward.
type GetCustomRewardParams struct {
BroadcasterID string
IDs []string // Reward IDs (max 50)
OnlyManageableRewards bool
}

// GetCustomRewards gets custom rewards for a channel.
// GetCustomReward gets custom rewards for a channel.
// Requires: channel:read:redemptions or channel:manage:redemptions scope.
func (c *Client) GetCustomRewards(ctx context.Context, params *GetCustomRewardsParams) (*Response[CustomReward], error) {
func (c *Client) GetCustomReward(ctx context.Context, params *GetCustomRewardParams) (*Response[CustomReward], error) {
q := url.Values{}
q.Set("broadcaster_id", params.BroadcasterID)
for _, id := range params.IDs {
Expand Down Expand Up @@ -182,8 +182,8 @@ type CustomRewardRedemption struct {
} `json:"reward"`
}

// GetCustomRewardRedemptionsParams contains parameters for GetCustomRewardRedemptions.
type GetCustomRewardRedemptionsParams struct {
// GetCustomRewardRedemptionParams contains parameters for GetCustomRewardRedemption.
type GetCustomRewardRedemptionParams struct {
BroadcasterID string
RewardID string
Status string // CANCELED, FULFILLED, UNFULFILLED
Expand All @@ -192,9 +192,9 @@ type GetCustomRewardRedemptionsParams struct {
*PaginationParams
}

// GetCustomRewardRedemptions gets redemptions for a custom reward.
// GetCustomRewardRedemption gets redemptions for a custom reward.
// Requires: channel:read:redemptions or channel:manage:redemptions scope.
func (c *Client) GetCustomRewardRedemptions(ctx context.Context, params *GetCustomRewardRedemptionsParams) (*Response[CustomRewardRedemption], error) {
func (c *Client) GetCustomRewardRedemption(ctx context.Context, params *GetCustomRewardRedemptionParams) (*Response[CustomRewardRedemption], error) {
q := url.Values{}
q.Set("broadcaster_id", params.BroadcasterID)
q.Set("reward_id", params.RewardID)
Expand Down
Loading