Lodash.cloneDeep: unable to edit property of cloned object [discord.js execute command]

I’m currently trying to make an ~execute command for my bot. This command will allow me to execute commands as other users, thereby allowing to provide better debugging.

An example of this would be something like:

Example of the ~execute command in use, on an already-working instance.

In essence, what I want/need to do in this command is:

  1. grab the message object
  2. clone the message object
  3. edit the cloned message object such that its author and member are different, but everything else remains the same.
  4. pass this new object through client.emit("messageCreate", <NewMessageObject>).

I’ve tried the following…

JSON.parse(JSON.stringify(…))

This one was actually the most promising, however, it did not copy over all the "special" stiff -- during my research, I learned that this method only works well for primitive data types.

const cloned = JSON.parse(JSON.stringify(message));
// the normal functions/methods such as message.reply and message.guild.members.fetch were undefined.

By “most promising”, I mean that this method actually copied the guild object and everything else.

Note that JSON method will loose any Javascript types that have no equivalent in JSON. For example: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false})) will generate {a: null, b: null, c: null, g: false} – @oriadam

Source: a comment on this question (What is the most efficient way to deep clone an object in JavaScript?)

Lodash’s cloneDeep()

I found this question (What is the most efficient way to deep clone an object in JavaScript?), and saw this. I tried it, however, it doesn’t seem to work. By “doesn’t seem to work”, I mean that when I attempt to reassign certain properties, it doesn’t do anything. Here’s my current code, which shuold hopefully make it easier to understand what I mean:

        // this is the code inside my command. I've removed the fluff to make it easier to read. It is inside an async function.
        const { cloneDeep } = require("lodash");
        const user = await client.config.fetchUser(args[0]); // works fine -- identifies user perfectly. eg User { id: '' ... }
        if (!user) return message.reply({ content: `Invalid user "${args[0]}"`, allowedMentions: { parse: [] } });
        console.log(cloneDeep(message)) // again, this bit is also fine - it logs a message object.
        const cloned = cloneDeep(message);
        cloned.author = user;
        console.log(cloned.author); // everything works fine - the user is changed fine.
        cloned.content = message.guild.prefix + args.slice(1).join(" ");
        cloned.emit = true;
        client.emit("messageCreate", cloned);

Note: the only 2 properties that were changed are: author and content. Everything else in the message object should remain the same, such as guild, channel, etc.

I’m getting this error:

TypeError: Cannot read properties of undefined (reading 'guilds') [index.js:478:15]

enter image description here

The error is pointing to this line in the messageCreate event:

    if (!message.guild || (message.author.bot && (!cst.includes("wl"))) || (message.system) || (message.webhookId)) return;

From what I gathered, in this context, the error basically meant that message.guild (and, in turn) cloned.guild was undefined. (as we were parsing the cloned object into the messageCreate event. This isn’t an issue with anything else, as all other commands work fine. Even ones done in dms.)

Next, I added console.log(cloned.guild) to the code, expecting a Guild object. Instead, I got the same error message: TypeError: Cannot read properties of undefined (reading 'guilds').

Simply adding cloned.guild = message.guild; has not seemed to fix the issue. It has resulted in the same error. I have checked the value of message.guild, and it is indeed a Guild object.

Here’s the final code which I’m currently working with:

./cmds/execute.js

const { Message } = require("discord.js");
const { cloneDeep } = require("lodash");

module.exports = {
    name: "execute",
    aliases: ["execute", "exec"],
    async run(client, message, args) {
        if (args.length < 2) return message.reply("You must specify a user and a command to execute as the user in order for this command to work!");
        const user = await client.config.fetchUser(args[0]);
        if (!user) return message.reply({ content: `Invalid user "${args[0]}"`, allowedMentions: { parse: [] } });
        const cloned = cloneDeep(message);
        cloned.author = user;
        cloned.content = message.guild.prefix + args.slice(1).join(" ");
        cloned.emit = true;
        // works FINE till here. For some reason, cloned.guild just doesn't work...
        cloned.guild = message.guild;
        client.emit("messageCreate", cloned);
    },
};

This is what a properly formed message object should look like:

