This commit is contained in:
binwiederhier 2025-07-16 21:50:29 +02:00
parent 610792b902
commit 4603802f62
5 changed files with 994 additions and 458 deletions

File diff suppressed because it is too large Load diff

View file

@ -1183,7 +1183,7 @@ func (s *Server) replaceTemplate(tpl string, source string) (string, error) {
if err := json.Unmarshal([]byte(source), &data); err != nil {
return "", errHTTPBadRequestTemplateMessageNotJSON
}
t, err := template.New("").Funcs(sprig.FuncMap()).Parse(tpl)
t, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(tpl)
if err != nil {
return "", errHTTPBadRequestTemplateInvalid.Wrap("%s", err.Error())
}
@ -2111,32 +2111,3 @@ 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
}

View file

@ -1,31 +1,56 @@
title: |
{{- if .pull_request }}
Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }}
{{- else if and .starred_at (eq .action "created")}}
⭐ {{ .sender.login }} starred {{ .repository.full_name }}
{{- if and .starred_at (eq .action "created")}}
⭐ {{ .sender.login }} starred {{ .repository.name }}
{{- else if and .repository (eq .action "started")}}
👀 {{ .sender.login }} started watching {{ .repository.name }}
{{- else if and .comment (eq .action "created") }}
💬 New comment on issue #{{ .issue.number }} — {{ .issue.title }}
💬 New comment on #{{ .issue.number }}: {{ .issue.title }}
{{- else if .pull_request }}
🔀 Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }}
{{- else if .issue }}
🐛 Issue {{ .action }}: #{{ .issue.number }} {{ .issue.title }}
{{- else }}
Unsupported GitHub event type or action.
{{ fail "Unsupported GitHub event type or action." }}
{{- end }}
message: |
{{- if .pull_request }}
Repository: {{ .repository.full_name }}, branch {{ .pull_request.head.ref }} → {{ .pull_request.base.ref }}
Created by: {{ .pull_request.user.login }}
Link: {{ .pull_request.html_url }}
{{ if .pull_request.body }}Description:
{{ .pull_request.body }}{{ end }}
{{- else if and .starred_at (eq .action "created")}}
⭐ {{ .sender.login }} starred {{ .repository.full_name }}
📦 {{ .repository.description | default "(no description)" }}
🔗 {{ .repository.html_url }}
📅 {{ .starred_at }}
{{ if and .starred_at (eq .action "created")}}
Stargazer: {{ .sender.html_url }}
Repository: {{ .repository.html_url }}
{{- else if and .repository (eq .action "started")}}
Watcher: {{ .sender.html_url }}
Repository: {{ .repository.html_url }}
{{- else if and .comment (eq .action "created") }}
💬 New comment on issue #{{ .issue.number }} — {{ .issue.title }}
📦 {{ .repository.full_name }}
👤 {{ .comment.user.login }}
🔗 {{ .comment.html_url }}
📝 {{ .comment.body | default "(no comment body)" }}
Commenter: {{ .comment.user.html_url }}
Repository: {{ .repository.html_url }}
Comment link: {{ .comment.html_url }}
{{ if .comment.body }}
Comment:
{{ .comment.body | trunc 2000 }}{{ end }}
{{- else if .pull_request }}
Branch: {{ .pull_request.head.ref }} → {{ .pull_request.base.ref }}
{{ .action | title }} by: {{ .pull_request.user.html_url }}
Repository: {{ .repository.html_url }}
Pull request: {{ .pull_request.html_url }}
{{ if .pull_request.body }}
Description:
{{ .pull_request.body | trunc 2000 }}{{ end }}
{{- else if .issue }}
{{ .action | title }} by: {{ .issue.user.html_url }}
Repository: {{ .repository.html_url }}
Issue link: {{ .issue.html_url }}
{{ if .issue.body }}
Description:
{{ .issue.body | trunc 2000 }}{{ end }}
{{- else }}
{{ fail "Unsupported GitHub event type or action." }}
{{- end }}

View file

