Asterisk - The Open Source Telephony Project GIT-master-f36a736
func_sayfiles.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2021, Naveen Albert <asterisk@phreaknet.org>
5 *
6 * See http://www.asterisk.org for more information about
7 * the Asterisk project. Please do not directly contact
8 * any of the maintainers of this project for assistance;
9 * the project provides a web site, mailing lists and IRC
10 * channels for your use.
11 *
12 * This program is free software, distributed under the terms of
13 * the GNU General Public License Version 2. See the LICENSE file
14 * at the top of the source tree.
15 */
16
17/*! \file
18 *
19 * \brief Returns files played by Say applications
20 *
21 * \author Naveen Albert <asterisk@phreaknet.org>
22 * \ingroup functions
23 */
24
25/*** MODULEINFO
26 <support_level>extended</support_level>
27 ***/
28
29#include "asterisk.h"
30
31#include "asterisk/pbx.h"
32#include "asterisk/file.h"
33#include "asterisk/channel.h"
34#include "asterisk/say.h"
35#include "asterisk/lock.h"
36#include "asterisk/localtime.h"
37#include "asterisk/utils.h"
38#include "asterisk/app.h"
39#include "asterisk/test.h"
40#include "asterisk/module.h"
42
43/*** DOCUMENTATION
44 <function name="SAYFILES" language="en_US">
45 <since>
46 <version>16.21.0</version>
47 <version>18.7.0</version>
48 <version>19.0.0</version>
49 </since>
50 <synopsis>
51 Returns the ampersand-delimited file names that would be played by the Say applications (e.g. SayAlpha, SayDigits).
52 </synopsis>
53 <syntax>
54 <parameter name="value" required="true">
55 <para>The value to be translated to filenames.</para>
56 </parameter>
57 <parameter name="type">
58 <para>Say application type.</para>
59 <enumlist>
60 <enum name="alpha">
61 <para>Files played by SayAlpha(). Default if none is specified.</para>
62 </enum>
63 <enum name="digits">
64 <para>Files played by SayDigits().</para>
65 </enum>
66 <enum name="money">
67 <para>Files played by SayMoney(). Currently supported for English and US dollars only.</para>
68 </enum>
69 <enum name="number">
70 <para>Files played by SayNumber(). Currently supported for English only.</para>
71 </enum>
72 <enum name="ordinal">
73 <para>Files played by SayOrdinal(). Currently supported for English only.</para>
74 </enum>
75 <enum name="phonetic">
76 <para>Files played by SayPhonetic().</para>
77 </enum>
78 </enumlist>
79 </parameter>
80 </syntax>
81 <description>
82 <para>Returns the files that would be played by a Say application. These filenames could then be
83 passed directly into Playback, BackGround, Read, Queue, or any application which supports
84 playback of multiple ampersand-delimited files.</para>
85 <example title="Read using the number 123">
86 same => n,Read(response,${SAYFILES(123,number)})
87 </example>
88 </description>
89 <see-also>
90 <ref type="application">SayAlpha</ref>
91 <ref type="application">SayDigits</ref>
92 <ref type="application">SayMoney</ref>
93 <ref type="application">SayNumber</ref>
94 <ref type="application">SayOrdinal</ref>
95 <ref type="application">SayPhonetic</ref>
96 </see-also>
97 </function>
98 ***/
99static int sayfile_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
100{
101 char *value, *type, *files;
102 const char *lang;
103 struct ast_str *filenames = NULL;
107 );
108
109 if (ast_strlen_zero(data)) {
110 ast_log(LOG_WARNING, "SAYFILES requires an argument\n");
111 return 0;
112 }
113
115
116 value = args.value;
117 type = (ast_strlen_zero(args.type) ? "alpha" : args.type);
118 lang = (chan ? ast_channel_language(chan) : "en"); /* No chan for unit tests */
119
120 if (!strcmp(type, "alpha")) {
122 } else if (!strcmp(type, "phonetic")) {
123 filenames = ast_get_phonetic_str(value, lang);
124 } else if (!strcmp(type, "digits")) {
125 filenames = ast_get_digit_str(value, lang);
126 } else if (!strcmp(type, "number")) {
127 int num;
128 if (ast_str_to_int(value, &num)) {
129 ast_log(LOG_WARNING, "Invalid numeric argument: %s\n", value);
130 } else {
131 filenames = ast_get_number_str(num, lang);
132 }
133 } else if (!strcmp(type, "ordinal")) {
134 int num;
135 if (ast_str_to_int(value, &num)) {
136 ast_log(LOG_WARNING, "Invalid numeric argument: %s\n", value);
137 } else {
138 filenames = ast_get_ordinal_str(num, lang);
139 }
140 } else if (!strcmp(type, "money")) {
141 filenames = ast_get_money_str(value, lang);
142 } else {
143 ast_log(LOG_WARNING, "Invalid say type specified: %s\n", type);
144 }
145
146 if (!filenames) {
147 return -1;
148 }
149
150 files = ast_str_buffer(filenames);
151 snprintf(buf, len, "%s", files);
152 ast_free(filenames);
153
154 return 0;
155}
156
158 .name = "SAYFILES",
159 .read = sayfile_exec,
160};
161
162#ifdef TEST_FRAMEWORK
163AST_TEST_DEFINE(test_SAYFILES_function)
164{
166 struct ast_str *expr, *result;
167
168 switch (cmd) {
169 case TEST_INIT:
170 info->name = "test_SAYFILES_function";
171 info->category = "/funcs/func_sayfiles/";
172 info->summary = "Test SAYFILES function substitution";
173 info->description =
174 "Executes a series of variable substitutions using the SAYFILES function and ensures that the expected results are received.";
175 return AST_TEST_NOT_RUN;
176 case TEST_EXECUTE:
177 break;
178 }
179
180 ast_test_status_update(test, "Testing SAYFILES() substitution ...\n");
181
182 if (!(expr = ast_str_create(16))) {
183 return AST_TEST_FAIL;
184 }
185 if (!(result = ast_str_create(16))) {
186 ast_free(expr);
187 return AST_TEST_FAIL;
188 }
189
190 ast_str_set(&expr, 0, "${SAYFILES(hi Th3re,alpha)}");
192 if (strcmp(ast_str_buffer(result), "letters/h&letters/i&letters/space&letters/t&letters/h&digits/3&letters/r&letters/e") != 0) {
193 ast_test_status_update(test, "SAYFILES(hi Th3re,alpha) test failed ('%s')\n",
195 res = AST_TEST_FAIL;
196 }
197
198 ast_str_set(&expr, 0, "${SAYFILES(phreak,phonetic)}");
200 if (strcmp(ast_str_buffer(result), "phonetic/p_p&phonetic/h_p&phonetic/r_p&phonetic/e_p&phonetic/a_p&phonetic/k_p") != 0) {
201 ast_test_status_update(test, "SAYFILES(phreak,phonetic) test failed ('%s')\n",
203 res = AST_TEST_FAIL;
204 }
205
206 ast_str_set(&expr, 0, "${SAYFILES(35,digits)}");
208 if (strcmp(ast_str_buffer(result), "digits/3&digits/5") != 0) {
209 ast_test_status_update(test, "SAYFILES(35,digits) test failed ('%s')\n",
211 res = AST_TEST_FAIL;
212 }
213
214 /* + should be ignored and there should not be a leading & */
215 ast_str_set(&expr, 0, "${SAYFILES(+18005551212,digits)}");
217 if (strcmp(ast_str_buffer(result), "digits/1&digits/8&digits/0&digits/0&digits/5&digits/5&digits/5&digits/1&digits/2&digits/1&digits/2") != 0) {
218 ast_test_status_update(test, "SAYFILES(+18005551212,digits) test failed ('%s')\n",
220 res = AST_TEST_FAIL;
221 }
222
223 ast_str_set(&expr, 0, "${SAYFILES(35,number)}");
225 if (strcmp(ast_str_buffer(result), "digits/30&digits/5") != 0) {
226 ast_test_status_update(test, "SAYFILES(35,number) test failed ('%s')\n",
228 res = AST_TEST_FAIL;
229 }
230
231 ast_str_set(&expr, 0, "${SAYFILES(747,number)}");
233 if (strcmp(ast_str_buffer(result), "digits/7&digits/hundred&digits/40&digits/7") != 0) {
234 ast_test_status_update(test, "SAYFILES(747,number) test failed ('%s')\n",
236 res = AST_TEST_FAIL;
237 }
238
239 ast_str_set(&expr, 0, "${SAYFILES(1042,number)}");
241 if (strcmp(ast_str_buffer(result), "digits/1&digits/thousand&digits/40&digits/2") != 0) {
242 ast_test_status_update(test, "SAYFILES(1042,number) test failed ('%s')\n",
244 res = AST_TEST_FAIL;
245 }
246
247 ast_str_set(&expr, 0, "${SAYFILES(0,number)}");
249 if (strcmp(ast_str_buffer(result), "digits/0") != 0) {
250 ast_test_status_update(test, "SAYFILES(0,digits) test failed ('%s')\n",
252 res = AST_TEST_FAIL;
253 }
254
255 ast_str_set(&expr, 0, "${SAYFILES(2001000001,number)}");
257 if (strcmp(ast_str_buffer(result), "digits/2&digits/billion&digits/1&digits/million&digits/1") != 0) {
258 ast_test_status_update(test, "SAYFILES(2001000001,number) test failed ('%s')\n",
260 res = AST_TEST_FAIL;
261 }
262
263 ast_str_set(&expr, 0, "${SAYFILES(7,ordinal)}");
265 if (strcmp(ast_str_buffer(result), "digits/h-7") != 0) {
266 ast_test_status_update(test, "SAYFILES(7,ordinal) test failed ('%s')\n",
268 res = AST_TEST_FAIL;
269 }
270
271 ast_str_set(&expr, 0, "${SAYFILES(35,ordinal)}");
273 if (strcmp(ast_str_buffer(result), "digits/30&digits/h-5") != 0) {
274 ast_test_status_update(test, "SAYFILES(35,ordinal) test failed ('%s')\n",
276 res = AST_TEST_FAIL;
277 }
278
279 ast_str_set(&expr, 0, "${SAYFILES(1042,ordinal)}");
281 if (strcmp(ast_str_buffer(result), "digits/1&digits/thousand&digits/40&digits/h-2") != 0) {
282 ast_test_status_update(test, "SAYFILES(1042,ordinal) test failed ('%s')\n",
284 res = AST_TEST_FAIL;
285 }
286
287 ast_str_set(&expr, 0, "${SAYFILES(11042,ordinal)}");
289 if (strcmp(ast_str_buffer(result), "digits/11&digits/thousand&digits/40&digits/h-2") != 0) {
290 ast_test_status_update(test, "SAYFILES(11042,ordinal) test failed ('%s')\n",
292 res = AST_TEST_FAIL;
293 }
294
295 ast_str_set(&expr, 0, "${SAYFILES(40000,ordinal)}");
297 if (strcmp(ast_str_buffer(result), "digits/40&digits/h-thousand") != 0) {
298 ast_test_status_update(test, "SAYFILES(40000,ordinal) test failed ('%s')\n",
300 res = AST_TEST_FAIL;
301 }
302
303 ast_str_set(&expr, 0, "${SAYFILES(43638,ordinal)}");
305 if (strcmp(ast_str_buffer(result), "digits/40&digits/3&digits/thousand&digits/6&digits/hundred&digits/30&digits/h-8") != 0) {
306 ast_test_status_update(test, "SAYFILES(43638,ordinal) test failed ('%s')\n",
308 res = AST_TEST_FAIL;
309 }
310
311 ast_str_set(&expr, 0, "${SAYFILES(1000000,ordinal)}");
313 if (strcmp(ast_str_buffer(result), "digits/1&digits/h-million") != 0) {
314 ast_test_status_update(test, "SAYFILES(1000000,ordinal) test failed ('%s')\n",
316 res = AST_TEST_FAIL;
317 }
318
319 ast_str_set(&expr, 0, "${SAYFILES(1000001,ordinal)}");
321 if (strcmp(ast_str_buffer(result), "digits/1&digits/million&digits/h-1") != 0) {
322 ast_test_status_update(test, "SAYFILES(1000001,ordinal) test failed ('%s')\n",
324 res = AST_TEST_FAIL;
325 }
326
327 ast_str_set(&expr, 0, "${SAYFILES(2001000001,ordinal)}");
329 if (strcmp(ast_str_buffer(result), "digits/2&digits/billion&digits/1&digits/million&digits/h-1") != 0) {
330 ast_test_status_update(test, "SAYFILES(2001000001,ordinal) test failed ('%s')\n",
332 res = AST_TEST_FAIL;
333 }
334
335 ast_str_set(&expr, 0, "${SAYFILES(0,money)}");
337 if (strcmp(ast_str_buffer(result), "digits/0&cents") != 0) {
338 ast_test_status_update(test, "SAYFILES(0,money) test failed ('%s')\n",
340 res = AST_TEST_FAIL;
341 }
342
343 ast_str_set(&expr, 0, "${SAYFILES(0.01,money)}");
345 if (strcmp(ast_str_buffer(result), "digits/1&cent") != 0) {
346 ast_test_status_update(test, "SAYFILES(0.01,money) test failed ('%s')\n",
348 res = AST_TEST_FAIL;
349 }
350
351 ast_str_set(&expr, 0, "${SAYFILES(0.42,money)}");
353 if (strcmp(ast_str_buffer(result), "digits/40&digits/2&cents") != 0) {
354 ast_test_status_update(test, "SAYFILES(0.42,money) test failed ('%s')\n",
356 res = AST_TEST_FAIL;
357 }
358
359 ast_str_set(&expr, 0, "${SAYFILES(.42,money)}");
361 if (strcmp(ast_str_buffer(result), "digits/40&digits/2&cents") != 0) {
362 ast_test_status_update(test, "SAYFILES(.42,money) test failed ('%s')\n",
364 res = AST_TEST_FAIL;
365 }
366
367 ast_str_set(&expr, 0, "${SAYFILES(1.00,money)}");
369 if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar") != 0) {
370 ast_test_status_update(test, "SAYFILES(1.00,money) test failed ('%s')\n",
372 res = AST_TEST_FAIL;
373 }
374
375 ast_str_set(&expr, 0, "${SAYFILES(1.42,money)}");
377 if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar_&and&digits/40&digits/2&cents") != 0) {
378 ast_test_status_update(test, "SAYFILES(1.42,money) test failed ('%s')\n",
380 res = AST_TEST_FAIL;
381 }
382
383 ast_str_set(&expr, 0, "${SAYFILES(2.00,money)}");
385 if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
386 ast_test_status_update(test, "SAYFILES(2.00,money) test failed ('%s')\n",
388 res = AST_TEST_FAIL;
389 }
390
391 ast_str_set(&expr, 0, "${SAYFILES(2,money)}");
393 if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
394 ast_test_status_update(test, "SAYFILES(2,money) test failed ('%s')\n",
396 res = AST_TEST_FAIL;
397 }
398
399 ast_str_set(&expr, 0, "${SAYFILES(2.42,money)}");
401 if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/40&digits/2&cents") != 0) {
402 ast_test_status_update(test, "SAYFILES(2.42,money) test failed ('%s')\n",
404 res = AST_TEST_FAIL;
405 }
406
407 ast_str_set(&expr, 0, "${SAYFILES(2.05,money)}");
409 if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/5&cents") != 0) {
410 ast_test_status_update(test, "SAYFILES(2.05,money) test failed ('%s')\n",
412 res = AST_TEST_FAIL;
413 }
414
415 ast_str_set(&expr, 0, "${SAYFILES(2.051,money)}");
417 if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/5&cents") != 0) {
418 ast_test_status_update(test, "SAYFILES(2.051,money) test failed ('%s')\n",
420 res = AST_TEST_FAIL;
421 }
422
423 /* Invalid amounts */
424 ast_str_set(&expr, 0, "${SAYFILES(blah,money)}");
426 if (strcmp(ast_str_buffer(result), "digits/0&cents") != 0) {
427 ast_test_status_update(test, "SAYFILES(blah,money) test failed ('%s')\n",
429 res = AST_TEST_FAIL;
430 }
431
432 ast_str_set(&expr, 0, "${SAYFILES(2blah.05,money)}");
434 if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
435 ast_test_status_update(test, "SAYFILES(2blah.05,money) test failed ('%s')\n",
437 res = AST_TEST_FAIL;
438 }
439
440 ast_str_set(&expr, 0, "${SAYFILES(2.-05,money)}");
442 if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
443 ast_test_status_update(test, "SAYFILES(2.-05,money) test failed ('%s')\n",
445 res = AST_TEST_FAIL;
446 }
447
448 ast_str_set(&expr, 0, "${SAYFILES(2. 05,money)}");
450 if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
451 ast_test_status_update(test, "SAYFILES(2. 05,money) test failed ('%s')\n",
453 res = AST_TEST_FAIL;
454 }
455
456 ast_str_set(&expr, 0, "${SAYFILES(. 05,money)}");
458 if (strcmp(ast_str_buffer(result), "digits/0&cents") != 0) {
459 ast_test_status_update(test, "SAYFILES(. 05,money) test failed ('%s')\n",
461 res = AST_TEST_FAIL;
462 }
463
464 ast_free(expr);
466
467 return res;
468}
469#endif
470
471static int unload_module(void)
472{
473 AST_TEST_UNREGISTER(test_SAYFILES_function);
475}
476
477static int load_module(void)
478{
479 AST_TEST_REGISTER(test_SAYFILES_function);
481}
482
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_log
Definition: astobj2.c:42
static PGresult * result
Definition: cel_pgsql.c:84
static const char type[]
Definition: chan_ooh323.c:109
General Asterisk PBX channel definitions.
const char * ast_channel_language(const struct ast_channel *chan)
Conversion utility functions.
int ast_str_to_int(const char *str, int *res)
Convert the given string to a signed integer.
Definition: conversions.c:44
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
static struct ast_custom_function sayfiles
static int sayfile_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_sayfiles.c:99
static int load_module(void)
static int unload_module(void)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define LOG_WARNING
Custom localtime functions for multiple timezones.
Asterisk locking-related definitions:
Asterisk module definitions.
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition: module.h:581
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
def info(msg)
Core PBX routines and definitions.
void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
#define NULL
Definition: resample.c:96
Say numbers and dates (maybe words one day too)
struct ast_str * ast_get_ordinal_str(int num, const char *lang)
Returns an ast_str of files for SayOrdinal playback.
Definition: say.c:686
struct ast_str * ast_get_character_str(const char *str, const char *lang, enum ast_say_case_sensitivity sensitivity)
Returns an ast_str of files for SayAlpha playback.
Definition: say.c:64
struct ast_str * ast_get_number_str(int num, const char *lang)
Returns an ast_str of files for SayNumber playback.
Definition: say.c:566
struct ast_str * ast_get_phonetic_str(const char *str, const char *lang)
Returns an ast_str of files for SayPhonetic playback.
Definition: say.c:216
struct ast_str * ast_get_money_str(const char *str, const char *lang)
Returns an ast_str of files for SayMoney playback.
Definition: say.c:449
@ AST_SAY_CASE_NONE
Definition: say.h:182
struct ast_str * ast_get_digit_str(const char *str, const char *lang)
Returns an ast_str of files for SayDigits playback.
Definition: say.c:300
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
Main Channel structure associated with a channel.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Support for dynamic strings.
Definition: strings.h:623
int value
Definition: syslog.c:37
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
ast_test_result_state
Definition: test.h:193
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
@ AST_TEST_NOT_RUN
Definition: test.h:194
const char * args
Utility functions.