Initial commit
This commit is contained in:
commit
422102ccee
|
@ -0,0 +1,7 @@
|
||||||
|
package twitch
|
||||||
|
|
||||||
|
import "go.fifitido.net/twitch/api"
|
||||||
|
|
||||||
|
func NewAPI() *api.API {
|
||||||
|
return api.New()
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package ads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ads struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client *http.Client, baseUrl *url.URL) *Ads {
|
||||||
|
return &Ads{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package ads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetAdScheduleResponse struct {
|
||||||
|
// A list that contains information related to the channel’s ad schedule.
|
||||||
|
Data []GetAdScheduleData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAdScheduleData struct {
|
||||||
|
// The number of snoozes available for the broadcaster.
|
||||||
|
SnoozeCount int `json:"snooze_count"`
|
||||||
|
|
||||||
|
// The UTC timestamp when the broadcaster will gain an additional snooze, in RFC3339 format.
|
||||||
|
SnoozeRefreshAt time.Time `json:"snooze_refresh_at"`
|
||||||
|
|
||||||
|
// The UTC timestamp of the broadcaster’s next scheduled ad, in RFC3339 format. Empty if the channel has no ad scheduled or is not live.
|
||||||
|
NextAdAt time.Time `json:"next_ad_at"`
|
||||||
|
|
||||||
|
// The length in seconds of the scheduled upcoming ad break.
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
|
||||||
|
// The UTC timestamp of the broadcaster’s last ad-break, in RFC3339 format. Empty if the channel has not run an ad or is not live.
|
||||||
|
LastAdAt time.Time `json:"last_ad_at"`
|
||||||
|
|
||||||
|
// The amount of pre-roll free time remaining for the channel in seconds. Returns 0 if they are currently not pre-roll free.
|
||||||
|
PrerollFreeTime int `json:"preroll_free_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// This endpoint returns ad schedule related information, including snooze, when the last ad was run, when the next ad is scheduled,
|
||||||
|
// and if the channel is currently in pre-roll free time. Note that a new ad cannot be run until 8 minutes after running a previous ad.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:read:ads scope.
|
||||||
|
// The user_id in the user access token must match the broadcaster_id.
|
||||||
|
func (e *Ads) GetAdSchedule(broadcasterID string) (*GetAdScheduleResponse, error) {
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "channels/ads", RawQuery: "broadcaster_id=" + broadcasterID})
|
||||||
|
|
||||||
|
resp, err := e.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetAdScheduleResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package ads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnoozeNextAdResponse struct {
|
||||||
|
// A list that contains information about the channel’s snoozes and next upcoming ad after successfully snoozing.
|
||||||
|
Data []SnoozeNextAdData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnoozeNextAdData struct {
|
||||||
|
// The number of snoozes available for the broadcaster.
|
||||||
|
SnoozeCount int `json:"snooze_count"`
|
||||||
|
|
||||||
|
// The UTC timestamp when the broadcaster will gain an additional snooze, in RFC3339 format.
|
||||||
|
SnoozeRefreshAt time.Time `json:"snooze_refresh_at"`
|
||||||
|
|
||||||
|
// The UTC timestamp of the broadcaster’s next scheduled ad, in RFC3339 format.
|
||||||
|
NextAdAt time.Time `json:"next_ad_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If available, pushes back the timestamp of the upcoming automatic mid-roll ad by 5 minutes.
|
||||||
|
// This endpoint duplicates the snooze functionality in the creator dashboard’s Ads Manager.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:manage:ads scope.
|
||||||
|
// The user_id in the user access token must match the broadcaster_id.
|
||||||
|
func (e *Ads) SnoozeNextAd(broadcasterID string) (*SnoozeNextAdResponse, error) {
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "channels/ads/schedule/snooze", RawQuery: "broadcaster_id=" + broadcasterID})
|
||||||
|
|
||||||
|
resp, err := e.client.Post(endpoint.String(), "application/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data SnoozeNextAdResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package ads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StartCommercialRequest struct {
|
||||||
|
// The ID of the partner or affiliate broadcaster that wants to run the commercial. This ID must match the user ID found in the OAuth token.
|
||||||
|
BroadcasterID string `json:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The length of the commercial to run, in seconds.
|
||||||
|
// Twitch tries to serve a commercial that’s the requested length, but it may be shorter or longer.
|
||||||
|
// The maximum length you should request is 180 seconds.
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartCommercialResponse struct {
|
||||||
|
// An array that contains a single object with the status of your start commercial request.
|
||||||
|
Data []StartCommercialData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartCommercialData struct {
|
||||||
|
// The length of the commercial you requested. If you request a commercial that’s longer than 180 seconds, the API uses 180 seconds.
|
||||||
|
Length int `json:"length"`
|
||||||
|
|
||||||
|
// A message that indicates whether Twitch was able to serve an ad.
|
||||||
|
Message string `json:"message"`
|
||||||
|
|
||||||
|
// The number of seconds you must wait before running another commercial.
|
||||||
|
RetryAfter int `json:"retry_after"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts a commercial on the specified channel.
|
||||||
|
//
|
||||||
|
// NOTE: Only partners and affiliates may run commercials and they must be streaming live at the time.
|
||||||
|
//
|
||||||
|
// NOTE: Only the broadcaster may start a commercial; the broadcaster’s editors and moderators may not start commercials on behalf of the broadcaster.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:edit:commercial scope.
|
||||||
|
func (e *Ads) StartCommercial(req *StartCommercialRequest) (*StartCommercialResponse, error) {
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "channels/commercial"})
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(w).Encode(req); err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
} else {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := e.client.Post(endpoint.String(), "application/json", r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data StartCommercialResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package analytics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Analytics struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client *http.Client, baseUrl *url.URL) *Analytics {
|
||||||
|
return &Analytics{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package analytics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetExtensionAnalyticsParams struct {
|
||||||
|
// The extension's client ID. If specified, the response contains a report for the specified extension.
|
||||||
|
// If not specified, the response includes a report for each extension that the authenticated user owns.
|
||||||
|
ExtensionID *string `url:"extension_id,omitempty"`
|
||||||
|
|
||||||
|
// The type of analytics report to get. Possible values are:
|
||||||
|
//
|
||||||
|
// - overview_v2
|
||||||
|
Type *string `url:"type,omitempty"`
|
||||||
|
|
||||||
|
// The reporting window's start date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-22T00:00:00Z).
|
||||||
|
//
|
||||||
|
// The start date must be on or after January 31, 2018. If you specify an earlier date, the API ignores it and uses January 31, 2018.
|
||||||
|
// If you specify a start date, you must specify an end date. If you don't specify a start and end date,
|
||||||
|
// the report includes all available data since January 31, 2018.
|
||||||
|
//
|
||||||
|
// The report contains one row of data for each day in the reporting window.
|
||||||
|
StartedAt *time.Time `url:"started_at,omitempty"`
|
||||||
|
|
||||||
|
// The reporting window's end date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-27T00:00:00Z).
|
||||||
|
// The report is inclusive of the end date.
|
||||||
|
//
|
||||||
|
// Specify an end date only if you provide a start date. Because it can take up to two days for the data to be available,
|
||||||
|
// you must specify an end date that's earlier than today minus one to two days.
|
||||||
|
// If not, the API ignores your end date and uses an end date that is today minus one to two days.
|
||||||
|
EndedAt *time.Time `url:"ended_at,omitempty"`
|
||||||
|
|
||||||
|
// The maximum number of report URLs to return per page in the response.
|
||||||
|
// The minimum page size is 1 URL per page and the maximum is 100 URLs per page. The default is 20.
|
||||||
|
//
|
||||||
|
// NOTE: While you may specify a maximum value of 100, the response will contain at most 20 URLs per page.
|
||||||
|
First *int `url:"first,omitempty"`
|
||||||
|
|
||||||
|
// The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value.
|
||||||
|
// Read More: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
//
|
||||||
|
// This parameter is ignored if the extension_id parameter is set.
|
||||||
|
After *types.Cursor `url:"after,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetExtensionAnalyticsResponse struct {
|
||||||
|
// A list of reports. The reports are returned in no particular order; however, the data within each report is in ascending order by date (newest first).
|
||||||
|
// The report contains one row of data per day of the reporting window; the report contains rows for only those days that the extension was used.
|
||||||
|
// The array is empty if there are no reports.
|
||||||
|
Data []ExtensionAnalyticsReport `json:"data"`
|
||||||
|
|
||||||
|
// Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.
|
||||||
|
// Read More: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
Pagination types.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtensionAnalyticsReport struct {
|
||||||
|
// An ID that identifies the extension that the report was generated for.
|
||||||
|
ExtensionID string `json:"extension_id"`
|
||||||
|
|
||||||
|
// The URL that you use to download the report. The URL is valid for 5 minutes.
|
||||||
|
URL string `json:"URL"`
|
||||||
|
|
||||||
|
// The type of report.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// The reporting window’s start and end dates, in RFC3339 format.
|
||||||
|
DateRange types.DateRange `json:"date_range"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets an analytics report for one or more extensions. The response contains the URLs used to download the reports (CSV files).
|
||||||
|
// Learn More: https://dev.twitch.tv/docs/insights
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the analytics:read:extensions scope.
|
||||||
|
func (e *Analytics) GetExtensionAnalytics(params GetExtensionAnalyticsParams) (*GetExtensionAnalyticsResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "analytics/extensions", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := e.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetExtensionAnalyticsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package analytics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetGameAnalyticsParams struct {
|
||||||
|
// The game’s client ID. If specified, the response contains a report for the specified game.
|
||||||
|
// If not specified, the response includes a report for each of the authenticated user’s games.
|
||||||
|
GameID *string `url:"game_id,omitempty"`
|
||||||
|
|
||||||
|
// The type of analytics report to get. Possible values are:
|
||||||
|
//
|
||||||
|
// - overview_v2
|
||||||
|
Type *string `url:"type,omitempty"`
|
||||||
|
|
||||||
|
// The reporting window’s start date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-22T00:00:00Z).
|
||||||
|
// If you specify a start date, you must specify an end date.
|
||||||
|
//
|
||||||
|
// The start date must be within one year of today’s date. If you specify an earlier date,
|
||||||
|
// the API ignores it and uses a date that’s one year prior to today’s date. If you don’t specify a start and end date,
|
||||||
|
// the report includes all available data for the last 365 days from today.
|
||||||
|
//
|
||||||
|
// The report contains one row of data for each day in the reporting window.
|
||||||
|
StartedAt *time.Time `url:"started_at,omitempty"`
|
||||||
|
|
||||||
|
// The reporting window’s end date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-22T00:00:00Z).
|
||||||
|
// The report is inclusive of the end date.
|
||||||
|
//
|
||||||
|
// Specify an end date only if you provide a start date. Because it can take up to two days for the data to be available,
|
||||||
|
// you must specify an end date that’s earlier than today minus one to two days.
|
||||||
|
// If not, the API ignores your end date and uses an end date that is today minus one to two days.
|
||||||
|
EndedAt *time.Time `url:"ended_at,omitempty"`
|
||||||
|
|
||||||
|
// The maximum number of report URLs to return per page in the response.
|
||||||
|
// The minimum page size is 1 URL per page and the maximum is 100 URLs per page.The default is 20.
|
||||||
|
//
|
||||||
|
// NOTE: While you may specify a maximum value of 100, the response will contain at most 20 URLs per page.
|
||||||
|
First *int `url:"first,omitempty"`
|
||||||
|
|
||||||
|
// The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value.
|
||||||
|
// Read More: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
//
|
||||||
|
// This parameter is ignored if game_id parameter is set.
|
||||||
|
After *types.Cursor `url:"after,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetGameAnalyticsResponse struct {
|
||||||
|
// A list of reports. The reports are returned in no particular order; however, the data within each report is in ascending order by date (newest first).
|
||||||
|
// The report contains one row of data per day of the reporting window; the report contains rows for only those days that the game was used.
|
||||||
|
// A report is available only if the game was broadcast for at least 5 hours over the reporting period. The array is empty if there are no reports.
|
||||||
|
Data []GameAnalyticsReport `json:"data"`
|
||||||
|
|
||||||
|
// Contains the information used to page through the list of results.
|
||||||
|
// The object is empty if there are no more pages left to page through.
|
||||||
|
Pagination types.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameAnalyticsReport struct {
|
||||||
|
// An ID that identifies the game that the report was generated for.
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
|
||||||
|
// The URL that you use to download the report. The URL is valid for 5 minutes.
|
||||||
|
URL string `json:"URL"`
|
||||||
|
|
||||||
|
// The type of report.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// The reporting window’s start and end dates, in RFC3339 format.
|
||||||
|
DateRange types.DateRange `json:"date_range"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets an analytics report for one or more games. The response contains the URLs used to download the reports (CSV files).
|
||||||
|
// Learn more: https://dev.twitch.tv/docs/insights
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the analytics:read:games scope.
|
||||||
|
func (e *Analytics) GetGameAnalytics(params GetGameAnalyticsParams) (*GetGameAnalyticsResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "analytics/games", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := e.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetGameAnalyticsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"go.fifitido.net/twitch/api/ads"
|
||||||
|
"go.fifitido.net/twitch/api/analytics"
|
||||||
|
"go.fifitido.net/twitch/api/bits"
|
||||||
|
"go.fifitido.net/twitch/api/channelpoints"
|
||||||
|
"go.fifitido.net/twitch/api/channels"
|
||||||
|
"go.fifitido.net/twitch/api/eventsub"
|
||||||
|
)
|
||||||
|
|
||||||
|
const HelixBaseUrl = "https://api.twitch.tv/helix"
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
|
||||||
|
Ads *ads.Ads
|
||||||
|
Analytics *analytics.Analytics
|
||||||
|
Bits *bits.Bits
|
||||||
|
Channels *channels.Channels
|
||||||
|
ChannelPoints *channelpoints.ChannelPoints
|
||||||
|
EventSub *eventsub.EventSub
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *API {
|
||||||
|
client := &http.Client{}
|
||||||
|
baseUrl, _ := url.Parse(HelixBaseUrl)
|
||||||
|
|
||||||
|
return &API{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
|
||||||
|
Ads: ads.New(client, baseUrl),
|
||||||
|
Analytics: analytics.New(client, baseUrl),
|
||||||
|
Bits: bits.New(client, baseUrl),
|
||||||
|
Channels: channels.New(client, baseUrl),
|
||||||
|
ChannelPoints: channelpoints.New(client, baseUrl),
|
||||||
|
EventSub: eventsub.New(client, baseUrl),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bits struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client *http.Client, baseUrl *url.URL) *Bits {
|
||||||
|
return &Bits{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Period string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Day Period = "day"
|
||||||
|
Week Period = "week"
|
||||||
|
Month Period = "month"
|
||||||
|
Year Period = "year"
|
||||||
|
All Period = "all"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheermoteType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GlobalFirstParty CheermoteType = "global_first_party"
|
||||||
|
GlobalThirdParty CheermoteType = "global_third_party"
|
||||||
|
ChannelCustom CheermoteType = "channel_custom"
|
||||||
|
DisplayOnly CheermoteType = "display_only"
|
||||||
|
Sponsored CheermoteType = "sponsored"
|
||||||
|
)
|
|
@ -0,0 +1,86 @@
|
||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetBitsLeaderboardParams struct {
|
||||||
|
// The number of results to return. The minimum count is 1 and the maximum is 100. The default is 10.
|
||||||
|
Count *int `url:"count,omitempty"`
|
||||||
|
|
||||||
|
// The time period over which data is aggregated (uses the PST time zone).
|
||||||
|
Period *Period `url:"period,omitempty"`
|
||||||
|
|
||||||
|
// The start date, in RFC3339 format, used for determining the aggregation period. Specify this parameter only if you specify the period query parameter.
|
||||||
|
// The start date is ignored if period is all.
|
||||||
|
//
|
||||||
|
// Note that the date is converted to PST before being used, so if you set the start time to 2022-01-01T00:00:00.0Z and period to month,
|
||||||
|
// the actual reporting period is December 2021, not January 2022. If you want the reporting period to be January 2022,
|
||||||
|
// you must set the start time to 2022-01-01T08:00:00.0Z or 2022-01-01T00:00:00.0-08:00.
|
||||||
|
//
|
||||||
|
// If your start date uses the ‘+’ offset operator (for example, 2022-01-01T00:00:00.0+05:00), you must URL encode the start date.
|
||||||
|
StartedAt *time.Time `url:"started_at,omitempty"`
|
||||||
|
|
||||||
|
// An ID that identifies a user that cheered bits in the channel.
|
||||||
|
// If count is greater than 1, the response may include users ranked above and below the specified user.
|
||||||
|
// To get the leaderboard’s top leaders, don’t specify a user ID.
|
||||||
|
UserID *string `url:"user_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetBitsLeaderboardResponse struct {
|
||||||
|
// A list of leaderboard leaders. The leaders are returned in rank order by how much they’ve cheered.
|
||||||
|
// The array is empty if nobody has cheered bits.
|
||||||
|
Data []LeaderboardEntry `json:"data"`
|
||||||
|
|
||||||
|
// The reporting window’s start and end dates, in RFC3339 format. The dates are calculated by using the started_at and period query parameters.
|
||||||
|
// If you don’t specify the started_at query parameter, the fields contain empty strings.
|
||||||
|
DateRange types.DateRange `json:"date_range"`
|
||||||
|
|
||||||
|
// The number of ranked users in data.
|
||||||
|
// This is the value in the count query parameter or the total number of entries on the leaderboard, whichever is less.
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardEntry struct {
|
||||||
|
// An ID that identifies a user on the leaderboard.
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
|
||||||
|
// The user’s login name.
|
||||||
|
UserLogin string `json:"user_login"`
|
||||||
|
|
||||||
|
// The user’s display name.
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
|
||||||
|
// The user’s position on the leaderboard.
|
||||||
|
Rank int `json:"rank"`
|
||||||
|
|
||||||
|
// The number of Bits the user has cheered.
|
||||||
|
Score int `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the Bits leaderboard for the authenticated broadcaster.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the bits:read scope.
|
||||||
|
func (b *Bits) GetBitsLeaderboard(params *GetBitsLeaderboardParams) (*GetBitsLeaderboardResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := b.baseUrl.ResolveReference(&url.URL{Path: "bits/leaderboard", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := b.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetBitsLeaderboardResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCheermotesResponse struct {
|
||||||
|
// The list of Cheermotes. The list is in ascending order by the order field’s value.
|
||||||
|
Data []Cheermote `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cheermote struct {
|
||||||
|
// The name portion of the Cheermote string that you use in chat to cheer Bits.
|
||||||
|
// The full Cheermote string is the concatenation of {prefix} + {number of Bits}.
|
||||||
|
// For example, if the prefix is “Cheer” and you want to cheer 100 Bits, the full Cheermote string is Cheer100.
|
||||||
|
// When the Cheermote string is entered in chat, Twitch converts it to the image associated with the Bits tier that was cheered.
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
|
||||||
|
// A list of tier levels that the Cheermote supports.
|
||||||
|
// Each tier identifies the range of Bits that you can cheer at that tier level and an image that graphically identifies the tier level.
|
||||||
|
Tiers []CheermoteTier `json:"tiers"`
|
||||||
|
|
||||||
|
// The type of Cheermote.
|
||||||
|
Type CheermoteType `json:"type"`
|
||||||
|
|
||||||
|
// The order that the Cheermotes are shown in the Bits card. The numbers may not be consecutive. For example, the numbers may jump from 1 to 7 to 13.
|
||||||
|
// The order numbers are unique within a Cheermote type (for example, global_first_party) but may not be unique amongst all Cheermotes in the response.
|
||||||
|
Order int `json:"order"`
|
||||||
|
|
||||||
|
// The date and time, in RFC3339 format, when this Cheermote was last updated.
|
||||||
|
LastUpdated time.Time `json:"last_updated"`
|
||||||
|
|
||||||
|
// A Boolean value that indicates whether this Cheermote provides a charitable contribution match during charity campaigns.
|
||||||
|
IsCharitable bool `json:"is_charitable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheermoteTier struct {
|
||||||
|
// The minimum number of Bits that you must cheer at this tier level.
|
||||||
|
// The maximum number of Bits that you can cheer at this level is determined by the required minimum Bits of the next tier level minus 1.
|
||||||
|
// For example, if min_bits is 1 and min_bits for the next tier is 100, the Bits range for this tier level is 1 through 99.
|
||||||
|
// The minimum Bits value of the last tier is the maximum number of Bits you can cheer using this Cheermote. For example, 10000.
|
||||||
|
MinBits int `json:"min_bits"`
|
||||||
|
|
||||||
|
// The tier level. Possible tiers are:
|
||||||
|
//
|
||||||
|
// 1, 100, 500, 1000, 5000, 10000, 100000
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// The hex code of the color associated with this tier level (for example, #979797).
|
||||||
|
Color string `json:"color"`
|
||||||
|
|
||||||
|
// The animated and static image sets for the Cheermote. The dictionary of images is organized by theme, format, and size.
|
||||||
|
// The theme keys are dark and light. Each theme is a dictionary of formats: animated and static.
|
||||||
|
// Each format is a dictionary of sizes: 1, 1.5, 2, 3, and 4. The value of each size contains the URL to the image.
|
||||||
|
Images map[string]CheermoteImage `json:"images"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether users can cheer at this tier level.
|
||||||
|
CanCheer bool `json:"can_cheer"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether this tier level is shown in the Bits card. Is true if this tier level is shown in the Bits card.
|
||||||
|
ShowInBitsCard bool `json:"show_in_bits_card"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheermoteImage struct {
|
||||||
|
Animated *CheermoteImageSizes `json:"animated"`
|
||||||
|
Static *CheermoteImageSizes `json:"static"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheermoteImageSizes struct {
|
||||||
|
One CheermoteImage `json:"1"`
|
||||||
|
One5 CheermoteImage `json:"1.5"`
|
||||||
|
Two CheermoteImage `json:"2"`
|
||||||
|
Three CheermoteImage `json:"3"`
|
||||||
|
Four CheermoteImage `json:"4"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a list of Cheermotes that users can use to cheer Bits in any Bits-enabled channel’s chat room. Cheermotes are animated emotes that viewers can assign Bits to.
|
||||||
|
//
|
||||||
|
// Requires an app access token or user access token.
|
||||||
|
func (b *Bits) GetCheermotes(broadcasterID string) (*GetCheermotesResponse, error) {
|
||||||
|
endpoint := b.baseUrl.ResolveReference(&url.URL{Path: "bits/cheermotes", RawQuery: "broadcaster_id=" + broadcasterID})
|
||||||
|
|
||||||
|
resp, err := b.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetCheermotesResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetExtensionTransactionsParams struct {
|
||||||
|
// The ID of the extension whose list of transactions you want to get.
|
||||||
|
ExtensionID string `url:"extension_id"`
|
||||||
|
|
||||||
|
// A transaction ID used to filter the list of transactions. Specify this parameter for each transaction you want to get.
|
||||||
|
// For example, id=1234&id=5678. You may specify a maximum of 100 IDs.
|
||||||
|
IDs []string `url:"ids,omitempty"`
|
||||||
|
|
||||||
|
// The maximum number of items to return per page in the response.
|
||||||
|
// The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20.
|
||||||
|
First *int `url:"first,omitempty"`
|
||||||
|
|
||||||
|
// The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value.
|
||||||
|
// Read More: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
After *types.Cursor `url:"after,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetExtensionTransactionsResponse struct {
|
||||||
|
// The list of transactions.
|
||||||
|
Data []ExtensionTransaction `json:"data"`
|
||||||
|
|
||||||
|
// Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.
|
||||||
|
// Read More: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
Pagination types.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtensionTransaction struct {
|
||||||
|
// An ID that identifies the transaction.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// The UTC date and time (in RFC3339 format) of the transaction.
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
|
||||||
|
// The ID of the broadcaster that owns the channel where the transaction occurred.
|
||||||
|
BroadcasterID string `json:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The broadcaster’s login name.
|
||||||
|
BroadcasterLogin string `json:"broadcaster_login"`
|
||||||
|
|
||||||
|
// The broadcaster’s display name.
|
||||||
|
BroadcasterName string `json:"broadcaster_name"`
|
||||||
|
|
||||||
|
// The type of transaction. Possible values are:
|
||||||
|
//
|
||||||
|
// BITS_IN_EXTENSION
|
||||||
|
ProductType string `json:"product_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductData struct {
|
||||||
|
// An ID that identifies the digital product.
|
||||||
|
SKU string `json:"sku"`
|
||||||
|
|
||||||
|
// Set to twitch.ext. + <the extension's ID>.
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
|
||||||
|
// Contains details about the digital product’s cost.
|
||||||
|
Cost ProductDataCost `json:"cost"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the product is in development. Is true if the digital product is in development and cannot be exchanged.
|
||||||
|
InDevelopment bool `json:"in_development"`
|
||||||
|
|
||||||
|
// The name of the digital product.
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
|
||||||
|
// This field is always empty since you may purchase only unexpired products.
|
||||||
|
Expiration string `json:"expiration"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the data was broadcast to all instances of the extension. Is true if the data was broadcast to all instances.
|
||||||
|
Broadcast bool `json:"broadcast"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductDataCost struct {
|
||||||
|
// The amount exchanged for the digital product.
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
|
||||||
|
// The type of currency exchanged. Possible values are:
|
||||||
|
//
|
||||||
|
// bits
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets an extension’s list of transactions. A transaction records the exchange of a currency (for example, Bits) for a digital product.
|
||||||
|
//
|
||||||
|
// Requires an app access token.
|
||||||
|
func (b *Bits) GetExtensionTransactions(params *GetExtensionTransactionsParams) (*GetExtensionTransactionsResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := b.baseUrl.ResolveReference(&url.URL{Path: "extensions/transactions", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := b.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetExtensionTransactionsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChannelPoints struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client *http.Client, baseUrl *url.URL) *ChannelPoints {
|
||||||
|
return &ChannelPoints{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateCustomRewardsRequest struct {
|
||||||
|
// The custom reward’s title. The title may contain a maximum of 45 characters and it must be unique amongst all of the broadcaster’s custom rewards.
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// The cost of the reward, in Channel Points.
|
||||||
|
// The minimum is 1 point.
|
||||||
|
Cost int64 `json:"cost"`
|
||||||
|
|
||||||
|
// The prompt shown to the viewer when they redeem the reward.
|
||||||
|
// Specify a prompt if is_user_input_required is true.
|
||||||
|
// The prompt is limited to a maximum of 200 characters.
|
||||||
|
Prompt *string `json:"prompt"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the reward is enabled. Viewers see only enabled rewards.
|
||||||
|
// The default is true.
|
||||||
|
IsEnabled *bool `json:"is_enabled"`
|
||||||
|
|
||||||
|
// The background color to use for the reward. Specify the color using Hex format (for example, #9147FF).
|
||||||
|
BackgroundColor *string `json:"background_color"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the user needs to enter information when redeeming the reward. See the prompt field.
|
||||||
|
// The default is false.
|
||||||
|
IsUserInputRequired *bool `json:"is_user_input_required"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether to limit the maximum number of redemptions allowed per live stream (see the max_per_stream field).
|
||||||
|
// The default is false.
|
||||||
|
IsMaxPerStreamEnabled *bool `json:"is_max_per_stream_enabled"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions allowed per live stream. Applied only if is_max_per_stream_enabled is true.
|
||||||
|
// The minimum value is 1.
|
||||||
|
MaxPerStream *int `json:"max_per_stream"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether to limit the maximum number of redemptions allowed per user per stream (see the max_per_user_per_stream field).
|
||||||
|
// The default is false.
|
||||||
|
IsMaxPerUserPerStreamEnabled *bool `json:"is_max_per_user_per_stream_enabled"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions allowed per user per stream. Applied only if is_max_per_user_per_stream_enabled is true.
|
||||||
|
// The minimum value is 1.
|
||||||
|
MaxPerUserPerStream *int `json:"max_per_user_per_stream"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether to apply a cooldown period between redemptions (see the global_cooldown_seconds field for the duration of the cooldown period).
|
||||||
|
// The default is false.
|
||||||
|
IsGlobalCooldownEnabled *bool `json:"is_global_cooldown_enabled"`
|
||||||
|
|
||||||
|
// The cooldown period, in seconds. Applied only if the is_global_cooldown_enabled field is true.
|
||||||
|
// The minimum value is 1; however, the minimum value is 60 for it to be shown in the Twitch UX.
|
||||||
|
GlobalCooldownSeconds *int `json:"global_cooldown_seconds"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed.
|
||||||
|
// If false, status is set to UNFULFILLED and follows the normal request queue process.
|
||||||
|
// The default is false.
|
||||||
|
ShouldRedemptionsSkipRequestQueue *bool `json:"should_redemptions_skip_request_queue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateCustomRewardsResponse struct {
|
||||||
|
Data []CustomReward `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a Custom Reward in the broadcaster’s channel. The maximum number of custom rewards per channel is 50, which includes both enabled and disabled rewards.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:manage:redemptions scope.
|
||||||
|
func (c *ChannelPoints) CreateCustomRewards(broadcastID string, req *CreateCustomRewardsRequest) (*CreateCustomRewardsResponse, error) {
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channel_points/custom_rewards", RawQuery: "broadcaster_id=" + broadcastID})
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(w).Encode(req); err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
} else {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := c.client.Post(endpoint.String(), "application/json", r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data CreateCustomRewardsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeleteCustomRewardParams struct {
|
||||||
|
// The ID of the broadcaster that created the custom reward. This ID must match the user ID found in the OAuth token.
|
||||||
|
BroadcasterID string `url:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The ID of the custom reward to delete.
|
||||||
|
ID string `url:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes a custom reward that the broadcaster created.
|
||||||
|
//
|
||||||
|
// The app used to create the reward is the only app that may delete it.
|
||||||
|
// If the reward’s redemption status is UNFULFILLED at the time the reward is deleted, its redemption status is marked as FULFILLED.
|
||||||
|
//
|
||||||
|
// / Requires a user access token that includes the channel:manage:redemptions scope.
|
||||||
|
func (c *ChannelPoints) DeleteCustomReward(params *DeleteCustomRewardParams) error {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channel_points/custom_rewards", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := c.client.Do(&http.Request{
|
||||||
|
Method: http.MethodDelete,
|
||||||
|
URL: endpoint,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCustomRewardParams struct {
|
||||||
|
// The ID of the broadcaster whose custom rewards you want to get. This ID must match the user ID found in the OAuth token.
|
||||||
|
BroadcasterID string `url:"broadcaster_id"`
|
||||||
|
|
||||||
|
// A list of IDs to filter the rewards by. To specify more than one ID, include this parameter for each reward you want to get.
|
||||||
|
// For example, id=1234&id=5678. You may specify a maximum of 50 IDs.
|
||||||
|
//
|
||||||
|
// Duplicate IDs are ignored. The response contains only the IDs that were found. If none of the IDs were found, the response is 404 Not Found.
|
||||||
|
IDs []string `url:"id,omitempty"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the response contains only the custom rewards that the app may manage
|
||||||
|
// (the app is identified by the ID in the Client-Id header). Set to true to get only the custom rewards that the app may manage.
|
||||||
|
// The default is false.
|
||||||
|
OnlyManageableRewards *bool `url:"only_manageable_rewards,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCustomRewardResponse struct {
|
||||||
|
// A list of custom rewards. The list is in ascending order by id. If the broadcaster hasn’t created custom rewards, the list is empty.
|
||||||
|
Data []CustomReward `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a list of custom rewards that the specified broadcaster created.
|
||||||
|
//
|
||||||
|
// NOTE: A channel may offer a maximum of 50 rewards, which includes both enabled and disabled rewards.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:read:redemptions or channel:manage:redemptions scope.
|
||||||
|
func (c *ChannelPoints) GetCustomReward(params *GetCustomRewardParams) (*GetCustomRewardResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channel_points/custom_rewards", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := c.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetCustomRewardResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCustomRewardRedemptionParams struct {
|
||||||
|
// The ID of the broadcaster that owns the custom reward. This ID must match the user ID found in the user OAuth token.
|
||||||
|
BroadcasterID string `url:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The ID that identifies the custom reward whose redemptions you want to get.
|
||||||
|
RewardID string `url:"reward_id"`
|
||||||
|
|
||||||
|
// The status of the redemptions to return. The possible case-sensitive values are:
|
||||||
|
//
|
||||||
|
// NOTE: This field is required only if you don’t specify the id query parameter.
|
||||||
|
//
|
||||||
|
// NOTE: Canceled and fulfilled redemptions are returned for only a few days after they’re canceled or fulfilled.
|
||||||
|
Status *RewardRedemptionStatus `url:"status,omitempty"`
|
||||||
|
|
||||||
|
// A list of IDs to filter the redemptions by. To specify more than one ID, include this parameter for each redemption you want to get.
|
||||||
|
// For example, id=1234&id=5678. You may specify a maximum of 50 IDs.
|
||||||
|
//
|
||||||
|
// Duplicate IDs are ignored. The response contains only the IDs that were found. If none of the IDs were found, the response is 404 Not Found.
|
||||||
|
IDs []string `url:"id,omitempty"`
|
||||||
|
|
||||||
|
// The order to sort redemptions by. The default is OLDEST.
|
||||||
|
Sort *types.SortOrder `url:"sort,omitempty"`
|
||||||
|
|
||||||
|
// The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value.
|
||||||
|
// Read more: https://dev.twitch.tv/docs/api/guide/#pagination
|
||||||
|
After *string `url:"after,omitempty"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions to return per page in the response.
|
||||||
|
// The minimum page size is 1 redemption per page and the maximum is 50.
|
||||||
|
// The default is 20.
|
||||||
|
First *int `url:"first,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCustomRewardRedemptionResponse struct {
|
||||||
|
// The list of redemptions for the specified reward. The list is empty if there are no redemptions that match the redemption criteria.
|
||||||
|
Data []CustomRewardRedemption `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a list of redemptions for the specified custom reward. The app used to create the reward is the only app that may get the redemptions.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:read:redemptions or channel:manage:redemptions scope.
|
||||||
|
func (c *ChannelPoints) GetCustomRewardRedemption(params *GetCustomRewardRedemptionParams) (*GetCustomRewardRedemptionResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channel_points/custom_rewards/redemptions", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := c.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetCustomRewardRedemptionResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type CustomReward struct {
|
||||||
|
// The ID that uniquely identifies the broadcaster.
|
||||||
|
BroadcasterID string `json:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The broadcaster’s login name.
|
||||||
|
BroadcasterLogin string `json:"broadcaster_login"`
|
||||||
|
|
||||||
|
// The broadcaster’s display name.
|
||||||
|
BroadcasterName string `json:"broadcaster_name"`
|
||||||
|
|
||||||
|
// The ID that uniquely identifies this custom reward.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// The title of the custom reward.
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// The prompt shown to the viewer when they redeem the reward if user input is required (see the is_user_input_required field).
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
|
||||||
|
// The cost of the reward in Channel Points.
|
||||||
|
Cost int `json:"cost"`
|
||||||
|
|
||||||
|
// A set of custom images for the reward.
|
||||||
|
// This field is set to null if the broadcaster didn’t upload images.
|
||||||
|
CustomImages *CustomRewardImage `json:"custom_images"`
|
||||||
|
|
||||||
|
// A set of default images for the reward.
|
||||||
|
DefaultImage CustomRewardImage `json:"default_image"`
|
||||||
|
|
||||||
|
// The background color to use for the reward. The color is in Hex format (for example, #00E5CB).
|
||||||
|
BackgroundColor string `json:"background_color"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the reward is enabled.
|
||||||
|
// Is true if enabled; otherwise, false. Disabled rewards aren’t shown to the user.
|
||||||
|
IsEnabled bool `json:"is_enabled"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the user must enter information when redeeming the reward.
|
||||||
|
// Is true if the reward requires user input.
|
||||||
|
IsUserInputRequired bool `json:"is_user_input_required"`
|
||||||
|
|
||||||
|
// The settings used to determine whether to apply a maximum to the number to the redemptions allowed per live stream.
|
||||||
|
MaxPerStreamSetting struct {
|
||||||
|
// A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per live stream.
|
||||||
|
// Is true if the reward applies a limit.
|
||||||
|
IsEnabled bool `json:"is_enabled"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions allowed per live stream.
|
||||||
|
MaxPerStream int64 `json:"max_per_stream"`
|
||||||
|
} `json:"max_per_stream_setting"`
|
||||||
|
|
||||||
|
// The settings used to determine whether to apply a maximum to the number of redemptions allowed per user per live stream.
|
||||||
|
MaxPerUserPerStreamSetting struct {
|
||||||
|
// A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per user per live stream.
|
||||||
|
// Is true if the reward applies a limit.
|
||||||
|
IsEnabled bool `json:"is_enabled"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions allowed per user per live stream.
|
||||||
|
MaxPerUserPerStream int64 `json:"max_per_user_per_stream"`
|
||||||
|
} `json:"max_per_user_per_stream_setting"`
|
||||||
|
|
||||||
|
// The settings used to determine whether to apply a cooldown period between redemptions and the length of the cooldown.
|
||||||
|
GlobalCooldownSetting struct {
|
||||||
|
// A Boolean value that determines whether to apply a cooldown period. Is true if a cooldown period is enabled.
|
||||||
|
IsEnabled bool `json:"is_enabled"`
|
||||||
|
|
||||||
|
// The cooldown period, in seconds.
|
||||||
|
GlobalCooldownSeconds int64 `json:"global_cooldown_seconds"`
|
||||||
|
} `json:"global_cooldown_setting"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the reward is currently paused. Is true if the reward is paused. Viewers can’t redeem paused rewards.
|
||||||
|
IsPaused bool `json:"is_paused"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the reward is currently in stock. Is true if the reward is in stock. Viewers can’t redeem out of stock rewards.
|
||||||
|
IsInStock bool `json:"is_in_stock"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed.
|
||||||
|
// If false, status is UNFULFILLED and follows the normal request queue process.
|
||||||
|
ShouldRedemptionsSkipRequestQueue bool `json:"should_redemptions_skip_request_queue"`
|
||||||
|
|
||||||
|
// The number of redemptions redeemed during the current live stream. The number counts against the max_per_stream_setting limit.
|
||||||
|
// This field is null if the broadcaster’s stream isn’t live or max_per_stream_setting isn’t enabled.
|
||||||
|
RedemptionsRedeemedCurrentStream *int `json:"redemptions_redeemed_current_stream"`
|
||||||
|
|
||||||
|
// The timestamp of when the cooldown period expires. Is null if the reward isn’t in a cooldown state (see the global_cooldown_setting field).
|
||||||
|
CooldownExpiresAt *time.Time `json:"cooldown_expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomRewardImage struct {
|
||||||
|
// The URL to a small version of the image.
|
||||||
|
URL1X string `json:"url_1x"`
|
||||||
|
|
||||||
|
// The URL to a medium version of the image.
|
||||||
|
URL2X string `json:"url_2x"`
|
||||||
|
|
||||||
|
// The URL to a large version of the image.
|
||||||
|
URL4X string `json:"url_4x"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RewardRedemptionStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RewardRedemptionStatusCanceled RewardRedemptionStatus = "CANCELED"
|
||||||
|
RewardRedemptionStatusFulfilled RewardRedemptionStatus = "FULFILLED"
|
||||||
|
RewardRedemptionStatusUnfulfilled RewardRedemptionStatus = "UNFULFILLED"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomRewardRedemption struct {
|
||||||
|
// The ID that uniquely identifies the broadcaster.
|
||||||
|
BroadcasterID string `json:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The broadcaster's login name.
|
||||||
|
BroadcasterLogin string `json:"broadcaster_login"`
|
||||||
|
|
||||||
|
// The broadcaster's display name.
|
||||||
|
BroadcasterName string `json:"broadcaster_name"`
|
||||||
|
|
||||||
|
// The ID that uniquely identifies this redemption.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// The ID that uniquely identifies the user that redeemed the reward.
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
|
||||||
|
// The user's login name.
|
||||||
|
UserLogin string `json:"user_login"`
|
||||||
|
|
||||||
|
// The user's display name.
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
|
||||||
|
// The state of the redemption.
|
||||||
|
Status RewardRedemptionStatus `json:"status"`
|
||||||
|
|
||||||
|
//The date and time of when the reward was redeemed, in RFC3339 format.
|
||||||
|
RedeemedAt time.Time `json:"redeemed_at"`
|
||||||
|
|
||||||
|
// The reward that the user redeemed.
|
||||||
|
Reward struct {
|
||||||
|
// The ID that uniquely identifies the redeemed reward.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// The reward's title.
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// The prompt displayed to the viewer if user input is required.
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
|
||||||
|
// The reward’s cost, in Channel Points.
|
||||||
|
Cost int64 `json:"cost"`
|
||||||
|
} `json:"reward"`
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateCustomRewardParams struct {
|
||||||
|
// The ID of the broadcaster that’s updating the reward. This ID must match the user ID found in the OAuth token.
|
||||||
|
BroadcasterID string `url:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The ID of the reward to update.
|
||||||
|
ID string `url:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCustomRewardRequest struct {
|
||||||
|
// The custom reward’s title. The title may contain a maximum of 45 characters and it must be unique amongst all of the broadcaster’s custom rewards.
|
||||||
|
Title *string `json:"title"`
|
||||||
|
|
||||||
|
// The cost of the reward, in Channel Points.
|
||||||
|
// The minimum is 1 point.
|
||||||
|
Cost *int64 `json:"cost"`
|
||||||
|
|
||||||
|
// The prompt shown to the viewer when they redeem the reward.
|
||||||
|
// Specify a prompt if is_user_input_required is true.
|
||||||
|
// The prompt is limited to a maximum of 200 characters.
|
||||||
|
Prompt *string `json:"prompt"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the reward is enabled. Viewers see only enabled rewards.
|
||||||
|
// The default is true.
|
||||||
|
IsEnabled *bool `json:"is_enabled"`
|
||||||
|
|
||||||
|
// The background color to use for the reward. Specify the color using Hex format (for example, #9147FF).
|
||||||
|
BackgroundColor *string `json:"background_color"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether the user needs to enter information when redeeming the reward. See the prompt field.
|
||||||
|
// The default is false.
|
||||||
|
IsUserInputRequired *bool `json:"is_user_input_required"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether to limit the maximum number of redemptions allowed per live stream (see the max_per_stream field).
|
||||||
|
// The default is false.
|
||||||
|
IsMaxPerStreamEnabled *bool `json:"is_max_per_stream_enabled"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions allowed per live stream. Applied only if is_max_per_stream_enabled is true.
|
||||||
|
// The minimum value is 1.
|
||||||
|
MaxPerStream *int `json:"max_per_stream"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether to limit the maximum number of redemptions allowed per user per stream (see the max_per_user_per_stream field).
|
||||||
|
// The default is false.
|
||||||
|
IsMaxPerUserPerStreamEnabled *bool `json:"is_max_per_user_per_stream_enabled"`
|
||||||
|
|
||||||
|
// The maximum number of redemptions allowed per user per stream. Applied only if is_max_per_user_per_stream_enabled is true.
|
||||||
|
// The minimum value is 1.
|
||||||
|
MaxPerUserPerStream *int `json:"max_per_user_per_stream"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether to apply a cooldown period between redemptions (see the global_cooldown_seconds field for the duration of the cooldown period).
|
||||||
|
// The default is false.
|
||||||
|
IsGlobalCooldownEnabled *bool `json:"is_global_cooldown_enabled"`
|
||||||
|
|
||||||
|
// The cooldown period, in seconds. Applied only if the is_global_cooldown_enabled field is true.
|
||||||
|
// The minimum value is 1; however, the minimum value is 60 for it to be shown in the Twitch UX.
|
||||||
|
GlobalCooldownSeconds *int `json:"global_cooldown_seconds"`
|
||||||
|
|
||||||
|
// A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed.
|
||||||
|
// If false, status is set to UNFULFILLED and follows the normal request queue process.
|
||||||
|
// The default is false.
|
||||||
|
ShouldRedemptionsSkipRequestQueue *bool `json:"should_redemptions_skip_request_queue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCustomRewardResponse struct {
|
||||||
|
// The list contains the single reward that you updated.
|
||||||
|
Data []CustomReward `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates a custom reward. The app used to create the reward is the only app that may update the reward.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:manage:redemptions scope.
|
||||||
|
func (c *ChannelPoints) UpdateCustomReward(params *UpdateCustomRewardParams, req *UpdateCustomRewardRequest) (*UpdateCustomRewardResponse, error) {
|
||||||
|
v, _ := query.Values(req)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channel_points/custom_rewards", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(w).Encode(req); err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
} else {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := c.client.Do(&http.Request{
|
||||||
|
Method: http.MethodPatch,
|
||||||
|
URL: endpoint,
|
||||||
|
Body: r,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data UpdateCustomRewardResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package channelpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateRedemptionStatusParams struct {
|
||||||
|
// A list of IDs that identify the redemptions to update. To specify more than one ID, include this parameter for each redemption you want to update.
|
||||||
|
// For example, id=1234&id=5678. You may specify a maximum of 50 IDs.
|
||||||
|
IDs []string `url:"id"`
|
||||||
|
|
||||||
|
// The ID of the broadcaster that’s updating the redemption. This ID must match the user ID in the user access token.
|
||||||
|
BroadcasterID string `url:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The ID that identifies the reward that’s been redeemed.
|
||||||
|
RewardID string `url:"reward_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRedemptionStatusRequest struct {
|
||||||
|
// The status to set the redemption to. Possible values are:
|
||||||
|
//
|
||||||
|
// CANCELED, FULFILLED
|
||||||
|
//
|
||||||
|
// Setting the status to CANCELED refunds the user’s channel points.
|
||||||
|
Status RewardRedemptionStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRedemptionStatusResponse struct {
|
||||||
|
// The list contains the single redemption that you updated.
|
||||||
|
Data []CustomRewardRedemption `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates a redemption’s status. You may update a redemption only if its status is UNFULFILLED.
|
||||||
|
// The app used to create the reward is the only app that may update the redemption.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:manage:redemptions scope.
|
||||||
|
func (c *ChannelPoints) UpdateRedemptionStatus(params *UpdateRedemptionStatusParams, req *UpdateRedemptionStatusRequest) (*UpdateRedemptionStatusResponse, error) {
|
||||||
|
v, _ := query.Values(req)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channel_points/custom_rewards/redemptions", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(w).Encode(req); err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
} else {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := c.client.Do(&http.Request{
|
||||||
|
Method: http.MethodPatch,
|
||||||
|
URL: endpoint,
|
||||||
|
Body: r,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data UpdateRedemptionStatusResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Channels struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client *http.Client, baseUrl *url.URL) *Channels {
|
||||||
|
return &Channels{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetChannelEditorsResponse struct {
|
||||||
|
// A list of users that are editors for the specified broadcaster. The list is empty if the broadcaster doesn’t have editors.
|
||||||
|
Data []ChannelEditor `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelEditor struct {
|
||||||
|
// An ID that uniquely identifies a user with editor permissions.
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
|
||||||
|
// The user’s display name.
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
|
||||||
|
// The date and time, in RFC3339 format, when the user became one of the broadcaster’s editors.
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the broadcaster’s list editors.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:read:editors scope.
|
||||||
|
func (c *Channels) GetChannelEditors(broadcasterID string) (*GetChannelEditorsResponse, error) {
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channels/editors", RawQuery: "broadcaster_id=" + broadcasterID})
|
||||||
|
|
||||||
|
resp, err := c.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetChannelEditorsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetChannelFollowersParams struct {
|
||||||
|
// A user’s ID. Use this parameter to see whether the user follows this broadcaster.
|
||||||
|
// If specified, the response contains this user if they follow the broadcaster.
|
||||||
|
// If not specified, the response contains all users that follow the broadcaster.
|
||||||
|
//
|
||||||
|
// Using this parameter requires both a user access token with the moderator:read:followers scope and the user ID
|
||||||
|
// in the access token match the broadcaster_id or be the user ID for a moderator of the specified broadcaster.
|
||||||
|
UserID *string `url:"user_id"`
|
||||||
|
|
||||||
|
// The broadcaster’s ID. Returns the list of users that follow this broadcaster.
|
||||||
|
BroadcasterID string `url:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100. The default is 20.
|
||||||
|
First *int `url:"first,omitempty"`
|
||||||
|
|
||||||
|
// The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value.
|
||||||
|
// Read more: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
After *types.Cursor `url:"after,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetChannelFollowersResponse struct {
|
||||||
|
// The list of users that follow the specified broadcaster. The list is in descending order by followed_at (with the most recent follower first).
|
||||||
|
// The list is empty if nobody follows the broadcaster, the specified user_id isn’t in the follower list,
|
||||||
|
// the user access token is missing the moderator:read:followers scope, or the user isn’t the broadcaster or moderator for the channel.
|
||||||
|
Data []ChannelFollower `json:"data"`
|
||||||
|
|
||||||
|
// Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.
|
||||||
|
// Read more: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
Pagination types.Pagination `json:"pagination"`
|
||||||
|
|
||||||
|
// The total number of users that follow this broadcaster.
|
||||||
|
// As someone pages through the list, the number of users may change as users follow or unfollow the broadcaster.
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelFollower struct {
|
||||||
|
// An ID that uniquely identifies the user that’s following the broadcaster.
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
|
||||||
|
// The user’s login name.
|
||||||
|
UserLogin string `json:"user_login"`
|
||||||
|
|
||||||
|
// The user’s display name.
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
|
||||||
|
// The UTC timestamp when the user started following the broadcaster.
|
||||||
|
FollowedAt time.Time `json:"followed_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a list of users that follow the specified broadcaster. You can also use this endpoint to see whether a specific user follows the broadcaster.
|
||||||
|
//
|
||||||
|
// - Requires a user access token that includes the moderator:read:followers scope.
|
||||||
|
//
|
||||||
|
// - The ID in the broadcaster_id query parameter must match the user ID in the access token or the user ID in the access token must be a moderator for the specified broadcaster.
|
||||||
|
//
|
||||||
|
// This endpoint will return specific follower information only if both of the above are true.
|
||||||
|
// If a scope is not provided or the user isn’t the broadcaster or a moderator for the specified channel,
|
||||||
|
// only the total follower count will be included in the response.
|
||||||
|
func (c *Channels) GetChannelFollowers(params *GetChannelFollowersParams) (*GetChannelFollowersResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channels/followers", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := c.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetChannelFollowersResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetChannelInformationParams struct {
|
||||||
|
// The ID of the broadcaster whose channel you want to get. To specify more than one ID, include this parameter for each broadcaster you want to get.
|
||||||
|
// For example, broadcaster_id=1234&broadcaster_id=5678. You may specify a maximum of 100 IDs. The API ignores duplicate IDs and IDs that are not found.
|
||||||
|
BroadcasterIDs []string `url:"broadcaster_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetChannelInformdationResponse struct {
|
||||||
|
// A list that contains information about the specified channels. The list is empty if the specified channels weren’t found.
|
||||||
|
Data []ChannelInformation `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelInformation struct {
|
||||||
|
// An ID that uniquely identifies the broadcaster.
|
||||||
|
BroadcasterID string `json:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The broadcaster’s login name.
|
||||||
|
BroadcasterLogin string `json:"broadcaster_login"`
|
||||||
|
|
||||||
|
// The broadcaster’s display name.
|
||||||
|
BroadcasterName string `json:"broadcaster_name"`
|
||||||
|
|
||||||
|
// The broadcaster’s preferred language. The value is an ISO 639-1 two-letter language code (for example, en for English).
|
||||||
|
// The value is set to “other” if the language is not a Twitch supported language.
|
||||||
|
BroadcasterLanguage string `json:"broadcaster_language"`
|
||||||
|
|
||||||
|
// The name of the game that the broadcaster is playing or last played. The value is an empty string if the broadcaster has never played a game.
|
||||||
|
GameName string `json:"game_name"`
|
||||||
|
|
||||||
|
// An ID that uniquely identifies the game that the broadcaster is playing or last played.
|
||||||
|
// The value is an empty string if the broadcaster has never played a game.
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
|
||||||
|
// The title of the stream that the broadcaster is currently streaming or last streamed. The value is an empty string if the broadcaster has never streamed.
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// The value of the broadcaster’s stream delay setting, in seconds.
|
||||||
|
// This field’s value defaults to zero unless
|
||||||
|
//
|
||||||
|
// 1) the request specifies a user access token
|
||||||
|
//
|
||||||
|
// 2) the ID in the broadcaster_id query parameter matches the user ID in the access token
|
||||||
|
//
|
||||||
|
// 3) the broadcaster has partner status and they set a non-zero stream delay value.
|
||||||
|
Delay uint `json:"delay"`
|
||||||
|
|
||||||
|
// The tags applied to the channel.
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
|
||||||
|
// The CCLs applied to the channel.
|
||||||
|
ContentClassficationLabels []types.CCL `json:"content_classification"`
|
||||||
|
|
||||||
|
// Boolean flag indicating if the channel has branded content.
|
||||||
|
IsBrandedContent bool `json:"is_branded_content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets information about one or more channels.
|
||||||
|
//
|
||||||
|
// Requires an app access token or user access token.
|
||||||
|
func (c *Channels) GetChannelInformation(params *GetChannelInformationParams) (*GetChannelInformdationResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channels", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := c.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetChannelInformdationResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetFollowedChannelsParams struct {
|
||||||
|
// A user’s ID. Returns the list of broadcasters that this user follows. This ID must match the user ID in the user OAuth token.
|
||||||
|
UserID string `url:"user_id"`
|
||||||
|
|
||||||
|
// A broadcaster’s ID. Use this parameter to see whether the user follows this broadcaster.
|
||||||
|
// If specified, the response contains this broadcaster if the user follows them.
|
||||||
|
// If not specified, the response contains all broadcasters that the user follows.
|
||||||
|
BroadcasterID *string `url:"broadcaster_id,omitempty"`
|
||||||
|
|
||||||
|
// The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100. The default is 20.
|
||||||
|
First *int `url:"first,omitempty"`
|
||||||
|
|
||||||
|
// The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value.
|
||||||
|
// Read more: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
After *types.Cursor `url:"after,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFollowedChannelsResponse struct {
|
||||||
|
// The list of broadcasters that the user follows.
|
||||||
|
// The list is in descending order by followed_at (with the most recently followed broadcaster first)
|
||||||
|
// The list is empty if the user doesn’t follow anyone.
|
||||||
|
Data []FollowedChannel `json:"data"`
|
||||||
|
|
||||||
|
// Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.
|
||||||
|
// Read more: https://dev.twitch.tv/docs/api/guide#pagination
|
||||||
|
Pagination types.Pagination `json:"pagination"`
|
||||||
|
|
||||||
|
// The total number of broadcasters that the user follows.
|
||||||
|
// As someone pages through the list, the number may change as the user follows or unfollows broadcasters.
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FollowedChannel struct {
|
||||||
|
// An ID that uniquely identifies the broadcaster that this user is following.
|
||||||
|
BroadcasterID string `json:"broadcaster_id"`
|
||||||
|
|
||||||
|
// The broadcaster’s login name.
|
||||||
|
BroadcasterLogin string `json:"broadcaster_login"`
|
||||||
|
|
||||||
|
// The broadcaster’s display name.
|
||||||
|
BroadcasterName string `json:"broadcaster_name"`
|
||||||
|
|
||||||
|
// The UTC timestamp when the user started following the broadcaster.
|
||||||
|
FollowedAt time.Time `json:"followed_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a list of broadcasters that the specified user follows. You can also use this endpoint to see whether a user follows a specific broadcaster.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the user:read:follows scope.
|
||||||
|
func (c *Channels) GetFollowedChannels(params *GetFollowedChannelsParams) (*GetFollowedChannelsResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "users/follows", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := c.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetFollowedChannelsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModifyChannelInformationRequest struct {
|
||||||
|
// The ID of the game that the user plays. The game is not updated if the ID isn’t a game ID that Twitch recognizes.
|
||||||
|
// To unset this field, use “0” or “” (an empty string).
|
||||||
|
GameID *string `json:"game_id,omitempty"`
|
||||||
|
|
||||||
|
// The user’s preferred language. Set the value to an ISO 639-1 two-letter language code (for example, en for English).
|
||||||
|
// Set to “other” if the user’s preferred language is not a Twitch supported language.
|
||||||
|
// The language isn’t updated if the language code isn’t a Twitch supported language.
|
||||||
|
Language *string `json:"language,omitempty"`
|
||||||
|
|
||||||
|
// The title of the user’s stream. You may not set this field to an empty string.
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
|
||||||
|
// The number of seconds you want your broadcast buffered before streaming it live. The delay helps ensure fairness during competitive play.
|
||||||
|
// Only users with Partner status may set this field. The maximum delay is 900 seconds (15 minutes).
|
||||||
|
Delay *int `json:"delay,omitempty"`
|
||||||
|
|
||||||
|
// A list of channel-defined tags to apply to the channel. To remove all tags from the channel, set tags to an empty array.
|
||||||
|
// Tags help identify the content that the channel streams. Learn More: https://help.twitch.tv/s/article/guide-to-tags
|
||||||
|
//
|
||||||
|
// A channel may specify a maximum of 10 tags.
|
||||||
|
// Each tag is limited to a maximum of 25 characters and may not be an empty string or contain spaces or special characters.
|
||||||
|
// Tags are case insensitive. For readability, consider using camelCasing or PascalCasing.
|
||||||
|
Tags *[]string `json:"tags,omitempty"`
|
||||||
|
|
||||||
|
// List of labels that should be set as the Channel’s CCLs.
|
||||||
|
ContentClassificationLabels []ModifyContentClassificationLabel `json:"content_classification_labels,omitempty"`
|
||||||
|
|
||||||
|
// Boolean flag indicating if the channel has branded content.
|
||||||
|
IsBrandedContent *bool `json:"is_branded_content,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyContentClassificationLabel struct {
|
||||||
|
// ID of the Content Classification Labels that must be added/removed from the channel.
|
||||||
|
ID types.CCL `json:"id"`
|
||||||
|
|
||||||
|
// Boolean flag indicating whether the label should be enabled (true) or disabled for the channel.
|
||||||
|
IsEnabled bool `json:"is_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates a channel’s properties.
|
||||||
|
//
|
||||||
|
// Requires a user access token that includes the channel:manage:broadcast scope.
|
||||||
|
func (c *Channels) ModifyChannelInformation(broadcasterID string, req *ModifyChannelInformationRequest) error {
|
||||||
|
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "channels", RawQuery: "broadcaster_id=" + broadcasterID})
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(w).Encode(req); err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
} else {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err := c.client.Do(&http.Request{
|
||||||
|
Method: http.MethodPatch,
|
||||||
|
URL: endpoint,
|
||||||
|
Body: r,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package eventsub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateSubscriptionRequest struct {
|
||||||
|
SubscriptionType
|
||||||
|
Condition Condition `json:"condition"`
|
||||||
|
Transport *Transport `json:"transport"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSubscriptionResponse struct {
|
||||||
|
Data []*Subscription `json:"data"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
TotalCost int `json:"total_cost"`
|
||||||
|
MaxTotalCost int `json:"max_total_cost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventSub) CreateSubscription(req *CreateSubscriptionRequest) (*CreateSubscriptionResponse, error) {
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "eventsub/subscriptions"})
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(w).Encode(req); err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
} else {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := e.client.Post(endpoint.String(), "application/json", r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data CreateSubscriptionResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package eventsub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *EventSub) DeleteSubscription(id string) error {
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "eventsub/subscriptions", RawQuery: "id=" + id})
|
||||||
|
|
||||||
|
_, err := e.client.Do(&http.Request{
|
||||||
|
Method: http.MethodDelete,
|
||||||
|
URL: endpoint,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eventsub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventSub struct {
|
||||||
|
client *http.Client
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client *http.Client, baseUrl *url.URL) *EventSub {
|
||||||
|
return &EventSub{
|
||||||
|
client: client,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package eventsub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetSubscriptionsParams struct {
|
||||||
|
Status *Status `url:"status,omitempty"`
|
||||||
|
Type *SubscriptionType `url:"type,omitempty"`
|
||||||
|
UserID *string `url:"user_id,omitempty"`
|
||||||
|
After *types.Cursor `url:"after,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSubscriptionsResponse struct {
|
||||||
|
Data []Subscription `json:"data"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
TotalCost int `json:"total_cost"`
|
||||||
|
MaxTotalCost int `json:"max_total_cost"`
|
||||||
|
Pagination types.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventSub) GetSubscriptions(params *GetSubscriptionsParams) (*GetSubscriptionsResponse, error) {
|
||||||
|
v, _ := query.Values(params)
|
||||||
|
endpoint := e.baseUrl.ResolveReference(&url.URL{Path: "eventsub/subscriptions", RawQuery: v.Encode()})
|
||||||
|
|
||||||
|
resp, err := e.client.Get(endpoint.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data GetSubscriptionsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package eventsub
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Condition map[string]any
|
||||||
|
|
||||||
|
type Subscription struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Condition Condition `json:"condition"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Transport *Transport `json:"transport"`
|
||||||
|
Cost int `json:"cost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusEnabled = "enabled"
|
||||||
|
StatusWebhookCallbackVerificationPending = "webhook_callback_verification_pending"
|
||||||
|
StatusWebhookCallbackVerificationFailed = "webhook_callback_verification_failed"
|
||||||
|
NotificationFailuresExceeded = "notification_failures_exceeded"
|
||||||
|
AuthorizationRevoked = "authorization_revoked"
|
||||||
|
ModeratorRemoved = "moderator_removed"
|
||||||
|
USerRemoved = "user_removed"
|
||||||
|
VersionRemoved = "version_removed"
|
||||||
|
BetaMaintenance = "beta_maintenance"
|
||||||
|
WebsocketDisconnected = "websocket_disconnected"
|
||||||
|
WebsocketFailedPingPong = "websocket_failed_ping_pong"
|
||||||
|
WebsocketReceivedInboundTraffic = "websocket_received_inbound_traffic"
|
||||||
|
WebsocketConnectionUnused = "websocket_connection_unused"
|
||||||
|
WebsocketInternalError = "websocket_internal_error"
|
||||||
|
WebsocketNetworkTimeout = "websocket_network_timeout"
|
||||||
|
WebsocketnetworkError = "websocket_network_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Callback *string `json:"callback,omitempty"`
|
||||||
|
Secret *string `json:"secret,omitempty"`
|
||||||
|
SessionID *string `json:"session_id,omitempty"`
|
||||||
|
ConduitID *string `json:"conduit_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func WebhookTransport(callback string, secret string) *Transport {
|
||||||
|
return &Transport{
|
||||||
|
Method: "webhook",
|
||||||
|
Callback: &callback,
|
||||||
|
Secret: &secret,
|
||||||
|
SessionID: nil,
|
||||||
|
ConduitID: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WebSocketTransport(sessionID string) *Transport {
|
||||||
|
return &Transport{
|
||||||
|
Method: "websocket",
|
||||||
|
Callback: nil,
|
||||||
|
Secret: nil,
|
||||||
|
SessionID: &sessionID,
|
||||||
|
ConduitID: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConduitTransport(conduitID string) *Transport {
|
||||||
|
return &Transport{
|
||||||
|
Method: "websocket",
|
||||||
|
Callback: nil,
|
||||||
|
Secret: nil,
|
||||||
|
SessionID: nil,
|
||||||
|
ConduitID: &conduitID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionType struct {
|
||||||
|
Name string `json:"type"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ChannelUpdate = SubscriptionType{Name: "channel.update", Version: "2"}
|
||||||
|
ChannelFollow = SubscriptionType{Name: "channel.follow", Version: "2"}
|
||||||
|
ChannelAdBreakBegin = SubscriptionType{Name: "channel.ad_break_begin", Version: "1"}
|
||||||
|
ChannelChatClear = SubscriptionType{Name: "channel.chat.clear", Version: "1"}
|
||||||
|
ChannelChatClearUserMessages = SubscriptionType{Name: "channel.chat.clear_user_messages", Version: "1"}
|
||||||
|
ChannelChatMessage = SubscriptionType{Name: "channel.chat.message", Version: "1"}
|
||||||
|
ChannelChatMessageDelete = SubscriptionType{Name: "channel.chat.message_delete", Version: "1"}
|
||||||
|
ChannelChatNotification = SubscriptionType{Name: "channel.chat.notification", Version: "1"}
|
||||||
|
ChannelChatSettingsUpdate = SubscriptionType{Name: "channel.chat_settings.update", Version: "beta"}
|
||||||
|
ChannelSubscribe = SubscriptionType{Name: "channel.subscribe", Version: "1"}
|
||||||
|
ChannelSubscriptionEnd = SubscriptionType{Name: "channel.subscription.end", Version: "1"}
|
||||||
|
ChannelSubscriptionGift = SubscriptionType{Name: "channel.subscription.gift", Version: "1"}
|
||||||
|
ChannelSubscriptionMessage = SubscriptionType{Name: "channel.subscription.message", Version: "1"}
|
||||||
|
ChannelCheer = SubscriptionType{Name: "channel.cheer", Version: "1"}
|
||||||
|
ChannelRaid = SubscriptionType{Name: "channel.raid", Version: "1"}
|
||||||
|
ChannelBan = SubscriptionType{Name: "channel.ban", Version: "1"}
|
||||||
|
ChannelUnban = SubscriptionType{Name: "channel.unban", Version: "1"}
|
||||||
|
ChannelModeratorAdd = SubscriptionType{Name: "channel.moderator.add", Version: "1"}
|
||||||
|
ChannelModeratorRemove = SubscriptionType{Name: "channel.moderator.remove", Version: "1"}
|
||||||
|
ChannelGuestStarSessionBegin = SubscriptionType{Name: "channel.guest_star_session.begin", Version: "beta"}
|
||||||
|
ChannelGuestStarSessionEnd = SubscriptionType{Name: "channel.guest_star_session.end", Version: "beta"}
|
||||||
|
ChannelGuestStarGuestUpdate = SubscriptionType{Name: "channel.guest_star_guest.update", Version: "beta"}
|
||||||
|
ChannelGuestStarSettingsUpdate = SubscriptionType{Name: "channel.guest_star_settings.update", Version: "beta"}
|
||||||
|
ChannelPointsCustomRewardAdd = SubscriptionType{Name: "channel.channel_points_custom_reward.add", Version: "1"}
|
||||||
|
ChannelPointsCustomRewardUpdate = SubscriptionType{Name: "channel.channel_points_custom_reward.update", Version: "1"}
|
||||||
|
ChannelPointsCustomRewardRemove = SubscriptionType{Name: "channel.channel_points_custom_reward.remove", Version: "1"}
|
||||||
|
ChannelPointsCustomRewardRedemptionAdd = SubscriptionType{Name: "channel.channel_points_custom_reward_redemption.add", Version: "1"}
|
||||||
|
ChannelPointsCustomRewardRedemptionUpdate = SubscriptionType{Name: "channel.channel_points_custom_reward_redemption.update", Version: "1"}
|
||||||
|
ChannelPollBegin = SubscriptionType{Name: "channel.poll.begin", Version: "1"}
|
||||||
|
ChannelPollProgress = SubscriptionType{Name: "channel.poll.progress", Version: "1"}
|
||||||
|
ChannelPollEnd = SubscriptionType{Name: "channel.poll.end", Version: "1"}
|
||||||
|
ChannelPredictionBegin = SubscriptionType{Name: "channel.prediction.begin", Version: "1"}
|
||||||
|
ChannelPredictionProgress = SubscriptionType{Name: "channel.prediction.progress", Version: "1"}
|
||||||
|
ChannelPredictionLock = SubscriptionType{Name: "channel.prediction.lock", Version: "1"}
|
||||||
|
ChannelPredictionEnd = SubscriptionType{Name: "channel.prediction.end", Version: "1"}
|
||||||
|
CharityCampaignDonate = SubscriptionType{Name: "channel.charity_campaign.donate", Version: "1"}
|
||||||
|
CharityCampaignStart = SubscriptionType{Name: "channel.charity_campaign.start", Version: "1"}
|
||||||
|
CharityCampaignProgress = SubscriptionType{Name: "channel.charity_campaign.progress", Version: "1"}
|
||||||
|
CharityCampaignStop = SubscriptionType{Name: "channel.charity_campaign.stop", Version: "1"}
|
||||||
|
ConduitShardDisabled = SubscriptionType{Name: "conduit.shard.disabled", Version: "1"}
|
||||||
|
DropEntitlementGrant = SubscriptionType{Name: "drop.entitlement.grant", Version: "1"}
|
||||||
|
ExtensionBitsTransactionCreate = SubscriptionType{Name: "extension.bits.transaction.create", Version: "1"}
|
||||||
|
GoalBegin = SubscriptionType{Name: "goal.begin", Version: "1"}
|
||||||
|
GoalProgress = SubscriptionType{Name: "goal.progress", Version: "1"}
|
||||||
|
GoalEnd = SubscriptionType{Name: "goal.end", Version: "1"}
|
||||||
|
HypeTrainBegin = SubscriptionType{Name: "hype_train.begin", Version: "1"}
|
||||||
|
HypeTrainProgress = SubscriptionType{Name: "hype_train.progress", Version: "1"}
|
||||||
|
HypeTrainEnd = SubscriptionType{Name: "hype_train.end", Version: "1"}
|
||||||
|
ShieldModeBegin = SubscriptionType{Name: "shield_mode.begin", Version: "1"}
|
||||||
|
ShieldModeEnd = SubscriptionType{Name: "shield_mode.end", Version: "1"}
|
||||||
|
ShoutoutCreate = SubscriptionType{Name: "shoutout.create", Version: "1"}
|
||||||
|
ShoutoutReceived = SubscriptionType{Name: "shoutout.received", Version: "1"}
|
||||||
|
StreamOnline = SubscriptionType{Name: "stream.online", Version: "1"}
|
||||||
|
StreamOffline = SubscriptionType{Name: "stream.offline", Version: "1"}
|
||||||
|
UserAuthorizationGrant = SubscriptionType{Name: "user.authorization.grant", Version: "1"}
|
||||||
|
UserAuthorizationRevoke = SubscriptionType{Name: "user.authorization.revoke", Version: "1"}
|
||||||
|
UserUpdate = SubscriptionType{Name: "user.update", Version: "1"}
|
||||||
|
)
|
|
@ -0,0 +1,66 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cursor string
|
||||||
|
|
||||||
|
func (c Cursor) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) UnmarshalText(text []byte) error {
|
||||||
|
*c = Cursor(string(text))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cursor) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(string(c)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = Cursor(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cursor) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(string(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains the information used to page through the list of results.
|
||||||
|
// The object is empty if there are no more pages left to page through.
|
||||||
|
type Pagination struct {
|
||||||
|
// The cursor used to get the next page of results. Use the cursor to set the request’s after query parameter.
|
||||||
|
Cursor Cursor `json:"cursor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateRange struct {
|
||||||
|
StartedAt time.Time `json:"started_at"`
|
||||||
|
EndedAt time.Time `json:"ended_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content Classification Label
|
||||||
|
type CCL string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CCLDrugsIntoxication CCL = "DrugsIntoxication"
|
||||||
|
CCLSexualThemes CCL = "SexualThemes"
|
||||||
|
CCLViolentGraphic CCL = "ViolentGraphic"
|
||||||
|
CCLGambling CCL = "Gambling"
|
||||||
|
CCLProfanityVulgarity CCL = "ProfanityVulgarity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sort Order
|
||||||
|
type SortOrder string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SortOrderOldest SortOrder = "OLDEST"
|
||||||
|
SortOrderNewest SortOrder = "NEWEST"
|
||||||
|
)
|
|
@ -0,0 +1,48 @@
|
||||||
|
package eventsub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.fifitido.net/twitch/api"
|
||||||
|
"go.fifitido.net/twitch/api/eventsub"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventSub struct {
|
||||||
|
api *api.API
|
||||||
|
transport *eventsub.Transport
|
||||||
|
|
||||||
|
subscriptions map[string]*eventsub.Subscription
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(api *api.API, trans *eventsub.Transport) *EventSub {
|
||||||
|
return &EventSub{
|
||||||
|
api: api,
|
||||||
|
transport: trans,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventSub) Subscribe(subType eventsub.SubscriptionType, cond eventsub.Condition) error {
|
||||||
|
res, err := e.api.EventSub.CreateSubscription(&eventsub.CreateSubscriptionRequest{
|
||||||
|
SubscriptionType: subType,
|
||||||
|
Condition: cond,
|
||||||
|
Transport: e.transport,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range res.Data {
|
||||||
|
e.subscriptions[sub.ID] = sub
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventSub) Close() error {
|
||||||
|
for _, sub := range e.subscriptions {
|
||||||
|
if err := e.api.EventSub.DeleteSubscription(sub.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module go.fifitido.net/twitch
|
||||||
|
|
||||||
|
go 1.21.7
|
||||||
|
|
||||||
|
require github.com/google/go-querystring v1.1.0
|
|
@ -0,0 +1,5 @@
|
||||||
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
@ -0,0 +1,152 @@
|
||||||
|
package twitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.fifitido.net/twitch/api/bits"
|
||||||
|
"go.fifitido.net/twitch/api/channelpoints"
|
||||||
|
"go.fifitido.net/twitch/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func String(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToString(s *string) string {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringSlice(s []string) *[]string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToStringSlice(s *[]string) []string {
|
||||||
|
if s == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bool(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToBool(b *bool) bool {
|
||||||
|
if b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *b
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToInt(i *int) int {
|
||||||
|
if i == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *i
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int64(i int64) *int64 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToInt64(i *int64) int64 {
|
||||||
|
if i == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *i
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int32(i int32) *int32 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToInt32(i *int32) int32 {
|
||||||
|
if i == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *i
|
||||||
|
}
|
||||||
|
|
||||||
|
func Float32(f float32) *float32 {
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFloat32(f *float32) float32 {
|
||||||
|
if f == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
|
func Float64(f float64) *float64 {
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFloat64(f *float64) float64 {
|
||||||
|
if f == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
|
func Time(t time.Time) *time.Time {
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTime(t *time.Time) time.Time {
|
||||||
|
if t == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return *t
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cursor(c types.Cursor) *types.Cursor {
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToCursor(c *types.Cursor) types.Cursor {
|
||||||
|
if c == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c
|
||||||
|
}
|
||||||
|
|
||||||
|
func BitsPeriod(p bits.Period) *bits.Period {
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToBitsPeriod(p *bits.Period) bits.Period {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
func RewardRedemptionStatus(s channelpoints.RewardRedemptionStatus) *channelpoints.RewardRedemptionStatus {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRewardRedemptionStatus(s *channelpoints.RewardRedemptionStatus) channelpoints.RewardRedemptionStatus {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortOrder(s types.SortOrder) *types.SortOrder {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSortOrder(s *types.SortOrder) types.SortOrder {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
Loading…
Reference in New Issue