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.
Files changed (52) hide show
  1. package/README.md +12 -12
  2. package/dist/data/bibleAudios/bibleAudios.json +12 -0
  3. package/dist/data/bibleStatuses/bibleStatuses.json +852 -0
  4. package/dist/data/bibleTexts/bibleTexts.json +11 -0
  5. package/dist/data/countriesAndLanguages/countriesAndLanguages.json +25 -20
  6. package/dist/data/countriesAndLanguages/countriesAndLanguages.schema.json +1 -1
  7. package/dist/data/countriesAndLanguages/countriesAndLanguages.zod.js +1 -1
  8. package/dist/data/languages/index.d.ts +1 -1
  9. package/dist/data/languages/languages.json +18 -11
  10. package/dist/data/languages/languages.schema.json +3 -1
  11. package/dist/data/languages/languages.zod.d.ts +1 -1
  12. package/dist/data/languages/languages.zod.js +6 -1
  13. package/dist/data/mediaDurations/mediaDurations.json +1090 -0
  14. package/dist/data/notification/index.d.ts +2 -2
  15. package/dist/data/notification/notification.schema.json +9 -3
  16. package/dist/data/notification/notification.zod.d.ts +3 -3
  17. package/dist/data/notification/notification.zod.js +4 -6
  18. package/dist/data/phoneLanguages/phoneLanguages.json +32 -0
  19. package/dist/data/releaseNotes/releaseNotes.json +9 -2
  20. package/dist/data/translationsApp/translationsApp.json +878 -8
  21. package/dist/data/translationsFtb/translationsFtb.json +136 -0
  22. package/dist/data/translationsIntroduction/translationsIntroduction.json +31 -31
  23. package/dist/data/translationsQuestion/translationsQuestion.json +64 -0
  24. package/dist/data/translationsSet/translationsSet.json +1661 -83
  25. package/dist/data/translationsSpokenQuestion/translationsSpokenQuestion.json +64 -0
  26. package/dist/functions/activeCampaign.d.ts +6 -0
  27. package/dist/functions/activeCampaign.js +10 -1
  28. package/dist/functions/bibleBooks.d.ts +6 -1
  29. package/dist/functions/bibleBooks.js +6 -1
  30. package/dist/functions/bibles.d.ts +22 -8
  31. package/dist/functions/bibles.js +54 -120
  32. package/dist/functions/crowdin.d.ts +53 -4
  33. package/dist/functions/crowdin.js +55 -43
  34. package/dist/functions/donations.d.ts +40 -0
  35. package/dist/functions/donations.js +135 -0
  36. package/dist/functions/ffmpeg.d.ts +21 -0
  37. package/dist/functions/ffmpeg.js +37 -9
  38. package/dist/functions/languages.d.ts +2 -2
  39. package/dist/functions/languages.js +26 -44
  40. package/dist/functions/scripturePassages.d.ts +9 -5
  41. package/dist/functions/scripturePassages.js +6 -10
  42. package/dist/functions/sets.d.ts +15 -7
  43. package/dist/functions/sets.js +34 -28
  44. package/dist/functions/upload.d.ts +16 -6
  45. package/dist/functions/upload.js +15 -6
  46. package/dist/functions/utils.d.ts +0 -2
  47. package/dist/functions/utils.js +1 -9
  48. package/dist/types/logs.d.ts +2 -2
  49. package/dist/types/notifications.d.ts +3 -3
  50. package/dist/types/sets.d.ts +14 -0
  51. package/dist/types/users.d.ts +1 -1
  52. package/package.json +4 -4
@@ -75,6 +75,23 @@ function getSetTranslations({ setInfo, lessonId, languageId, t, }) {
75
75
  : undefined;
76
76
  }
77
77
  }
78
+ /**
79
+ * Fills in a section's own audio duration and its FTB clip's, so nothing
80
+ * downstream has to look them up. A story section's own duration only exists
81
+ * for custom passage recordings.
82
+ */
83
+ function resolveDurations(section) {
84
+ const [bibleId, passageId] = section.id.split(';');
85
+ return {
86
+ ...section,
87
+ duration: section.chapter === sets_2.Chapter.STORY
88
+ ? bibleStatuses_1.bibleStatuses[bibleId]?.customPassages?.[passageId]
89
+ : (0, media_1.getCachedDuration)(section.path),
90
+ ftbDuration: section.ftbPath
91
+ ? (0, media_1.getCachedDuration)(section.ftbPath)
92
+ : undefined,
93
+ };
94
+ }
78
95
  /** Assembles all of the audio sections for a lesson. */
79
96
  function assembleLessonSections({ languageInfo, setInfo, lesson, t, useSpokenQuestions, }) {
80
97
  const fellowshipQuestions = languageInfo.questionSets.find((questionSet) => questionSet.questionSetId === lesson.f)?.questions ?? [];
@@ -214,18 +231,14 @@ function assembleLessonSections({ languageInfo, setInfo, lesson, t, useSpokenQue
214
231
  ].join('.')),
215
232
  });
216
233
  });
234
+ const resolved = sections.map(resolveDurations);
235
+ const chapterDuration = (chapter) => resolved
236
+ .filter((s) => s.chapter === chapter)
237
+ .reduce((sum, section) => sum + (section.pauseBefore ?? 0) + (section.duration ?? 0), 0);
217
238
  return {
218
- sections,
219
- fellowshipDuration: sections
220
- .filter((s) => s.chapter === sets_2.Chapter.FELLOWSHIP)
221
- .reduce((sum, section) => sum +
222
- (section.pauseBefore ?? 0) +
223
- ((0, media_1.getCachedDuration)(section.path) ?? 0), 0),
224
- applicationDuration: sections
225
- .filter((s) => s.chapter === sets_2.Chapter.APPLICATION)
226
- .reduce((sum, section) => sum +
227
- (section.pauseBefore ?? 0) +
228
- ((0, media_1.getCachedDuration)(section.path) ?? 0), 0),
239
+ sections: resolved,
240
+ fellowshipDuration: chapterDuration(sets_2.Chapter.FELLOWSHIP),
241
+ applicationDuration: chapterDuration(sets_2.Chapter.APPLICATION),
229
242
  };
230
243
  }
231
244
  function getTrainingVideoPaths({ languageInfo, lessonId, }) {
@@ -296,10 +309,12 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
296
309
  ...languageInfo,
297
310
  };
298
311
  if (specialIds_1.specialIds.evaluationQuestionLessonIds.includes(lesson.lessonId)) {
312
+ const eqPath = (0, utils_1.join)(languageInfo.contentLanguages.intros, 'introductions', `${languageInfo.contentLanguages.intros}.eq.mp3`);
299
313
  return {
300
314
  type: 'eq',
301
315
  ...baseInfo,
302
- eqPath: (0, utils_1.join)(languageInfo.contentLanguages.intros, 'introductions', `${languageInfo.contentLanguages.intros}.eq.mp3`),
316
+ eqPath,
317
+ mediaDuration: (0, media_1.getCachedDuration)(eqPath),
303
318
  };
304
319
  }
305
320
  if (setInfo.setId === specialIds_1.specialIds.dmCourseSetId || languageId === 'ase') {
@@ -333,6 +348,7 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
333
348
  lessonSectionHeader: thisSetTranslations?.sectionHeaders?.[lessonId],
334
349
  lessonSectionBody: thisSetTranslations?.sectionBodies?.[lessonId],
335
350
  videoPath,
351
+ mediaDuration: (0, media_1.getCachedDuration)(videoPath),
336
352
  fullPath: lesson.s.length > 0 && languageId !== 'ase'
337
353
  ? videoFullAudioPath
338
354
  : undefined,
@@ -352,6 +368,7 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
352
368
  passagesString,
353
369
  fullPath,
354
370
  storyPath,
371
+ mediaDuration: (0, media_1.getCachedDuration)(fullPath),
355
372
  dbsCastPath: (0, utils_1.join)(languageId, `full_lessons${languageInfo.assetVersions?.cast ?? ''}`, setInfo.setId, [languageId, lessonId, '1080', 'mp4'].join('.')),
356
373
  };
357
374
  }
@@ -362,24 +379,13 @@ function shouldShowLesson(lessonInfo) {
362
379
  console.log('Missing lesson info or lessonTitle lesson:', lessonInfo?.lessonId);
363
380
  return false;
364
381
  }
365
- else if (lessonInfo.type === 'eq' &&
366
- (0, media_1.getCachedDuration)(lessonInfo.eqPath) === undefined) {
367
- console.log('Missing remote eq file for lesson:', lessonInfo.lessonId);
368
- return false;
369
- }
370
- else if (lessonInfo.type === 'dbs' &&
371
- (0, media_1.getCachedDuration)(lessonInfo.fullPath) === undefined) {
372
- console.log('Missing remote dbs file for lesson:', lessonInfo.lessonId);
373
- return false;
374
- }
375
- else if (lessonInfo.type === 'video' &&
376
- lessonInfo.videoPath &&
377
- (0, media_1.getCachedDuration)(lessonInfo.videoPath) === undefined) {
378
- console.log('Missing remote video for lesson:', lessonInfo.lessonId);
382
+ // Video-only lessons without a video file have nothing to check.
383
+ const hasMedia = lessonInfo.type !== 'video' || Boolean(lessonInfo.videoPath);
384
+ if (hasMedia && lessonInfo.mediaDuration === undefined) {
385
+ console.log(`Missing remote ${lessonInfo.type} file for lesson:`, lessonInfo.lessonId);
379
386
  return false;
380
387
  }
381
- else
382
- return true;
388
+ return true;
383
389
  }
384
390
  /**
385
391
  * Validates that the computed lesson duration (from section lengths) matches
@@ -17,6 +17,21 @@ export interface UploadBucket {
17
17
  }): Promise<unknown>;
18
18
  }
19
19
  export type UploadResult = 'uploaded' | 'skipped';
20
+ /** The content type to upload a file with, from its extension. */
21
+ export declare function contentTypeFor(path: string): string;
22
+ export interface UploadFileParams {
23
+ bucket: UploadBucket;
24
+ localPath: string;
25
+ remotePath: string;
26
+ overwrite?: boolean;
27
+ }
28
+ /**
29
+ * The metadata recording a file's duration, or undefined when the probe didn't
30
+ * return a usable one.
31
+ */
32
+ export declare function durationMetadata(duration: number): {
33
+ duration: string;
34
+ } | undefined;
20
35
  /**
21
36
  * Upload a local file to a Cloud Storage bucket. The content type is inferred
22
37
  * from the file extension via {@link CONTENT_TYPE_BY_EXTENSION}. Returns
@@ -26,9 +41,4 @@ export type UploadResult = 'uploaded' | 'skipped';
26
41
  * Sets a `duration` metadata field (in seconds) from ffprobe when probing
27
42
  * succeeds; metadata failures are swallowed since they're non-fatal.
28
43
  */
29
- export declare function uploadFile({ bucket, localPath, overwrite, remotePath, }: {
30
- bucket: UploadBucket;
31
- localPath: string;
32
- remotePath: string;
33
- overwrite?: boolean;
34
- }): Promise<UploadResult>;
44
+ export declare function uploadFile({ bucket, localPath, overwrite, remotePath, }: UploadFileParams): Promise<UploadResult>;
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.contentTypeFor = contentTypeFor;
4
+ exports.durationMetadata = durationMetadata;
3
5
  exports.uploadFile = uploadFile;
4
6
  const path_1 = require("path");
5
7
  const ffmpeg_1 = require("./ffmpeg");
@@ -7,6 +9,7 @@ const CONTENT_TYPE_BY_EXTENSION = {
7
9
  '.mp3': 'audio/mpeg',
8
10
  '.mp4': 'video/mp4',
9
11
  };
12
+ /** The content type to upload a file with, from its extension. */
10
13
  function contentTypeFor(path) {
11
14
  const ext = (0, path_1.extname)(path).toLowerCase();
12
15
  const contentType = CONTENT_TYPE_BY_EXTENSION[ext];
@@ -15,6 +18,15 @@ function contentTypeFor(path) {
15
18
  `Add it to CONTENT_TYPE_BY_EXTENSION in shared/functions/upload.ts.`);
16
19
  return contentType;
17
20
  }
21
+ /**
22
+ * The metadata recording a file's duration, or undefined when the probe didn't
23
+ * return a usable one.
24
+ */
25
+ function durationMetadata(duration) {
26
+ if (isNaN(duration) || duration <= 0)
27
+ return;
28
+ return { duration: duration.toString() };
29
+ }
18
30
  /**
19
31
  * Upload a local file to a Cloud Storage bucket. The content type is inferred
20
32
  * from the file extension via {@link CONTENT_TYPE_BY_EXTENSION}. Returns
@@ -35,12 +47,9 @@ async function uploadFile({ bucket, localPath, overwrite, remotePath, }) {
35
47
  public: true,
36
48
  });
37
49
  try {
38
- const duration = await (0, ffmpeg_1.getDurationFromFile)(localPath);
39
- if (!isNaN(duration) && duration > 0) {
40
- await bucket.file(remotePath).setMetadata({
41
- metadata: { duration: duration.toString() },
42
- });
43
- }
50
+ const metadata = durationMetadata(await (0, ffmpeg_1.getDurationFromFile)(localPath));
51
+ if (metadata)
52
+ await bucket.file(remotePath).setMetadata({ metadata });
44
53
  }
45
54
  catch {
46
55
  // Non-fatal — proceed even if setting duration metadata fails.
@@ -2,8 +2,6 @@ export declare const join: (...parts: string[]) => string;
2
2
  export declare const basename: (p: string) => string;
3
3
  export declare const first: (p: string) => string;
4
4
  export declare const dirname: (p: string) => string;
5
- export declare const extname: (p: string) => string;
6
- export declare const basenamenoext: (p: string) => string;
7
5
  /** Safely read `code`/`message` off an unknown thrown value. */
