Google Drive: Copy File

Basic Configs
Step Name
Note
Auto Step icon
Configs for this Auto Step
conf_Auth
C1: Service Account Setting *
conf_SourceFileId
C2: Source File ID *
conf_DestinationFolderId
C3: Folder ID to store (The same folder as the source if blank)
conf_NewFileName
C4: New File Name (named automatically if blank)#{EL}
conf_NewFileIdItem
C5: Data item to save new File ID
conf_NewFileUrlItem
C6: Data item to save new File Web View URL

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 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-copy.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 COPY_FIELDS = 'id,name,parents,webViewLink';

function main() {
    const auth = configs.getObject('conf_Auth');
    const sourceFileId = getSourceFileId();
    const destinationFolderId = getDestinationFolderId();
    const newFileName = getNewFileName();
    const newFileIdDataDef = configs.getObject('conf_NewFileIdItem');
    const newFileUrlDataDef = configs.getObject('conf_NewFileUrlItem');

    const copiedFile = copyFile(auth, sourceFileId, destinationFolderId, newFileName);

    engine.log(`Succeeded to copy. Source ID: ${sourceFileId}, New ID: ${copiedFile.id}`);

    if (newFileIdDataDef !== null) {
        engine.setData(newFileIdDataDef, copiedFile.id);
    }
    if (newFileUrlDataDef !== null) {
        const webViewUrl = copiedFile.webViewLink ?? `https://drive.google.com/file/d/${copiedFile.id}/view`;
        engine.setData(newFileUrlDataDef, webViewUrl);
    }
}

/**
 * コピー元のファイル ID を取得する
 * @return {String} コピー元ファイル ID
 */
function getSourceFileId() {
    let fileId = configs.get('conf_SourceFileId');
    const fileIdDef = configs.getObject('conf_SourceFileId');
    if (fileIdDef !== null) {
        fileId = engine.findData(fileIdDef);
    }
    if (fileId === null) {
        throw "Source File ID isn't set.";
    }
    const trimmed = fileId.trim();
    if (trimmed === '') {
        throw "Source File ID isn't set.";
    }
    return trimmed;
}

/**
 * 保存先フォルダ ID を取得する(未指定の場合は空文字を返す)
 * @return {String} 保存先フォルダ ID または空文字
 */
function getDestinationFolderId() {
    let folderId = configs.get('conf_DestinationFolderId');
    const folderIdDef = configs.getObject('conf_DestinationFolderId');
    if (folderIdDef !== null) {
        folderId = engine.findData(folderIdDef);
    }
    if (folderId === null) {
        return '';
    }
    return folderId.trim();
}

/**
 * 新しいファイル名を取得する(未指定の場合は空文字を返す)
 * @return {String} 新しいファイル名または空文字
 */
function getNewFileName() {
    const newName = configs.get('conf_NewFileName');
    if (newName === null) {
        return '';
    }
    return newName.trim();
}

/**
 * ファイルをコピーする
 * @param {HttpAuthSettingWrapper} auth 認証設定
 * @param {String} sourceFileId コピー元のファイル ID
 * @param {String} destinationFolderId 保存先フォルダ ID
 * @param {String} newFileName 新しいファイル名
 * @return {Object} コピー後のファイル情報
 */
function copyFile(auth, sourceFileId, destinationFolderId, newFileName) {
    const url = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(sourceFileId)}/copy`;
    const body = {};
    if (destinationFolderId !== '') {
        body.parents = [destinationFolderId];
    }
    if (newFileName !== '') {
        body.name = newFileName;
    }

    const response = httpClient.begin()
        .oauth2JwtBearer(auth, () => getAccessToken(auth))
        .queryParam('fields', COPY_FIELDS)
        .queryParam('supportsAllDrives', 'true')
        .body(JSON.stringify(body), 'application/json; charset=UTF-8')
        .post(url);

    const status = response.getStatusCode();
    const responseBody = response.getResponseAsString();
    if (status >= 300) {
        engine.log(responseBody);
        throw `Failed to copy. Source ID: ${sourceFileId}, status: ${status}`;
    }

    let json;
    try {
        json = JSON.parse(responseBody);
    } catch (error) {
        throw `Failed to parse response. Source ID: ${sourceFileId}`;
    }

    if (json.id === undefined) {
        throw `Failed to retrieve new file information. Source ID: ${sourceFileId}`;
    }

    return json;
}

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

/**
 * @param {HttpAuthSettingWrapper} auth HTTP 認証設定
 * @returns {{access_token: string}} アクセストークンを含むオブジェクト
 */
function 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,
        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(responseText);
    if (result.access_token === undefined) {
        engine.log(responseText);
        throw new Error('Failed to get Access token. access token not found.');
    }
    return result;
}

Discover more from Questetra Support

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

Continue reading