package main
import (
"encoding/xml"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"text/tabwriter"
)
type Channel struct {
ID string `xml:"yt:channelId"`
Name string `xml:"title"`
Videos []Video `xml:"entry"`
}
type Video struct {
XMLName xml.Name `xml:"entry"`
ID string `xml:"videoId"`
Title string `xml:"title"`
Date string `xml:"published"`
}
var CONFIG_DIR string
func init() {
// Sets XDG_CONFIG_HOME
configDir, err := os.UserConfigDir()
if err != nil {
log.Fatal(err)
}
// Create config dir
configDir += "/yt-cli"
err = os.MkdirAll(configDir, 0700)
if err != nil {
log.Fatal(err)
return
}
// Check if config file exits
if _, err = os.Stat(configDir + "/channels"); os.IsNotExist(err) {
err = ioutil.WriteFile(configDir+"/channels", nil, 0600)
if err != nil {
log.Fatal(err)
return
}
}
// Set global var
CONFIG_DIR = configDir
}
func main() {
// set flags
var add = flag.String("add", "", "Add a channel to subscription list.")
var list = flag.Bool("list", false, "List all videos from subscriptions.")
flag.Parse()
// Print usage if no arguments
if len(os.Args[1:]) == 0 {
flag.Usage()
return
}
if *add != "" && *list == true {
log.Fatal("only one operation may be used at a time")
return
}
// Add a channel if `-add` flag is set
if *add != "" {
if err := addSub(*add); err != nil {
log.Fatal(err)
return
}
}
// List all channels' videos to stdout
if *list == true {
subs, _ := readSubs()
// minwidth, tabwidth, padding, padchar, flags
w := new(tabwriter.Writer)
w.Init(os.Stdout, 5, 0, 1, ' ', 0)
for _, k := range subs {
ch, err := getVideos(k)
if err != nil {
panic(err)
}
for i, v := range ch.Videos {
if i > 4 {
break
}
fmt.Fprintf(w, "[%.10s]\t%.20s\t- %.50s\t(%.12s)\n", v.Date, ch.Name, v.Title, v.ID)
}
}
w.Flush()
}
}
// Appends subscription to file
func addSub(id string) (err error) {
// Open file in append mode only
file, err := os.OpenFile(CONFIG_DIR+"/channels", os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return
}
defer file.Close()
// Append the new subscription
_, err = file.WriteString(id + "\n")
if err != nil {
return
}
return
}
// Reads subcriptions from file
func readSubs() (subs []string, err error) {
data, err := ioutil.ReadFile(CONFIG_DIR + "/channels")
if err != nil {
return
}
subs = strings.Split(string(data), "\n")
return
}
// Gets videos for subscription
func getVideos(chID string) (ch Channel, err error) {
url := "https://www.youtube.com/feeds/videos.xml?channel_id=" + chID
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return
}
xml.Unmarshal(body, &ch)
return
}