diff --git a/README.md b/README.md index 8e3aeef..3e44ae6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,10 @@ import ( ) func main() { - res := youtube.SearchVideos("Jim Yosef Hate You") + res := youtube.Search("Nora & Chris, Drenchill Remedy", youtube.SearchOptions{ + Type: "video", + Limit: 15 + }) fmt.Println(res) } diff --git a/parsers.go b/parsers.go new file mode 100644 index 0000000..7491a81 --- /dev/null +++ b/parsers.go @@ -0,0 +1,64 @@ +package youtubego + +import "fmt" + +func ParsePlaylist(data interface{}) PlaylistParser { + if data != nil { + return PlaylistParser{ + IsSuccess: false, + } + } else { + return PlaylistParser{ + IsSuccess: false, + } + } +} + +func ParseChannel(data interface{}) ChannelParser { + if data != nil { + navEndpoint := data.(map[string]interface{})["navigationEndpoint"] + url := fmt.Sprintf("https://www.youtube.com%s", navEndpoint.(map[string]interface{})["browseEndpoint"].(map[string]interface{})["canonicalBaseUrl"] || navEndpoint.(map[string]interface{})["commandMetadata"].(map[string]interface{})["webCommandMetadata"].(map[string]interface{})["url"]) + thumbnail := data.(map[string]interface{})["thumbnail"].(map[string]interface{})["thumbnails"] + + var out ChannelParser + out = ChannelParser{ + Id: data.(map[string]interface{})["channelId"].(string), + Url: url, + Name: data.(map[string]interface{})["title"].(map[string]interface{})["simpleText"].(string), + Icon: thumbnail.([]interface{})[len(thumbnail.([]interface{}))-1], + Subscribers: data.(map[string]interface{})["subscriberCountText"].(map[string]interface{})["simpleText"], + } + } else { + return ChannelParser{ + IsSuccess: false, + } + } +} + +func ParseVideo(data interface{}) VideoParser { + if data != nil { + thumbnail := data.(map[string]interface{})["thumbnail"].(map[string]interface{})["thumbnails"].([]interface{}) + + var out VideoParser + out = VideoParser{ + Video: Video{ + Id: data.(map[string]interface{})["videoId"].(string), + Title: data.(map[string]interface{})["title"].(map[string]interface{})["runs"].([]interface{})[0].(map[string]interface{})["text"].(string), + Url: fmt.Sprintf("https://www.youtube.com/watch?v=%s", data.(map[string]interface{})["videoId"].(string)), + Thumbnail: Thumbnail{ + Id: data.(map[string]interface{})["videoId"].(string), + Url: thumbnail[len(thumbnail)-1].(map[string]interface{})["url"].(string), + Width: fmt.Sprintf("%v", thumbnail[len(thumbnail)-1].(map[string]interface{})["width"]), + Height: fmt.Sprintf("%v", thumbnail[len(thumbnail)-1].(map[string]interface{})["height"]), + }, + }, + IsSuccess: true, + } + + return out + } else { + return VideoParser{ + IsSuccess: false, + } + } +} diff --git a/types.go b/types.go index 71f20b9..db1f3f3 100644 --- a/types.go +++ b/types.go @@ -1,20 +1,49 @@ package youtubego type Thumbnail struct { - Id string - Width string - Height string - Url string + Id, Width, Height, Url string } type Video struct { Thumbnail - Id string - Title string - Url string + Id, Title, Url string } type VideoParser struct { Video IsSuccess bool } + +type Channel struct { + Thumbnail + Id, Url, Name, Subscribers string + Verified bool +} + +type ChannelParser struct { + Channel + IsSuccess bool +} + +type Playlist struct { + Thumbnail + Channel + Id, title string + Videos int +} + +type PlaylistParser struct { + Playlist + IsSuccess bool +} + +type SearchResult struct { + Video + Playlist + Channels +} + +type SearchOptions struct { + Limit int + Type string +} diff --git a/util.go b/util.go index e8fdae5..eeabd08 100644 --- a/util.go +++ b/util.go @@ -10,7 +10,7 @@ import ( "strings" ) -func CreateRequest(searchWord string) []Video { +func CreateRequest(searchWord string, options SearchOptions) []SearchResult { Url, err := url.Parse("http://youtube.com/results") if err != nil { @@ -19,7 +19,14 @@ func CreateRequest(searchWord string) []Video { query := url.Values{} query.Add("search_query", searchWord) - query.Add("sp", "EgIQAQ%253D%253D") + + if strings.ToLower(options.Type) == "video" { + query.Add("sp", "EgIQAQ%253D%253D") + } else if strings.ToLower(options.Type) == "playlist" { + query.Add("sp", "EgIQAw%253D%253D") + } else if strings.ToLower(options.Type) == "channel" { + query.Add("sp", "EgIQAg%253D%253D") + } Url.RawQuery = query.Encode() @@ -36,7 +43,18 @@ func CreateRequest(searchWord string) []Video { log.Fatalf("status code error: %d %s", res.StatusCode, res.Status) } bodyResp, err := io.ReadAll(res.Body) - html := []byte(ParseHTML(string(bodyResp))) + if err != nil { + log.Fatalf("Cannot read the body stream.") + } + + return ParseHTML(bodyResp, options.Limit) +} + +func ParseHTML(html string, limit int) []SearchResult { + index := len(strings.Split(html, `{"itemSectionRenderer":`)) - 1 + items := strings.Split(html, `{"itemSectionRenderer":`)[index] + parsed := strings.Split(items, `},{"continuationItemRenderer":{`)[0] + html := []byte(ParseHTML(string(parsed))) var out map[string]interface{} err = json.Unmarshal(html, &out) @@ -45,51 +63,37 @@ func CreateRequest(searchWord string) []Video { panic("Something went wrong, the problem was encountered while analyzing JSON!") } arr := out["contents"].([]interface{}) - output := []Video{} + output := []SearchResult{} for i := 0; len(arr) > i; i++ { - sdata := arr[i].(map[string]interface{})["videoRenderer"] - parsedVideo := ParseVideo(sdata) - - if parsedVideo.IsSuccess { - output = append(output, parsedVideo.Video) + sdata := arr[i].(map[string]interface{}) + + if sdata["videoRenderer"] { + parsed := ParseVideo(sdata["videoRenderer"]) + + if parsed.IsSuccess { + output = append(output, &SearchResult{ + Video: parsed.Video, + }) + } + } else if sdata["playlistRenderer"] { + parsed := ParsePlaylist(sdata["playlistRenderer"]) + + if parsed.IsSuccess { + output = append(output, &SearchResult{ + Playlist: parsed.Playlist, + }) + } + } else if sdata["channelRenderer"] { + parsed := ParseVideo(sdata["channelRenderer"]) + + if parsed.IsSuccess { + output = append(output, &SearchResult{ + Channel: parsed.Channel, + }) + } } } return output } - -func ParseHTML(html string) string { - index := len(strings.Split(html, `{"itemSectionRenderer":`)) - 1 - items := strings.Split(html, `{"itemSectionRenderer":`)[index] - - return strings.Split(items, `},{"continuationItemRenderer":{`)[0] -} - -func ParseVideo(data interface{}) VideoParser { - if data != nil { - thumbnail := data.(map[string]interface{})["thumbnail"].(map[string]interface{})["thumbnails"].([]interface{}) - - var out VideoParser - out = VideoParser{ - Video: Video{ - Id: data.(map[string]interface{})["videoId"].(string), - Title: data.(map[string]interface{})["title"].(map[string]interface{})["runs"].([]interface{})[0].(map[string]interface{})["text"].(string), - Url: fmt.Sprintf("https://www.youtube.com/watch?v=%s", data.(map[string]interface{})["videoId"].(string)), - Thumbnail: Thumbnail{ - Id: data.(map[string]interface{})["videoId"].(string), - Url: thumbnail[len(thumbnail)-1].(map[string]interface{})["url"].(string), - Width: fmt.Sprintf("%v", thumbnail[len(thumbnail)-1].(map[string]interface{})["width"]), - Height: fmt.Sprintf("%v", thumbnail[len(thumbnail)-1].(map[string]interface{})["height"]), - }, - }, - IsSuccess: true, - } - - return out - } else { - return VideoParser{ - IsSuccess: false, - } - } -} diff --git a/youtube.go b/youtube.go index 9a42c54..6ccced6 100644 --- a/youtube.go +++ b/youtube.go @@ -1,7 +1,7 @@ package youtubego -func SearchVideos(searchq string) []Video { - res := CreateRequest(searchq) +func Search(searchq string, options SearchOptions) []interface{} { + res := CreateRequest(searchq, options) return res }