Google App ScriptでGoogleスライドのプレゼンテーション内の全コメントを削除してみた

Google App ScriptでGoogleスライドのプレゼンテーション内の全コメントを削除してみた

面倒な処理にはGoogle App Scriptを使おう
Clock Icon2025.04.14

Googleスライドのプレゼンテーションのコメントを全て削除したい

こんにちは、のんピ(@non____97)です。

皆さんはGoogleスライドのプレゼンテーションのコメントを全て削除したいなと思ったことはありますか? 私はあります。

テンプレート用のGoogleスライドのプレゼンテーションに適宜修正してほしい箇所についてはコメントをしている場合があります。

1.Googleスライドのコメント.png

それ以外にも内部のやり取りをコメントとして残している場合もあるでしょう。

提出や共有する際はこれらのコメントをまとめて削除したいことがあります。

PDFにエクスポートする際は気にする必要はありませんが、そのまま共有する場合やpptx形式でエクスポートした場合はコメントがそのまま残ってしまいます。

私が確認した限りではGoogleスライドの操作画面上では複数のコメントをまとめて削除することはできませんでした。

ということで、Google App Script(以降GAS)を用いてGoogleスライドのプレゼンテーション内のコメントをまとめて削除します。

コードの紹介

用意するGASでは以下のような処理を行えるようにします。

  • GASのコンソールからではなく、Googleスライドのメニューバーから操作を選択して、コメントを削除できるようにする
  • 削除対象は以下のいずれかから選択できるようにする
    • 全てのコメント
    • 指定した文字列から始まるコメント
  • 指定した文字列から始まるコメントの削除の検索範囲は親コメントのみ
    • スレッド内のコメントについては検索しない
  • 指定した文字列から始まるコメントの削除で親コメントが削除対象となった場合は子コメントについても削除する
  • 削除したコメントはLoggerでログ出力する

Googleスライドのコメントを削除するにあたってはDrive API v3を使用しています。

実際のコードは以下のとおりです。

/**
 * プレゼンテーションを開いたときに実行される関数
 * カスタムメニューを追加する
 */
function onOpen() {
  SlidesApp.getUi()
    .createMenu('コメント管理')
    .addItem('全てのコメントを削除', 'deleteAllComments')
    .addItem('特定のコメントのみ削除', 'deleteFilteredComments')
    .addToUi();
}

/**
 * プレゼンテーション内の全てのコメントを削除
 */
function deleteAllComments() {
  const ui = SlidesApp.getUi();
  
  // ユーザーに確認
  const response = ui.alert(
    'コメント削除の確認',
    'このプレゼンテーション内の全てのコメントを削除します。\nこの操作は元に戻せません。よろしいですか?',
    ui.ButtonSet.OK_CANCEL
  );
  
  // キャンセルが押されたらスクリプトを終了する
  if (response !== ui.Button.OK) {
    return;
  }
  
  // フィルターなしで削除を実行
  deleteCommentsWithFilter(null);
}

/**
 * プレゼンテーション内の特定の文字列から始まるコメントのみを削除
 */
function deleteFilteredComments() {
  const ui = SlidesApp.getUi();
  
  // ユーザーに検索文字列の入力を求める
  const promptResponse = ui.prompt(
    '特定のコメントを削除',
    '削除するコメントの先頭文字列を入力してください:\n(例: "TODO:" と入力すると、"TODO: ~"で始まるコメントのみ削除されます)',
    ui.ButtonSet.OK_CANCEL
  );
  
  // キャンセルが押されたらスクリプトを終了する
  if (promptResponse.getSelectedButton() !== ui.Button.OK) {
    return;
  }
  
  // 入力された検索文字列を取得
  const searchString = promptResponse.getResponseText().trim();
  
  // 検索文字列が空の場合は警告を表示して終了
  if (searchString === '') {
    ui.alert('検索文字列が入力されていません', '検索文字列を入力してください。', ui.ButtonSet.OK);
    return;
  }
  
  // ユーザーに確認
  const confirmResponse = ui.alert(
    'コメント削除の確認',
    `"${searchString}" で始まるコメントのみを削除します。\nこの操作は元に戻せません。よろしいですか?`,
    ui.ButtonSet.OK_CANCEL
  );
  
  // キャンセルが押されたらスクリプトを終了する
  if (confirmResponse !== ui.Button.OK) {
    return;
  }
  
  // フィルター付きで削除を実行
  deleteCommentsWithFilter(searchString);
}

/**
 * フィルター条件に基づいてコメントを削除する
 * @param {string|null} filterString - フィルター文字列(nullの場合は全て削除)
 */