8
6
  export declare function getErrorInfo(e: unknown): {
9
7
  code?: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.basenamenoext = exports.extname = exports.dirname = exports.first = exports.basename = exports.join = void 0;
3
+ exports.dirname = exports.first = exports.basename = exports.join = void 0;
4
4
  exports.getErrorInfo = getErrorInfo;
5
5
  const join = (...parts) => parts.join('/').replace(/\/+/g, '/');
6
6
  exports.join = join;
@@ -10,14 +10,6 @@ const first = (p) => p.split('/')[0];
10
10
  exports.first = first;
11
11
  const dirname = (p) => p.substring(0, p.lastIndexOf('/'));
12
12
  exports.dirname = dirname;
13
- const extname = (p) => p.slice(p.lastIndexOf('.'));
14
- exports.extname = extname;
15
- const basenamenoext = (p) => {
16
- const base = (0, exports.basename)(p);
17
- const dotIndex = base.lastIndexOf('.');
18
- return dotIndex !== -1 ? base.slice(0, dotIndex) : base;
19
- };
20
- exports.basenamenoext = basenamenoext;
21
13
  /** Safely read `code`/`message` off an unknown thrown value. */
22
14
  function getErrorInfo(e) {
23
15
  const code = typeof e === 'object' &&
@@ -2,8 +2,8 @@ import z from 'zod';
2
2
  export declare const WahaShareContent: z.ZodEnum<{
3
3
  lesson: "lesson";
4
4
  note: "note";
5
- setLink: "setLink";
6
5
  lessonLink: "lessonLink";
6
+ setLink: "setLink";
7
7
  app: "app";
8
8
  passcode: "passcode";
9
9
  lessonAudio: "lessonAudio";
@@ -30,8 +30,8 @@ export declare const ShareLogEvent: z.ZodObject<{
30
30
  content: z.ZodEnum<{
31
31
  lesson: "lesson";
32
32
  note: "note";
33
- setLink: "setLink";
34
33
  lessonLink: "lessonLink";
34
+ setLink: "setLink";
35
35
  app: "app";
36
36
  passcode: "passcode";
37
37
  lessonAudio: "lessonAudio";
@@ -19,13 +19,13 @@ export declare const PushNotificationRecord: z.ZodObject<{
19
19
  actionUrl: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
20
20
  countriesToNotSendTo: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
21
  countriesToSendTo: z.ZodOptional<z.ZodArray<z.ZodString>>;
22
- faithJourney: z.ZodOptional<z.ZodEnum<{
22
+ faithJourney: z.ZodEnum<{
23
23
  all: "all";
24
24
  seekers: "seekers";
25
25
  believers: "believers";
26
- }>>;
26
+ }>;
27
27
  imageUrl: z.ZodOptional<z.ZodString>;
28
- meetLanguages: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>>;
28
+ meetLanguages: z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>;
29
29
  minCompletions: z.ZodOptional<z.ZodNumber>;
30
30
  pushBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
31
31
  pushTitle: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -22,6 +22,12 @@ export interface BaseInfo extends LanguageInfo {
22
22
  fellowshipDuration: number;
23
23
  /** Full duration in seconds of all of the lesson's application questions. */
24
24
  applicationDuration: number;
25
+ /**
26
+ * Duration in seconds of the lesson's main media file (`fullPath` for DBS,
27
+ * `videoPath` for video, `eqPath` for EQ). Undefined when the file isn't in
28
+ * `mediaDurations`, i.e. it hasn't been uploaded.
29
+ */
30
+ mediaDuration: number | undefined;
25
31
  }
26
32
  export interface Section {
27
33
  id: string;
@@ -38,6 +44,14 @@ export interface Section {
38
44
  */
39
45
  ftbPath?: string;
40
46
  path: string;
47
+ /**
48
+ * Duration in seconds of this section's own audio, resolved when the lesson
49
+ * is assembled. Story sections only have one when they're a custom passage
50
+ * recording; otherwise their length comes from verse timings.
51
+ */
52
+ duration?: number;
53
+ /** Duration in seconds of the `ftbPath` clip, when there is one. */
54
+ ftbDuration?: number;
41
55
  }
42
56
  export interface EnrichedSection extends Section {
43
57
  startTime: number;
@@ -245,8 +245,8 @@ export declare const LegacyWahaUser: z.ZodObject<{
245
245
  content: z.ZodEnum<{
246
246
  lesson: "lesson";
247
247
  note: "note";
248
- setLink: "setLink";
249
248
  lessonLink: "lessonLink";
249
+ setLink: "setLink";
250
250
  app: "app";
251
251
  passcode: "passcode";
252
252
  lessonAudio: "lessonAudio";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waha-shared",
3
- "version": "1.0.510",
3
+ "version": "1.0.511",
4
4
  "author": "Waha",
5
5
  "dependencies": {
6
6
  "@types/signale": "^1.4.7",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "description": "Shared assets for Waha's various apps",
15
15
  "devDependencies": {
16
- "@crowdin/crowdin-api-client": "^1.59.0",
16
+ "@crowdin/crowdin-api-client": "^1.59.1",
17
17
  "@types/react": "^19.3.0",
18
18
  "copyfiles": "^2.4.1",
19
19
  "react": "^19.3.0",
@@ -28,7 +28,7 @@
28
28
  "license": "MIT",
29
29
  "scripts": {
30
30
  "build": "npx tsc && npx copyfiles \"data/**/*.json\" dist",
31
- "update": "yarn build && REGISTRY_VERSION=$(npm view waha-shared version) && npm version $REGISTRY_VERSION --no-git-tag-version --allow-same-version && npm version patch --no-git-tag-version && git add . && (git commit -m 'chore: build waha-shared' || true) && bash -c 'npm publish --access public --registry https://registry.npmjs.org --//registry.npmjs.org/:_authToken=$(grep NPM_TOKEN= ../.env | cut -d \"=\" -f2-)'",
32
- "updateManual": "(npm whoami || npm login) && yarn build && REGISTRY_VERSION=$(npm view waha-shared version) && npm version $REGISTRY_VERSION --no-git-tag-version --allow-same-version && npm version patch --no-git-tag-version && git add dist package.json && (git commit -m 'chore: build waha-shared' || true) && npm publish"
31
+ "update": "npm run build && REGISTRY_VERSION=$(npm view waha-shared version) && npm version $REGISTRY_VERSION --no-git-tag-version --allow-same-version && npm version patch --no-git-tag-version && git add . && (git commit -m 'chore: build waha-shared' || true) && bash -c 'npm publish --access public --registry https://registry.npmjs.org --//registry.npmjs.org/:_authToken=$(grep NPM_TOKEN= ../.env | cut -d \"=\" -f2-)'",
32
+ "updateManual": "(npm whoami || npm login) && npm run build && REGISTRY_VERSION=$(npm view waha-shared version) && npm version $REGISTRY_VERSION --no-git-tag-version --allow-same-version && npm version patch --no-git-tag-version && git add dist package.json && (git commit -m 'chore: build waha-shared' || true) && npm publish"
33
33
  }
34
34
  }