botium-core 1.15.9 → 1.15.12

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 (85) hide show
  1. package/dist/botium-cjs.js +215 -55
  2. package/dist/botium-cjs.js.map +1 -1
  3. package/dist/botium-es.js +215 -54
  4. package/dist/botium-es.js.map +1 -1
  5. package/package.json +35 -40
  6. package/src/BotDriver.js +1 -1
  7. package/src/Events.js +1 -3
  8. package/src/containers/GridContainer.js +0 -4
  9. package/src/helpers/TranscriptUtils.js +66 -0
  10. package/src/scripting/Convo.js +9 -0
  11. package/src/scripting/ScriptingProvider.js +36 -5
  12. package/src/scripting/logichook/userinput/MediaInput.js +10 -2
  13. package/src/utils/boolean.js +39 -0
  14. package/test/compiler/compilercsv.spec.js +34 -0
  15. package/test/compiler/compilermarkdown.spec.js +11 -0
  16. package/test/compiler/compilertxt.spec.js +21 -0
  17. package/test/compiler/compilerxlsx.spec.js +27 -0
  18. package/test/compiler/precompilerjson.spec.js +1 -0
  19. package/test/compiler/precompilermarkdownrasa.spec.js +1 -0
  20. package/test/connectors/pluginconnectorcontainer.spec.js +1 -0
  21. package/test/connectors/simplerest.spec.js +3 -1
  22. package/test/convo/failure.spec.js +1 -0
  23. package/test/convo/fillAndApplyScriptingMemory.spec.js +56 -0
  24. package/test/convo/partialconvo.spec.js +3 -0
  25. package/test/convo/retry.spec.js +9 -0
  26. package/test/convo/retryasserter.spec.js +9 -0
  27. package/test/convo/transcript.spec.js +33 -0
  28. package/test/convo/tree.spec.js +5 -0
  29. package/test/driver/capabilities.spec.js +16 -0
  30. package/test/helpers/capabilitiesutils.spec.js +8 -0
  31. package/test/helpers/transcriptutils.spec.js +1 -0
  32. package/test/hooks/customhooks.spec.js +3 -0
  33. package/test/logichooks/hookfromsrc.spec.js +3 -0
  34. package/test/logichooks/textfromhook.spec.js +1 -0
  35. package/test/plugins/plugins.spec.js +3 -0
  36. package/test/scripting/asserters/buttonsAsserter.spec.js +15 -0
  37. package/test/scripting/asserters/cardsAsserter.spec.js +12 -0
  38. package/test/scripting/asserters/convoStepParameters.spec.js +11 -0
  39. package/test/scripting/asserters/entitiesAsserter.spec.js +1 -0
  40. package/test/scripting/asserters/entityValuesAsserter.spec.js +7 -0
  41. package/test/scripting/asserters/formsAsserter.spec.js +10 -0
  42. package/test/scripting/asserters/intentAsserter.spec.js +4 -0
  43. package/test/scripting/asserters/intentUniqueAsserter.spec.js +2 -0
  44. package/test/scripting/asserters/jsonpathAsserter.spec.js +25 -0
  45. package/test/scripting/asserters/mediaAsserter.spec.js +20 -0
  46. package/test/scripting/asserters/responseLengthAsserter.spec.js +4 -0
  47. package/test/scripting/asserters/textAsserter.spec.js +1 -0
  48. package/test/scripting/asserters/textContainsAllAsserter.spec.js +1 -0
  49. package/test/scripting/asserters/textContainsAnyAsserter.spec.js +1 -0
  50. package/test/scripting/asserters/textEqualsAsserter.spec.js +1 -0
  51. package/test/scripting/asserters/textRegexpAllAsserter.spec.js +1 -0
  52. package/test/scripting/asserters/textRegexpAnyAsserter.spec.js +1 -0
  53. package/test/scripting/asserters/textWildcardAllAsserter.spec.js +1 -0
  54. package/test/scripting/asserters/textWildcardAnyAsserter.spec.js +1 -0
  55. package/test/scripting/asserters/textWildcardExactAllAsserter.spec.js +1 -0
  56. package/test/scripting/asserters/textWildcardExactAnyAsserter.spec.js +1 -0
  57. package/test/scripting/asserters/werAsserter.spec.js +6 -0
  58. package/test/scripting/logichooks/customConditionalStepLogicHook.spec.js +2 -0
  59. package/test/scripting/logichooks/localvsglobal.spec.js +1 -0
  60. package/test/scripting/logichooks/pauseLogic.spec.js +4 -0
  61. package/test/scripting/logichooks/setClearScriptingMemory.spec.js +3 -0
  62. package/test/scripting/logichooks/updateCustom.spec.js +2 -0
  63. package/test/scripting/matching/matchingmode.spec.js +48 -0
  64. package/test/scripting/scriptingModificator.spec.js +1 -0
  65. package/test/scripting/scriptingmemory/fillScriptingMemoryFromFile.spec.js +4 -0
  66. package/test/scripting/scriptingmemory/regexp.spec.js +1 -0
  67. package/test/scripting/scriptingmemory/useScriptingMemoryForAssertion.spec.js +3 -0
  68. package/test/scripting/txt/decompile.spec.js +20 -0
  69. package/test/scripting/userinputs/buttonInputConvos.spec.js +1 -0
  70. package/test/scripting/userinputs/defaultUserInputs.spec.js +13 -0
  71. package/test/scripting/userinputs/mediaInputConvos.spec.js +10 -0
  72. package/test/scripting/utteranceexpansion/associateByIndex.spec.js +2 -0
  73. package/test/security/allowUnsafe.spec.js +5 -0
  74. package/test/utils.spec.js +2 -0
  75. package/samples/postman/Botium Agent Sample.postman_collection.json +0 -834
  76. package/samples/postman/README.md +0 -5
  77. package/samples/postman/botiumFluent.js +0 -37
  78. package/src/grid/agent/AgentWorker.js +0 -204
  79. package/src/grid/agent/agent.js +0 -96
  80. package/src/grid/agent/agentworkerpool.js +0 -58
  81. package/src/grid/agent/routes.js +0 -353
  82. package/src/grid/agent/swagger.json +0 -327
  83. package/src/grid/agent/swaggerDef.json +0 -8
  84. package/src/grid/agent/views/index.html +0 -39
  85. package/test/grid/agent/client.js +0 -65
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.15.9",
3
+ "version": "1.15.12",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -13,8 +13,6 @@
13
13
  "eslint": "eslint \"./src/**/*.js\" \"./test/**/*.js\"",
