.
675 |
--------------------------------------------------------------------------------
/README.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 | rssc - rss customizer
9 |
171 |
172 |
173 |
176 | rssc provides a real-time, self-hostable regex-oriented
177 | customization service for atom, rss and json feeds.
178 | Usage
179 | rssc uses get request parameters to read user's preferences.
180 | following are the supported parameters:
181 |
182 | -
183 |
src
: the source URL of the feeds. Example:
184 | https://hnrss.org/newest
186 |
187 | t
: the type of desired feeds (that rssc will
188 | return), can be rss
, atom
or
189 | json
.
190 | descriptionf
: regular expression filter to
191 | filter feeds based on the description property.
192 | titlef
: regular expression filter to filter
193 | feeds based on the title property.
194 | contentf
: regular expression filter to filter
195 | feeds based on the content property.
196 | linkf
: regular expression filter to filter
197 | feeds based on the link property.
198 | net
: boolean (1 or 0) (default to 0 when
199 | omitted) whether to use the .NET engine regex instead of
200 | Go's.
201 |
202 | All regex should be valid RE2 regex syntax
204 | (in case of net
, see
206 | MS' Regular Expression Language).
207 | Examples
208 | Replace https://rssc.fly.dev/ in the
210 | following examples with your instance of rssc (can be a
211 | localhost:8080
if self-hosted).
212 |
213 | -
214 |
Get all new feeds that contain the word "emacs" but not
215 | the word "vim", and not posted by user lr0 (hnrss.org does
216 | not provide a creator/author property, so the author filter
217 | here is only for a demonstration purpose.):
218 |
219 | https://rssc.fly.dev/rss?src=https://hnrss.org/newest?q=emacs&titlef=^(?!.*\bvim\b).*\bEmacs\b.*&net=1&authorf=^(?!.*\blr0\b).*$
220 | Do note that net
is used here because RE2
221 | does not support lookarounds.
222 |
223 | -
224 |
Get geopolitical updates from your favorite hacker and
225 | skip all free software movement blessed propaganda:
226 |
227 | https://rssc.fly.dev/rss?src=https://stallman.org/rss/rss.xml&contentf=^(?=.*(?:palestine|syria|egypt|iraq|israel|algeria|morocco))(?!.*(?:linux|gnu|software|programming|program)).*$&net=1
228 |
229 | -
230 |
Get BBC middle east updates, only ones that are related to
231 | Palestine:
232 |
233 | https://rssc.fly.dev/rss?src=http://feeds.bbci.co.uk/news/world/middle_east/rss.xml&titlef=(?i)palestine|palestinian|gaza
234 |
235 |
236 | Self-host
237 | To host rssc locally, install it using Go:
238 | go install github.com/larrasket/rssc@latest
240 |
241 | Set environment variable for PORT
, if user
242 | doesn't set a PORT
value, rssc will use
243 | :8080
by default.
244 | PORT=4040 rssc
245 |
246 | Now rssc should be running at localhost:4040
. You
247 | can use the same examples provided, with
248 | replacing fly domain with your localhost.
249 | Notes
250 |
251 | - RE2 is much safer than the .NET Regex, that's to say, all
252 | the good features that .NET engine enables you to use come with
253 | a risk cost and ends up enabling
255 | catastrophic backtracking, therefore a timeout of 5
256 | seconds is currently enabled on the fly.dev instance when
257 | using
net=1
.
258 |
259 | - rssc is pre-alpha. Please report bugs here (url)
262 |
263 |
264 |
265 |
266 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | rssc provides a real-time, self-hostable regex-oriented customization service for atom,
2 | rss and json feeds.
3 |
4 |
5 | # Usage
6 |
7 | rssc uses get request parameters to read user’s preferences. following are the
8 | supported parameters:
9 |
10 | - `src`: the source URL of the feeds. Example:
11 | - `t`: the type of desired feeds (that rssc will return), can be `rss`, `atom` or `json`.
12 | - `descriptionf`: regular expression filter to filter feeds based on the description property.
13 | - `titlef`: regular expression filter to filter feeds based on the title property.
14 | - `contentf`: regular expression filter to filter feeds based on the content property.
15 | - `linkf`: regular expression filter to filter feeds based on the link property.
16 | - `net`: boolean (1 or 0) (default to 0 when omitted) whether to use the .NET
17 | engine regex instead of Go’s.
18 |
19 | All regex should be valid [RE2 regex syntax](https://github.com/google/re2/wiki/Syntax) (in case of `net`, see [MS’ Regular Expression Language](https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference)).
20 |
21 |
22 |
23 |
24 | # Examples
25 |
26 | Replace in the following examples with your instance of
27 | rssc (can be a `localhost:8080` if self-hosted).
28 |
29 | - Get all new feeds that contain the word “emacs” but not the word “vim”, and
30 | not posted by user lr0 (hnrss.org does not provide a creator/author property,
31 | so the author filter here is only for a demonstration purpose.):
32 |
33 | [https://rssc.fly.dev/rss?src=https://hnrss.org/newest?q=emacs&titlef=^(?!.\*\bvim\b).\*\bEmacs\b.\*&net=1&authorf=^(?!.\*\blr0\b).\*$](https://rssc.fly.dev/rss?src=https://hnrss.org/newest?q=emacs&titlef=^(?!.*\bvim\b).*\bEmacs\b.*&net=1&authorf=^(?!.*\blr0\b).*$)
34 |
35 | Do note that `net` is used here because RE2 does not support lookarounds.
36 |
37 | - Get geopolitical updates from your favorite hacker and skip all free software
38 | movement blessed propaganda:
39 |
40 | [https://rssc.fly.dev/rss?src=https://stallman.org/rss/rss.xml&contentf=^(?=.\*(?:palestine|syria|egypt|iraq|israel|algeria|morocco))(?!.\*(?:linux|gnu|software|programming|program)).\*$&net=1](https://rssc.fly.dev/rss?src=https://stallman.org/rss/rss.xml&contentf=^(?=.*(?:palestine|syria|egypt|iraq|israel|algeria|morocco))(?!.*(?:linux|gnu|software|programming|program)).*$&net=1)
41 | - Get BBC middle east updates, only ones that are related to Palestine:
42 |
43 | [https://rssc.fly.dev/rss?src=http://feeds.bbci.co.uk/news/world/middle\_east/rss.xml&titlef=(?i)palestine|palestinian|gaza](https://rssc.fly.dev/rss?src=http://feeds.bbci.co.uk/news/world/middle_east/rss.xml&titlef=(?i)palestine|palestinian|gaza)
44 |
45 |
46 | # Self-host
47 |
48 | To host rssc locally, install it using Go:
49 |
50 | go install github.com/larrasket/rssc@latest
51 |
52 | Set environment variable for `PORT`, if user doesn’t set a `PORT` value, rssc will use `:8080` by default.
53 |
54 | PORT=4040 rssc
55 |
56 | Now rssc should be running at `localhost:4040`. You can use the same [examples](#ex)
57 | provided, with replacing fly domain with your localhost.
58 |
59 |
60 | # Notes
61 |
62 | - RE2 is much safer than the .NET Regex, that’s to say, all the good features
63 | that .NET engine enables you to use come with a risk cost and ends up
64 | enabling [catastrophic backtracking](https://github.com/dlclark/regexp2#catastrophic-backtracking-and-timeouts), therefore a timeout of 5 seconds is currently
65 | enabled on the fly.dev instance when using `net=1`.
66 | - rssc is pre-alpha. Please report bugs [here](mailto:~lr0/public-inbox@lists.sr.ht) ([url](https://lists.sr.ht/~lr0/public-inbox))
67 |
68 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+TITLE: rssc - rss customizer
2 | #+OPTIONS: num:nil
3 | #+OPTIONS: toc:nil
4 | #+OPTIONS: ^:nil
5 | #+OPTIONS: author:nil
6 |
7 |
8 | rssc provides a real-time, self-hostable regex-oriented customization service for atom,
9 | rss and json feeds.
10 |
11 | * Usage
12 | rssc uses get request parameters to read user's preferences. following are the
13 | supported parameters:
14 | + ~src~: the source URL of the feeds. Example: https://hnrss.org/newest
15 | + ~t~: the type of desired feeds (that rssc will return), can be ~rss~, ~atom~ or ~json~.
16 | + ~descriptionf~: regular expression filter to filter feeds based on the description property.
17 | + ~titlef~: regular expression filter to filter feeds based on the title property.
18 | + ~contentf~: regular expression filter to filter feeds based on the content property.
19 | + ~linkf~: regular expression filter to filter feeds based on the link property.
20 | + ~net~: boolean (1 or 0) (default to 0 when omitted) whether to use the .NET
21 | engine regex instead of Go's.
22 |
23 | All regex should be valid [[https://github.com/google/re2/wiki/Syntax][RE2 regex syntax]] (in case of ~net~, see [[https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference][MS' Regular Expression Language]]).
24 | * Examples
25 | :PROPERTIES:
26 | :CUSTOM_ID: ex
27 | :END:
28 | Replace https://rssc.fly.dev/ in the following examples with your instance of
29 | rssc (can be a ~localhost:8080~ if self-hosted).
30 |
31 | + Get all new feeds that contain the word "emacs" but not the word "vim", and
32 | not posted by user lr0 (hnrss.org does not provide a creator/author property,
33 | so the author filter here is only for a demonstration purpose.):
34 |
35 | ~https://rssc.fly.dev/rss?src=https://hnrss.org/newest?q=emacs&titlef=^(?!.*\bvim\b).*\bEmacs\b.*&net=1&authorf=^(?!.*\blr0\b).*$~
36 |
37 | Do note that ~net~ is used here because RE2 does not support lookarounds.
38 | + Get geopolitical updates from your favorite hacker and skip all free software
39 | movement blessed propaganda:
40 |
41 | ~https://rssc.fly.dev/rss?src=https://stallman.org/rss/rss.xml&contentf=^(?=.*(?:palestine|syria|egypt|iraq|israel|algeria|morocco))(?!.*(?:linux|gnu|software|programming|program)).*$&net=1~
42 | + Get BBC middle east updates, only ones that are related to Palestine:
43 |
44 | ~https://rssc.fly.dev/rss?src=http://feeds.bbci.co.uk/news/world/middle_east/rss.xml&titlef=(?i)palestine|palestinian|gaza~
45 |
46 | * Self-host
47 | To host rssc locally, install it using Go:
48 | #+begin_src shell
49 | go install github.com/larrasket/rssc@latest
50 | #+end_src
51 |
52 | Set environment variable for ~PORT~, if user doesn't set a ~PORT~ value, rssc will use ~:8080~ by default.
53 |
54 | #+begin_src shell
55 | PORT=4040 rssc
56 | #+end_src
57 |
58 | Now rssc should be running at ~localhost:4040~. You can use the same [[#ex][examples]]
59 | provided, with replacing fly domain with your localhost.
60 | * Notes
61 | + RE2 is much safer than the .NET Regex, that's to say, all the good features
62 | that .NET engine enables you to use come with a risk cost and ends up
63 | enabling [[https://github.com/dlclark/regexp2#catastrophic-backtracking-and-timeouts][catastrophic backtracking]], therefore a timeout of 5 seconds is currently
64 | enabled on the fly.dev instance when using ~net=1~.
65 | + rssc is pre-alpha. Please report bugs [[mailto:~lr0/public-inbox@lists.sr.ht][here]] ([[https://lists.sr.ht/~lr0/public-inbox][url]])
66 |
--------------------------------------------------------------------------------
/filter/filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "context"
5 | "regexp"
6 | "time"
7 |
8 | "github.com/dlclark/regexp2"
9 | "github.com/mmcdole/gofeed"
10 | "github.com/pkg/errors"
11 | )
12 |
13 | type FilterPrams struct {
14 | URL,
15 | AuthorRegex,
16 | ContentRegex,
17 | TitleRegex,
18 | LinkRegex,
19 | DescriptionRegex string
20 | DotNet bool // whether to use .NET Regex Engineg
21 | }
22 |
23 | // var tmpf FilterPrams = FilterPrams{TitleRegex: `\bAnalysis\b`}
24 | // FilterFeed("https://hnrss.org/newest", &tmpf)
25 |
26 | // Filter `url` based on `prams` if `prams` is nil (or all filters are empty)
27 | // return the original feeds
28 | func FilterFeeds(prams *FilterPrams) (*gofeed.Feed, error) {
29 | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
30 | defer cancel()
31 |
32 | fp := gofeed.NewParser()
33 | feeds, err := fp.ParseURLWithContext(prams.URL, ctx)
34 | if err != nil {
35 | return nil, errors.Wrapf(err, "couldn't read feeds %s ", prams.URL)
36 | }
37 |
38 | if prams == nil ||
39 | len(prams.AuthorRegex)+len(prams.ContentRegex)+
40 | len(prams.TitleRegex)+len(prams.DescriptionRegex)+len(prams.LinkRegex) == 0 {
41 | return feeds, nil
42 | }
43 | if prams.DotNet {
44 | return withDotNETRegex(feeds, prams)
45 | }
46 | return withRE2(feeds, prams)
47 | }
48 |
49 | // Implements filtering for Google's RE2 standards
50 | func withRE2(feeds *gofeed.Feed, prams *FilterPrams) (*gofeed.Feed, error) {
51 | var authorF, contentF, titleF, descriptionF, linkF *regexp.Regexp
52 |
53 | // validate filters
54 | authorF, err := validateRE2(prams.AuthorRegex)
55 | if err != nil {
56 | return nil, err
57 | }
58 |
59 | contentF, err = validateRE2(prams.ContentRegex)
60 | if err != nil {
61 | return nil, err
62 | }
63 |
64 | titleF, err = validateRE2(prams.TitleRegex)
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | descriptionF, err = validateRE2(prams.DescriptionRegex)
70 | if err != nil {
71 | return nil, err
72 | }
73 |
74 | linkF, err = validateRE2(prams.LinkRegex)
75 | if err != nil {
76 | return nil, err
77 | }
78 |
79 | // I considered using the new Slices.Delete function instead of building new
80 | // a new feeds; however "Delete is O(len(s)-j)" (from current go docs), I
81 | // think we can save some overhead by doing it by hand.
82 | fitlerd := []*gofeed.Item{}
83 | for _, i := range feeds.Items {
84 | // search authors
85 | if authorF != nil {
86 | match := false
87 | for _, a := range i.Authors {
88 | if authorF.MatchString(a.Name) {
89 | fitlerd = append(fitlerd, i)
90 | match = true
91 | break
92 | }
93 | }
94 | if match {
95 | continue
96 | }
97 | }
98 | // search content
99 | if contentF != nil && contentF.MatchString(i.Content) {
100 | fitlerd = append(fitlerd, i)
101 | continue
102 | }
103 | // search title
104 | if titleF != nil && titleF.MatchString(i.Title) {
105 | fitlerd = append(fitlerd, i)
106 | continue
107 | }
108 | // search link
109 | if linkF != nil && linkF.MatchString(i.Link) {
110 | fitlerd = append(fitlerd, i)
111 | continue
112 | }
113 | // search description
114 | if descriptionF != nil && descriptionF.MatchString(i.Description) {
115 | fitlerd = append(fitlerd, i)
116 | }
117 | }
118 | feeds.Items = fitlerd
119 | return feeds, nil
120 | }
121 |
122 | // Implements filtering for Microsoft's .NET regex engine
123 | // TODO refactor
124 | func withDotNETRegex(feeds *gofeed.Feed, prams *FilterPrams) (*gofeed.Feed, error) {
125 | var authorF, contentF, titleF, descriptionF, linkF *regexp2.Regexp
126 |
127 | // validate filters
128 | authorF, err := validateNET(prams.AuthorRegex)
129 | if err != nil {
130 | return nil, err
131 | }
132 |
133 | contentF, err = validateNET(prams.ContentRegex)
134 | if err != nil {
135 | return nil, err
136 | }
137 |
138 | titleF, err = validateNET(prams.TitleRegex)
139 | if err != nil {
140 | return nil, err
141 | }
142 |
143 | descriptionF, err = validateNET(prams.DescriptionRegex)
144 | if err != nil {
145 | return nil, err
146 | }
147 |
148 | linkF, err = validateNET(prams.LinkRegex)
149 | if err != nil {
150 | return nil, err
151 | }
152 |
153 | // I considered using the new Slices.Delete function instead of building new
154 | // a new feeds; however "Delete is O(len(s)-j)" (from current go docs), I
155 | // think we can save some overhead by doing it by hand.
156 | fitlerd := []*gofeed.Item{}
157 | for _, i := range feeds.Items {
158 | // search authors
159 | if authorF != nil {
160 | match := false
161 | for _, a := range i.Authors {
162 | p, err := authorF.MatchString(a.Name)
163 | if err != nil {
164 | return nil, errors.Wrapf(err, "timeout on expression %s",
165 | prams.AuthorRegex)
166 | }
167 | if p {
168 | fitlerd = append(fitlerd, i)
169 | match = true
170 | break
171 | }
172 | }
173 | if match {
174 | continue
175 | }
176 | }
177 | // search content
178 | if contentF != nil {
179 | p, err := contentF.MatchString(i.Content)
180 | if err != nil {
181 | return nil, errors.Wrapf(err, "timeout on expression %s",
182 | prams.ContentRegex)
183 | }
184 | if p {
185 | fitlerd = append(fitlerd, i)
186 | continue
187 | }
188 | }
189 |
190 | // search title
191 | if linkF != nil {
192 | p, err := linkF.MatchString(i.Link)
193 | if err != nil {
194 | return nil, errors.Wrapf(err, "timeout on expression %s",
195 | prams.LinkRegex)
196 | }
197 | if p {
198 | fitlerd = append(fitlerd, i)
199 | continue
200 | }
201 | }
202 |
203 | // search title
204 | if titleF != nil {
205 | p, err := titleF.MatchString(i.Title)
206 | if err != nil {
207 | return nil, errors.Wrapf(err, "timeout on expression %s",
208 | prams.TitleRegex)
209 | }
210 | if p {
211 | fitlerd = append(fitlerd, i)
212 | continue
213 | }
214 | }
215 | // search description
216 | if descriptionF != nil {
217 | p, err := descriptionF.MatchString(i.Description)
218 | if err != nil {
219 | return nil, errors.Wrapf(err, "timeout on expression %s",
220 | prams.DescriptionRegex)
221 | }
222 | if p {
223 | fitlerd = append(fitlerd, i)
224 | }
225 | }
226 | }
227 | feeds.Items = fitlerd
228 | return feeds, nil
229 | }
230 |
231 | func validateRE2(reg string) (*regexp.Regexp, error) {
232 | if len(reg) == 0 {
233 | return nil, nil
234 | }
235 | r, err := regexp.Compile(reg)
236 | if err != nil {
237 | return nil, errors.Wrapf(err, "malformatted regex: %s", r)
238 | }
239 | return r, nil
240 | }
241 |
242 | func validateNET(reg string) (*regexp2.Regexp, error) {
243 | if len(reg) == 0 {
244 | return nil, nil
245 | }
246 | r, err := regexp2.Compile(reg, regexp2.None)
247 | if err != nil {
248 | return nil, errors.Wrapf(err, "malformatted regex: %s", r)
249 | }
250 | return r, nil
251 | }
252 |
253 | func init() {
254 | regexp2.SetTimeoutCheckPeriod(time.Second * 3)
255 | }
256 |
--------------------------------------------------------------------------------
/filter/generate.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | generator "github.com/gorilla/feeds"
8 | "github.com/mmcdole/gofeed"
9 | )
10 |
11 | // Geerate a feed string of type t (a string should be json, rss, or atom). If t
12 | // is malformatted or invalid, it returns XML rss feeds
13 | func GenerateFeeds(feeds *gofeed.Feed, t string) (string, error) {
14 |
15 | // TODO unlike mmcdole/gofeed, does not support multiple authors. I think it
16 | // has other limitation as well, I will consider writing a better XML
17 | // generator. Or even better, considering not changing the original feeds format
18 | if feeds == nil {
19 | return "", fmt.Errorf(" no feeds provided")
20 | }
21 |
22 | author := &generator.Author{}
23 | if len(feeds.Authors) > 0 {
24 | author.Name = feeds.Authors[0].Name
25 | author.Email = feeds.Authors[0].Email
26 | }
27 | var created time.Time
28 | if feeds.PublishedParsed != nil {
29 | created = *feeds.PublishedParsed
30 | }
31 | feed := &generator.Feed{
32 | Title: feeds.Title,
33 | Link: &generator.Link{Href: feeds.Link},
34 | Description: feeds.Description,
35 | Author: author,
36 | Created: created,
37 | }
38 | gfeeds := []*generator.Item{}
39 | for _, i := range feeds.Items {
40 | if len(feeds.Authors) > 0 {
41 | author.Name = feeds.Authors[0].Name
42 | author.Email = feeds.Authors[0].Email
43 | } else {
44 | author = nil
45 | }
46 | gfeeds = append(gfeeds, &generator.Item{
47 | Title: i.Title,
48 | Link: &generator.Link{Href: i.Link},
49 | Description: i.Description,
50 | Author: author,
51 | Content: i.Content,
52 | Created: *i.PublishedParsed})
53 | }
54 | feed.Items = gfeeds
55 | if t == "json" {
56 | return feed.ToJSON()
57 | }
58 | if t == "atom" {
59 | return feed.ToAtom()
60 | }
61 | return feed.ToRss()
62 | }
63 |
--------------------------------------------------------------------------------
/fly.toml:
--------------------------------------------------------------------------------
1 | # fly.toml app configuration file generated for rssc on 2023-08-21T19:45:05+03:00
2 | #
3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4 | #
5 |
6 | app = "rssc"
7 | primary_region = "ams"
8 |
9 | [build]
10 |
11 | [http_service]
12 | internal_port = 8080
13 | force_https = true
14 | auto_stop_machines = true
15 | auto_start_machines = true
16 | min_machines_running = 0
17 | processes = ["app"]
18 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/larrasket/rssc
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/dlclark/regexp2 v1.10.0
7 | github.com/gorilla/feeds v1.1.1
8 | github.com/mmcdole/gofeed v1.2.1
9 | github.com/patrickmn/go-cache v2.1.0+incompatible
10 | github.com/pkg/errors v0.9.1
11 | )
12 |
13 | require (
14 | github.com/PuerkitoBio/goquery v1.8.0 // indirect
15 | github.com/andybalholm/cascadia v1.3.1 // indirect
16 | github.com/json-iterator/go v1.1.12 // indirect
17 | github.com/kr/pretty v0.3.1 // indirect
18 | github.com/mmcdole/goxpp v1.1.0 // indirect
19 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
20 | github.com/modern-go/reflect2 v1.0.2 // indirect
21 | golang.org/x/net v0.17.0 // indirect
22 | golang.org/x/text v0.13.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
2 | github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
3 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
4 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
10 | github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
11 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
12 | github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
13 | github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
14 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
15 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
16 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
17 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
18 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
19 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
20 | github.com/mmcdole/gofeed v1.2.1 h1:tPbFN+mfOLcM1kDF1x2c/N68ChbdBatkppdzf/vDe1s=
21 | github.com/mmcdole/gofeed v1.2.1/go.mod h1:2wVInNpgmC85q16QTTuwbuKxtKkHLCDDtf0dCmnrNr4=
22 | github.com/mmcdole/goxpp v1.1.0 h1:WwslZNF7KNAXTFuzRtn/OKZxFLJAAyOA9w82mDz2ZGI=
23 | github.com/mmcdole/goxpp v1.1.0/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
24 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
27 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
28 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
29 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
30 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
31 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
32 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
33 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
36 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
37 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
38 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
39 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
40 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
41 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
42 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
43 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
44 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
45 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
46 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
47 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
48 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
49 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
51 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
52 |
--------------------------------------------------------------------------------
/rssc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "os"
8 | "time"
9 |
10 | _ "embed"
11 |
12 | "github.com/larrasket/rssc/filter"
13 | "github.com/patrickmn/go-cache"
14 | )
15 |
16 | //go:embed README.html
17 | var README string
18 |
19 | func main() {
20 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
21 | w.Write([]byte(README))
22 | })
23 |
24 | c := cache.New(5*time.Second, 20*time.Second)
25 | http.HandleFunc("/rss", func(w http.ResponseWriter, r *http.Request) {
26 | q := r.URL.Query()
27 |
28 | if req, found := c.Get(q.Encode()); found {
29 | res := req.(string)
30 | w.Write([]byte(res))
31 | return
32 | }
33 |
34 | prams := filter.FilterPrams{AuthorRegex: q.Get("authorf"),
35 | ContentRegex: q.Get("contentf"),
36 | TitleRegex: q.Get("titlef"),
37 | DescriptionRegex: q.Get("descriptionf"),
38 | LinkRegex: q.Get("linkf"),
39 | URL: q.Get("src"),
40 | DotNet: func(b string) bool {
41 | if b == "1" {
42 | return true
43 | }
44 | return false
45 | }(q.Get("net")),
46 | }
47 |
48 | entries, err := filter.FilterFeeds(&prams)
49 | if err != nil {
50 | w.Write([]byte(err.Error()))
51 | }
52 |
53 | ftype := q.Get("t")
54 | feeds, err := filter.GenerateFeeds(entries, ftype)
55 | if err != nil {
56 | w.Write([]byte(err.Error()))
57 | }
58 |
59 | if ftype == "json" {
60 | w.Header().Set("Content-Type", "application/json")
61 | } else {
62 | w.Header().Set("Content-Type", "application/xml")
63 | }
64 |
65 | c.Set(q.Encode(), feeds, cache.DefaultExpiration)
66 | w.Write([]byte(feeds))
67 | })
68 |
69 | port, ok := os.LookupEnv("PORT")
70 | if ok == false {
71 | port = "8080"
72 | }
73 |
74 | fmt.Printf("Listening on :%s...\n", port)
75 | log.Fatal(http.ListenAndServe(":"+port, nil))
76 |
77 | }
78 |
--------------------------------------------------------------------------------