Add twitch point song requests to the bot
This commits adds new feature for galchedbot: video requests in the twitch chat via highlighted chat messages. This messages parsed by the bot and added to the video queue, that can be accessed by the dedicated web server. Video queue requires authorization based on random token added to the cookies.
This commit is contained in:
parent
65fc1ccad4
commit
f0f31a8415
18 changed files with 813 additions and 13 deletions
|
@ -55,6 +55,7 @@ func (h *SubdayListHandler) Handle(s *discordgo.Session, m *discordgo.MessageCre
|
|||
message += fmt.Sprintf(" **- %s** от _%s_\n", game, nickname)
|
||||
}
|
||||
}
|
||||
message += "\nВсе команды бота: !galched\n"
|
||||
SendMessage(s, m, strings.Trim(message, "\n"))
|
||||
}
|
||||
|
||||
|
@ -154,7 +155,8 @@ func (h *SubdayHistoryHandler) Handle(s *discordgo.Session, m *discordgo.Message
|
|||
"**26.01.19**: _Disney’s Aladdin_ -> _~~Gothic~~_ -> _Scrapland_ -> _Donut County_\n" +
|
||||
"**24.02.19**: _Tetris 99_ -> _~~Bully~~_ -> _~~GTA: Vice City~~_\n" +
|
||||
"**02.06.19**: _Spec Ops: The Line_ -> _Escape from Tarkov_\n" +
|
||||
"**28.07.19**: _Crypt of the Necrodancer_ -> _My Friend Pedro_ -> _Ape Out_\n"
|
||||
"**28.07.19**: _Crypt of the Necrodancer_ -> _My Friend Pedro_ -> _Ape Out_\n" +
|
||||
"\nВсе команды бота: !galched\n"
|
||||
SendMessage(s, m, message)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
version = "4.2.0"
|
||||
version = "5.0.0"
|
||||
twitchUser = "galchedbot"
|
||||
twitchIRCRoom = "galched"
|
||||
discordTokenPath = "./tokens/.discordtoken"
|
||||
twitchTokenPath = "./tokens/.twitchtoken"
|
||||
subdayDataPath = "./backups/subday"
|
||||
youtubeTokenPath = "./tokens/.youtubetoken"
|
||||
webLoginsPath = "./tokens/.weblogins"
|
||||
|
||||
// Permitted roles in discord for subday
|
||||
subRole1 = "433672344737677322"
|
||||
subRole2 = "433680494635515904"
|
||||
galchedRole = "301467455497175041"
|
||||
smorcRole = "301470784491356172"
|
||||
|
||||
defaultQueueAddr = ":8888"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -36,10 +41,14 @@ type (
|
|||
TwitchUser string
|
||||
TwitchIRCRoom string
|
||||
TwitchToken string
|
||||
YoutubeToken string
|
||||
SubdayDataPath string
|
||||
PermittedRoles []string
|
||||
DiscordVoiceChannel string
|
||||
Songs []SongInfo
|
||||
|
||||
QueueAddress string
|
||||
LoginUsers map[string]string
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -52,11 +61,27 @@ func New() (*Settings, error) {
|
|||
if err != nil {
|
||||
log.Print("settings: cannot read twitch token file", err)
|
||||
}
|
||||
youtubetoken, err := ioutil.ReadFile(youtubeTokenPath)
|
||||
if err != nil {
|
||||
log.Print("settings: cannot read twitch token file", err)
|
||||
}
|
||||
|
||||
webLogins := make(map[string]string)
|
||||
webLoginsRaw, err := ioutil.ReadFile(webLoginsPath)
|
||||
if err != nil {
|
||||
log.Print("settings: cannot read web login file", err)
|
||||
} else {
|
||||
err = json.Unmarshal(webLoginsRaw, &webLogins)
|
||||
if err != nil {
|
||||
log.Print("settings: cannot parse web login file", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &Settings{
|
||||
Version: version,
|
||||
DiscordToken: string(discordToken),
|
||||
TwitchToken: string(twitchToken),
|
||||
YoutubeToken: string(youtubetoken),
|
||||
TwitchUser: twitchUser,
|
||||
TwitchIRCRoom: twitchIRCRoom,
|
||||
SubdayDataPath: subdayDataPath,
|
||||
|
@ -66,7 +91,7 @@ func New() (*Settings, error) {
|
|||
{
|
||||
Path: "songs/polka.dca",
|
||||
Signature: "!song",
|
||||
Description: "сыграть гимн галчед (только для избранных",
|
||||
Description: "сыграть гимн галчед (только для избранных)",
|
||||
Permissions: []string{"AlexV", "Rummy_Quamox", "Lidiya_owl"},
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
|
@ -76,6 +101,14 @@ func New() (*Settings, error) {
|
|||
Description: "kreygasm",
|
||||
Timeout: 20 * time.Second,
|
||||
},
|
||||
{
|
||||
Path: "songs/st.dca",
|
||||
Signature: "!chiki",
|
||||
Description: "briki v damki",
|
||||
Timeout: 20 * time.Second,
|
||||
},
|
||||
},
|
||||
QueueAddress: defaultQueueAddr,
|
||||
LoginUsers: webLogins,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func DupHandler() PrivateMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *dupHandler) IsValid(m string) bool {
|
||||
func (h *dupHandler) IsValid(m *twitch.PrivateMessage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ type (
|
|||
}
|
||||
|
||||
PrivateMessageHandler interface {
|
||||
IsValid(string) bool
|
||||
IsValid(m *twitch.PrivateMessage) bool
|
||||
Handle(m *twitch.PrivateMessage, r Responser)
|
||||
}
|
||||
)
|
||||
|
|
23
modules/twitchat/logcheck.go
Normal file
23
modules/twitchat/logcheck.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package twitchat
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gempir/go-twitch-irc/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
logCheck struct{}
|
||||
)
|
||||
|
||||
func LogCheck() PrivateMessageHandler {
|
||||
return new(logCheck)
|
||||
}
|
||||
|
||||
func (h *logCheck) IsValid(m *twitch.PrivateMessage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *logCheck) Handle(m *twitch.PrivateMessage, r Responser) {
|
||||
log.Print("chat <", m.User.DisplayName, "> : ", m.Message)
|
||||
}
|
61
modules/twitchat/songrequest.go
Normal file
61
modules/twitchat/songrequest.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package twitchat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gempir/go-twitch-irc/v2"
|
||||
|
||||
"galched-bot/modules/youtube"
|
||||
)
|
||||
|
||||
const (
|
||||
songMsg = "!song"
|
||||
reqPrefix = "!req " // space in the end is important
|
||||
)
|
||||
|
||||
type (
|
||||
songRequest struct {
|
||||
r *youtube.Requester
|
||||
}
|
||||
)
|
||||
|
||||
func SongRequest(r *youtube.Requester) PrivateMessageHandler {
|
||||
return &songRequest{r: r}
|
||||
}
|
||||
|
||||
func (h *songRequest) IsValid(m *twitch.PrivateMessage) bool {
|
||||
return (strings.HasPrefix(m.Message, reqPrefix) && m.Tags["msg-id"] == "highlighted-message") ||
|
||||
strings.TrimSpace(m.Message) == songMsg
|
||||
// return strings.HasPrefix(m.Message, reqPrefix) || strings.TrimSpace(m.Message) == songMsg
|
||||
}
|
||||
|
||||
func (h *songRequest) Handle(m *twitch.PrivateMessage, r Responser) {
|
||||
if strings.TrimSpace(m.Message) == "!song" {
|
||||
list := h.r.List()
|
||||
if len(list) > 0 {
|
||||
line := fmt.Sprintf("Сейчас играет: <%s>", list[0].Title)
|
||||
r.Say(m.Channel, line)
|
||||
} else {
|
||||
r.Say(m.Channel, "Очередь видео пуста")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
query := strings.TrimPrefix(m.Message, "!req ")
|
||||
if len(query) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
chatMsg, err := h.r.AddVideo(query, m.User.DisplayName)
|
||||
if err != nil {
|
||||
log.Printf("yt: cannot add song from msg <%s>, err: %v", m.Message, err)
|
||||
if len(chatMsg) > 0 {
|
||||
r.Say(m.Channel, m.User.DisplayName+" "+chatMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
r.Say(m.Channel, m.User.DisplayName+" добавил "+chatMsg)
|
||||
return
|
||||
}
|
|
@ -2,6 +2,7 @@ package twitchat
|
|||
|
||||
import (
|
||||
"galched-bot/modules/settings"
|
||||
"galched-bot/modules/youtube"
|
||||
|
||||
"github.com/gempir/go-twitch-irc/v2"
|
||||
)
|
||||
|
@ -14,12 +15,14 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func New(s *settings.Settings) (*TwitchIRC, error) {
|
||||
func New(s *settings.Settings, r *youtube.Requester) (*TwitchIRC, error) {
|
||||
var irc = new(TwitchIRC)
|
||||
|
||||
irc.username = s.TwitchUser
|
||||
|
||||
irc.handlers = append(irc.handlers, DupHandler())
|
||||
irc.handlers = append(irc.handlers, SongRequest(r))
|
||||
// irc.handlers = append(irc.handlers, LogCheck())
|
||||
|
||||
irc.chat = twitch.NewClient(s.TwitchUser, s.TwitchToken)
|
||||
irc.chat.OnPrivateMessage(irc.PrivateMessageHandler)
|
||||
|
@ -45,7 +48,7 @@ func (c *TwitchIRC) PrivateMessageHandler(msg twitch.PrivateMessage) {
|
|||
return
|
||||
}
|
||||
for i := range c.handlers {
|
||||
if c.handlers[i].IsValid(msg.Message) {
|
||||
if c.handlers[i].IsValid(&msg) {
|
||||
c.handlers[i].Handle(&msg, c.chat)
|
||||
}
|
||||
}
|
||||
|
|
159
modules/web/server.go
Normal file
159
modules/web/server.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"galched-bot/modules/settings"
|
||||
"galched-bot/modules/youtube"
|
||||
)
|
||||
|
||||
type (
|
||||
WebServer struct {
|
||||
server http.Server
|
||||
r *youtube.Requester
|
||||
|
||||
users map[string]string
|
||||
authed map[string]struct{}
|
||||
}
|
||||
)
|
||||
|
||||
func New(s *settings.Settings, r *youtube.Requester) *WebServer {
|
||||
srv := http.Server{
|
||||
Addr: s.QueueAddress,
|
||||
}
|
||||
|
||||
webServer := &WebServer{
|
||||
server: srv,
|
||||
r: r,
|
||||
|
||||
users: s.LoginUsers,
|
||||
authed: make(map[string]struct{}, 10),
|
||||
}
|
||||
|
||||
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != http.MethodGet {
|
||||
return
|
||||
}
|
||||
|
||||
if webServer.IsAuthorized(request) {
|
||||
http.ServeFile(writer, request, "web/index.html")
|
||||
} else {
|
||||
http.Redirect(writer, request, "/login", 301)
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/login", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodGet {
|
||||
http.ServeFile(writer, request, "web/login.html")
|
||||
return
|
||||
} else if request.Method != http.MethodPost {
|
||||
return
|
||||
}
|
||||
|
||||
login := request.FormValue("login")
|
||||
pwd := request.FormValue("password")
|
||||
|
||||
log.Print("web: trying to log in with user: ", login)
|
||||
|
||||
if webServer.IsRegistered(login, pwd) {
|
||||
webServer.Authorize(writer)
|
||||
} else {
|
||||
log.Print("web: incorrect password attempt")
|
||||
}
|
||||
http.Redirect(writer, request, "/", http.StatusSeeOther)
|
||||
})
|
||||
|
||||
http.HandleFunc("/scripts.js", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != http.MethodGet {
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(writer, request, "web/scripts.js")
|
||||
})
|
||||
|
||||
http.HandleFunc("/style.css", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != http.MethodGet {
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(writer, request, "web/style.css")
|
||||
})
|
||||
|
||||
http.HandleFunc("/queue", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if !webServer.IsAuthorized(request) {
|
||||
http.Error(writer, "not authorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
switch request.Method {
|
||||
case http.MethodGet:
|
||||
case http.MethodPost:
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Print("web: cannot read body msg, %v", err)
|
||||
return
|
||||
}
|
||||
id := string(body)
|
||||
if len(id) != youtube.YoutubeIDLength && len(id) > 0 {
|
||||
log.Printf("web: incorrect data in body, <%s>", id)
|
||||
return
|
||||
}
|
||||
r.Remove(id)
|
||||
default:
|
||||
return
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
json.NewEncoder(writer).Encode(webServer.r.List())
|
||||
})
|
||||
|
||||
return webServer
|
||||
}
|
||||
|
||||
func (s WebServer) Start() error {
|
||||
go func() {
|
||||
s.server.ListenAndServe()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s WebServer) Stop(ctx context.Context) error {
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (s WebServer) IsAuthorized(request *http.Request) bool {
|
||||
if cookie, err := request.Cookie("session"); err == nil {
|
||||
if _, ok := s.authed[cookie.Value]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s WebServer) IsRegistered(login, pwd string) bool {
|
||||
return s.users[login] == pwd
|
||||
}
|
||||
|
||||
func (s WebServer) Authorize(response http.ResponseWriter) {
|
||||
var byteKey = make([]byte, 16)
|
||||
rand.Read(byteKey[:])
|
||||
stringKey := hex.EncodeToString(byteKey)
|
||||
|
||||
s.authed[stringKey] = struct{}{}
|
||||
log.Print("web: authenticated new user")
|
||||
|
||||
expires := time.Now().AddDate(0, 1, 0)
|
||||
http.SetCookie(response, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: stringKey,
|
||||
Path: "/",
|
||||
Expires: expires,
|
||||
})
|
||||
}
|
195
modules/youtube/requester.go
Normal file
195
modules/youtube/requester.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package youtube
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/youtube/v3"
|
||||
|
||||
"galched-bot/modules/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
YoutubeIDLength = 11
|
||||
youtubeRegexpID = `^.*((youtu.be\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?\s]*).*`
|
||||
)
|
||||
|
||||
var (
|
||||
urlRegex = regexp.MustCompile(youtubeRegexpID)
|
||||
durationRegex = regexp.MustCompile(`P(?P<years>\d+Y)?(?P<months>\d+M)?(?P<days>\d+D)?T?(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`)
|
||||
)
|
||||
|
||||
type (
|
||||
Video struct {
|
||||
ID string
|
||||
Title string
|
||||
From string
|
||||
Duration string
|
||||
|
||||
Upvotes uint64
|
||||
Downvotes uint64
|
||||
Views uint64
|
||||
}
|
||||
|
||||
Requester struct {
|
||||
mu *sync.RWMutex
|
||||
|
||||
srv *youtube.Service
|
||||
requests []Video
|
||||
}
|
||||
)
|
||||
|
||||
func New(ctx context.Context, s *settings.Settings) (*Requester, error) {
|
||||
srv, err := youtube.NewService(ctx, option.WithAPIKey(s.YoutubeToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Requester{
|
||||
mu: new(sync.RWMutex),
|
||||
srv: srv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Requester) AddVideo(query, from string) (string, error) {
|
||||
var (
|
||||
id string
|
||||
err error
|
||||
)
|
||||
|
||||
// try parse video id from the query
|
||||
id, err = videoID(query)
|
||||
if err != nil {
|
||||
// if we can't fo that, then search for the query
|
||||
resp, err := r.srv.Search.List("snippet").Type("video").MaxResults(1).Q(query).Do()
|
||||
if err != nil || len(resp.Items) == 0 || resp.Items[0].Id == nil {
|
||||
return "", fmt.Errorf("cannot parse youtube id: %w", err)
|
||||
}
|
||||
|
||||
id = resp.Items[0].Id.VideoId
|
||||
}
|
||||
|
||||
// get video info from api
|
||||
resp, err := r.srv.Videos.List("snippet,statistics,contentDetails").Id(id).Do()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot send request to youtube api: %w", err)
|
||||
}
|
||||
|
||||
// check if response have all required fields
|
||||
if len(resp.Items) == 0 {
|
||||
return "", errors.New("youtube api response does not contain items")
|
||||
}
|
||||
if resp.Items[0].Snippet == nil {
|
||||
return "", errors.New("youtube api response does not contain snippet")
|
||||
}
|
||||
if resp.Items[0].Statistics == nil {
|
||||
return "", errors.New("youtube api response does not contain statistics")
|
||||
}
|
||||
if resp.Items[0].ContentDetails == nil {
|
||||
return "", errors.New("youtube api response does not contain content details")
|
||||
}
|
||||
|
||||
// check length of the video not more than 5 minutes
|
||||
if parseDuration(resp.Items[0].ContentDetails.Duration) == 0 {
|
||||
err = errors.New("видео не должно быть трансляцией")
|
||||
return err.Error(), err
|
||||
}
|
||||
|
||||
// check video is not live
|
||||
if parseDuration(resp.Items[0].ContentDetails.Duration) > time.Minute*5 {
|
||||
err = errors.New("видео должно быть короче 5 минут")
|
||||
return err.Error(), err
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// check if video already in the queue
|
||||
for i := range r.requests {
|
||||
if r.requests[i].ID == id {
|
||||
err = errors.New("видео уже есть в очереди")
|
||||
return err.Error(), err
|
||||
}
|
||||
}
|
||||
|
||||
r.requests = append(r.requests, Video{
|
||||
ID: id,
|
||||
From: from,
|
||||
Duration: strings.ToLower(resp.Items[0].ContentDetails.Duration[2:]),
|
||||
Title: resp.Items[0].Snippet.Title,
|
||||
Upvotes: resp.Items[0].Statistics.LikeCount,
|
||||
Views: resp.Items[0].Statistics.ViewCount,
|
||||
Downvotes: resp.Items[0].Statistics.DislikeCount,
|
||||
})
|
||||
log.Printf("yt: added video < %s > from < %s >\n", resp.Items[0].Snippet.Title, from)
|
||||
|
||||
return resp.Items[0].Snippet.Title, nil
|
||||
}
|
||||
|
||||
func (r *Requester) List() []Video {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
result := make([]Video, len(r.requests))
|
||||
copy(result, r.requests)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Requester) Remove(id string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for i := range r.requests {
|
||||
if r.requests[i].ID == id {
|
||||
r.requests = append(r.requests[:i], r.requests[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func videoID(url string) (string, error) {
|
||||
result := urlRegex.FindStringSubmatch(url)
|
||||
|
||||
ln := len(result)
|
||||
if ln == 0 || len(result[ln-1]) != YoutubeIDLength {
|
||||
return "", fmt.Errorf("id haven't matched in \"%s\"", url)
|
||||
}
|
||||
|
||||
return result[ln-1], nil
|
||||
}
|
||||
|
||||
func parseDuration(str string) time.Duration {
|
||||
matches := durationRegex.FindStringSubmatch(str)
|
||||
|
||||
years := parseInt64(matches[1])
|
||||
months := parseInt64(matches[2])
|
||||
days := parseInt64(matches[3])
|
||||
hours := parseInt64(matches[4])
|
||||
minutes := parseInt64(matches[5])
|
||||
seconds := parseInt64(matches[6])
|
||||
|
||||
hour := int64(time.Hour)
|
||||
minute := int64(time.Minute)
|
||||
second := int64(time.Second)
|
||||
return time.Duration(years*24*365*hour + months*30*24*hour + days*24*hour + hours*hour + minutes*minute + seconds*second)
|
||||
}
|
||||
|
||||
func parseInt64(value string) int64 {
|
||||
if len(value) == 0 {
|
||||
return 0
|
||||
}
|
||||
parsed, err := strconv.Atoi(value[:len(value)-1])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int64(parsed)
|
||||
}
|
67
modules/youtube/requester_test.go
Normal file
67
modules/youtube/requester_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package youtube
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlayground(t *testing.T) {
|
||||
youtubeTokenPath := "../../tokens/.youtubetoken"
|
||||
youtubetoken, err := ioutil.ReadFile(youtubeTokenPath)
|
||||
if err != nil {
|
||||
t.Errorf("cannot read youtube token: %v", err)
|
||||
}
|
||||
_ = youtubetoken
|
||||
}
|
||||
|
||||
func TestFetchID(t *testing.T) {
|
||||
positiveCases := [][]string{
|
||||
{
|
||||
"https://www.youtube.com/watch?v=_v5IzvVTw7A&feature=feedrec_grec_index",
|
||||
"_v5IzvVTw7A",
|
||||
},
|
||||
{
|
||||
"https://www.youtube.com/watch?v=_v5IzvVTw7A#t=0m10s",
|
||||
"_v5IzvVTw7A",
|
||||
},
|
||||
{
|
||||
"https://www.youtube.com/embed/_v5IzvVTw7A?rel=0",
|
||||
"_v5IzvVTw7A",
|
||||
},
|
||||
{
|
||||
"https://www.youtube.com/watch?v=_v5IzvVTw7A ", // multiple spaces in the end
|
||||
"_v5IzvVTw7A",
|
||||
},
|
||||
{
|
||||
"https://youtu.be/_v5IzvVTw7A",
|
||||
"_v5IzvVTw7A",
|
||||
},
|
||||
{
|
||||
"https://www.youtube.com/watch?v=PCp2iXA1uLE&list=PLvx4lPhqncyf10ymYz8Ph8EId0cafzhdZ&index=2&t=0s",
|
||||
"PCp2iXA1uLE",
|
||||
},
|
||||
}
|
||||
|
||||
negativeCases := []string{
|
||||
"https://youtu.be/_vIzvVTw7A", // short video
|
||||
"https://vimeo.com/_v5IzvVTw7A", // incorrect domain
|
||||
"youtube prime video",
|
||||
}
|
||||
|
||||
for _, testCase := range positiveCases {
|
||||
res, err := videoID(testCase[0])
|
||||
if err != nil {
|
||||
t.Errorf("expecting error: <nil>, got: <%v>, url: %s\n", err, testCase[0])
|
||||
}
|
||||
if res != testCase[1] {
|
||||
t.Errorf("expecting result: %s, got: %s\n", testCase[0], res)
|
||||
}
|
||||
}
|
||||
|
||||
for _, testCase := range negativeCases {
|
||||
_, err := videoID(testCase)
|
||||
if err == nil {
|
||||
t.Error("expecting error: <non nil>, got: <nil>")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue