+7
-1
README.md
+7
-1
README.md
···
1
1
# Herald ๐
2
2
3
-

3
+

4
4
5
5
This was inspired by the sunsetting of [pico.sh/feeds](https://blog.pico.sh/ann-033-moving-rss-to-email-pico-plus) being available outside of `pico+`. It is a totally understandable move from them as their email costs were skyrocketing and they needed to pay for it somehow. This was created to allow me to still get my rss feeds delivered to me each day by email which I have grown quite accustomed to. The config is completely compatible with the `pico.sh` format as of `2026-01-09` and should stay fairly stable. It is also configured over ssh with the slight addition that you can view your feeds on a website as well as I found myself wanting to hot load my feeds into my website :)
6
6
···
136
136
- `HERALD_SMTP_USER`
137
137
- `HERALD_SMTP_PASS`
138
138
- `HERALD_SMTP_FROM`
139
+
140
+
## Screenshots
141
+
142
+
here is an example of what an email digest looks like:
143
+
144
+

139
145
140
146
<p align="center">
141
147
<img src="https://raw.githubusercontent.com/taciturnaxolotl/carriage/main/.github/images/line-break.svg" />
+40
crush.json
+40
crush.json
···
1
+
{
2
+
"$schema": "https://charm.land/crush.json",
3
+
"lsp": {
4
+
"gopls": {
5
+
"options": {
6
+
"gofumpt": true,
7
+
"codelenses": {
8
+
"gc_details": true,
9
+
"generate": true,
10
+
"run_govulncheck": true,
11
+
"test": true,
12
+
"tidy": true,
13
+
"upgrade_dependency": true
14
+
},
15
+
"hints": {
16
+
"assignVariableTypes": true,
17
+
"compositeLiteralFields": true,
18
+
"compositeLiteralTypes": true,
19
+
"constantValues": true,
20
+
"functionTypeParameters": true,
21
+
"parameterNames": true,
22
+
"rangeVariableTypes": true
23
+
},
24
+
"analyses": {
25
+
"nilness": true,
26
+
"unusedparams": true,
27
+
"unusedvariable": true,
28
+
"unusedwrite": true,
29
+
"useany": true
30
+
},
31
+
"staticcheck": true,
32
+
"directoryFilters": [
33
+
"-.git",
34
+
"-node_modules"
35
+
],
36
+
"semanticTokens": true
37
+
}
38
+
}
39
+
}
40
+
}
+100
emoji_u1f38f.svg
+100
emoji_u1f38f.svg
···
1
+
<?xml version="1.0" encoding="utf-8"?>
2
+
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
5
+
<path style="fill:#FBC219;" d="M7.2,20.67l6.85-0.61l12.94,100.19c0,0-0.45,2.79-3.05,3.05c-3.05,0.3-3.81-2.59-3.81-2.59L7.2,20.67
6
+
z"/>
7
+
<path style="fill:#E07F16;" d="M2.67,17.47c0.63,5.16,5.25,6.72,8.34,6.51c3.43-0.23,7.88-2.88,7.31-8.45
8
+
c-0.67-6.54-6.4-7.77-9.6-6.97C5.07,9.47,2.1,12.79,2.67,17.47z"/>
9
+
<path style="fill:#FFC020;" d="M11.35,10.84C8.8,9.02,5.64,11.3,4.73,13.59S4.5,17.7,5.75,18.73s4,0.69,5.6-1.26
10
+
C12.85,15.65,13.75,12.56,11.35,10.84z"/>
11
+
<path style="fill:#016BA2;" d="M30.43,31.98c0,0,4.11-6.63,12.22-12s17.02-9.25,32.79-9.03c10.74,0.16,17.02,0.96,23.42,1.03
12
+
c10.62,0.11,17.2-3.89,18.85-1.26c2.28,3.66-6.34,14-6.17,17.02c0.23,4,11.54,2.74,11.65,6.97c0.08,2.86-6.28,10.05-15.99,12.45
13
+
S85.5,49,77.84,50.14c-7.65,1.14-21.14,5.5-25.53,6.9C45.9,59.09,30.43,31.98,30.43,31.98z"/>
14
+
<path style="fill:#DDE2E1;" d="M26,37.96l5.03-6.95c0,0,1.36,1,3.44,1.63c2.08,0.63,11.7,2.68,16.3,9.42
15
+
C55.85,49.49,52.82,57,52.82,57s-4.15,1.97-11.5,3.04c-4.76,0.69-7.79-0.17-7.79-0.17s-5.85-10.19-5.94-10.47S26,37.96,26,37.96z"/>
16
+
<path style="fill:#016BA2;" d="M42.56,39.08c-1.01-0.41-2.66,0.64-2.79,3.51c-0.24,5.17,5.35,6.42,5.35,6.42s0.66,2.8,2.56,3.98
17
+
c3.45,2.14,4.7,0.12,4.46-0.71c-0.24-0.83-1.43-0.77-2.5-2.2c-0.74-0.99-1.13-3.09-1.43-3.51s-2.5-0.18-4.22-3.09
18
+
C42.77,41.42,43.15,39.32,42.56,39.08z"/>
19
+
<path style="fill:#016BA2;" d="M27.22,37c-2.5,0.71-3.15,6.64-1.01,13.38c1.66,5.23,5.77,10.58,9.39,9.39
20
+
c3.15-1.03,4.04-6.66,1.19-13.85C33.94,38.73,30.29,36.12,27.22,37z"/>
21
+
<path style="fill:#DDE2E1;" d="M28.65,39.86c-1.04,0.26-0.59,6.78,0.71,9.87s3.92,7.37,5.41,6.72c0.76-0.34,0.65-5.41-0.71-9.33
22
+
C32.69,43.19,29.6,39.62,28.65,39.86z"/>
23
+
<path style="fill:#FFFFFF;" d="M53.64,25.39c-3.66,2.11-4.29,7.27-2.18,11.06c1.81,3.26,5.36,4.85,8.86,3.5
24
+
c3.37-1.3,4.79-6.38,2.89-10.63C61.4,25.28,56.77,23.59,53.64,25.39z"/>
25
+
<path style="fill:#2D2D2E;" d="M55.16,28.62c-2.21,1.31-2.27,4.03-1.14,6.03c1.24,2.19,3.99,2.19,5.46,1.28
26
+
c1.73-1.06,2.12-3.31,1.24-5.32C59.67,28.24,57.01,27.53,55.16,28.62z"/>
27
+
<path style="fill:#F26D31;" d="M37.9,75.09c0,0,4.35-6.95,14.41-10.58c15.3-5.52,29.65-4.43,40.23-4.67
28
+
c18.46-0.41,21.88-6.64,24.68-4.15c2.36,2.09-4.46,11.92-3.94,15.76c0.29,2.15,1.24,4.04,4.15,4.87s6.4,1.25,6.74,2.8
29
+
c0.41,1.87-5.08,7.88-15.55,11.92c-8.61,3.33-18.66,3.63-27.27,4.98c-7.28,1.14-15.1,3.63-19.39,4.98
30
+
c-5.6,1.76-10.26,2.07-10.26,2.07s-9.54-24.68-9.85-24.99C41.53,77.79,37.9,75.09,37.9,75.09z"/>
31
+
<path style="fill:#DDE2E1;" d="M38.1,74.51c0,0,6.12-0.37,11.59,3.55c5.08,3.64,10.91,10.83,10.66,17.51
32
+
c-0.14,3.81-2.4,6.52-2.4,6.52s-3.67,1.52-9.45,1.18c-7.18-0.42-9.22-3.05-9.22-3.05l-5.75-15.06c0,0,0.51-4.99,1.78-6.94
33
+
C36.58,76.29,38.1,74.51,38.1,74.51z"/>
34
+
<path style="fill:#F26D31;" d="M32.98,91.27c1.35,5.56,5.57,10.78,8.95,9.99c3.12-0.73,4.21-5.49,2.39-12.7
35
+
c-1.77-7.03-5-11.29-9.02-10.33C31.53,79.15,31.78,86.33,32.98,91.27z"/>
36
+
<path style="fill:#DDE2E1;" d="M36.16,81.49c-1.41,0.42-1.1,4.77-0.1,8.9c0.73,3.02,2.55,7.86,4.22,7.44
37
+
c1.67-0.42,1.09-5.67,0.31-8.69C39.8,86.12,37.89,80.97,36.16,81.49z"/>
38
+
<path style="fill:#F26D31;" d="M48.7,81.95c-2.13,0-1.62,4.38-0.26,6.56c1.82,2.91,4.06,3.54,4.06,3.54s0.49,2.79,2.19,4.68
39
+
c0.88,0.99,2.21,1.71,2.76,1.35c0.6-0.39-0.07-2.12-0.57-4.11c-0.68-2.65-0.78-4.27-0.78-4.27s-2.55-1.51-3.85-3.33
40
+
S50.79,81.95,48.7,81.95z"/>
41
+
<path style="fill:#F8FFFD;" d="M59.32,72.22c-3.5,2.13-3.64,7.13-0.94,11.09c2.71,3.96,7.08,3.8,9.42,2.45
42
+
c2.34-1.35,3.85-6.09,1.2-10.67C66.6,70.95,61.97,70.61,59.32,72.22z"/>
43
+
<path style="fill:#2D2D2E;" d="M61.61,74.77c-1.87,0.83-2.29,4.32-0.68,6.45c1.61,2.13,3.8,2.18,5,1.25
44
+
c1.87-1.46,1.57-4.11,0.68-5.72C65.56,74.88,63.31,74.02,61.61,74.77z"/>
45
+
<g>
46
+
<path style="fill:#B6CED5;" d="M13.96,56.39c-2.52,0-3.81-0.94-3.97-1.05c-0.66-0.51-0.78-1.45-0.27-2.1
47
+
c0.51-0.66,1.45-0.77,2.1-0.27l0,0c0,0,0.85,0.55,2.73,0.4c2.18-0.17,3.55-1.04,3.56-1.05l0.27-0.14l12.74-5.01
48
+
c0.77-0.3,1.64,0.08,1.95,0.85c0.3,0.77-0.08,1.64-0.85,1.95l-12.61,4.96c-0.53,0.32-2.26,1.24-4.83,1.44
49
+
C14.5,56.38,14.22,56.39,13.96,56.39z"/>
50
+
</g>
51
+
<g>
52
+
<path style="fill:#B6CED5;" d="M20.29,98.16c-2.62,0-4.37-0.89-4.56-0.99c-0.73-0.39-1.01-1.29-0.63-2.03
53
+
c0.39-0.73,1.29-1.01,2.03-0.63c0,0,1.28,0.64,3.16,0.64c1.62,0,2.87-1.4,2.88-1.42l0.27-0.31l0.4-0.14l14-4.63
54
+
c0.79-0.26,1.64,0.17,1.9,0.95c0.26,0.79-0.17,1.63-0.95,1.89l-13.65,4.51C24.44,96.71,22.67,98.16,20.29,98.16z"/>
55
+
</g>
56
+
<path style="fill:#39BDE3;" d="M73.1,35.27c-1.38,0.83,0.52,2.61-0.06,8.01c-0.5,4.7-4.17,8.84-4.17,8.84s2.4-0.67,3.72-0.94
57
+
c1.33-0.28,3.48-0.72,3.48-0.72s1.22-2.54,1.27-6.91C77.41,39.35,75.23,33.99,73.1,35.27z"/>
58
+
<path style="fill:#39BDE3;" d="M75.37,21.18c-0.17,0.31,0.44,1.88,0.83,3.43c0.39,1.55,0.64,3.68,1.11,3.87
59
+
c0.66,0.28,3.91-1.57,2.93-5.08C79.18,19.63,75.7,20.57,75.37,21.18z"/>
60
+
<path style="fill:#39BDE3;" d="M83.77,25.49c-0.77,0.88,0.33,2.04,0.72,3.59c0.39,1.55,0.48,3.18,0.94,3.37
61
+
c0.66,0.28,3.87-1.33,3.26-4.48C87.95,24.15,84.22,24.97,83.77,25.49z"/>
62
+
<path style="fill:#39BDE3;" d="M81.32,15.9c-0.48,0.97,0.52,2.02,0.9,4.01c0.3,1.57,0.53,3.56,0.99,3.76
63
+
c0.66,0.28,4.19-1.95,3.2-5.47C85.37,14.44,81.62,15.28,81.32,15.9z"/>
64
+
<path style="fill:#39BDE3;" d="M90.93,20.21c-0.17,0.31,0.44,1.88,0.83,3.43c0.39,1.55,0.64,3.68,1.11,3.87
65
+
c0.66,0.28,3.91-1.57,2.93-5.08C94.74,18.67,91.26,19.61,90.93,20.21z"/>
66
+
<path style="fill:#39BDE3;" d="M96.18,15.57c-0.15,0.32,0.52,2.51,0.9,4.06c0.39,1.55,0.75,3.29,1.22,3.48
67
+
c0.66,0.28,3.87-1.71,3.04-5.53C100.51,13.78,96.64,14.61,96.18,15.57z"/>
68
+
<path style="fill:#39BDE3;" d="M99.05,24.6c-0.19,0.33,0.64,2.42,1.07,4.04c0.43,1.63,0.59,3.38,1.1,3.62
69
+
c1,0.47,4.52-1.69,3.28-5.34C103.38,23.61,99.42,23.96,99.05,24.6z"/>
70
+
<path style="fill:#39BDE3;" d="M93,29.58c-0.17,0.34,0.66,1.84,1.16,3.7c0.45,1.68,0.67,4.01,1.13,4.22
71
+
c0.66,0.31,4.08-1.36,3.13-5.21C97.39,28.17,93.33,28.92,93,29.58z"/>
72
+
<path style="fill:#39BDE3;" d="M77.97,30.63c-0.17,0.31,0.39,2.21,0.77,3.76c0.39,1.55,0.62,3.32,1.09,3.51
73
+
c0.66,0.28,3.82-1.54,2.93-5.08C82,29.8,78.3,30.02,77.97,30.63z"/>
74
+
<path style="fill:#39BDE3;" d="M86.18,35.46c-0.48,0.86,0.41,1.91,0.79,3.45c0.39,1.55,0.69,3.62,1.14,3.84
75
+
c0.96,0.47,3.91-1.66,3.28-4.83C90.64,34.1,86.52,34.86,86.18,35.46z"/>
76
+
<path style="fill:#39BDE3;" d="M101.24,35.08c-0.17,0.3,0.42,2.19,0.93,3.67s1.08,3.17,1.56,3.32c0.68,0.22,4.11-2.36,2.92-5.75
77
+
C105.59,33.34,101.67,34.33,101.24,35.08z"/>
78
+
<path style="fill:#FEC516;" d="M80.7,77.14c-0.16,0.32,0.5,1.88,0.95,3.51c0.42,1.55,0.91,3.36,1.38,3.54
79
+
c0.67,0.26,3.87-1.67,2.81-5.18C84.7,75.26,81.02,76.52,80.7,77.14z"/>
80
+
<path style="fill:#FEC516;" d="M86.27,71.68c-0.17,0.31,0.79,2.03,1.31,3.45c0.55,1.5,0.64,3.68,1.11,3.87
81
+
c0.66,0.28,3.43-1.35,2.59-4.9C90.43,70.49,86.6,71.07,86.27,71.68z"/>
82
+
<path style="fill:#FEC516;" d="M78.11,67.53c-0.18,0.34,0.54,2,1,3.64c0.47,1.64,0.72,3.07,1.15,3.42c1.13,0.9,4.38-0.71,3.36-4.71
83
+
C82.59,65.82,78.46,66.87,78.11,67.53z"/>
84
+
<path style="fill:#FEC516;" d="M84.16,62.1c-0.66,0.47,0.32,1.9,0.6,3.47c0.28,1.57,0.3,3.4,0.77,3.58c1.35,0.51,4.27-0.3,3.55-4.45
85
+
C88.4,60.82,84.73,61.7,84.16,62.1z"/>
86
+
<path style="fill:#FEC516;" d="M92.23,66.89c-0.23,1.07,0.97,1.48,1.66,3.57c0.6,1.83,0.47,3.09,0.95,3.24
87
+
c1.46,0.45,4.65-2.1,3.47-5.12C96.91,64.97,92.38,66.17,92.23,66.89z"/>
88
+
<path style="fill:#FEC516;" d="M98.66,62.3c-0.17,0.31,0.33,1.38,1.02,3.26c0.55,1.5,0.64,3.68,1.11,3.87
89
+
c0.66,0.28,3.8-1.55,2.82-5.07C102.55,60.6,98.99,61.69,98.66,62.3z"/>
90
+
<path style="fill:#FEC516;" d="M101.52,71.6c-0.17,0.31,0.51,1.78,0.9,3.06c0.46,1.53,0.41,3.18,1.12,3.66
91
+
c1.07,0.73,4.17-1.48,3.04-4.96C105.54,70.13,101.86,70.99,101.52,71.6z"/>
92
+
<path style="fill:#FEC516;" d="M95.6,76.05c-0.17,0.31,0.38,1.91,0.64,3.5c0.28,1.77,0.27,3.34,0.72,3.55
93
+
c1.07,0.51,4.64-0.82,3.77-4.62C99.87,74.72,95.93,75.45,95.6,76.05z"/>
94
+
<path style="fill:#FEC516;" d="M103.86,80.66c-0.17,0.31,0.44,1.88,0.83,3.43c0.39,1.55,0.7,3,1.1,3.3c0.9,0.68,4.06-0.9,3.22-4.56
95
+
C108.13,79.02,104.19,80.05,103.86,80.66z"/>
96
+
<path style="fill:#FEC516;" d="M88.88,81.06c-0.48,0.65,0.6,2.13,0.79,3.72c0.23,1.86,0.28,3.27,0.73,3.49
97
+
c1.29,0.62,4.81-1.41,3.61-4.95C92.79,79.68,89.29,80.5,88.88,81.06z"/>
98
+
<path style="fill:#FEC516;" d="M72.8,97.85c0,0,3.47-3.86,3.39-6.75c-0.11-4.33-1.35-6.47,0.34-7.09c1.4-0.51,2.93,1.75,3.49,4.17
99
+
c1.35,5.82,0.06,8.1,0.06,8.1s-2.13,0.46-3.78,0.79C74.89,97.34,72.8,97.85,72.8,97.85z"/>
100
+
</svg>
+6
-6
scheduler/scheduler.go
+6
-6
scheduler/scheduler.go
···
179
179
}
180
180
}()
181
181
182
-
now := time.Now()
182
+
now := time.Now().UTC()
183
183
configs, err := s.store.GetDueConfigs(ctx, now)
184
184
if err != nil {
185
185
s.logger.Error("failed to get due configs", "err", err)
···
257
257
s.logger.Debug("RunNow: feed metadata updated")
258
258
259
259
s.logger.Debug("RunNow: calculating next run")
260
-
now := time.Now()
261
-
nextRun, err := gronx.NextTick(cfg.CronExpr, false)
260
+
now := time.Now().UTC()
261
+
nextRun, err := gronx.NextTick(cfg.CronExpr, true)
262
262
if err != nil {
263
263
return stats, fmt.Errorf("calculate next run: %w", err)
264
264
}
···
277
277
func (s *Scheduler) collectNewItems(ctx context.Context, results []*FetchResult) ([]email.FeedGroup, int, error) {
278
278
var feedGroups []email.FeedGroup
279
279
totalNew := 0
280
-
maxAge := time.Now().Add(-itemMaxAge)
280
+
maxAge := time.Now().UTC().Add(-itemMaxAge)
281
281
feedErrors := 0
282
282
283
283
for _, result := range results {
···
486
486
}
487
487
}
488
488
489
-
now := time.Now()
490
-
nextRun, err := gronx.NextTick(cfg.CronExpr, false)
489
+
now := time.Now().UTC()
490
+
nextRun, err := gronx.NextTick(cfg.CronExpr, true)
491
491
if err != nil {
492
492
return fmt.Errorf("calculate next run: %w", err)
493
493
}
+1
-1
ssh/commands.go
+1
-1
ssh/commands.go
+1
-1
ssh/scp.go
+1
-1
ssh/scp.go
+3
-3
store/configs.go
+3
-3
store/configs.go
···
49
49
InlineContent: inline,
50
50
RawText: rawText,
51
51
NextRun: sql.NullTime{Time: nextRun, Valid: true},
52
-
CreatedAt: time.Now(),
52
+
CreatedAt: time.Now().UTC(),
53
53
}, nil
54
54
}
55
55
···
78
78
InlineContent: inline,
79
79
RawText: rawText,
80
80
NextRun: sql.NullTime{Time: nextRun, Valid: true},
81
-
CreatedAt: time.Now(),
81
+
CreatedAt: time.Now().UTC(),
82
82
}, nil
83
83
}
84
84
···
219
219
return err
220
220
}
221
221
222
-
nextRun, err := gronx.NextTick(cfg.CronExpr, false)
222
+
nextRun, err := gronx.NextTick(cfg.CronExpr, true)
223
223
if err != nil {
224
224
return fmt.Errorf("calculate next run: %w", err)
225
225
}
+1
-1
store/users.go
+1
-1
store/users.go
+10
web/handlers.go
+10
web/handlers.go
···
60
60
_, _ = w.Write(css)
61
61
}
62
62
63
+
func (s *Server) handleFaviconSVG(w http.ResponseWriter, r *http.Request) {
64
+
svg, err := publicFS.ReadFile("public/favicon.svg")
65
+
if err != nil {
66
+
http.Error(w, "Not Found", http.StatusNotFound)
67
+
return
68
+
}
69
+
w.Header().Set("Content-Type", "image/svg+xml")
70
+
_, _ = w.Write(svg)
71
+
}
72
+
63
73
type userPageData struct {
64
74
Fingerprint string
65
75
ShortFingerprint string
+100
web/public/favicon.svg
+100
web/public/favicon.svg
···
1
+
<?xml version="1.0" encoding="utf-8"?>
2
+
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
5
+
<path style="fill:#FBC219;" d="M7.2,20.67l6.85-0.61l12.94,100.19c0,0-0.45,2.79-3.05,3.05c-3.05,0.3-3.81-2.59-3.81-2.59L7.2,20.67
6
+
z"/>
7
+
<path style="fill:#E07F16;" d="M2.67,17.47c0.63,5.16,5.25,6.72,8.34,6.51c3.43-0.23,7.88-2.88,7.31-8.45
8
+
c-0.67-6.54-6.4-7.77-9.6-6.97C5.07,9.47,2.1,12.79,2.67,17.47z"/>
9
+
<path style="fill:#FFC020;" d="M11.35,10.84C8.8,9.02,5.64,11.3,4.73,13.59S4.5,17.7,5.75,18.73s4,0.69,5.6-1.26
10
+
C12.85,15.65,13.75,12.56,11.35,10.84z"/>
11
+
<path style="fill:#016BA2;" d="M30.43,31.98c0,0,4.11-6.63,12.22-12s17.02-9.25,32.79-9.03c10.74,0.16,17.02,0.96,23.42,1.03
12
+
c10.62,0.11,17.2-3.89,18.85-1.26c2.28,3.66-6.34,14-6.17,17.02c0.23,4,11.54,2.74,11.65,6.97c0.08,2.86-6.28,10.05-15.99,12.45
13
+
S85.5,49,77.84,50.14c-7.65,1.14-21.14,5.5-25.53,6.9C45.9,59.09,30.43,31.98,30.43,31.98z"/>
14
+
<path style="fill:#DDE2E1;" d="M26,37.96l5.03-6.95c0,0,1.36,1,3.44,1.63c2.08,0.63,11.7,2.68,16.3,9.42
15
+
C55.85,49.49,52.82,57,52.82,57s-4.15,1.97-11.5,3.04c-4.76,0.69-7.79-0.17-7.79-0.17s-5.85-10.19-5.94-10.47S26,37.96,26,37.96z"/>
16
+
<path style="fill:#016BA2;" d="M42.56,39.08c-1.01-0.41-2.66,0.64-2.79,3.51c-0.24,5.17,5.35,6.42,5.35,6.42s0.66,2.8,2.56,3.98
17
+
c3.45,2.14,4.7,0.12,4.46-0.71c-0.24-0.83-1.43-0.77-2.5-2.2c-0.74-0.99-1.13-3.09-1.43-3.51s-2.5-0.18-4.22-3.09
18
+
C42.77,41.42,43.15,39.32,42.56,39.08z"/>
19
+
<path style="fill:#016BA2;" d="M27.22,37c-2.5,0.71-3.15,6.64-1.01,13.38c1.66,5.23,5.77,10.58,9.39,9.39
20
+
c3.15-1.03,4.04-6.66,1.19-13.85C33.94,38.73,30.29,36.12,27.22,37z"/>
21
+
<path style="fill:#DDE2E1;" d="M28.65,39.86c-1.04,0.26-0.59,6.78,0.71,9.87s3.92,7.37,5.41,6.72c0.76-0.34,0.65-5.41-0.71-9.33
22
+
C32.69,43.19,29.6,39.62,28.65,39.86z"/>
23
+
<path style="fill:#FFFFFF;" d="M53.64,25.39c-3.66,2.11-4.29,7.27-2.18,11.06c1.81,3.26,5.36,4.85,8.86,3.5
24
+
c3.37-1.3,4.79-6.38,2.89-10.63C61.4,25.28,56.77,23.59,53.64,25.39z"/>
25
+
<path style="fill:#2D2D2E;" d="M55.16,28.62c-2.21,1.31-2.27,4.03-1.14,6.03c1.24,2.19,3.99,2.19,5.46,1.28
26
+
c1.73-1.06,2.12-3.31,1.24-5.32C59.67,28.24,57.01,27.53,55.16,28.62z"/>
27
+
<path style="fill:#F26D31;" d="M37.9,75.09c0,0,4.35-6.95,14.41-10.58c15.3-5.52,29.65-4.43,40.23-4.67
28
+
c18.46-0.41,21.88-6.64,24.68-4.15c2.36,2.09-4.46,11.92-3.94,15.76c0.29,2.15,1.24,4.04,4.15,4.87s6.4,1.25,6.74,2.8
29
+
c0.41,1.87-5.08,7.88-15.55,11.92c-8.61,3.33-18.66,3.63-27.27,4.98c-7.28,1.14-15.1,3.63-19.39,4.98
30
+
c-5.6,1.76-10.26,2.07-10.26,2.07s-9.54-24.68-9.85-24.99C41.53,77.79,37.9,75.09,37.9,75.09z"/>
31
+
<path style="fill:#DDE2E1;" d="M38.1,74.51c0,0,6.12-0.37,11.59,3.55c5.08,3.64,10.91,10.83,10.66,17.51
32
+
c-0.14,3.81-2.4,6.52-2.4,6.52s-3.67,1.52-9.45,1.18c-7.18-0.42-9.22-3.05-9.22-3.05l-5.75-15.06c0,0,0.51-4.99,1.78-6.94
33
+
C36.58,76.29,38.1,74.51,38.1,74.51z"/>
34
+
<path style="fill:#F26D31;" d="M32.98,91.27c1.35,5.56,5.57,10.78,8.95,9.99c3.12-0.73,4.21-5.49,2.39-12.7
35
+
c-1.77-7.03-5-11.29-9.02-10.33C31.53,79.15,31.78,86.33,32.98,91.27z"/>
36
+
<path style="fill:#DDE2E1;" d="M36.16,81.49c-1.41,0.42-1.1,4.77-0.1,8.9c0.73,3.02,2.55,7.86,4.22,7.44
37
+
c1.67-0.42,1.09-5.67,0.31-8.69C39.8,86.12,37.89,80.97,36.16,81.49z"/>
38
+
<path style="fill:#F26D31;" d="M48.7,81.95c-2.13,0-1.62,4.38-0.26,6.56c1.82,2.91,4.06,3.54,4.06,3.54s0.49,2.79,2.19,4.68
39
+
c0.88,0.99,2.21,1.71,2.76,1.35c0.6-0.39-0.07-2.12-0.57-4.11c-0.68-2.65-0.78-4.27-0.78-4.27s-2.55-1.51-3.85-3.33
40
+
S50.79,81.95,48.7,81.95z"/>
41
+
<path style="fill:#F8FFFD;" d="M59.32,72.22c-3.5,2.13-3.64,7.13-0.94,11.09c2.71,3.96,7.08,3.8,9.42,2.45
42
+
c2.34-1.35,3.85-6.09,1.2-10.67C66.6,70.95,61.97,70.61,59.32,72.22z"/>
43
+
<path style="fill:#2D2D2E;" d="M61.61,74.77c-1.87,0.83-2.29,4.32-0.68,6.45c1.61,2.13,3.8,2.18,5,1.25
44
+
c1.87-1.46,1.57-4.11,0.68-5.72C65.56,74.88,63.31,74.02,61.61,74.77z"/>
45
+
<g>
46
+
<path style="fill:#B6CED5;" d="M13.96,56.39c-2.52,0-3.81-0.94-3.97-1.05c-0.66-0.51-0.78-1.45-0.27-2.1
47
+
c0.51-0.66,1.45-0.77,2.1-0.27l0,0c0,0,0.85,0.55,2.73,0.4c2.18-0.17,3.55-1.04,3.56-1.05l0.27-0.14l12.74-5.01
48
+
c0.77-0.3,1.64,0.08,1.95,0.85c0.3,0.77-0.08,1.64-0.85,1.95l-12.61,4.96c-0.53,0.32-2.26,1.24-4.83,1.44
49
+
C14.5,56.38,14.22,56.39,13.96,56.39z"/>
50
+
</g>
51
+
<g>
52
+
<path style="fill:#B6CED5;" d="M20.29,98.16c-2.62,0-4.37-0.89-4.56-0.99c-0.73-0.39-1.01-1.29-0.63-2.03
53
+
c0.39-0.73,1.29-1.01,2.03-0.63c0,0,1.28,0.64,3.16,0.64c1.62,0,2.87-1.4,2.88-1.42l0.27-0.31l0.4-0.14l14-4.63
54
+
c0.79-0.26,1.64,0.17,1.9,0.95c0.26,0.79-0.17,1.63-0.95,1.89l-13.65,4.51C24.44,96.71,22.67,98.16,20.29,98.16z"/>
55
+
</g>
56
+
<path style="fill:#39BDE3;" d="M73.1,35.27c-1.38,0.83,0.52,2.61-0.06,8.01c-0.5,4.7-4.17,8.84-4.17,8.84s2.4-0.67,3.72-0.94
57
+
c1.33-0.28,3.48-0.72,3.48-0.72s1.22-2.54,1.27-6.91C77.41,39.35,75.23,33.99,73.1,35.27z"/>
58
+
<path style="fill:#39BDE3;" d="M75.37,21.18c-0.17,0.31,0.44,1.88,0.83,3.43c0.39,1.55,0.64,3.68,1.11,3.87
59
+
c0.66,0.28,3.91-1.57,2.93-5.08C79.18,19.63,75.7,20.57,75.37,21.18z"/>
60
+
<path style="fill:#39BDE3;" d="M83.77,25.49c-0.77,0.88,0.33,2.04,0.72,3.59c0.39,1.55,0.48,3.18,0.94,3.37
61
+
c0.66,0.28,3.87-1.33,3.26-4.48C87.95,24.15,84.22,24.97,83.77,25.49z"/>
62
+
<path style="fill:#39BDE3;" d="M81.32,15.9c-0.48,0.97,0.52,2.02,0.9,4.01c0.3,1.57,0.53,3.56,0.99,3.76
63
+
c0.66,0.28,4.19-1.95,3.2-5.47C85.37,14.44,81.62,15.28,81.32,15.9z"/>
64
+
<path style="fill:#39BDE3;" d="M90.93,20.21c-0.17,0.31,0.44,1.88,0.83,3.43c0.39,1.55,0.64,3.68,1.11,3.87
65
+
c0.66,0.28,3.91-1.57,2.93-5.08C94.74,18.67,91.26,19.61,90.93,20.21z"/>
66
+
<path style="fill:#39BDE3;" d="M96.18,15.57c-0.15,0.32,0.52,2.51,0.9,4.06c0.39,1.55,0.75,3.29,1.22,3.48
67
+
c0.66,0.28,3.87-1.71,3.04-5.53C100.51,13.78,96.64,14.61,96.18,15.57z"/>
68
+
<path style="fill:#39BDE3;" d="M99.05,24.6c-0.19,0.33,0.64,2.42,1.07,4.04c0.43,1.63,0.59,3.38,1.1,3.62
69
+
c1,0.47,4.52-1.69,3.28-5.34C103.38,23.61,99.42,23.96,99.05,24.6z"/>
70
+
<path style="fill:#39BDE3;" d="M93,29.58c-0.17,0.34,0.66,1.84,1.16,3.7c0.45,1.68,0.67,4.01,1.13,4.22
71
+
c0.66,0.31,4.08-1.36,3.13-5.21C97.39,28.17,93.33,28.92,93,29.58z"/>
72
+
<path style="fill:#39BDE3;" d="M77.97,30.63c-0.17,0.31,0.39,2.21,0.77,3.76c0.39,1.55,0.62,3.32,1.09,3.51
73
+
c0.66,0.28,3.82-1.54,2.93-5.08C82,29.8,78.3,30.02,77.97,30.63z"/>
74
+
<path style="fill:#39BDE3;" d="M86.18,35.46c-0.48,0.86,0.41,1.91,0.79,3.45c0.39,1.55,0.69,3.62,1.14,3.84
75
+
c0.96,0.47,3.91-1.66,3.28-4.83C90.64,34.1,86.52,34.86,86.18,35.46z"/>
76
+
<path style="fill:#39BDE3;" d="M101.24,35.08c-0.17,0.3,0.42,2.19,0.93,3.67s1.08,3.17,1.56,3.32c0.68,0.22,4.11-2.36,2.92-5.75
77
+
C105.59,33.34,101.67,34.33,101.24,35.08z"/>
78
+
<path style="fill:#FEC516;" d="M80.7,77.14c-0.16,0.32,0.5,1.88,0.95,3.51c0.42,1.55,0.91,3.36,1.38,3.54
79
+
c0.67,0.26,3.87-1.67,2.81-5.18C84.7,75.26,81.02,76.52,80.7,77.14z"/>
80
+
<path style="fill:#FEC516;" d="M86.27,71.68c-0.17,0.31,0.79,2.03,1.31,3.45c0.55,1.5,0.64,3.68,1.11,3.87
81
+
c0.66,0.28,3.43-1.35,2.59-4.9C90.43,70.49,86.6,71.07,86.27,71.68z"/>
82
+
<path style="fill:#FEC516;" d="M78.11,67.53c-0.18,0.34,0.54,2,1,3.64c0.47,1.64,0.72,3.07,1.15,3.42c1.13,0.9,4.38-0.71,3.36-4.71
83
+
C82.59,65.82,78.46,66.87,78.11,67.53z"/>
84
+
<path style="fill:#FEC516;" d="M84.16,62.1c-0.66,0.47,0.32,1.9,0.6,3.47c0.28,1.57,0.3,3.4,0.77,3.58c1.35,0.51,4.27-0.3,3.55-4.45
85
+
C88.4,60.82,84.73,61.7,84.16,62.1z"/>
86
+
<path style="fill:#FEC516;" d="M92.23,66.89c-0.23,1.07,0.97,1.48,1.66,3.57c0.6,1.83,0.47,3.09,0.95,3.24
87
+
c1.46,0.45,4.65-2.1,3.47-5.12C96.91,64.97,92.38,66.17,92.23,66.89z"/>
88
+
<path style="fill:#FEC516;" d="M98.66,62.3c-0.17,0.31,0.33,1.38,1.02,3.26c0.55,1.5,0.64,3.68,1.11,3.87
89
+
c0.66,0.28,3.8-1.55,2.82-5.07C102.55,60.6,98.99,61.69,98.66,62.3z"/>
90
+
<path style="fill:#FEC516;" d="M101.52,71.6c-0.17,0.31,0.51,1.78,0.9,3.06c0.46,1.53,0.41,3.18,1.12,3.66
91
+
c1.07,0.73,4.17-1.48,3.04-4.96C105.54,70.13,101.86,70.99,101.52,71.6z"/>
92
+
<path style="fill:#FEC516;" d="M95.6,76.05c-0.17,0.31,0.38,1.91,0.64,3.5c0.28,1.77,0.27,3.34,0.72,3.55
93
+
c1.07,0.51,4.64-0.82,3.77-4.62C99.87,74.72,95.93,75.45,95.6,76.05z"/>
94
+
<path style="fill:#FEC516;" d="M103.86,80.66c-0.17,0.31,0.44,1.88,0.83,3.43c0.39,1.55,0.7,3,1.1,3.3c0.9,0.68,4.06-0.9,3.22-4.56
95
+
C108.13,79.02,104.19,80.05,103.86,80.66z"/>
96
+
<path style="fill:#FEC516;" d="M88.88,81.06c-0.48,0.65,0.6,2.13,0.79,3.72c0.23,1.86,0.28,3.27,0.73,3.49
97
+
c1.29,0.62,4.81-1.41,3.61-4.95C92.79,79.68,89.29,80.5,88.88,81.06z"/>
98
+
<path style="fill:#FEC516;" d="M72.8,97.85c0,0,3.47-3.86,3.39-6.75c-0.11-4.33-1.35-6.47,0.34-7.09c1.4-0.51,2.93,1.75,3.49,4.17
99
+
c1.35,5.82,0.06,8.1,0.06,8.1s-2.13,0.46-3.78,0.79C74.89,97.34,72.8,97.85,72.8,97.85z"/>
100
+
</svg>
+4
web/server.go
+4
web/server.go
···
18
18
//go:embed templates/*
19
19
var templatesFS embed.FS
20
20
21
+
//go:embed public/*
22
+
var publicFS embed.FS
23
+
21
24
const (
22
25
// HTTP rate limiting
23
26
httpRequestsPerSecond = 10
···
56
59
57
60
mux.HandleFunc("/", s.routeHandler)
58
61
mux.HandleFunc("/style.css", s.handleStyleCSS)
62
+
mux.HandleFunc("/favicon.svg", s.handleFaviconSVG)
59
63
mux.HandleFunc("/health", s.handleHealth)
60
64
mux.HandleFunc("/metrics", s.handleMetrics)
61
65
+1
-1
web/templates/404.html
+1
-1
web/templates/404.html
···
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
6
<title>HERALD - {{if .Title}}{{.Title}}{{else}}404{{end}}</title>
7
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>๐</text></svg>">
7
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
8
<link rel="stylesheet" href="/style.css">
9
9
</head>
10
10
<body>
+37
-2
web/templates/index.html
+37
-2
web/templates/index.html
···
3
3
<head>
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
-
<title>HERALD</title>
7
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>๐</text></svg>">
6
+
<title>HERALD - RSS-to-Email via SSH</title>
7
+
<meta name="description" content="Herald delivers RSS feeds to your inbox via SSH. Upload feed configs, manage subscriptions, and receive updates by email.">
8
+
9
+
<!-- Open Graph -->
10
+
<meta property="og:type" content="website">
11
+
<meta property="og:title" content="HERALD - RSS-to-Email via SSH">
12
+
<meta property="og:description" content="Herald delivers RSS feeds to your inbox via SSH. Upload feed configs, manage subscriptions, and receive updates by email.">
13
+
<meta property="og:url" content="{{.Origin}}">
14
+
<meta property="og:image" content="https://l4.dunkirk.sh/i/AQ0z6BnQAwWu.webp">
15
+
<meta property="og:site_name" content="HERALD">
16
+
17
+
<!-- Twitter Card -->
18
+
<meta name="twitter:card" content="summary_large_image">
19
+
<meta name="twitter:title" content="HERALD - RSS-to-Email via SSH">
20
+
<meta name="twitter:description" content="Herald delivers RSS feeds to your inbox via SSH. Upload feed configs, manage subscriptions, and receive updates by email.">
21
+
<meta name="twitter:image" content="https://l4.dunkirk.sh/i/AQ0z6BnQAwWu.webp">
22
+
23
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
24
<link rel="stylesheet" href="/style.css">
9
25
</head>
10
26
<body>
···
13
29
<h2>NAME</h2>
14
30
<pre>
15
31
herald - RSS-to-Email via SSH
32
+
</pre>
33
+
34
+
<h2>FILE FORMAT</h2>
35
+
<pre>
36
+
Feed configs are plain text files with directives:
37
+
38
+
=: email you@example.com Recipient email address
39
+
=: cron 0 8 * * * Standard cron (5 fields)
40
+
=: digest true Combine items (default: true)
41
+
=: inline false Include content (default: false)
42
+
=> https://example.com/feed RSS/Atom feed URL
43
+
=> https://blog.com/rss "Blog" Feed with display name
44
+
45
+
Example feeds.txt:
46
+
=: email you@example.com
47
+
=: cron 0 8 * * *
48
+
=: digest true
49
+
=> https://dunkirk.sh/atom.xml
50
+
=> https://news.ycombinator.com/rss
16
51
</pre>
17
52
18
53
<h2>COMMANDS</h2>
+1
-1
web/templates/keepalive.html
+1
-1
web/templates/keepalive.html
···
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
6
<title>HERALD - Digest Active</title>
7
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>๐</text></svg>">
7
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
8
<link rel="stylesheet" href="/style.css">
9
9
</head>
10
10
<body>
+1
-1
web/templates/unsubscribe.html
+1
-1
web/templates/unsubscribe.html
···
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
6
<title>HERALD - Unsubscribe</title>
7
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>๐</text></svg>">
7
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
8
<link rel="stylesheet" href="/style.css">
9
9
</head>
10
10
<body>
+17
-1
web/templates/user.html
+17
-1
web/templates/user.html
···
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
6
<title>HERALD - {{.ShortFingerprint}}</title>
7
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>๐</text></svg>">
7
+
<meta name="description" content="Herald dashboard for user {{.ShortFingerprint}}. View feed configs and delivery status.">
8
+
9
+
<!-- Open Graph -->
10
+
<meta property="og:type" content="website">
11
+
<meta property="og:title" content="HERALD - {{.ShortFingerprint}}">
12
+
<meta property="og:description" content="Herald dashboard for user {{.ShortFingerprint}}. View feed configs and delivery status.">
13
+
<meta property="og:url" content="{{.Origin}}/{{.Fingerprint}}">
14
+
<meta property="og:image" content="https://l4.dunkirk.sh/i/AQ0z6BnQAwWu.webp">
15
+
<meta property="og:site_name" content="HERALD">
16
+
17
+
<!-- Twitter Card -->
18
+
<meta name="twitter:card" content="summary_large_image">
19
+
<meta name="twitter:title" content="HERALD - {{.ShortFingerprint}}">
20
+
<meta name="twitter:description" content="Herald dashboard for user {{.ShortFingerprint}}. View feed configs and delivery status.">
21
+
<meta name="twitter:image" content="https://l4.dunkirk.sh/i/AQ0z6BnQAwWu.webp">
22
+
23
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
24
<link rel="stylesheet" href="/style.css">
9
25
</head>
10
26
<body>