whatsapp-chat-parser
Version:
A package to parse WhatsApp chats with Node.js or in the browser 💬
1 lines • 18.2 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/date.ts","../src/time.ts","../src/parser.ts"],"sourcesContent":["import { makeArrayOfMessages, parseMessages } from './parser';\nimport { Attachment, Message, ParseStringOptions } from './types';\n\nconst newlinesRegex = /(?:\\r\\n|\\r|\\n)/;\n\n/**\n * Parses a string containing a WhatsApp chat log.\n *\n * Returns an array of parsed messages.\n *\n * @since 3.2.0\n * @since 4.0.0 Renamed from parseStringSync\n */\nexport function parseString(\n string: string,\n options: ParseStringOptions = { parseAttachments: false },\n): Message[] {\n const lines = string.split(newlinesRegex);\n return parseMessages(makeArrayOfMessages(lines), options);\n}\n\nexport type { Attachment, Message, ParseStringOptions };\n","/**\n * Checks that the number at a certain index of an array is greater than a\n * certain value.\n */\nfunction indexAboveValue(index: number, value: number) {\n return (array: number[]): boolean => array[index] > value;\n}\n\n/**\n * Returns `true` for a negative number, `false` otherwise.\n *\n * `0` and `-0` are considered positive.\n */\nfunction isNegative(number: number): boolean {\n return number < 0;\n}\n\n/**\n * Takes an array of arrays and an index and groups the inner arrays by the\n * value at the index provided.\n * @see `utils.test.ts` for a better understanding of this function.\n */\nfunction groupArrayByValueAtIndex<T extends unknown[]>(\n array: T[],\n index: number,\n): T[][] {\n return Object.values(\n array.reduce((obj: { [key: string]: T[] }, item) => {\n /*\n * Keys that are numbers (even strings containing a number) get sorted\n * when using `Object.values()`.\n * Adding a prefix avoids this issue.\n */\n const key = `_${item[index]}`;\n\n if (!obj[key]) obj[key] = [];\n\n obj[key].push(item);\n\n return obj;\n }, {}),\n );\n}\n\nexport { indexAboveValue, isNegative, groupArrayByValueAtIndex };\n","import { indexAboveValue, isNegative, groupArrayByValueAtIndex } from './utils';\n\n/**\n * Takes an array of numeric dates and tries to understand if the days come\n * before the month or the other way around by checking if numbers go above\n * `12`.\n *\n * Output is `true` if days are first, `false` if they are second, or `null` if\n * it failed to understand the order.\n */\nfunction checkAbove12(numericDates: number[][]): boolean | null {\n const daysFirst = numericDates.some(indexAboveValue(0, 12));\n\n if (daysFirst) return true;\n\n const daysSecond = numericDates.some(indexAboveValue(1, 12));\n\n if (daysSecond) return false;\n\n return null;\n}\n\n/**\n * Takes an array of numeric dates and tries to understand if the days come\n * before the month or the other way around by checking if a set of numbers\n * during the same year decrease at some point.\n *\n * If it does it's probably the days since months can only increase in a given\n * year.\n *\n * Output is `true` if days are first, `false` if they are second, or `null` if\n * it failed to understand the order.\n */\nfunction checkDecreasing(numericDates: number[][]): boolean | null {\n const datesByYear = groupArrayByValueAtIndex(numericDates, 2);\n const results = datesByYear.map(dates => {\n const daysFirst = dates.slice(1).some((date, i) => {\n const [first1] = dates[i];\n const [first2] = date;\n\n return isNegative(first2 - first1);\n });\n\n if (daysFirst) return true;\n\n const daysSecond = dates.slice(1).some((date, i) => {\n const [, second1] = dates[i];\n const [, second2] = date;\n\n return isNegative(second2 - second1);\n });\n\n if (daysSecond) return false;\n\n return null;\n });\n\n const anyTrue = results.some(value => value === true);\n\n if (anyTrue) return true;\n\n const anyFalse = results.some(value => value === false);\n\n if (anyFalse) return false;\n\n return null;\n}\n\n/**\n * Takes an array of numeric dates and tries to understand if the days come\n * before the month or the other way around by looking at which number changes\n * more frequently.\n *\n * Output is `true` if days are first, `false` if they are second, or `null` if\n * it failed to understand the order.\n */\nfunction changeFrequencyAnalysis(numericDates: number[][]): boolean | null {\n const diffs = numericDates\n .slice(1)\n .map((date, i) => date.map((num, j) => Math.abs(numericDates[i][j] - num)));\n const [first, second] = diffs.reduce(\n (total, diff) => {\n const [first1, second1] = total;\n const [first2, second2] = diff;\n\n return [first1 + first2, second1 + second2];\n },\n [0, 0],\n );\n\n if (first > second) return true;\n if (first < second) return false;\n\n return null;\n}\n\n/**\n * Takes an array of numeric dates and tries to understand if the days come\n * before the month or the other way around by running the dates through various\n * checks.\n *\n * Output is `true` if days are first, `false` if they are second, or `null` if\n * it failed to understand the order.\n */\nfunction daysBeforeMonths(numericDates: number[][]): boolean | null {\n const firstCheck = checkAbove12(numericDates);\n\n if (firstCheck !== null) return firstCheck;\n\n const secondCheck = checkDecreasing(numericDates);\n\n if (secondCheck !== null) return secondCheck;\n\n return changeFrequencyAnalysis(numericDates);\n}\n\n/**\n * Takes `year`, `month` and `day` as strings and pads them to `4`, `2`, `2`\n * digits respectively.\n */\nfunction normalizeDate(\n year: string,\n month: string,\n day: string,\n): [string, string, string] {\n return [\n // 2 digit years are assumed to be in the 2000-2099 range\n year.padStart(4, '2000'),\n month.padStart(2, '0'),\n day.padStart(2, '0'),\n ];\n}\n\n/**\n * Pushes the longest number in a date to the end, if there is one. Necessary to\n * ensure the year is the last number.\n */\nfunction orderDateComponents(date: string): [string, string, string] {\n const regexSplitDate = /[-/.] ?/;\n const [a, b, c] = date.split(regexSplitDate);\n const maxLength = Math.max(a.length, b.length, c.length);\n\n if (c.length === maxLength) return [a, b, c];\n if (b.length === maxLength) return [a, c, b];\n return [b, c, a];\n}\n\nexport {\n checkAbove12,\n checkDecreasing,\n changeFrequencyAnalysis,\n daysBeforeMonths,\n normalizeDate,\n orderDateComponents,\n};\n","const regexSplitTime = /[:.]/;\n\n/**\n * Converts time from 12 hour format to 24 hour format.\n *\n * Reference:\n * {@link https://stackoverflow.com/a/40197728/5303634}\n */\nfunction convertTime12to24(time: string, ampm: string): string {\n // eslint-disable-next-line prefer-const\n let [hours, minutes, seconds] = time.split(regexSplitTime);\n\n if (hours === '12') hours = '00';\n if (ampm === 'PM') hours = String(parseInt(hours, 10) + 12);\n\n return `${hours}:${minutes}${seconds ? `:${seconds}` : ''}`;\n}\n\n/**\n * Normalizes a time string to have the following format: `hh:mm:ss`.\n */\nfunction normalizeTime(time: string): string {\n const [hours, minutes, seconds] = time.split(regexSplitTime);\n\n return `${hours.padStart(2, '0')}:${minutes}:${seconds || '00'}`;\n}\n\n/**\n * Normalizes `am` / `a.m.` / etc. to `AM` (uppercase, no other characters).\n */\nfunction normalizeAMPM(ampm: string): string {\n return ampm.replace(/[^apm]/gi, '').toUpperCase();\n}\n\nexport { regexSplitTime, convertTime12to24, normalizeTime, normalizeAMPM };\n","import { daysBeforeMonths, normalizeDate, orderDateComponents } from './date';\nimport {\n regexSplitTime,\n convertTime12to24,\n normalizeAMPM,\n normalizeTime,\n} from './time';\nimport { Attachment, Message, RawMessage, ParseStringOptions } from './types';\n\nconst sharedRegex =\n /^(?:\\u200E|\\u200F)*\\[?(\\d{1,4}[-/.]\\s?\\d{1,4}[-/.]\\s?\\d{1,4})[,.]?\\s\\D*?(\\d{1,2}[.:]\\d{1,2}(?:[.:]\\d{1,2})?)(?:\\s([ap]\\.?\\s?m\\.?))?\\]?(?:\\s-|:)?\\s/;\nconst authorAndMessageRegex = /(.+?):\\s([^]*)/;\nconst messageRegex = /([^]+)/;\nconst regexParser = new RegExp(\n sharedRegex.source + authorAndMessageRegex.source,\n 'i',\n);\nconst regexParserSystem = new RegExp(\n sharedRegex.source + messageRegex.source,\n 'i',\n);\nconst regexAttachment =\n /^(?:\\u200E|\\u200F)*(?:<.+:(.+)>|([\\w-]+\\.\\w+)\\s[(<].+[)>])/;\n\n/**\n * Takes an array of lines and detects the lines that are part of a previous\n * message (multiline messages) and merges them.\n *\n * It also labels messages without an author as system messages.\n */\nfunction makeArrayOfMessages(lines: string[]): RawMessage[] {\n return lines.reduce((acc: RawMessage[], line) => {\n /*\n * If the line doesn't match the regex it's probably part of the previous\n * message or a \"WhatsApp event\"\n */\n if (!regexParser.test(line)) {\n /*\n * If it doesn't match the first regex but still matches the system regex\n * it should be considered a \"WhatsApp event\" so it gets labeled \"system\"\n */\n if (regexParserSystem.test(line)) {\n acc.push({ system: true, msg: line });\n }\n\n // Else it's part of the previous message and it should be concatenated\n else if (typeof acc[acc.length - 1] !== 'undefined') {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const prevMessage = acc.pop()!;\n\n acc.push({\n system: prevMessage.system,\n msg: `${prevMessage.msg}\\n${line}`,\n });\n }\n } else {\n acc.push({ system: false, msg: line });\n }\n\n return acc;\n }, []);\n}\n\n/**\n * Parses a message extracting the attachment if it's present.\n */\nfunction parseMessageAttachment(message: string): Attachment | null {\n const attachmentMatch = message.match(regexAttachment);\n\n if (!attachmentMatch) return null;\n return {\n fileName: (attachmentMatch[1] || attachmentMatch[2]).trim(),\n };\n}\n\n/**\n * Parses and array of raw messages into an array of structured objects.\n */\nfunction parseMessages(\n messages: RawMessage[],\n options: ParseStringOptions = {},\n): Message[] {\n let { daysFirst } = options;\n const { parseAttachments } = options;\n\n // Parse messages with regex\n const parsed = messages.map(obj => {\n const { system, msg } = obj;\n\n // If it's a system message another regex should be used to parse it\n if (system) {\n const [, date, time, ampm, message] = regexParserSystem.exec(\n msg,\n ) as RegExpExecArray;\n\n return { date, time, ampm: ampm || null, author: null, message };\n }\n\n const [, date, time, ampm, author, message] = regexParser.exec(\n msg,\n ) as RegExpExecArray;\n\n return { date, time, ampm: ampm || null, author, message };\n });\n\n // Understand date format if not supplied (do days come first?)\n if (typeof daysFirst !== 'boolean') {\n const numericDates = Array.from(\n new Set(parsed.map(({ date }) => date)),\n date => orderDateComponents(date).map(Number),\n );\n\n daysFirst = daysBeforeMonths(numericDates);\n }\n\n // Convert date and time in a `Date` object, return the final object\n return parsed.map(({ date, time, ampm, author, message }) => {\n let day: string;\n let month: string;\n let year: string;\n const splitDate = orderDateComponents(date);\n\n if (daysFirst === false) {\n [month, day, year] = splitDate;\n } else {\n [day, month, year] = splitDate;\n }\n\n [year, month, day] = normalizeDate(year, month, day);\n\n const [hours, minutes, seconds] = normalizeTime(\n ampm ? convertTime12to24(time, normalizeAMPM(ampm)) : time,\n ).split(regexSplitTime);\n\n const finalObject: Message = {\n date: new Date(+year, +month - 1, +day, +hours, +minutes, +seconds),\n author,\n message,\n };\n\n // Optionally parse attachments\n if (parseAttachments) {\n const attachment = parseMessageAttachment(message);\n if (attachment) finalObject.attachment = attachment;\n }\n\n return finalObject;\n });\n}\n\nexport { makeArrayOfMessages, parseMessages };\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,iBAAAE,IAAA,eAAAC,EAAAH,GCIA,SAASI,EAAgBC,EAAeC,EAAe,CACrD,OAAQC,GAA6BA,EAAMF,CAAK,EAAIC,CACtD,CAOA,SAASE,EAAWC,EAAyB,CAC3C,OAAOA,EAAS,CAClB,CAOA,SAASC,EACPH,EACAF,EACO,CACP,OAAO,OAAO,OACZE,EAAM,OAAO,CAACI,EAA6BC,IAAS,CAMlD,IAAMC,EAAM,IAAID,EAAKP,CAAK,CAAC,GAE3B,OAAKM,EAAIE,CAAG,IAAGF,EAAIE,CAAG,EAAI,CAAC,GAE3BF,EAAIE,CAAG,EAAE,KAAKD,CAAI,EAEXD,CACT,EAAG,CAAC,CAAC,CACP,CACF,CChCA,SAASG,EAAaC,EAA0C,CAG9D,OAFkBA,EAAa,KAAKC,EAAgB,EAAG,EAAE,CAAC,EAEpC,GAEHD,EAAa,KAAKC,EAAgB,EAAG,EAAE,CAAC,EAEpC,GAEhB,IACT,CAaA,SAASC,EAAgBF,EAA0C,CAEjE,IAAMG,EADcC,EAAyBJ,EAAc,CAAC,EAChC,IAAIK,GACZA,EAAM,MAAM,CAAC,EAAE,KAAK,CAACC,EAAMC,IAAM,CACjD,GAAM,CAACC,CAAM,EAAIH,EAAME,CAAC,EAClB,CAACE,CAAM,EAAIH,EAEjB,OAAOI,EAAWD,EAASD,CAAM,CACnC,CAAC,EAEqB,GAEHH,EAAM,MAAM,CAAC,EAAE,KAAK,CAACC,EAAMC,IAAM,CAClD,GAAM,CAAC,CAAEI,CAAO,EAAIN,EAAME,CAAC,EACrB,CAAC,CAAEK,CAAO,EAAIN,EAEpB,OAAOI,EAAWE,EAAUD,CAAO,CACrC,CAAC,EAEsB,GAEhB,IACR,EAID,OAFgBR,EAAQ,KAAKU,GAASA,IAAU,EAAI,EAEhC,GAEHV,EAAQ,KAAKU,GAASA,IAAU,EAAK,EAEjC,GAEd,IACT,CAUA,SAASC,EAAwBd,EAA0C,CACzE,IAAMe,EAAQf,EACX,MAAM,CAAC,EACP,IAAI,CAACM,EAAMC,IAAMD,EAAK,IAAI,CAACU,EAAKC,IAAM,KAAK,IAAIjB,EAAaO,CAAC,EAAEU,CAAC,EAAID,CAAG,CAAC,CAAC,EACtE,CAACE,EAAOC,CAAM,EAAIJ,EAAM,OAC5B,CAACK,EAAOC,IAAS,CACf,GAAM,CAACb,EAAQG,CAAO,EAAIS,EACpB,CAACX,EAAQG,CAAO,EAAIS,EAE1B,MAAO,CAACb,EAASC,EAAQE,EAAUC,CAAO,CAC5C,EACA,CAAC,EAAG,CAAC,CACP,EAEA,OAAIM,EAAQC,EAAe,GACvBD,EAAQC,EAAe,GAEpB,IACT,CAUA,SAASG,EAAiBtB,EAA0C,CAClE,IAAMuB,EAAaxB,EAAaC,CAAY,EAE5C,GAAIuB,IAAe,KAAM,OAAOA,EAEhC,IAAMC,EAActB,EAAgBF,CAAY,EAEhD,OAAIwB,IAAgB,KAAaA,EAE1BV,EAAwBd,CAAY,CAC7C,CAMA,SAASyB,EACPC,EACAC,EACAC,EAC0B,CAC1B,MAAO,CAELF,EAAK,SAAS,EAAG,MAAM,EACvBC,EAAM,SAAS,EAAG,GAAG,EACrBC,EAAI,SAAS,EAAG,GAAG,CACrB,CACF,CAMA,SAASC,EAAoBvB,EAAwC,CACnE,IAAMwB,EAAiB,UACjB,CAACC,EAAGC,EAAGC,CAAC,EAAI3B,EAAK,MAAMwB,CAAc,EACrCI,EAAY,KAAK,IAAIH,EAAE,OAAQC,EAAE,OAAQC,EAAE,MAAM,EAEvD,OAAIA,EAAE,SAAWC,EAAkB,CAACH,EAAGC,EAAGC,CAAC,EACvCD,EAAE,SAAWE,EAAkB,CAACH,EAAGE,EAAGD,CAAC,EACpC,CAACA,EAAGC,EAAGF,CAAC,CACjB,CCjJA,IAAMI,EAAiB,OAQvB,SAASC,EAAkBC,EAAcC,EAAsB,CAE7D,GAAI,CAACC,EAAOC,EAASC,CAAO,EAAIJ,EAAK,MAAMF,CAAc,EAEzD,OAAII,IAAU,OAAMA,EAAQ,MACxBD,IAAS,OAAMC,EAAQ,OAAO,SAASA,EAAO,EAAE,EAAI,EAAE,GAEnD,GAAGA,CAAK,IAAIC,CAAO,GAAGC,EAAU,IAAIA,CAAO,GAAK,EAAE,EAC3D,CAKA,SAASC,EAAcL,EAAsB,CAC3C,GAAM,CAACE,EAAOC,EAASC,CAAO,EAAIJ,EAAK,MAAMF,CAAc,EAE3D,MAAO,GAAGI,EAAM,SAAS,EAAG,GAAG,CAAC,IAAIC,CAAO,IAAIC,GAAW,IAAI,EAChE,CAKA,SAASE,EAAcL,EAAsB,CAC3C,OAAOA,EAAK,QAAQ,WAAY,EAAE,EAAE,YAAY,CAClD,CCvBA,IAAMM,EACJ,qJACIC,EAAwB,iBACxBC,EAAe,SACfC,EAAc,IAAI,OACtBH,EAAY,OAASC,EAAsB,OAC3C,GACF,EACMG,EAAoB,IAAI,OAC5BJ,EAAY,OAASE,EAAa,OAClC,GACF,EACMG,EACJ,6DAQF,SAASC,EAAoBC,EAA+B,CAC1D,OAAOA,EAAM,OAAO,CAACC,EAAmBC,IAAS,CAK/C,GAAKN,EAAY,KAAKM,CAAI,EAoBxBD,EAAI,KAAK,CAAE,OAAQ,GAAO,IAAKC,CAAK,CAAC,UAfjCL,EAAkB,KAAKK,CAAI,EAC7BD,EAAI,KAAK,CAAE,OAAQ,GAAM,IAAKC,CAAK,CAAC,UAI7B,OAAOD,EAAIA,EAAI,OAAS,CAAC,GAAM,YAAa,CAEnD,IAAME,EAAcF,EAAI,IAAI,EAE5BA,EAAI,KAAK,CACP,OAAQE,EAAY,OACpB,IAAK,GAAGA,EAAY,GAAG;AAAA,EAAKD,CAAI,EAClC,CAAC,CACH,CAKF,OAAOD,CACT,EAAG,CAAC,CAAC,CACP,CAKA,SAASG,EAAuBC,EAAoC,CAClE,IAAMC,EAAkBD,EAAQ,MAAMP,CAAe,EAErD,OAAKQ,EACE,CACL,UAAWA,EAAgB,CAAC,GAAKA,EAAgB,CAAC,GAAG,KAAK,CAC5D,EAH6B,IAI/B,CAKA,SAASC,EACPC,EACAC,EAA8B,CAAC,EACpB,CACX,GAAI,CAAE,UAAAC,CAAU,EAAID,EACd,CAAE,iBAAAE,CAAiB,EAAIF,EAGvBG,EAASJ,EAAS,IAAIK,GAAO,CACjC,GAAM,CAAE,OAAAC,EAAQ,IAAAC,CAAI,EAAIF,EAGxB,GAAIC,EAAQ,CACV,GAAM,CAAC,CAAEE,EAAMC,EAAMC,EAAMb,CAAO,EAAIR,EAAkB,KACtDkB,CACF,EAEA,MAAO,CAAE,KAAAC,EAAM,KAAAC,EAAM,KAAMC,GAAQ,KAAM,OAAQ,KAAM,QAAAb,CAAQ,CACjE,CAEA,GAAM,CAAC,CAAEW,EAAMC,EAAMC,EAAMC,EAAQd,CAAO,EAAIT,EAAY,KACxDmB,CACF,EAEA,MAAO,CAAE,KAAAC,EAAM,KAAAC,EAAM,KAAMC,GAAQ,KAAM,OAAAC,EAAQ,QAAAd,CAAQ,CAC3D,CAAC,EAGD,GAAI,OAAOK,GAAc,UAAW,CAClC,IAAMU,EAAe,MAAM,KACzB,IAAI,IAAIR,EAAO,IAAI,CAAC,CAAE,KAAAI,CAAK,IAAMA,CAAI,CAAC,EACtCA,GAAQK,EAAoBL,CAAI,EAAE,IAAI,MAAM,CAC9C,EAEAN,EAAYY,EAAiBF,CAAY,CAC3C,CAGA,OAAOR,EAAO,IAAI,CAAC,CAAE,KAAAI,EAAM,KAAAC,EAAM,KAAAC,EAAM,OAAAC,EAAQ,QAAAd,CAAQ,IAAM,CAC3D,IAAIkB,EACAC,EACAC,EACEC,EAAYL,EAAoBL,CAAI,EAEtCN,IAAc,GAChB,CAACc,EAAOD,EAAKE,CAAI,EAAIC,EAErB,CAACH,EAAKC,EAAOC,CAAI,EAAIC,EAGvB,CAACD,EAAMD,EAAOD,CAAG,EAAII,EAAcF,EAAMD,EAAOD,CAAG,EAEnD,GAAM,CAACK,EAAOC,EAASC,CAAO,EAAIC,EAChCb,EAAOc,EAAkBf,EAAMgB,EAAcf,CAAI,CAAC,EAAID,CACxD,EAAE,MAAMiB,CAAc,EAEhBC,EAAuB,CAC3B,KAAM,IAAI,KAAK,CAACV,EAAM,CAACD,EAAQ,EAAG,CAACD,EAAK,CAACK,EAAO,CAACC,EAAS,CAACC,CAAO,EAClE,OAAAX,EACA,QAAAd,CACF,EAGA,GAAIM,EAAkB,CACpB,IAAMyB,EAAahC,EAAuBC,CAAO,EAC7C+B,IAAYD,EAAY,WAAaC,EAC3C,CAEA,OAAOD,CACT,CAAC,CACH,CJjJA,IAAME,EAAgB,iBAUf,SAASC,EACdC,EACAC,EAA8B,CAAE,iBAAkB,EAAM,EAC7C,CACX,IAAMC,EAAQF,EAAO,MAAMF,CAAa,EACxC,OAAOK,EAAcC,EAAoBF,CAAK,EAAGD,CAAO,CAC1D","names":["src_exports","__export","parseString","__toCommonJS","indexAboveValue","index","value","array","isNegative","number","groupArrayByValueAtIndex","obj","item","key","checkAbove12","numericDates","indexAboveValue","checkDecreasing","results","groupArrayByValueAtIndex","dates","date","i","first1","first2","isNegative","second1","second2","value","changeFrequencyAnalysis","diffs","num","j","first","second","total","diff","daysBeforeMonths","firstCheck","secondCheck","normalizeDate","year","month","day","orderDateComponents","regexSplitDate","a","b","c","maxLength","regexSplitTime","convertTime12to24","time","ampm","hours","minutes","seconds","normalizeTime","normalizeAMPM","sharedRegex","authorAndMessageRegex","messageRegex","regexParser","regexParserSystem","regexAttachment","makeArrayOfMessages","lines","acc","line","prevMessage","parseMessageAttachment","message","attachmentMatch","parseMessages","messages","options","daysFirst","parseAttachments","parsed","obj","system","msg","date","time","ampm","author","numericDates","orderDateComponents","daysBeforeMonths","day","month","year","splitDate","normalizeDate","hours","minutes","seconds","normalizeTime","convertTime12to24","normalizeAMPM","regexSplitTime","finalObject","attachment","newlinesRegex","parseString","string","options","lines","parseMessages","makeArrayOfMessages"]}