package main import ( "fmt" "io/ioutil" "log" "strings" "time" "gopkg.in/yaml.v2" "github.com/matrix-org/gomatrix" "github.com/mmcdole/gofeed" "golang.org/x/net/html" ) type Config struct { Homeserver string `yaml:"homeserver"` Username string `yaml:"username"` Password string `yaml:"password"` Feeds []struct { URL string `yaml:"url"` Alias string `yaml:"alias"` } `yaml:"feeds"` } var ( client *gomatrix.Client processedFeed map[string]map[string]bool // 保存已处理的条目链接 ) func loadConfig(filename string) (*Config, error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, err } var config Config err = yaml.Unmarshal(data, &config) if err != nil { return nil, err } return &config, nil } func login(config *Config) error { var err error client, err = gomatrix.NewClient(config.Homeserver, "", "") if err != nil { return err } resp, err := client.Login(&gomatrix.ReqLogin{ Type: "m.login.password", User: config.Username, Password: config.Password, }) if err != nil { return err } client.SetCredentials(resp.UserID, resp.AccessToken) return nil } func processFeed(fp *gofeed.Parser, feedURL, alias string) error { rssFeed, err := fp.ParseURL(feedURL) if err != nil { return fmt.Errorf("failed to parse feed %s: %v", feedURL, err) } if processedFeed[alias] == nil { processedFeed[alias] = make(map[string]bool) } for _, item := range rssFeed.Items { if processedFeed[alias][item.Link] { continue // Skip already processed items } // Extract and format message content message := formatMessage(item) htmlMessage := formatHTMLMessage(item) _, err := client.SendMessageEvent(alias, "m.room.message", map[string]interface{}{ "msgtype": "m.text", "body": message, "format": "org.matrix.custom.html", "formatted_body": htmlMessage, }) if err != nil { log.Printf("Failed to send message to %s: %v", alias, err) } else { log.Printf("Successfully sent message to %s", alias) processedFeed[alias][item.Link] = true } } return nil } func formatMessage(item *gofeed.Item) string { message := fmt.Sprintf("**%s**\n%s\n\n%s", item.Title, item.Published, item.Link) if item.Description != "" { message += fmt.Sprintf("\n\n%s", extractText(item.Description)) } if item.Image != nil { message += fmt.Sprintf("\n\n![Image](%s)", item.Image.URL) } if item.Author != nil { message += fmt.Sprintf("\n\nAuthor: %s", item.Author.Name) } message += "\n\nSponsor: 更流畅的富强节点,与时代共「加速」" return message } func formatHTMLMessage(item *gofeed.Item) string { htmlMessage := fmt.Sprintf("%s
%s
%s

%s", item.Title, item.Published, item.Link, item.Link, extractText(item.Description)) if item.Image != nil { htmlMessage += fmt.Sprintf("
\"Image\"", item.Image.URL) } if item.Author != nil { htmlMessage += fmt.Sprintf("

Author: %s", item.Author.Name) } htmlMessage += "

Sponsor: 更流畅的富强节点,与时代共「加速」" return htmlMessage } // Extract text content from HTML func extractText(htmlContent string) string { doc, err := html.Parse(strings.NewReader(htmlContent)) if err != nil { log.Printf("Failed to parse HTML content: %v", err) return "" } var textContent strings.Builder var f func(*html.Node) f = func(n *html.Node) { if n.Type == html.TextNode { textContent.WriteString(n.Data) } for c := n.FirstChild; c != nil; c = c.NextSibling { f(c) } } f(doc) return textContent.String() } func main() { config, err := loadConfig("config.yaml") if err != nil { log.Fatalf("Failed to load config: %v", err) } err = login(config) if err != nil { log.Fatalf("Failed to login: %v", err) } fp := gofeed.NewParser() processedFeed = make(map[string]map[string]bool) ticker := time.NewTicker(10 * time.Minute) defer ticker.Stop() for { for _, feed := range config.Feeds { err := processFeed(fp, feed.URL, feed.Alias) if err != nil { log.Printf("Error processing feed %s: %v", feed.URL, err) } } <-ticker.C } }