You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
5.0 KiB
Go
230 lines
5.0 KiB
Go
//TODO
|
|
// 1- Build model for basic youtube API answer
|
|
// We need to put the data into model using JSON Unmarshal
|
|
|
|
// 2- Test simple channel request based on user handle (we only need channel ID)
|
|
|
|
// 3- Now we need to get the content inside Items in each Answer
|
|
|
|
// 4- Get Playlists list from a channel
|
|
|
|
// 5- Get Videos(PlayListItems) lists from playlist
|
|
|
|
// DONE
|
|
//
|
|
// 6- Ignore empty results
|
|
|
|
// 7- Handle Pagination (not today :))
|
|
//
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
// Global variables
|
|
var (
|
|
YOUTUBE_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
|
|
YOUTUBE_API_ENDPOINT = "https://www.googleapis.com/youtube/v3/"
|
|
|
|
TEST_CHANNEL_HANDLE = "TotalHalibut"
|
|
)
|
|
|
|
/////////////////////////////
|
|
//////////// MODELS
|
|
///////////////////////////
|
|
|
|
// model for a generic youtube answer
|
|
// We only define the fields we want to capture
|
|
// from the json answer from the API
|
|
|
|
type PageInfo struct {
|
|
TotalResults int
|
|
ResultsPerPage int
|
|
}
|
|
|
|
type YoutubeAnswer struct {
|
|
Kind string
|
|
Etag string
|
|
Items []Item
|
|
PageInfo
|
|
|
|
// Will be used to get the actual we need (resources)
|
|
// Now we need to get the content inside Items in each Answer
|
|
// Let's build a type(model) for one Item
|
|
// interface{} means any type like void* in C
|
|
// Now we can use the ChannelItem type
|
|
}
|
|
|
|
type PlayList struct {
|
|
}
|
|
|
|
type ChannelItem struct {
|
|
}
|
|
|
|
type ResourceId struct {
|
|
VideoId string
|
|
}
|
|
|
|
type PlayListItemSnippet struct {
|
|
ResourceId ResourceId
|
|
}
|
|
|
|
type PlayListItem struct {
|
|
Snippet PlayListItemSnippet
|
|
}
|
|
|
|
// Our container type Item
|
|
// We will embed other type of Items here
|
|
// to extract data we want from our resources with json.Unmarshal
|
|
// Unmarshal should set to nil json fields which are not defined
|
|
// There might be a cleaner way to do this
|
|
// Ref on embedding: https://github.com/luciotato/golang-notes/blob/master/OOP.md#golang-embedding-is-akin-to-multiple-inheritance-with-non-virtual-methods
|
|
|
|
type Item struct {
|
|
Id string
|
|
ChannelItem
|
|
PlayList
|
|
PlayListItem
|
|
}
|
|
|
|
////////////////////////////
|
|
////// API Functions
|
|
///////////////////////////
|
|
|
|
// Helper function to build our youtube query URL
|
|
func buildUrl(resource string, params map[string]string) string {
|
|
|
|
// we build a url.Values object from a map of key,values
|
|
|
|
queryParams := url.Values{}
|
|
|
|
// Automatically add the API key params to all our calls
|
|
queryParams.Set("key", YOUTUBE_API_KEY)
|
|
|
|
for k, v := range params {
|
|
queryParams.Set(k, v)
|
|
}
|
|
|
|
// Build URL
|
|
queryUrl := fmt.Sprint(YOUTUBE_API_ENDPOINT, resource, "?", queryParams.Encode())
|
|
return queryUrl
|
|
}
|
|
|
|
// We can make this function more generic and reuse it for other resources
|
|
// Since youtube API sends the actual data inside ITEMS we make a func
|
|
// that returns the list of Items based on the resource and params we pass
|
|
// This func returns a list of any kind of Item type (channel, playlist, video ...)
|
|
|
|
func getResource(resource string, params map[string]string) []Item {
|
|
|
|
queryUrl := buildUrl(resource, params)
|
|
resp, err := http.Get(queryUrl)
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
data, _ := ioutil.ReadAll(resp.Body)
|
|
|
|
answer := &YoutubeAnswer{}
|
|
jsonErr := json.Unmarshal(data, answer)
|
|
|
|
if jsonErr != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// We need to make an type embedding so we can have polymorphism
|
|
// Interfaces are used to implement methods , my mistake
|
|
// We want to return a list of any "kind" if Item
|
|
|
|
return answer.Items
|
|
}
|
|
|
|
func getChannelId(channelHandle string) string {
|
|
|
|
// Use our buildUrl func
|
|
params := map[string]string{
|
|
"key": YOUTUBE_API_KEY,
|
|
"forUsername": channelHandle,
|
|
"part": "id",
|
|
}
|
|
|
|
queryUrl := buildUrl("channels", params)
|
|
|
|
// Test request
|
|
resp, err := http.Get(queryUrl)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
data, _ := ioutil.ReadAll(resp.Body)
|
|
answer := &YoutubeAnswer{}
|
|
jsonErr := json.Unmarshal(data, answer)
|
|
|
|
if jsonErr != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return answer.Items[0].Id
|
|
}
|
|
|
|
///////////////////////////////////
|
|
|
|
func main() {
|
|
|
|
// Get channel data
|
|
channelParams := map[string]string{
|
|
"forUsername": TEST_CHANNEL_HANDLE,
|
|
"part": "id",
|
|
}
|
|
channel := getResource("channels", channelParams)[0]
|
|
|
|
// Use channel Id to get list of playlists
|
|
|
|
playlistsParams := map[string]string{
|
|
"part": "id",
|
|
"channelId": channel.Id,
|
|
}
|
|
|
|
playlists := getResource("playlists", playlistsParams)
|
|
|
|
// Step 2 get list of playlists from channel
|
|
|
|
fmt.Printf("Id for channel %s is: %s\n", TEST_CHANNEL_HANDLE, channel.Id)
|
|
fmt.Println("_________Playlists IDS___________\n")
|
|
|
|
// Step 3 get list of videos inside each playlist
|
|
// Videos are PlayListItem objects
|
|
// We want the id of videos also (part=id,snippet)
|
|
|
|
for _, pl := range playlists {
|
|
fmt.Printf("\n%+v", pl)
|
|
|
|
fmt.Printf("playlist: %s\n", pl.Id)
|
|
|
|
playListItemsParams := map[string]string{
|
|
"part": "id,snippet",
|
|
"playlistId": pl.Id,
|
|
}
|
|
|
|
playListItems := getResource("playlistItems", playListItemsParams)
|
|
|
|
fmt.Printf("video IDs for playlist %s:\n", pl.Id)
|
|
|
|
for _, plI := range playListItems {
|
|
fmt.Printf("video id : %s\n", plI.PlayListItem.Snippet.ResourceId.VideoId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
}
|