Rich Deployment Messages on Discord

Currently we're using discord-action to post successful deployments on our IPFS gateway in our Discord. The messages are a simple string and lack information:

Yeah okay but uh... huh?
Yeah okay but uh... huh?

It gives users a simple information without enabling less technically adept users to understand what they are supposed to do with this message. The basic idea was to enrich the message with information about how to host your own IPFS node and pin the CID so it becomes redundantly available in the IPFS network.

Discord-Action

The action is using a webhook to push updates to our Discord's change-logs channel. It takes a simple string argument to craft the payload so it's not possible to post rich messages:

const core = require('@actions/core');
const axios = require('axios');

try {
  const webhook = core.getInput('webhook');
  const message = core.getInput('message');

  const options = {
    method: 'POST',
    url: webhook,
    headers: {'Content-Type': 'application/json'},
    data: {content: message}
  };

  axios
    .request(options)
    .then(() => {
      core.info('Message was sent successfully');
    });
} catch (error) {
  core.setFailed(error.message);
}

As can be seen the data key inside the options object only contains the Github input message, which is not strictly defined as a string, but everything passed into the action through that input will be treated as such. So to enable a rich message, we need to fork the action and modify it to allow us to post our desired output.

Rich Messages

According to Discord Developer Portal we can pass a specific object called "Embed Object" as our webhook message to create rich previews that allow for interactive content:

Built with https://autocode.com/tools/discord/embed-builder/
Built with https://autocode.com/tools/discord/embed-builder/

Generated code for the above screenshot:

const lib = require('lib')({token: process.env.STDLIB_SECRET_TOKEN});

await lib.discord.channels['@0.3.0'].messages.create({
  "channel_id": `${context.params.event.channel_id}`,
  "content": "",
  "tts": false,
  "components": [
    {
      "type": 1,
      "components": [
        {
          "style": 5,
          "label": `Learn about IPFS`,
          "url": `https://en.wikipedia.org/wiki/InterPlanetary_File_System`,
          "disabled": false,
          "type": 2
        },
        {
          "style": 5,
          "label": `Run your own node`,
          "url": `https://docs.ipfs.tech/install/ipfs-desktop/`,
          "disabled": false,
          "type": 2
        }
      ]
    }
  ],
  "embeds": [
    {
      "type": "rich",
      "title": `New IPFS deployment successful!`,
      "description": "",
      "color": 0xa100c1,
      "fields": [
        {
          "name": `CID`,
          "value": `QmexvqKGREpf2hhvpUpCXGjPQhvnDLDRibqZcxh4ffk2CQ`,
          "inline": true
        },
        {
          "name": `Commit`,
          "value": `c576b9fdcd3fef8330de38af4f86b5f6e6f8e31f`,
          "inline": true
        }
      ],
      "url": `https://ipfs.alchemix.fi/`
    }
  ]
});

So all we have to do is to adjust the data value from the forked repo like so:

const core = require('@actions/core');
const axios = require('axios');

try {
  const webhook = core.getInput('webhook');
  const cid = core.getInput('cid');
  const commit = core.getInput('commit');

  const options = {
    method: 'POST',
    url: webhook,
    headers: {'Content-Type': 'application/json'},
    data: {
        embeds: [
            {
                "type": "rich",
                "title": `New IPFS deployment successful!`,
                "description": "",
                "color": 0xee3da4,
                "fields": [
                    {
                        "name": `CID`,
                        "value": cid,
                        "inline": true
                    },
                    {
                        "name": `Commit`,
                        "value": commit,
                        "inline": true
                    }
                ],
                "url": `https://ipfs.alchemix.fi/`
            }
        ]
    }
  };

We now have two new constants cid and commit which receive values from the inputs which we also have to adjust inside the action.yml of the forked repo:

...
inputs:
  webhook:
    description: 'Discord webhook URL'
    required: true
  cid:
    description: 'CID of the deployment'
    required: true
  commit:
    description: 'Github commit that triggered this deployment'
    required: true
...

Now all that's left to do is to publish the action on the marketplace and adjust the CD pipeline to make use of the new action:

...  
  deploy:
    name: Deploy
    needs: build
    runs-on: ubuntu-latest

    steps:
      - name: Fetch artifact
        uses: actions/download-artifact@v2
        with:
          name: production-files
          path: ./dist
      
      - name: Managa Pinata pin and gateway
        id: pinata-manager
        uses: n4n0GH/pinata-manager@main
        with:
          path: ./dist
          secret: ${{secrets.PINATA_SECRET}}
          key: ${{secrets.PINATA_KEY}}
          token: ${{secrets.PINATA_TOKEN}}
          pinName: "Alchemix v2"
          unpinOld: "true"
          gatewayName: "alchemix"
          gatewayId: ${{secrets.PINATA_GATEWAY_ID}}
          
      - name: Notify Discord Channel
        uses: n4n0GH/discord-ipfs-action@v1.0.0
        with:
          webhook: ${{secrets.DISCORD_WEBHOOK}}
          cid: ${{steps.pinata-manager.outputs.cid}}
          commit: ${{github.sha}}
...

And we're done!

Aah... but something is off...
Aah... but something is off...

But What About The Buttons?

Ah, the buttons. You see, we properly implemented the buttons using Discord's components parameter here but because the webhook is made by a user and not application-owned (i.e. created by a bot), Discord is not rendering buttons as a security measure, as documented on the Discord Developer Portal. So if at some point we'll switch the webhook to an application-owned one, the buttons will show up as well.