eventsub

package module
v2.0.0-beta.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 18, 2024 License: MIT Imports: 17 Imported by: 0

README

twitch-eventsub-framework

Framework for Twitch EventSub applications built with webhooks

GoDoc Build Status

Installation

go get -u github.com/dnsge/twitch-eventsub-framework/v2

Go 1.21+ is required.

Quick Start

Use a Handler to listen for incoming notifications from Twitch servers.

// Create my handler with no secret key verification
handler := eventsub.NewHandler(nil)

// Process channel.update EventSub notifications
handler.HandleChannelUpdate = func(h bindings.NotificationHeaders, sub bindings.Subscription, event bindings.EventChannelUpdate) {
    fmt.Println("Got a channel.update notification!")
    fmt.Printf("Channel = %s, Title = %s\n", event.BroadcasterUserName, event.Title)
}

// Listen for HTTP requests from Twitch EventSub servers
http.ListenAndServe("127.0.0.1:8080", handler)

// Test it with the Twitch CLI!
// $ twitch event trigger channel.update -v 2 -F http://127.0.0.1:8080

Use a SubClient to subscribe to EventSub subscriptions.

// Create a client with a ClientID and App Token
client := eventsub.NewClient(eventsub.NewStaticCredentials(clientID, appToken))

// Subscribe to channel.update events for forsen
client.Subscribe(context.Background(), &eventsub.SubRequest{
    Type:    "channel.update", 
    Version: "2",
    Condition: bindings.ConditionChannelUpdate{
        BroadcasterUserID: "22484632",
    },
    Callback: "https://my.website/api/twitch/webhooks",
    Secret:   `my signing secret`,
})

Examples

  1. See examples/sub_client/main.go for an example usage of creating a new webhook subscription.
  2. See examples/sub_handler/main.go for an example usage of receiving webhook notifications from Twitch.

Migrating to v2

See MIGRATE_FROM_V1.md for details on the differences between v1 and v2.

Documentation

Overview

Package eventsub implements a http.Handler for processing Twitch EventSub notifications and validation requests (Handler) and provides a client for interacting with the EventSub API (Client).

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrEmptySubscriptionVersion = errors.New("empty subscription version")
)

Functions

func VerifyRequestSignature

func VerifyRequestSignature(req *http.Request, body, secret []byte) (bool, error)

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(credentials Credentials) *Client

NewClient creates a new Client with the given Credentials provider.

func NewClientHTTP

func NewClientHTTP(credentials Credentials, client *http.Client) *Client

NewClientHTTP creates a new Client with the given Credentials provider and http.Client instance.

func (*Client) GetSubscriptions

func (s *Client) GetSubscriptions(ctx context.Context, statusFilter Status) (*bindings.RequestStatus, error)

GetSubscriptions returns all EventSub subscriptions. If statusFilter != StatusAny, it will apply the filter to the query.

func (*Client) Subscribe

func (s *Client) Subscribe(ctx context.Context, srq *SubRequest) (*bindings.RequestStatus, error)

Subscribe creates a new Webhook subscription.

func (*Client) Unsubscribe

func (s *Client) Unsubscribe(ctx context.Context, subscriptionID string) error

Unsubscribe deletes a Webhook subscription by the subscription's ID.

type Credentials

type Credentials interface {
	ClientID(ctx context.Context) (string, error)
	AppToken(ctx context.Context) (string, error)
}

Credentials represents a method of obtaining Twitch API client credentials

func NewStaticCredentials

func NewStaticCredentials(clientID string, appToken string) Credentials

NewStaticCredentials creates a Credentials instance with a fixed ClientID string and AppToken string.

This Credentials implementation should only be used for development as the app token will eventually expire and API calls will subsequently fail.

type EventHandler

type EventHandler[EventMessage any] func(headers bindings.NotificationHeaders, sub bindings.Subscription, event EventMessage)

EventHandler is an event callback to process a notification from EventSub.

type Handler

