popeyelau / wiki

📒Wiki for many useful notes, source, commands and snippets.
2 stars 0 forks source link

dogetv-cli #17

Open popeyelau opened 5 years ago

popeyelau commented 5 years ago
package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "os"
    "os/exec"
    "path/filepath"
    "regexp"
    "runtime"
    "strings"

    "github.com/urfave/cli"
)

const (
    API_HOST = "https://tv.popeye.vip"
)

type Video struct {
    Name  string `json:"name"`
    ID    string `json:"id"`
    Area  string `json:"area"`
    Year  string `json:"year"`
    Cover string `json:"cover"`
}

func main() {
    app := cli.NewApp()
    app.Name = "dogetv-cli"
    app.Usage = "搜索影视资源"
    app.Version = "1.0.0"
    app.Author = "Popeye Lau"
    app.Email = "0x12fd22c@gmail.com"
    app.UsageText = "dogetv-cli search -k 权力的游戏"

    app.Commands = []cli.Command{
        {
            Name:      "search",
            Usage:     "按关键字搜索电影/演员/导演",
            UsageText: "dogetv-cli search -k 权力的游戏",
            Flags: []cli.Flag{
                cli.StringFlag{
                    Name:  "keyword, k",
                    Usage: "关键字",
                },
            },
            Action:      searchAction,
            Description: "搜索",
        },
    }
    app.Run(os.Args)
}

func searchAction(c *cli.Context) error {
    keyword := strings.TrimSpace(c.String("keyword"))
    if len(keyword) == 0 {
        keyword = readStringFromStdIn()
    }

    api := fmt.Sprintf("%v/pumpkin/search/%v", API_HOST, keyword)

    var resp struct {
        Data []Video `json:"data"`
    }

    if ok, err := readJSONFromURL(api, http.MethodGet, nil, &resp); !ok {
        fmt.Fprintf(c.App.Writer, "\n\033[1;31m%s\033[0m", err.Error())
        return nil
    }

    if len(resp.Data) == 0 {
        fmt.Fprintf(c.App.Writer, "\n\033[1;31m%s\033[0m", "未找到匹配资源")
        return nil
    }

    for index, item := range resp.Data {
        fmt.Fprintf(c.App.Writer, "\n\033[1;36m[%d]\033[0m %s", index, item.Name)
    }

    index := readIntFromStdIn(len(resp.Data))
    return fetchVideoInfo(resp.Data[index], c)
}

func fetchVideoInfo(video Video, c *cli.Context) error {
    id := video.ID
    api := fmt.Sprintf("%v/pumpkin/video/%v", API_HOST, id)

    if !strings.Contains(video.Cover, "vcinema") {
        return fetchEpisodes(fmt.Sprintf("%v/4k/detail/%v/episodes", API_HOST, id), c)
    }

    var resp struct {
        Data struct {
            Info    Video `json:"info"`
            Seasons []struct {
                ID   string `json:"id"`
                Name string `json:"name"`
            } `json:"seasons"`
        } `json:"data"`
    }
    if ok, err := readJSONFromURL(api, http.MethodGet, nil, &resp); !ok {
        fmt.Fprintf(c.App.Writer, "\n\033[1;31m%s\033[0m", err.Error())
        return err
    }

    if len(resp.Data.Seasons) == 0 {
        return fetchEpisodes(fmt.Sprintf("%v/pumpkin/video/%v/stream", API_HOST, id), c)
    }

    fmt.Fprint(c.App.Writer, "\n")
    for i, v := range resp.Data.Seasons {
        fmt.Fprintf(c.App.Writer, "\033[1;36m[%d]\033[0m %s\t", i, v.Name)
    }

    index := readIntFromStdIn(len(resp.Data.Seasons))
    sid := resp.Data.Seasons[index].ID
    return fetchSeasonEpisode(id, sid, c)
}

func fetchEpisodes(api string, c *cli.Context) error {
    var resp struct {
        Data []struct {
            URL   string `json:"url"`
            Title string `json:"title"`
        } `json:"data"`
    }

    if ok, err := readJSONFromURL(api, http.MethodGet, nil, &resp); !ok {
        fmt.Fprintf(c.App.Writer, "\n\033[1;31m%s\033[0m", err.Error())
        return err
    }

    if len(resp.Data) == 1 {
        openIINA(resp.Data[0].URL)
        return nil
    }

    fmt.Fprint(c.App.Writer, "\n")
    for i, v := range resp.Data {
        fmt.Fprintf(c.App.Writer, "\033[1;36m[%d]\033[0m %s\t", i, v.Title)
    }

    index := readIntFromStdIn(len(resp.Data))
    openIINA(resp.Data[index].URL)
    return nil
}

