waha-shared 1.0.510 → 1.0.511
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/dist/data/bibleAudios/bibleAudios.json +12 -0
- package/dist/data/bibleStatuses/bibleStatuses.json +852 -0
- package/dist/data/bibleTexts/bibleTexts.json +11 -0
- package/dist/data/countriesAndLanguages/countriesAndLanguages.json +25 -20
- package/dist/data/countriesAndLanguages/countriesAndLanguages.schema.json +1 -1
- package/dist/data/countriesAndLanguages/countriesAndLanguages.zod.js +1 -1
- package/dist/data/languages/index.d.ts +1 -1
- package/dist/data/languages/languages.json +18 -11
- package/dist/data/languages/languages.schema.json +3 -1
- package/dist/data/languages/languages.zod.d.ts +1 -1
- package/dist/data/languages/languages.zod.js +6 -1
- package/dist/data/mediaDurations/mediaDurations.json +1090 -0
- package/dist/data/notification/index.d.ts +2 -2
- package/dist/data/notification/notification.schema.json +9 -3
- package/dist/data/notification/notification.zod.d.ts +3 -3
- package/dist/data/notification/notification.zod.js +4 -6
- package/dist/data/phoneLanguages/phoneLanguages.json +32 -0
- package/dist/data/releaseNotes/releaseNotes.json +9 -2
- package/dist/data/translationsApp/translationsApp.json +878 -8
- package/dist/data/translationsFtb/translationsFtb.json +136 -0
- package/dist/data/translationsIntroduction/translationsIntroduction.json +31 -31
- package/dist/data/translationsQuestion/translationsQuestion.json +64 -0
- package/dist/data/translationsSet/translationsSet.json +1661 -83
- package/dist/data/translationsSpokenQuestion/translationsSpokenQuestion.json +64 -0
- package/dist/functions/activeCampaign.d.ts +6 -0
- package/dist/functions/activeCampaign.js +10 -1
- package/dist/functions/bibleBooks.d.ts +6 -1
- package/dist/functions/bibleBooks.js +6 -1
- package/dist/functions/bibles.d.ts +22 -8
- package/dist/functions/bibles.js +54 -120
- package/dist/functions/crowdin.d.ts +53 -4
- package/dist/functions/crowdin.js +55 -43
- package/dist/functions/donations.d.ts +40 -0
- package/dist/functions/donations.js +135 -0
- package/dist/functions/ffmpeg.d.ts +21 -0
- package/dist/functions/ffmpeg.js +37 -9
- package/dist/functions/languages.d.ts +2 -2
- package/dist/functions/languages.js +26 -44
- package/dist/functions/scripturePassages.d.ts +9 -5
- package/dist/functions/scripturePassages.js +6 -10
- package/dist/functions/sets.d.ts +15 -7
- package/dist/functions/sets.js +34 -28
- package/dist/functions/upload.d.ts +16 -6
- package/dist/functions/upload.js +15 -6
- package/dist/functions/utils.d.ts +0 -2
- package/dist/functions/utils.js +1 -9
- package/dist/types/logs.d.ts +2 -2
- package/dist/types/notifications.d.ts +3 -3
- package/dist/types/sets.d.ts +14 -0
- package/dist/types/users.d.ts +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currencies Stripe takes in whole units rather than minor units, from
|
|
3
|
+
* https://docs.stripe.com/currencies#zero-decimal. HUF, IDR, and COP look
|
|
4
|
+
* zero-decimal but aren't: Stripe takes them in minor units like USD.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ZERO_DECIMAL_CURRENCIES: Set<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Currencies donors enter as whole numbers. ISK and UGX have no fractional
|
|
9
|
+
* amounts, but Stripe still takes them as two-decimal values ending in `00`
|
|
10
|
+
* (https://docs.stripe.com/currencies#special-cases), so they aren't in
|
|
11
|
+
* {@link ZERO_DECIMAL_CURRENCIES}.
|
|
12
|
+
*/
|
|
13
|
+
export declare const WHOLE_AMOUNT_CURRENCIES: Set<string>;
|
|
14
|
+
export declare const toStripeAmount: (amount: number, currency: string) => number;
|
|
15
|
+
export declare const fromStripeAmount: (amount: number, currency: string) => number;
|
|
16
|
+
/**
|
|
17
|
+
* Cleans typed custom-amount text down to digits and at most one `.` with
|
|
18
|
+
* two decimal places, or digits only when `wholeOnly`. The first `.` or `,`
|
|
19
|
+
* is the decimal point, since comma-decimal locales type `5,50` on the
|
|
20
|
+
* decimal pad; any later separators are dropped.
|
|
21
|
+
*/
|
|
22
|
+
export declare function normalizeAmountInput(text: string, wholeOnly: boolean): string;
|
|
23
|
+
/**
|
|
24
|
+
* The smallest and largest donation in `currency`, in major units. Both
|
|
25
|
+
* scale from the currency's first preset so they track its value — 1 to
|
|
26
|
+
* 100,000 for USD — and the minimum stays above Stripe's per-currency charge
|
|
27
|
+
* minimums (https://docs.stripe.com/currencies#minimum-and-maximum-charge-amounts).
|
|
28
|
+
*/
|
|
29
|
+
export declare function getDonationLimits(currency: string): {
|
|
30
|
+
min: number;
|
|
31
|
+
max: number;
|
|
32
|
+
};
|
|
33
|
+
/** Validates that a custom amount string is within `currency`'s limits. */
|
|
34
|
+
export declare function isValidDonationAmount(amount: string, currency: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Validates an amount already in Stripe's units (from
|
|
37
|
+
* {@link toStripeAmount}), for the server that creates the PaymentIntent.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isValidStripeDonationAmount(amount: number, currency: string): boolean;
|
|
40
|
+
export declare const DONATION_PRESETS: Record<string, number[]>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DONATION_PRESETS = exports.fromStripeAmount = exports.toStripeAmount = exports.WHOLE_AMOUNT_CURRENCIES = exports.ZERO_DECIMAL_CURRENCIES = void 0;
|
|
4
|
+
exports.normalizeAmountInput = normalizeAmountInput;
|
|
5
|
+
exports.getDonationLimits = getDonationLimits;
|
|
6
|
+
exports.isValidDonationAmount = isValidDonationAmount;
|
|
7
|
+
exports.isValidStripeDonationAmount = isValidStripeDonationAmount;
|
|
8
|
+
/**
|
|
9
|
+
* Currencies Stripe takes in whole units rather than minor units, from
|
|
10
|
+
* https://docs.stripe.com/currencies#zero-decimal. HUF, IDR, and COP look
|
|
11
|
+
* zero-decimal but aren't: Stripe takes them in minor units like USD.
|
|
12
|
+
*/
|
|
13
|
+
exports.ZERO_DECIMAL_CURRENCIES = new Set([
|
|
14
|
+
'BIF',
|
|
15
|
+
'CLP',
|
|
16
|
+
'DJF',
|
|
17
|
+
'GNF',
|
|
18
|
+
'JPY',
|
|
19
|
+
'KMF',
|
|
20
|
+
'KRW',
|
|
21
|
+
'MGA',
|
|
22
|
+
'PYG',
|
|
23
|
+
'RWF',
|
|
24
|
+
'VND',
|
|
25
|
+
'VUV',
|
|
26
|
+
'XAF',
|
|
27
|
+
'XOF',
|
|
28
|
+
'XPF',
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Currencies donors enter as whole numbers. ISK and UGX have no fractional
|
|
32
|
+
* amounts, but Stripe still takes them as two-decimal values ending in `00`
|
|
33
|
+
* (https://docs.stripe.com/currencies#special-cases), so they aren't in
|
|
34
|
+
* {@link ZERO_DECIMAL_CURRENCIES}.
|
|
35
|
+
*/
|
|
36
|
+
exports.WHOLE_AMOUNT_CURRENCIES = new Set([
|
|
37
|
+
...exports.ZERO_DECIMAL_CURRENCIES,
|
|
38
|
+
'ISK',
|
|
39
|
+
'UGX',
|
|
40
|
+
]);
|
|
41
|
+
// Rounds because float math makes 19.99 * 100 = 1998.9999999999998, and
|
|
42
|
+
// Stripe only accepts integer amounts.
|
|
43
|
+
const toStripeAmount = (amount, currency) => exports.ZERO_DECIMAL_CURRENCIES.has(currency.toUpperCase())
|
|
44
|
+
? amount
|
|
45
|
+
: Math.round(amount * 100);
|
|
46
|
+
exports.toStripeAmount = toStripeAmount;
|
|
47
|
+
const fromStripeAmount = (amount, currency) => exports.ZERO_DECIMAL_CURRENCIES.has(currency.toUpperCase()) ? amount : amount / 100;
|
|
48
|
+
exports.fromStripeAmount = fromStripeAmount;
|
|
49
|
+
/**
|
|
50
|
+
* Cleans typed custom-amount text down to digits and at most one `.` with
|
|
51
|
+
* two decimal places, or digits only when `wholeOnly`. The first `.` or `,`
|
|
52
|
+
* is the decimal point, since comma-decimal locales type `5,50` on the
|
|
53
|
+
* decimal pad; any later separators are dropped.
|
|
54
|
+
*/
|
|
55
|
+
function normalizeAmountInput(text, wholeOnly) {
|
|
56
|
+
const digits = (s) => s.replace(/[^0-9]/g, '');
|
|
57
|
+
if (wholeOnly)
|
|
58
|
+
return digits(text);
|
|
59
|
+
const match = text.match(/[.,]/);
|
|
60
|
+
if (!match || match.index === undefined)
|
|
61
|
+
return digits(text);
|
|
62
|
+
const whole = digits(text.slice(0, match.index));
|
|
63
|
+
const fraction = digits(text.slice(match.index + 1)).slice(0, 2);
|
|
64
|
+
return `${whole}.${fraction}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The smallest and largest donation in `currency`, in major units. Both
|
|
68
|
+
* scale from the currency's first preset so they track its value — 1 to
|
|
69
|
+
* 100,000 for USD — and the minimum stays above Stripe's per-currency charge
|
|
70
|
+
* minimums (https://docs.stripe.com/currencies#minimum-and-maximum-charge-amounts).
|
|
71
|
+
*/
|
|
72
|
+
function getDonationLimits(currency) {
|
|
73
|
+
const [firstPreset] = exports.DONATION_PRESETS[currency.toUpperCase()] ?? exports.DONATION_PRESETS.USD;
|
|
74
|
+
return { min: firstPreset / 5, max: firstPreset * 20000 };
|
|
75
|
+
}
|
|
76
|
+
/** Validates that a custom amount string is within `currency`'s limits. */
|
|
77
|
+
function isValidDonationAmount(amount, currency) {
|
|
78
|
+
const parsed = parseFloat(amount);
|
|
79
|
+
const { min, max } = getDonationLimits(currency);
|
|
80
|
+
return !isNaN(parsed) && parsed >= min && parsed <= max;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validates an amount already in Stripe's units (from
|
|
84
|
+
* {@link toStripeAmount}), for the server that creates the PaymentIntent.
|
|
85
|
+
*/
|
|
86
|
+
function isValidStripeDonationAmount(amount, currency) {
|
|
87
|
+
const { min, max } = getDonationLimits(currency);
|
|
88
|
+
return (Number.isInteger(amount) &&
|
|
89
|
+
amount >= (0, exports.toStripeAmount)(min, currency) &&
|
|
90
|
+
amount <= (0, exports.toStripeAmount)(max, currency));
|
|
91
|
+
}
|
|
92
|
+
exports.DONATION_PRESETS = {
|
|
93
|
+
// North America
|
|
94
|
+
USD: [5, 10, 25], // US Dollar
|
|
95
|
+
CAD: [5, 10, 25], // Canadian Dollar
|
|
96
|
+
MXN: [50, 100, 250], // Mexican Peso
|
|
97
|
+
// Europe
|
|
98
|
+
EUR: [5, 10, 25], // Euro
|
|
99
|
+
GBP: [5, 10, 25], // British Pound
|
|
100
|
+
CHF: [5, 10, 25], // Swiss Franc
|
|
101
|
+
SEK: [50, 100, 250], // Swedish Krona
|
|
102
|
+
NOK: [50, 100, 250], // Norwegian Krone
|
|
103
|
+
DKK: [50, 100, 250], // Danish Krone
|
|
104
|
+
PLN: [20, 50, 100], // Polish Zloty
|
|
105
|
+
CZK: [100, 250, 500], // Czech Koruna
|
|
106
|
+
HUF: [1000, 2000, 5000], // Hungarian Forint
|
|
107
|
+
RON: [25, 50, 100], // Romanian Leu
|
|
108
|
+
// Asia Pacific
|
|
109
|
+
JPY: [500, 1000, 2500], // Japanese Yen (zero-decimal)
|
|
110
|
+
CNY: [35, 70, 175], // Chinese Yuan
|
|
111
|
+
HKD: [40, 80, 200], // Hong Kong Dollar
|
|
112
|
+
SGD: [7, 15, 35], // Singapore Dollar
|
|
113
|
+
AUD: [5, 10, 25], // Australian Dollar
|
|
114
|
+
NZD: [8, 15, 30], // New Zealand Dollar
|
|
115
|
+
KRW: [5000, 10000, 25000], // South Korean Won (zero-decimal)
|
|
116
|
+
INR: [100, 250, 500], // Indian Rupee
|
|
117
|
+
IDR: [25000, 50000, 150000], // Indonesian Rupiah
|
|
118
|
+
MYR: [20, 45, 110], // Malaysian Ringgit
|
|
119
|
+
THB: [150, 350, 850], // Thai Baht
|
|
120
|
+
PHP: [250, 500, 1250], // Philippine Peso
|
|
121
|
+
VND: [100000, 250000], // Vietnamese Dong (zero-decimal)
|
|
122
|
+
TWD: [150, 300, 750], // Taiwan Dollar
|
|
123
|
+
// Middle East & Africa
|
|
124
|
+
AED: [20, 35, 90], // UAE Dirham
|
|
125
|
+
SAR: [20, 40, 95], // Saudi Riyal
|
|
126
|
+
ILS: [20, 35, 90], // Israeli Shekel
|
|
127
|
+
ZAR: [90, 180, 450], // South African Rand
|
|
128
|
+
NGN: [2000, 5000], // Nigerian Naira
|
|
129
|
+
// Latin America
|
|
130
|
+
BRL: [25, 50, 125], // Brazilian Real
|
|
131
|
+
ARS: [1000, 2500, 5000], // Argentine Peso
|
|
132
|
+
CLP: [2500, 5000, 12500], // Chilean Peso (zero-decimal)
|
|
133
|
+
COP: [10000, 25000, 50000], // Colombian Peso
|
|
134
|
+
PEN: [20, 40, 100], // Peruvian Sol
|
|
135
|
+
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contents of a concat demuxer list file, one `file '<path>'` line per
|
|
3
|
+
* input. Single quotes in a path are escaped the way the demuxer expects
|
|
4
|
+
* (`'\''`); unescaped, they would end the path early.
|
|
5
|
+
*/
|
|
6
|
+
export declare function concatListFile(files: string[]): string;
|
|
1
7
|
/** Concatenate audio files using ffmpeg's concat demuxer. */
|
|
2
8
|
export declare function concatAudio(files: string[], output: string): Promise<void>;
|
|
3
9
|
/** Generate a silence MP3 of the given duration in seconds. */
|
|
@@ -11,6 +17,11 @@ export declare function extractAudioSegment({ endTime, input, output, startTime,
|
|
|
11
17
|
}): Promise<void>;
|
|
12
18
|
/** Extract the audio track from a video file and re-encode to MP3. */
|
|
13
19
|
export declare function extractVideoAudio(input: string, output: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Where {@link normalizeAudio} writes: a sibling `.normalized.mp3`, whatever
|
|
22
|
+
* the input's extension, so the output never overwrites the input.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizedPath(input: string): string;
|
|
14
25
|
/**
|
|
15
26
|
* Normalize audio to EBU R128 standard using ffmpeg-normalize. Returns the path
|
|
16
27
|
* to the normalized file. Skips if already exists.
|
|
@@ -90,6 +101,16 @@ export declare function compressVideo({ input, output, audio, }: {
|
|
|
90
101
|
output: string;
|
|
91
102
|
audio?: string;
|
|
92
103
|
}): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* The filter graph for {@link mixAudioWithMusic}. Expects narration as input 0
|
|
106
|
+
* and music as input 1: narration plays at full volume, music at 10% trimmed
|
|
107
|
+
* to the shorter of the two, and the mix is padded to `targetDuration`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function musicMixFilter({ narrationDuration, musicDuration, targetDuration, }: {
|
|
110
|
+
narrationDuration: number;
|
|
111
|
+
musicDuration: number;
|
|
112
|
+
targetDuration: number;
|
|
113
|
+
}): string;
|
|
93
114
|
/**
|
|
94
115
|
* Mix narration audio with background music at a fixed volume ratio. Music is
|
|
95
116
|
* trimmed to the shorter of the two inputs; if `targetDuration` is provided,
|
package/dist/functions/ffmpeg.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VIDEO_REMOTION_OVERRIDE_ARGS = exports.VIDEO_AUDIO_ENCODE_ARGS = exports.VIDEO_ENCODE_ARGS = exports.VIDEO_ENCODE_SETTINGS = void 0;
|
|
4
|
+
exports.concatListFile = concatListFile;
|
|
4
5
|
exports.concatAudio = concatAudio;
|
|
5
6
|
exports.generateSilence = generateSilence;
|
|
6
7
|
exports.extractAudioSegment = extractAudioSegment;
|
|
7
8
|
exports.extractVideoAudio = extractVideoAudio;
|
|
9
|
+
exports.normalizedPath = normalizedPath;
|
|
8
10
|
exports.normalizeAudio = normalizeAudio;
|
|
9
11
|
exports.compressVideo = compressVideo;
|
|
12
|
+
exports.musicMixFilter = musicMixFilter;
|
|
10
13
|
exports.mixAudioWithMusic = mixAudioWithMusic;
|
|
11
14
|
exports.getDurationFromFile = getDurationFromFile;
|
|
12
15
|
const child_process_1 = require("child_process");
|
|
@@ -35,11 +38,19 @@ const BITRATE = ['-b:a', '64k'];
|
|
|
35
38
|
const SAMPLE_RATE = ['-ar', '22050'];
|
|
36
39
|
/** Audio channels: stereo (2 channels). */
|
|
37
40
|
const CHANNELS = ['-ac', '2'];
|
|
41
|
+
/**
|
|
42
|
+
* The contents of a concat demuxer list file, one `file '<path>'` line per
|
|
43
|
+
* input. Single quotes in a path are escaped the way the demuxer expects
|
|
44
|
+
* (`'\''`); unescaped, they would end the path early.
|
|
45
|
+
*/
|
|
46
|
+
function concatListFile(files) {
|
|
47
|
+
return files.map((f) => `file '${f.replace(/'/g, "'\\''")}'`).join('\n');
|
|
48
|
+
}
|
|
38
49
|
/** Concatenate audio files using ffmpeg's concat demuxer. */
|
|
39
50
|
async function concatAudio(files, output) {
|
|
40
51
|
const dir = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), 'ffconcat-'));
|
|
41
52
|
const listFile = (0, path_1.join)(dir, 'list.txt');
|
|
42
|
-
await (0, promises_1.writeFile)(listFile, files
|
|
53
|
+
await (0, promises_1.writeFile)(listFile, concatListFile(files));
|
|
43
54
|
await exec('ffmpeg', [
|
|
44
55
|
// Use the concat demuxer to join multiple input files
|
|
45
56
|
'-f',
|
|
@@ -131,12 +142,19 @@ async function extractVideoAudio(input, output) {
|
|
|
131
142
|
output,
|
|
132
143
|
]);
|
|
133
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Where {@link normalizeAudio} writes: a sibling `.normalized.mp3`, whatever
|
|
147
|
+
* the input's extension, so the output never overwrites the input.
|
|
148
|
+
*/
|
|
149
|
+
function normalizedPath(input) {
|
|
150
|
+
return input.replace(/\.[^./]+$/, '') + '.normalized.mp3';
|
|
151
|
+
}
|
|
134
152
|
/**
|
|
135
153
|
* Normalize audio to EBU R128 standard using ffmpeg-normalize. Returns the path
|
|
136
154
|
* to the normalized file. Skips if already exists.
|
|
137
155
|
*/
|
|
138
156
|
async function normalizeAudio(input) {
|
|
139
|
-
const output = input
|
|
157
|
+
const output = normalizedPath(input);
|
|
140
158
|
if ((0, fs_1.existsSync)(output))
|
|
141
159
|
return output;
|
|
142
160
|
await exec('ffmpeg-normalize', [
|
|
@@ -266,15 +284,13 @@ async function compressVideo({ input, output, audio, }) {
|
|
|
266
284
|
await exec('ffmpeg', args);
|
|
267
285
|
}
|
|
268
286
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* the
|
|
287
|
+
* The filter graph for {@link mixAudioWithMusic}. Expects narration as input 0
|
|
288
|
+
* and music as input 1: narration plays at full volume, music at 10% trimmed
|
|
289
|
+
* to the shorter of the two, and the mix is padded to `targetDuration`.
|
|
272
290
|
*/
|
|
273
|
-
|
|
274
|
-
const narrationDuration = await getDurationFromFile(narration);
|
|
275
|
-
const musicDuration = await getDurationFromFile(music);
|
|
291
|
+
function musicMixFilter({ narrationDuration, musicDuration, targetDuration, }) {
|
|
276
292
|
const minDuration = Math.min(narrationDuration, musicDuration);
|
|
277
|
-
|
|
293
|
+
return [
|
|
278
294
|
`[0:a]volume=1.0[a1]`,
|
|
279
295
|
`[1:a]atrim=0:${minDuration},volume=0.1[a2]`,
|
|
280
296
|
/**
|
|
@@ -283,6 +299,18 @@ async function mixAudioWithMusic({ music, narration, output, targetDuration, })
|
|
|
283
299
|
*/
|
|
284
300
|
`[a1][a2]amix=inputs=2:duration=first:dropout_transition=0:normalize=0,apad=whole_dur=${targetDuration}`,
|
|
285
301
|
].join(';');
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Mix narration audio with background music at a fixed volume ratio. Music is
|
|
305
|
+
* trimmed to the shorter of the two inputs; if `targetDuration` is provided,
|
|
306
|
+
* the output is padded with silence to match exactly.
|
|
307
|
+
*/
|
|
308
|
+
async function mixAudioWithMusic({ music, narration, output, targetDuration, }) {
|
|
309
|
+
const filterComplex = musicMixFilter({
|
|
310
|
+
narrationDuration: await getDurationFromFile(narration),
|
|
311
|
+
musicDuration: await getDurationFromFile(music),
|
|
312
|
+
targetDuration,
|
|
313
|
+
});
|
|
286
314
|
await exec('ffmpeg', [
|
|
287
315
|
'-i',
|
|
288
316
|
narration,
|
|
@@ -5,11 +5,11 @@ export declare function getLanguageInfo(languageId: LanguageId, options?: {
|
|
|
5
5
|
includeInProgressDmcSets?: boolean;
|
|
6
6
|
}): LanguageInfo;
|
|
7
7
|
export declare function getInitialAppInterfaceLanguage(userPhoneLanguages: Array<string | null>): Language['languageId'];
|
|
8
|
-
export declare function isLanguageAvailable(status: Language['status'], isDev?: boolean): boolean;
|
|
8
|
+
export declare function isLanguageAvailable(status: Pick<Language['status'], 'dbs'>, isDev?: boolean): boolean;
|
|
9
9
|
export declare function numerals(text: string, scriptName: string): string;
|
|
10
10
|
/**
|
|
11
11
|
* These values are not the name of the font necessarily, but the identifier of
|
|
12
12
|
* the font which can be found by inspecting the font in an app like Font Book.
|
|
13
13
|
*/
|
|
14
14
|
export declare function getAppTranslations(appLanguageId: LanguageId): TranslationsApp[string];
|
|
15
|
-
export declare function getMeetTranslations(meetLanguageInfo: LanguageInfo): MeetTranslations;
|
|
15
|
+
export declare function getMeetTranslations(meetLanguageInfo: Pick<LanguageInfo, 'contentLanguages'>): MeetTranslations;
|
|
@@ -6,8 +6,6 @@ exports.isLanguageAvailable = isLanguageAvailable;
|
|
|
6
6
|
exports.numerals = numerals;
|
|
7
7
|
exports.getAppTranslations = getAppTranslations;
|
|
8
8
|
exports.getMeetTranslations = getMeetTranslations;
|
|
9
|
-
const bibleAudios_1 = require("../data/bibleAudios");
|
|
10
|
-
const bibleTexts_1 = require("../data/bibleTexts");
|
|
11
9
|
const curriculumFoundations_1 = require("../data/curriculumFoundations");
|
|
12
10
|
const curriculumOnboarding_1 = require("../data/curriculumOnboarding");
|
|
13
11
|
const curriculumQuestions_1 = require("../data/curriculumQuestions");
|
|
@@ -25,48 +23,28 @@ const translationsQuestion_1 = require("../data/translationsQuestion");
|
|
|
25
23
|
const translationsSet_1 = require("../data/translationsSet");
|
|
26
24
|
const translationsSpokenQuestion_1 = require("../data/translationsSpokenQuestion");
|
|
27
25
|
const bibles_1 = require("./bibles");
|
|
26
|
+
/**
|
|
27
|
+
* What an unknown language, or a part of one that can't be resolved, falls back
|
|
28
|
+
* to: English, matching the English content languages. English always resolves
|
|
29
|
+
* (`languages.test.ts` checks it), so this never recurses more than once, and
|
|
30
|
+
* if it didn't, failing loudly beats a half-resolved language. Not cached, so a
|
|
31
|
+
* test that swaps the data gets a fresh result.
|
|
32
|
+
*/
|
|
33
|
+
function getEnglishFallback(languageId) {
|
|
34
|
+
if (languageId === 'eng')
|
|
35
|
+
throw new Error("English can't be resolved, so there's nothing to fall back to");
|
|
36
|
+
return getLanguageInfo('eng');
|
|
37
|
+
}
|
|
28
38
|
function getLanguageInfo(languageId, options) {
|
|
29
|
-
const defaultReturn = {
|
|
30
|
-
...languages_1.languages[0],
|
|
31
|
-
setIds: [...curriculumFoundations_1.curriculumFoundations[0].sets],
|
|
32
|
-
questionSets: curriculumQuestions_1.curriculumQuestions[0].questionSets,
|
|
33
|
-
bible: {
|
|
34
|
-
...bibleTexts_1.bibleTexts[0],
|
|
35
|
-
...bibleAudios_1.bibleAudios[0],
|
|
36
|
-
availableTextChapters: [],
|
|
37
|
-
availableAudioChapters: [],
|
|
38
|
-
availableTimingsChapters: [],
|
|
39
|
-
availableChapters: [],
|
|
40
|
-
language: languages_1.languages[0],
|
|
41
|
-
fullCopyright: '',
|
|
42
|
-
bookNames: {},
|
|
43
|
-
script: scripts_1.scripts[0],
|
|
44
|
-
},
|
|
45
|
-
bibleFallbacks: [],
|
|
46
|
-
contentLanguages: {
|
|
47
|
-
ftbs: 'eng',
|
|
48
|
-
intros: 'eng',
|
|
49
|
-
titles: 'eng',
|
|
50
|
-
trainingVideos: 'eng',
|
|
51
|
-
questionAudio: 'eng',
|
|
52
|
-
questionText: 'eng',
|
|
53
|
-
onboardingPassages: 'eng',
|
|
54
|
-
},
|
|
55
|
-
script: scripts_1.scripts[0],
|
|
56
|
-
lessonPauses: lessonPauses_1.lessonPauses,
|
|
57
|
-
allBibleIds: [],
|
|
58
|
-
onboardingPassageIds: curriculumOnboarding_1.curriculumOnboarding[0].passages,
|
|
59
|
-
status: { ...languages_1.languages[0].status, dmCourse: 'notStarted' },
|
|
60
|
-
};
|
|
61
39
|
const language = languages_1.languages.find((language) => language.languageId === languageId);
|
|
62
40
|
if (!language) {
|
|
63
41
|
console.error(`Can't find language with id ${languageId}`);
|
|
64
|
-
return
|
|
42
|
+
return getEnglishFallback(languageId);
|
|
65
43
|
}
|
|
66
44
|
const script = scripts_1.scripts.find((s) => s.name === language.script);
|
|
67
45
|
if (!script) {
|
|
68
46
|
console.error(`Can't find script with name ${language.script}`);
|
|
69
|
-
return
|
|
47
|
+
return getEnglishFallback(languageId);
|
|
70
48
|
}
|
|
71
49
|
const questionsCurriculum = curriculumQuestions_1.curriculumQuestions.find((questionCurriculum) => questionCurriculum.curriculumId === language.curriculum.questions);
|
|
72
50
|
const foundationsCurriculum = curriculumFoundations_1.curriculumFoundations.find((foundationsCurriculum) => foundationsCurriculum.curriculumId === language.curriculum.foundations);
|
|
@@ -110,12 +88,12 @@ function getLanguageInfo(languageId, options) {
|
|
|
110
88
|
const toReturn = {
|
|
111
89
|
...language,
|
|
112
90
|
setIds: setIds,
|
|
113
|
-
questionSets: questionsCurriculum?.questionSets ??
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
91
|
+
questionSets: questionsCurriculum?.questionSets ??
|
|
92
|
+
getEnglishFallback(languageId).questionSets,
|
|
93
|
+
bible: (language.bible && (0, bibles_1.getBibleInfo)(language.bible)) ||
|
|
94
|
+
getEnglishFallback(languageId).bible,
|
|
117
95
|
bibleFallbacks: (language.bibleFallbacks ?? [])
|
|
118
|
-
.map(bibles_1.getBibleInfo)
|
|
96
|
+
.map((id) => (0, bibles_1.getBibleInfo)(id))
|
|
119
97
|
.filter((b) => b != null),
|
|
120
98
|
contentLanguages: {
|
|
121
99
|
trainingVideos: language.bridgeLanguages?.trainingVideos ?? language.languageId,
|
|
@@ -129,7 +107,8 @@ function getLanguageInfo(languageId, options) {
|
|
|
129
107
|
script,
|
|
130
108
|
lessonPauses: lessonPauses_1.lessonPauses,
|
|
131
109
|
allBibleIds: (0, bibles_1.getAllBibleIds)(language),
|
|
132
|
-
onboardingPassageIds: onboardingCurriculum?.passages ??
|
|
110
|
+
onboardingPassageIds: onboardingCurriculum?.passages ??
|
|
111
|
+
getEnglishFallback(languageId).onboardingPassageIds,
|
|
133
112
|
status: { ...language.status, dmCourse: dmCourseStatus },
|
|
134
113
|
};
|
|
135
114
|
return toReturn;
|
|
@@ -138,15 +117,18 @@ function getInitialAppInterfaceLanguage(userPhoneLanguages) {
|
|
|
138
117
|
for (const userPhoneLanguage of userPhoneLanguages) {
|
|
139
118
|
if (!userPhoneLanguage)
|
|
140
119
|
continue;
|
|
120
|
+
// The language subtag of a locale like `en-US`. Not just the first two
|
|
121
|
+
// letters, since Filipino is `fil`.
|
|
122
|
+
const languageCode = userPhoneLanguage.split(/[-_]/)[0];
|
|
141
123
|
for (const phoneLanguage of phoneLanguages_1.phoneLanguages) {
|
|
142
124
|
// Step 1: find the matching phone language.
|
|
143
|
-
if (phoneLanguage.twoLetterId !==
|
|
125
|
+
if (phoneLanguage.twoLetterId !== languageCode)
|
|
144
126
|
continue;
|
|
145
127
|
// Step 2: search through alternatives to find the best option.
|
|
146
128
|
for (const alternative of phoneLanguage.alternatives) {
|
|
147
129
|
// If the alternative is a valid waha app interface language, we've
|
|
148
130
|
// found a match.
|
|
149
|
-
if (languages_1.languages.some((wahaLanguage) => wahaLanguage.appInterface
|
|
131
|
+
if (languages_1.languages.some((wahaLanguage) => wahaLanguage.appInterface &&
|
|
150
132
|
isLanguageAvailable(wahaLanguage.status) &&
|
|
151
133
|
wahaLanguage.languageId === alternative.threeLetterId))
|
|
152
134
|
return alternative.threeLetterId;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BibleChapter } from '../types/bibleChapters';
|
|
2
2
|
import { ScripturePassage } from '../types/scripturePassages';
|
|
3
|
-
import { DbsInfo, EnrichedSection, LessonInfo, Section
|
|
3
|
+
import { DbsInfo, EnrichedSection, LessonInfo, Section } from '../types/sets';
|
|
4
4
|
export { parseVerseRange } from './bibleBooks';
|
|
5
5
|
export type { VerseRangeInfo } from './bibleBooks';
|
|
6
6
|
type GetChapterUrlParams = {
|
|
@@ -62,7 +62,7 @@ interface GetScripturePassageParams {
|
|
|
62
62
|
export declare function getScripturePassage({ bibleId, passageId, getChapter, noTextAvailableString, }: GetScripturePassageParams): Promise<ScripturePassage | undefined>;
|
|
63
63
|
/** Gets all scripture passages for a lesson. */
|
|
64
64
|
export declare function getLessonScripture({ getChapter, lessonInfo, noTextAvailableString, }: {
|
|
65
|
-
lessonInfo: LessonInfo
|
|
65
|
+
lessonInfo: Pick<LessonInfo, 'sections'>;
|
|
66
66
|
getChapter: (params: GetChapterParams) => Promise<BibleChapter | undefined>;
|
|
67
67
|
noTextAvailableString: string;
|
|
68
68
|
}): Promise<Array<ScripturePassage | undefined>>;
|
|
@@ -75,17 +75,21 @@ export declare function getLessonScripture({ getChapter, lessonInfo, noTextAvail
|
|
|
75
75
|
* @returns The superscript string representation of the verse number.
|
|
76
76
|
*/
|
|
77
77
|
export declare function verseToSuperscript(num: string | undefined): string;
|
|
78
|
+
/** The part of a lesson's pauses that an FTB clip's duration depends on. */
|
|
79
|
+
export type FtbPauses = {
|
|
80
|
+
lessonPauses: Pick<DbsInfo['lessonPauses'], 'afterFtb'>;
|
|
81
|
+
};
|
|
78
82
|
/**
|
|
79
83
|
* Returns the FTB audio duration + afterFtb pause for a section, or 0 if the
|
|
80
84
|
* section has no FTB.
|
|
81
85
|
*/
|
|
82
|
-
export declare function getFtbDuration(section: Section, lessonInfo:
|
|
86
|
+
export declare function getFtbDuration(section: Pick<Section, 'ftbPath' | 'ftbDuration'>, lessonInfo: FtbPauses): number;
|
|
83
87
|
/**
|
|
84
88
|
* Fills in `length` (for verse-timed story sections) and `startTime` (for all
|
|
85
89
|
* sections) by accumulating forward through the lesson. After this, every
|
|
86
90
|
* section has both fields set.
|
|
87
91
|
*/
|
|
88
|
-
export declare function enrichSections(lessonInfo:
|
|
92
|
+
export declare function enrichSections(lessonInfo: Pick<LessonInfo, 'sections' | 'mediaDuration'> & FtbPauses, scripture: Array<ScripturePassage | undefined>): EnrichedSection[];
|
|
89
93
|
/**
|
|
90
94
|
* Converts verse timings from chapter-relative to lesson-relative using each
|
|
91
95
|
* section's `startTime` (set by {@link enrichSections}).
|
|
@@ -93,5 +97,5 @@ export declare function enrichSections(lessonInfo: DbsInfo | VideoInfo, scriptur
|
|
|
93
97
|
export declare function normalizeVerseTimings({ enrichedSections, scripture, lessonInfo, }: {
|
|
94
98
|
enrichedSections: EnrichedSection[];
|
|
95
99
|
scripture: Array<ScripturePassage | undefined>;
|
|
96
|
-
lessonInfo:
|
|
100
|
+
lessonInfo: FtbPauses;
|
|
97
101
|
}): Array<ScripturePassage | undefined>;
|
|
@@ -10,13 +10,11 @@ exports.verseToSuperscript = verseToSuperscript;
|
|
|
10
10
|
exports.getFtbDuration = getFtbDuration;
|
|
11
11
|
exports.enrichSections = enrichSections;
|
|
12
12
|
exports.normalizeVerseTimings = normalizeVerseTimings;
|
|
13
|
-
const bibleStatuses_1 = require("../data/bibleStatuses");
|
|
14
13
|
const firebase_1 = require("../data/firebase");
|
|
15
14
|
const languages_1 = require("../functions/languages");
|
|
16
15
|
const sets_1 = require("../types/sets");
|
|
17
16
|
const bibleBooks_1 = require("./bibleBooks");
|
|
18
17
|
const bibles_1 = require("./bibles");
|
|
19
|
-
const media_1 = require("./media");
|
|
20
18
|
const utils_1 = require("./utils");
|
|
21
19
|
var bibleBooks_2 = require("./bibleBooks");
|
|
22
20
|
Object.defineProperty(exports, "parseVerseRange", { enumerable: true, get: function () { return bibleBooks_2.parseVerseRange; } });
|
|
@@ -215,11 +213,10 @@ function verseToSuperscript(num) {
|
|
|
215
213
|
function getFtbDuration(section, lessonInfo) {
|
|
216
214
|
if (!section.ftbPath)
|
|
217
215
|
return 0;
|
|
218
|
-
|
|
219
|
-
if (ftbDuration == null) {
|
|
216
|
+
if (section.ftbDuration == null) {
|
|
220
217
|
console.warn(`FTB duration not found for ${(0, utils_1.basename)(section.ftbPath)}`);
|
|
221
218
|
}
|
|
222
|
-
return (ftbDuration ?? 0) + lessonInfo.lessonPauses.afterFtb;
|
|
219
|
+
return (section.ftbDuration ?? 0) + lessonInfo.lessonPauses.afterFtb;
|
|
223
220
|
}
|
|
224
221
|
/**
|
|
225
222
|
* Fills in `length` (for verse-timed story sections) and `startTime` (for all
|
|
@@ -245,12 +242,12 @@ function enrichSections(lessonInfo, scripture) {
|
|
|
245
242
|
const ignoreTimings = isStory && priorStoryLengthUnknown ? true : undefined;
|
|
246
243
|
const startTime = currentTime;
|
|
247
244
|
if (!isStory) {
|
|
248
|
-
length =
|
|
245
|
+
length = section.duration;
|
|
249
246
|
}
|
|
250
247
|
else {
|
|
251
248
|
currentTime += getFtbDuration(section, lessonInfo);
|
|
252
|
-
|
|
253
|
-
length =
|
|
249
|
+
// Set only for custom passage recordings.
|
|
250
|
+
length = section.duration;
|
|
254
251
|
if (!length) {
|
|
255
252
|
const passage = scripture[storySections.indexOf(section)];
|
|
256
253
|
if (passage) {
|
|
@@ -275,8 +272,7 @@ function enrichSections(lessonInfo, scripture) {
|
|
|
275
272
|
* lesson file, so they are always correct even if story durations are
|
|
276
273
|
* slightly off.
|
|
277
274
|
*/
|
|
278
|
-
const
|
|
279
|
-
const totalDuration = (0, media_1.getCachedDuration)(path);
|
|
275
|
+
const totalDuration = lessonInfo.mediaDuration;
|
|
280
276
|
if (totalDuration) {
|
|
281
277
|
let timeFromEnd = totalDuration;
|
|
282
278
|
const applicationSections = enriched.filter((s) => s.chapter === sets_1.Chapter.APPLICATION);
|
package/dist/functions/sets.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { type LanguageId } from '../data/languages/languages.zod';
|
|
2
|
-
import {
|
|
2
|
+
import { type FtbPauses } from '../functions/scripturePassages';
|
|
3
3
|
import type { AppTranslations, LanguageInfo, MeetTranslations } from '../types/languages';
|
|
4
|
-
import { DbsInfo, EnrichedSection, Lesson, LessonInfo, SetInfo, VideoInfo, type Section } from '../types/sets';
|
|
4
|
+
import { DbsInfo, EnrichedSection, EqInfo, Lesson, LessonInfo, SetInfo, VideoInfo, type Section } from '../types/sets';
|
|
5
|
+
/** The translations set and lesson assembly read. */
|
|
6
|
+
export type LessonTranslations = Pick<MeetTranslations, 'sets' | 'questions' | 'questionsSpoken'> & {
|
|
7
|
+
introductions: Pick<MeetTranslations['introductions'], 'introduction'> & {
|
|
8
|
+
introductions: Partial<MeetTranslations['introductions']['introductions']>;
|
|
9
|
+
};
|
|
10
|
+
} & Pick<AppTranslations, 'foundations' | 'topics' | 'training' | 'fellowship' | 'application' | 'training_video'>;
|
|
11
|
+
/** The translations {@link getSetInfo} reads. */
|
|
12
|
+
export type SetInfoTranslations = Pick<LessonTranslations, 'sets' | 'foundations' | 'topics' | 'training'>;
|
|
5
13
|
export declare function getSetInfo({ setId, languageId, setIds, t, }: {
|
|
6
14
|
setId: string;
|
|
7
15
|
languageId: LanguageId;
|
|
8
|
-
t:
|
|
16
|
+
t: SetInfoTranslations;
|
|
9
17
|
setIds: string[];
|
|
10
18
|
}): SetInfo | undefined;
|
|
11
19
|
/** Assembles all of the audio sections for a lesson. */
|
|
@@ -13,7 +21,7 @@ export declare function assembleLessonSections({ languageInfo, setInfo, lesson,
|
|
|
13
21
|
languageInfo: LanguageInfo;
|
|
14
22
|
setInfo: SetInfo;
|
|
15
23
|
lesson: Lesson;
|
|
16
|
-
t:
|
|
24
|
+
t: LessonTranslations;
|
|
17
25
|
useSpokenQuestions: boolean | undefined;
|
|
18
26
|
}): {
|
|
19
27
|
sections: Section[];
|
|
@@ -24,11 +32,11 @@ export declare function getLessonInfo({ lessonId, languageInfo, setInfo, t, useS
|
|
|
24
32
|
lessonId: Lesson['lessonId'] | undefined;
|
|
25
33
|
languageInfo: LanguageInfo;
|
|
26
34
|
setInfo?: SetInfo;
|
|
27
|
-
t:
|
|
35
|
+
t: LessonTranslations;
|
|
28
36
|
useSpokenQuestions?: boolean;
|
|
29
37
|
}): LessonInfo | undefined;
|
|
30
38
|
/** Determines whether a lesson should be visible in Waha. */
|
|
31
|
-
export declare function shouldShowLesson(lessonInfo:
|
|
39
|
+
export declare function shouldShowLesson(lessonInfo: Pick<DbsInfo | EqInfo, 'type' | 'lessonId' | 'lessonTitle' | 'mediaDuration'> | Pick<VideoInfo, 'type' | 'lessonId' | 'lessonTitle' | 'mediaDuration' | 'videoPath'> | undefined): boolean;
|
|
32
40
|
/**
|
|
33
41
|
* Validates that the computed lesson duration (from section lengths) matches
|
|
34
42
|
* the actual audio file duration. Sections must be enriched (all have `length`)
|
|
@@ -38,5 +46,5 @@ export declare function shouldShowLesson(lessonInfo: LessonInfo | undefined): bo
|
|
|
38
46
|
*/
|
|
39
47
|
export declare function getExpectedLessonDuration({ enrichedSections, lessonInfo, }: {
|
|
40
48
|
enrichedSections: EnrichedSection[];
|
|
41
|
-
lessonInfo:
|
|
49
|
+
lessonInfo: FtbPauses;
|
|
42
50
|
}): number;
|