@ -0,0 +1,216 @@
{
"action": "opened",
"issue": {
"url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391",
"repository_url": "https://api.github.com/repos/binwiederhier/ntfy",
"labels_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/labels{/name}",
"comments_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/comments",
"events_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/events",
"html_url": "https://github.com/binwiederhier/ntfy/issues/1391",
"id": 3236389051,
"node_id": "I_kwDOGRBhi87A52C7",
"number": 1391,
"title": "http 500 error (ntfy error 50001)",
"user": {
"login": "TheUser-dev",
"id": 213207407,
"node_id": "U_kgDODLVJbw",
"avatar_url": "https://avatars.githubusercontent.com/u/213207407?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/TheUser-dev",
"html_url": "https://github.com/TheUser-dev",
"followers_url": "https://api.github.com/users/TheUser-dev/followers",
"following_url": "https://api.github.com/users/TheUser-dev/following{/other_user}",
"gists_url": "https://api.github.com/users/TheUser-dev/gists{/gist_id}",
"starred_url": "https://api.github.com/users/TheUser-dev/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/TheUser-dev/subscriptions",
"organizations_url": "https://api.github.com/users/TheUser-dev/orgs",
"repos_url": "https://api.github.com/users/TheUser-dev/repos",
"events_url": "https://api.github.com/users/TheUser-dev/events{/privacy}",
"received_events_url": "https://api.github.com/users/TheUser-dev/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"labels": [
{
"id": 3480884102,
"node_id": "LA_kwDOGRBhi87PehOG",
"url": "https://api.github.com/repos/binwiederhier/ntfy/labels/%F0%9F%AA%B2%20bug",
"name": "🪲 bug",
"color": "d73a4a",
"default": false,
"description": "Something isn't working"
}
],
"state": "open",
"locked": false,
"assignee": null,
"assignees": [
],
"milestone": null,
"comments": 0,
"created_at": "2025-07-16T15:20:56Z",
"updated_at": "2025-07-16T15:20:56Z",
"closed_at": null,
"author_association": "NONE",
"active_lock_reason": null,
"sub_issues_summary": {
"total": 0,
"completed": 0,
"percent_completed": 0
},
"body": ":lady_beetle: **Describe the bug**\nWhen sending a notification (especially when it happens with multiple requests) this error occurs\n\n:computer: **Components impacted**\nntfy server 2.13.0 in docker, debian 12 arm64\n\n:bulb: **Screenshots and/or logs**\n```\nclosed with HTTP 500 (ntfy error 50001) (error=database table is locked, http_method=POST, http_path=/_matrix/push/v1/notify, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:<edited>, visitor_ip=<edited>, visitor_messages=448, visitor_messages_limit=17280, visitor_messages_remaining=16832, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=57.049697891799994, visitor_seen=2025-07-16T15:06:35.429Z)\n```\n\n:crystal_ball: **Additional context**\nLooks like this has already been fixed by #498, regression?\n",
"reactions": {
"url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/reactions",
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"hooray": 0,
"confused": 0,
"heart": 0,
"rocket": 0,
"eyes": 0
},
"timeline_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/timeline",
"performed_via_github_app": null,
"state_reason": null
},
"repository": {
"id": 420503947,
"node_id": "R_kgDOGRBhiw",
"name": "ntfy",
"full_name": "binwiederhier/ntfy",
"private": false,
"owner": {
"login": "binwiederhier",
"id": 664597,
"node_id": "MDQ6VXNlcjY2NDU5Nw==",
"avatar_url": "https://avatars.githubusercontent.com/u/664597?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/binwiederhier",
"html_url": "https://github.com/binwiederhier",
"followers_url": "https://api.github.com/users/binwiederhier/followers",
"following_url": "https://api.github.com/users/binwiederhier/following{/other_user}",
"gists_url": "https://api.github.com/users/binwiederhier/gists{/gist_id}",
"starred_url": "https://api.github.com/users/binwiederhier/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/binwiederhier/subscriptions",
"organizations_url": "https://api.github.com/users/binwiederhier/orgs",
"repos_url": "https://api.github.com/users/binwiederhier/repos",
"events_url": "https://api.github.com/users/binwiederhier/events{/privacy}",
"received_events_url": "https://api.github.com/users/binwiederhier/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"html_url": "https://github.com/binwiederhier/ntfy",
"description": "Send push notifications to your phone or desktop using PUT/POST",
"fork": false,
"url": "https://api.github.com/repos/binwiederhier/ntfy",
"forks_url": "https://api.github.com/repos/binwiederhier/ntfy/forks",
"keys_url": "https://api.github.com/repos/binwiederhier/ntfy/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/binwiederhier/ntfy/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/binwiederhier/ntfy/teams",
"hooks_url": "https://api.github.com/repos/binwiederhier/ntfy/hooks",
"issue_events_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/events{/number}",
"events_url": "https://api.github.com/repos/binwiederhier/ntfy/events",
"assignees_url": "https://api.github.com/repos/binwiederhier/ntfy/assignees{/user}",
"branches_url": "https://api.github.com/repos/binwiederhier/ntfy/branches{/branch}",
"tags_url": "https://api.github.com/repos/binwiederhier/ntfy/tags",
"blobs_url": "https://api.github.com/repos/binwiederhier/ntfy/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/binwiederhier/ntfy/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/binwiederhier/ntfy/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/binwiederhier/ntfy/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/binwiederhier/ntfy/statuses/{sha}",
"languages_url": "https://api.github.com/repos/binwiederhier/ntfy/languages",
"stargazers_url": "https://api.github.com/repos/binwiederhier/ntfy/stargazers",
"contributors_url": "https://api.github.com/repos/binwiederhier/ntfy/contributors",
"subscribers_url": "https://api.github.com/repos/binwiederhier/ntfy/subscribers",
"subscription_url": "https://api.github.com/repos/binwiederhier/ntfy/subscription",
"commits_url": "https://api.github.com/repos/binwiederhier/ntfy/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/binwiederhier/ntfy/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/binwiederhier/ntfy/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/binwiederhier/ntfy/contents/{+path}",
"compare_url": "https://api.github.com/repos/binwiederhier/ntfy/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/binwiederhier/ntfy/merges",
"archive_url": "https://api.github.com/repos/binwiederhier/ntfy/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/binwiederhier/ntfy/downloads",
"issues_url": "https://api.github.com/repos/binwiederhier/ntfy/issues{/number}",
"pulls_url": "https://api.github.com/repos/binwiederhier/ntfy/pulls{/number}",
"milestones_url": "https://api.github.com/repos/binwiederhier/ntfy/milestones{/number}",
"notifications_url": "https://api.github.com/repos/binwiederhier/ntfy/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/binwiederhier/ntfy/labels{/name}",
"releases_url": "https://api.github.com/repos/binwiederhier/ntfy/releases{/id}",
"deployments_url": "https://api.github.com/repos/binwiederhier/ntfy/deployments",
"created_at": "2021-10-23T19:25:32Z",
"updated_at": "2025-07-16T14:54:16Z",
"pushed_at": "2025-07-16T11:49:26Z",
"git_url": "git://github.com/binwiederhier/ntfy.git",
"ssh_url": "git@github.com:binwiederhier/ntfy.git",
"clone_url": "https://github.com/binwiederhier/ntfy.git",
"svn_url": "https://github.com/binwiederhier/ntfy",
"homepage": "https://ntfy.sh",
"size": 36831,
"stargazers_count": 25112,
"watchers_count": 25112,
"language": "Go",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": false,
"forks_count": 984,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 369,
"license": {
"key": "apache-2.0",
"name": "Apache License 2.0",
"spdx_id": "Apache-2.0",
"url": "https://api.github.com/licenses/apache-2.0",
"node_id": "MDc6TGljZW5zZTI="
},
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"topics": [
"curl",
"notifications",
"ntfy",
"ntfysh",
"pubsub",
"push-notifications",
"rest-api"
],
"visibility": "public",
"forks": 984,
"open_issues": 369,
"watchers": 25112,
"default_branch": "main"
},
"sender": {
"login": "TheUser-dev",
"id": 213207407,
"node_id": "U_kgDODLVJbw",
"avatar_url": "https://avatars.githubusercontent.com/u/213207407?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/TheUser-dev",
"html_url": "https://github.com/TheUser-dev",
"followers_url": "https://api.github.com/users/TheUser-dev/followers",
"following_url": "https://api.github.com/users/TheUser-dev/following{/other_user}",
"gists_url": "https://api.github.com/users/TheUser-dev/gists{/gist_id}",
"starred_url": "https://api.github.com/users/TheUser-dev/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/TheUser-dev/subscriptions",
"organizations_url": "https://api.github.com/users/TheUser-dev/orgs",
"repos_url": "https://api.github.com/users/TheUser-dev/repos",
"events_url": "https://api.github.com/users/TheUser-dev/events{/privacy}",
"received_events_url": "https://api.github.com/users/TheUser-dev/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
}
}

View file

@ -2,40 +2,26 @@ package sprig
import (
"errors"
"html/template"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"math/rand"
"path"
"path/filepath"
"reflect"
"strconv"
"strings"
ttemplate "text/template"
"text/template"
"time"
"golang.org/x/text/cases"
)
// FuncMap produces the function map.
// TxtFuncMap produces the function map.
//
// Use this to pass the functions into the template engine:
//
// tpl := template.New("foo").Funcs(sprig.FuncMap()))
func FuncMap() template.FuncMap {
return HTMLFuncMap()
}
//
// TxtFuncMap returns a 'text/template'.FuncMap
func TxtFuncMap() ttemplate.FuncMap {
return GenericFuncMap()
}
// HTMLFuncMap returns an 'html/template'.Funcmap
func HTMLFuncMap() template.FuncMap {
return GenericFuncMap()
}
// GenericFuncMap returns a copy of the basic function map as a map[string]any.
func GenericFuncMap() map[string]any {
func TxtFuncMap() template.FuncMap {
gfm := make(map[string]any, len(genericMap))
for k, v := range genericMap {
gfm[k] = v
@ -63,11 +49,13 @@ var genericMap = map[string]any{
"unixEpoch": unixEpoch,
// Strings
"trunc": trunc,
"trim": strings.TrimSpace,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": cases.Title,
"trunc": trunc,
"trim": strings.TrimSpace,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": func(s string) string {
return cases.Title(language.English).String(s)
},
"substr": substring,
// Switch order so that "foo" | repeat 5
"repeat": func(count int, str string) string { return strings.Repeat(str, count) },
@ -99,11 +87,6 @@ var genericMap = map[string]any{
"seq": seq,
"toDecimal": toDecimal,
//"gt": func(a, b int) bool {return a > b},
//"gte": func(a, b int) bool {return a >= b},
//"lt": func(a, b int) bool {return a < b},
//"lte": func(a, b int) bool {return a <= b},
// split "/" foo/bar returns map[int]string{0: foo, 1: bar}
"split": split,
"splitList": func(sep, orig string) []string { return strings.Split(orig, sep) },