Saturday, 26 December 2020

How to Move Files Uploads from Google Forms to a Specific Folder in Google Drive

The File Upload feature of Google Forms lets you receive files from form respondents directly in your Google Drive. You may add the File Upload question in your Google Form to receive PDF assignments from students, résumé applications, portfolio images from contestants, and so on.

When a respondent uploads a file through Google Forms, the file are stored in a fixed folder of your Google Drive. All files are uploaded in the same folder and, thus looking at the file in your Google Drive, it is difficult to determine which respondent has uploaded which set of files.

We can however use Google Apps Script with Google Form triggers to instantly organize files in Google Drive as soon as they are uploaded by the form respondent. You can change the destination folder where files are stored or create custom folders based on the form response.

Organiza File Uploads in Google Drive

Google Drive Folder

In the following example, we will create a parent folder in Google Drive to house all the uploaded files. Each form response will have its own subfolder and all files for that specific form entry will go in the same folder.

Create Parent Folder

To get started, go to your Google Drive and create a new folder (or use an existing folder). Open the folder and grab the ID of the folder from the browser’s address bar as shown in the screenshot.

Add the Google Script

Next, go to your Google Form that is accepting File Uploads and choose Script Editor from the 3-dot menu.

Google Forms Script Editor

Inside the script editor, remove all the existing code and copy-paste the following snippet. Remember to replace the Folder Id in line #1 with the Id of the folder that you’ve created in the previous step.

const PARENT_FOLDER_ID = "<<Folder ID here>>";

const initialize = () => {
  const form = FormApp.getActiveForm();
  ScriptApp.newTrigger("onFormSubmit").forForm(form).onFormSubmit().create();
};

const onFormSubmit = ({ response } = {}) => {
  try {
    // Get a list of all files uploaded with the response
    const files = response
      .getItemResponses()
      // We are only interested in File Upload type of questions
      .filter(
        (itemResponse) =>
          itemResponse.getItem().getType().toString() === "FILE_UPLOAD"
      )
      .map((itemResponse) => itemResponse.getResponse())
      // The response includes the file ids in an array that we can flatten
      .reduce((a, b) => [...a, ...b], []);

    if (files.length > 0) {
      // Each form response has a unique Id
      const subfolderName = response.getId();
      const parentFolder = DriveApp.getFolderById(PARENT_FOLDER_ID);
      const subfolder = parentFolder.createFolder(subfolderName);
      files.forEach((fileId) => {
        // Move each file into the custom folder
        DriveApp.getFileById(fileId).moveTo(subfolder);
      });
    }
  } catch (f) {
    Logger.log(f);
  }
};

Tip: The script can also be enhanced to create custom folder names based on the user’s answers in the form response.

Create OnFormSubmit Trigger

Inside the script editor, select initialize from the function drop-down and click the Run button to create the OnFormSubmit trigger for your current Google Form.

This will essentially run the Apps Script code whenever someone submits a new form entry and upload files to a specific folder in Google Drive.

OnFormSubmit Trigger

That’s it. Go to your Google Form and submit a new test entry. You should now see all the uploaded files neatly organized in a custom folder under the parent folder. The name of the custom folder is the unique Response Id that Google Forms automatically assigns to every form submission.

PS: You can combine File Uploads in Google Forms with Document Studio to generate customized PDFs (certificates, employee ID cards, etc) from the uploaded images



from Digital Inspiration https://ift.tt/2KscKAV

Friday, 25 December 2020

How to Send a File to Participants After they Submit your Google Form

When someone fills out your Google Form, you may want to send a file or two to the form respondent. For instance:

  1. A school teacher can send a Word document containing a copy of the answers to students who have completed the quiz.
  2. A musician can send her latest composition as an MP3 file to users who have completed her website survey built with Google Forms.
  3. An author can email a preview copy of her upcoming book in PDF to fans who have subscribed to her newsletter.
  4. An online store may send their catalog PDF to customers who have placed orders through Google Forms.

Demo - Send Files with Google Forms

Open this Google Form, type your email address and hit the Submit button. Check your inbox and you should immediately find a PDF copy of our famous 101 useful websites collection.

How to Attach Files to Form Emails

The Email Notifications add-on for Google Forms lets you send customized emails to form respondents after a new entry is received. The same notification can also be customized to include one or more file attachments that are picked from your Google Drive and sent as an attachment in the auto-responder email.

Add File to Google Drive

  1. Go to the Google Drive website and upload any file that you wish to send via Google Forms.
  2. Change the sharing settings on the file so that it is accessible to anyone with a link. You cannot attach private files to form emails.
  3. Right-click the file in Google Drive and choose Get Link to copy the full URL of the file to the clipboard.

File Attachments in Google Drive

