Google Drive: Export Google Workspace File as PDF

Google Drive: Export Google Workspace File as PDF

Google ドライブ: Google Workspace ファイル PDF エクスポート

This item downloads the specified Google Workspace files (such as Google Docs, Sheets, or Slides) in PDF format. You can download multiple files at once. When you download multiple files, you should write one File ID per line.

Basic Configs
Step Name
Note
Auto Step icon
Configs for this Auto Step
conf_Auth
C1: Service Account Setting *
conf_FileIds
C2: File IDs to download (Write one per line) *
conf_FileData
C3: Data item to add the downloaded files *

Notes

  • The files that will be downloaded must be shared in advance with the service account set in [C1: Service Account Setting]
  • To set up [C1: Service Account Setting] :
    1. Prepare a service account on Google Cloud Console
    2. Create an OAuth2 JWT Bearer setting on Questetra BPM Suite and set it to C1
      • Scope https://www.googleapis.com/auth/drive.readonly is required
      • Set up other items as shown in the table below :
Settings on Questetra BPM SuiteCorresponding Information
of Google Cloud Service Account Key
Property Name in JSON FileRequired or Not
Client IDOAuth2 Client IDclient_idNot Required
Private Key IDKey IDprivate_key_idRequired
Private KeyPrivate Keyprivate_keyRequired
Custom Secret Information 1Email Addressclient_emailRequired

Errors

  • "code": 403, "message": "Google Drive API has not been used in project 123456789012 before or it is disabled.".
    • ‘Google Drive API’ needs to be enabled
  • "code": 404, "message": "File not found:"
    • The file may not exist
    • ‘Google Service Account’ may not have permission to read the file

Capture

See Also

Script (click to open)
  • An XML file that contains the code below is available to download
    • google-drive-file-export.xml (C) Questetra, Inc. (MIT License)
    • If you are using Professional, you can modify the contents of this file and use it as your own add-on auto step


const REQUEST_NUM_PER_FILE = 4; // Make 4 requests per file
const CONTENT_TYPE = 'application/pdf';

function main() {
    ////// == 工程コンフィグ・ワークフローデータの参照 / Config & Data Retrieving ==
    const auth = configs.getObject('conf_Auth');
    const fileIds = retrieveFileIds();

    const fileDef = configs.getObject('conf_FileData');
    let files = engine.findData( fileDef );
    if (files === null) {
        files = new java.util.ArrayList();
    }

    ////// == 演算 / Calculating ==
    if (fileIds.length * REQUEST_NUM_PER_FILE > httpClient.getRequestingLimit()) {
        throw new Error('Number of File IDs exceeds the limit.');
    }
    fileIds.forEach(fileId => {
        const originalFileName = getFileMetadata(auth, fileId);
        const saveAs = `${originalFileName}.pdf`;
        const content = exportFile(auth, fileId);
        const qfile = new com.questetra.bpms.core.event.scripttask.NewQfile(
            saveAs, CONTENT_TYPE, content
        );
        files.add(qfile);
    });

    ////// == ワークフローデータへの代入 / Data Updating ==
    engine.setData(fileDef, files);
}

/**
 * Read the file ID from the config
 * Error if file ID is not set.
 * @returns {Array<String>} an array of file IDs
 */
const retrieveFileIds = () => {
    let fileIdsStr = configs.get('conf_FileIds');
    const fileIdsDef = configs.getObject('conf_FileIds');
    if (fileIdsDef !== null) {
        fileIdsStr = engine.findData(fileIdsDef);
    }
    if (fileIdsStr === null) {
        throw new Error('No File IDs.');
    }
    const fileIds =  fileIdsStr.split('\n').filter(key => key.length !== 0);
    if (fileIds.length === 0) {
        throw new Error('No File IDs.');
    }
    return fileIds;
};

const URL_TOKEN_REQUEST = 'https://oauth2.googleapis.com/token';
const SCOPE = 'https://www.googleapis.com/auth/drive.readonly';

/**
 * @param auth HTTP Authentication Settings
 * @returns {any} an object containing the access token
 */
const getAccessToken = (auth) => {
    const privateKeyId = auth.getPrivateKeyId();
    const privateKey = auth.getPrivateKey();
    const serviceAccount = auth.getCustomSecret1();
    const scope = auth.getScope();
    if (!scope.split(' ').includes(SCOPE)) {
        throw new Error(`Scope ${SCOPE} must be included in the scope.`);
    }
    if (privateKeyId === '') {
        throw new Error('Private Key ID is required.');
    }
    if (privateKey === '') {
        throw new Error('Private Key is required.');
    }
    if (serviceAccount === '') {
        throw new Error('Service Account must be set to Custom Secret 1.');
    }
    const header = {
        "alg": "RS256",
        "typ": "at+jwt",
        "kid": privateKeyId
    };
    const now = Math.floor(Date.now() / 1000);
    const payload = {
        "iss": serviceAccount,
        "aud": URL_TOKEN_REQUEST,
        "sub": '',
        "iat": now,
        "exp": now + 3600,
        /**
         * https://developers.google.com/identity/protocols/oauth2/service-account#jwt-auth
         * “without OAuth”, but it seems to be 1 hour even with OAuth.
         * If it is longer than 1 hour, an error occurs. If it is shorter, a 1 hour token is returned.
         */
        scope
    };
    const keyB = rsa.readKeyFromPkcs8(privateKey);
    const assertion = jwt.build(header, payload, keyB);

    const response = httpClient.begin()
        .formParam("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
        .formParam('assertion', assertion)
        .post(URL_TOKEN_REQUEST);
    const responseText = response.getResponseAsString();
    if (response.getStatusCode() !== 200) {
        engine.log(responseText);
        throw new Error(`Failed to get Access token. status: ${response.getStatusCode()}`);
    }
    const result = JSON.parse(response.getResponseAsString());
    if (result.access_token === undefined) {
        engine.log(responseText);
        throw new Error(`Failed to get Access token. access token not found.`);
    }
    return result;
};

/**
 * Get File Metadata
 * @param auth HTTP Authentication Settings
 * @param fileId File ID
 * @returns {String} File name
 */
const getFileMetadata = (auth, fileId) => {
    const URL = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}`;

    const response = httpClient.begin()
        .oauth2JwtBearer(auth, () => getAccessToken(auth))
        .queryParam("supportsAllDrives", "true")
        .queryParam("fields", "name") // Get file name only
        .get(URL);

    const status = response.getStatusCode();
    const respTxt = response.getResponseAsString();
    if (status !== 200) {
        engine.log(respTxt);
        throw new Error(`Failed to get metadata of file. status: ${status}`);
    }
    const respJson = JSON.parse(respTxt);
    return respJson.name;
};

/**
 * File Export
 * @param auth HTTP Authentication Settings
 * @param fileId File ID
 * @returns {ByteArrayWrapper} The binary data of the file
 */
const exportFile = (auth, fileId) => {
    const URL = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}/export`;

    const response = httpClient.begin()
        .oauth2JwtBearer(auth, () => getAccessToken(auth))
        .queryParam("mimeType", CONTENT_TYPE)
        .get(URL);

    const status = response.getStatusCode();
    const respTxt = response.getResponseAsString();
    if (status !== 200) {
        engine.log(respTxt);
        throw new Error(`Failed to export file. status: ${status}`);
    }
    return response.getResponse();
};
Scroll to Top

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading