-
Notifications
You must be signed in to change notification settings - Fork 0
add rss feed scraping #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,139 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "encoding/xml" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "regexp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type RSS struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Channel Channel `xml:"channel"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Channel struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Items []Item `xml:"item"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Item struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Title string `xml:"title"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Description string `xml:"description"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PubDate string `xml:"pubDate"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Song struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Title string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Source string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Artist string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type TimeSlot struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Time string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Songs []Song | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ConcertDay struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Title string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TimeSlots []TimeSlot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // matches HTML tag | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var tagPattern = regexp.MustCompile(`<[^>]+>`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // removes all HTML tags from a string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func stripTags(s string) string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return strings.TrimSpace(tagPattern.ReplaceAllString(s, "")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var originPattern = regexp.MustCompile(`\(from "([^"]+)"\)`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func parseSong(line string) Song { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| song := Song{} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title, artist, found := strings.Cut(line, " / ") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if found { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| song.Artist = strings.TrimSpace(artist) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match := originPattern.FindStringSubmatch(title) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if match != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| song.Source = match[1] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title = strings.TrimSpace(originPattern.ReplaceAllString(title, "")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| song.Title = title | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return song | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🔧 Suggested fix func parseSong(line string) Song {
song := Song{}
+ // Extract source first so it can't be split across title/artist.
+ if match := originPattern.FindStringSubmatch(line); match != nil {
+ song.Source = match[1]
+ line = strings.TrimSpace(originPattern.ReplaceAllString(line, ""))
+ }
+
title, artist, found := strings.Cut(line, " / ")
if found {
song.Artist = strings.TrimSpace(artist)
}
-
- match := originPattern.FindStringSubmatch(title)
-
- if match != nil {
- song.Source = match[1]
- title = strings.TrimSpace(originPattern.ReplaceAllString(title, ""))
- }
- song.Title = title
+ song.Title = strings.TrimSpace(title)
return song
-
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // converts HTML into TimeSlot structure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func parseDescription(desc string) []TimeSlot { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var slots []TimeSlot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var current *TimeSlot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var lines []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, current := range strings.Split(desc, "<br>") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, line := range strings.Split(current, "\n") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines = append(lines, line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, line := range lines { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text := stripTags(line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if text == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isHeader := false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch text { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Morning", "Afternoon", "Evening": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isHeader = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isHeader { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slots = append(slots, TimeSlot{Time: text}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current = &slots[len(slots)-1] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if current != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current.Songs = append(current.Songs, parseSong(text)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return slots | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resp, err := http.Get("https://apps.chimes.cornell.edu/music/rss.xml") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| panic(err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer resp.Body.Close() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := io.ReadAll(resp.Body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| panic(err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var rss RSS | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := xml.Unmarshal(data, &rss); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| panic(err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+104
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set an HTTP timeout and check the response status.
🔧 Suggested fix-func main() {
- resp, err := http.Get("https://apps.chimes.cornell.edu/music/rss.xml")
- if err != nil {
- panic(err)
- }
- defer resp.Body.Close()
- data, err := io.ReadAll(resp.Body)
- if err != nil {
- panic(err)
- }
-
- var rss RSS
- if err := xml.Unmarshal(data, &rss); err != nil {
- panic(err)
- }
+func FetchConcerts(ctx context.Context, url string) ([]ConcertDay, error) {
+ client := &http.Client{Timeout: 15 * time.Second}
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("fetch rss: %w", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode/100 != 2 {
+ return nil, fmt.Errorf("rss feed returned status %d", resp.StatusCode)
+ }
+ data, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("read rss body: %w", err)
+ }
+ var rss RSS
+ if err := xml.Unmarshal(data, &rss); err != nil {
+ return nil, fmt.Errorf("parse rss xml: %w", err)
+ }
+ // ... build and return []ConcertDay
+}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var allConcerts []ConcertDay | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, item := range rss.Channel.Items { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanHTML := strings.NewReplacer("<", "<", ">", ">", "&", "&").Replace(item.Description) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| day := ConcertDay{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Title: item.Title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TimeSlots: parseDescription(cleanHTML), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allConcerts = append(allConcerts, day) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, day := range allConcerts { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println("=====", day.Title, "=====") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, slot := range day.TimeSlots { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println(slot.Time) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, song := range slot.Songs { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Printf("%q by %q from %q\n", song.Title, song.Artist, song.Source) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
Repository: cuappdev/chimes-backend
Length of output: 160
Fix package declaration:
package maininmodels/directory will not compile.This file declares
package mainwhile sibling files inmodels/declarepackage models. Go requires all files in the same directory to share the same package, so this will cause a compilation error.Refactor by splitting into:
models/rss.go— type definitions and helper functions aspackage models(reusable by handlers and tests).cmd/rss_scraper/main.go— themainentry point that fetches the feed and invokes the library.🤖 Prompt for AI Agents