-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[templating] Add
currentVOD
function
Signed-off-by: Knut Ahlers <[email protected]>
- Loading branch information
Showing
4 changed files
with
215 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package twitch | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" | ||
"github.com/Luzifer/twitch-bot/v3/plugins" | ||
) | ||
|
||
func init() { | ||
regFn = append( | ||
regFn, | ||
tplTwitchCurrentVOD, | ||
) | ||
} | ||
|
||
func tplTwitchCurrentVOD(args plugins.RegistrationArguments) { | ||
args.RegisterTemplateFunction("currentVOD", plugins.GenericTemplateFunctionGetter(func(username string) (string, error) { | ||
si, err := args.GetTwitchClient().GetCurrentStreamInfo(context.Background(), strings.TrimLeft(username, "#")) | ||
if err != nil { | ||
return "", fmt.Errorf("getting stream info: %w", err) | ||
} | ||
|
||
vids, err := args.GetTwitchClient().GetVideos(context.TODO(), twitch.GetVideoOpts{ | ||
UserID: si.UserID, | ||
}) | ||
if err != nil { | ||
return "", fmt.Errorf("getting videos: %w", err) | ||
} | ||
|
||
for _, v := range vids { | ||
if v.StreamID == nil || *v.StreamID != si.ID { | ||
continue | ||
} | ||
|
||
return v.URL, nil | ||
} | ||
|
||
return "", fmt.Errorf("no matching VOD found") | ||
}), plugins.TemplateFuncDocumentation{ | ||
Description: "Returns the VOD of the currently running stream in the given channel (causes an error if no current stream / VOD is found)", | ||
Syntax: "currentVOD <username>", | ||
Example: &plugins.TemplateFuncDocumentationExample{ | ||
Template: `{{ currentVOD .channel }}`, | ||
FakedOutput: "https://www.twitch.tv/videos/123456789", | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package twitch | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/mitchellh/hashstructure/v2" | ||
) | ||
|
||
type ( | ||
// GetVideoOpts contain the query parameter for the GetVideos query | ||
// | ||
// See https://dev.twitch.tv/docs/api/reference/#get-videos for details | ||
GetVideoOpts struct { | ||
ID string // Required: Exactly one of ID, UserID, GameID | ||
UserID string // Required: Exactly one of ID, UserID, GameID | ||
GameID string // Required: Exactly one of ID, UserID, GameID | ||
Language string // Optional: Use only with GameID | ||
Period GetVideoOptsPeriod // Optional: Use only with GameID or UserID | ||
Sort GetVideoOptsSort // Optional: Use only with GameID or UserID | ||
Type GetVideoOptsType // Optional: Use only with GameID or UserID | ||
First int64 // Optional: Use only with GameID or UserID | ||
After string // Optional: Use only with UserID | ||
Before string // Optional: Use only with UserID | ||
} | ||
|
||
// GetVideoOptsPeriod represents a filter used to filter the list of | ||
// videos by when they were published | ||
GetVideoOptsPeriod string | ||
// GetVideoOptsSort represents the order to sort the returned videos in | ||
GetVideoOptsSort string | ||
// GetVideoOptsType represents a filter used to filter the list of | ||
// videos by the video's type | ||
GetVideoOptsType string | ||
|
||
// Video contains information about a published video | ||
Video struct { | ||
ID string `json:"id"` | ||
StreamID *string `json:"stream_id"` | ||
UserID string `json:"user_id"` | ||
UserLogin string `json:"user_login"` | ||
UserName string `json:"user_name"` | ||
Title string `json:"title"` | ||
Description string `json:"description"` | ||
CreatedAt time.Time `json:"created_at"` | ||
PublishedAt time.Time `json:"published_at"` | ||
URL string `json:"url"` | ||
ThumbnailURL string `json:"thumbnail_url"` | ||
Viewable string `json:"viewable"` | ||
ViewCount int64 `json:"view_count"` | ||
Language string `json:"language"` | ||
Type string `json:"type"` | ||
Duration string `json:"duration"` | ||
MutedSegments []struct { | ||
Duration int64 `json:"duration"` | ||
Offset int64 `json:"offset"` | ||
} `json:"muted_segments"` | ||
} | ||
) | ||
|
||
// List of filters for GetVideoOpts.Period | ||
const ( | ||
GetVideoOptsPeriodAll GetVideoOptsPeriod = "all" | ||
GetVideoOptsPeriodDay GetVideoOptsPeriod = "day" | ||
GetVideoOptsPeriodMonth GetVideoOptsPeriod = "month" | ||
GetVideoOptsPeriodWeek GetVideoOptsPeriod = "week" | ||
) | ||
|
||
// List of sort options for GetVideoOpts.Sort | ||
const ( | ||
GetVideoOptsSortTime GetVideoOptsSort = "time" | ||
GetVideoOptsSortTrending GetVideoOptsSort = "trending" | ||
GetVideoOptsSortViews GetVideoOptsSort = "views" | ||
) | ||
|
||
// List of types for GetVideoOpts.Type | ||
const ( | ||
GetVideoOptsTypeAll GetVideoOptsType = "all" | ||
GetVideoOptsTypeArchive GetVideoOptsType = "archive" | ||
GetVideoOptsTypeHighlight GetVideoOptsType = "highlight" | ||
GetVideoOptsTypeUpload GetVideoOptsType = "upload" | ||
) | ||
|
||
// GetVideos fetches information about one or more published videos | ||
func (c *Client) GetVideos(ctx context.Context, opts GetVideoOpts) (videos []Video, err error) { | ||
optsCacheKey, err := opts.cacheKey() | ||
if err != nil { | ||
return nil, fmt.Errorf("getting opts cache key: %w", err) | ||
} | ||
|
||
cacheKey := []string{"currentVideos", optsCacheKey} | ||
if vids := c.apiCache.Get(cacheKey); vids != nil { | ||
return vids.([]Video), nil | ||
} | ||
|
||
var payload struct { | ||
Data []Video `json:"data"` | ||
} | ||
|
||
if err := c.Request(ctx, ClientRequestOpts{ | ||
AuthType: AuthTypeAppAccessToken, | ||
Method: http.MethodGet, | ||
OKStatus: http.StatusOK, | ||
Out: &payload, | ||
URL: fmt.Sprintf("https://api.twitch.tv/helix/videos?%s", opts.queryParams()), | ||
}); err != nil { | ||
return nil, fmt.Errorf("requesting videos: %w", err) | ||
} | ||
|
||
// Videos can be changed at any moment, cache for a short period of time | ||
c.apiCache.Set(cacheKey, twitchMinCacheTime, payload.Data) | ||
|
||
return payload.Data, nil | ||
} | ||
|
||
func (g GetVideoOpts) cacheKey() (string, error) { | ||
h, err := hashstructure.Hash(g, hashstructure.FormatV2, nil) | ||
if err != nil { | ||
return "", fmt.Errorf("hashing opts: %w", err) | ||
} | ||
|
||
return strconv.FormatUint(h, 10), nil | ||
} | ||
|
||
func (g GetVideoOpts) queryParams() string { | ||
params := url.Values{} | ||
|
||
for k, v := range map[string]string{ | ||
"id": g.ID, | ||
"user_id": g.UserID, | ||
"game_id": g.GameID, | ||
"language": g.Language, | ||
"period": string(g.Period), | ||
"sort": string(g.Sort), | ||
"type": string(g.Type), | ||
"first": strconv.FormatInt(g.First, 10), | ||
"after": g.After, | ||
"before": g.Before, | ||
} { | ||
if v != "" && v != "0" { | ||
params.Set(k, v) | ||
} | ||
} | ||
|
||
return params.Encode() | ||
} |