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
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"`
	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"`
}

type UpdateSchedule struct {
	Day    int `json:"day"`
	Week   int `json:"week"`
	Weeks4 int `json:"weeks4"`
	Older  int `json:"older"`
}

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
}