1
0
Fork 1
mirror of https://github.com/0x2E/fusion.git synced 2025-06-08 05:27:15 +09:00
fusion/pkg/feedfinder/service.go
Yuan 4c79b2b2fa
Fix http proxy (#144)
* update deps

* make the sniff package independent

* rename sniffer to feedfinder for  a more neutral meaning

* allow setting Proxy when creating a feed
2025-04-24 01:18:44 +08:00

198 lines
6.5 KiB
Go

package feedfinder
import (
"context"
"fmt"
"io"
"regexp"
"strings"
)
type serviceMatcher func(context.Context) ([]FeedLink, error)
func (f *Finder) tryService(ctx context.Context) ([]FeedLink, error) {
matcher := []serviceMatcher{
f.githubMatcher,
f.redditMatcher,
f.youtubeMatcher,
}
for _, fn := range matcher {
feed, err := fn(ctx)
if err != nil {
continue
}
if len(feed) != 0 {
return feed, nil
}
}
return nil, nil
}
var githubGlobalFeed = []FeedLink{
{Title: "global public timeline", Link: "https://github.com/timeline"},
{Title: "global security advisories", Link: "https://github.com/security-advisories.atom"},
}
// https://docs.github.com/en/rest/activity/feeds?apiVersion=2022-11-28#get-feeds
func (s Finder) githubMatcher(ctx context.Context) ([]FeedLink, error) {
if !strings.HasSuffix(s.target.Hostname(), "github.com") {
return nil, nil
}
splited := strings.SplitN(s.target.Path, "/", 4) // split "/user/repo/" -> []string{"", "user", "repo", ""}
splitedLen := len(splited)
if splitedLen < 2 {
return githubGlobalFeed, nil
}
username, reponame := "", ""
if splitedLen >= 2 {
username = splited[1]
}
if splitedLen >= 3 {
reponame = splited[2]
}
if reponame != "" {
re, err := regexp.Compile(`^[a-zA-Z0-9][a-zA-Z0-9-_\.]{0,98}[a-zA-Z0-9]$`) // todo need improve
if err != nil {
return nil, err
}
if !re.MatchString(reponame) {
return nil, nil
}
return genGitHubRepoFeed(username + "/" + reponame), nil
}
if username != "" {
re, err := regexp.Compile(`^[a-zA-Z0-9][-]?[a-zA-Z0-9]{0,38}$`)
if err != nil {
return nil, err
}
if !re.MatchString(username) {
return nil, nil
}
return genGitHubUserFeed(username), nil
}
return nil, nil
}
func genGitHubUserFeed(username string) []FeedLink {
return []FeedLink{{Title: username + " public timeline", Link: fmt.Sprintf("https://github.com/%s.atom", username)}}
}
func genGitHubRepoFeed(userRepo string) []FeedLink {
return []FeedLink{
{Title: fmt.Sprintf("%s commits", userRepo), Link: fmt.Sprintf("https://github.com/%s/commits.atom", userRepo)},
{Title: fmt.Sprintf("%s releases", userRepo), Link: fmt.Sprintf("https://github.com/%s/releases.atom", userRepo)},
{Title: fmt.Sprintf("%s tags", userRepo), Link: fmt.Sprintf("https://github.com/%s/tags.atom", userRepo)},
{Title: fmt.Sprintf("%s wiki", userRepo), Link: fmt.Sprintf("https://github.com/%s/wiki.atom", userRepo)},
}
}
var redditGlobalFeed = []FeedLink{
{Title: "global", Link: "https://www.reddit.com/.rss"},
}
// https://www.reddit.com/wiki/rss/
func (s Finder) redditMatcher(ctx context.Context) ([]FeedLink, error) {
if !strings.HasSuffix(s.target.Hostname(), "reddit.com") {
return nil, nil
}
splited := strings.SplitN(s.target.Path, "/", 4)
splitedLen := len(splited)
if splitedLen < 2 {
return redditGlobalFeed, nil
}
mode, param := splited[1], splited[2]
switch mode {
case "r":
if splitedLen >= 4 && strings.HasPrefix(splited[3], "comments") {
// "comments/{postID}/{title}"
// "comments/{postID}/{title}/comment/{commentID}"
return genRedditCommentFeed(s.target.String()), nil
}
return genRedditSubFeed(param), nil
case "user":
return genRedditUserFeed(param), nil
case "domain":
return genRedditDomainSubmissionFeed(param), nil
default:
}
return nil, nil
}
func genRedditSubFeed(sub string) []FeedLink {
return []FeedLink{
{Title: fmt.Sprintf("/r/%s hot", sub), Link: fmt.Sprintf("https://reddit.com/r/%s/hot/.rss", sub)},
{Title: fmt.Sprintf("/r/%s new", sub), Link: fmt.Sprintf("https://reddit.com/r/%s/new/.rss", sub)},
{Title: fmt.Sprintf("/r/%s top", sub), Link: fmt.Sprintf("https://reddit.com/r/%s/top/.rss", sub)},
{Title: fmt.Sprintf("/r/%s rising", sub), Link: fmt.Sprintf("https://reddit.com/r/%s/rising/.rss", sub)},
}
}
func genRedditCommentFeed(fullURL string) []FeedLink {
return []FeedLink{{Title: "post", Link: fullURL + ".rss"}}
}
func genRedditUserFeed(username string) []FeedLink {
return []FeedLink{
{Title: fmt.Sprintf("/u/%s overview new", username), Link: fmt.Sprintf("https://reddit.com/user/%s/.rss?sort=new", username)},
{Title: fmt.Sprintf("/u/%s overview hot", username), Link: fmt.Sprintf("https://reddit.com/user/%s/.rss?sort=hot", username)},
{Title: fmt.Sprintf("/u/%s overview top", username), Link: fmt.Sprintf("https://reddit.com/user/%s/.rss?sort=top", username)},
{Title: fmt.Sprintf("/u/%s post new", username), Link: fmt.Sprintf("https://reddit.com/user/%s/submitted/.rss?sort=new", username)},
{Title: fmt.Sprintf("/u/%s post hot", username), Link: fmt.Sprintf("https://reddit.com/user/%s/submitted/.rss?sort=hot", username)},
{Title: fmt.Sprintf("/u/%s post top", username), Link: fmt.Sprintf("https://reddit.com/user/%s/submitted/.rss?sort=top", username)},
{Title: fmt.Sprintf("/u/%s comments new", username), Link: fmt.Sprintf("https://reddit.com/user/%s/comments/.rss?sort=new", username)},
{Title: fmt.Sprintf("/u/%s comments hot", username), Link: fmt.Sprintf("https://reddit.com/user/%s/comments/.rss?sort=hot", username)},
{Title: fmt.Sprintf("/u/%s comments top", username), Link: fmt.Sprintf("https://reddit.com/user/%s/comments/.rss?sort=top", username)},
{Title: fmt.Sprintf("/u/%s awards received (legacy)", username), Link: fmt.Sprintf("https://old.reddit.com/user/%s/gilded/.rss", username)},
}
}
func genRedditDomainSubmissionFeed(domain string) []FeedLink {
return []FeedLink{{Title: "/domain/" + domain, Link: fmt.Sprintf("https://reddit.com/domain/%s/.rss", domain)}}
}
func (s Finder) youtubeMatcher(ctx context.Context) ([]FeedLink, error) {
if !strings.HasSuffix(s.target.Hostname(), "youtube.com") && !strings.HasSuffix(s.target.Hostname(), "youtu.be") {
return nil, nil
}
resp, err := s.httpClient.Get(s.target.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if strings.HasPrefix(s.target.Path, "/@") {
re, err := regexp.Compile(`{"key":"browse_id","value":"(.+?)"}`)
if err != nil {
return nil, err
}
match := re.FindStringSubmatch(string(content))
if len(match) < 2 {
return nil, nil
}
id := match[1]
if id == "" {
return nil, nil
}
return []FeedLink{{Title: "Channel", Link: "https://www.youtube.com/feeds/videos.xml?channel_id=" + id}}, nil
} else if strings.HasPrefix(s.target.Path, "/playlist") {
id := s.target.Query().Get("list")
if id == "" {
return nil, nil
}
return []FeedLink{{Title: "Playlist", Link: "https://www.youtube.com/feeds/videos.xml?playlist_id=" + id}}, nil
}
return nil, nil
}