whatsapp-web.js 1.23.1-alpha.0 → 1.23.1-alpha.2

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 CHANGED
@@ -63,8 +63,8 @@ For more information on saving and restoring sessions, check out the available [
63
63
  | Receive media (images/audio/video/documents) | ✅ |
64
64
  | Send contact cards | ✅ |
65
65
  | Send location | ✅ |
66
- | Send buttons | |
67
- | Send lists | (business accounts not supported) |
66
+ | Send buttons | |
67
+ | Send lists | ❌ [(DEPRECATED)](https://www.youtube.com/watch?v=hv1R1rLeVVE) |
68
68
  | Receive location | ✅ |
69
69
  | Message replies | ✅ |
70
70
  | Join groups by invite | ✅ |
@@ -81,6 +81,8 @@ For more information on saving and restoring sessions, check out the available [
81
81
  | Get profile pictures | ✅ |
82
82
  | Set user status message | ✅ |
83
83
  | React to messages | ✅ |
84
+ | Vote in polls | 🔜 |
85
+ | Create polls | ✅ |
84
86
 
85
87
  Something missing? Make an issue and let us know!
86
88
 
package/example.js CHANGED
@@ -66,6 +66,9 @@ client.on('message', async msg => {
66
66
  } else if (msg.body.startsWith('!echo ')) {
67
67
  // Replies with the same message
68
68
  msg.reply(msg.body.slice(6));
69
+ } else if (msg.body.startsWith('!preview ')) {
70
+ const text = msg.body.slice(9);
71
+ msg.reply(text, null, { linkPreview: true });
69
72
  } else if (msg.body.startsWith('!desc ')) {
70
73
  // Change the group description
71
74
  let chat = await msg.getChat();
@@ -383,6 +386,14 @@ client.on('message_create', (msg) => {
383
386
  }
384
387
  });
385
388
 
389
+ client.on('message_ciphertext', (msg) => {
390
+ // Receiving new incoming messages that have been encrypted
391
+ // msg.type === 'ciphertext'
392
+ msg.body = 'Waiting for this message. Check your phone.';
393
+
394
+ // do stuff here
395
+ });
396
+
386
397
  client.on('message_revoke_everyone', async (after, before) => {
387
398
  // Fired whenever a message is deleted by anyone (including you)
388
399
  console.log(after); // message after it was deleted.
package/index.d.ts CHANGED
@@ -304,6 +304,12 @@ declare namespace WAWebJS {
304
304
  /** The message that was created */
305
305
  message: Message
306
306
  ) => void): this
307
+
308
+ /** Emitted when a new message ciphertext is received */
309
+ on(event: 'message_ciphertext', listener: (
310
+ /** The message that was ciphertext */
311
+ message: Message
312
+ ) => void): this
307
313
 
308
314
  /** Emitted when a message is deleted for everyone in the chat */
309
315
  on(event: 'message_revoke_everyone', listener: (
@@ -440,7 +446,7 @@ declare namespace WAWebJS {
440
446
  /** User agent to use in puppeteer.
441
447
  * @default 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36' */
442
448
  userAgent?: string
443
- /** Ffmpeg path to use when formating videos to webp while sending stickers
449
+ /** Ffmpeg path to use when formatting videos to webp while sending stickers
444
450
  * @default 'ffmpeg' */
445
451
  ffmpegPath?: string,
446
452
  /** Object with proxy autentication requirements @default: undefined */
@@ -647,6 +653,7 @@ declare namespace WAWebJS {
647
653
  AUTHENTICATION_FAILURE = 'auth_failure',
648
654
  READY = 'ready',
649
655
  MESSAGE_RECEIVED = 'message',
656
+ MESSAGE_CIPHERTEXT = 'message_ciphertext',
650
657
  MESSAGE_CREATE = 'message_create',
651
658
  MESSAGE_REVOKED_EVERYONE = 'message_revoke_everyone',
652
659
  MESSAGE_REVOKED_ME = 'message_revoke_me',
@@ -981,17 +988,19 @@ declare namespace WAWebJS {
981
988
  * The custom message secret, can be used as a poll ID
982
989
  * @note It has to be a unique vector with a length of 32
983
990
  */
984
- messageSecret: ?Array<number>
991
+ messageSecret: Array<number>|undefined
985
992
  }
986
993
 
987
994
  /** Represents a Poll on WhatsApp */
988
- export interface Poll {
989
- pollName: string,
995
+ export class Poll {
996
+ pollName: string
990
997
  pollOptions: Array<{
991
998
  name: string,
992
999
  localId: number
993
- }>,
1000
+ }>
994
1001
  options: PollSendOptions
1002
+
1003
+ constructor(pollName: string, pollOptions: Array<string>, options?: PollSendOptions)
995
1004
  }
996
1005
 
997
1006
  export interface Label {
@@ -1189,13 +1198,84 @@ declare namespace WAWebJS {
1189
1198
  user: string,
1190
1199
  _serialized: string,
1191
1200
  }
1201
+
1202
+ export interface BusinessCategory {
1203
+ id: string,
1204
+ localized_display_name: string,
1205
+ }
1206
+
1207
+ export interface BusinessHoursOfDay {
1208
+ mode: string,
1209
+ hours: number[]
1210
+ }
1211
+
1212
+ export interface BusinessHours {
1213
+ config: {
1214
+ sun: BusinessHoursOfDay,
1215
+ mon: BusinessHoursOfDay,
1216
+ tue: BusinessHoursOfDay,
1217
+ wed: BusinessHoursOfDay,
1218
+ thu: BusinessHoursOfDay,
1219
+ fri: BusinessHoursOfDay,
1220
+ }
1221
+ timezone: string,
1222
+ }
1223
+
1224
+
1192
1225
 
1193
1226
  export interface BusinessContact extends Contact {
1194
1227
  /**
1195
1228
  * The contact's business profile
1196
- * @todo add a more specific type for the object
1197
1229
  */
1198
- businessProfile: object
1230
+ businessProfile: {
1231
+ /** The contact's business profile id */
1232
+ id: ContactId,
1233
+
1234
+ /** The contact's business profile tag */
1235
+ tag: string,
1236
+
1237
+ /** The contact's business profile description */
1238
+ description: string,
1239
+
1240
+ /** The contact's business profile categories */
1241
+ categories: BusinessCategory[],
1242
+
1243
+ /** The contact's business profile options */
1244
+ profileOptions: {
1245
+ /** The contact's business profile commerce experience*/
1246
+ commerceExperience: string,
1247
+
1248
+ /** The contact's business profile cart options */
1249
+ cartEnabled: boolean,
1250
+ }
1251
+
1252
+ /** The contact's business profile email */
1253
+ email: string,
1254
+
1255
+ /** The contact's business profile websites */
1256
+ website: string[],
1257
+
1258
+ /** The contact's business profile latitude */
1259
+ latitude: number,
1260
+
1261
+ /** The contact's business profile longitude */
1262
+ longitude: number,
1263
+
1264
+ /** The contact's business profile work hours*/
1265
+ businessHours: BusinessHours
1266
+
1267
+ /** The contact's business profile address */
1268
+ address: string,
1269
+
1270
+ /** The contact's business profile facebook page */
1271
+ fbPage: object,
1272
+
1273
+ /** Indicate if the contact's business profile linked */
1274
+ ifProfileLinked: boolean
1275
+
1276
+ /** The contact's business profile coverPhoto */
1277
+ coverPhoto: null | any,
1278
+ }
1199
1279
  }
1200
1280
 
1201
1281
  export interface PrivateContact extends Contact {
@@ -1341,7 +1421,7 @@ declare namespace WAWebJS {
1341
1421
  code: number;
1342
1422
  message: string;
1343
1423
  isInviteV4Sent: boolean,
1344
- };
1424
+ }
1345
1425
  };
1346
1426
 
1347
1427
  /** An object that handles options for adding participants */
@@ -1410,7 +1490,7 @@ declare namespace WAWebJS {
1410
1490
  /** Group participants */
1411
1491
  participants: Array<GroupParticipant>;
1412
1492
  /** Adds a list of participants by ID to the group */
1413
- addParticipants: (participantIds: string|string[], options?: AddParticipantsOptions) => Promise<Object.<string, AddParticipantsResult>|string>;
1493
+ addParticipants: (participantIds: string | string[], options?: AddParticipantsOptions) => Promise<{ [key: string]: AddParticipantsResult } | string>;
1414
1494
  /** Removes a list of participants by ID to the group */
1415
1495
  removeParticipants: (participantIds: string[]) => Promise<{ status: number }>;
1416
1496
  /** Promotes participants by IDs to admins */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.23.1-alpha.0",
3
+ "version": "1.23.1-alpha.2",
4
4
  "description": "Library for interacting with the WhatsApp Web API ",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
package/src/Client.js CHANGED
@@ -30,7 +30,7 @@ const NoAuth = require('./authStrategies/NoAuth');
30
30
  * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
31
31
  * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
32
32
  * @param {string} options.userAgent - User agent to use in puppeteer
33
- * @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers
33
+ * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting videos to webp while sending stickers
34
34
  * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
35
35
  * @param {object} options.proxyAuthentication - Proxy Authentication object.
36
36
  *
@@ -43,6 +43,8 @@ const NoAuth = require('./authStrategies/NoAuth');
43
43
  * @fires Client#message_create
44
44
  * @fires Client#message_revoke_me
45
45
  * @fires Client#message_revoke_everyone
46
+ * @fires Client#message_ciphertext
47
+ * @fires Client#message_edit
46
48
  * @fires Client#media_uploaded
47
49
  * @fires Client#group_join
48
50
  * @fires Client#group_leave
@@ -658,6 +660,16 @@ class Client extends EventEmitter {
658
660
  */
659
661
  this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
660
662
  });
663
+
664
+ await page.exposeFunction('onAddMessageCiphertextEvent', msg => {
665
+
666
+ /**
667
+ * Emitted when messages are edited
668
+ * @event Client#message_ciphertext
669
+ * @param {Message} message
670
+ */
671
+ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
672
+ });
661
673
 
662
674
  await page.evaluate(() => {
663
675
  window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
@@ -665,7 +677,7 @@ class Client extends EventEmitter {
665
677
  window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
666
678
  window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); });
667
679
  window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); });
668
- window.Store.Msg.on('change:body', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); });
680
+ window.Store.Msg.on('change:body change:caption', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); });
669
681
  window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
670
682
  window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
671
683
  window.Store.Call.on('add', (call) => { window.onIncomingCall(call); });
@@ -676,6 +688,7 @@ class Client extends EventEmitter {
676
688
  if(msg.type === 'ciphertext') {
677
689
  // defer message event until ciphertext is resolved (type changed)
678
690
  msg.once('change:type', (_msg) => window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg)));
691
+ window.onAddMessageCiphertextEvent(window.WWebJS.getMessageModel(msg));
679
692
  } else {
680
693
  window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
681
694
  }
@@ -891,7 +904,7 @@ class Client extends EventEmitter {
891
904
 
892
905
 
893
906
  if (sendSeen) {
894
- window.WWebJS.sendSeen(chatId);
907
+ await window.WWebJS.sendSeen(chatId);
895
908
  }
896
909
 
897
910
  const msg = await window.WWebJS.sendMessage(chat, message, options, sendSeen);
@@ -431,7 +431,7 @@ class Message extends Base {
431
431
 
432
432
  const result = await this.client.pupPage.evaluate(async (msgId) => {
433
433
  const msg = window.Store.Msg.get(msgId);
434
- if (!msg) {
434
+ if (!msg || !msg.mediaData) {
435
435
  return undefined;
436
436
  }
437
437
  if (msg.mediaData.mediaStage != 'RESOLVED') {
@@ -534,15 +534,20 @@ class Message extends Base {
534
534
  */
535
535
 
536
536
  /**
537
- * Get information about message delivery status. May return null if the message does not exist or is not sent by you.
537
+ * Get information about message delivery status.
538
+ * May return null if the message does not exist or is not sent by you.
538
539
  * @returns {Promise<?MessageInfo>}
539
540
  */
540
541
  async getInfo() {
541
542
  const info = await this.client.pupPage.evaluate(async (msgId) => {
542
543
  const msg = window.Store.Msg.get(msgId);
543
- if (!msg) return null;
544
+ if (!msg || !msg.id.fromMe) return null;
544
545
 
545
- return await window.Store.MessageInfo.sendQueryMsgInfo(msg.id);
546
+ return new Promise((resolve) => {
547
+ setTimeout(async () => {
548
+ resolve(await window.Store.getMsgInfo(msg.id));
549
+ }, (Date.now() - msg.t * 1000 < 1250) && Math.floor(Math.random() * (1200 - 1100 + 1)) + 1100 || 0);
550
+ });
546
551
  }, this.id._serialized);
547
552
 
548
553
  return info;
@@ -639,7 +644,7 @@ class Message extends Base {
639
644
  let msg = window.Store.Msg.get(msgId);
640
645
  if (!msg) return null;
641
646
 
642
- let catEdit = (msg.type === 'chat' && window.Store.MsgActionChecks.canEditText(msg));
647
+ let catEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg);
643
648
  if (catEdit) {
644
649
  const msgEdit = await window.WWebJS.editMessage(msg, message, options);
645
650
  return msgEdit.serialize();
@@ -44,6 +44,7 @@ exports.Events = {
44
44
  CHAT_REMOVED: 'chat_removed',
45
45
  CHAT_ARCHIVED: 'chat_archived',
46
46
  MESSAGE_RECEIVED: 'message',
47
+ MESSAGE_CIPHERTEXT: 'message_ciphertext',
47
48
  MESSAGE_CREATE: 'message_create',
48
49
  MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone',
49
50
  MESSAGE_REVOKED_ME: 'message_revoke_me',
@@ -23,7 +23,6 @@ exports.ExposeStore = (moduleRaidStr) => {
23
23
  window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0];
24
24
  window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0];
25
25
  window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default;
26
- window.Store.MessageInfo = window.mR.findModule('sendQueryMsgInfo')[0];
27
26
  window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default;
28
27
  window.Store.QueryProduct = window.mR.findModule('queryProduct')[0];
29
28
  window.Store.QueryOrder = window.mR.findModule('queryOrder')[0];
@@ -51,6 +50,7 @@ exports.ExposeStore = (moduleRaidStr) => {
51
50
  window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0];
52
51
  window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0];
53
52
  window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0];
53
+ window.Store.LinkPreview = window.mR.findModule('getLinkPreview')[0];
54
54
  window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0];
55
55
  window.Store.SocketWap = window.mR.findModule('wap')[0];
56
56
  window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext;
@@ -58,16 +58,17 @@ exports.ExposeStore = (moduleRaidStr) => {
58
58
  window.Store.LidUtils = window.mR.findModule('getCurrentLid')[0];
59
59
  window.Store.WidToJid = window.mR.findModule('widToUserJid')[0];
60
60
  window.Store.JidToWid = window.mR.findModule('userJidToUserWid')[0];
61
- window.Store.Settings = {
62
- ...window.mR.findModule('ChatlistPanelState')[0],
63
- setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname
64
- };
65
61
 
66
62
  /* eslint-disable no-undef, no-cond-assign */
67
63
  window.Store.QueryExist = ((m = window.mR.findModule('queryExists')[0]) ? m.queryExists : window.mR.findModule('queryExist')[0].queryWidExists);
68
64
  window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 && m[0];
65
+ window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo;
69
66
  /* eslint-enable no-undef, no-cond-assign */
70
67
 
68
+ window.Store.Settings = {
69
+ ...window.mR.findModule('ChatlistPanelState')[0],
70
+ setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname
71
+ };
71
72
  window.Store.StickerTools = {
72
73
  ...window.mR.findModule('toWebpSticker')[0],
73
74
  ...window.mR.findModule('addWebpMetadata')[0]
@@ -107,12 +108,6 @@ exports.ExposeStore = (moduleRaidStr) => {
107
108
  // eslint-disable-next-line no-undef
108
109
  if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find;
109
110
 
110
- // TODO remove these once everybody has been updated to WWebJS with legacy sessions removed
111
- const _linkPreview = window.mR.findModule('queryLinkPreview');
112
- if (_linkPreview && _linkPreview[0] && _linkPreview[0].default) {
113
- window.Store.Wap = _linkPreview[0].default;
114
- }
115
-
116
111
  const _isMDBackend = window.mR.findModule('isMDBackend');
117
112
  if(_isMDBackend && _isMDBackend[0] && _isMDBackend[0].isMDBackend) {
118
113
  window.Store.MDBackend = _isMDBackend[0].isMDBackend();
@@ -128,9 +123,9 @@ exports.ExposeStore = (moduleRaidStr) => {
128
123
  /**
129
124
  * Target options object description
130
125
  * @typedef {Object} TargetOptions
131
- * @property {string|number} moduleId The name or a key of the target module to search
126
+ * @property {string|number} module The name or a key of the target module to search
132
127
  * @property {number} index The index value of the target module
133
- * @property {string} property The function name to get from a module
128
+ * @property {string} function The function name to get from a module
134
129
  */
135
130
 
136
131
  /**
@@ -139,17 +134,17 @@ exports.ExposeStore = (moduleRaidStr) => {
139
134
  * @param {Function} callback Modified function
140
135
  */
141
136
  window.injectToFunction = (target, callback) => {
142
- const module = typeof target.moduleId === 'string'
143
- ? window.mR.findModule(target.moduleId)
144
- : window.mR.modules[target.moduleId];
145
- const originalFunction = module[target.index][target.property];
137
+ const module = typeof target.module === 'string'
138
+ ? window.mR.findModule(target.module)
139
+ : window.mR.modules[target.module];
140
+ const originalFunction = module[target.index][target.function];
146
141
  const modifiedFunction = (...args) => callback(originalFunction, ...args);
147
- module[target.index][target.property] = modifiedFunction;
142
+ module[target.index][target.function] = modifiedFunction;
148
143
  };
149
144
 
150
- window.injectToFunction({ moduleId: 'mediaTypeFromProtobuf', index: 0, property: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
145
+ window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
151
146
 
152
- window.injectToFunction({ moduleId: 'typeAttributeFromProtobuf', index: 0, property: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
147
+ window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
153
148
  };
154
149
 
155
150
  exports.LoadUtils = () => {
@@ -176,9 +171,7 @@ exports.LoadUtils = () => {
176
171
  forceGif: options.sendVideoAsGif
177
172
  });
178
173
 
179
- if (options.caption){
180
- attOptions.caption = options.caption;
181
- }
174
+ attOptions.caption = options.caption;
182
175
  content = options.sendMediaAsSticker ? undefined : attOptions.preview;
183
176
  attOptions.isViewOnce = options.isViewOnce;
184
177
 
@@ -271,15 +264,14 @@ exports.LoadUtils = () => {
271
264
 
272
265
  if (options.linkPreview) {
273
266
  delete options.linkPreview;
274
-
275
- // Not supported yet by WhatsApp Web on MD
276
- if(!window.Store.MDBackend) {
277
- const link = window.Store.Validators.findLink(content);
278
- if (link) {
279
- const preview = await window.Store.Wap.queryLinkPreview(link.url);
267
+ const link = window.Store.Validators.findLink(content);
268
+ if (link) {
269
+ let preview = await window.Store.LinkPreview.getLinkPreview(link);
270
+ if (preview && preview.data) {
271
+ preview = preview.data;
280
272
  preview.preview = true;
281
273
  preview.subtype = 'url';
282
- options = { ...options, ...preview };
274
+ options = {...options, ...preview};
283
275
  }
284
276
  }
285
277
  }
@@ -379,17 +371,13 @@ exports.LoadUtils = () => {
379
371
  }
380
372
 
381
373
  if (options.linkPreview) {
382
- options.linkPreview = null;
383
-
384
- // Not supported yet by WhatsApp Web on MD
385
- if(!window.Store.MDBackend) {
386
- const link = window.Store.Validators.findLink(content);
387
- if (link) {
388
- const preview = await window.Store.Wap.queryLinkPreview(link.url);
389
- preview.preview = true;
390
- preview.subtype = 'url';
391
- options = { ...options, ...preview };
392
- }
374
+ delete options.linkPreview;
375
+ const link = window.Store.Validators.findLink(content);
376
+ if (link) {
377
+ const preview = await window.Store.LinkPreview.getLinkPreview(link);
378
+ preview.preview = true;
379
+ preview.subtype = 'url';
380
+ options = { ...options, ...preview };
393
381
  }
394
382
  }
395
383