1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2025-11-29 19:59:59 +01:00

Tempalte dir

This commit is contained in:
binwiederhier 2025-07-16 10:01:59 +02:00
parent 2a468493f9
commit 93e14b73bb
3 changed files with 62 additions and 3 deletions

View file

@ -107,6 +107,7 @@ var flagsServe = append(
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-startup-queries", Aliases: []string{"web_push_startup_queries"}, EnvVars: []string{"NTFY_WEB_PUSH_STARTUP_QUERIES"}, Usage: "queries run when the web push database is initialized"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-duration", Aliases: []string{"web_push_expiry_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryDuration), Usage: "automatically expire unused subscriptions after this time"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-warning-duration", Aliases: []string{"web_push_expiry_warning_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryWarningDuration), Usage: "send web push warning notification after this time before expiring unused subscriptions"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "template-directory", Aliases: []string{"template_directory"}, EnvVars: []string{"NTFY_TEMPLATE_DIRECTORY"}, Usage: "directory to load named templates from"}),
)
var cmdServe = &cli.Command{
@ -205,6 +206,7 @@ func execServe(c *cli.Context) error {
metricsListenHTTP := c.String("metrics-listen-http")
enableMetrics := c.Bool("enable-metrics") || metricsListenHTTP != ""
profileListenHTTP := c.String("profile-listen-http")
templateDirectory := c.String("template-directory")
// Convert durations
cacheDuration, err := util.ParseDuration(cacheDurationStr)
@ -461,6 +463,7 @@ func execServe(c *cli.Context) error {
conf.WebPushStartupQueries = webPushStartupQueries
conf.WebPushExpiryDuration = webPushExpiryDuration
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
conf.TemplateDirectory = templateDirectory
conf.Version = c.App.Version
// Set up hot-reloading of config

View file

@ -167,6 +167,7 @@ type Config struct {
WebPushExpiryDuration time.Duration
WebPushExpiryWarningDuration time.Duration
Version string // injected by App
TemplateDirectory string // Directory to load named templates from
}
// NewConfig instantiates a default new server config
@ -257,5 +258,6 @@ func NewConfig() *Config {
WebPushEmailAddress: "",
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
TemplateDirectory: "",
}
}

View file

@ -62,6 +62,7 @@ type Server struct {
metricsHandler http.Handler // Handles /metrics if enable-metrics set, and listen-metrics-http not set
closeChan chan bool
mu sync.RWMutex
templates map[string]*template.Template // Loaded named templates
}
// handleFunc extends the normal http.HandlerFunc to be able to easily return errors
@ -222,8 +223,16 @@ func New(conf *Config) (*Server, error) {
messagesHistory: []int64{messages},
visitors: make(map[string]*visitor),
stripe: stripe,
templates: make(map[string]*template.Template),
}
s.priceCache = util.NewLookupCache(s.fetchStripePrices, conf.StripePriceCacheDuration)
if conf.TemplateDirectory != "" {
tmpls, err := loadTemplatesFromDir(conf.TemplateDirectory)
if err != nil {
return nil, fmt.Errorf("failed to load templates from %s: %w", conf.TemplateDirectory, err)
}
s.templates = tmpls
}
return s, nil
}
@ -1113,10 +1122,10 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedR
return errHTTPEntityTooLargeJSONBody
}
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
if m.Message, err = replaceTemplate(m.Message, peekedBody); err != nil {
if m.Message, err = s.replaceTemplate(m.Message, peekedBody); err != nil {
return err
}
if m.Title, err = replaceTemplate(m.Title, peekedBody); err != nil {
if m.Title, err = s.replaceTemplate(m.Title, peekedBody); err != nil {
return err
}
if len(m.Message) > s.config.MessageSizeLimit {
@ -1125,10 +1134,26 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedR
return nil
}
func replaceTemplate(tpl string, source string) (string, error) {
func (s *Server) replaceTemplate(tpl string, source string) (string, error) {
if templateDisallowedRegex.MatchString(tpl) {
return "", errHTTPBadRequestTemplateDisallowedFunctionCalls
}
if strings.HasPrefix(tpl, "@") {
name := strings.TrimPrefix(tpl, "@")
t, ok := s.templates[name]
if !ok {
return "", fmt.Errorf("template '@%s' not found", name)
}
var data any
if err := json.Unmarshal([]byte(source), &data); err != nil {
return "", errHTTPBadRequestTemplateMessageNotJSON
}
var buf bytes.Buffer
if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
return "", errHTTPBadRequestTemplateExecuteFailed
}
return buf.String(), nil
}
var data any
if err := json.Unmarshal([]byte(source), &data); err != nil {
return "", errHTTPBadRequestTemplateMessageNotJSON
@ -2061,3 +2086,32 @@ func (s *Server) updateAndWriteStats(messagesCount int64) {
}
}()
}
func loadTemplatesFromDir(dir string) (map[string]*template.Template, error) {
templates := make(map[string]*template.Template)
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasSuffix(name, ".tmpl") {
continue
}
path := filepath.Join(dir, name)
content, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read template %s: %w", name, err)
}
tmpl, err := template.New(name).Funcs(sprig.FuncMap()).Parse(string(content))
if err != nil {
return nil, fmt.Errorf("failed to parse template %s: %w", name, err)
}
base := strings.TrimSuffix(name, ".tmpl")
templates[base] = tmpl
}
return templates, nil
}