itsMapleLeaf / reacord

Create interactive Discord messages using React. ⚛
https://reacord.mapleleaf.dev/
MIT License
754 stars 13 forks source link

Interaction has already been acknowledged or Unknown interaction #26

Closed BlacKlyExactly closed 1 year ago

BlacKlyExactly commented 1 year ago

Hi. Im making a tic tac toe discord bot. Im using zustand to manage invitations and game state.

Every time i click any button from Board component i got one of two errors:

(Invitations works as expected and displays board correctly)

/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:687
      throw new DiscordAPIError(data, "code" in data ? data.code : data.error, status, method, url, requestData);
            ^

DiscordAPIError[40060]: Interaction has already been acknowledged.
    at handleErrors (/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:687:13)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at BurstHandler.runRequest (/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:786:23)
    at _REST.request (/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:1218:22)
    at ButtonInteraction.deferUpdate (/home/black/Projects/reacord-test/node_modules/.pnpm/discord.js@14.13.0/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:200:5)
    at Object.deferUpdate (/home/black/Projects/reacord-test/node_modules/.pnpm/reacord@0.5.2_2sdlrk3sndnc3ryoat4rv6qpma/node_modules/reacord/library/core/reacord-discord-js.ts:241:9)
    at ChannelMessageRenderer.updateMessage (/home/black/Projects/reacord-test/node_modules/.pnpm/reacord@0.5.2_2sdlrk3sndnc3ryoat4rv6qpma/node_modules/reacord/library/internal/renderers/renderer.ts:101:7) {
  requestBody: { files: undefined, json: { type: 6 } },
  rawError: {
    message: 'Interaction has already been acknowledged.',
    code: 40060
  },
  code: 40060,
  status: 400,
  method: 'POST',
  url: 'https://discord.com/api/v10/interactions/1156629104301441024/aW50ZXJhY3Rpb246MTE1NjYyOTEwNDMwMTQ0MTAyNDp3TFdzcFhjN0JqQ1VrN3NLUG9EQnlhY01BeXR0TGRKRTBoWXRRZ2VpTnBya1pCZWNvYzBpbVZROEdFbmhnY1VZYTI1UWdGM1dZS0taYm9XSmc1MElrNVlQM056TVI2dHluOWZXbWppcHJid0RwRTE2Q2NCT3RyNFVlZEF3YXFkVA/callback'
}
/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:687
      throw new DiscordAPIError(data, "code" in data ? data.code : data.error, status, method, url, requestData);
            ^

DiscordAPIError[10062]: Unknown interaction
    at handleErrors (/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:687:13)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at BurstHandler.runRequest (/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:786:23)
    at _REST.request (/home/black/Projects/reacord-test/node_modules/.pnpm/@discordjs+rest@2.0.1/node_modules/@discordjs/rest/dist/index.js:1218:22)
    at ButtonInteraction.deferUpdate (/home/black/Projects/reacord-test/node_modules/.pnpm/discord.js@14.13.0/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:200:5)
    at Object.deferUpdate (/home/black/Projects/reacord-test/node_modules/.pnpm/reacord@0.5.2_2sdlrk3sndnc3ryoat4rv6qpma/node_modules/reacord/library/core/reacord-discord-js.ts:241:9) 
    at ChannelMessageRenderer.updateMessage (/home/black/Projects/reacord-test/node_modules/.pnpm/reacord@0.5.2_2sdlrk3sndnc3ryoat4rv6qpma/node_modules/reacord/library/internal/renderers/renderer.ts:101:7) {
  requestBody: { files: undefined, json: { type: 6 } },
  rawError: { message: 'Unknown interaction', code: 10062 },
  code: 10062,
  status: 404,
  method: 'POST',
  url: 'https://discord.com/api/v10/interactions/1156632450626228256/aW50ZXJhY3Rpb246MTE1NjYzMjQ1MDYyNjIyODI1NjoxT1FaRFRvUUZjbGxlaEZxYXlvWkNPR3BhN2RMNEZsbUY0WmpBdVRsMlRMejZGeDlqeHBFWlRqNlFaaWpqMTREVEpkaWtlRkxFbkVYQkhPTEdLYW4ybFFWRVplUXV3SVA3RTVlRWdoSzJQWTVPM25DTURSUk9kamx6QjFZV3o1NA/callback'
}

Board component

import { Button, ActionRow, ComponentEvent } from "reacord";

import useGameStore, { Mark } from "../stores/game";

const labels: { [key in Mark]: string } = {
  [Mark.None]: "­",
  [Mark.O]: "⭕",
  [Mark.X]: "❌",
};