14
14
  "eslint-fix": "eslint --fix \"./src/**/*.js\" \"./test/**/*.js\"",
15
15
  "newpatch": "npm version patch",
16
- "agent": "node ./src/grid/agent/agent.js",
17
- "agent-jsdoc": "swagger-jsdoc -d ./src/grid/agent/swaggerDef.json -o ./src/grid/agent/swagger.json ./src/grid/agent/routes.js",
18
16
  "link": "npm link botium-connector-dialogflow botium-connector-webdriverio botium-connector-directline3 botium-connector-watson botium-connector-alexa-smapi botium-connector-echo",
19
17
  "test": "cross-env NODE_PATH=\"./test/plugins/plugindir/fromfolder:./test/plugins/plugindir/fromfile:./test/security/resources\" mocha \"./test/**/*.spec.js\"",
20
18
  "coverage:report": "nyc report --reporter=lcov npm test",
@@ -31,66 +29,63 @@
31
29
  },
32
30
  "homepage": "https://www.botium.ai",
33
31
  "dependencies": {
34
- "@babel/runtime": "^7.23.9",
35
- "async": "^3.2.5",
36
- "body-parser": "^1.20.2",
37
- "boolean": "^3.2.0",
32
+ "@babel/runtime": "^7.28.6",
33
+ "async": "^3.2.6",
34
+ "body-parser": "^2.2.2",
38
35
  "bottleneck": "^2.19.5",
39
- "csv-parse": "^5.5.3",
40
- "debug": "^4.3.4",
41
- "express": "^4.18.2",
36
+ "csv-parse": "^6.1.0",
37
+ "debug": "^4.4.3",
38
+ "express": "^5.2.1",
42
39
  "globby": "11.0.4",
43
- "ioredis": "^5.3.2",
40
+ "ioredis": "^5.10.0",
44
41
  "is-class": "^0.0.9",
45
42
  "is-json": "^2.0.1",
46
- "jsonpath": "^1.1.1",
47
- "lodash": "^4.17.21",
48
- "markdown-it": "^14.0.0",
49
- "mime-types": "^2.1.35",
43
+ "jsonpath": "^1.3.0",
44
+ "lodash": "^4.17.23",
45
+ "markdown-it": "^14.1.1",
46
+ "mime-types": "^3.0.2",
50
47
  "mkdirp": "^3.0.1",
51
48
  "moment": "^2.30.1",
52
- "moment-timezone": "^0.5.45",
49
+ "moment-timezone": "^0.6.0",
53
50
  "mustache": "^4.2.0",
54
51
  "promise-retry": "^2.0.1",
55
52
  "promise.allsettled": "^1.0.7",
56
53
  "randomatic": "^3.1.1",
57
- "rimraf": "^5.0.5",
54
+ "rimraf": "^6.1.3",
58
55
  "sanitize-filename": "^1.6.3",
59
56
  "slugify": "^1.6.6",
60
- "socket.io": "^4.7.4",
61
- "socket.io-client": "^4.7.4",
57
+ "socket.io": "^4.8.3",
58
+ "socket.io-client": "^4.8.3",
62
59
  "socketio-auth": "^0.1.1",
63
- "swagger-jsdoc": "^6.2.8",
64
- "swagger-ui-express": "^5.0.0",
65
- "tinyglobby": "^0.2.10",
66
- "undici": "^6.21.0",
60
+ "tinyglobby": "^0.2.15",
61
+ "undici": "^7.22.0",
67
62
  "uuid": "^9.0.1",
68
63
  "word-error-rate": "0.0.7",
69
64
  "write-yaml": "^1.0.0",
70
65
  "xlsx": "^0.18.5",
71
- "xregexp": "^5.1.1",
72
- "yaml": "^2.3.4"
66
+ "xregexp": "^5.1.2",
67
+ "yaml": "^2.8.2"
73
68
  },
