1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00
2020-01-17 20:42:25 +00:00

198 lines
6.1 KiB
Go

package helix
import (
"net/http"
"regexp"
)
// WebhookSubscription ...
type WebhookSubscription struct {
Topic string `json:"topic"`
Callback string `json:"callback"`
ExpiresAt Time `json:"expires_at"`
}
// ManyWebhookSubscriptions ...
type ManyWebhookSubscriptions struct {
Total int `json:"total"`
WebhookSubscriptions []WebhookSubscription `json:"data"`
Pagination Pagination `json:"pagination"`
}
// WebhookSubscriptionsResponse ...
type WebhookSubscriptionsResponse struct {
ResponseCommon
Data ManyWebhookSubscriptions
}
// WebhookSubscriptionsParams ...
type WebhookSubscriptionsParams struct {
After string `query:"after"`
First int `query:"first,20"` // Limit 100
}
// GetWebhookSubscriptions gets webhook subscriptions, in order of expiration.
// Requires an app access token.
func (c *Client) GetWebhookSubscriptions(params *WebhookSubscriptionsParams) (*WebhookSubscriptionsResponse, error) {
resp, err := c.get("/webhooks/subscriptions", &ManyWebhookSubscriptions{}, params)
if err != nil {
return nil, err
}
webhooks := &WebhookSubscriptionsResponse{}
webhooks.StatusCode = resp.StatusCode
webhooks.Header = resp.Header
webhooks.Error = resp.Error
webhooks.ErrorStatus = resp.ErrorStatus
webhooks.ErrorMessage = resp.ErrorMessage
webhooks.Data.Total = resp.Data.(*ManyWebhookSubscriptions).Total
webhooks.Data.WebhookSubscriptions = resp.Data.(*ManyWebhookSubscriptions).WebhookSubscriptions
webhooks.Data.Pagination = resp.Data.(*ManyWebhookSubscriptions).Pagination
return webhooks, nil
}
// WebhookSubscriptionResponse ...
type WebhookSubscriptionResponse struct {
ResponseCommon
}
// WebhookSubscriptionPayload ...
type WebhookSubscriptionPayload struct {
Mode string `json:"hub.mode"`
Topic string `json:"hub.topic"`
Callback string `json:"hub.callback"`
LeaseSeconds int `json:"hub.lease_seconds,omitempty"`
Secret string `json:"hub.secret,omitempty"`
}
// PostWebhookSubscription ...
func (c *Client) PostWebhookSubscription(payload *WebhookSubscriptionPayload) (*WebhookSubscriptionResponse, error) {
resp, err := c.post("/webhooks/hub", nil, payload)
if err != nil {
return nil, err
}
webhook := &WebhookSubscriptionResponse{}
webhook.StatusCode = resp.StatusCode
webhook.Header = resp.Header
webhook.Error = resp.Error
webhook.ErrorStatus = resp.ErrorStatus
webhook.ErrorMessage = resp.ErrorMessage
return webhook, nil
}
// Regular expressions used for parsing webhook link headers
var (
UserFollowsRegexp = regexp.MustCompile("helix/users/follows\\?first=1(&from_id=(?P<from_id>\\d+))?(&to_id=(?P<to_id>\\d+))?>")
StreamChangedRegexp = regexp.MustCompile("helix/streams\\?user_id=(?P<user_id>\\d+)>")
UserChangedRegexp = regexp.MustCompile("helix/users\\?id=(?P<id>\\d+)>")
GameAnalyticsRegexp = regexp.MustCompile("helix/analytics\\?game_id=(?P<game_id>\\w+)>")
ExtensionAnalyticsRegexp = regexp.MustCompile("helix/analytics\\?extension_id=(?P<extension_id>\\w+)>")
)
// WebhookTopic is a topic that relates to a specific webhook event.
type WebhookTopic int
// Enumerated webhook topics
const (
UserFollowsTopic WebhookTopic = iota
StreamChangedTopic
UserChangedTopic
GameAnalyticsTopic
ExtensionAnalyticsTopic
)
// GetWebhookTopicFromRequest inspects the "Link" request header to
// determine if it matches against any recognised webhooks topics.
// The matched topic is returned. Otherwise -1 is returned.
func GetWebhookTopicFromRequest(req *http.Request) WebhookTopic {
header := getLinkHeaderFromWebhookRequest(req)
if UserFollowsRegexp.MatchString(header) {
return UserFollowsTopic
}
if StreamChangedRegexp.MatchString(header) {
return StreamChangedTopic
}
if UserChangedRegexp.MatchString(header) {
return UserChangedTopic
}
if GameAnalyticsRegexp.MatchString(header) {
return GameAnalyticsTopic
}
if ExtensionAnalyticsRegexp.MatchString(header) {
return ExtensionAnalyticsTopic
}
return -1
}
// GetWebhookTopicValuesFromRequest inspects the "Link" request header to
// determine if it matches against any recognised webhooks topics and
// returns the unique values specified in the header.
//
// For example, say we receive a "User Follows" webhook event from Twitch.
// Its "Link" header value look likes the following:
//
// <https://api.twitch.tv/helix/webhooks/hub>; rel="hub", <https://api.twitch.tv/helix/users/follows?first=1&from_id=111116&to_id=22222>; rel="self"
//
// From which GetWebhookTopicValuesFromRequest will return a map with the
// values of from_id and to_id:
//
// map[from_id:111116 to_id:22222]
//
// This is particularly useful for webhooks events that do not have a distinguishable
// JSON payload, such as the "Stream Changed" down event.
//
// Additionally, if topic is not known you can pass -1 as its value and
func GetWebhookTopicValuesFromRequest(req *http.Request, topic WebhookTopic) map[string]string {
values := make(map[string]string)
webhookTopic := topic
header := getLinkHeaderFromWebhookRequest(req)
// Webhook topic may not be known, so let's attempt to
// determine its value based on the request.
if webhookTopic < 0 && header != "" {
webhookTopic = GetWebhookTopicFromRequest(req)
}
switch webhookTopic {
case UserFollowsTopic:
values = findStringSubmatchMap(UserFollowsRegexp, header)
case StreamChangedTopic:
values = findStringSubmatchMap(StreamChangedRegexp, header)
case UserChangedTopic:
values = findStringSubmatchMap(UserChangedRegexp, header)
case GameAnalyticsTopic:
values = findStringSubmatchMap(GameAnalyticsRegexp, header)
case ExtensionAnalyticsTopic:
values = findStringSubmatchMap(ExtensionAnalyticsRegexp, header)
}
return values
}
func getLinkHeaderFromWebhookRequest(req *http.Request) string {
return req.Header.Get("link")
}
func findStringSubmatchMap(r *regexp.Regexp, s string) map[string]string {
captures := make(map[string]string)
match := r.FindStringSubmatch(s)
if match == nil {
return captures
}
for i, name := range r.SubexpNames() {
if i == 0 || name == "" {
continue
}
captures[name] = match[i]
}
return captures
}