function deleteCommentsWithFilter(filterString) {
  const ui = SlidesApp.getUi();
  
  try {
    // 開いているGoogleスライドのプレゼンテーションのオブジェクトを取得
    const presentation = SlidesApp.getActivePresentation();
    const presentationId = presentation.getId();
    const presentationName = presentation.getName();

    // drive.file スコープで動作させるために、
    // DriveApp を使用してファイルへのアクセスを明示的に確立する
    const file = DriveApp.getFileById(presentationId);
    
    // ファイルが取得できたことを確認
    if (!file) {
      throw new Error('プレゼンテーションファイルにアクセスできません');
    }

    Logger.log([
      `削除日時: ${new Date().toString()}`,
      `プレゼンテーション名: ${presentationName}`,
      `プレゼンテーションID: ${presentationId}`,
      filterString ? `フィルター文字列: "${filterString}"` : "フィルター: なし(全て削除)"
    ].join("\n"));
    
    let deletedCommentCount = 0;  // 削除したメインコメント数
    let deletedReplyCount = 0;    // 削除した返信数
    let skippedCommentCount = 0;  // スキップしたコメント数
    let pageToken = null;
    let commentIndex = 1;
    
    do {
      // コメントを取得するためのオプション
      const options = {
        pageSize: 100,
        fields: "*"  // すべてのフィールドを取得
      };
      
      if (pageToken) {
        options.pageToken = pageToken;
      }
      
      // コメントを取得
      const response = Drive.Comments.list(presentationId, options);
      const comments = response.comments || [];
      
      // コメントを処理
      comments.forEach(comment => {
        try {
          const commentContent = comment.content || "";
          
          // フィルターが指定されている場合、コメントの内容をチェック
          if (filterString && !commentContent.startsWith(filterString)) {
            // フィルターに一致しないコメントはスキップ
            skippedCommentCount++;
            Logger.log(`コメント #${commentIndex} はフィルター "${filterString}" に一致しないためスキップします: "${commentContent.substring(0, 50)}${commentContent.length > 50 ? '...' : ''}"`);
            commentIndex++;
            return;
          }
          
          Logger.log(`コメント #${commentIndex}:\n${JSON.stringify(comment, null, 2)}`);
          
          // 返信数をカウント
          const replyCount = comment.replies ? comment.replies.length : 0;
          
          // コメントを削除
          Drive.Comments.remove(presentationId, comment.id);
          deletedCommentCount++;
          deletedReplyCount += replyCount;
          
          commentIndex++;
        } catch (e) {
          Logger.log(`エラー: コメントID ${comment.id} の削除に失敗しました: ${e.message}`);
        }
      });
      
      // 次のページがあれば取得
      pageToken = response.nextPageToken;
    } while (pageToken);
    
    Logger.log([
      `削除したメインコメント数: ${deletedCommentCount}`,
      `削除した返信数: ${deletedReplyCount}`,
      `削除したコメント総数: ${deletedCommentCount + deletedReplyCount}`,
      `スキップしたコメント数: ${skippedCommentCount}`
    ].join("\n"));

    // 結果をユーザーに表示
    let resultMessage = `${deletedCommentCount} 件のメインコメントと ${deletedReplyCount} 件の返信(合計 ${deletedCommentCount + deletedReplyCount} 件)を削除しました。`;
    
    if (filterString) {
      resultMessage += `\n\n${skippedCommentCount} 件のコメントはフィルター条件に一致しなかったためスキップされました。`;
    }
    
    ui.alert('削除完了', resultMessage, ui.ButtonSet.OK);
  } catch (e) {
    Logger.log(`致命的なエラー: ${e.message}`);
    ui.alert('エラー', `予期せぬエラーが発生しました: ${e.message}`, ui.ButtonSet.OK);
  }
}

appsscript.jsonは以下のとおりです。

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Drive",
        "version": "v3",
        "serviceId": "drive"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/drive.metadata",
    "https://www.googleapis.com/auth/presentations.currentonly"
  ]
}

Googleドライブ内の全ファイルの操作権限を付与するhttps://www.googleapis.com/auth/driveと現在開いているGoogleスライドのプレゼンテーション情報を取得するgetActivePresentation()を実行するためのhttps://www.googleapis.com/auth/presentations.currentonlyが付与されていれば動作はします。

ただし、Googleドライブ内の全ファイルに読み込みだけでなく、書き込みも許可するのはなんだか気が引けたので以下で必要最小限の権限を付与するようにしています。

  • https://www.googleapis.com/auth/drive.readonly
  • https://www.googleapis.com/auth/drive.file
  • https://www.googleapis.com/auth/drive.metadata

2.delete-slide-comments.png

やってみた

全コメントの削除

実際にやってみましょう。

まずは全コメントの削除です。

メニューのコメント管理-全てのコメントを削除をクリックします。

3.全コメントの削除.png

初めての実行だったので認証が要求されました。OKをクリックします。

4.認証が必要です.png

使用するアカウントを選択します。

5.アカウントを選択してください.png

GASに許可する権限を確認して許可をクリックします。

6.アクセス許可.png

コメント削除の確認のウィンドウが表示されます。OKをクリックします。

7.コメント削除の確認.png

削除完了したことを確認します。削除されたコメント数はプレゼンテーションに設定されたコメント数と一致していることが確認できます。

8.削除完了.png

Googleスプレッドシートのタブをリロードすると、全てのコメントが削除されたことを確認できました。

9.リロード後.png

コメント削除時のログはGASの実行数から確認できます。

10.コメント削除のログ.png

コメントを間違えて削除してしまった場合はここから確認できます。

指定した文字列から始まるコメントの削除

次に指定した文字列から始まるコメントの削除を試してみます。

メニューのコメント管理-特定のコメントのみ削除をクリックします。

11.特定のコメントのみ削除.png

テキストボックスに[スライドテンプレートコメント]を入力して、こちらから始まるコメントのみを削除するようにします。

12.特定のコメントを削除.png

コメント削除の確認のウィンドウが表示されます。OKをクリックします。

13.コメント削除の確認.png

削除完了したことを確認します。削除されたコメント数は返信を含まない[スライドテンプレートコメント]から始まるコメント数と一致していることが確認できます。

14.削除完了.png

Googleスプレッドシートのタブをリロードすると、返信を含まない[スライドテンプレートコメント]から始まるコメントが削除されたことを確認できました。

15.[スライドテンプレートコメント]から始まるコメントの削除.png

ログは以下のように出力されます。

16.特定コメントの削除のログ.png

面倒な処理にはGoogle App Scriptを使おう

Google App ScriptでGoogleスライドのプレゼンテーション内の全コメントを削除してみました。

面倒な処理にはGoogle App Scriptを使うのがやはり便利ですね。

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.