Configure Google Forms

  1. Go to your Google Form, and create a new email rule.
  2. Check the Notify Form Submitter option and select the form field where you ask for the email address of the form submitter.
  3. Go to the Attach Files section and paste the URL of the Google Drive file that you have copied in the previous step.

Save the rule to activate it. Submit a new form and when new users submit the form, they’ll automatically receive a copy of your file as an attachment.

Send File Attachment

PS: You may use this workflow to send files in any format including PDF, Images, Audio, Video, GIFs, PowerPoint, Excel and Word documents. The file size should be less than 5 MB. Also, it is not possible to attach native Google file formats like Google Docs, Sheets and Google Slides.



from Digital Inspiration https://ift.tt/2Jk7xul

Monday, 21 December 2020

JavaScript Design Patterns

This article is a summary of the various design patterns in JavaScript that help us create clean, easier to maintain code without polluting the global namespace.

Object Literal Design Pattern

To avoid the possibility of collusion with other variables of the same name in the global namespace, take all your variables and functionn and make them part of an object with a unique name.

var com = com || {};
com.digitalinspiration = com.digitalinspiration || {};
com.digitalinspiration.person = {
  _name: "Amit Agarwal",
  _country: "",
  setCountry: function (country) {
    this._country = country;
  },
  printCountry: function () {
    console.log(this._name + " lives in " + this._country);
  },
};
com.digitalinspiration.person.setCountry("India");
com.digitalinspiration.person.printCountry();

Module Design Pattern

This pattern helps create private variables in JavaScript that cannot be accessed from the global scope as everything is wrapped inside an IIFE. We create a module that returns an object containing all the public functions. The variable are not accessible outside the module.

var personModule = (function () {
  // private variables and methods
  var _name = "Amit Agarwal";
  var _country = "";
  var print = function () {
    console.log(_name + " lives in " + _country);
  };
  return {
    setCountry: function (country) {
      _country = country;
    },
    printCountry: function () {
      console.log("Calling private method to print " + _country);
      print();
    },
  };
})();

personModule.setCountry("India");
personModule.printCountry();

Module Reveal Pattern

The Reveal Module design pattern makes it easy for the private methods and properties to communicate with the public methods. All methods and variables are hidden unless deliberately exposed inside the returning object.

var personModule = (function () {
  var _name = "Amit Agarwal";
  var _interests = [];
  function _printInterests() {
    console.log(_name + " likes " + _interests.join(", "));
  }
  function addInterest(interest) {
    _interests.push(interest);
  }
  function printInterests() {
    console.log("Calling private method");
    _printInterests();
  }
  return {
    printInterests: printInterests,
    addInterest: addInterest,
  };
})();

personModule.addInterest("Travel");
personModule.addInterest("Reading");
personModule.printInterests();

Avoid the Global Scope

Here we conditionally add our module to the global scope and make everyithng private by wrapping our entire module in an IIFE. The advantage with the pattern is that we are not immediately adding elements to the global scope but performing checks to avoid overriding names.

(function (win) {
  var personModule = (function () {
    var _name = "Amit Agarwal";
    function printName() {
      console.log(_name);
    }
    return {
      printName: printName,
    };
  })();
  if (!win.personModule) {
    win.personModule = personModule;
  } else {
    throw new Error("Cannot initialize application");
  }
})(window);

window.personModule.printName();


from Digital Inspiration https://ift.tt/3nEmeau

YouTube Email Alerts - Monitor Videos around your favorite Topics

The Google Alerts service makes it easy for you to monitor brand mentions and your favorite topics on the Internet. Just specify one or more keywords and Google will send an email notification when new web pages matching your search keywords are found on the web.

The YouTube Email Alerts service is similar to Google Alerts but instead of scanning the whole worldwide web, it limits the searches to videos uploaded on the YouTube website. It then sends automatic email notifications when new videos are uploaded on YouTube around your topics of interest.

Here’s a sample email notification sent by the YouTube Alert system that, in this example, is configured to track new video uploads around three topics - Tesla Model Y, The Queen’s Gambit, and Minecraft tutorials.

YouTube Email Alerts

Setup YouTube Email Alerts

Here’s a step by step guide on how to set up your own YouTube email alert system for monitoring videos around your topics of interest.

  1. Click here to make a copy of the YouTube alert script in your Google Account.

  2. Inside the Google Script, go to line #12 and update the default configuration. You need to specify the email address where the alerts should arrive, the topic or keywords that you wish to track, and the list of negative words. If a matching video contains any of the negative words, they’ll be filtered out of the email notification.

  3. Go to the Run menu and choose the initialize option. Authorize the Google Script and your email system is deployed instantly. Go to your Gmail sent items and you should see an email alert with the matching videos.

The system is now deployed and it will send one email digest per day with a list of matching videos. The email is sent around 11 AM GMT but you can change this time by updating the value of the emailAlertHour property in the configuration.

Configure Video Alerts

How YouTube Email Alerts Work

The alert system is built using the official YouTube API with Google Apps Script. You can find the complete source code on Github.

When you run the initialize function, it creates a cron job that will automatically run once per day around the specified hour. It then uses the YouTube API to find all matching videos that have been uploaded on YouTube since the last execution of the script. It takes the 10 most relevant videos and adds them to the notification email.

const fetchYouTubeVideos = (query = "cats") => {
  const date = new Date();
  date.setDate(date.getDate() - 3);
  const dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'";
  const publishedAfter = Utilities.formatDate(date, "UTC", dateFormat);
  const { items = [] } = YouTube.Search.list(["snippet"], {
    maxResults: 10,
    regionCode: "US",
    publishedAfter: publishedAfter,
    relevanceLanguage: "en",
    q: query,
    type: ["video"],
    fields: "items(id(videoId),snippet(title, channelTitle, channelId))",
  });
  return items.map((item) => {
    const {
      id: { videoId },
      snippet: { title, channelTitle, channelId },
    } = item;
    return { videoId, title, channelTitle, channelId };
  });
};

The preferredLanguage parameter in the configuration is, by default, set to en (English) and instructs YouTube to return videos that are most relevant to the specified language. You can use your two-letter language ISO code (like fr from French or hi for Hindi) here.

Similarly, the regionCode parameter (default is US) helps you restrict search results to videos that can be viewed in your country.

Also see: Google Drive File Monitor



from Digital Inspiration https://ift.tt/3h7Btq7

Tuesday, 1 December 2020

How to Share User Properties between Google Apps Script Projects

The Properties Service of Google Apps Script is used by developers to store app configuration and user specific settings. The properties data is scoped to a specific user, or a specific project, and cannot be shared between different projects.

The Email Form Notifications add-on also uses the Properties Service to store rules that are defined by the user. The rules created by User A are not accessible to User B.

However, in some specific cases, we may want to give access to our store data to another user so they may build upon the existing configuration rather than having to build everything from scratch.

The new import export option allows the user to export properties data as a plain text file that can be imported into the property store of another user.

Access the Property Store

On the server side (Google Script), we define two methods - one for exporting data as a JSON file and the other method for importing data from the property store of another user into our own.

/* Choose DocProperties for editor add-ons */
const getStore = () => {
  return PropertiesService.getUserProperties();
};

/* Export user settings */
const exportUserData = () => {
  const data = getStore().getProperties();
  return JSON.stringify(data);
};

/* Import user settings */
const importUserData = (data) => {
  const json = JSON.parse(data);
  getStore().setProperties(json);
  return 'OK';
};

Export User Properties as a Text File

For exporting data, the HTML file contains a simple download button that connects to the server, fetches the data and allows the user to save this data as a text file on their computer.

<p>Export Data</p>
<button onclick="downloadFile();return false;" href="#">Export</button>

<script>
  function downloadFile() {
    google.script
      .withSuccessHandler(function (data) {
        var a = document.createElement('a');
        var blob = new Blob([data], {
          type: 'text/plain',
        });
        var url = URL.createObjectURL(blob);
        a.setAttribute('href', url);
        a.setAttribute('download', 'file.txt');
        a.click();
      })
      .exportUserData();
  }
</script>

Import User Properties from a Text File

For importing data into the property store, the user can upload a text (JSON) file that contains data as key-value pairs. These files are easily readable in any text editor and you can also add define new properties by adding new keys to the JSON file.

<p>Import data</p>
<input type="file" id="file" accept="text/plain" />

<script>
  document.getElementById('file').addEventListener(
    'change',
    function (event) {
      var file = event.target.files[0];
      if (file.type !== 'text/plain') {
        window.alert('Unsupported file');
        return;
      }
      var reader = new FileReader();
      reader.onload = function (e) {
        google.script.run
          .withSuccessHandler(function (success) {
            window.alert(success);
          })
          .withFailureHandler(function (err) {
            window.alert(err);
          })
          .importUserData(e.target.result);
      };
      reader.readAsText(file);
    },
    false
  );
</script>

The File Reader API of JavaScript is used to read the contents of the selected text file. The onload event gets fired when the file has been successfully read in memory.

The readAsText method of File Reader will read the file as a string but you may also use the readAsDataURL method should be wish to upload file in base64 encoded format that can be decoded on the server.



from Digital Inspiration https://ift.tt/3qgnAtz

Useful JavaScript Functions

The code snippets are from a JavaScript course on Udemy.

  1. Create a function that can be invoked only once.
const once = (fn, ...args) => {
  let called = false;
  return () => {
    if (called === false) {
      called = true;
      return fn(...args);
    }
    return 'Cannot call again';
  };
};

const printName = (text, time) => console.log(`${text} at ${time}`);
const fn = once(printName, 'Google', new Date().toString());

console.log(fn());
console.log(fn());
  1. Measure the time it takes for a JavaScript function to run.
const getUserData = async (user) => {
  const response = await fetch(`https://api.github.com/users/${user}`);
  const json = await response.json();
  return json;
};

const time = (fn, ...args) => {
  console.time('time');
  const result = fn(...args);
  console.timeEnd('time');
  return result;
};

time(() => getUserData('labnol'));
  1. A debounce function that delays invocation until a certain amount of time has passed since the last time that debounce function was invoked.
const debounce = (fn, waitInMs) => {
  let debounced = false;
  return (...args) => {
    if (debounced) clearTimeout(debounced);
    debounced = setTimeout(() => fn(...args), waitInMs);
  };
};

const getWindowLayout = (event) => {
  console.log(event, window.innerHeight, window.innerWidth);
};

window.addEventListener('resize', debounce(getWindowLayout, 500));


from Digital Inspiration https://ift.tt/33wVGQe

Saturday, 28 November 2020

How to Create JSON Web Token (JWT) with Google Apps Script

You can use Google Script to create JSON Web Tokens (JWT) that can be provided to secure routes so that only authenticated requests that contain a valid token can connect to the APIs (e.g., the Zoom API).

All JSON Web Tokens have three parts:

  1. The header that specifies the hash algorithm that is used for signing and decrypting the JWT.
  2. The payload in JSON format that contains all the user data. The iat and exp properties represent the issue date and the expiration time respectively but you can pass any data to the payload.
  3. The signature data that allows APIs to establish the authenticity of the access token.

The parts are joined with a dot (period) and data is encoded in Base64 using the Utilities.base64EncodeWebSafe method of Apps Script.

Create JSON Web Token

const createJwt = ({ privateKey, expiresInHours, data = {} }) => {
  // Sign token using HMAC with SHA-256 algorithm
  const header = {
    alg: 'HS256',
    typ: 'JWT',
  };

  const now = Date.now();
  const expires = new Date(now);
  expires.setHours(expires.getHours() + expiresInHours);

  // iat = issued time, exp = expiration time
  const payload = {
    exp: Math.round(expires.getTime() / 1000),
    iat: Math.round(now / 1000),
  };

  // add user payload
  Object.keys(data).forEach(function (key) {
    payload[key] = data[key];
  });

  const base64Encode = (text, json = true) => {
    const data = json ? JSON.stringify(text) : text;
    return Utilities.base64EncodeWebSafe(data).replace(/=+$/, '');
  };

  const toSign = `${base64Encode(header)}.${base64Encode(payload)}`;
  const signatureBytes = Utilities.computeHmacSha256Signature(
    toSign,
    privateKey
  );
  const signature = base64Encode(signatureBytes, false);
  return `${toSign}.${signature}`;
};

Generate Token with your Private Key & Payload

const generateAccessToken = () => {
  // Your super secret private key
  const privateKey =
    'ZPYu33tz8QYU3hwJQXgHpZsKfYn0r2poopBx7x1n3rmeIvuGU4wf65kk6rV1DrN';
  const accessToken = createJwt({
    privateKey,
    expiresInHours: 6, // expires in 6 hours
    data: {
      iss: Session.getActiveUser().getEmail(),
      userId: 123,
      name: 'Amit Agarwal',
    },
  });
  Logger.log(accessToken);
};

You can paste the generated access token in jwt.io and you’ll be able to see the content (payload) of the decoded token. Please note that if the token has invalid signature data, the payload may still be decoded as it is encoded in Base64.

JSON Web Token with Google Apps Script

Decoding JWT Payload with Google Apps Script

const parseJwt = (jsonWebToken, privateKey) => {
  const [header, payload, signature] = jsonWebToken.split('.');
  const signatureBytes = Utilities.computeHmacSha256Signature(
    `${header}.${payload}`,
    privateKey
  );
  const validSignature = Utilities.base64EncodeWebSafe(signatureBytes);
  if (signature === validSignature.replace(/=+$/, '')) {
    const blob = Utilities.newBlob(
      Utilities.base64Decode(payload)
    ).getDataAsString();
    const { exp, ...data } = JSON.parse(blob);
    if (new Date(exp * 1000) < new Date()) {
      throw new Error('The token has expired');
    }
    Logger.log(data);
  } else {
    Logger.log('🔴', 'Invalid Signature');
  }
};

If you are new to JWT, the video tutorials by Kyle Cook here and here are a good place to start.



from Digital Inspiration https://ift.tt/37drtqx