<ref *1> Message {
  channelId: '911802238442287124',
  guildId: '911784758600679455',
  deleted: false,
  id: '920762096373891134',
  createdTimestamp: 1639597190708,
  type: 'DEFAULT',
  system: false,
  content: '~eval message',
  author: User {
    id: '501710994293129216',
    bot: false,
    system: false,
    flags: [UserFlags],
    username: 'Paradox',
    discriminator: '1234',
    avatar: 'fd6c2479694970c0a357155bd43860d4',
    banner: undefined,
    accentColor: undefined,
    debug: false, // this is a custom property that I've attached myself - isnt here by default. same with color and colors.
    color: 'RANDOM',
    colors: [Array]
  },
  pinned: false,
  tts: false,
  nonce: '920762095794913280',
  embeds: [],
  components: [],
  attachments: Collection(0) [Map] {},
  stickers: Collection(0) [Map] {},
  editedTimestamp: null,
  reactions: ReactionManager { message: [Circular *1] },
  mentions: MessageMentions {
    everyone: false,
    users: Collection(0) [Map] {},
    roles: Collection(0) [Map] {},
    _members: null,
    _channels: null,
    crosspostedChannels: Collection(0) [Map] {},
    repliedUser: null
  },
  webhookId: null,
  groupActivityApplication: null,
  applicationId: null,
  activity: null,
  flags: MessageFlags { bitfield: 0 },
  reference: null,
  interaction: null
}

And this is what the deepCloned one looks like:

<ref *1> Message {
  channelId: '911802238442287124',
  guildId: '911784758600679455',
  deleted: false,
  id: '920762763272392715',
  createdTimestamp: 1639597349709,
  type: 'DEFAULT',
  system: false,
  content: '~bal',
  author: User {
    id: '504619833007013899',
    bot: false,
    system: false,
    flags: UserFlags { bitfield: 0 },
    username: 'ephemeral',
    discriminator: '2341',
    avatar: '2f274547855bf700b44408c593d37cad',
    banner: undefined,
    accentColor: undefined
  },
  pinned: false,
  tts: false,
  nonce: '920762762311761920',
  embeds: [],
  components: [],
  attachments: Collection(0) [Map] {},
  stickers: Collection(0) [Map] {},
  editedTimestamp: null,
  reactions: ReactionManager { message: [Circular *1] },
  mentions: MessageMentions {
    everyone: false,
    users: Collection(0) [Map] {},
    roles: Collection(0) [Map] {},
    _members: Collection(0) [Map] {},
    _channels: null,
    crosspostedChannels: Collection(0) [Map] {},
    repliedUser: null
  },
  webhookId: null,
  groupActivityApplication: null,
  applicationId: null,
  activity: null,
  flags: MessageFlags { bitfield: 0 },
  reference: null,
  interaction: null,
  emit: true
}

PACKAGE.JSON DEPENDENCY VERSIONS:

  "dependencies": {
    "@keyv/mysql": "^1.1.4",
    "@keyv/sqlite": "^2.0.2",
    "babel-plugin-syntax-class-properties": "^6.13.0",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "bootstrap": "^4.5.2",
    "delay": "^4.4.1",
    "discord.js": "^13.0.0",
    "discord.js-reaction-menu": "^1.0.2",
    "dotenv": "^8.2.0",
    "enmap": "github:eslachance/enmap#v3",
    "enmap-mongo": "^2.0.2",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "helmet": "^4.1.1",
    "keyv": "^4.0.0",
    "lodash": "^4.17.21",
    "lodash.clonedeep": "^4.5.0",
    "mathjs": "^7.2.0",
    "moment": "^2.27.0",
    "mongochrome": "0.0.3",
    "mongoose": "^5.10.7",
    "morgan": "^1.10.0",
    "ms": "^2.1.3",
    "mysql": "^2.18.1",
    "node-fetch": "^2.6.0",
    "node-os-utils": "^1.2.2",
    "passport": "^0.4.1",
    "passport-discord": "^0.1.4",
    "pug": "^3.0.0",
    "quickmongo": "^2.0.1",
    "serve-favicon": "^2.5.0",
    "sqlite3": "^5.0.2",
    "statcord.js": "^3.1.0",
    "uptime-robot": "^1.3.0"
  }

node: v16.10.0

I’ve tried my best to construct a good question. Apologies if this is unclear. If any further information is required, please do let me know.