type Handler struct {

	// VerifyChallenge is called to determine whether a subscription challenge
	// should be accepted.
	VerifyChallenge func(context.Context, *bindings.NotificationHeaders, *bindings.SubscriptionChallenge) bool

	// IDTracker used to deduplicate notifications.
	IDTracker IDTracker
	// OnDuplicateNotification is called when the provided IDTracker rejects a
	// EventSub notification as duplicate. Completes before returning a response
	// to Twitch, so this function should not take too long to execute.
	OnDuplicateNotification func(context.Context, *bindings.NotificationHeaders)

	// BeforeHandleEvent is called with the request context, the notification
	// headers, raw event notification body. Completes before executing the event
	// handler.
	BeforeHandleEvent func(context.Context, *bindings.NotificationHeaders, *bindings.EventNotification)

	HandleChannelUpdate                       EventHandler[bindings.EventChannelUpdate]                       `eventsub-type:"channel.update" eventsub-version:"2"`
	HandleChannelFollow                       EventHandler[bindings.EventChannelFollow]                       `eventsub-type:"channel.follow" eventsub-version:"2"`
	HandleChannelSubscribe                    EventHandler[bindings.EventChannelSubscribe]                    `eventsub-type:"channel.subscribe" eventsub-version:"1"`
	HandleChannelSubscriptionEnd              EventHandler[bindings.EventChannelSubscriptionEnd]              `eventsub-type:"channel.subscription.end" eventsub-version:"1"`
	HandleChannelSubscriptionGift             EventHandler[bindings.EventChannelSubscriptionGift]             `eventsub-type:"channel.subscription.gift" eventsub-version:"1"`
	HandleChannelSubscriptionMessage          EventHandler[bindings.EventChannelSubscriptionMessage]          `eventsub-type:"channel.subscription.message" eventsub-version:"1"`
	HandleChannelCheer                        EventHandler[bindings.EventChannelCheer]                        `eventsub-type:"channel.cheer" eventsub-version:"1"`
	HandleChannelRaid                         EventHandler[bindings.EventChannelRaid]                         `eventsub-type:"channel.raid" eventsub-version:"1"`
	HandleChannelBan                          EventHandler[bindings.EventChannelBan]                          `eventsub-type:"channel.ban" eventsub-version:"1"`
	HandleChannelUnban                        EventHandler[bindings.EventChannelUnban]                        `eventsub-type:"channel.unban" eventsub-version:"1"`
	HandleChannelModeratorAdd                 EventHandler[bindings.EventChannelModeratorAdd]                 `eventsub-type:"channel.moderator.add" eventsub-version:"1"`
	HandleChannelModeratorRemove              EventHandler[bindings.EventChannelModeratorRemove]              `eventsub-type:"channel.moderator.remove" eventsub-version:"1"`
	HandleChannelPointsRewardAdd              EventHandler[bindings.EventChannelPointsRewardAdd]              `eventsub-type:"channel.channel_points_custom_reward.add" eventsub-version:"1"`
	HandleChannelPointsRewardUpdate           EventHandler[bindings.EventChannelPointsRewardUpdate]           `eventsub-type:"channel.channel_points_custom_reward.update" eventsub-version:"1"`
	HandleChannelPointsRewardRemove           EventHandler[bindings.EventChannelPointsRewardRemove]           `eventsub-type:"channel.channel_points_custom_reward.remove" eventsub-version:"1"`
	HandleChannelPointsRewardRedemptionAdd    EventHandler[bindings.EventChannelPointsRewardRedemptionAdd]    `eventsub-type:"channel.channel_points_custom_reward_redemption.add" eventsub-version:"1"`
	HandleChannelPointsRewardRedemptionUpdate EventHandler[bindings.EventChannelPointsRewardRedemptionUpdate] `eventsub-type:"channel.channel_points_custom_reward_redemption.update" eventsub-version:"1"`
	HandleChannelPollBegin                    EventHandler[bindings.EventChannelPollBegin]                    `eventsub-type:"channel.poll.begin" eventsub-version:"1"`
	HandleChannelPollProgress                 EventHandler[bindings.EventChannelPollProgress]                 `eventsub-type:"channel.poll.progress" eventsub-version:"1"`
	HandleChannelPollEnd                      EventHandler[bindings.EventChannelPollEnd]                      `eventsub-type:"channel.poll.end" eventsub-version:"1"`
	HandleChannelPredictionBegin              EventHandler[bindings.EventChannelPredictionBegin]              `eventsub-type:"channel.prediction.begin" eventsub-version:"1"`
	HandleChannelPredictionProgress           EventHandler[bindings.EventChannelPredictionProgress]           `eventsub-type:"channel.prediction.progress" eventsub-version:"1"`
	HandleChannelPredictionLock               EventHandler[bindings.EventChannelPredictionLock]               `eventsub-type:"channel.prediction.lock" eventsub-version:"1"`
	HandleChannelPredictionEnd                EventHandler[bindings.EventChannelPredictionEnd]                `eventsub-type:"channel.prediction.end" eventsub-version:"1"`
	HandleDropEntitlementGrant                EventHandler[bindings.EventDropEntitlementGrant]                `eventsub-type:"drop.entitlement.grant" eventsub-version:"1"`
	HandleExtensionBitsTransactionCreate      EventHandler[bindings.EventBitsTransactionCreate]               `eventsub-type:"extension.bits_transaction.create" eventsub-version:"1"`
	HandleGoalBegin                           EventHandler[bindings.EventGoals]                               `eventsub-type:"channel.goal.begin" eventsub-version:"1"`
	HandleGoalProgress                        EventHandler[bindings.EventGoals]                               `eventsub-type:"channel.goal.progress" eventsub-version:"1"`
	HandleGoalEnd                             EventHandler[bindings.EventGoals]                               `eventsub-type:"channel.goal.end" eventsub-version:"1"`
	HandleHypeTrainBegin                      EventHandler[bindings.EventHypeTrainBegin]                      `eventsub-type:"channel.hype_train.begin" eventsub-version:"1"`
	HandleHypeTrainProgress                   EventHandler[bindings.EventHypeTrainProgress]                   `eventsub-type:"channel.hype_train.progress" eventsub-version:"1"`
	HandleHypeTrainEnd                        EventHandler[bindings.EventHypeTrainEnd]                        `eventsub-type:"channel.hype_train.end" eventsub-version:"1"`
	HandleStreamOnline                        EventHandler[bindings.EventStreamOnline]                        `eventsub-type:"stream.online" eventsub-version:"1"`
	HandleStreamOffline                       EventHandler[bindings.EventStreamOffline]                       `eventsub-type:"stream.offline" eventsub-version:"1"`
	HandleUserUpdate                          EventHandler[bindings.EventUserUpdate]                          `eventsub-type:"user.update" eventsub-version:"1"`
	HandleUserAuthorizationGrant              EventHandler[bindings.EventUserAuthorizationGrant]              `eventsub-type:"user.authorization.grant" eventsub-version:"1"`
	HandleUserAuthorizationRevoke             EventHandler[bindings.EventUserAuthorizationRevoke]             `eventsub-type:"user.authorization.revoke" eventsub-version:"1"`
	HandleChannelChatMessage                  EventHandler[bindings.EventChannelChatMessage]                  `eventsub-type:"channel.chat.message" eventsub-version:"1"`
	HandleChannelChatClear                    EventHandler[bindings.EventChannelChatClear]                    `eventsub-type:"channel.chat.clear" eventsub-version:"1"`
	HandleChannelChatClearUserMessages        EventHandler[bindings.EventChannelChatClearUserMessages]        `eventsub-type:"channel.chat.clear_user_messages" eventsub-version:"1"`
	HandleChannelChatMessageDelete            EventHandler[bindings.EventChannelChatMessageDelete]            `eventsub-type:"channel.chat.message_delete" eventsub-version:"1"`
	HandleChannelChatNotification             EventHandler[bindings.EventChannelChatNotification]             `eventsub-type:"channel.chat.notification" eventsub-version:"1"`

	// ======================================================
	// NOTE: Beta handlers, may break backwards-compatibility
	// ======================================================
	HandleChannelUnbanRequestCreate  EventHandler[beta.EventChannelUnbanRequestCreate]  `eventsub-type:"channel.unban_request.create" eventsub-version:"beta"`
	HandleChannelUnbanRequestResolve EventHandler[beta.EventChannelUnbanRequestResolve] `eventsub-type:"channel.unban_request.resolve" eventsub-version:"beta"`
	// contains filtered or unexported fields
}

Handler implements http.Handler to receive Twitch EventSub webhook requests.

Handler handles both verification of new subscriptions and dispatching of event notifications. To handle a specific event, set the corresponding HandleXXX struct field. When a notification is received and validated, the handler function will be invoked in a new goroutine.

To conditionally verify challenges, set the VerifyChallenge function.

To deduplicate EventSub notifications sent by Twitch (highly recommended), set IDTracker to proper implementation of the IDTracker interface. Set OnDuplicateNotification to perform some custom logic when a duplicate notification is detected by the set IDTracker.

To perform some logic before processing every request (e.g. log something, metrics, tracing), set BeforeHandleEvent.

func NewHandler

func NewHandler(secret []byte) *Handler

NewHandler creates a new, empty EventSub handler. It implements http.Handler.

When passed a non-nil secret, validation of notification events will be enabled and computed using the secret.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type IDTracker

type IDTracker interface {
	// AddAndCheckIfDuplicate returns if the ID is a duplicate and an error.
	AddAndCheckIfDuplicate(ctx context.Context, id string) (bool, error)
}

IDTracker keeps track of EventSub Message IDs that have already been processed.

type MapTracker

type MapTracker struct {
	// contains filtered or unexported fields
}

MapTracker uses an in-memory map to check if a notification ID is a duplicate.

