package main import ( "fmt" "log" "net/http" "strings" "sync" "time" ) var ( // cache will hold all previous network calls and re-use within set timeframe cache safeCache // cacheTimeout sets the time in in seconds for how long a channel will be cached cacheTimeout float64 ) const ( outputHTML = iota outputRSS ) func init() { cache = safeCache{v: make(map[string]*channel)} cacheTimeout = 60 * 15 // seconds } type safeCache struct { sync.Mutex v map[string]*channel } type channel struct { Title string Link string Name string Time time.Time Description string Items []*post } func (c *channel) Print(outputMode int) string { var template string switch outputMode { case outputHTML: template = htmlRoot case outputRSS: template = rssRoot } var s string s = strings.Replace(template, "{{title}}", c.Title, -1) s = strings.Replace(s, "{{link}}", c.Link, -1) s = strings.Replace(s, "{{description}}", c.Description, -1) s = strings.Replace(s, "{{name}}", c.Name, -1) var items string for i := range c.Items { item := c.Items[i].Print(outputMode) item = strings.Replace(item, "{{link}}", c.Link, -1) items += item } s = strings.Replace(s, "{{items}}", items, 1) return s } type post struct { Time time.Time Link string Content string Images []*image // list of urls } type image struct { Source string Caption string } func (p *post) Print(outputMode int) string { var template string switch outputMode { case outputHTML: template = htmlItem case outputRSS: template = rssItem } var s string // time format: Mon Jan 2 15:04:05 -0700 MST 2006 s = strings.Replace(template, "{{time}}", p.Time.Format("Mon, 2 Jan 2006 15:04:05"), 2) s = strings.Replace(s, "{{content}}", p.Content, 1) var imgs string for i := range p.Images { imgs += `` } s = strings.Replace(s, "{{images}}", imgs, 1) return s } type handler struct{} func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path[1:] switch path { case "": http.Error(w, fmt.Sprintf("usage: %s/@facebookGroupName", r.Host), 400) return case "favicon.ico": return case ".cache": var s string for k, v := range cache.v { s += v.Time.Format("2006-01-02 15:04:05") s += "\t" + k + "\n" } fmt.Fprintf(w, "%s", s) return } if path[0] == '@' { path = path[1:] } outputMode := outputHTML if strings.HasSuffix(path, ".rss") { path = strings.TrimSuffix(path, ".rss") outputMode = outputRSS } c, ok := cache.v[path] if !ok || time.Now().Sub(c.Time).Seconds() > cacheTimeout { var err error c, err = fetch(path) if err != nil { http.Error(w, fmt.Sprintf("error: %s", err), 400) return } if c == nil || len(c.Items) < 1 { http.Error(w, fmt.Sprintf("%s", "Page not found."), 400) return } c.Name = path c.Time = time.Now() cache.Lock() cache.v[c.Name] = c cache.Unlock() } fmt.Fprintf(w, "%s\n", c.Print(outputMode)) } func fetch(path string) (c *channel, err error) { if path == "" { return } url := "https://www.facebook.com/pg/" + path + "/posts/" log.Println("Fetching:", url) resp, err := http.Get(url) if err != nil { return } defer resp.Body.Close() c, err = parse(resp.Body) if err != nil { return } c.Link = url return } func main() { // clean the cache when channels have expired go func() { for { time.Sleep(time.Duration(cacheTimeout) * time.Second) for k, c := range cache.v { if time.Now().Sub(c.Time).Seconds() > cacheTimeout { cache.Lock() delete(cache.v, k) cache.Unlock() log.Println("Removed from cache:", k) } } } }() log.Println("Serving: http://localhost:1212") http.ListenAndServe(":1212", handler{}) }