]> Sergey Matveev's repositories - feeder.git/blob - cmd/feed2mdir/main.go
45e5d66edbf5eba1929979834552cd63ad1ddb15
[feeder.git] / cmd / feed2mdir / main.go
1 /*
2 feeder  -- newsfeeds aggregator
3 Copyright (C) 2022 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "crypto/sha512"
22         "encoding/base64"
23         "encoding/hex"
24         "flag"
25         "fmt"
26         "log"
27         "mime"
28         "os"
29         "path"
30         "strings"
31         "time"
32
33         "github.com/mmcdole/gofeed"
34 )
35
36 func main() {
37         maxEntries := flag.Uint("max-entries", 0, "Max entries to process (0=unlimited)")
38         flag.Parse()
39         mdir := flag.Arg(0)
40         fp := gofeed.NewParser()
41         feed, err := fp.Parse(os.Stdin)
42         if err != nil {
43                 log.Fatalln(err)
44         }
45
46         guids := make(map[string]struct{}, len(feed.Items))
47         useGUID := true
48         for _, item := range feed.Items {
49                 if _, exists := guids[item.GUID]; exists {
50                         useGUID = false
51                         break
52                 } else {
53                         guids[item.GUID] = struct{}{}
54                 }
55         }
56
57         h := sha512.New()
58         news := 0
59         var when *time.Time
60         now := time.Now()
61         latest := &time.Time{}
62         for n, item := range feed.Items {
63                 if *maxEntries > 0 && n == int(*maxEntries) {
64                         break
65                 }
66                 when = nil
67                 if item.PublishedParsed != nil {
68                         when = item.PublishedParsed
69                 } else if item.UpdatedParsed != nil {
70                         when = item.UpdatedParsed
71                 } else {
72                         when = &now
73                 }
74                 if latest.Before(*when) {
75                         latest = when
76                 }
77                 var what string
78                 if len(item.Content) == 0 {
79                         what = item.Description
80                 } else {
81                         what = item.Content
82                 }
83                 what = strings.TrimPrefix(what, "<![CDATA[")
84                 what = strings.TrimSuffix(what, "]]>")
85                 h.Reset()
86                 if useGUID {
87                         h.Write([]byte(item.GUID))
88                 } else {
89                         h.Write([]byte(item.Title))
90                         h.Write([]byte{0})
91                         h.Write([]byte(what))
92                 }
93                 fn := hex.EncodeToString(h.Sum(nil)[:sha512.Size/2])
94                 exists := false
95                 for _, d := range []string{"cur", "new"} {
96                         entries, err := os.ReadDir(path.Join(mdir, d))
97                         if err != nil {
98                                 log.Fatalln(err)
99                         }
100                         for _, entry := range entries {
101                                 if strings.HasPrefix(entry.Name(), fn) {
102                                         exists = true
103                                         break
104                                 }
105                         }
106                 }
107                 if exists {
108                         continue
109                 }
110                 fn = path.Join(mdir, "new", fn)
111                 fd, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
112                 if err != nil {
113                         log.Fatalln(err)
114                 }
115                 fd.WriteString("From: \"" + feed.Title + "\" <feeder@localhost\n")
116                 fd.WriteString("Date: " + when.UTC().Format(time.RFC1123Z) + "\n")
117                 fd.WriteString("Subject: " + mime.BEncoding.Encode("UTF-8", item.Title) + "\n")
118                 fd.WriteString("MIME-Version: 1.0\n")
119                 fd.WriteString("Content-Type: text/html; charset=utf-8\n")
120                 fd.WriteString("Content-Transfer-Encoding: base64\n")
121                 for _, author := range item.Authors {
122                         if len(author.Name) > 0 {
123                                 fd.WriteString("X-Author: " + author.Name + "\n")
124                         }
125                 }
126                 for _, link := range item.Links {
127                         fd.WriteString("X-URL: " + link + "\n")
128                 }
129                 for _, enc := range item.Enclosures {
130                         fd.WriteString("X-Enclosure: " + enc.URL + "\n")
131                 }
132                 if len(item.Categories) > 0 {
133                         fd.WriteString("X-Categories: " + strings.Join(item.Categories, ", ") + "\n")
134                 }
135                 fd.WriteString("\n")
136                 what = base64.StdEncoding.EncodeToString([]byte(what))
137                 for i := 0; i < len(what); i += 72 {
138                         b := i + 72
139                         if b > len(what) {
140                                 b = len(what)
141                         }
142                         fd.WriteString(what[i:b] + "\n")
143                 }
144                 fd.Close()
145                 if err = os.Chtimes(fn, *when, *when); err != nil {
146                         log.Fatalln(err)
147                 }
148                 news++
149         }
150         for _, d := range []string{"cur", "new"} {
151                 if err = os.Chtimes(path.Join(mdir, d), *latest, *latest); err != nil {
152                         log.Fatalln(err)
153                 }
154         }
155         fmt.Println(feed.Title)
156 }