func NewMapTracker

func NewMapTracker() *MapTracker

NewMapTracker creates a new MapTracker instance which uses an in-memory map to check if a notification ID is a duplicate.

func (*MapTracker) AddAndCheckIfDuplicate

func (m *MapTracker) AddAndCheckIfDuplicate(_ context.Context, id string) (bool, error)

type Status

type Status string
const (
	StatusAny Status = ""
	// StatusEnabled indicates Twitch has verified your callback and is able to
	// send you notifications.
	StatusEnabled Status = "enabled"
	// StatusVerificationPending indicates Twitch is verifying that you own the
	// callback specified in the create subscription request.
	StatusVerificationPending Status = "webhook_callback_verification_pending"
	// StatusVerificationFailed indicates Twitch failed to verify that you own
	// the callback specified in the create subscription request.
	StatusVerificationFailed Status = "webhook_callback_verification_failed"
	// StatusFailuresExceeded indicates Twitch revoked your subscription because
	// the notification delivery failure rate was too high.
	StatusFailuresExceeded Status = "notification_failures_exceeded"
	// StatusAuthorizationRevoked indicates Twitch revoked your subscription
	// because the users in the condition object revoked their authorization
	// letting you get events on their behalf, or changed their password.
	StatusAuthorizationRevoked Status = "authorization_revoked"
	// StatusModeratorRemoved indicates The moderator that authorized the
	// subscription is no longer one of the broadcaster’s moderators.
	StatusModeratorRemoved Status = "moderator_removed"
	// StatusUserRemoved indicates Twitch revoked your subscription because
	// the users in the condition object are no longer Twitch users.
	StatusUserRemoved Status = "user_removed"
	// StatusVersionRemoved indicates Twitch revoked your subscription because
	// the subscription to subscription type and version is no longer supported
	StatusVersionRemoved Status = "version_removed"
	// StatusBetaMaintenance indicates Twitch revoked your subscription because
	// the beta subscription type was undergoing maintenance.
	StatusBetaMaintenance Status = "beta_maintenance"
)

type SubRequest

type SubRequest struct {
	// The type of event being subscribed to.
	Type string
	// The subscription type version.
	Version string
	// The parameters under which the event will be fired.
	Condition interface{}
	// The Webhook HTTP callback address.
	Callback string
	// The HMAC secret used to verify the event data.
	Secret string
}

type TrackerFunc

type TrackerFunc func(context.Context, string) (bool, error)

TrackerFunc is a functional adapter for the IDTracker interface.

func (TrackerFunc) AddAndCheckIfDuplicate

func (f TrackerFunc) AddAndCheckIfDuplicate(ctx context.Context, id string) (bool, error)

type TwitchError

type TwitchError struct {
	ErrorText string `json:"error"`
	Status    int    `json:"status"`
	Message   string `json:"message"`
}

TwitchError describes an error from the Twitch API.

For example:

{
  "error": "Unauthorized",
  "status": 401,
  "message": "Invalid OAuth token"
}

func (*TwitchError) Error

func (t *TwitchError) Error() string

Directories

Path Synopsis
beta
Package beta contains bindings for beta-versioned EventSub structures.
Package beta contains bindings for beta-versioned EventSub structures.
examples
scripts

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL