How to send Google Form results to Discord through Webhooks

Hey! Have a group and want to review Google Form applications in Discord? Well here is a tutorial showing you just how to do that!

Step 1 - Creating a Webhook :incoming_envelope:

To create a Webhook, go to the channel you want the applications to appear and click Edit Channel

image

Once you are there, go under the category Integrations and create a brand new Webhook. You can change the name and profile picture to whatever you would like.

image

Step 2 - Forms :incoming_envelope:

Create a new google form or edit a pre-existing one. Once you are there, click on the 3 dots at the top and click on Script Editor

You will then see a screen that says Select a project to open. Create a new project. Name the project whatever you would like. You should then see code that looks something like below:

function myFunction() {
  
}

Delete that and then copy the code below and paste.

const webhooks = ["Webhook URL"];

const title = "Title", avatarImage = "", shortDescription = "Description", colour = "#338ccc", mention = "/User ID", type = ""; 

const bonusFeatures = {
    convert2Link: 'ON', // Links in embeds will be clickable | Text links won't embed.
    convert2Mention: 'ON' // Checks if there's a Discord ID, and converts it into a mention.
}

const form = FormApp.getActiveForm(), allResponses = form.getResponses(), latestResponse = allResponses[allResponses.length - 1];
let response;
var items = [];

// Checks if there's a response, if not - throw error.
try {
    response = latestResponse.getItemResponses()
} catch (error) {
    throw "No Responses found in your form."
}

// Just a safe check to make sure you've entered a webhook.
for (const hook of webhooks) {
    if (!/^(?:https?:\/\/)?(?:www\.)?(?:(?:canary|ptb)\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w-+]+$/i.test(hook)) throw `Webhook ${i + 1 || 1} is not valid.`;
}

// An extra check as people have been having issues.
if (avatarImage && !/\.(jpeg|jpg|gif|png)$/.test(avatarImage)) throw "Image URL is not a direct link";


// This loops through our latest response and fetches the Question titles/answers; then stores them in the items array above.
for (var i = 0; i < response.length; i++) {
    const question = response[i].getItem().getTitle(), answer = response[i].getResponse();
    if (answer == "") continue;
    items.push({ "name": question, "value": answer });

    function data(item) {
        const linkValidate = /(?:(?:https?|http?):\/\/)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#\/%=~_|$])/i;

        // Checks if there's an ID, if there is, convert into a mention.
        if (bonusFeatures.convert2Mention == 'ON' && !isNaN(item.value) && item.value.length == 18) item.value = `<@!${item.value}>`;

        // Checks if type is text, or default to embed.
        if (bonusFeatures.convert2Link == 'ON' && type.toLowerCase() !== 'text') {
            // Validates the link, if it's true, make it a hyperlink for embed.
            if (linkValidate.test(item.value)) item.value = `[${item.value}](${item.value})`;
        } else {
            // Validates the link, if it's true, make it not-embed for text.
            if (bonusFeatures.convert2Link == 'ON' && linkValidate.test(item.value)) item.value = `<${item.value}>`;
        }

        return [`**${item.name}**`, `${item.value}`].join("\n");
    }
}

if (items.map(data).toString().length + shortDescription.length > 1999) throw "Discord limit reached. Please add limits to your questions!";

function plainText(e) {

    // A webhook construct, which sets up the correct formatting for sending to Discord.
    const text = {
        "method": "post",
        "headers": { "Content-Type": "application/json" },
        "muteHttpExceptions": true,
        "payload": JSON.stringify({
            "content": `${mention ? mention : ''}${title ? `**${title}**` : `**${form.getTitle()}**`}\n\n${shortDescription ? `${shortDescription}\n\n${items.map(data).join('\n\n')}` : items.map(data).join('\n\n')}`
        }),
    };

    // We now loop through our webhooks and send them one by one to the respectful channels.
    for (var i = 0; i < webhooks.length; i++) { UrlFetchApp.fetch(webhooks[i], text); };
}

function embedText(e) {

    // A webhook embed construct, which sets up the correct formatting for sending to Discord.
    const embed = {
        "method": "post",
        "headers": { "Content-Type": "application/json" },
        "muteHttpExceptions": true,
        "payload": JSON.stringify({
            "content": mention ? mention : '',
            "embeds": [{
                "title": title ? title : form.getTitle(), // Either the set title or the forms title.
                "description": shortDescription ? `${shortDescription}\n\n${items.map(data).join('\n\n')}` : items.map(data).join('\n\n'), // Either the desc or just the res.
                "thumbnail": { url: avatarImage ? encodeURI(avatarImage) : null }, // The tiny image in the right of the embed
                "color": colour ? parseInt(colour.substr(1), 16) : Math.floor(Math.random() * 16777215), // Either the set colour or random.
                "timestamp": new Date().toISOString() // Today's date.
            }]
        }),
    };

    // We now loop through our webhooks and send them one by one to the respectful channels.
    for (var i = 0; i < webhooks.length; i++) { UrlFetchApp.fetch(webhooks[i], embed); };

At the top, get your Webhook URL. Then paste it where it says const webhooks = ["Webhook URL"]; The link will go inside of the [" "].

Once that is pasted in, press save at the top.

image

Step 3 - Making a trigger :incoming_envelope:

Go to the icon that looks like a timer and click on it. Then, create a new trigger.

Make it look like this and then save.

And just like that, you should be good to go!

6 Likes

This is a great and straightforward tutorial. Thanks!

3 Likes

Wow!

We can now link our Cookie Tech bot to our google forms!

Sooo useful. :slight_smile:

4 Likes

ok but in all serious was a good tutorial

2 Likes

@Conos I believe there is a mistake, it should be notify me immediately

2 Likes

I will try it soon. This may help a lot so thanks.

1 Like

No problem. (20 char)

just a notice to all that are trying this there is a small mistake image
it shouldnt be notify me daily rather notify me immediately so if you’re doing this please remember to change that to “Notify me immediately”

2 Likes

Wowwww! Thank you so much! This is so helpful Ty!

WOAAH! So cool, I’ve never thought you could do this before! Great tutorial!


Getting this error when I’m trying to save it

Can you check that you have properly copied & followed the guide?

Ok so I am getting this error when trying to save it and yes I copied and pasted exactly as it was and added my discord web hook at the top.

This is an old post, it may not work anymore.

This does not work anymore, follow the steps and put this in instead:


const webhooks = [ "" ];

/* Start of optional section */
const title = ""; /*    Add a nice custom title, to make the script truly yours.    */
const avatarImage = ""; /*    The logo of your brand or Discord server, maybe?    */
const shortDescription = ""; /*    A little bit of information about the response received, so you don't forget in the future?    */
const colour = ""; /*    A custom colour? Example: #78A8C6    */
const mention = ""; /*Mention yourself or a role - it should look like <@!7890975289098612689> or <@&7890975289098612689>    */
/* End of optional section */


const form = FormApp.getActiveForm();
const allResponses = form.getResponses();
const latestResponse = allResponses[ allResponses.length - 1 ];
const response = latestResponse.getItemResponses();
var items = [];

if ( !webhooks ) throw "You forgot the webhook :)";

function embedText( e ) {
    for ( var i = 0; i < response.length; i++ ) {
        const question = response[ i ].getItem().getTitle();
        const answer = response[ i ].getResponse();
        if ( answer == "" ) continue;
        items.push( { "name": question, "value": answer } );
        function data( item ) { return [ `**${ item.name }**`,`${ item.value }` ].join( "\n" ); }
    }

    try {
      if ( avatarImage !== null ) {
          const embedSetup = { "method": "post", "headers": { "Content-Type": "application/json" }, muteHttpExceptions: true, "payload": JSON.stringify( { "content": ( mention ) ? `${ mention }` : " ", "embeds": [ { "title": ( title ) ? title : form.getTitle(), "thumbnail": { "url": encodeURI( avatarImage ) }, "color": ( colour ) ? parseInt(colour.substr(1), 16) : Math.floor( Math.random() * 16777215 ), "description": ( shortDescription ) ? `${ shortDescription }\n\n${ items.map( data ).join( '\n\n' ) }` : items.map( data ).join( '\n\n' ), "timestamp": new Date().toISOString(), } ] } ) };
          for ( var i = 0; i < webhooks.length; i++ ) { UrlFetchApp.fetch( webhooks[ i ], embedSetup ); }
          return form.deleteResponse( latestResponse.getId() );
      } else {
          const embedSetup = { "method": "post", "headers": { "Content-Type": "application/json" }, muteHttpExceptions: true, "payload": JSON.stringify( { "content": ( mention ) ? `${ mention }` : " ", "embeds": [ { "title": ( title ) ? title : form.getTitle(), "color": ( colour ) ? parseInt(colour.substr(1), 16) : Math.floor( Math.random() * 16777215 ), "description": ( shortDescription ) ? `${ shortDescription }\n\n${ items.map( data ).join( '\n\n' ) }` : items.map( data ).join( '\n\n' ), "timestamp": new Date().toISOString(), } ] } ) };
          for ( var i = 0; i < webhooks.length; i++ ) { UrlFetchApp.fetch( webhooks[ i ], embedSetup ); }
          return form.deleteResponse( latestResponse.getId() );
      }
    } catch(error) {
      return Logger.log(error);
    }
}


2 Likes

Does this output as the description or does each question get its own field?