new file: README
new file: src/Makefile new file: src/atom.go new file: src/author.go new file: src/category.go new file: src/channel.go new file: src/cloud.go new file: src/content.go new file: src/enclosure.go new file: src/feed.go new file: src/feed_test.go new file: src/generator.go new file: src/image.go new file: src/input.go new file: src/item.go new file: src/link.go new file: src/rss.go new file: src/source.go new file: src/subtitle.go
This commit is contained in:
		
						commit
						c2601a5ce3
					
				
					 19 changed files with 682 additions and 0 deletions
				
			
		
							
								
								
									
										23
									
								
								README
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								README
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					 Author: jim teeuwen <jimteeuwen@gmail.com>
 | 
				
			||||||
 | 
					 Dependencies: go-pkg-xmlx (http://github.com/jteeuwen/go-pkg-xmlx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 This package allows us to fetch Rss and Atom feeds from the internet.
 | 
				
			||||||
 | 
					 They are parsed into an object tree which is a hyvrid of both the RSS and Atom
 | 
				
			||||||
 | 
					 standards.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Supported feeds are:
 | 
				
			||||||
 | 
					 	- Rss v0.91, 0.91 and 2.0
 | 
				
			||||||
 | 
					 	- Atom 1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 The package allows us to maintain cache timeout management. This prevents us
 | 
				
			||||||
 | 
					 from querying the servers for feed updates too often and risk ip bams. Appart 
 | 
				
			||||||
 | 
					 from setting a cache timeout manually, the package also optionally adheres to
 | 
				
			||||||
 | 
					 the TTL, SkipDays and SkipHours values specied in the feeds themselves.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Note that the TTL, SkipDays and SkipHour fields are only part of the RSS spec.
 | 
				
			||||||
 | 
					 For Atom feeds, we use the CacheTimeout in the Feed struct.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Because the object structure is a hybrid between both RSS and Atom specs, not
 | 
				
			||||||
 | 
					 all fields will be filled when requesting either an RSS or Atom feed. I have
 | 
				
			||||||
 | 
					 tried to create as many shared fields as possiblem but some of them simply do
 | 
				
			||||||
 | 
					 not occur in either the RSS or Atom spec.
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/Makefile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Makefile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include $(GOROOT)/src/Make.$(GOARCH)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TARG=feeder
 | 
				
			||||||
 | 
					GOFILES=feed.go rss.go atom.go channel.go image.go item.go cloud.go \
 | 
				
			||||||
 | 
						enclosure.go source.go input.go category.go generator.go link.go\
 | 
				
			||||||
 | 
						subtitle.go author.go content.go\
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include $(GOROOT)/src/Make.pkg
 | 
				
			||||||
							
								
								
									
										97
									
								
								src/atom.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/atom.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,97 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					import "xmlx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) readAtom(doc *xmlx.Document) (err os.Error) {
 | 
				
			||||||
 | 
						ns := "http://www.w3.org/2005/Atom";
 | 
				
			||||||
 | 
						channels := doc.SelectNodes(ns, "feed");
 | 
				
			||||||
 | 
						for _, node := range channels {
 | 
				
			||||||
 | 
							ch := Channel{};
 | 
				
			||||||
 | 
							ch.Title = node.GetValue(ns, "title");
 | 
				
			||||||
 | 
							ch.LastBuildDate = node.GetValue(ns, "updated");
 | 
				
			||||||
 | 
							ch.Id = node.GetValue(ns, "id");
 | 
				
			||||||
 | 
							ch.Rights = node.GetValue(ns, "rights");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list := node.SelectNodes(ns, "link");
 | 
				
			||||||
 | 
							ch.Links = make([]Link, len(list));
 | 
				
			||||||
 | 
							for i, v := range list {
 | 
				
			||||||
 | 
								ch.Links[i].Href = v.GetAttr("", "href");
 | 
				
			||||||
 | 
								ch.Links[i].Rel = v.GetAttr("", "rel");
 | 
				
			||||||
 | 
								ch.Links[i].Type = v.GetAttr("", "type");
 | 
				
			||||||
 | 
								ch.Links[i].HrefLang = v.GetAttr("", "hreflang");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tn := node.SelectNode(ns, "subtitle");
 | 
				
			||||||
 | 
							if tn != nil {
 | 
				
			||||||
 | 
								ch.SubTitle = SubTitle{};
 | 
				
			||||||
 | 
								ch.SubTitle.Type = tn.GetAttr("", "type");
 | 
				
			||||||
 | 
								ch.SubTitle.Text = tn.Value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tn = node.SelectNode(ns, "generator");
 | 
				
			||||||
 | 
							if tn != nil {
 | 
				
			||||||
 | 
								ch.Generator = Generator{};
 | 
				
			||||||
 | 
								ch.Generator.Uri = tn.GetAttr("", "uri");
 | 
				
			||||||
 | 
								ch.Generator.Version = tn.GetAttr("", "version");
 | 
				
			||||||
 | 
								ch.Generator.Text = tn.Value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tn = node.SelectNode(ns, "author");
 | 
				
			||||||
 | 
							if tn != nil {
 | 
				
			||||||
 | 
								ch.Author = Author{};
 | 
				
			||||||
 | 
								ch.Author.Name = tn.GetValue("", "name");
 | 
				
			||||||
 | 
								ch.Author.Uri = tn.GetValue("", "uri");
 | 
				
			||||||
 | 
								ch.Author.Email = tn.GetValue("", "email");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list = node.SelectNodes(ns, "entry");
 | 
				
			||||||
 | 
							ch.Items = make([]Item, len(list));
 | 
				
			||||||
 | 
							for _, v := range list {
 | 
				
			||||||
 | 
								item := Item{};
 | 
				
			||||||
 | 
								item.Title = v.GetValue(ns, "title");
 | 
				
			||||||
 | 
								item.Id = v.GetValue(ns, "id");
 | 
				
			||||||
 | 
								item.PubDate = v.GetValue(ns, "updated");
 | 
				
			||||||
 | 
								item.Description = v.GetValue(ns, "summary");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								list = v.SelectNodes(ns, "link");
 | 
				
			||||||
 | 
								item.Links = make([]Link, 0);
 | 
				
			||||||
 | 
								for _, lv := range list {
 | 
				
			||||||
 | 
									if tn.GetAttr(ns, "rel") == "enclosure" {
 | 
				
			||||||
 | 
										enc := Enclosure{};
 | 
				
			||||||
 | 
										enc.Url = lv.GetAttr("", "href");
 | 
				
			||||||
 | 
										enc.Type = lv.GetAttr("", "type");
 | 
				
			||||||
 | 
										item.addEnclosure(enc);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										lnk := Link{};
 | 
				
			||||||
 | 
										lnk.Href = lv.GetAttr("", "href");
 | 
				
			||||||
 | 
										lnk.Rel = lv.GetAttr("", "rel");
 | 
				
			||||||
 | 
										lnk.Type = lv.GetAttr("", "type");
 | 
				
			||||||
 | 
										lnk.HrefLang = lv.GetAttr("", "hreflang");
 | 
				
			||||||
 | 
										item.addLink(lnk);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								list = v.SelectNodes(ns, "contributor");
 | 
				
			||||||
 | 
								item.Contributors = make([]string, len(list));
 | 
				
			||||||
 | 
								for ci, cv := range list {
 | 
				
			||||||
 | 
									item.Contributors[ci] = cv.GetValue("", "name");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								tn = v.SelectNode(ns, "content");
 | 
				
			||||||
 | 
								if tn != nil {
 | 
				
			||||||
 | 
									item.Content = Content{};
 | 
				
			||||||
 | 
									item.Content.Type = tn.GetAttr("", "type");
 | 
				
			||||||
 | 
									item.Content.Lang = tn.GetValue("xml", "lang");
 | 
				
			||||||
 | 
									item.Content.Base = tn.GetValue("xml", "base");
 | 
				
			||||||
 | 
									item.Content.Text = tn.Value;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ch.addItem(item);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.addChannel(ch);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/author.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/author.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Author struct {
 | 
				
			||||||
 | 
						Name		string;
 | 
				
			||||||
 | 
						Uri		string;
 | 
				
			||||||
 | 
						Email		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/category.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/category.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Category struct {
 | 
				
			||||||
 | 
						Domain		string;
 | 
				
			||||||
 | 
						Text		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/channel.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/channel.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Channel struct {
 | 
				
			||||||
 | 
						Title			string;
 | 
				
			||||||
 | 
						Links			[]Link;
 | 
				
			||||||
 | 
						Description		string;
 | 
				
			||||||
 | 
						Language		string;
 | 
				
			||||||
 | 
						Copyright		string;
 | 
				
			||||||
 | 
						ManagingEditor		string;
 | 
				
			||||||
 | 
						WebMaster		string;
 | 
				
			||||||
 | 
						PubDate			string;
 | 
				
			||||||
 | 
						LastBuildDate		string;
 | 
				
			||||||
 | 
						Docs			string;
 | 
				
			||||||
 | 
						Categories		[]Category;
 | 
				
			||||||
 | 
						Generator		Generator;
 | 
				
			||||||
 | 
						TTL			int;
 | 
				
			||||||
 | 
						Rating			string;
 | 
				
			||||||
 | 
						SkipHours		[]int;
 | 
				
			||||||
 | 
						SkipDays		[]int;
 | 
				
			||||||
 | 
						Image			Image;
 | 
				
			||||||
 | 
						Items			[]Item;
 | 
				
			||||||
 | 
						Cloud			Cloud;
 | 
				
			||||||
 | 
						TextInput		Input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Atom fields
 | 
				
			||||||
 | 
						Id			string;
 | 
				
			||||||
 | 
						Rights			string;
 | 
				
			||||||
 | 
						Author			Author;
 | 
				
			||||||
 | 
						SubTitle		SubTitle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Channel) addItem(item Item) {
 | 
				
			||||||
 | 
						slice := make([]Item, len(this.Items) + 1);
 | 
				
			||||||
 | 
						for i,v := range this.Items {
 | 
				
			||||||
 | 
							slice[i] = v;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						slice[len(slice) - 1] = item;
 | 
				
			||||||
 | 
						this.Items = slice;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Channel) addLink(l Link) {
 | 
				
			||||||
 | 
						slice := make([]Link, len(this.Links) + 1);
 | 
				
			||||||
 | 
						for i,v := range this.Links {
 | 
				
			||||||
 | 
							slice[i] = v;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						slice[len(slice) - 1] = l;
 | 
				
			||||||
 | 
						this.Links = slice;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/cloud.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/cloud.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Cloud struct {
 | 
				
			||||||
 | 
						Domain			string;
 | 
				
			||||||
 | 
						Port			int;
 | 
				
			||||||
 | 
						Path			string;
 | 
				
			||||||
 | 
						RegisterProcedure	string;
 | 
				
			||||||
 | 
						Protocol		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/content.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/content.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Content struct {
 | 
				
			||||||
 | 
						Type		string;
 | 
				
			||||||
 | 
						Lang		string;
 | 
				
			||||||
 | 
						Base		string;
 | 
				
			||||||
 | 
						Text		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/enclosure.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/enclosure.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Enclosure struct {
 | 
				
			||||||
 | 
						Url		string;
 | 
				
			||||||
 | 
						Length		int64;
 | 
				
			||||||
 | 
						Type		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										205
									
								
								src/feed.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								src/feed.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,205 @@
 | 
				
			||||||
 | 
					// Copyright 2009 The Go Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a BSD-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 Author: jim teeuwen <jimteeuwen@gmail.com>
 | 
				
			||||||
 | 
					 Dependencies: go-pkg-xmlx (http://github.com/jteeuwen/go-pkg-xmlx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 This package allows us to fetch Rss and Atom feeds from the internet.
 | 
				
			||||||
 | 
					 They are parsed into an object tree which is a hyvrid of both the RSS and Atom
 | 
				
			||||||
 | 
					 standards.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Supported feeds are:
 | 
				
			||||||
 | 
					 	- Rss v0.91, 0.91 and 2.0
 | 
				
			||||||
 | 
					 	- Atom 1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 The package allows us to maintain cache timeout management. This prevents us
 | 
				
			||||||
 | 
					 from querying the servers for feed updates too often and risk ip bams. Appart 
 | 
				
			||||||
 | 
					 from setting a cache timeout manually, the package also optionally adheres to
 | 
				
			||||||
 | 
					 the TTL, SkipDays and SkipHours values specied in the feeds themselves.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Note that the TTL, SkipDays and SkipHour fields are only part of the RSS spec.
 | 
				
			||||||
 | 
					 For Atom feeds, we use the CacheTimeout in the Feed struct.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Because the object structure is a hybrid between both RSS and Atom specs, not
 | 
				
			||||||
 | 
					 all fields will be filled when requesting either an RSS or Atom feed. I have
 | 
				
			||||||
 | 
					 tried to create as many shared fields as possiblem but some of them simply do
 | 
				
			||||||
 | 
					 not occur in either the RSS or Atom spec.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					import "http"
 | 
				
			||||||
 | 
					import "io"
 | 
				
			||||||
 | 
					import "time"
 | 
				
			||||||
 | 
					import "xmlx"
 | 
				
			||||||
 | 
					import "fmt"
 | 
				
			||||||
 | 
					import "strconv"
 | 
				
			||||||
 | 
					import "strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Feed struct {
 | 
				
			||||||
 | 
						// Custom cache timeout in minutes.
 | 
				
			||||||
 | 
						CacheTimeout int;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Make sure we adhere to the cache timeout specified in the feed. If
 | 
				
			||||||
 | 
						// our CacheTimeout is higher than that, we will use that instead.
 | 
				
			||||||
 | 
						EnforceCacheLimit bool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Type of feed. Rss, Atom, etc
 | 
				
			||||||
 | 
						Type string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Version of the feed. Major and Minor.
 | 
				
			||||||
 | 
						Version	[2]int;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Channels with content.
 | 
				
			||||||
 | 
						Channels []Channel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Url from which this feed was created.
 | 
				
			||||||
 | 
						Url string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Last time content was fetched. Used in conjunction with CacheTimeout
 | 
				
			||||||
 | 
						// to ensure we don't get content too often.
 | 
				
			||||||
 | 
						lastupdate int64;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func New(cachetimeout int, enforcecachelimit bool) *Feed {
 | 
				
			||||||
 | 
						return &Feed{
 | 
				
			||||||
 | 
							CacheTimeout: cachetimeout,
 | 
				
			||||||
 | 
							EnforceCacheLimit: enforcecachelimit,
 | 
				
			||||||
 | 
							Type: "none",
 | 
				
			||||||
 | 
							Version: [2]int{0, 0},
 | 
				
			||||||
 | 
							Channels: make([]Channel, 0),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) addChannel(ch Channel) {
 | 
				
			||||||
 | 
						slice := make([]Channel, len(this.Channels) + 1);
 | 
				
			||||||
 | 
						for i,v := range this.Channels {
 | 
				
			||||||
 | 
							slice[i] = v;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						slice[len(slice) - 1] = ch;
 | 
				
			||||||
 | 
						this.Channels = slice;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) Fetch(uri string) (err os.Error) {
 | 
				
			||||||
 | 
						if !this.canUpdate() { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch data from remote location.
 | 
				
			||||||
 | 
						r, _, err := http.Get(uri);
 | 
				
			||||||
 | 
						if err != nil { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer r.Body.Close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b, err := io.ReadAll(r.Body);
 | 
				
			||||||
 | 
						if err != nil { return }
 | 
				
			||||||
 | 
						content := string(b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.Url = uri;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Extract type and version of the feed so we can have the appropriate
 | 
				
			||||||
 | 
						// function parse it (rss 0.91, rss 0.92, rss 2, atom etc).
 | 
				
			||||||
 | 
						doc := xmlx.New();
 | 
				
			||||||
 | 
						err = doc.LoadString(content);
 | 
				
			||||||
 | 
						if err != nil { return }
 | 
				
			||||||
 | 
						this.Type, this.Version = this.GetVersionInfo(doc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ok := this.testVersions();
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							err = os.NewError(fmt.Sprintf("Unsupported feed: %s, version: %+v", this.Type, this.Version));
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = this.buildFeed(doc);
 | 
				
			||||||
 | 
						if err != nil || len(this.Channels) == 0 { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// reset cache timeout values according to feed specified values (TTL)
 | 
				
			||||||
 | 
						if  this.EnforceCacheLimit && this.CacheTimeout < this.Channels[0].TTL {
 | 
				
			||||||
 | 
							this.CacheTimeout = this.Channels[0].TTL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) canUpdate() bool {
 | 
				
			||||||
 | 
						// Make sure we are not within the specified cache-limit.
 | 
				
			||||||
 | 
						// This ensures we don't request data too often.
 | 
				
			||||||
 | 
						utc := time.UTC();
 | 
				
			||||||
 | 
						if utc.Seconds() - this.lastupdate < int64(this.CacheTimeout * 60) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If skipDays or skipHours are set in the RSS feed, use these to see if
 | 
				
			||||||
 | 
						// we can update.
 | 
				
			||||||
 | 
						if len(this.Channels) == 0 && this.Type == "rss" {
 | 
				
			||||||
 | 
							if this.EnforceCacheLimit && len(this.Channels[0].SkipDays) > 0 {
 | 
				
			||||||
 | 
								for _ ,v := range this.Channels[0].SkipDays {
 | 
				
			||||||
 | 
									if v == utc.Weekday {
 | 
				
			||||||
 | 
										return false
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if this.EnforceCacheLimit && len(this.Channels[0].SkipHours) > 0 {
 | 
				
			||||||
 | 
								for _ ,v := range this.Channels[0].SkipHours {
 | 
				
			||||||
 | 
									if v == utc.Hour {
 | 
				
			||||||
 | 
										return false
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.lastupdate = utc.Seconds();	
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) buildFeed(doc *xmlx.Document) (err os.Error) {
 | 
				
			||||||
 | 
						switch this.Type {
 | 
				
			||||||
 | 
						case "rss": err = this.readRss2(doc);
 | 
				
			||||||
 | 
						case "atom": err = this.readAtom(doc);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) testVersions() bool {
 | 
				
			||||||
 | 
						switch this.Type {
 | 
				
			||||||
 | 
						case "rss":
 | 
				
			||||||
 | 
							if this.Version[0] > 2 || (this.Version[0] == 2 && this.Version[1] > 0) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case "atom":
 | 
				
			||||||
 | 
							if this.Version[0] > 1 || (this.Version[0] == 1 && this.Version[1] > 0) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) GetVersionInfo(doc *xmlx.Document) (ftype string, fversion [2]int) {
 | 
				
			||||||
 | 
						node := doc.SelectNode("http://www.w3.org/2005/Atom", "feed");
 | 
				
			||||||
 | 
						if node == nil { goto rss }
 | 
				
			||||||
 | 
						ftype = "atom";
 | 
				
			||||||
 | 
						fversion = [2]int{1, 0};
 | 
				
			||||||
 | 
						return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rss:
 | 
				
			||||||
 | 
						node = doc.SelectNode("", "rss");
 | 
				
			||||||
 | 
						if node == nil { goto end }
 | 
				
			||||||
 | 
						ftype = "rss";
 | 
				
			||||||
 | 
						version := node.GetAttr("", "version");
 | 
				
			||||||
 | 
						p := strings.Index(version, ".");
 | 
				
			||||||
 | 
						major, _ := strconv.Atoi(version[0:p]);
 | 
				
			||||||
 | 
						minor, _ := strconv.Atoi(version[p+1 : len(version)]);
 | 
				
			||||||
 | 
						fversion = [2]int{major, minor};
 | 
				
			||||||
 | 
						return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    end:
 | 
				
			||||||
 | 
						ftype = "unknown";
 | 
				
			||||||
 | 
						fversion = [2]int{0, 0};
 | 
				
			||||||
 | 
						return;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/feed_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/feed_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestFeed(t *testing.T) {
 | 
				
			||||||
 | 
						urilist := []string{
 | 
				
			||||||
 | 
							"http://cyber.law.harvard.edu/rss/examples/sampleRss091.xml",
 | 
				
			||||||
 | 
							"http://cyber.law.harvard.edu/rss/examples/sampleRss092.xml",
 | 
				
			||||||
 | 
							"http://cyber.law.harvard.edu/rss/examples/rss2sample.xml",
 | 
				
			||||||
 | 
							"http://blog.case.edu/news/feed.atom",
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, uri := range urilist {
 | 
				
			||||||
 | 
							feed := New(5, true);
 | 
				
			||||||
 | 
							err := feed.Fetch(uri);
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Errorf("%s >>> %s", uri, err);
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/generator.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/generator.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Generator struct {
 | 
				
			||||||
 | 
						Uri		string;
 | 
				
			||||||
 | 
						Version		string;
 | 
				
			||||||
 | 
						Text		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/image.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/image.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Image struct {
 | 
				
			||||||
 | 
						Title		string;
 | 
				
			||||||
 | 
						Url		string;
 | 
				
			||||||
 | 
						Link		string;
 | 
				
			||||||
 | 
						Width		int;
 | 
				
			||||||
 | 
						Height		int;
 | 
				
			||||||
 | 
						Description	string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/input.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/input.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Input struct {
 | 
				
			||||||
 | 
						Title		string;
 | 
				
			||||||
 | 
						Description	string;
 | 
				
			||||||
 | 
						Name		string;
 | 
				
			||||||
 | 
						Link		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/item.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/item.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Item struct {
 | 
				
			||||||
 | 
						// RSS and Shared fields
 | 
				
			||||||
 | 
						Title		string;
 | 
				
			||||||
 | 
						Links		[]Link;
 | 
				
			||||||
 | 
						Description	string;
 | 
				
			||||||
 | 
						Author		Author;
 | 
				
			||||||
 | 
						Categories	[]Category;
 | 
				
			||||||
 | 
						Comments	string;
 | 
				
			||||||
 | 
						Enclosures	[]Enclosure;
 | 
				
			||||||
 | 
						Guid		string;
 | 
				
			||||||
 | 
						PubDate		string;
 | 
				
			||||||
 | 
						Source		Source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Atom specific fields
 | 
				
			||||||
 | 
						Id		string;
 | 
				
			||||||
 | 
						Generator	Generator;
 | 
				
			||||||
 | 
						Contributors	[]string;
 | 
				
			||||||
 | 
						Content		Content;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Item) addEnclosure(e Enclosure) {
 | 
				
			||||||
 | 
						slice := make([]Enclosure, len(this.Enclosures) + 1);
 | 
				
			||||||
 | 
						for i,v := range this.Enclosures {
 | 
				
			||||||
 | 
							slice[i] = v;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						slice[len(slice) - 1] = e;
 | 
				
			||||||
 | 
						this.Enclosures = slice;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Item) addLink(l Link) {
 | 
				
			||||||
 | 
						slice := make([]Link, len(this.Links) + 1);
 | 
				
			||||||
 | 
						for i,v := range this.Links {
 | 
				
			||||||
 | 
							slice[i] = v;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						slice[len(slice) - 1] = l;
 | 
				
			||||||
 | 
						this.Links = slice;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/link.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/link.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Link struct {
 | 
				
			||||||
 | 
						Href		string;
 | 
				
			||||||
 | 
						Rel		string;
 | 
				
			||||||
 | 
						Type		string;
 | 
				
			||||||
 | 
						HrefLang	string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										150
									
								
								src/rss.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/rss.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,150 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					import "xmlx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Feed) readRss2(doc *xmlx.Document) (err os.Error) {
 | 
				
			||||||
 | 
						channels := doc.SelectNodes("", "channel");
 | 
				
			||||||
 | 
						for _, node := range channels {
 | 
				
			||||||
 | 
							ch := Channel{};
 | 
				
			||||||
 | 
							ch.Title = node.GetValue("", "title");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list := node.SelectNodes("", "link");
 | 
				
			||||||
 | 
							ch.Links = make([]Link, len(list));
 | 
				
			||||||
 | 
							for i, v := range list {
 | 
				
			||||||
 | 
								ch.Links[i].Href = v.Value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ch.Description = node.GetValue("", "description");
 | 
				
			||||||
 | 
							ch.Language = node.GetValue("", "language");
 | 
				
			||||||
 | 
							ch.Copyright = node.GetValue("", "copyright");
 | 
				
			||||||
 | 
							ch.ManagingEditor = node.GetValue("", "managingEditor");
 | 
				
			||||||
 | 
							ch.WebMaster = node.GetValue("", "webMaster");
 | 
				
			||||||
 | 
							ch.PubDate = node.GetValue("", "pubDate");
 | 
				
			||||||
 | 
							ch.LastBuildDate = node.GetValue("", "lastBuildDate");
 | 
				
			||||||
 | 
							ch.Docs = node.GetValue("", "docs");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list = node.SelectNodes("", "category");
 | 
				
			||||||
 | 
							ch.Categories = make([]Category, len(list));
 | 
				
			||||||
 | 
							for i, v := range list {
 | 
				
			||||||
 | 
								ch.Categories[i].Domain = v.GetAttr("", "domain");
 | 
				
			||||||
 | 
								ch.Categories[i].Text = v.Value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n := node.SelectNode("", "generator");
 | 
				
			||||||
 | 
							if n != nil {
 | 
				
			||||||
 | 
								ch.Generator = Generator{};
 | 
				
			||||||
 | 
								ch.Generator.Text = n.Value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ch.TTL = node.GetValuei("", "ttl");
 | 
				
			||||||
 | 
							ch.Rating = node.GetValue("", "rating");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list = node.SelectNodes("", "hour");
 | 
				
			||||||
 | 
							ch.SkipHours = make([]int, len(list));
 | 
				
			||||||
 | 
							for i, v := range list {
 | 
				
			||||||
 | 
								ch.SkipHours[i] = int(v.GetValuei("", "hour"));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list = node.SelectNodes("", "days");
 | 
				
			||||||
 | 
							ch.SkipDays = make([]int, len(list));
 | 
				
			||||||
 | 
							for i, v := range list {
 | 
				
			||||||
 | 
								ch.SkipDays[i] = mapDay(v.Value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n = node.SelectNode("", "image");
 | 
				
			||||||
 | 
							if n != nil {
 | 
				
			||||||
 | 
								ch.Image.Title = n.GetValue("", "title");
 | 
				
			||||||
 | 
								ch.Image.Url = n.GetValue("", "url");
 | 
				
			||||||
 | 
								ch.Image.Link = n.GetValue("", "link");
 | 
				
			||||||
 | 
								ch.Image.Width = n.GetValuei("", "width");
 | 
				
			||||||
 | 
								ch.Image.Height = n.GetValuei("", "height");
 | 
				
			||||||
 | 
								ch.Image.Description = n.GetValue("", "description");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n = node.SelectNode("", "cloud");
 | 
				
			||||||
 | 
							if n != nil {
 | 
				
			||||||
 | 
								ch.Cloud = Cloud{};
 | 
				
			||||||
 | 
								ch.Cloud.Domain = n.GetAttr("", "domain");
 | 
				
			||||||
 | 
								ch.Cloud.Port = n.GetAttri("", "port");
 | 
				
			||||||
 | 
								ch.Cloud.Path = n.GetAttr("", "path");
 | 
				
			||||||
 | 
								ch.Cloud.RegisterProcedure = n.GetAttr("", "registerProcedure");
 | 
				
			||||||
 | 
								ch.Cloud.Protocol = n.GetAttr("", "protocol");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n = node.SelectNode("", "textInput");
 | 
				
			||||||
 | 
							if n != nil {
 | 
				
			||||||
 | 
								ch.TextInput = Input{};
 | 
				
			||||||
 | 
								ch.TextInput.Title = n.GetValue("", "title");
 | 
				
			||||||
 | 
								ch.TextInput.Description = n.GetValue("", "description");
 | 
				
			||||||
 | 
								ch.TextInput.Name = n.GetValue("", "name");
 | 
				
			||||||
 | 
								ch.TextInput.Link = n.GetValue("", "link");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							list = node.SelectNodes("", "item");
 | 
				
			||||||
 | 
							for _, item := range list {
 | 
				
			||||||
 | 
								i := Item{};
 | 
				
			||||||
 | 
								i.Title = item.GetValue("", "title");
 | 
				
			||||||
 | 
								i.Description = item.GetValue("", "description");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								list = node.SelectNodes("", "link");
 | 
				
			||||||
 | 
								i.Links = make([]Link, 0);
 | 
				
			||||||
 | 
								for _, v := range list {
 | 
				
			||||||
 | 
									lnk := Link{};
 | 
				
			||||||
 | 
									lnk.Href = v.Value;
 | 
				
			||||||
 | 
									i.addLink(lnk);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								n = item.SelectNode("", "author");
 | 
				
			||||||
 | 
								if n != nil {
 | 
				
			||||||
 | 
									i.Author = Author{};
 | 
				
			||||||
 | 
									i.Author.Name = n.Value;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								i.Comments = item.GetValue("", "comments");
 | 
				
			||||||
 | 
								i.Guid = item.GetValue("", "guid");
 | 
				
			||||||
 | 
								i.PubDate = item.GetValue("", "pubDate");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								list := item.SelectNodes("", "category");
 | 
				
			||||||
 | 
								i.Categories = make([]Category, len(list));
 | 
				
			||||||
 | 
								for li, lv := range list {
 | 
				
			||||||
 | 
									i.Categories[li].Domain = lv.GetAttr("", "domain");
 | 
				
			||||||
 | 
									i.Categories[li].Text = lv.Value;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								list = item.SelectNodes("", "enclosure");
 | 
				
			||||||
 | 
								i.Enclosures = make([]Enclosure, len(list));
 | 
				
			||||||
 | 
								for li, lv := range list {
 | 
				
			||||||
 | 
									i.Enclosures[li].Url = lv.GetAttr("", "url");
 | 
				
			||||||
 | 
									i.Enclosures[li].Length = lv.GetAttri64("", "length");
 | 
				
			||||||
 | 
									i.Enclosures[li].Type = lv.GetAttr("", "type");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								src := item.SelectNode("", "source");
 | 
				
			||||||
 | 
								if src != nil {
 | 
				
			||||||
 | 
									i.Source = Source{};
 | 
				
			||||||
 | 
									i.Source.Url = src.GetAttr("", "url");
 | 
				
			||||||
 | 
									i.Source.Text = src.Value;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ch.addItem(i);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.addChannel(ch);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mapDay(day string) int {
 | 
				
			||||||
 | 
						switch day {
 | 
				
			||||||
 | 
						case "Monday": return 1;
 | 
				
			||||||
 | 
						case "Tuesday": return 2;
 | 
				
			||||||
 | 
						case "Wednesday": return 3;
 | 
				
			||||||
 | 
						case "Thursday": return 4;
 | 
				
			||||||
 | 
						case "Friday": return 5;
 | 
				
			||||||
 | 
						case "Saturday": return 6;
 | 
				
			||||||
 | 
						case "Sunday": return 7;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/source.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/source.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Source struct {
 | 
				
			||||||
 | 
						Url		string;
 | 
				
			||||||
 | 
						Text		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/subtitle.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/subtitle.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					package feeder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SubTitle struct {
 | 
				
			||||||
 | 
						Type		string;
 | 
				
			||||||
 | 
						Text		string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue