newsbot-api/services/twitch.go
James Tombleson 333a4f5345
Features/twitch (#7)
* ran go mod tity

* updated formatting and moved to using some common errors

* some common source errors

* bumped go version

* a bulk of the work has been completed to parse twitch videos/users

* Added twitch config values

* added common errors

* moved the scheduler to its own package to get it out of main and also start adding tests

* added a new err for invalid author images

* Updated an error used

* tests updated to avoid name duplication

* new func to replace the cached source recor and updated getcontent to return collected posts

* updated scheduler ref

* moved to services/cron
2022-05-15 21:48:23 -07:00

230 lines
5.7 KiB
Go

package services
import (
"errors"
"fmt"
"strings"
"time"
//"log"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/services/config"
"github.com/nicklaw5/helix/v2"
)
type TwitchClient struct {
SourceRecord model.Sources
// config
monitorClips string
monitorVod string
// API Connection
api *helix.Client
}
var (
ErrTwitchClientIdMissing = errors.New("unable to find the client id for auth")
ErrTwitchClientSecretMissing = errors.New("unable to find the client secret for auth")
twitchScopes = "user:read:email"
)
func NewTwitchClient(source model.Sources) (TwitchClient, error) {
c := config.New()
id := c.GetConfig(config.TWITCH_CLIENT_ID)
if id == "" {
return TwitchClient{}, ErrTwitchClientIdMissing
}
secret := c.GetConfig(config.TWITCH_CLIENT_SECRET)
if secret == "" {
return TwitchClient{}, ErrTwitchClientSecretMissing
}
api, err := initTwitchApi(id, secret)
if err != nil {
return TwitchClient{}, nil
}
client := TwitchClient{
SourceRecord: source,
monitorClips: c.GetConfig(config.TWITCH_MONITOR_CLIPS),
monitorVod: c.GetConfig(config.TWITCH_MONITOR_VOD),
api: &api,
}
return client, nil
}
// Sets up the API connection to Twitch.
func initTwitchApi(ClientId string, ClientSecret string) (helix.Client, error) {
api, err := helix.NewClient(&helix.Options{
ClientID: ClientId,
ClientSecret: ClientSecret,
})
if err != nil {
return helix.Client{}, err
}
return *api, nil
}
// This will let you replace the bound source record to keep the same session alive.
func (tc TwitchClient) ReplaceSourceRecord(source model.Sources) {
tc.SourceRecord = source
}
// Invokes Logon request to the API
func (tc TwitchClient) Login() error {
token, err := tc.api.RequestAppAccessToken([]string{twitchScopes})
if err != nil {
return err
}
tc.api.SetAppAccessToken(token.Data.AccessToken)
return nil
}
func (tc TwitchClient) GetContent() ([]model.Articles, error) {
var items []model.Articles
user, err := tc.GetUserDetails()
if err != nil {
return items, err
}
posts, err := tc.GetPosts(user)
if err != nil {
return items, err
}
for _, video := range posts {
article := model.Articles{}
article.AuthorName, err = tc.ExtractAuthor(video)
if err != nil { return items, err }
article.AuthorImage, err = tc.ExtractAuthorImage(user)
if err != nil { return items, err }
article.Description, err = tc.ExtractDescription(video)
if err != nil {return items, err }
article.PubDate, err = tc.ExtractPubDate(video)
if err != nil { return items, err }
article.SourceID = tc.SourceRecord.ID
article.Tags, err = tc.ExtractTags(video, user)
if err != nil { return items, err }
article.Thumbnail, err = tc.ExtractThumbnail(video)
if err != nil { return items, err }
article.Title, err = tc.ExtractTitle(video)
if err != nil { return items, err }
article.Url, err = tc.ExtractUrl(video)
if err != nil { return items, err }
items = append(items, article)
}
return items, nil
}
func (tc TwitchClient) GetUserDetails() (helix.User, error) {
var blank helix.User
users, err := tc.api.GetUsers(&helix.UsersParams{
Logins: []string{tc.SourceRecord.Name},
})
if err != nil {
return blank, err
}
return users.Data.Users[0], nil
}
// This will reach out and collect the posts made by the user.
func (tc TwitchClient) GetPosts(user helix.User) ([]helix.Video, error) {
var blank []helix.Video
videos, err := tc.api.GetVideos(&helix.VideosParams{
UserID: user.ID,
})
if err != nil {
return blank, err
}
//log.Println(videos.Data.Videos)
return videos.Data.Videos, nil
}
func (tc TwitchClient) ExtractAuthor(post helix.Video) (string, error) {
if post.UserName == "" {
return "", ErrMissingAuthorName
}
return post.UserName, nil
}
func (tc TwitchClient) ExtractThumbnail(post helix.Video) (string, error) {
if post.ThumbnailURL == "" {
return "", ErrMissingThumbnail
}
var thumb = post.ThumbnailURL
thumb = strings.Replace(thumb, "%{width}", "600", -1)
thumb = strings.Replace(thumb, "%{height}", "400", -1)
return thumb, nil
}
func (tc TwitchClient) ExtractPubDate(post helix.Video) (time.Time, error) {
if post.PublishedAt == "" {
return time.Now(), ErrMissingPublishDate
}
pubDate, err := time.Parse("2006-01-02T15:04:05Z", post.PublishedAt)
if err != nil {
return time.Now(), err
}
return pubDate, nil
}
func (tc TwitchClient) ExtractDescription(post helix.Video) (string, error) {
// Check if the description is null but we have a title.
// The poster didnt add a description but this isnt an error.
if post.Description == "" && post.Title == "" {
return "", ErrMissingDescription
}
if post.Description == "" {
return "No description was given", nil
}
return post.Description, nil
}
// Extracts the avatar of the author with some validation.
func (tc TwitchClient) ExtractAuthorImage(user helix.User) (string, error) {
if user.ProfileImageURL == "" { return "", ErrMissingAuthorImage }
if !strings.Contains(user.ProfileImageURL, "-profile_image-") { return "", ErrInvalidAuthorImage }
return user.ProfileImageURL, nil
}
// Generate tags based on the video metadata.
// TODO Figure out how to query what game is played
func (tc TwitchClient) ExtractTags(post helix.Video, user helix.User) (string, error) {
res := fmt.Sprintf("twitch,%v,%v", post.Title, user.DisplayName)
return res, nil
}
// Extracts the title from a post with some validation.
func (tc TwitchClient) ExtractTitle(post helix.Video) (string, error) {
if post.Title == "" {
return "", errors.New("unable to find the title on the requested post")
}
return post.Title, nil
}
func (tc TwitchClient) ExtractUrl(post helix.Video) (string, error) {
if post.URL == "" { return "", ErrMissingUrl }
return post.URL, nil
}