mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-20 11:51:17 +00:00
[templating] Add currentVOD
function
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
740a71a173
commit
19038dbc6e
4 changed files with 215 additions and 2 deletions
|
@ -165,6 +165,19 @@ Example:
|
||||||
* 1 6
|
* 1 6
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `currentVOD`
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ currentVOD .channel }}
|
||||||
|
* https://www.twitch.tv/videos/123456789
|
||||||
|
```
|
||||||
|
|
||||||
### `displayName`
|
### `displayName`
|
||||||
|
|
||||||
Returns the display name the specified user set for themselves
|
Returns the display name the specified user set for themselves
|
||||||
|
@ -467,7 +480,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
||||||
< Your int this hour: 41%
|
< Your int this hour: 23%
|
||||||
```
|
```
|
||||||
|
|
||||||
### `spotifyCurrentPlaying`
|
### `spotifyCurrentPlaying`
|
||||||
|
|
|
@ -116,7 +116,7 @@ SocketMessage received for every event and passed to the new `(eventObj) => { ..
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [event_id] | <code>Number</code> | UID of the event used to re-trigger an event |
|
| [event_id] | <code>String</code> | UID of the event used to re-trigger an event |
|
||||||
| [is_live] | <code>Boolean</code> | Whether the event was sent through a replay (false) or occurred live (true) |
|
| [is_live] | <code>Boolean</code> | Whether the event was sent through a replay (false) or occurred live (true) |
|
||||||
| [reason] | <code>String</code> | Reason of this message (one of `bulk-replay`, `live-event`, `single-replay`) |
|
| [reason] | <code>String</code> | Reason of this message (one of `bulk-replay`, `live-event`, `single-replay`) |
|
||||||
| [time] | <code>String</code> | RFC3339 timestamp of the event |
|
| [time] | <code>String</code> | RFC3339 timestamp of the event |
|
||||||
|
|
50
internal/template/twitch/videos.go
Normal file
50
internal/template/twitch/videos.go
Normal file
|
@ -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",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
150
pkg/twitch/videos.go
Normal file
150
pkg/twitch/videos.go
Normal file
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in a new issue