1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
package main
import (
"bufio"
"encoding/xml"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
)
type Item struct {
GUID string `xml:"guid"`
PubDate string `xml:"pubDate"`
Title string `xml:"title"`
Description string `xml:"description"`
Link string `xml:"link"`
Author string `xml:"author"`
}
type Feed struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
Items []Item `xml:"channel>item"`
Title string `xml:"channel>title"`
}
func Parse(r io.Reader) ([]Item, error) {
var f Feed
dec := xml.NewDecoder(r)
err := dec.Decode(&f)
return f.Items, err
}
func subscribedTo() ([]string, error) {
var chans []string
p := os.ExpandEnv("$HOME/.youtube-feed")
f, err := os.Open(p)
if err != nil {
return nil, fmt.Errorf("failed reading %s: %s", p, err)
}
defer f.Close()
scan := bufio.NewScanner(f)
for scan.Scan() {
chans = append(chans, strings.TrimSpace(scan.Text()))
}
if err := scan.Err(); err != nil {
return nil, fmt.Errorf("failed reading %s: %s", p, err)
}
return chans, err
}
const retries = 5
func getLatestVideos(ytChan string, itemChan chan<- Item, status chan<- error) {
var resp *http.Response
var err error
for i := 0; i < retries; i++ {
resp, err = http.Get("http://gdata.youtube.com/feeds/base/users/" + ytChan + "/uploads?alt=rss&v=2&orderby=published&client=ytapi-youtube-profile")
if err == nil {
if resp.StatusCode == http.StatusOK {
break
} else {
resp.Body.Close()
err = fmt.Errorf("Received status %d, expected %d", resp.StatusCode, http.StatusOK)
}
}
}
if err != nil {
status <- fmt.Errorf("Failed getting channel feed for %s: %s", ytChan, err)
return
}
defer resp.Body.Close()
items, err := Parse(resp.Body)
if err != nil {
err = fmt.Errorf("Failed parsing feed of %s: %s", ytChan, err)
}
for _, it := range items {
itemChan <- it
}
status <- err
}
var vidID = regexp.MustCompile(`v=(.*?)&`)
func main() {
subs, err := subscribedTo()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed reading channel list: %s\n", err)
os.Exit(1)
}
itemChan := make(chan Item)
status := make(chan error)
var items []Item
go func() {
for it := range itemChan {
it.Title = fmt.Sprintf("[%s] %s", it.Author, it.Title)
if m := vidID.FindStringSubmatch(it.Link); len(m) == 2 {
it.GUID = "video:" + m[1]
}
items = append(items, it)
}
}()
for _, ytChan := range subs {
go getLatestVideos(ytChan, itemChan, status)
}
for _ = range subs {
if err := <-status; err != nil {
fmt.Fprintln(os.Stderr, "Warning:", err)
}
}
close(itemChan)
feed := Feed{Title: "Combined YouTube Feed", Items: items, Version: "2.0"}
enc := xml.NewEncoder(os.Stdout)
if err := enc.Encode(feed); err != nil {
fmt.Fprintf(os.Stderr, "Failed writing feed: %s\n", err)
os.Exit(1)
}
}
|