2022-06-30 14:54:58 -07:00
|
|
|
package input
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
import (
|
2022-06-08 21:17:08 -07:00
|
|
|
"database/sql"
|
2022-05-15 21:48:23 -07:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-06-08 21:17:08 -07:00
|
|
|
"github.com/jtom38/newsbot/collector/database"
|
2022-05-15 21:48:23 -07:00
|
|
|
"github.com/jtom38/newsbot/collector/services/config"
|
|
|
|
"github.com/nicklaw5/helix/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type TwitchClient struct {
|
2022-06-08 21:17:08 -07:00
|
|
|
SourceRecord database.Source
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
// 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"
|
|
|
|
)
|
|
|
|
|
2022-06-08 21:17:08 -07:00
|
|
|
func NewTwitchClient() (TwitchClient, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
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{
|
2022-06-08 21:17:08 -07:00
|
|
|
//SourceRecord: &source,
|
2022-05-15 21:48:23 -07:00
|
|
|
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.
|
2022-06-08 21:17:08 -07:00
|
|
|
func (tc *TwitchClient) ReplaceSourceRecord(source database.Source) {
|
2022-05-15 21:48:23 -07:00
|
|
|
tc.SourceRecord = source
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invokes Logon request to the API
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) Login() error {
|
2022-05-15 21:48:23 -07:00
|
|
|
token, err := tc.api.RequestAppAccessToken([]string{twitchScopes})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tc.api.SetAppAccessToken(token.Data.AccessToken)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) GetContent() ([]database.Article, error) {
|
2022-06-08 21:17:08 -07:00
|
|
|
var items []database.Article
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
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 {
|
2022-06-08 21:17:08 -07:00
|
|
|
var article database.Article
|
2022-05-15 21:48:23 -07:00
|
|
|
|
2022-06-08 21:17:08 -07:00
|
|
|
AuthorName, err := tc.ExtractAuthor(video)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-06-08 21:17:08 -07:00
|
|
|
article.Authorname = sql.NullString{String: AuthorName}
|
2022-12-04 08:49:17 -08:00
|
|
|
|
2022-06-08 21:17:08 -07:00
|
|
|
Authorimage, err := tc.ExtractAuthorImage(user)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-06-08 21:17:08 -07:00
|
|
|
article.Authorimage = sql.NullString{String: Authorimage}
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
article.Description, err = tc.ExtractDescription(video)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
|
2022-06-08 21:17:08 -07:00
|
|
|
article.Pubdate, err = tc.ExtractPubDate(video)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
|
2022-06-08 21:17:08 -07:00
|
|
|
article.Sourceid = tc.SourceRecord.ID
|
2022-05-15 21:48:23 -07:00
|
|
|
article.Tags, err = tc.ExtractTags(video, user)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
article.Thumbnail, err = tc.ExtractThumbnail(video)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
article.Title, err = tc.ExtractTitle(video)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
|
|
|
|
2022-05-15 21:48:23 -07:00
|
|
|
article.Url, err = tc.ExtractUrl(video)
|
2022-12-04 08:49:17 -08:00
|
|
|
if err != nil {
|
|
|
|
return items, err
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
|
|
|
|
items = append(items, article)
|
|
|
|
}
|
|
|
|
|
|
|
|
return items, nil
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) GetUserDetails() (helix.User, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
var blank helix.User
|
|
|
|
|
|
|
|
users, err := tc.api.GetUsers(&helix.UsersParams{
|
|
|
|
Logins: []string{tc.SourceRecord.Name},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return blank, err
|
|
|
|
}
|
2022-07-12 15:28:31 -07:00
|
|
|
|
|
|
|
if len(users.Data.Users) == 0 {
|
|
|
|
return blank, errors.New("no results have been returned")
|
|
|
|
}
|
|
|
|
|
2022-05-15 21:48:23 -07:00
|
|
|
return users.Data.Users[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This will reach out and collect the posts made by the user.
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) GetPosts(user helix.User) ([]helix.Video, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractAuthor(post helix.Video) (string, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
if post.UserName == "" {
|
|
|
|
return "", ErrMissingAuthorName
|
|
|
|
}
|
|
|
|
return post.UserName, nil
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractThumbnail(post helix.Video) (string, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractPubDate(post helix.Video) (time.Time, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractDescription(post helix.Video) (string, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
// 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.
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractAuthorImage(user helix.User) (string, error) {
|
2022-12-04 08:49:17 -08:00
|
|
|
if user.ProfileImageURL == "" {
|
|
|
|
return "", ErrMissingAuthorImage
|
|
|
|
}
|
|
|
|
if !strings.Contains(user.ProfileImageURL, "-profile_image-") {
|
|
|
|
return "", ErrInvalidAuthorImage
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
return user.ProfileImageURL, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate tags based on the video metadata.
|
|
|
|
// TODO Figure out how to query what game is played
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractTags(post helix.Video, user helix.User) (string, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
res := fmt.Sprintf("twitch,%v,%v", post.Title, user.DisplayName)
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extracts the title from a post with some validation.
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractTitle(post helix.Video) (string, error) {
|
2022-05-15 21:48:23 -07:00
|
|
|
if post.Title == "" {
|
|
|
|
return "", errors.New("unable to find the title on the requested post")
|
|
|
|
}
|
|
|
|
return post.Title, nil
|
|
|
|
}
|
|
|
|
|
2022-07-12 15:28:31 -07:00
|
|
|
func (tc *TwitchClient) ExtractUrl(post helix.Video) (string, error) {
|
2022-12-04 08:49:17 -08:00
|
|
|
if post.URL == "" {
|
|
|
|
return "", ErrMissingUrl
|
|
|
|
}
|
2022-05-15 21:48:23 -07:00
|
|
|
return post.URL, nil
|
2022-12-04 08:49:17 -08:00
|
|
|
}
|