binwiederhier 1 месяц назад
Родитель
Сommit
b23f6632b1
6 измененных файлов с 69 добавлено и 43 удалено
  1. 8 1
      cmd/serve.go
  2. 33 8
      docs/config.md
  3. 3 4
      docs/releases.md
  4. 3 2
      server/config.go
  5. 11 19
      server/server_twilio.go
  6. 11 9
      server/server_twilio_test.go

+ 8 - 1
cmd/serve.go

@@ -14,6 +14,7 @@ import (
 	"os/signal"
 	"strings"
 	"syscall"
+	"text/template"
 	"time"
 
 	"github.com/urfave/cli/v2"
@@ -458,7 +459,13 @@ func execServe(c *cli.Context) error {
 	conf.TwilioAuthToken = twilioAuthToken
 	conf.TwilioPhoneNumber = twilioPhoneNumber
 	conf.TwilioVerifyService = twilioVerifyService
-	conf.TwilioCallFormat = twilioCallFormat
+	if twilioCallFormat != "" {
+		tmpl, err := template.New("twiml").Parse(twilioCallFormat)
+		if err != nil {
+			return fmt.Errorf("failed to parse twilio-call-format template: %w", err)
+		}
+		conf.TwilioCallFormat = tmpl
+	}
 	conf.MessageSizeLimit = int(messageSizeLimit)
 	conf.MessageDelayMax = messageDelayLimit
 	conf.TotalTopicLimit = totalTopicLimit

+ 33 - 8
docs/config.md

@@ -1261,12 +1261,12 @@ are the easiest), and then configure the following options:
 * `twilio-auth-token` is the Twilio auth token, e.g. affebeef258625862586258625862586
 * `twilio-phone-number` is the outgoing phone number you purchased, e.g. +18775132586 
 * `twilio-verify-service` is the Twilio Verify service SID, e.g. VA12345beefbeef67890beefbeef122586
-* `twilio-call-format` is the custom TwiML send to the Call API (optional, see [TwiML](https://www.twilio.com/docs/voice/twiml))
+* `twilio-call-format` is the custom Twilio markup ([TwiML](https://www.twilio.com/docs/voice/twiml)) to use for phone calls (optional)
 
 After you have configured phone calls, create a [tier](#tiers) with a call limit (e.g. `ntfy tier create --call-limit=10 ...`),
 and then assign it to a user. Users may then use the `X-Call` header to receive a phone call when publishing a message.
 
-To customize your message send to Twilio's Call API, set the `twilio-call-format` option with [TwiML](https://www.twilio.com/docs/voice/twiml). The format is
+To customize the message that is spoken out loud, set the `twilio-call-format` option with [TwiML](https://www.twilio.com/docs/voice/twiml). The format is
 rendered as a [Go template](https://pkg.go.dev/text/template), so you can use the following fields from the message:
 
 * `{{.Topic}}` is the topic name
@@ -1278,7 +1278,37 @@ rendered as a [Go template](https://pkg.go.dev/text/template), so you can use th
 
 Here's an example:
 
-=== English example
+=== "Custom TwiML (English)"
+    ``` yaml
+    twilio-account: "AC12345beefbeef67890beefbeef122586"
+    twilio-auth-token: "affebeef258625862586258625862586"
+    twilio-phone-number: "+18775132586"
+    twilio-verify-service: "VA12345beefbeef67890beefbeef122586"
+    twilio-call-format: |
+      <Response>
+        <Pause length="1"/>
+        <Say loop="3">
+          Yo yo yo, you should totally check out this message for {{.Topic}}.
+          {{ if eq .Priority 5 }}
+            It's really really important, dude. So listen up!
+          {{ end }}
+          <break time="1s"/>
+          {{ if neq .Title "" }}
+            Bro, it's titled: {{.Title}}.
+          {{ end }}
+          <break time="1s"/>
+          {{.Message}}
+          <break time="1s"/>
+          That is all.
+          <break time="1s"/>
+          You know who this message is from? It is from {{.Sender}}.
+          <break time="3s"/>
+        </Say>
+        <Say>See ya!</Say>
+      </Response>
+    ```
+
+=== "Custom TwiML (German)"
     ``` yaml
     twilio-account: "AC12345beefbeef67890beefbeef122586"
     twilio-auth-token: "affebeef258625862586258625862586"
@@ -1310,11 +1340,6 @@ Here's an example:
       </Response>
     ```
 
-The TwiML is internaly used as a format string:
-1. The first `%s` will be replaced with the topic.
-1. The second `%s` will be replaced with the message.
-1. The third `%s` will be replaced with the message`s sender name.
-
 ## Message limits
 There are a few message limits that you can configure:
 

+ 3 - 4
docs/releases.md

@@ -1603,10 +1603,9 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
 
 **Features:**
 
-* Support for [updating and deleting notifications](publish.md#updating-deleting-notifications) 
-  ([#303](https://github.com/binwiederhier/ntfy/issues/303), [#1536](https://github.com/binwiederhier/ntfy/pull/1536),
-  [ntfy-android#151](https://github.com/binwiederhier/ntfy-android/pull/151), thanks to [@wunter8](https://github.com/wunter8)
-  for the initial implementation)
+* Support for [updating and deleting notifications](publish.md#updating-deleting-notifications) ([#303](https://github.com/binwiederhier/ntfy/issues/303), [#1536](https://github.com/binwiederhier/ntfy/pull/1536),
+  [ntfy-android#151](https://github.com/binwiederhier/ntfy-android/pull/151), thanks to [@wunter8](https://github.com/wunter8) for the initial implementation)
+* Support for a [custom Twilio call format](config.md#phone-calls) ([#1289](https://github.com/binwiederhier/ntfy/pull/1289), thanks to [@mmichaa](https://github.com/mmichaa) for the initial implementation) 
 
 ### ntfy Android app v1.22.x (UNRELEASED)
 

+ 3 - 2
server/config.go

@@ -3,6 +3,7 @@ package server
 import (
 	"io/fs"
 	"net/netip"
+	"text/template"
 	"time"
 
 	"heckel.io/ntfy/v2/user"
@@ -128,7 +129,7 @@ type Config struct {
 	TwilioCallsBaseURL                   string
 	TwilioVerifyBaseURL                  string
 	TwilioVerifyService                  string
-	TwilioCallFormat                     string
+	TwilioCallFormat                     *template.Template
 	MetricsEnable                        bool
 	MetricsListenHTTP                    string
 	ProfileListenHTTP                    string
@@ -227,7 +228,7 @@ func NewConfig() *Config {
 		TwilioPhoneNumber:                    "",
 		TwilioVerifyBaseURL:                  "https://verify.twilio.com", // Override for tests
 		TwilioVerifyService:                  "",
-		TwilioCallFormat:                     "",
+		TwilioCallFormat:                     nil,
 		MessageSizeLimit:                     DefaultMessageSizeLimit,
 		MessageDelayMin:                      DefaultMessageDelayMin,
 		MessageDelayMax:                      DefaultMessageDelayMax,

+ 11 - 19
server/server_twilio.go

@@ -15,14 +15,13 @@ import (
 	"heckel.io/ntfy/v2/util"
 )
 
-const (
-	// defaultTwilioCallFormat is the default TwiML format used for Twilio calls.
-	// It can be overridden in the server configuration's twilio-call-format field.
-	//
-	// The format uses Go template syntax with the following fields:
-	// {{.Topic}}, {{.Title}}, {{.Message}}, {{.Priority}}, {{.Tags}}, {{.Sender}}
-	// String fields are automatically XML-escaped.
-	defaultTwilioCallFormat = `
+// defaultTwilioCallFormatTemplate is the default TwiML template used for Twilio calls.
+// It can be overridden in the server configuration's twilio-call-format field.
+//
+// The format uses Go template syntax with the following fields:
+// {{.Topic}}, {{.Title}}, {{.Message}}, {{.Priority}}, {{.Tags}}, {{.Sender}}
+// String fields are automatically XML-escaped.
+var defaultTwilioCallFormatTemplate = template.Must(template.New("twiml").Parse(`
 <Response>
 	<Pause length="1"/>
 	<Say loop="3">
@@ -37,8 +36,7 @@ const (
 		<break time="3s"/>
 	</Say>
 	<Say>Goodbye.</Say>
-</Response>`
-)
+</Response>`))
 
 // twilioCallData holds the data passed to the Twilio call format template
 type twilioCallData struct {
@@ -83,15 +81,9 @@ func (s *Server) callPhone(v *visitor, r *http.Request, m *message, to string) {
 	if u != nil {
 		sender = u.Name
 	}
-	templateStr := defaultTwilioCallFormat
-	if s.config.TwilioCallFormat != "" {
-		templateStr = s.config.TwilioCallFormat
-	}
-	tmpl, err := template.New("twiml").Parse(templateStr)
-	if err != nil {
-		logvrm(v, r, m).Tag(tagTwilio).Err(err).Warn("Error parsing Twilio call format template")
-		minc(metricCallsMadeFailure)
-		return
+	tmpl := defaultTwilioCallFormatTemplate
+	if s.config.TwilioCallFormat != nil {
+		tmpl = s.config.TwilioCallFormat
 	}
 	tags := make([]string, len(m.Tags))
 	for i, tag := range m.Tags {

+ 11 - 9
server/server_twilio_test.go

@@ -1,14 +1,16 @@
 package server
 
 import (
-	"github.com/stretchr/testify/require"
-	"heckel.io/ntfy/v2/user"
-	"heckel.io/ntfy/v2/util"
 	"io"
 	"net/http"
 	"net/http/httptest"
 	"sync/atomic"
 	"testing"
+	"text/template"
+
+	"github.com/stretchr/testify/require"
+	"heckel.io/ntfy/v2/user"
+	"heckel.io/ntfy/v2/util"
 )
 
 func TestServer_Twilio_Call_Add_Verify_Call_Delete_Success(t *testing.T) {
@@ -222,22 +224,22 @@ func TestServer_Twilio_Call_Success_with_custom_twiml(t *testing.T) {
 	c.TwilioAccount = "AC1234567890"
 	c.TwilioAuthToken = "AAEAA1234567890"
 	c.TwilioPhoneNumber = "+1234567890"
-	c.TwilioCallFormat = `
+	c.TwilioCallFormat = template.Must(template.New("twiml").Parse(`
 <Response>
 	<Pause length="1"/>
 	<Say language="de-DE" loop="3">
-		Du hast eine Nachricht von notify im Thema %s. Nachricht:
+		Du hast eine Nachricht von notify im Thema {{.Topic}}. Nachricht:
 		<break time="1s"/>
-		%s
+		{{.Message}}
 		<break time="1s"/>
 		Ende der Nachricht.
 		<break time="1s"/>
-		Diese Nachricht wurde von Benutzer %s gesendet. Sie wird drei Mal wiederholt.
+		Diese Nachricht wurde von Benutzer {{.Sender}} gesendet. Sie wird drei Mal wiederholt.
 		Um dich von Anrufen wie diesen abzumelden, entferne deine Telefonnummer in der notify web app.
 		<break time="3s"/>
 	</Say>
 	<Say language="de-DE">Auf Wiederhören.</Say>
-</Response>`
+</Response>`))
 	s := newTestServer(t, c)
 
 	// Add tier and user
@@ -246,7 +248,7 @@ func TestServer_Twilio_Call_Success_with_custom_twiml(t *testing.T) {
 		MessageLimit: 10,
 		CallLimit:    1,
 	}))
-	require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
+	require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
 	require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
 	u, err := s.userManager.User("phil")
 	require.Nil(t, err)