1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
package shared
import (
"encoding/json"
"github.com/tailscale/hujson"
"log"
"os"
"time"
)
const (
configVarName = "CONFIG" // If set, will load config.json from this path and not from devConfigPath
secretsVarName = "SECRETS" // If set, will load secrets.json from this path and not from devSecretsPath
devConfigPath = "../dev/config.dev.jsonc" // Path to config.json in development environment
devSecretsPath = "../dev/secrets.dev.jsonc" // Path to config.json in development environment
)
type Config struct {
Secrets Secrets `json:"-"`
LogFile string `json:"log_file"`
LogLevel string `json:"log_level"`
ServicePort uint `json:"service_port"`
Host string `json:"host"`
DbFile string `json:"db_file"`
BlockedFeedsFile string `json:"blocked_feeds_file"`
// RestrictRequests, when true, closes new-feed requests by default: only
// fediverse monikers (@user@host) listed in AllowedRequestersFile (one per
// line) may request feeds; everyone else is refused, and an empty or missing
// list denies everyone. When false (default) anyone may request feeds.
RestrictRequests bool `json:"restrict_requests"`
AllowedRequestersFile string `json:"allowed_requesters_file"`
ProfileDir string `json:"profile_dir"`
ProfileKeepDays int `json:"profile_keep_days"`
CachePageTemplates bool `json:"cache_page_templates"`
UpdateSchedule UpdateSchedule `json:"update_schedule"`
// MaxRetryDelayHours caps how long a feed is left alone after a failed
// check, whatever the backoff or the server's Retry-After say, unless its
// normal interval is longer. 0 means the default of 24.
MaxRetryDelayHours int `json:"max_retry_delay_hours"`
// BackdateGraceHours: an item never seen before is tooted only if it is
// dated after the newest item seen so far, or at most this many hours
// before it (for posts published with an earlier date, or a wrong time
// zone). nil means the default of 48.
BackdateGraceHours *int `json:"backdate_grace_hours"`
// MaxNewItemsPerFetch, if above 0, limits how many new items one fetch of
// a feed may toot: if a feed suddenly shows more, only the newest that
// many are tooted, and the rest are remembered as seen. 0 (default) is
// no limit.
MaxNewItemsPerFetch int `json:"max_new_items_per_fetch"`
PostsMinCountKept int `json:"posts_min_count_kept"`
PostsMinDaysKept int `json:"posts_min_days_kept"`
PurgeWaitSec int `json:"purge_wait_sec"`
FallbackProfilePic string `json:"fallback_profile_pic"`
Birb *UserInfo `json:"birb"`
}
// UpdateSchedule is how often a feed is checked, in hours, by how long ago it
// last had a new post: within a day, a week, four weeks, or longer.
type UpdateSchedule struct {
Day int `json:"day"`
Week int `json:"week"`
Weeks4 int `json:"weeks4"`
Older int `json:"older"`
}
// DefaultUpdateSchedule is upstream's documented schedule.
var DefaultUpdateSchedule = UpdateSchedule{Day: 1, Week: 3, Weeks4: 6, Older: 12}
// WithDefaults fills in every interval that is not set (or not positive) from
// DefaultUpdateSchedule. An interval of 0 would schedule the next check right
// away, so the feed would be checked as fast as the loop runs.
func (s UpdateSchedule) WithDefaults() UpdateSchedule {
def := DefaultUpdateSchedule
if s.Day <= 0 {
s.Day = def.Day
}
if s.Week <= 0 {
s.Week = def.Week
}
if s.Weeks4 <= 0 {
s.Weeks4 = def.Weeks4
}
if s.Older <= 0 {
s.Older = def.Older
}
return s
}
type UserInfo struct {
User string `json:"user"`
Published time.Time `json:"published"`
ManuallyApprovesFollows bool `json:"manually_approves_follows"`
ProfilePic string `json:"profile_pic"`
HeaderPic string `json:"header_pic"`
PubKey string `json:"pub_key"`
PrivKey string `json:"priv_key"`
}
type Secrets struct {
BirdPrivKeyPass string `json:"birb_privkey_passphrase"`
ApiKeys []string `json:"api_keys"`
MetricsAuth string `json:"metrics_auth"`
}
func LoadConfig() *Config {
// Where are our config and secrets files?
cfgPath := os.Getenv(configVarName)
if len(cfgPath) == 0 {
cfgPath = devConfigPath
}
secretsPath := os.Getenv(secretsVarName)
if len(secretsPath) == 0 {
secretsPath = devSecretsPath
}
// Read config file
var config Config
mustDeserializeFile(cfgPath, &config)
// Read secrets member from secrets file
mustDeserializeFile(secretsPath, &config.Secrets)
return &config
}
func mustDeserializeFile[T any](fileName string, obj *T) {
var err error
var cfgJson []byte
cfgJson, err = os.ReadFile(fileName)
if err != nil {
log.Fatal(err)
}
// JSONC => JSON
cfgJson, err = standardizeJSON(cfgJson)
if err != nil {
log.Fatal(err)
}
// Parse
if err := json.Unmarshal(cfgJson, obj); err != nil {
log.Fatal(err)
}
}
func standardizeJSON(b []byte) ([]byte, error) {
ast, err := hujson.Parse(b)
if err != nil {
return b, err
}
ast.Standardize()
return ast.Pack(), nil
}
|