mirror of
https://github.com/nkanaev/yarr.git
synced 2026-05-02 07:17:41 +00:00
golines -w src
This commit is contained in:
@@ -27,10 +27,16 @@ var (
|
||||
|
||||
blacklistCandidatesRegexp = regexp.MustCompile(`(?i)popupbody|-ad|g-plus`)
|
||||
okMaybeItsACandidateRegexp = regexp.MustCompile(`(?i)and|article|body|column|main|shadow`)
|
||||
unlikelyCandidatesRegexp = regexp.MustCompile(`(?i)banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|modal|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote`)
|
||||
unlikelyCandidatesRegexp = regexp.MustCompile(
|
||||
`(?i)banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|modal|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote`,
|
||||
)
|
||||
|
||||
negativeRegexp = regexp.MustCompile(`(?i)hidden|^hid$|hid$|hid|^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|modal|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget|byline|author|dateline|writtenby|p-author`)
|
||||
positiveRegexp = regexp.MustCompile(`(?i)article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story`)
|
||||
negativeRegexp = regexp.MustCompile(
|
||||
`(?i)hidden|^hid$|hid$|hid|^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|modal|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget|byline|author|dateline|writtenby|p-author`,
|
||||
)
|
||||
positiveRegexp = regexp.MustCompile(
|
||||
`(?i)article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story`,
|
||||
)
|
||||
)
|
||||
|
||||
type nodeScores map[*html.Node]float32
|
||||
|
||||
@@ -146,7 +146,10 @@ func sanitizeAttributes(baseURL, tagName string, attributes []html.Attribute) ([
|
||||
}
|
||||
|
||||
attrNames = append(attrNames, attribute.Key)
|
||||
htmlAttrs = append(htmlAttrs, fmt.Sprintf(`%s="%s"`, attribute.Key, html.EscapeString(value)))
|
||||
htmlAttrs = append(
|
||||
htmlAttrs,
|
||||
fmt.Sprintf(`%s="%s"`, attribute.Key, html.EscapeString(value)),
|
||||
)
|
||||
}
|
||||
|
||||
extraAttrNames, extraHTMLAttributes := getExtraAttributes(tagName)
|
||||
@@ -161,11 +164,25 @@ func sanitizeAttributes(baseURL, tagName string, attributes []html.Attribute) ([
|
||||
func getExtraAttributes(tagName string) ([]string, []string) {
|
||||
switch tagName {
|
||||
case "a":
|
||||
return []string{"rel", "target", "referrerpolicy"}, []string{`rel="noopener noreferrer"`, `target="_blank"`, `referrerpolicy="no-referrer"`}
|
||||
return []string{
|
||||
"rel",
|
||||
"target",
|
||||
"referrerpolicy",
|
||||
}, []string{
|
||||
`rel="noopener noreferrer"`,
|
||||
`target="_blank"`,
|
||||
`referrerpolicy="no-referrer"`,
|
||||
}
|
||||
case "video", "audio":
|
||||
return []string{"controls"}, []string{"controls"}
|
||||
case "iframe":
|
||||
return []string{"sandbox", "loading"}, []string{`sandbox="allow-scripts allow-same-origin allow-popups"`, `loading="lazy"`}
|
||||
return []string{
|
||||
"sandbox",
|
||||
"loading",
|
||||
}, []string{
|
||||
`sandbox="allow-scripts allow-same-origin allow-popups"`,
|
||||
`loading="lazy"`,
|
||||
}
|
||||
case "img":
|
||||
return []string{"loading"}, []string{`loading="lazy"`, `referrerpolicy="no-referrer"`}
|
||||
default:
|
||||
|
||||
@@ -91,13 +91,22 @@ func ParseAtom(r io.Reader) (*Feed, error) {
|
||||
|
||||
mediaLinks := srcitem.mediaLinks()
|
||||
|
||||
link := firstNonEmpty(srcitem.OrigLink, srcitem.Links.First("alternate"), srcitem.Links.First(""), linkFromID)
|
||||
link := firstNonEmpty(
|
||||
srcitem.OrigLink,
|
||||
srcitem.Links.First("alternate"),
|
||||
srcitem.Links.First(""),
|
||||
linkFromID,
|
||||
)
|
||||
dstfeed.Items = append(dstfeed.Items, Item{
|
||||
GUID: firstNonEmpty(guidFromID, srcitem.ID, link),
|
||||
Date: dateParse(firstNonEmpty(srcitem.Published, srcitem.Updated)),
|
||||
URL: link,
|
||||
Title: srcitem.Title.Text(),
|
||||
Content: firstNonEmpty(srcitem.Content.String(), srcitem.Summary.String(), srcitem.firstMediaDescription()),
|
||||
Content: firstNonEmpty(
|
||||
srcitem.Content.String(),
|
||||
srcitem.Summary.String(),
|
||||
srcitem.firstMediaDescription(),
|
||||
),
|
||||
MediaLinks: mediaLinks,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,7 +40,12 @@ func TestSniff(t *testing.T) {
|
||||
want := testcase.want
|
||||
have := sniff(testcase.input)
|
||||
if want.encoding != have.encoding || want.feedType != have.feedType {
|
||||
t.Errorf("Invalid output\n---\n%s\n---\n\nwant=%#v\nhave=%#v", testcase.input, want, have)
|
||||
t.Errorf(
|
||||
"Invalid output\n---\n%s\n---\n\nwant=%#v\nhave=%#v",
|
||||
testcase.input,
|
||||
want,
|
||||
have,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,10 @@ func (m *media) mediaLinks() []MediaLink {
|
||||
} else if strings.HasPrefix(content.MediaType, "video/") {
|
||||
links = append(links, MediaLink{URL: url, Type: "video", Description: description})
|
||||
} else if content.MediaMedium == "image" || content.MediaMedium == "audio" || content.MediaMedium == "video" {
|
||||
links = append(links, MediaLink{URL: url, Type: content.MediaMedium, Description: description})
|
||||
links = append(
|
||||
links,
|
||||
MediaLink{URL: url, Type: content.MediaMedium, Description: description},
|
||||
)
|
||||
} else {
|
||||
if len(content.MediaThumbnails) > 0 {
|
||||
links = append(links, MediaLink{
|
||||
|
||||
@@ -42,8 +42,16 @@ func TestRDFFeed(t *testing.T) {
|
||||
Title: "Mozilla Dot Org",
|
||||
SiteURL: "http://www.mozilla.org",
|
||||
Items: []Item{
|
||||
{GUID: "http://www.mozilla.org/status/", URL: "http://www.mozilla.org/status/", Title: "New Status Updates"},
|
||||
{GUID: "http://www.mozilla.org/bugs/", URL: "http://www.mozilla.org/bugs/", Title: "Bugzilla Reorganized"},
|
||||
{
|
||||
GUID: "http://www.mozilla.org/status/",
|
||||
URL: "http://www.mozilla.org/status/",
|
||||
Title: "New Status Updates",
|
||||
},
|
||||
{
|
||||
GUID: "http://www.mozilla.org/bugs/",
|
||||
URL: "http://www.mozilla.org/bugs/",
|
||||
Title: "Bugzilla Reorganized",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ func ParseRSS(r io.Reader) (*Feed, error) {
|
||||
for _, e := range srcitem.Enclosures {
|
||||
if strings.HasPrefix(e.Type, "audio/") {
|
||||
podcastURL := e.URL
|
||||
if srcitem.OrigEnclosureLink != "" && strings.Contains(podcastURL, path.Base(srcitem.OrigEnclosureLink)) {
|
||||
if srcitem.OrigEnclosureLink != "" &&
|
||||
strings.Contains(podcastURL, path.Base(srcitem.OrigEnclosureLink)) {
|
||||
podcastURL = srcitem.OrigEnclosureLink
|
||||
}
|
||||
mediaLinks = append(mediaLinks, MediaLink{URL: podcastURL, Type: "audio"})
|
||||
@@ -101,7 +102,11 @@ func ParseRSS(r io.Reader) (*Feed, error) {
|
||||
Date: dateParse(firstNonEmpty(srcitem.DublinCoreDate, srcitem.PubDate)),
|
||||
URL: firstNonEmpty(srcitem.OrigLink, srcitem.Link, permalink),
|
||||
Title: srcitem.Title,
|
||||
Content: firstNonEmpty(srcitem.ContentEncoded, srcitem.Description, srcitem.firstMediaDescription()),
|
||||
Content: firstNonEmpty(
|
||||
srcitem.ContentEncoded,
|
||||
srcitem.Description,
|
||||
srcitem.firstMediaDescription(),
|
||||
),
|
||||
MediaLinks: mediaLinks,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -303,9 +303,21 @@ func TestRSSMultipleMedia(t *testing.T) {
|
||||
GUID: "http://example.com/posts/1",
|
||||
URL: "http://example.com/posts/1",
|
||||
MediaLinks: []MediaLink{
|
||||
{URL: "https://example.com/path/to/image1.png", Type: "image", Description: "description 1"},
|
||||
{URL: "https://example.com/path/to/image2.png", Type: "image", Description: "description 2"},
|
||||
{URL: "https://example.com/path/to/video1.mp4", Type: "video", Description: "video description"},
|
||||
{
|
||||
URL: "https://example.com/path/to/image1.png",
|
||||
Type: "image",
|
||||
Description: "description 1",
|
||||
},
|
||||
{
|
||||
URL: "https://example.com/path/to/image2.png",
|
||||
Type: "image",
|
||||
Description: "description 2",
|
||||
},
|
||||
{
|
||||
URL: "https://example.com/path/to/video1.mp4",
|
||||
Type: "video",
|
||||
Description: "video description",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -78,7 +78,11 @@ func TestParseFallback(t *testing.T) {
|
||||
Folders: []Folder{{
|
||||
Title: "foldertitle",
|
||||
Feeds: []Feed{
|
||||
{Title: "feedtext", FeedUrl: "https://example.com/feed.xml", SiteUrl: "https://example.com"},
|
||||
{
|
||||
Title: "feedtext",
|
||||
FeedUrl: "https://example.com/feed.xml",
|
||||
SiteUrl: "https://example.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ func (s *Server) handleStatic(c *router.Context) {
|
||||
c.Out.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.StripPrefix(s.BasePath+"/static/", http.FileServer(http.FS(assets.FS))).ServeHTTP(c.Out, c.Req)
|
||||
http.StripPrefix(s.BasePath+"/static/", http.FileServer(http.FS(assets.FS))).
|
||||
ServeHTTP(c.Out, c.Req)
|
||||
}
|
||||
|
||||
func (s *Server) handleManifest(c *router.Context) {
|
||||
@@ -236,7 +237,10 @@ func (s *Server) handleFeedList(c *router.Context) {
|
||||
log.Printf("Faild to discover feed for %s: %s", form.Url, err)
|
||||
c.JSON(http.StatusOK, map[string]string{"status": "notfound"})
|
||||
case len(result.Sources) > 0:
|
||||
c.JSON(http.StatusOK, map[string]interface{}{"status": "multiple", "choice": result.Sources})
|
||||
c.JSON(
|
||||
http.StatusOK,
|
||||
map[string]interface{}{"status": "multiple", "choice": result.Sources},
|
||||
)
|
||||
case result.Feed != nil:
|
||||
feed := s.db.CreateFeed(
|
||||
result.Feed.Title,
|
||||
|
||||
@@ -191,7 +191,10 @@ func listQueryPredicate(filter ItemFilter, newestFirst bool) (string, []interfac
|
||||
terms[idx] = word + "*"
|
||||
}
|
||||
|
||||
cond = append(cond, "i.search_rowid in (select rowid from search where search match :search)")
|
||||
cond = append(
|
||||
cond,
|
||||
"i.search_rowid in (select rowid from search where search match :search)",
|
||||
)
|
||||
args = append(args, sql.Named("search", strings.Join(terms, " ")))
|
||||
}
|
||||
if filter.After != nil {
|
||||
@@ -199,7 +202,13 @@ func listQueryPredicate(filter ItemFilter, newestFirst bool) (string, []interfac
|
||||
if newestFirst {
|
||||
compare = "<"
|
||||
}
|
||||
cond = append(cond, fmt.Sprintf("(i.date, i.id) %s (select date, id from items where id = :after_id)", compare))
|
||||
cond = append(
|
||||
cond,
|
||||
fmt.Sprintf(
|
||||
"(i.date, i.id) %s (select date, id from items where id = :after_id)",
|
||||
compare,
|
||||
),
|
||||
)
|
||||
args = append(args, sql.Named("after_id", *filter.After))
|
||||
}
|
||||
if filter.IDs != nil && len(*filter.IDs) > 0 {
|
||||
@@ -249,7 +258,12 @@ func (s *Storage) CountItems(filter ItemFilter) int {
|
||||
return count
|
||||
}
|
||||
|
||||
func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, withContent bool) []Item {
|
||||
func (s *Storage) ListItems(
|
||||
filter ItemFilter,
|
||||
limit int,
|
||||
newestFirst bool,
|
||||
withContent bool,
|
||||
) []Item {
|
||||
predicate, args := listQueryPredicate(filter, newestFirst)
|
||||
result := make([]Item, 0)
|
||||
|
||||
@@ -450,7 +464,8 @@ func (s *Storage) DeleteOldItems() {
|
||||
}
|
||||
|
||||
for feedId, limit := range feedLimits {
|
||||
result, err := s.db.Exec(`
|
||||
result, err := s.db.Exec(
|
||||
`
|
||||
delete from items
|
||||
where id in (
|
||||
select i.id
|
||||
@@ -463,7 +478,10 @@ func (s *Storage) DeleteOldItems() {
|
||||
sql.Named("feed_id", feedId),
|
||||
sql.Named("starred_status", STARRED),
|
||||
sql.Named("limit", limit),
|
||||
sql.Named("date_limit", time.Now().UTC().Add(-time.Hour*time.Duration(24*itemsKeepDays))),
|
||||
sql.Named(
|
||||
"date_limit",
|
||||
time.Now().UTC().Add(-time.Hour*time.Duration(24*itemsKeepDays)),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
|
||||
@@ -47,21 +47,62 @@ func testItemsSetup(db *Storage) testItemScope {
|
||||
db.CreateItems([]Item{
|
||||
// feed11
|
||||
{GUID: "item111", FeedId: feed11.Id, Title: "title111", Date: now.Add(time.Hour * 24 * 1)},
|
||||
{GUID: "item112", FeedId: feed11.Id, Title: "title112", Date: now.Add(time.Hour * 24 * 2)}, // read
|
||||
{GUID: "item113", FeedId: feed11.Id, Title: "title113", Date: now.Add(time.Hour * 24 * 3)}, // starred
|
||||
{
|
||||
GUID: "item112",
|
||||
FeedId: feed11.Id,
|
||||
Title: "title112",
|
||||
Date: now.Add(time.Hour * 24 * 2),
|
||||
}, // read
|
||||
{
|
||||
GUID: "item113",
|
||||
FeedId: feed11.Id,
|
||||
Title: "title113",
|
||||
Date: now.Add(time.Hour * 24 * 3),
|
||||
}, // starred
|
||||
// feed12
|
||||
{GUID: "item121", FeedId: feed12.Id, Title: "title121", Date: now.Add(time.Hour * 24 * 4)},
|
||||
{GUID: "item122", FeedId: feed12.Id, Title: "title122", Date: now.Add(time.Hour * 24 * 5)}, // read
|
||||
{
|
||||
GUID: "item122",
|
||||
FeedId: feed12.Id,
|
||||
Title: "title122",
|
||||
Date: now.Add(time.Hour * 24 * 5),
|
||||
}, // read
|
||||
// feed21
|
||||
{GUID: "item211", FeedId: feed21.Id, Title: "title211", Date: now.Add(time.Hour * 24 * 6)}, // read
|
||||
{GUID: "item212", FeedId: feed21.Id, Title: "title212", Date: now.Add(time.Hour * 24 * 7)}, // starred
|
||||
{
|
||||
GUID: "item211",
|
||||
FeedId: feed21.Id,
|
||||
Title: "title211",
|
||||
Date: now.Add(time.Hour * 24 * 6),
|
||||
}, // read
|
||||
{
|
||||
GUID: "item212",
|
||||
FeedId: feed21.Id,
|
||||
Title: "title212",
|
||||
Date: now.Add(time.Hour * 24 * 7),
|
||||
}, // starred
|
||||
// feed01
|
||||
{GUID: "item011", FeedId: feed01.Id, Title: "title011", Date: now.Add(time.Hour * 24 * 8)},
|
||||
{GUID: "item012", FeedId: feed01.Id, Title: "title012", Date: now.Add(time.Hour * 24 * 9)}, // read
|
||||
{GUID: "item013", FeedId: feed01.Id, Title: "title013", Date: now.Add(time.Hour * 24 * 10)}, // starred
|
||||
{
|
||||
GUID: "item012",
|
||||
FeedId: feed01.Id,
|
||||
Title: "title012",
|
||||
Date: now.Add(time.Hour * 24 * 9),
|
||||
}, // read
|
||||
{
|
||||
GUID: "item013",
|
||||
FeedId: feed01.Id,
|
||||
Title: "title013",
|
||||
Date: now.Add(time.Hour * 24 * 10),
|
||||
}, // starred
|
||||
})
|
||||
db.db.Exec(`update items set status = :status where guid in ("item112", "item122", "item211", "item012")`, sql.Named("status", READ))
|
||||
db.db.Exec(`update items set status = :status where guid in ("item113", "item212", "item013")`, sql.Named("status", STARRED))
|
||||
db.db.Exec(
|
||||
`update items set status = :status where guid in ("item112", "item122", "item211", "item012")`,
|
||||
sql.Named("status", READ),
|
||||
)
|
||||
db.db.Exec(
|
||||
`update items set status = :status where guid in ("item113", "item212", "item013")`,
|
||||
sql.Named("status", STARRED),
|
||||
)
|
||||
|
||||
return testItemScope{
|
||||
feed11: feed11,
|
||||
@@ -208,7 +249,9 @@ func TestListItemsPaginated(t *testing.T) {
|
||||
|
||||
// unread, newest first
|
||||
unread := UNREAD
|
||||
have = getItemGuids(db.ListItems(ItemFilter{After: &item012.Id, Status: &unread}, 3, true, false))
|
||||
have = getItemGuids(
|
||||
db.ListItems(ItemFilter{After: &item012.Id, Status: &unread}, 3, true, false),
|
||||
)
|
||||
want = []string{"item011", "item121", "item111"}
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Logf("want: %#v", want)
|
||||
@@ -218,7 +261,9 @@ func TestListItemsPaginated(t *testing.T) {
|
||||
|
||||
// starred, oldest first
|
||||
starred := STARRED
|
||||
have = getItemGuids(db.ListItems(ItemFilter{After: &item121.Id, Status: &starred}, 3, false, false))
|
||||
have = getItemGuids(
|
||||
db.ListItems(ItemFilter{After: &item121.Id, Status: &starred}, 3, false, false),
|
||||
)
|
||||
want = []string{"item212", "item013"}
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Logf("want: %#v", want)
|
||||
|
||||
@@ -248,7 +248,11 @@ var wt winTray
|
||||
|
||||
// WindowProc callback function that processes messages sent to a window.
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
|
||||
func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
func (t *winTray) wndProc(
|
||||
hWnd windows.Handle,
|
||||
message uint32,
|
||||
wParam, lParam uintptr,
|
||||
) (lResult uintptr) {
|
||||
const (
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_LBUTTONUP = 0x0202
|
||||
@@ -494,7 +498,12 @@ func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) {
|
||||
return menu, nil
|
||||
}
|
||||
|
||||
func (t *winTray) addOrUpdateMenuItem(menuItemId uint32, parentId uint32, title string, disabled, checked bool) error {
|
||||
func (t *winTray) addOrUpdateMenuItem(
|
||||
menuItemId uint32,
|
||||
parentId uint32,
|
||||
title string,
|
||||
disabled, checked bool,
|
||||
) error {
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
|
||||
const (
|
||||
MIIM_FTYPE = 0x00000100
|
||||
@@ -888,7 +897,13 @@ func (item *MenuItem) SetIcon(iconBytes []byte) {
|
||||
wt.menuItemIcons[uint32(item.id)] = h
|
||||
wt.muMenuItemIcons.Unlock()
|
||||
|
||||
err = wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
|
||||
err = wt.addOrUpdateMenuItem(
|
||||
uint32(item.id),
|
||||
item.parentId(),
|
||||
item.title,
|
||||
item.disabled,
|
||||
item.checked,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Unable to addOrUpdateMenuItem: %v", err)
|
||||
return
|
||||
@@ -905,7 +920,13 @@ func SetTooltip(tooltip string) {
|
||||
}
|
||||
|
||||
func addOrUpdateMenuItem(item *MenuItem) {
|
||||
err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
|
||||
err := wt.addOrUpdateMenuItem(
|
||||
uint32(item.id),
|
||||
item.parentId(),
|
||||
item.title,
|
||||
item.disabled,
|
||||
item.checked,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Unable to addOrUpdateMenuItem: %v", err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user