Google Drive: Copy File

Google Drive: Copy File

Google ドライブ: ファイルコピー

This item copies the specified file on Google Drive. You can optionally specify the destination folder and the new file name, and store the ID or web view URL of the created 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