diff --git a/internal/reader/rewrite/rewrite_functions.go b/internal/reader/rewrite/rewrite_functions.go index 0e4aadbb7d2..6950c33304f 100644 --- a/internal/reader/rewrite/rewrite_functions.go +++ b/internal/reader/rewrite/rewrite_functions.go @@ -12,6 +12,7 @@ import ( "strings" "miniflux.app/v2/internal/config" + "miniflux.app/v2/internal/logger" "github.com/PuerkitoBio/goquery" "github.com/yuin/goldmark" @@ -321,6 +322,55 @@ func decodeBase64Content(entryContent string) string { } } +func addHackerNewsLinksUsing(entryContent, app string) string { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(entryContent)) + if err != nil { + return entryContent + } + + hn_prefix := "https://news.ycombinator.com/" + matches := doc.Find(`a[href^="` + hn_prefix + `"]`) + + if matches.Length() > 0 { + matches.Each(func(i int, a *goquery.Selection) { + hrefAttr, _ := a.Attr("href") + + hn_uri, err := url.Parse(hrefAttr) + if err != nil { + return + } + + if app == "opener" { + params := url.Values{} + params.Add("url", hn_uri.String()) + + url := url.URL{ + Scheme: "opener", + Host: "x-callback-url", + Path: "show-options", + RawQuery: params.Encode(), + } + + open_with_opener := `Open with Opener` + a.Parent().AppendHtml(" " + open_with_opener) + } else if app == "hack" { + url := strings.Replace(hn_uri.String(), hn_prefix, "hack://", 1) + + open_with_hack := `Open with HACK` + a.Parent().AppendHtml(" " + open_with_hack) + } else { + logger.Error("[openHackerNewsLinksWith] unknown app provided: %q", app) + return + } + }) + + output, _ := doc.Find("body").First().Html() + return output + } + + return entryContent +} + func parseMarkdown(entryContent string) string { var sb strings.Builder md := goldmark.New( diff --git a/internal/reader/rewrite/rewriter.go b/internal/reader/rewrite/rewriter.go index 40ca492fd25..65c66ff2675 100644 --- a/internal/reader/rewrite/rewriter.go +++ b/internal/reader/rewrite/rewriter.go @@ -113,6 +113,10 @@ func applyRule(entryURL string, entry *model.Entry, rule rule) { } else { entry.Content = applyFuncOnTextContent(entry.Content, "body", decodeBase64Content) } + case "add_hn_links_using_hack": + entry.Content = addHackerNewsLinksUsing(entry.Content, "hack") + case "add_hn_links_using_opener": + entry.Content = addHackerNewsLinksUsing(entry.Content, "opener") case "parse_markdown": entry.Content = parseMarkdown(entry.Content) case "remove_tables": diff --git a/internal/reader/rewrite/rewriter_test.go b/internal/reader/rewrite/rewriter_test.go index 6b9d2013cb0..bbf5bfdda58 100644 --- a/internal/reader/rewrite/rewriter_test.go +++ b/internal/reader/rewrite/rewriter_test.go @@ -577,3 +577,49 @@ func TestRemoveClickbait(t *testing.T) { t.Errorf(`Not expected output: got "%+v" instead of "%+v"`, testEntry, controlEntry) } } + +func TestAddHackerNewsLinksUsingHack(t *testing.T) { + testEntry := &model.Entry{ + Title: `A title`, + Content: `
Article URL: https://example.org/article
+Comments URL: https://news.ycombinator.com/item?id=37620043
+Points: 23
+# Comments: 38
`, + } + + controlEntry := &model.Entry{ + Title: `A title`, + Content: `Article URL: https://example.org/article
+Comments URL: https://news.ycombinator.com/item?id=37620043 Open with HACK
+Points: 23
+# Comments: 38
`, + } + Rewriter("https://example.org/article", testEntry, `add_hn_links_using_hack`) + + if !reflect.DeepEqual(testEntry, controlEntry) { + t.Errorf(`Not expected output: got "%+v" instead of "%+v"`, testEntry, controlEntry) + } +} + +func TestAddHackerNewsLinksUsingOpener(t *testing.T) { + testEntry := &model.Entry{ + Title: `A title`, + Content: `Article URL: https://example.org/article
+Comments URL: https://news.ycombinator.com/item?id=37620043
+Points: 23
+# Comments: 38
`, + } + + controlEntry := &model.Entry{ + Title: `A title`, + Content: `Article URL: https://example.org/article
+Comments URL: https://news.ycombinator.com/item?id=37620043 Open with Opener
+Points: 23
+# Comments: 38
`, + } + Rewriter("https://example.org/article", testEntry, `add_hn_links_using_opener`) + + if !reflect.DeepEqual(testEntry, controlEntry) { + t.Errorf(`Not expected output: got "%+v" instead of "%+v"`, testEntry, controlEntry) + } +} diff --git a/internal/reader/sanitizer/sanitizer.go b/internal/reader/sanitizer/sanitizer.go index 84c15dc34c7..958058ed302 100644 --- a/internal/reader/sanitizer/sanitizer.go +++ b/internal/reader/sanitizer/sanitizer.go @@ -297,6 +297,10 @@ func hasValidURIScheme(src string) bool { "tel:", "webcal://", "xmpp:", + + // iOS Apps + "opener://", // https://www.opener.link + "hack://", // https://apps.apple.com/it/app/hack-for-hacker-news-reader/id1464477788?l=en-GB } for _, prefix := range whitelist {