const Board = ({ gameId }: BoardProps) => {
  const { setMark, games } = useGameStore(({ setMark, games }) => ({
    setMark,
    games,
  }));

  const game = games.find(({ id }) => id === gameId)!;

  const handleButtonClick = ({ user }: ComponentEvent, idx: number) => {
    if (turn.user.id !== user.id || map[idx].player) return;

    setMark({
      playerId: user.id,
      gameId,
      index: idx,
    });
  };

  const { map, turn } = game;

  const rows = [map.slice(0, 3), map.slice(3, 6), map.slice(6, 9)].map(
    (row, idx) => ({ row, rowIndex: idx })
  );

  return (
    <>
      **Turn**: {turn.user.displayName}
      {rows.map(({ row, rowIndex }) => (
        <ActionRow key={rowIndex}>
          {row.map(({ mark, id }) => (
            <Button
              key={id}
              label={labels[mark]}
              onClick={(e) => handleButtonClick(e, id)}
            />
          ))}
        </ActionRow>
      ))}
    </>
  );
};

type BoardProps = {
  gameId: string;
};

export default Board;

Invitation component

import { User } from "discord.js";

import { Button, useInstance } from "reacord";

import useInvitationStore from "../stores/invitation";
import useGameStore from "../stores/game";

import Board from "./Board";

import sendDirect from "../utils/sendDirect";

const Invitation = ({ challengedUser, sender }: InvitationProps) => {
  const instance = useInstance();

  const removeInvitation = useInvitationStore(
    ({ removeInvitation }) => removeInvitation
  );

  const addGame = useGameStore(({ addGame }) => addGame);

  const handleDeclineClick = () => {
    removeInvitation(sender.id, challengedUser.id);
    sender.send(`**${challengedUser.tag}** odrzucił twoje wyzwanie.`);
    instance.destroy();
  };

  const handleAcceptClick = async () => {
    removeInvitation(sender.id, challengedUser.id);

    const gameId = addGame(sender, challengedUser);

    await sendDirect(sender, <Board gameId={gameId} />);
    instance.render(<Board gameId={gameId} />);
  };

  return (
    <>
      **{sender.tag}** wysyła ci zaproszenie do gry
      <Button label="Akceptuj" onClick={handleAcceptClick} style="success" />
      <Button label="Odrzuć" onClick={handleDeclineClick} style="danger" />
    </>
  );
};

type InvitationProps = {
  sender: User;
  challengedUser: User;
};

export default Invitation;

sendDirect function

import { ReactNode } from "react";
import { User } from "discord.js";
import { reacord } from "..";

const sendDirect = async (user: User, Component: ReactNode) => {
  const dm = await user.createDM();
  reacord.send(dm.id, Component);
};

export default sendDirect;
itsMapleLeaf commented 1 year ago

I haven't tested Reacord extensively with DMs, so there might be an unhandled issue with that flow

BlacKlyExactly commented 1 year ago

I haven't tested Reacord extensively with DMs, so there might be an unhandled issue with that flow

Im gonna try in server text channel

Update:

Same error in server text channel If you wish i can push code to the repo

itsMapleLeaf commented 1 year ago

Actually, I think I might see the issue:

  const instance = useInstance();

  // ...

  const handleAcceptClick = async () => {
    // ...
    instance.render(<Board gameId={gameId} />);
  };

While this technically works, useInstance was made primarily for self-destroying messages, and calling render on this effectively erases itself, which might mess up the instance's data flow.

Try this instead:

  const handleAcceptClick = async () => {
    // ...
    reacord.send(currentChannelId, <Board gameId={gameId} />);
    instance.destroy()
  };

And you'll probably have to pass currentChannelId to the component as a prop

BlacKlyExactly commented 1 year ago

I rewrote the function and passed challengedChannelId and senderChannelId as props but still same error

const handleAcceptClick = async () => {
    removeInvitation(sender.id, challengedUser.id);

    const gameId = addGame(sender, challengedUser);

    reacord.send(senderChannelId, <Board gameId={gameId} />);
    reacord.send(challengedChannelId, <Board gameId={gameId} />);

    instance.destroy();
  };
itsMapleLeaf commented 1 year ago

Oof

Yeah, go ahead and upload the repo. I'll look into this later

BlacKlyExactly commented 1 year ago

https://github.com/BlacKlyExactly/tic-tac-toe-bot And thanks for help :)

I reverted some changes from ealier

itsMapleLeaf commented 1 year ago

Alright, I made some renderer fixes in #30 that should resolve this, give it a try 🙂

BlacKlyExactly commented 1 year ago

Now It's working as expected. Thanks for help!