74
69
  "devDependencies": {
75
- "@babel/core": "^7.23.9",
76
- "@babel/node": "^7.23.9",
77
- "@babel/plugin-transform-runtime": "^7.23.9",
78
- "@babel/preset-env": "^7.23.9",
70
+ "@babel/core": "^7.29.0",
71
+ "@babel/node": "^7.29.0",
72
+ "@babel/plugin-transform-runtime": "^7.29.0",
73
+ "@babel/preset-env": "^7.29.0",
79
74
  "chai": "4.3.10",
80
- "chai-as-promised": "^7.1.1",
81
- "cross-env": "^7.0.3",
82
- "eslint": "^8.56.0",
75
+ "chai-as-promised": "^7.1.2",
76
+ "cross-env": "^10.1.0",
77
+ "eslint": "^8.57.1",
83
78
  "eslint-config-standard": "^17.1.0",
84
- "eslint-plugin-import": "^2.29.1",
85
- "eslint-plugin-mocha": "^10.2.0",
79
+ "eslint-plugin-import": "^2.32.0",
80
+ "eslint-plugin-mocha": "^10.5.0",
86
81
  "eslint-plugin-n": "^16.6.2",
87
- "eslint-plugin-promise": "^6.1.1",
82
+ "eslint-plugin-promise": "^6.6.0",
88
83
  "eslint-plugin-standard": "^4.1.0",
89
- "mocha": "^10.3.0",
90
- "nock": "^14.0.0-beta.19",
91
- "npm-check-updates": "^16.14.15",
92
- "nyc": "^15.1.0",
93
- "rollup": "2.79.1",
84
+ "mocha": "^11.7.5",
85
+ "nock": "^14.0.11",
86
+ "npm-check-updates": "^19.6.3",
87
+ "nyc": "^18.0.0",
88
+ "rollup": "2.80.0",
94
89
  "rollup-plugin-babel": "^4.4.0",
95
90
  "rollup-plugin-commonjs": "^10.1.0",
96
91
  "rollup-plugin-json": "^4.0.0",
package/src/BotDriver.js CHANGED
@@ -8,7 +8,7 @@ const sanitize = require('sanitize-filename')
8
8
  const moment = require('moment')
9
9
  const randomize = require('randomatic')
10
10
  const _ = require('lodash')
11
- const { boolean } = require('boolean')
11
+ const { boolean } = require('./utils/boolean')
12
12
  const EventEmitter = require('events')
13
13
  const debug = require('debug')('botium-core-BotDriver')
14
14
 
package/src/Events.js CHANGED
@@ -20,7 +20,5 @@ module.exports = {
20
20
  MESSAGE_RECEIVEDFROMBOT: 'MESSAGE_RECEIVEDFROMBOT',
21
21
  MESSAGE_RECEIVEFROMBOT_ERROR: 'MESSAGE_RECEIVEFROMBOT_ERROR',
22
22
  MESSAGE_ATTACHMENT: 'MESSAGE_ATTACHMENT',
23
- MESSAGE_TRANSCRIPT: 'MESSAGE_TRANSCRIPT',
24
- // Botium Agent Events
25
- TOOMUCHWORKERS_ERROR: 'TOOMUCHWORKERS_ERROR'
23
+ MESSAGE_TRANSCRIPT: 'MESSAGE_TRANSCRIPT'
26
24
  }
@@ -49,10 +49,6 @@ module.exports = class GridContainer extends BaseContainer {
49
49
  debug(`unauthorized ${err.message}`)
50
50
  socketComplete(`Grid Access not authorized: ${err.message}`)
51
51
  })
52
- this.socket.on(Events.TOOMUCHWORKERS_ERROR, (err) => {
53
- debug(`TOOMUCHWORKERS_ERROR ${err.message}`)
54
- socketComplete(`Grid Access not possible: ${err.message}`)
55
- })
56
52
  this.socket.on(Events.CONTAINER_BUILT, () => {
57
53
  debug(Events.CONTAINER_BUILT)
58
54
  socketComplete()
@@ -1,4 +1,70 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
1
3
  const _ = require('lodash')
4
+ const { parse: csvParseSync } = require('csv-parse/sync')
5
+
6
+ /**
7
+ * Find transcription for an audio file: .txt same base name, or transcript.csv in parent dirs.
8
+ * @param {string} baseDir - Base directory for resolving paths
9
+ * @param {string} audioFile - Relative path to audio file
10
+ * @param {object} [options] - Optional: { csvCache: {}, onError: (msg) => {} }
11
+ * @returns {string|null} Transcription text or null
12
+ */
13
+ module.exports.findTranscription = (baseDir, audioFile, options = {}) => {
14
+ const { csvCache = {}, onError } = options
15
+ const transcriptionFilename = `${audioFile.substring(0, audioFile.lastIndexOf('.'))}.txt`
16
+ const transcriptionFilenameAbs = path.resolve(baseDir, transcriptionFilename)
17
+ try {
18
+ if (fs.existsSync(transcriptionFilenameAbs)) {
19
+ return fs.readFileSync(transcriptionFilenameAbs, { encoding: 'utf-8' }).trim()
20
+ }
21
+ } catch (err) {
22
+ if (onError) onError(`Transcription File ${transcriptionFilenameAbs} not readable: ${err.message}`)
23
+ throw new Error(`Reading transcription file ${transcriptionFilename} for ${audioFile} failed`)
24
+ }
25
+ if (csvCache[audioFile]) {
26
+ return csvCache[audioFile]
27
+ }
28
+ const audioFileComponents = audioFile.split('/')
29
+ for (let parentIndex = audioFileComponents.length - 1; parentIndex >= 0; parentIndex--) {
30
+ const csvDirectory = audioFileComponents.slice(0, parentIndex)
31
+ const csvFilename = path.join(...csvDirectory, 'transcript.csv')
32
+ const csvFilenameAbs = path.resolve(baseDir, csvFilename)
33
+ try {
34
+ if (fs.existsSync(csvFilenameAbs)) {
35
+ const records = csvParseSync(fs.readFileSync(csvFilenameAbs, { encoding: 'utf-8' }).trim(), {
36
+ columns: ['filename', 'transcription'],
37
+ delimiter: [',', ';', ':', '\t'],
38
+ trim: true,
39
+ skip_empty_lines: true
40
+ })
41
+ if (records && records.length > 0) {
42
+ for (const record of records) {
43
+ const fnKey = path.join(...csvDirectory, record.filename)
44
+ csvCache[fnKey] = record.transcription
45
+ }
46
+ }
47
+ }
48
+ } catch (err) {
49
+ if (onError) onError(`Transcription CSV File ${csvFilenameAbs} not readable: ${err.message}`)
50
+ throw new Error(`Reading transcription CSV file for ${csvFilename} failed`)
51
+ }
52
+ if (csvCache[audioFile]) {
53
+ return csvCache[audioFile]
54
+ }
55
+ }
56
+ return null
57
+ }
58
+
59
+ /**
60
+ * Derive transcription text from audio filename (basename without extension, underscores/hyphens → spaces).
61
+ * @param {string} audioFile - Path or filename of audio file
62
+ * @returns {string}
63
+ */
64
+ module.exports.transcriptionFromFilename = (audioFile) => {
65
+ const filename = path.basename(audioFile, path.extname(audioFile))
66
+ return filename.split(/[_-]+/).join(' ')
67
+ }
2
68
 
3
69
  module.exports.hasWaitForBotTimeout = (transciptError) => {
4
70
  if (!transciptError) {
@@ -270,6 +270,7 @@ class Convo {
270
270
 
271
271
  async runConversation (container, scriptingMemory, transcript) {
272
272
  const transcriptSteps = []
273
+ transcript.steps = transcriptSteps
273
274
  try {
274
275
  let lastMeConvoStep = null
275
276
  let botMsg = null
@@ -416,6 +417,14 @@ class Convo {
416
417
  throw failErr
417
418
  }
418
419
  } else if (convoStep.sender === 'bot') {
420
+ if (this.scriptingEvents.executeBotStep) {
421
+ const executeBotStepResult = await this.scriptingEvents.executeBotStep({ convo: this, convoStep, container, scriptingMemory, transcript, transcriptStep, transcriptSteps })
422
+ if (executeBotStepResult) {
423
+ skipTranscriptStep = true
424
+ continue
425
+ }
426
+ }
427
+
419
428
  if (waitForBotSays) {
420
429
  botMsg = null
421
430
  } else {
@@ -115,6 +115,25 @@ module.exports = class ScriptingProvider {
115
115
  onBotEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
116
116
  return this._createLogicHookPromises({ hookType: 'onBotEnd', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
117
117
  },
118
+ executeBotStep: ({ convo, convoStep, container, scriptingMemory, ...rest }) => {
119
+ const logicHooks = (convoStep?.logicHooks || [])
120
+ const executeBotStepHooks = logicHooks.filter(l => this.logicHooks[l.name] && typeof this.logicHooks[l.name].executeBotStep === 'function')
121
+ if (executeBotStepHooks.length > 1) {
122
+ throw new Error(`${convo?.header?.name}/${convoStep?.stepTag}: Multiple logic hooks implement executeBotStep: ${executeBotStepHooks.map(l => l.name).join(', ')}. Only one is allowed per step.`)
123
+ }
124
+ if (executeBotStepHooks.length === 1) {
125
+ const lh = executeBotStepHooks[0]
126
+ return this.logicHooks[lh.name].executeBotStep({
127
+ convo,
128
+ convoStep,
129
+ scriptingMemory,
130
+ container,
131
+ args: ScriptingMemory.applyToArgs(lh.args, scriptingMemory, container.caps),
132
+ ...rest
133
+ })
134
+ }
135
+ return null
136
+ },
118
137
  assertConvoBegin: ({ convo, convoStep, scriptingMemory, ...rest }) => {
119
138
  return this._createAsserterPromises({ asserterType: 'assertConvoBegin', asserters: (convo?.beginAsserter || []), convo, convoStep, scriptingMemory, ...rest })
120
139
  },
@@ -480,6 +499,7 @@ module.exports = class ScriptingProvider {
480
499
  onBotStart: this.scriptingEvents.onBotStart.bind(this),
481
500
  onBotPrepare: this.scriptingEvents.onBotPrepare.bind(this),
482
501
  onBotEnd: this.scriptingEvents.onBotEnd.bind(this),
502
+ executeBotStep: this.scriptingEvents.executeBotStep.bind(this),
483
503
  setUserInput: this.scriptingEvents.setUserInput.bind(this),
484
504
  fail: this.scriptingEvents.fail && this.scriptingEvents.fail.bind(this)
485
505
  }
@@ -1004,7 +1024,11 @@ module.exports = class ScriptingProvider {
1004
1024
  // use skip and keep, or justHeader
1005
1025
  justHeader: false,
1006
1026
  // drop unwanted convos
1007
- convoFilter: null
1027
+ convoFilter: null,
1028
+ mediaInput: {
1029
+ // MESSAGE_TEXT_FROM_FILENAME or MESSAGE_TEXT_FROM_TRANSCRIPTION or falsy
1030
+ messageTextMode: null
1031
+ }
1008
1032
  }, options)
1009
1033
  const expandedConvos = []
1010
1034
  // The globalContext is going to keep the data even if the Object.assign which happening to create the myContext in _expandConvo function
@@ -1043,7 +1067,11 @@ module.exports = class ScriptingProvider {
1043
1067
  ExpandConvosIterable (options = {}) {
1044
1068
  options = Object.assign({
1045
1069
  // drop unwanted convos
1046
- convoFilter: null
1070
+ convoFilter: null,
1071
+ mediaInput: {
1072
+ // MESSAGE_TEXT_FROM_FILENAME or MESSAGE_TEXT_FROM_TRANSCRIPTION or falsy
1073
+ messageTextMode: null
1074
+ }
1047
1075
  }, options)
1048
1076
  // The globalContext is going to keep the data even if the Object.assign which happening to create the myContext in _expandConvo function
1049
1077
  const context = {
@@ -1176,7 +1204,7 @@ module.exports = class ScriptingProvider {
1176
1204
  const ui = currentStep.userInputs[uiIndex]
1177
1205
  const userInput = this.userInputs[ui.name]
1178
1206
  if (userInput && userInput.expandConvo) {
1179
- const expandedUserInputs = userInput.expandConvo({ convo: currentConvo, convoStep: currentStep, args: ui.args })
1207
+ const expandedUserInputs = userInput.expandConvo({ convo: currentConvo, convoStep: currentStep, args: ui.args, options })
1180
1208
  if (expandedUserInputs && expandedUserInputs.length > 0) {
1181
1209
  // let sampleinputs = expandedUserInputs
1182
1210
  const processSampleInputs = function * (sampleinputs, myContext, uiIndex) {
@@ -1185,10 +1213,13 @@ module.exports = class ScriptingProvider {
1185
1213
  }
1186
1214
  }
1187
1215
  const processSampleInput = function * (sampleinput, length, index, myContext, uiIndex) {
1216
+ const { messageText, ...userInput } = sampleinput
1188
1217
  const currentStepsStack = convoStepsStack.slice()
1189
1218
  const currentStepMod = _.cloneDeep(currentStep)
1190
- currentStepMod.userInputs[uiIndex] = sampleinput
1191
-
1219
+ currentStepMod.userInputs[uiIndex] = userInput
1220
+ if (messageText) {
1221
+ currentStepMod.messageText = messageText
1222
+ }
1192
1223
  currentStepsStack.push(currentStepMod)
1193
1224
  const currentConvoLabeled = _.cloneDeep(currentConvo)
1194
1225
  if (length > 1) {
@@ -8,6 +8,7 @@ const _ = require('lodash')
8
8
  const { BotiumMockMedia } = require('../../../../src/mocks/BotiumMockRichMessageTypes')
9
9
  const { BotiumError } = require('../../../../src/scripting/BotiumError')
10
10
  const Capabilities = require('../../../../src/Capabilities')
11
+ const TranscriptUtils = require('../../../helpers/TranscriptUtils')
11
12
 
12
13
  const DEFAULT_BASE_SELECTOR = 'sourceTag.testSetId'
13
14
 
@@ -151,7 +152,7 @@ module.exports = class MediaInput {
151
152
  return mime.lookup(arg)
152
153
  }
153
154
 
154
- expandConvo ({ convo, convoStep, args }) {
155
+ expandConvo ({ convo, convoStep, args, options }) {
155
156
  const hasWildcard = args.findIndex(a => this._isWildcard(a)) >= 0
156
157
 
157
158
  if (args && (args.length > 1 || hasWildcard)) {
@@ -161,10 +162,17 @@ module.exports = class MediaInput {
161
162
  // we need to escape brackets to find files
162
163
  const mediaFiles = globSync(arg.replace(/[()[\]{}]/g, '\\$&'), { cwd: baseDir })
163
164
  mediaFiles.forEach(mf => {
165
+ let messageText = null
166
+ if (options.mediaInput.messageTextMode === 'MESSAGE_TEXT_FROM_FILENAME') {
167
+ messageText = TranscriptUtils.transcriptionFromFilename(mf)
168
+ } else if (options.mediaInput.messageTextMode === 'MESSAGE_TEXT_FROM_TRANSCRIPTION') {
169
+ messageText = TranscriptUtils.findTranscription(baseDir, mf)
170
+ }
164
171
  e.push({
165
172
  name: 'MEDIA',
166
173
  args: [mf],
167
- convoPostfix: _.last(mf.split('/'))
174
+ convoPostfix: _.last(mf.split('/')),
175
+ ...messageText ? { messageText } : {}
168
176
  })
169
177
  })
170
178
  } else {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Converts a value to a boolean.
3
+ * Similar to the 'boolean' npm package implementation.
4
+ *
5
+ * @param {*} value - The value to convert to boolean
6
+ * @returns {boolean} The boolean representation of the value
7
+ */
8
+ function boolean (value) {
9
+ // Handle null and undefined
10
+ if (value == null) {
11
+ return false
12
+ }
13
+
14
+ // Handle boolean values
15
+ if (typeof value === 'boolean') {
16
+ return value
17
+ }
18
+
19
+ // Handle numbers
20
+ if (typeof value === 'number') {
21
+ return value !== 0 && !isNaN(value)
22
+ }
23
+
24
+ // Handle strings
25
+ if (typeof value === 'string') {
26
+ const normalized = value.trim().toLowerCase()
27
+ if (normalized === 'true' || normalized === 'yes' || normalized === 'on' || normalized === '1') {
28
+ return true
29
+ }
30
+ if (normalized === 'false' || normalized === 'no' || normalized === 'off' || normalized === '0' || normalized === '') {
31
+ return false
32
+ }
33
+ }
34
+
35
+ // For all other values, use JavaScript's truthiness
36
+ return Boolean(value)
37
+ }
38
+
39
+ module.exports = { boolean }