func fetchSeasonEpisode(id, sid string, c *cli.Context) error {
    api := fmt.Sprintf("%v/pumpkin/video/%v?sid=%v", API_HOST, id, sid)
    var resp struct {
        Data struct {
            Seasons []struct {
                ID       string `json:"id"`
                Name     string `json:"name"`
                Episodes []struct {
                    ID    string `json:"id"`
                    Title string `json:"title"`
                } `json:"episodes"`
            } `json:"seasons"`
        } `json:"data"`
    }

    if ok, err := readJSONFromURL(api, http.MethodGet, nil, &resp); !ok {
        fmt.Fprintf(c.App.Writer, "\n\033[1;31m%s\033[0m", err.Error())
        return err
    }

    var seasonIndex int
    for i, item := range resp.Data.Seasons {
        if len(item.Episodes) > 0 {
            seasonIndex = i
            fmt.Fprint(c.App.Writer, "\n")
            for index, episode := range item.Episodes {
                fmt.Fprintf(c.App.Writer, "\033[1;36m[%d]\033[0m %s\t", index, episode.Title)
            }
            break
        }
    }

    episodes := resp.Data.Seasons[seasonIndex].Episodes
    index := readIntFromStdIn(len(episodes))
    return fetchEpisodes(fmt.Sprintf("%v/pumpkin/video/%v/stream", API_HOST, episodes[index].ID), c)

}

func openIINA(episodeURL string) {
    episodeURL, err := getStreamURL(episodeURL)
    if err != nil {
        fmt.Printf("\n\033[1;31m%s\033[0m", err.Error())
        return
    }

    fmt.Println(episodeURL)
    encodeURL := url.QueryEscape(episodeURL)
    iina := fmt.Sprintf("iina://weblink?url=%s", encodeURL)
    openBrowser(iina)
}

func getStreamURL(source string) (string, error) {
    ext := filepath.Ext(source)
    if !strings.HasPrefix(ext, ".m3u8") && !strings.HasPrefix(ext, ".mp4") {
        url, err := fetchStreamURL(source)
        if err != nil {
            return "", err
        }
        return url, nil
    }
    return source, nil
}

func readJSONFromURL(url string, method string, params url.Values, target interface{}) (bool, error) {
    client := &http.Client{}
    req, err := http.NewRequest(method, url, strings.NewReader(params.Encode()))

    if err != nil {
        return false, err
    }

    resp, err := client.Do(req)
    if err != nil {
        return false, err
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    body = bytes.Trim(body, " ();")

    if err != nil {
        return false, err
    }

    err = json.Unmarshal(body, target)
    if err != nil {
        return false, err
    }

    return true, nil
}

func fetchStreamURL(source string) (string, error) {
    url, err := url.Parse(source)
    if err != nil {
        return "", err
    }

    client := &http.Client{}

    req, err := http.NewRequest("GET", url.String(), nil)
    if err != nil {
        return "", err
    }

    req.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 12_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Mobile/15E148 Safari/604.1")

    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    html := string(body)
    rp := regexp.MustCompile(`(http:|https:)\/\/(.*)\.m3u8`)
    result := rp.FindString(html)
    if len(result) == 0 {
        return "", errors.New("获取失败")
    }

    return result, nil
}

func openBrowser(url string) {
    var err error

    switch runtime.GOOS {
    case "linux":
        err = exec.Command("xdg-open", url).Start()
    case "windows":
        err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
    case "darwin":
        err = exec.Command("open", url).Start()
    default:
        err = fmt.Errorf("unsupported platform")
    }
    if err != nil {
        log.Fatal(err)
    }

}

func readIntFromStdIn(max int) int {
    index := -1
    fmt.Print("\n\033[1;33m序号> \033[0m")
    for index < 0 || index >= max {
        fmt.Scan(&index)
    }
    return index
}

func readStringFromStdIn() string {
    var input string
    fmt.Print("\n\033[1;33m关键字> \033[0m")
    for len(strings.TrimSpace(input)) == 0 {
        fmt.Scan(&input)
    }
    return input
}

A simple, fast, and fun package for building command line apps in Go

popeyelau commented 5 years ago
dogetv-cli (master) ✚  ./dogetv-cli search -k 复仇者联盟

[0] 复仇者联盟4 尝鲜版
[1] 复仇者联盟3:无限战争
[2] 复仇者联盟2:奥创纪元
[3] 复仇者联盟
[4] 复仇者联盟3(国语版)
[5] 复仇者联盟2:奥创纪元(国语版)
[6] 复仇者联盟(国语版)
[7] 复仇者联盟
[8] 复仇者联盟2:奥创纪元
[9] 复仇者联盟3
序号> 9

[0] 标清  [1] 高清  [2] 超清
序号> 2