Full Code of googlesamples/apps-script for AI

main 8a31678d1d7e cached
520 files
1.2 MB
327.5k tokens
357 symbols
1 requests
Download .txt
Showing preview only (1,388K chars total). Download the full file or copy to clipboard to get everything.
Repository: googlesamples/apps-script
Branch: main
Commit: 8a31678d1d7e
Files: 520
Total size: 1.2 MB

Directory structure:
gitextract_1q8uhr3u/

├── .gemini/
│   ├── GEMINI.md
│   ├── config.yaml
│   └── settings.json
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE.md
│   ├── linters/
│   │   ├── .htmlhintrc
│   │   ├── .yaml-lint.yml
│   │   └── sun_checks.xml
│   ├── pull_request_template.md
│   ├── scripts/
│   │   ├── biome-gs.ts
│   │   ├── check-gs.ts
│   │   └── clasp_push.sh
│   ├── snippet-bot.yml
│   ├── sync-repo-settings.yaml
│   └── workflows/
│       ├── automation.yml
│       ├── lint.yml
│       ├── publish.yaml
│       └── test.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CONTRIBUTING.md
├── GEMINI.md
├── LICENSE
├── README.md
├── SECURITY.md
├── adminSDK/
│   ├── directory/
│   │   └── quickstart.gs
│   ├── reports/
│   │   └── quickstart.gs
│   └── reseller/
│       └── quickstart.gs
├── advanced/
│   ├── README.md
│   ├── adminSDK.gs
│   ├── adsense.gs
│   ├── analytics.gs
│   ├── analyticsAdmin.gs
│   ├── analyticsData.gs
│   ├── bigquery.gs
│   ├── calendar.gs
│   ├── chat.gs
│   ├── classroom.gs
│   ├── displayvideo.gs
│   ├── docs.gs
│   ├── doubleclick.gs
│   ├── doubleclickbidmanager.gs
│   ├── drive.gs
│   ├── driveActivity.gs
│   ├── driveLabels.gs
│   ├── events.gs
│   ├── gmail.gs
│   ├── iot.gs
│   ├── people.gs
│   ├── sheets.gs
│   ├── shoppingContent.gs
│   ├── slides.gs
│   ├── tagManager.gs
│   ├── tasks.gs
│   ├── test_adminSDK.gs
│   ├── test_adsense.gs
│   ├── test_analytics.gs
│   ├── test_bigquery.gs
│   ├── test_calendar.gs
│   ├── test_classroom.gs
│   ├── test_displayvideo.gs
│   ├── test_docs.gs
│   ├── test_doubleclick.gs
│   ├── test_doubleclickbidmanager.gs
│   ├── test_drive.gs
│   ├── test_gmail.gs
│   ├── test_people.gs
│   ├── test_sheets.gs
│   ├── test_shoppingContent.gs
│   ├── test_slides.gs
│   ├── test_tagManager.gs
│   ├── test_tasks.gs
│   ├── test_youtube.gs
│   ├── test_youtubeAnalytics.gs
│   ├── test_youtubeContentId.gs
│   ├── youtube.gs
│   ├── youtubeAnalytics.gs
│   └── youtubeContentId.gs
├── ai/
│   ├── autosummarize/
│   │   ├── README.md
│   │   ├── appsscript.json
│   │   ├── gemini.js
│   │   ├── main.js
│   │   ├── sidebar.html
│   │   └── summarize.js
│   ├── custom-func-ai-agent/
│   │   ├── AiVertex.js
│   │   ├── Code.js
│   │   ├── README.md
│   │   └── appsscript.json
│   ├── custom-func-ai-studio/
│   │   ├── Code.js
│   │   ├── README.md
│   │   ├── appsscript.json
│   │   └── gemini.js
│   ├── custom_func_vertex/
│   │   ├── Code.js
│   │   ├── README.md
│   │   ├── aiVertex.js
│   │   └── appsscript.json
│   ├── devdocs-link-preview/
│   │   ├── Cards.js
│   │   ├── Helpers.js
│   │   ├── Main.js
│   │   ├── README.md
│   │   ├── Vertex.js
│   │   └── appsscript.json
│   ├── drive-rename/
│   │   ├── README.md
│   │   ├── ai.js
│   │   ├── appsscript.json
│   │   ├── drive.js
│   │   ├── main.js
│   │   └── ui.js
│   ├── email-classifier/
│   │   ├── Cards.gs
│   │   ├── ClassifyEmail.gs
│   │   ├── Code.gs
│   │   ├── Constants.gs
│   │   ├── DraftEmail.gs
│   │   ├── Labels.gs
│   │   ├── README.md
│   │   ├── Sheet.gs
│   │   └── appsscript.json
│   ├── gmail-sentiment-analysis/
│   │   ├── Cards.gs
│   │   ├── Code.gs
│   │   ├── Gmail.gs
│   │   ├── README.md
│   │   ├── Vertex.gs
│   │   └── appsscript.json
│   └── standup-chat-app/
│       ├── README.md
│       ├── appsscript.json
│       ├── db.js
│       ├── gemini.js
│       ├── main.js
│       └── memoize.js
├── apps-script/
│   └── execute/
│       └── target.js
├── biome.json
├── calendar/
│   └── quickstart/
│       └── quickstart.gs
├── chat/
│   ├── advanced-service/
│   │   ├── AppAuthenticationUtils.gs
│   │   ├── Main.gs
│   │   ├── README.md
│   │   └── appsscript.json
│   └── quickstart/
│       ├── Code.gs
│       ├── README.md
│       └── appsscript.json
├── classroom/
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── snippets/
│       ├── addAlias.gs
│       ├── courseUpdate.gs
│       ├── createAlias.gs
│       ├── createCourse.gs
│       ├── getCourse.gs
│       ├── listCourses.gs
│       ├── patchCourse.gs
│       └── test_classroom_snippets.gs
├── data-studio/
│   ├── appsscript.json
│   ├── appsscript2.json
│   ├── auth.gs
│   ├── build.gs
│   ├── caas.gs
│   ├── data-source.gs
│   ├── errors.gs
│   ├── links.gs
│   ├── manifest.gs
│   └── semantics.gs
├── docs/
│   ├── README.md
│   ├── cursorInspector/
│   │   ├── README.md
│   │   ├── cursorInspector.gs
│   │   ├── sidebar.css.html
│   │   ├── sidebar.html
│   │   └── sidebar.js.html
│   ├── dialog2sidebar/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── Intercom.js.html
│   │   ├── README.md
│   │   └── Sidebar.html
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── translate/
│       ├── README.md
│       ├── sidebar.html
│       └── translate.gs
├── drive/
│   ├── activity/
│   │   └── quickstart.gs
│   ├── activity-v2/
│   │   └── quickstart.gs
│   └── quickstart/
│       └── quickstart.gs
├── forms/
│   ├── README.md
│   └── notifications/
│       ├── README.md
│       ├── about.html
│       ├── authorizationEmail.html
│       ├── creatorNotification.html
│       ├── notification.gs
│       ├── respondentNotification.html
│       └── sidebar.html
├── forms-api/
│   ├── demos/
│   │   └── AppsScriptFormsAPIWebApp/
│   │       ├── Code.gs
│   │       ├── FormsAPI.gs
│   │       ├── Main.html
│   │       ├── README.md
│   │       └── appsscript.json
│   └── snippets/
│       ├── README.md
│       └── retrieve_all_responses.gs
├── gmail/
│   ├── README.md
│   ├── add-ons/
│   │   ├── appsscript.json
│   │   └── quickstart.gs
│   ├── inlineimage/
│   │   └── inlineimage.gs
│   ├── markup/
│   │   ├── Code.gs
│   │   └── mail_template.html
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── sendingEmails/
│       └── sendingEmails.gs
├── gmail-sentiment-analysis/
│   ├── .clasp.json
│   ├── Cards.gs
│   ├── Code.gs
│   ├── Gmail.gs
│   ├── README.md
│   ├── Vertex.gs
│   └── appsscript.json
├── mashups/
│   ├── sheets2calendar.gs
│   ├── sheets2chat.gs
│   ├── sheets2contacts.gs
│   ├── sheets2docs.gs
│   ├── sheets2drive.gs
│   ├── sheets2forms.gs
│   ├── sheets2gmail.gs
│   ├── sheets2maps.gs
│   ├── sheets2slides.gs
│   └── sheets2translate.gs
├── package.json
├── people/
│   └── quickstart/
│       └── quickstart.gs
├── picker/
│   ├── README.md
│   ├── appsscript.json
│   ├── code.gs
│   └── dialog.html
├── pnpm-workspace.yaml
├── service/
│   ├── jdbc.gs
│   ├── propertyService.gs
│   ├── test_jdbc.gs
│   └── test_propertyServices.gs
├── sheets/
│   ├── README.md
│   ├── api/
│   │   ├── helpers.gs
│   │   ├── spreadsheet_snippets.gs
│   │   └── test_spreadsheet_snippets.gs
│   ├── customFunctions/
│   │   ├── btc.gs
│   │   └── customFunctions.gs
│   ├── dateAddAndSubtract/
│   │   ├── README.md
│   │   ├── dateAddAndSubtract.gs
│   │   └── moment.gs
│   ├── forms/
│   │   └── forms.gs
│   ├── maps/
│   │   └── maps.gs
│   ├── next18/
│   │   ├── .claspignore
│   │   ├── Constants.gs
│   │   ├── Invoice.gs
│   │   ├── LinkDialog.html
│   │   ├── README.md
│   │   ├── Salesforce.gs
│   │   └── appsscript.json
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── removingDuplicates/
│       └── removingDuplicates.gs
├── slides/
│   ├── README.md
│   ├── SpeakerNotesScript/
│   │   ├── README.md
│   │   ├── appscript.json
│   │   └── scriptGen.gs
│   ├── api/
│   │   ├── Helpers.gs
│   │   ├── Snippets.gs
│   │   └── Tests.gs
│   ├── imageSlides/
│   │   ├── add_image/
│   │   │   └── add_image.gs
│   │   ├── add_image_slide/
│   │   │   └── add_image_slide.gs
│   │   ├── create/
│   │   │   └── create.gs
│   │   ├── full/
│   │   │   └── full.gs
│   │   └── main/
│   │       └── main.gs
│   ├── progress/
│   │   └── progress.gs
│   ├── quickstart/
│   │   └── quickstart.gs
│   ├── selection/
│   │   └── selection.gs
│   ├── style/
│   │   ├── style.gs
│   │   └── test_style.gs
│   └── translate/
│       ├── sidebar.html
│       └── translate.gs
├── solutions/
│   ├── add-on/
│   │   ├── book-smartchip/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   └── share-macro/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── README.md
│   │       ├── UI.js
│   │       └── appsscript.json
│   ├── automations/
│   │   ├── agenda-maker/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── aggregate-document-content/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── Menu.js
│   │   │   ├── README.md
│   │   │   ├── Setup.js
│   │   │   ├── Utilities.js
│   │   │   └── appsscript.json
│   │   ├── bracket-maker/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── calendar-timesheet/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── Page.html
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── content-signup/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── course-feedback-response/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── employee-certificate/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── equipment-requests/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   ├── appsscript.json
│   │   │   ├── new-equipment-request.html
│   │   │   └── request-complete.html
│   │   ├── event-session-signup/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── feedback-sentiment-analysis/
│   │   │   ├── .clasp.json
│   │   │   ├── README.md
│   │   │   ├── appsscript.json
│   │   │   └── code.js
│   │   ├── folder-creation/
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appscript.json
│   │   ├── generate-pdfs/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── Menu.js
│   │   │   ├── README.md
│   │   │   ├── Utilities.js
│   │   │   └── appsscript.json
│   │   ├── import-csv-sheets/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   ├── SampleData.js
│   │   │   ├── SetupSample.js
│   │   │   ├── Utilities.js
│   │   │   └── appsscript.json
│   │   ├── mail-merge/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── news-sentiment/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── offsite-activity-signup/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── tax-loss-harvest-alerts/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── timesheets/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── upload-files/
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   ├── Setup.js
│   │   │   └── appsscript.json
│   │   ├── vacation-calendar/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   └── youtube-tracker/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── README.md
│   │       ├── appsscript.json
│   │       └── email.html
│   ├── custom-functions/
│   │   ├── calculate-driving-distance/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── summarize-sheets-data/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   └── tier-pricing/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── README.md
│   │       └── appsscript.json
│   ├── editor-add-on/
│   │   └── clean-sheet/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── Menu.js
│   │       ├── README.md
│   │       └── appsscript.json
│   ├── ooo-assistant/
│   │   ├── .clasp.json
│   │   ├── Chat.gs
│   │   ├── Common.gs
│   │   ├── README.md
│   │   └── appsscript.json
│   └── webhook-chat-app/
│       ├── README.md
│       ├── thread-reply.gs
│       └── webhook.gs
├── tasks/
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── simpleTasks/
│       ├── README.md
│       ├── appsscript.json
│       ├── javascript.html
│       ├── page.html
│       ├── simpleTasks.gs
│       └── stylesheet.html
├── templates/
│   ├── README.md
│   ├── custom-functions/
│   │   ├── Code.gs
│   │   └── README.md
│   ├── docs-addon/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── DialogJavaScript.html
│   │   ├── README.md
│   │   ├── Sidebar.html
│   │   ├── SidebarJavaScript.html
│   │   └── Stylesheet.html
│   ├── forms-addon/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── DialogJavaScript.html
│   │   ├── README.md
│   │   ├── Sidebar.html
│   │   ├── SidebarJavaScript.html
│   │   └── Stylesheet.html
│   ├── sheets-addon/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── DialogJavaScript.html
│   │   ├── README.md
│   │   ├── Sidebar.html
│   │   ├── SidebarJavaScript.html
│   │   └── Stylesheet.html
│   ├── sheets-import/
│   │   ├── APICode.gs
│   │   ├── Auth.gs
│   │   ├── AuthCallbackView.html
│   │   ├── AuthorizationEmail.html
│   │   ├── Configurations.gs
│   │   ├── JavaScript.html
│   │   ├── README.md
│   │   ├── Server.gs
│   │   ├── Sidebar.html
│   │   ├── Stylesheet.html
│   │   ├── Utilities.gs
│   │   └── intercom.js.html
│   ├── standalone/
│   │   └── helloWorld.gs
│   └── web-app/
│       ├── Code.gs
│       ├── Index.html
│       ├── JavaScript.html
│       ├── README.md
│       └── Stylesheet.html
├── triggers/
│   ├── form/
│   │   ├── AuthorizationEmail.html
│   │   └── Code.gs
│   ├── test_triggers.gs
│   └── triggers.gs
├── tsconfig.json
├── ui/
│   ├── communication/
│   │   ├── basic/
│   │   │   ├── code.gs
│   │   │   └── index.html
│   │   ├── failure/
│   │   │   ├── code.gs
│   │   │   └── index.html
│   │   ├── private/
│   │   │   ├── code.gs
│   │   │   └── index.html
│   │   ├── runner.gs
│   │   └── success/
│   │       ├── code.gs
│   │       └── index.html
│   ├── dialogs/
│   │   ├── alert/
│   │   │   └── alert.gs
│   │   ├── custom_dialog/
│   │   │   ├── Page.html
│   │   │   └── custom_dialog.gs
│   │   ├── custom_sidebar/
│   │   │   ├── Page.html
│   │   │   └── custom_sidebar.gs
│   │   ├── menus.gs
│   │   └── prompt/
│   │       └── prompt.gs
│   ├── forms/
│   │   ├── code.gs
│   │   └── index.html
│   ├── html/
│   │   ├── printing_scriptlet.html
│   │   ├── scriptlet.html
│   │   └── standard_scriptlet.html
│   ├── sidebar/
│   │   ├── code.gs
│   │   └── index.html
│   ├── user/
│   │   ├── code.gs
│   │   └── index.html
│   └── webapp/
│       ├── code.gs
│       └── index.html
├── utils/
│   ├── logging.gs
│   └── test_logging.gs
└── wasm/
    ├── README.md
    ├── hello-world/
    │   ├── .clasp.json
    │   ├── .gitattributes
    │   ├── .gitignore
    │   ├── Cargo.toml
    │   ├── README.md
    │   ├── build.js
    │   ├── package.json
    │   ├── polyfill.js
    │   └── src/
    │       ├── appsscript.json
    │       ├── lib.rs
    │       ├── main.js
    │       ├── test.js
    │       └── wasm.js
    ├── image-add-on/
    │   ├── .clasp.json
    │   ├── .gitattributes
    │   ├── .gitignore
    │   ├── Cargo.toml
    │   ├── README.md
    │   ├── build.js
    │   ├── package.json
    │   ├── polyfill.js
    │   └── src/
    │       ├── add-on.js
    │       ├── appsscript.json
    │       ├── lib.rs
    │       ├── main.js
    │       ├── test.js
    │       └── wasm.js
    └── python/
        ├── .clasp.json
        ├── .gitattributes
        ├── .gitignore
        ├── Cargo.toml
        ├── README.md
        ├── build.js
        ├── package.json
        ├── polyfill.js
        └── src/
            ├── appsscript.json
            ├── lib.rs
            ├── main.js
            ├── test.js
            └── wasm.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gemini/GEMINI.md
================================================
# Overview

This codebase is part of the Google Workspace GitHub organization, https://github.com/googleworkspace.

## Style Guide

Use open source best practices for code style and formatting with a preference for Google's style guides.

## Tools

- Verify against Google Workspace documentation with the `workspace-developer` MCP server tools.
- Use `gh` for GitHub interactions.


================================================
FILE: .gemini/config.yaml
================================================
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Config for the Gemini Pull Request Review Bot.
# https://github.com/marketplace/gemini-code-assist
have_fun: false
code_review:
  disable: false
  comment_severity_threshold: "HIGH"
  max_review_comments: -1
  pull_request_opened:
    help: false
    summary: true
    code_review: true
ignore_patterns: []


================================================
FILE: .gemini/settings.json
================================================
{
  "mcpServers": {
    "workspace-developer": {
      "httpUrl": "https://workspace-developer.goog/mcp",
      "trust": true
    }
  },
  "tools": {
    "allowed": [
      "run_shell_command(pnpm install)",
      "run_shell_command(pnpm format)",
      "run_shell_command(pnpm lint)",
      "run_shell_command(pnpm check)",
      "run_shell_command(pnpm test)"
    ]
  }
}


================================================
FILE: .github/CODEOWNERS
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners

.github/ @googleworkspace/workspace-devrel-dpe


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
# Summary

TODO

## Expected Behavior

Sample URL:
Description:

## Actual Behavior


## Steps to Reproduce the Problem

1.
1.
1.


================================================
FILE: .github/linters/.htmlhintrc
================================================
{
  "tagname-lowercase": true,
  "attr-lowercase": true,
  "attr-value-double-quotes": true,
  "attr-value-not-empty": false,
  "attr-no-duplication": true,
  "doctype-first": false,
  "tag-pair": true,
  "tag-self-close": false,
  "spec-char-escape": false,
  "id-unique": true,
  "src-not-empty": true,
  "title-require": false,
  "alt-require": true,
  "doctype-html5": true,
  "id-class-value": false,
  "style-disabled": false,
  "inline-style-disabled": false,
  "inline-script-disabled": false,
  "space-tab-mixed-disabled": "space",
  "id-class-ad-disabled": false,
  "href-abs-or-rel": false,
  "attr-unsafe-chars": true,
  "head-script-disabled": false
}


================================================
FILE: .github/linters/.yaml-lint.yml
================================================
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

---
###########################################
# These are the rules used for            #
# linting all the yaml files in the stack #
# NOTE:                                   #
# You can disable line with:              #
# # yamllint disable-line                 #
###########################################
rules:
  braces:
    level: warning
    min-spaces-inside: 0
    max-spaces-inside: 0
    min-spaces-inside-empty: 1
    max-spaces-inside-empty: 5
  brackets:
    level: warning
    min-spaces-inside: 0
    max-spaces-inside: 0
    min-spaces-inside-empty: 1
    max-spaces-inside-empty: 5
  colons:
    level: warning
    max-spaces-before: 0
    max-spaces-after: 1
  commas:
    level: warning
    max-spaces-before: 0
    min-spaces-after: 1
    max-spaces-after: 1
  comments: disable
  comments-indentation: disable
  document-end: disable
  document-start:
    level: warning
    present: true
  empty-lines:
    level: warning
    max: 2
    max-start: 0
    max-end: 0
  hyphens:
    level: warning
    max-spaces-after: 1
  indentation:
    level: warning
    spaces: consistent
    indent-sequences: true
    check-multi-line-strings: false
  key-duplicates: enable
  line-length:
    level: warning
    max: 120
    allow-non-breakable-words: true
    allow-non-breakable-inline-mappings: true
  new-line-at-end-of-file: disable
  new-lines:
    type: unix
  trailing-spaces: disable

================================================
FILE: .github/linters/sun_checks.xml
================================================
<?xml version="1.0"?>
<!--
 Copyright 2022 Google LLC
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->

<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">

<!--
    Checkstyle configuration that checks the Google coding conventions from Google Java Style
    that can be found at https://google.github.io/styleguide/javaguide.html
    Checkstyle is very configurable. Be sure to read the documentation at
    http://checkstyle.org (or in your downloaded distribution).
    To completely disable a check, just comment it out or delete it from the file.
    To suppress certain violations please review suppression filters.
    Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
 -->

<module name="Checker">
    <property name="charset" value="UTF-8"/>

    <property name="severity" value="warning"/>

    <property name="fileExtensions" value="java, properties, xml"/>
    <!-- Excludes all 'module-info.java' files              -->
    <!-- See https://checkstyle.org/config_filefilters.html -->
    <module name="BeforeExecutionExclusionFileFilter">
        <property name="fileNamePattern" value="module\-info\.java$"/>
    </module>
    <!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
    <module name="SuppressionFilter">
        <property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
                  default="checkstyle-suppressions.xml"/>
        <property name="optional" value="true"/>
    </module>

    <!-- Checks for whitespace                               -->
    <!-- See http://checkstyle.org/config_whitespace.html -->
    <module name="FileTabCharacter">
        <property name="eachLine" value="true"/>
    </module>

    <module name="LineLength">
        <property name="fileExtensions" value="java"/>
        <property name="max" value="100"/>
        <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
    </module>

    <module name="TreeWalker">
        <module name="OuterTypeFilename"/>
        <module name="IllegalTokenText">
            <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
            <property name="format"
                      value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
            <property name="message"
                      value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
        </module>
        <module name="AvoidEscapedUnicodeCharacters">
            <property name="allowEscapesForControlCharacters" value="true"/>
            <property name="allowByTailComment" value="true"/>
            <property name="allowNonPrintableEscapes" value="true"/>
        </module>
        <module name="AvoidStarImport"/>
        <module name="OneTopLevelClass"/>
        <module name="NoLineWrap">
            <property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
        </module>
        <module name="EmptyBlock">
            <property name="option" value="TEXT"/>
            <property name="tokens"
                      value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
        </module>
        <module name="NeedBraces">
            <property name="tokens"
                      value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
        </module>
        <module name="LeftCurly">
            <property name="tokens"
                      value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
                    INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,
                    LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
                    LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
                    OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/>
        </module>
        <module name="RightCurly">
            <property name="id" value="RightCurlySame"/>
            <property name="tokens"
                      value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
                    LITERAL_DO"/>
        </module>
        <module name="RightCurly">
            <property name="id" value="RightCurlyAlone"/>
            <property name="option" value="alone"/>
            <property name="tokens"
                      value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
                    INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,
                    COMPACT_CTOR_DEF"/>
        </module>
        <module name="SuppressionXpathSingleFilter">
            <!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
            <property name="id" value="RightCurlyAlone"/>
            <property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
                                     or preceding-sibling::*[last()][self::LCURLY]]"/>
        </module>
        <module name="WhitespaceAfter">
            <property name="tokens"
                      value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,
                    LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
        </module>
        <module name="WhitespaceAround">
            <property name="allowEmptyConstructors" value="true"/>
            <property name="allowEmptyLambdas" value="true"/>
            <property name="allowEmptyMethods" value="true"/>
            <property name="allowEmptyTypes" value="true"/>
            <property name="allowEmptyLoops" value="true"/>
            <property name="ignoreEnhancedForColon" value="false"/>
            <property name="tokens"
                      value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
                    BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
                    LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
                    LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
                    LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
                    NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
                    SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
            <message key="ws.notFollowed"
                     value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
            <message key="ws.notPreceded"
                     value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
        </module>
        <module name="OneStatementPerLine"/>
        <module name="MultipleVariableDeclarations"/>
        <module name="ArrayTypeStyle"/>
        <module name="MissingSwitchDefault"/>
        <module name="FallThrough"/>
        <module name="UpperEll"/>
        <module name="ModifierOrder"/>
        <module name="EmptyLineSeparator">
            <property name="tokens"
                      value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
                    STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,
                    COMPACT_CTOR_DEF"/>
            <property name="allowNoEmptyLineBetweenFields" value="true"/>
        </module>
        <module name="SeparatorWrap">
            <property name="id" value="SeparatorWrapDot"/>
            <property name="tokens" value="DOT"/>
            <property name="option" value="nl"/>
        </module>
        <module name="SeparatorWrap">
            <property name="id" value="SeparatorWrapComma"/>
            <property name="tokens" value="COMMA"/>
            <property name="option" value="EOL"/>
        </module>
        <module name="SeparatorWrap">
            <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->
            <property name="id" value="SeparatorWrapEllipsis"/>
            <property name="tokens" value="ELLIPSIS"/>
            <property name="option" value="EOL"/>
        </module>
        <module name="SeparatorWrap">
            <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->
            <property name="id" value="SeparatorWrapArrayDeclarator"/>
            <property name="tokens" value="ARRAY_DECLARATOR"/>
            <property name="option" value="EOL"/>
        </module>
        <module name="SeparatorWrap">
            <property name="id" value="SeparatorWrapMethodRef"/>
            <property name="tokens" value="METHOD_REF"/>
            <property name="option" value="nl"/>
        </module>
        <module name="PackageName">
            <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
            <message key="name.invalidPattern"
                     value="Package name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="TypeName">
            <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
                    ANNOTATION_DEF, RECORD_DEF"/>
            <message key="name.invalidPattern"
                     value="Type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="MemberName">
            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
            <message key="name.invalidPattern"
                     value="Member name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="ParameterName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Parameter name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="LambdaParameterName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="CatchParameterName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="LocalVariableName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Local variable name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="PatternVariableName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="ClassTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
                     value="Class type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="RecordComponentName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Record component name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="RecordTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
                     value="Record type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="MethodTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
                     value="Method type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="InterfaceTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
                     value="Interface type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="NoFinalizer"/>
        <module name="GenericWhitespace">
            <message key="ws.followed"
                     value="GenericWhitespace ''{0}'' is followed by whitespace."/>
            <message key="ws.preceded"
                     value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
            <message key="ws.illegalFollow"
                     value="GenericWhitespace ''{0}'' should followed by whitespace."/>
            <message key="ws.notPreceded"
                     value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
        </module>
        <module name="Indentation">
            <property name="basicOffset" value="2"/>
            <property name="braceAdjustment" value="2"/>
            <property name="caseIndent" value="2"/>
            <property name="throwsIndent" value="4"/>
            <property name="lineWrappingIndentation" value="4"/>
            <property name="arrayInitIndent" value="2"/>
        </module>
        <module name="AbbreviationAsWordInName">
            <property name="ignoreFinal" value="false"/>
            <property name="allowedAbbreviationLength" value="0"/>
            <property name="tokens"
                      value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
                    PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
                    RECORD_COMPONENT_DEF"/>
        </module>
        <module name="NoWhitespaceBeforeCaseDefaultColon"/>
        <module name="OverloadMethodsDeclarationOrder"/>
        <module name="VariableDeclarationUsageDistance"/>
        <module name="CustomImportOrder">
            <property name="sortImportsInGroupAlphabetically" value="true"/>
            <property name="separateLineBetweenGroups" value="true"/>
            <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
            <property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
        </module>
        <module name="MethodParamPad">
            <property name="tokens"
                      value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
                    SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/>
        </module>
        <module name="NoWhitespaceBefore">
            <property name="tokens"
                      value="COMMA, SEMI, POST_INC, POST_DEC, DOT,
                    LABELED_STAT, METHOD_REF"/>
            <property name="allowLineBreaks" value="true"/>
        </module>
        <module name="ParenPad">
            <property name="tokens"
                      value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
                    EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
                    LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
                    METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,
                    RECORD_DEF"/>
        </module>
        <module name="OperatorWrap">
            <property name="option" value="NL"/>
            <property name="tokens"
                      value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
                    LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF,
                    TYPE_EXTENSION_AND "/>
        </module>
        <module name="AnnotationLocation">
            <property name="id" value="AnnotationLocationMostCases"/>
            <property name="tokens"
                      value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,
                      RECORD_DEF, COMPACT_CTOR_DEF"/>
        </module>
        <module name="AnnotationLocation">
            <property name="id" value="AnnotationLocationVariables"/>
            <property name="tokens" value="VARIABLE_DEF"/>
            <property name="allowSamelineMultipleAnnotations" value="true"/>
        </module>
        <module name="NonEmptyAtclauseDescription"/>
        <module name="InvalidJavadocPosition"/>
        <module name="JavadocTagContinuationIndentation"/>
        <module name="SummaryJavadoc">
            <property name="forbiddenSummaryFragments"
                      value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
        </module>
        <module name="JavadocParagraph"/>
        <module name="RequireEmptyLineBeforeBlockTagGroup"/>
        <module name="AtclauseOrder">
            <property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
            <property name="target"
                      value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
        </module>
        <module name="JavadocMethod">
            <property name="accessModifiers" value="public"/>
            <property name="allowMissingParamTags" value="true"/>
            <property name="allowMissingReturnTag" value="true"/>
            <property name="allowedAnnotations" value="Override, Test"/>
            <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
        </module>
        <module name="MissingJavadocMethod">
            <property name="scope" value="public"/>
            <property name="minLineCount" value="2"/>
            <property name="allowedAnnotations" value="Override, Test"/>
            <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,
                                   COMPACT_CTOR_DEF"/>
        </module>
        <module name="MissingJavadocType">
            <property name="scope" value="protected"/>
            <property name="tokens"
                      value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
                      RECORD_DEF, ANNOTATION_DEF"/>
            <property name="excludeScope" value="nothing"/>
        </module>
        <module name="MethodName">
            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
            <message key="name.invalidPattern"
                     value="Method name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="SingleLineJavadoc"/>
        <module name="EmptyCatchBlock">
            <property name="exceptionVariableName" value="expected"/>
        </module>
        <module name="CommentsIndentation">
            <property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
        </module>
        <!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
        <module name="SuppressionXpathFilter">
            <property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
                      default="checkstyle-xpath-suppressions.xml"/>
            <property name="optional" value="true"/>
        </module>
    </module>
</module>

================================================
FILE: .github/pull_request_template.md
================================================
# Description

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes # (issue)

## Is it been tested?
- [ ] Development testing done

## Checklist

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have performed a peer-reviewed with team member(s)
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] Any dependent changes have been merged and published in downstream modules


================================================
FILE: .github/scripts/biome-gs.ts
================================================
/**
 * Copyright 2025 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { exec } from "node:child_process";
import { readdirSync, renameSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import { promisify } from "node:util";

const execAsync = promisify(exec);

async function findGsFiles(
  dir: string,
  fileList: string[] = [],
): Promise<string[]> {
  const files = readdirSync(dir);
  for (const file of files) {
    const filePath = join(dir, file);
    if (
      file === "node_modules" ||
      file === ".git" ||
      file === "dist" ||
      file === "target" ||
      file === "pkg"
    ) {
      continue;
    }
    const stat = statSync(filePath);
    if (stat.isDirectory()) {
      await findGsFiles(filePath, fileList);
    } else if (file.endsWith(".gs") && file !== "moment.gs") {
      fileList.push(filePath);
    }
  }
  return fileList;
}

async function main() {
  const command = process.argv[2]; // 'lint' or 'format'
  if (command !== "lint" && command !== "format") {
    console.error("Usage: tsx biome-gs.ts [lint|format]");
    process.exit(1);
  }

  const rootDir = resolve(".");
  const gsFiles = await findGsFiles(rootDir);
  const renamedFiles: { oldPath: string; newPath: string }[] = [];

  const restoreFiles = () => {
    for (const { oldPath, newPath } of renamedFiles) {
      try {
        renameSync(newPath, oldPath);
      } catch (e) {
        console.error(`Failed to restore ${newPath} to ${oldPath}:`, e);
      }
    }
    renamedFiles.length = 0;
  };

  process.on("SIGINT", () => {
    restoreFiles();
    process.exit(1);
  });
  process.on("SIGTERM", () => {
    restoreFiles();
    process.exit(1);
  });
  process.on("exit", restoreFiles);

  try {
    // 1. Rename .gs to .gs.js
    for (const gsFile of gsFiles) {
      const jsFile = `${gsFile}.js`;
      renameSync(gsFile, jsFile);
      renamedFiles.push({ oldPath: gsFile, newPath: jsFile });
    }

    // 2. Run Biome
    const biomeArgs = command === "format" ? "check --write ." : "check .";
    console.log(`Running biome ${biomeArgs}...`);
    try {
      const { stdout, stderr } = await execAsync(
        `pnpm exec biome ${biomeArgs}`,
        { cwd: rootDir },
      );
      if (stdout) console.log(stdout.replace(/\.gs\.js/g, ".gs"));
      if (stderr) console.error(stderr.replace(/\.gs\.js/g, ".gs"));
    } catch (e: unknown) {
      const err = e as { stdout?: string; stderr?: string };
      if (err.stdout) console.log(err.stdout.replace(/\.gs\.js/g, ".gs"));
      if (err.stderr) console.error(err.stderr.replace(/\.gs\.js/g, ".gs"));
      // Don't exit yet, we need to restore files
    }
  } catch (err) {
    console.error("An error occurred:", err);
  } finally {
    restoreFiles();
    // Remove listeners to avoid double-running or issues on exit
    process.removeAllListeners("exit");
    process.removeAllListeners("SIGINT");
    process.removeAllListeners("SIGTERM");
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});


================================================
FILE: .github/scripts/check-gs.ts
================================================
/**
 * Copyright 2025 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/// <reference types="node" />

import { exec } from "node:child_process";
import {
  copyFileSync,
  existsSync,
  mkdirSync,
  readdirSync,
  rmSync,
  statSync,
  writeFileSync,
} from "node:fs";
import { dirname, join, relative, resolve, sep } from "node:path";
import { promisify } from "node:util";

const execAsync = promisify(exec);
const TEMP_ROOT = ".tsc_check";

interface Project {
  files: string[];
  name: string;
  path: string;
}

interface CheckResult {
  name: string;
  success: boolean;
  output: string;
}

// Helper to recursively find all files with a specific extension
function findFiles(
  dir: string,
  extension: string,
  fileList: string[] = [],
): string[] {
  const files = readdirSync(dir);
  for (const file of files) {
    if (file.endsWith(".js")) continue;
    const filePath = join(dir, file);
    const stat = statSync(filePath);
    if (stat.isDirectory()) {
      if (file !== "node_modules" && file !== ".git" && file !== TEMP_ROOT) {
        findFiles(filePath, extension, fileList);
      }
    } else if (file.endsWith(extension)) {
      fileList.push(filePath);
    }
  }
  return fileList;
}

// Find all directories containing appsscript.json
function findProjectRoots(rootDir: string): string[] {
  return findFiles(rootDir, "appsscript.json").map((f) => dirname(f));
}

function createProjects(
  rootDir: string,
  projectRoots: string[],
  allGsFiles: string[],
): Project[] {
  // Holds files that belong to a formal Apps Script project (defined by the presence of appsscript.json).
  const projectGroups = new Map<string, string[]>();

  // Holds "orphan" files that do not belong to any defined Apps Script project (no appsscript.json found).
  const looseGroups = new Map<string, string[]>();

  // Initialize project groups
  for (const p of projectRoots) {
    projectGroups.set(p, []);
  }

  for (const file of allGsFiles) {
    let assigned = false;
    let currentDir = dirname(file);

    while (currentDir.startsWith(rootDir) && currentDir !== rootDir) {
      if (projectGroups.has(currentDir)) {
        projectGroups.get(currentDir)?.push(file);
        assigned = true;
        break;
      }
      currentDir = dirname(currentDir);
    }

    if (!assigned) {
      const dir = dirname(file);
      if (!looseGroups.has(dir)) {
        looseGroups.set(dir, []);
      }
      looseGroups.get(dir)?.push(file);
    }
  }

  const projects: Project[] = [];
  projectGroups.forEach((files, dir) => {
    if (files.length > 0) {
      projects.push({
        files,
        name: `Project: ${relative(rootDir, dir)}`,
        path: relative(rootDir, dir),
      });
    }
  });
  looseGroups.forEach((files, dir) => {
    if (files.length > 0) {
      projects.push({
        files,
        name: `Loose Project: ${relative(rootDir, dir)}`,
        path: relative(rootDir, dir),
      });
    }
  });

  return projects;
}

async function checkProject(
  project: Project,
  rootDir: string,
): Promise<CheckResult> {
  const projectNameSafe = project.name.replace(/[^a-zA-Z0-9]/g, "_");
  const projectTempDir = join(TEMP_ROOT, projectNameSafe);

  // Synchronous setup is fine as it's fast and avoids race conditions on mkdir if we were sharing dirs (we aren't)
  mkdirSync(projectTempDir, { recursive: true });

  for (const file of project.files) {
    const fileRelPath = relative(rootDir, file);
    const destPath = join(projectTempDir, fileRelPath.replace(/\.gs$/, ".js"));
    const destDir = dirname(destPath);
    mkdirSync(destDir, { recursive: true });
    copyFileSync(file, destPath);
  }

  const tsConfig = {
    extends: "../../tsconfig.json",
    compilerOptions: {
      noEmit: true,
      allowJs: true,
      checkJs: true,
      typeRoots: [resolve(rootDir, "node_modules/@types")],
    },
    include: ["**/*.js"],
  };

  writeFileSync(
    join(projectTempDir, "tsconfig.json"),
    JSON.stringify(tsConfig, null, 2),
  );

  try {
    await execAsync(`tsc -p \"${projectTempDir}\"`, { cwd: rootDir });
    return { name: project.name, success: true, output: "" };
  } catch (e) {
    const err = e as { stdout?: string; stderr?: string };
    const rawOutput = (err.stdout ?? "") + (err.stderr || "");

    const rewritten = rawOutput
      .split("\n")
      .map((line: string) => {
        if (line.includes(projectTempDir)) {
          let newLine = line.split(projectTempDir + sep).pop();
          if (!newLine) {
            return line;
          }
          newLine = newLine.replace(/\.js(:|\()/g, ".gs$1");
          return newLine;
        }
        return line;
      })
      .join("\n");

    return { name: project.name, success: false, output: rewritten };
  }
}

async function main() {
  try {
    const rootDir = resolve(".");
    const args = process.argv.slice(2);
    const searchArg = args.find((arg) => arg !== "--");

    // 1. Discovery
    const projectRoots = findProjectRoots(rootDir);
    const allGsFiles = findFiles(rootDir, ".gs");

    // 2. Grouping
    const projects = createProjects(rootDir, projectRoots, allGsFiles);

    // 3. Filtering
    const projectsToCheck = projects.filter((p) => {
      return !searchArg || p.path.startsWith(searchArg);
    });

    if (projectsToCheck.length === 0) {
      console.log("No projects found matching the search path.");
      return;
    }

    // 4. Setup
    if (existsSync(TEMP_ROOT)) {
      rmSync(TEMP_ROOT, { recursive: true, force: true });
    }
    mkdirSync(TEMP_ROOT);

    console.log(`Checking ${projectsToCheck.length} projects in parallel...`);

    // 5. Parallel Execution
    const results = await Promise.all(
      projectsToCheck.map((p) => checkProject(p, rootDir)),
    );

    // 6. Reporting
    let hasError = false;
    for (const result of results) {
      if (!result.success) {
        hasError = true;
        console.log(`\n--- Failed: ${result.name} ---`);
        console.log(result.output);
      }
    }

    if (hasError) {
      console.error("\nOne or more checks failed.");
      process.exit(1);
    } else {
      console.log("\nAll checks passed.");
    }
  } catch (err) {
    console.error("Unexpected error:", err);
    process.exit(1);
  } finally {
    if (existsSync(TEMP_ROOT)) {
      rmSync(TEMP_ROOT, { recursive: true, force: true });
    }
  }
}

main();


================================================
FILE: .github/scripts/clasp_push.sh
================================================
#! /bin/bash
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

export LC_ALL=C.UTF-8
export LANG=C.UTF-8

function contains_changes() {
  [[ "${*:2}" = "" ]] && return 0
  for f in "${@:2}"; do
    case $(realpath "$f")/ in 
      $(realpath "$1")/*) return 0;;
    esac
  done
  return 1
}

changed_files=$(echo "${@:1}" | xargs realpath | xargs -I {} dirname {}| sort -u | uniq)
dirs=()

IFS=$'\n' read -r -d '' -a dirs < <( find . -name '.clasp.json' -exec dirname '{}' \; | sort -u | xargs realpath )

exit_code=0

for dir in "${dirs[@]}"; do
  pushd "${dir}" > /dev/null || exit
  contains_changes "$dir" "${changed_files[@]}" || continue
  echo "Publishing ${dir}"
  clasp push -f
  status=$?
  if [ $status -ne 0 ]; then
    exit_code=$status
  fi
  popd > /dev/null || exit
done

if [ $exit_code -ne 0 ]; then
  echo "Script push failed."
fi

exit $exit_code

================================================
FILE: .github/snippet-bot.yml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.



================================================
FILE: .github/sync-repo-settings.yaml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# .github/sync-repo-settings.yaml
# See https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings for app options.
rebaseMergeAllowed: true
squashMergeAllowed: true
mergeCommitAllowed: false
deleteBranchOnMerge: true
branchProtectionRules:
  - pattern: main
    isAdminEnforced: false
    requiresStrictStatusChecks: false
    requiredStatusCheckContexts:
      # .github/workflows/test.yml with a job called "test"
      - "test"
      # .github/workflows/lint.yml with a job called "lint"
      - "lint"
      # Google bots below
      - "cla/google"
      - "snippet-bot check"
      - "header-check"
      - "conventionalcommits.org"
    requiredApprovingReviewCount: 1
    requiresCodeOwnerReviews: true
permissionRules:
  - team: workspace-devrel-dpe
    permission: admin
  - team: workspace-devrel
    permission: push


================================================
FILE: .github/workflows/automation.yml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
name: Automation
on: [ push, pull_request, workflow_dispatch ]
jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' &&  github.event_name == 'pull_request' }}
    env:
      PR_URL: ${{github.event.pull_request.html_url}}
      GITHUB_TOKEN: ${{secrets.GOOGLEWORKSPACE_BOT_TOKEN}}
    steps:
      - name: approve
        run: gh pr review --approve "$PR_URL"
      - name: merge
        run: gh pr merge --auto --squash --delete-branch "$PR_URL"
  default-branch-migration:
    # this job helps with migrating the default branch to main
    # it pushes main to master if master exists and main is the default branch
    # it pushes master to main if master is the default branch
    runs-on: ubuntu-latest
    if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }}
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
          # required otherwise GitHub blocks infinite loops in pushes originating in an action
          token: ${{ secrets.GOOGLEWORKSPACE_BOT_TOKEN }}
      - name: Set env
        run: |
          # set DEFAULT BRANCH
          echo "DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')" >> "$GITHUB_ENV";

          # set HAS_MASTER_BRANCH
          if [ -n "$(git ls-remote --heads origin master)" ]; then
            echo "HAS_MASTER_BRANCH=true" >> "$GITHUB_ENV"
          else
            echo "HAS_MASTER_BRANCH=false" >> "$GITHUB_ENV"
          fi
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: configure git
        run: |
          git config --global user.name 'googleworkspace-bot'
          git config --global user.email 'googleworkspace-bot@google.com'
      - if: ${{ env.DEFAULT_BRANCH == 'main' && env.HAS_MASTER_BRANCH == 'true' }}
        name: Update master branch from main
        run: |
          git checkout -B master
          git reset --hard origin/main
          git push origin master
      - if: ${{ env.DEFAULT_BRANCH == 'master'}}
        name: Update main branch from master
        run: |
          git checkout -B main
          git reset --hard origin/master
          git push origin main


================================================
FILE: .github/workflows/lint.yml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
name: Lint
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  lint:
    concurrency:
      group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.head_ref || github.ref }}
      cancel-in-progress: true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
      - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
        with:
          cache: "pnpm"
      - run: pnpm i
      - run: pnpm lint


================================================
FILE: .github/workflows/publish.yaml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
name: Publish Apps Script
on:
  workflow_dispatch:
  push:
    branches:
      - main
jobs:
  publish:
    concurrency:
      group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.head_ref || github.ref }}
      cancel-in-progress: true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
      - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
        with:
          cache: "pnpm"
      - run: pnpm i
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v23.1
      - name: Write test credentials
        run: |
          echo "${CLASP_CREDENTIALS}" > "${HOME}/.clasprc.json"
        env:
          CLASP_CREDENTIALS: ${{secrets.CLASP_CREDENTIALS}}
      - run: pnpm install -g @google/clasp
      - run: ./.github/scripts/clasp_push.sh ${{ steps.changed-files.outputs.all_changed_files }}


================================================
FILE: .github/workflows/test.yml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Test
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  test:
    # temporarily use matrix until all are passing
    strategy:
      fail-fast: false
      matrix:
        folder:
          - adminSDK
          - advanced
          - ai
          - calendar
          - chat
          - classroom
          - data-studio
          - docs
          - drive
          - forms
          - forms-api
          - gmail
          - gmail-sentiment-analysis
          - mashups
          - people
          - picker
          - service
          - sheets
          - slides
          - solutions
          - tasks
          - templates
          - triggers
          - ui
          - utils
    concurrency:
      group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.head_ref || github.ref }}-${{ matrix.folder }}
      cancel-in-progress: true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
      - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
        with:
          cache: "pnpm"
      - run: pnpm i
      - run: pnpm check ${{ matrix.folder }}


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
.gradle
**/dist
**/node_modules
**/target
**/.tsc_check

================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": ["google-workspace.google-workspace-developer-tools"]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "files.associations": {
    "*.gs": "javascript"
  }
}


================================================
FILE: CONTRIBUTING.md
================================================
# How to become a contributor and submit your own code

## Contributor License Agreements

We'd love to accept your sample apps and patches! Before we can take them, we
have to jump a couple of legal hurdles.

Please fill out either the individual or corporate Contributor License Agreement
(CLA).

* If you are an individual writing original source code and you're sure you
  own the intellectual property, then you'll need to sign an
  [individual CLA](https://developers.google.com/open-source/cla/individual).
* If you work for a company that wants to allow you to contribute your work,
  then you'll need to sign a
  [corporate CLA](https://developers.google.com/open-source/cla/corporate).

Follow either of the two links above to access the appropriate CLA and
instructions for how to sign and return it. Once we receive it, we'll be able to
accept your pull requests.

## Contributing A Patch

1. Submit an issue describing your proposed change to the repository in question.
1. The repository owner will respond to your issue promptly.
1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).
1. Fork the desired repository, develop and test your code changes.
1. Ensure that your code adheres to the existing style in the sample to which you are contributing.
1. Ensure that your code has an appropriate set of unit tests which all pass.
1. Run `pnpm check` to ensure there are no type errors or syntax issues in your `.gs` files.
1. Submit a pull request!

## Style

Samples in this repository follow the [JavaScript Semi-Standard
Style](https://github.com/Flet/semistandard).


================================================
FILE: GEMINI.md
================================================
# Apps Script Sample Development Guide

This guide outlines best practices for developing Google Apps Script projects, focusing on type safety and modern JavaScript features.

## Important

* For new sample directories, ensure the top-level folder is included in the [`test.yaml`](.github/workflows/test.yaml) GitHub workflow's matrix configuration.
* Do not move or delete snippet tags: `[END apps_script_... ]` or `[END apps_script_... ]`.
* Keep code within snippet tags self-contained. Avoid depending on helper functions defined outside the snippet tags if the snippet is intended to be copied and pasted.
* Avoid function name collisions (e.g., multiple `onOpen` or `main` functions) by placing separate samples in their own directories or files. Do not append suffixes like `_2`, `_3` to function names. For variables, replace collisions with a more descriptive name.

## Tools

Lint and format code using [Biome](https://biomejs.dev/).

```bash
pnpm lint
pnpm format
```

## Apps Script Code Best Practices

Apps Script supports the V8 runtime, which enables modern ECMAScript syntax. Using these features makes your code cleaner, more readable, and less error-prone.

### `let` and `const`
Use `let` and `const` instead of `var` for block-scoped variables.

*   **`const`**: Use for values that should not be reassigned.
*   **`let`**: Use for values that will change.

```javascript
const PI = 3.14;
let count = 0;

if (true) {
  let local = "I exist only in this block";
}
// local is not accessible here
```

### Arrow Functions
Use arrow functions for concise function expressions, especially for callbacks.

```javascript
const numbers = [1, 2, 3];
const squares = numbers.map(x => x * x); // [1, 4, 9]
```

### Destructuring
Unpack values from arrays or properties from objects into distinct variables.

```javascript
const user = { name: "Alice", age: 30 };
const { name, age } = user;

const coords = [10, 20];
const [x, y] = coords;
```

### Template Literals
Use template literals for string interpolation and multi-line strings.

```javascript
const name = "World";
const greeting = `Hello, ${name}!`;

const multiLine = `
  This is a
  multi-line string.
`;
```

### Default Parameters
Specify default values for function parameters.

```javascript
function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

greet(); // "Hello, Guest!"
```

### Prefer `for...of` for Iteration
While `forEach` is convenient, `for...of` loops generally offer better performance and more control (e.g., `break`, `continue`) in Apps Script, especially when dealing with large arrays.

```javascript
const numbers = [1, 2, 3];

// Using forEach (less performant for large arrays)
numbers.forEach(num => {
  console.log(num);
});

// Using for...of (preferred)
for (const num of numbers) {
  console.log(num);
}
```

## Apps Script V8 Runtime

It's important to understand that the Apps Script V8 runtime is
not a standard Node.js or browser environment. This can lead to compatibility
issues when incorporating third-party libraries or adapting code examples
from other JavaScript environments.

### Unavailable APIs

The following standard JavaScript APIs are **NOT** available in the
Apps Script V8 runtime:

*   **Timers**: `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`
*   **Streams**: `ReadableStream`, `WritableStream`, `TextEncoder`,
    `TextDecoder`
*   **Web APIs**: `fetch`, `FormData`, `File`, `Blob`, `URL`, `URLSearchParams`,
    `DOMException`, `atob`, `btoa`
*   **Crypto**: `crypto`, `SubtleCrypto`
*   **Global Objects**: `window`, `navigator`, `performance`, `process`
    (Node.js)

Instead of the unavailable APIs, you can use the following
Apps Script APIs as alternatives:

*   **Timers**: Use
    [`Utilities.sleep(milliseconds)`](https://developers.google.com/apps-script/reference/utilities/utilities#sleepmilliseconds)
    for synchronous pauses. Asynchronous timers are not supported.
*   **Fetch**: Use [`UrlFetchApp.fetch(url,
    params)`](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app) to make HTTP(S)
    requests.
*   **atob**: Use
    [`Utilities.base64Decode()`](https://developers.google.com/apps-script/reference/utilities/utilities#base64decodeencoded)
    to decode Base64-encoded strings.
*   **btoa**: Use
    [`Utilities.base64Encode()`](https://developers.google.com/apps-script/reference/utilities/utilities#base64encodedata)
    to encode strings in Base64.
*   **Crypto**: Use [`Utilities`](https://developers.google.com/apps-script/reference/utilities/utilities)
    for cryptographic functions like
    [`computeDigest()`](https://developers.google.com/apps-script/reference/utilities/utilities#computedigestalgorithm,-value),
    [`computeHmacSha256Signature()`](https://developers.google.com/apps-script/reference/utilities/utilities#computehmacsha256signaturevalue,-key),
    and
    [`computeRsaSha256Signature()`](https://developers.google.com/apps-script/reference/utilities/utilities#computersasha256signaturevalue,-key).

For some APIs, other workarounds might exist. For example, you might be able to
use a polyfill for `TextEncoder`.

### Asynchronous Limitations

The V8 runtime supports `async` and `await` syntax and the `Promise` object.
However, the Apps Script runtime environment is fundamentally
synchronous.

*   **Microtasks (Supported)**: The runtime processes the microtask queue (where
    `Promise.then()` callbacks and `await` resolutions occur) after the current
    call stack clears.
*   **Macrotasks (Not Supported)**: Apps Script does not have a
    standard event loop for macrotasks. Functions like `setTimeout()` and
    `setInterval()` are not available.
*   **WebAssembly Exception**: The WebAssembly API is the only built-in
    feature that operates in a non-blocking manner within the runtime, allowing
    for specific asynchronous compilation patterns (WebAssembly.instantiate).

All I/O operations, such as
[`UrlFetchApp.fetch()`](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app), are
blocking. To achieve parallel network requests, use
[`UrlFetchApp.fetchAll()`](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchallrequests).

### Class Limitations

The V8 runtime has specific limitations regarding modern ES6+ class features:

*   **Private Fields**: Private class fields (for example, `#field`) are not
    supported and cause parsing errors. Consider using closures or `WeakMap` for
    true encapsulation.
*   **Static Fields**: Direct static field declarations within the class body
    (for example, `static count = 0;`) are not supported. Assign static
    properties to the class after its definition (for example, `MyClass.count =
    0;`).

### Module Limitations

*   **ES6 Modules**: The V8 runtime does not support ES6 modules (`import` /
    `export`). To use libraries, you must either use the [
    Apps Script library mechanism](https://developers.google.com/apps-script/guides/libraries)
    or bundle your code and its dependencies into a single script file. ([Issue
    Tracker](https://issuetracker.google.com/issues/134627726))
*   **File Execution Order**: All script files in your project are executed in a
    global scope. It's best to avoid top-level code with side effects and ensure
    functions and classes are defined before being used across files. Explicitly
    order your files in the editor if dependencies exist between them.

## Type Checking with JSDoc

This project uses a type checker to validate `.gs` files for errors. Since `.gs` files are technically JavaScript, we use JSDoc comments to provide type information. This ensures your code is type-safe and well-documented.

### Running Checks

You can run the type checker from the root of the repository.

**Check all projects:**
```bash
pnpm run check
```

**Check a specific path:**
To check only projects within a specific directory (e.g., `solutions/automations`), pass the path as an argument:
```bash
pnpm run check solutions/automations
```

### Core Concepts

#### 1. Basic Types
Use `@param` and `@return` to define function inputs and outputs.

```javascript
/**
 * Adds two numbers.
 * @param {number} a The first number.
 * @param {number} b The second number.
 * @return {number} The sum.
 */
function add(a, b) {
  return a + b;
}
```

#### 2. Apps Script Types
You can reference global Apps Script types directly.

```javascript
/**
 * Gets the active sheet name.
 * @return {string} The name of the sheet.
 */
function getSheetName() {
  // Types like SpreadsheetApp, Sheet, Range are available globally
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getName();
}
```

#### 3. Optional Parameters
Use `[]` or `=` to denote optional parameters.

```javascript
/**
 * @param {string} name The name.
 * @param {number=} age Optional age.
 */
function greet(name, age) {
  if (age) { ... }
}
```

### Advanced Patterns

#### 1. Custom Objects (@typedef)
For complex objects, define a type using `@typedef`.

```javascript
/**
 * @typedef {Object} UserConfig
 * @property {string} username The user's name.
 * @property {boolean} isAdmin Whether the user is an admin.
 * @property {number} [retryCount] Optional retry attempts.
 */

/**
 * Processes a user configuration.
 * @param {UserConfig} config The configuration object.
 */
function processUser(config) {
  console.log(config.username);
}
```

#### 2. Type Casting
Sometimes the type checker cannot infer the type correctly. Use inline `@type` to cast.

```javascript
const data = JSON.parse(jsonString);

/** @type {UserConfig} */
const config = data;
```

#### 3. Arrays and Generics
Specify array contents clearly.

```javascript
/**
 * @param {string[]} names An array of strings.
 * @return {Array<number>} An array of numbers.
 */
function lengths(names) {
  return names.map(n => n.length);
}
```

#### 4. Handling `null` and `undefined`
Be explicit if a value can be null.

```javascript
/**
 * @param {string|null} id The ID, or null if not found.
 */
function find(id) { ... }
```

### Common Issues & Fixes

- **TypeScript**: DO NOT REFERENCE GoogleAppsScript in JSDocs. Instead use a locally defined type definition and link to the appropriate reference documenation page if possible.
- **"Property 'x' does not exist on type 'Object'"**: This usually means you are accessing a property on a generic object. Define a `@typedef` for that object structure.
- **Implicit 'any'**: If you see "Parameter 'x' implicitly has an 'any' type", it means you forgot a JSDoc `@param` tag. Add it to fix the error.
- **Advanced Services**: To fix errors with these globals, check for existence. This helps TypeScript narrow the type and prevents runtime errors if the service is not enabled.

   ```js
   if (!AdminDirectory) {
     console.log('AdminDirectory Advanced Service must be enabled.');
     return;
   }
   ```

- **Optional Properties**: Use optional chaining (`?.`) when accessing properties that might be undefined in API responses. This is often the case when when using `fields` to limit the response.

   ```js
   // Safe access
   console.log(user.name?.fullName);
   ```

- **Error Handling**: Avoid wrapping code in `try/catch` blocks if you are only logging the error message. Let the runtime handle the error reporting for cleaner sample code.

   ```js
   // Avoid this
   try {
     AdminDirectory.Users.list();
   } catch (err) {
     console.log(err.message);
   }

   // Prefer this
   AdminDirectory.Users.list();
   ```

================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# Google Apps Script Samples

Various sample code and projects for the Google Apps Script platform, a JavaScript platform in the cloud.

Learn more at [developers.google.com](https://developers.google.com/apps-script).

## Google APIs

<img
src="https://www.gstatic.com/images/branding/product/2x/admin_96dp.png"
align="left"
width="96px"/>
### AdminSDK
- [Manage domains and apps](adminSDK)
<br><br>

<img
src="https://www.gstatic.com/images/branding/product/2x/google_cloud_96dp.png"
align="left"
width="96px"/>
### Advanced Services
- [Access Google APIs via Advanced Google services](advanced/)
<br><br>

<img
src="https://www.gstatic.com/images/branding/product/2x/calendar_96dp.png"
align="left"
width="96px"/>
### Calendar
- [List upcoming events](calendar/quickstart)
- [Create a vacation calendar](solutions/automations/vacation-calendar/Code.js)

<img
src="https://www.gstatic.com/images/branding/product/2x/classroom_96dp.png"
align="left"
width="96px"/>
### Classroom
- [Manage Google Classroom](classroom/quickstart)
<br><br>

<img
src="https://www.gstatic.com/images/branding/product/2x/data_studio_96dp.png"
align="left"
width="96px"/>
### Data Studio
- [Build a connector](data-studio/build.gs)
- [Authentication and Authorization](data-studio/auth.gs)

<img
src="https://www.gstatic.com/images/branding/product/2x/docs_96dp.png"
align="left"
width="96px"/>
### Docs
- [Cursor inspector add-on](docs/cursorInspector)
- [Translate add-on](docs/translate)

<img
src="https://www.gstatic.com/images/branding/product/2x/drive_96dp.png"
align="left"
width="96px"/>
### Drive
- [Manage Google Drive files and folders](drive/quickstart)
- [View Google Drive activity](drive/activity)

<img
src="https://www.gstatic.com/images/branding/product/2x/forms_96dp.png"
align="left"
width="96px"/>
### Forms
- [Notification add-on](forms)
<br><br>

<img
src="https://www.gstatic.com/images/branding/product/2x/gmail_96dp.png"
align="left"
width="96px"/>
### Gmail
- [Sending email](gmail/sendingEmails)
- [Mailmerge: Merge a template email with content](gmail/mailmerge)

<img
src="https://www.gstatic.com/images/icons/material/system/2x/people_black_48dp.png"
align="left"
width="96px"/>
### People
- [Listing Connections](people/quickstart)
<br><br>

<img
src="https://www.gstatic.com/images/branding/product/2x/sheets_96dp.png"
align="left"
width="96px"/>
### Sheets
- [Managing Responses for Google Forms](sheets)
- [Menus and Custom Functions](sheets)

<img
src="https://www.gstatic.com/images/branding/product/2x/slides_96dp.png"
align="left"
width="96px"/>
### Slides
- [Translate Slides Add-on](slides/translate)
- [Progress Bars add-on](slides/progress)

<img
src="https://www.gstatic.com/images/branding/product/2x/tasks_96dp.png"
align="left"
width="96px"/>
### Tasks
- [List Tasks](tasks/quickstart)
- [Simple Tasks Web App](tasks/simpleTasks)

<img
src="https://www.gstatic.com/images/icons/material/system/2x/code_grey600_48dp.png"
align="left"
width="96px"/>
### Templates
- Build off a working framework for new Apps Script projects.
<br><br>

<img
src="https://www.gstatic.com/images/icons/material/system/2x/alarm_grey600_48dp.png"
align="left"
width="96px"/>
### Triggers
- Call an Apps Script function such as `onOpen`, `onEdit`, or `onInstall` in an add-on
- Create a [time-driven trigger](https://developers.google.com/apps-script/guides/triggers/installable#time_driven_triggers)

## Codelabs

Codelab tutorials combine detailed explanation, coding exercises, and documented best practices to help engineers get up to speed with key Google technologies. Here's a list of Apps Script codelabs:

- [Apps Script Intro](http://g.co/codelabs/apps-script-intro)
- [Apps Script CLI – clasp](http://g.co/codelabs/clasp)
- [BigQuery + Sheets + Slides](http://g.co/codelabs/bigquery-sheets-slides)
- [Docs Add-on + Cloud Natural Language API](http://g.co/codelabs/nlp-docs)
- [Gmail Add-ons](http://g.co/codelabs/gmail-add-ons)
- [Google Chat Apps](https://developers.google.com/codelabs/chat-apps-script)

## Clone using the `clasp` command-line tool

Learn how to clone, pull, and push Apps Script projects on the command-line
using [clasp](https://developers.google.com/apps-script/guides/clasp).

## Lint

Run ESLint over this whole repository with:

```shell
pnpm lint
```

This command will fix simple errors.

## Type Checking

Run the TypeScript-based check over the repository with:

```shell
pnpm check
```

This command validates `.gs` files by temporarily converting them to `.js` and running `tsc`. It checks for syntax errors and type issues using JSDoc annotations.


================================================
FILE: SECURITY.md
================================================
# Report a security issue

To report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz). We use
[https://g.co/vulnz](https://g.co/vulnz) for our intake, and do coordination and disclosure here on
GitHub (including using GitHub Security Advisory). The Google Security Team will
respond within 5 working days of your report on [https://g.co/vulnz](https://g.co/vulnz).


================================================
FILE: adminSDK/directory/quickstart.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START admin_sdk_directory_quickstart]
/**
 * Lists users in a Google Workspace domain.
 * @see https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/list
 */
function listUsers() {
  const optionalArgs = {
    customer: "my_customer",
    maxResults: 10,
    orderBy: "email",
  };
  if (!AdminDirectory || !AdminDirectory.Users) {
    throw new Error("Enable the AdminDirectory Advanced Service.");
  }
  const response = AdminDirectory.Users.list(optionalArgs);
  const users = response.users;
  if (!users || users.length === 0) {
    console.log("No users found.");
    return;
  }
  // Print the list of user's full name and email
  console.log("Users:");
  for (const user of users) {
    if (user.primaryEmail) {
      if (user.name?.fullName) {
        console.log("%s (%s)", user.primaryEmail, user.name.fullName);
      } else {
        console.log("%s", user.primaryEmail);
      }
    }
  }
}
// [END admin_sdk_directory_quickstart]


================================================
FILE: adminSDK/reports/quickstart.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START admin_sdk_reports_quickstart]
/**
 * List login events for a Google Workspace domain.
 * @see https://developers.google.com/admin-sdk/reports/reference/rest/v1/activities/list
 */
function listLogins() {
  const userKey = "all";
  const applicationName = "login";
  const optionalArgs = {
    maxResults: 10,
  };
  if (!AdminReports || !AdminReports.Activities) {
    throw new Error("Enable the AdminReports Advanced Service.");
  }
  const response = AdminReports.Activities.list(
    userKey,
    applicationName,
    optionalArgs,
  );
  const activities = response.items;
  if (!activities || activities.length === 0) {
    console.log("No logins found.");
    return;
  }
  // Print login events
  console.log("Logins:");
  for (const activity of activities) {
    if (
      activity.id?.time &&
      activity.actor?.email &&
      activity.events?.[0]?.name
    ) {
      console.log(
        "%s: %s (%s)",
        activity.id.time,
        activity.actor.email,
        activity.events[0].name,
      );
    }
  }
}
// [END admin_sdk_reports_quickstart]


================================================
FILE: adminSDK/reseller/quickstart.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START admin_sdk_reseller_quickstart]
/**
 * List Admin SDK reseller.
 * @see https://developers.google.com/admin-sdk/reseller/reference/rest/v1/subscriptions/list
 */
function listSubscriptions() {
  const optionalArgs = {
    maxResults: 10,
  };
  if (!AdminReseller || !AdminReseller.Subscriptions) {
    throw new Error("Enable the AdminReseller Advanced Service.");
  }
  const response = AdminReseller.Subscriptions.list(optionalArgs);
  const subscriptions = response.subscriptions;
  if (!subscriptions || subscriptions.length === 0) {
    console.log("No subscriptions found.");
    return;
  }
  console.log("Subscriptions:");
  for (const subscription of subscriptions) {
    if (subscription.customerId && subscription.skuId) {
      if (subscription.plan?.planName) {
        console.log(
          "%s (%s, %s)",
          subscription.customerId,
          subscription.skuId,
          subscription.plan.planName,
        );
      } else {
        console.log("%s (%s)", subscription.customerId, subscription.skuId);
      }
    }
  }
}
// [END admin_sdk_reseller_quickstart]


================================================
FILE: advanced/README.md
================================================
# Advanced Services Samples

This directory contains samples for using Apps Script Advanced Services.

> Note: These services must be [enabled](https://developers.google.com/apps-script/guides/services/advanced#enabling_advanced_services) before running these samples.

Learn more at [developers.google.com](https://developers.google.com/apps-script/guides/services/advanced).


================================================
FILE: advanced/adminSDK.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_admin_sdk_list_all_users]
/**
 * Lists all the users in a domain sorted by first name.
 * @see https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/list
 */
function listAllUsers() {
  let pageToken;
  let page;
  do {
    page = AdminDirectory.Users.list({
      domain: "example.com",
      orderBy: "givenName",
      maxResults: 100,
      pageToken: pageToken,
    });
    const users = page.users;
    if (!users) {
      console.log("No users found.");
      return;
    }
    // Print the user's full name and email.
    for (const user of users) {
      console.log("%s (%s)", user.name.fullName, user.primaryEmail);
    }
    pageToken = page.nextPageToken;
  } while (pageToken);
}
// [END apps_script_admin_sdk_list_all_users]

// [START apps_script_admin_sdk_get_users]
/**
 * Get a user by their email address and logs all of their data as a JSON string.
 * @see https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/get
 */
function getUser() {
  // TODO (developer) - Replace userEmail value with yours
  const userEmail = "liz@example.com";
  try {
    const user = AdminDirectory.Users.get(userEmail);
    console.log("User data:\n %s", JSON.stringify(user, null, 2));
  } catch (err) {
    // TODO (developer)- Handle exception from the API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_admin_sdk_get_users]

// [START apps_script_admin_sdk_add_user]
/**
 * Adds a new user to the domain, including only the required information. For
 * the full list of user fields, see the API's reference documentation:
 * @see https://developers.google.com/admin-sdk/directory/v1/reference/users/insert
 */
function addUser() {
  let user = {
    // TODO (developer) - Replace primaryEmail value with yours
    primaryEmail: "liz@example.com",
    name: {
      givenName: "Elizabeth",
      familyName: "Smith",
    },
    // Generate a random password string.
    password: Math.random().toString(36),
  };
  try {
    user = AdminDirectory.Users.insert(user);
    console.log("User %s created with ID %s.", user.primaryEmail, user.id);
  } catch (err) {
    // TODO (developer)- Handle exception from the API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_admin_sdk_add_user]

// [START apps_script_admin_sdk_create_alias]
/**
 * Creates an alias (nickname) for a user.
 * @see https://developers.google.com/admin-sdk/directory/reference/rest/v1/users.aliases/insert
 */
function createAlias() {
  // TODO (developer) - Replace userEmail value with yours
  const userEmail = "liz@example.com";
  let alias = {
    alias: "chica@example.com",
  };
  try {
    alias = AdminDirectory.Users.Aliases.insert(alias, userEmail);
    console.log("Created alias %s for user %s.", alias.alias, userEmail);
  } catch (err) {
    // TODO (developer)- Handle exception from the API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_admin_sdk_create_alias]

// [START apps_script_admin_sdk_list_all_groups]
/**
 * Lists all the groups in the domain.
 * @see https://developers.google.com/admin-sdk/directory/reference/rest/v1/groups/list
 */
function listAllGroups() {
  let pageToken;
  let page;
  do {
    page = AdminDirectory.Groups.list({
      domain: "example.com",
      maxResults: 100,
      pageToken: pageToken,
    });
    const groups = page.groups;
    if (!groups) {
      console.log("No groups found.");
      return;
    }
    // Print group name and email.
    for (const group of groups) {
      console.log("%s (%s)", group.name, group.email);
    }
    pageToken = page.nextPageToken;
  } while (pageToken);
}
// [END apps_script_admin_sdk_list_all_groups]

// [START apps_script_admin_sdk_add_group_member]
/**
 * Adds a user to an existing group in the domain.
 * @see https://developers.google.com/admin-sdk/directory/reference/rest/v1/members/insert
 */
function addGroupMember() {
  // TODO (developer) - Replace userEmail value with yours
  const userEmail = "liz@example.com";
  // TODO (developer) - Replace groupEmail value with yours
  const groupEmail = "bookclub@example.com";
  const member = {
    email: userEmail,
    role: "MEMBER",
  };
  try {
    AdminDirectory.Members.insert(member, groupEmail);
    console.log(
      "User %s added as a member of group %s.",
      userEmail,
      groupEmail,
    );
  } catch (err) {
    // TODO (developer)- Handle exception from the API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_admin_sdk_add_group_member]

// [START apps_script_admin_sdk_migrate]
/**
 * Gets three RFC822 formatted messages from the each of the latest three
 * threads in the user's Gmail inbox, creates a blob from the email content
 * (including attachments), and inserts it in a Google Group in the domain.
 */
function migrateMessages() {
  // TODO (developer) - Replace groupId value with yours
  const groupId = "exampleGroup@example.com";
  const messagesToMigrate = getRecentMessagesContent();
  for (const messageContent of messagesToMigrate) {
    const contentBlob = Utilities.newBlob(messageContent, "message/rfc822");
    AdminGroupsMigration.Archive.insert(groupId, contentBlob);
  }
}

/**
 * Gets a list of recent messages' content from the user's Gmail account.
 * By default, fetches 3 messages from the latest 3 threads.
 *
 * @return {Array} the messages' content.
 */
function getRecentMessagesContent() {
  const NUM_THREADS = 3;
  const NUM_MESSAGES = 3;
  const threads = GmailApp.getInboxThreads(0, NUM_THREADS);
  const messages = GmailApp.getMessagesForThreads(threads);
  const messagesContent = [];
  for (let i = 0; i < messages.length; i++) {
    for (let j = 0; j < NUM_MESSAGES; j++) {
      const message = messages[i][j];
      if (message) {
        messagesContent.push(message.getRawContent());
      }
    }
  }
  return messagesContent;
}
// [END apps_script_admin_sdk_migrate]

// [START apps_script_admin_sdk_get_group_setting]
/**
 * Gets a group's settings and logs them to the console.
 */
function getGroupSettings() {
  // TODO (developer) - Replace groupId value with yours
  const groupId = "exampleGroup@example.com";
  try {
    const group = AdminGroupsSettings.Groups.get(groupId);
    console.log(JSON.stringify(group, null, 2));
  } catch (err) {
    // TODO (developer)- Handle exception from the API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_admin_sdk_get_group_setting]

// [START apps_script_admin_sdk_update_group_setting]
/**
 * Updates group's settings. Here, the description is modified, but various
 * other settings can be changed in the same way.
 * @see https://developers.google.com/admin-sdk/groups-settings/v1/reference/groups/patch
 */
function updateGroupSettings() {
  const groupId = "exampleGroup@example.com";
  try {
    const group = AdminGroupsSettings.newGroups();
    group.description = "Newly changed group description";
    AdminGroupsSettings.Groups.patch(group, groupId);
  } catch (err) {
    // TODO (developer)- Handle exception from the API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_admin_sdk_update_group_setting]

// [START apps_script_admin_sdk_get_license_assignments]
/**
 * Logs the license assignments, including the product ID and the sku ID, for
 * the users in the domain. Notice the use of page tokens to access the full
 * list of results.
 */
function getLicenseAssignments() {
  const productId = "Google-Apps";
  const customerId = "example.com";
  let assignments = [];
  let pageToken = null;
  do {
    const response = AdminLicenseManager.LicenseAssignments.listForProduct(
      productId,
      customerId,
      {
        maxResults: 500,
        pageToken: pageToken,
      },
    );
    assignments = assignments.concat(response.items);
    pageToken = response.nextPageToken;
  } while (pageToken);
  // Print the productId and skuId
  for (const assignment of assignments) {
    console.log(
      "userId: %s, productId: %s, skuId: %s",
      assignment.userId,
      assignment.productId,
      assignment.skuId,
    );
  }
}
// [END apps_script_admin_sdk_get_license_assignments]

// [START apps_script_admin_sdk_insert_license_assignment]
/**
 * Insert a license assignment for a user, for a given product ID and sku ID
 * combination.
 * For more details follow the link
 * https://developers.google.com/admin-sdk/licensing/reference/rest/v1/licenseAssignments/insert
 */
function insertLicenseAssignment() {
  const productId = "Google-Apps";
  const skuId = "Google-Vault";
  const userId = "marty@hoverboard.net";
  try {
    const results = AdminLicenseManager.LicenseAssignments.insert(
      { userId: userId },
      productId,
      skuId,
    );
    console.log(results);
  } catch (e) {
    // TODO (developer) - Handle exception.
    console.log("Failed with an error %s ", e.message);
  }
}
// [END apps_script_admin_sdk_insert_license_assignment]

// [START apps_script_admin_sdk_generate_login_activity_report]
/**
 * Generates a login activity report for the last week as a spreadsheet. The
 * report includes the time, user, and login result.
 * @see https://developers.google.com/admin-sdk/reports/reference/rest/v1/activities/list
 */
function generateLoginActivityReport() {
  const now = new Date();
  const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
  const startTime = oneWeekAgo.toISOString();
  const endTime = now.toISOString();

  const rows = [];
  let pageToken;
  let page;
  do {
    page = AdminReports.Activities.list("all", "login", {
      startTime: startTime,
      endTime: endTime,
      maxResults: 500,
      pageToken: pageToken,
    });
    const items = page.items;
    if (items) {
      for (const item of items) {
        const row = [
          new Date(item.id.time),
          item.actor.email,
          item.events[0].name,
        ];
        rows.push(row);
      }
    }
    pageToken = page.nextPageToken;
  } while (pageToken);

  if (rows.length === 0) {
    console.log("No results returned.");
    return;
  }
  const spreadsheet = SpreadsheetApp.create("Google Workspace Login Report");
  const sheet = spreadsheet.getActiveSheet();

  // Append the headers.
  const headers = ["Time", "User", "Login Result"];
  sheet.appendRow(headers);

  // Append the results.
  sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);

  console.log("Report spreadsheet created: %s", spreadsheet.getUrl());
}
// [END apps_script_admin_sdk_generate_login_activity_report]

// [START apps_script_admin_sdk_generate_user_usage_report]
/**
 * Generates a user usage report for this day last week as a spreadsheet. The
 * report includes the date, user, last login time, number of emails received,
 * and number of drive files created.
 * @see https://developers.google.com/admin-sdk/reports/reference/rest/v1/userUsageReport/get
 */
function generateUserUsageReport() {
  const today = new Date();
  const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
  const timezone = Session.getScriptTimeZone();
  const date = Utilities.formatDate(oneWeekAgo, timezone, "yyyy-MM-dd");

  const parameters = [
    "accounts:last_login_time",
    "gmail:num_emails_received",
    "drive:num_items_created",
  ];
  const rows = [];
  let pageToken;
  let page;
  do {
    page = AdminReports.UserUsageReport.get("all", date, {
      parameters: parameters.join(","),
      maxResults: 500,
      pageToken: pageToken,
    });
    if (page.warnings) {
      for (const warning of page.warnings) {
        console.log(warning.message);
      }
    }
    const reports = page.usageReports;
    if (reports) {
      for (const report of reports) {
        const parameterValues = getParameterValues(report.parameters);
        const row = [
          report.date,
          report.entity.userEmail,
          parameterValues["accounts:last_login_time"],
          parameterValues["gmail:num_emails_received"],
          parameterValues["drive:num_items_created"],
        ];
        rows.push(row);
      }
    }
    pageToken = page.nextPageToken;
  } while (pageToken);

  if (rows.length === 0) {
    console.log("No results returned.");
    return;
  }
  const spreadsheet = SpreadsheetApp.create(
    "Google Workspace User Usage Report",
  );
  const sheet = spreadsheet.getActiveSheet();

  // Append the headers.
  const headers = [
    "Date",
    "User",
    "Last Login",
    "Num Emails Received",
    "Num Drive Files Created",
  ];
  sheet.appendRow(headers);

  // Append the results.
  sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);

  console.log("Report spreadsheet created: %s", spreadsheet.getUrl());
}

/**
 * Gets a map of parameter names to values from an array of parameter objects.
 * @param {Array} parameters An array of parameter objects.
 * @return {Object} A map from parameter names to their values.
 */
function getParameterValues(parameters) {
  return parameters.reduce((result, parameter) => {
    const name = parameter.name;
    let value;
    if (parameter.intValue !== undefined) {
      value = parameter.intValue;
    } else if (parameter.stringValue !== undefined) {
      value = parameter.stringValue;
    } else if (parameter.datetimeValue !== undefined) {
      value = new Date(parameter.datetimeValue);
    } else if (parameter.boolValue !== undefined) {
      value = parameter.boolValue;
    }
    result[name] = value;
    return result;
  }, {});
}
// [END apps_script_admin_sdk_generate_user_usage_report]

// [START apps_script_admin_sdk_get_subscriptions]
/**
 * Logs the list of subscriptions, including the customer ID, date created, plan
 * name, and the sku ID. Notice the use of page tokens to access the full list
 * of results.
 * @see https://developers.google.com/admin-sdk/reseller/reference/rest/v1/subscriptions/list
 */
function getSubscriptions() {
  let result;
  let pageToken;
  do {
    result = AdminReseller.Subscriptions.list({
      pageToken: pageToken,
    });
    for (const sub of result.subscriptions) {
      const creationDate = new Date();
      creationDate.setUTCSeconds(sub.creationTime);
      console.log(
        "customer ID: %s, date created: %s, plan name: %s, sku id: %s",
        sub.customerId,
        creationDate.toDateString(),
        sub.plan.planName,
        sub.skuId,
      );
    }
    pageToken = result.nextPageToken;
  } while (pageToken);
}
// [END apps_script_admin_sdk_get_subscriptions]


================================================
FILE: advanced/adsense.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_adsense_list_accounts]
/**
 * Lists available AdSense accounts.
 */
function listAccounts() {
  let pageToken;
  do {
    const response = AdSense.Accounts.list({ pageToken: pageToken });
    if (!response.accounts) {
      console.log("No accounts found.");
      return;
    }
    for (const account of response.accounts) {
      console.log(
        'Found account with resource name "%s" and display name "%s".',
        account.name,
        account.displayName,
      );
    }
    pageToken = response.nextPageToken;
  } while (pageToken);
}
// [END apps_script_adsense_list_accounts]

// [START apps_script_adsense_list_ad_clients]
/**
 * Logs available Ad clients for an account.
 *
 * @param {string} accountName The resource name of the account that owns the
 *     collection of ad clients.
 */
function listAdClients(accountName) {
  let pageToken;
  do {
    const response = AdSense.Accounts.Adclients.list(accountName, {
      pageToken: pageToken,
    });
    if (!response.adClients) {
      console.log("No ad clients found for this account.");
      return;
    }
    for (const adClient of response.adClients) {
      console.log(
        'Found ad client for product "%s" with resource name "%s".',
        adClient.productCode,
        adClient.name,
      );
      console.log(
        "Reporting dimension ID: %s",
        adClient.reportingDimensionId ?? "None",
      );
    }
    pageToken = response.nextPageToken;
  } while (pageToken);
}
// [END apps_script_adsense_list_ad_clients]

// [START apps_script_adsense_list_ad_units]
/**
 * Lists ad units.
 * @param {string} adClientName The resource name of the ad client that owns the collection
 *     of ad units.
 */
function listAdUnits(adClientName) {
  let pageToken;
  do {
    const response = AdSense.Accounts.Adclients.Adunits.list(adClientName, {
      pageSize: 50,
      pageToken: pageToken,
    });
    if (!response.adUnits) {
      console.log("No ad units found for this ad client.");
      return;
    }
    for (const adUnit of response.adUnits) {
      console.log(
        'Found ad unit with resource name "%s" and display name "%s".',
        adUnit.name,
        adUnit.displayName,
      );
    }

    pageToken = response.nextPageToken;
  } while (pageToken);
}
// [END apps_script_adsense_list_ad_units]

// [START apps_script_adsense_generate_report]
/**
 * Generates a spreadsheet report for a specific ad client in an account.
 * @param {string} accountName The resource name of the account.
 * @param {string} adClientReportingDimensionId The reporting dimension ID
 *     of the ad client.
 */
function generateReport(accountName, adClientReportingDimensionId) {
  // Prepare report.
  const today = new Date();
  const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);

  const report = AdSense.Accounts.Reports.generate(accountName, {
    // Specify the desired ad client using a filter.
    filters: [
      `AD_CLIENT_ID==${escapeFilterParameter(adClientReportingDimensionId)}`,
    ],
    metrics: [
      "PAGE_VIEWS",
      "AD_REQUESTS",
      "AD_REQUESTS_COVERAGE",
      "CLICKS",
      "AD_REQUESTS_CTR",
      "COST_PER_CLICK",
      "AD_REQUESTS_RPM",
      "ESTIMATED_EARNINGS",
    ],
    dimensions: ["DATE"],
    ...dateToJson("startDate", oneWeekAgo),
    ...dateToJson("endDate", today),
    // Sort by ascending date.
    orderBy: ["+DATE"],
  });

  if (!report.rows) {
    console.log("No rows returned.");
    return;
  }
  const spreadsheet = SpreadsheetApp.create("AdSense Report");
  const sheet = spreadsheet.getActiveSheet();

  // Append the headers.
  sheet.appendRow(report.headers.map((header) => header.name));

  // Append the results.
  sheet
    .getRange(2, 1, report.rows.length, report.headers.length)
    .setValues(report.rows.map((row) => row.cells.map((cell) => cell.value)));

  console.log("Report spreadsheet created: %s", spreadsheet.getUrl());
}

/**
 * Escape special characters for a parameter being used in a filter.
 * @param {string} parameter The parameter to be escaped.
 * @return {string} The escaped parameter.
 */
function escapeFilterParameter(parameter) {
  return parameter.replace("\\", "\\\\").replace(",", "\\,");
}

/**
 * Returns the JSON representation of a Date object (as a google.type.Date).
 *
 * @param {string} paramName the name of the date parameter
 * @param {Date} value the date
 * @return {object} formatted date
 */
function dateToJson(paramName, value) {
  return {
    [`${paramName}.year`]: value.getFullYear(),
    [`${paramName}.month`]: value.getMonth() + 1,
    [`${paramName}.day`]: value.getDate(),
  };
}

// [END apps_script_adsense_generate_report]


================================================
FILE: advanced/analytics.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_analytics_accounts]
/**
 * Lists Analytics accounts.
 */
function listAccounts() {
  try {
    const accounts = Analytics.Management.Accounts.list();
    if (!accounts.items || !accounts.items.length) {
      console.log("No accounts found.");
      return;
    }

    for (let i = 0; i < accounts.items.length; i++) {
      const account = accounts.items[i];
      console.log('Account: name "%s", id "%s".', account.name, account.id);

      // List web properties in the account.
      listWebProperties(account.id);
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}

/**
 * Lists web properites for an Analytics account.
 * @param  {string} accountId The account ID.
 */
function listWebProperties(accountId) {
  try {
    const webProperties = Analytics.Management.Webproperties.list(accountId);
    if (!webProperties.items || !webProperties.items.length) {
      console.log("\tNo web properties found.");
      return;
    }
    for (let i = 0; i < webProperties.items.length; i++) {
      const webProperty = webProperties.items[i];
      console.log(
        '\tWeb Property: name "%s", id "%s".',
        webProperty.name,
        webProperty.id,
      );

      // List profiles in the web property.
      listProfiles(accountId, webProperty.id);
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}

/**
 * Logs a list of Analytics accounts profiles.
 * @param  {string} accountId     The Analytics account ID
 * @param  {string} webPropertyId The web property ID
 */
function listProfiles(accountId, webPropertyId) {
  // Note: If you experience "Quota Error: User Rate Limit Exceeded" errors
  // due to the number of accounts or profiles you have, you may be able to
  // avoid it by adding a Utilities.sleep(1000) statement here.
  try {
    const profiles = Analytics.Management.Profiles.list(
      accountId,
      webPropertyId,
    );

    if (!profiles.items || !profiles.items.length) {
      console.log("\t\tNo web properties found.");
      return;
    }
    for (let i = 0; i < profiles.items.length; i++) {
      const profile = profiles.items[i];
      console.log('\t\tProfile: name "%s", id "%s".', profile.name, profile.id);
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_analytics_accounts]

// [START apps_script_analytics_reports]
/**
 * Runs a report of an Analytics profile ID. Creates a sheet with the report.
 * @param  {string} profileId The profile ID.
 */
function runReport(profileId) {
  const today = new Date();
  const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);

  const startDate = Utilities.formatDate(
    oneWeekAgo,
    Session.getScriptTimeZone(),
    "yyyy-MM-dd",
  );
  const endDate = Utilities.formatDate(
    today,
    Session.getScriptTimeZone(),
    "yyyy-MM-dd",
  );

  const tableId = `ga:${profileId}`;
  const metric = "ga:visits";
  const options = {
    dimensions: "ga:source,ga:keyword",
    sort: "-ga:visits,ga:source",
    filters: "ga:medium==organic",
    "max-results": 25,
  };
  const report = Analytics.Data.Ga.get(
    tableId,
    startDate,
    endDate,
    metric,
    options,
  );

  if (!report.rows) {
    console.log("No rows returned.");
    return;
  }

  const spreadsheet = SpreadsheetApp.create("Google Analytics Report");
  const sheet = spreadsheet.getActiveSheet();

  // Append the headers.
  const headers = report.columnHeaders.map((columnHeader) => {
    return columnHeader.name;
  });
  sheet.appendRow(headers);

  // Append the results.
  sheet
    .getRange(2, 1, report.rows.length, headers.length)
    .setValues(report.rows);

  console.log("Report spreadsheet created: %s", spreadsheet.getUrl());
}
// [END apps_script_analytics_reports]


================================================
FILE: advanced/analyticsAdmin.gs
================================================
/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_analyticsadmin]
/**
 * Logs the Google Analytics accounts accessible by the current user.
 */
function listAccounts() {
  try {
    accounts = AnalyticsAdmin.Accounts.list();
    if (!accounts.items || !accounts.items.length) {
      console.log("No accounts found.");
      return;
    }

    for (let i = 0; i < accounts.items.length; i++) {
      const account = accounts.items[i];
      console.log(
        'Account: name "%s", displayName "%s".',
        account.name,
        account.displayName,
      );
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_analyticsadmin]


================================================
FILE: advanced/analyticsData.gs
================================================
/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_analyticsdata]
/**
 * Runs a report of a Google Analytics 4 property ID. Creates a sheet with the
 * report.
 */
function runReport() {
  /**
   * TODO(developer): Uncomment this variable and replace with your
   *   Google Analytics 4 property ID before running the sample.
   */
  const propertyId = "YOUR-GA4-PROPERTY-ID";

  try {
    const metric = AnalyticsData.newMetric();
    metric.name = "activeUsers";

    const dimension = AnalyticsData.newDimension();
    dimension.name = "city";

    const dateRange = AnalyticsData.newDateRange();
    dateRange.startDate = "2020-03-31";
    dateRange.endDate = "today";

    const request = AnalyticsData.newRunReportRequest();
    request.dimensions = [dimension];
    request.metrics = [metric];
    request.dateRanges = dateRange;

    const report = AnalyticsData.Properties.runReport(
      request,
      `properties/${propertyId}`,
    );
    if (!report.rows) {
      console.log("No rows returned.");
      return;
    }

    const spreadsheet = SpreadsheetApp.create("Google Analytics Report");
    const sheet = spreadsheet.getActiveSheet();

    // Append the headers.
    const dimensionHeaders = report.dimensionHeaders.map((dimensionHeader) => {
      return dimensionHeader.name;
    });
    const metricHeaders = report.metricHeaders.map((metricHeader) => {
      return metricHeader.name;
    });
    const headers = [...dimensionHeaders, ...metricHeaders];

    sheet.appendRow(headers);

    // Append the results.
    const rows = report.rows.map((row) => {
      const dimensionValues = row.dimensionValues.map((dimensionValue) => {
        return dimensionValue.value;
      });
      const metricValues = row.metricValues.map((metricValues) => {
        return metricValues.value;
      });
      return [...dimensionValues, ...metricValues];
    });

    sheet.getRange(2, 1, report.rows.length, headers.length).setValues(rows);

    console.log("Report spreadsheet created: %s", spreadsheet.getUrl());
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_analyticsdata]


================================================
FILE: advanced/bigquery.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_bigquery_run_query]
/**
 * Runs a BigQuery query and logs the results in a spreadsheet.
 */
function runQuery() {
  // Replace this value with the project ID listed in the Google
  // Cloud Platform project.
  const projectId = "XXXXXXXX";

  const request = {
    // TODO (developer) - Replace query with yours
    query:
      "SELECT refresh_date AS Day, term AS Top_Term, rank " +
      "FROM `bigquery-public-data.google_trends.top_terms` " +
      "WHERE rank = 1 " +
      "AND refresh_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 WEEK) " +
      "GROUP BY Day, Top_Term, rank " +
      "ORDER BY Day DESC;",
    useLegacySql: false,
  };
  let queryResults = BigQuery.Jobs.query(request, projectId);
  const jobId = queryResults.jobReference.jobId;

  // Check on status of the Query Job.
  let sleepTimeMs = 500;
  while (!queryResults.jobComplete) {
    Utilities.sleep(sleepTimeMs);
    sleepTimeMs *= 2;
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
  }

  // Get all the rows of results.
  let rows = queryResults.rows;
  while (queryResults.pageToken) {
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId, {
      pageToken: queryResults.pageToken,
    });
    rows = rows.concat(queryResults.rows);
  }

  if (!rows) {
    console.log("No rows returned.");
    return;
  }
  const spreadsheet = SpreadsheetApp.create("BigQuery Results");
  const sheet = spreadsheet.getActiveSheet();

  // Append the headers.
  const headers = queryResults.schema.fields.map((field) => field.name);
  sheet.appendRow(headers);

  // Append the results.
  const data = new Array(rows.length);
  for (let i = 0; i < rows.length; i++) {
    const cols = rows[i].f;
    data[i] = new Array(cols.length);
    for (let j = 0; j < cols.length; j++) {
      data[i][j] = cols[j].v;
    }
  }
  sheet.getRange(2, 1, rows.length, headers.length).setValues(data);

  console.log("Results spreadsheet created: %s", spreadsheet.getUrl());
}
// [END apps_script_bigquery_run_query]

// [START apps_script_bigquery_load_csv]
/**
 * Loads a CSV into BigQuery
 */
function loadCsv() {
  // Replace this value with the project ID listed in the Google
  // Cloud Platform project.
  const projectId = "XXXXXXXX";
  // Create a dataset in the BigQuery UI (https://bigquery.cloud.google.com)
  // and enter its ID below.
  const datasetId = "YYYYYYYY";
  // Sample CSV file of Google Trends data conforming to the schema below.
  // https://docs.google.com/file/d/0BwzA1Orbvy5WMXFLaTR1Z1p2UDg/edit
  const csvFileId = "0BwzA1Orbvy5WMXFLaTR1Z1p2UDg";

  // Create the table.
  const tableId = `pets_${new Date().getTime()}`;
  let table = {
    tableReference: {
      projectId: projectId,
      datasetId: datasetId,
      tableId: tableId,
    },
    schema: {
      fields: [
        { name: "week", type: "STRING" },
        { name: "cat", type: "INTEGER" },
        { name: "dog", type: "INTEGER" },
        { name: "bird", type: "INTEGER" },
      ],
    },
  };
  try {
    table = BigQuery.Tables.insert(table, projectId, datasetId);
    console.log("Table created: %s", table.id);
  } catch (err) {
    console.log("unable to create table");
  }
  // Load CSV data from Drive and convert to the correct format for upload.
  const file = DriveApp.getFileById(csvFileId);
  const data = file.getBlob().setContentType("application/octet-stream");

  // Create the data upload job.
  const job = {
    configuration: {
      load: {
        destinationTable: {
          projectId: projectId,
          datasetId: datasetId,
          tableId: tableId,
        },
        skipLeadingRows: 1,
      },
    },
  };
  try {
    const jobResult = BigQuery.Jobs.insert(job, projectId, data);
    console.log(`Load job started. Status: ${jobResult.status.state}`);
  } catch (err) {
    console.log("unable to insert job");
  }
}
// [END apps_script_bigquery_load_csv]


================================================
FILE: advanced/calendar.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START calendar_list_calendars]
/**
 * Lists the calendars shown in the user's calendar list.
 * @see https://developers.google.com/calendar/api/v3/reference/calendarList/list
 */
function listCalendars() {
  let calendars;
  let pageToken;
  do {
    calendars = Calendar.CalendarList.list({
      maxResults: 100,
      pageToken: pageToken,
    });
    if (!calendars.items || calendars.items.length === 0) {
      console.log("No calendars found.");
      return;
    }
    // Print the calendar id and calendar summary
    for (const calendar of calendars.items) {
      console.log("%s (ID: %s)", calendar.summary, calendar.id);
    }
    pageToken = calendars.nextPageToken;
  } while (pageToken);
}
// [END calendar_list_calendars]

// [START calendar_create_event]
/**
 * Creates an event in the user's default calendar.
 * @see https://developers.google.com/calendar/api/v3/reference/events/insert
 */
function createEvent() {
  const calendarId = "primary";
  const start = getRelativeDate(1, 12);
  const end = getRelativeDate(1, 13);
  // event details for creating event.
  let event = {
    summary: "Lunch Meeting",
    location: "The Deli",
    description: "To discuss our plans for the presentation next week.",
    start: {
      dateTime: start.toISOString(),
    },
    end: {
      dateTime: end.toISOString(),
    },
    attendees: [
      { email: "gduser1@workspacesample.dev" },
      { email: "gduser2@workspacesample.dev" },
    ],
    // Red background. Use Calendar.Colors.get() for the full list.
    colorId: 11,
  };
  try {
    // call method to insert/create new event in provided calandar
    event = Calendar.Events.insert(event, calendarId);
    console.log(`Event ID: ${event.id}`);
  } catch (err) {
    console.log("Failed with error %s", err.message);
  }
}

/**
 * Helper function to get a new Date object relative to the current date.
 * @param {number} daysOffset The number of days in the future for the new date.
 * @param {number} hour The hour of the day for the new date, in the time zone
 *     of the script.
 * @return {Date} The new date.
 */
function getRelativeDate(daysOffset, hour) {
  const date = new Date();
  date.setDate(date.getDate() + daysOffset);
  date.setHours(hour);
  date.setMinutes(0);
  date.setSeconds(0);
  date.setMilliseconds(0);
  return date;
}
// [END calendar_create_event]

// [START calendar_list_events]
/**
 * Lists the next 10 upcoming events in the user's default calendar.
 * @see https://developers.google.com/calendar/api/v3/reference/events/list
 */
function listNext10Events() {
  const calendarId = "primary";
  const now = new Date();
  const events = Calendar.Events.list(calendarId, {
    timeMin: now.toISOString(),
    singleEvents: true,
    orderBy: "startTime",
    maxResults: 10,
  });
  if (!events.items || events.items.length === 0) {
    console.log("No events found.");
    return;
  }
  for (const event of events.items) {
    if (event.start.date) {
      // All-day event.
      const start = new Date(event.start.date);
      console.log("%s (%s)", event.summary, start.toLocaleDateString());
      continue;
    }
    const start = new Date(event.start.dateTime);
    console.log("%s (%s)", event.summary, start.toLocaleString());
  }
}
// [END calendar_list_events]

// [START calendar_log_synced_events]
/**
 * Retrieve and log events from the given calendar that have been modified
 * since the last sync. If the sync token is missing or invalid, log all
 * events from up to a month ago (a full sync).
 *
 * @param {string} calendarId The ID of the calender to retrieve events from.
 * @param {boolean} fullSync If true, throw out any existing sync token and
 *        perform a full sync; if false, use the existing sync token if possible.
 */
function logSyncedEvents(calendarId, fullSync) {
  const properties = PropertiesService.getUserProperties();
  const options = {
    maxResults: 100,
  };
  const syncToken = properties.getProperty("syncToken");
  if (syncToken && !fullSync) {
    options.syncToken = syncToken;
  } else {
    // Sync events up to thirty days in the past.
    options.timeMin = getRelativeDate(-30, 0).toISOString();
  }
  // Retrieve events one page at a time.
  let events;
  let pageToken;
  do {
    try {
      options.pageToken = pageToken;
      events = Calendar.Events.list(calendarId, options);
    } catch (e) {
      // Check to see if the sync token was invalidated by the server;
      // if so, perform a full sync instead.
      if (
        e.message === "Sync token is no longer valid, a full sync is required."
      ) {
        properties.deleteProperty("syncToken");
        logSyncedEvents(calendarId, true);
        return;
      }
      throw new Error(e.message);
    }
    if (events.items && events.items.length === 0) {
      console.log("No events found.");
      return;
    }
    for (const event of events.items) {
      if (event.status === "cancelled") {
        console.log("Event id %s was cancelled.", event.id);
        return;
      }
      if (event.start.date) {
        const start = new Date(event.start.date);
        console.log("%s (%s)", event.summary, start.toLocaleDateString());
        return;
      }
      // Events that don't last all day; they have defined start times.
      const start = new Date(event.start.dateTime);
      console.log("%s (%s)", event.summary, start.toLocaleString());
    }
    pageToken = events.nextPageToken;
  } while (pageToken);
  properties.setProperty("syncToken", events.nextSyncToken);
}
// [END calendar_log_synced_events]

// [START calendar_conditional_update]
/**
 * Creates an event in the user's default calendar, waits 30 seconds, then
 * attempts to update the event's location, on the condition that the event
 * has not been changed since it was created.  If the event is changed during
 * the 30-second wait, then the subsequent update will throw a 'Precondition
 * Failed' error.
 *
 * The conditional update is accomplished by setting the 'If-Match' header
 * to the etag of the new event when it was created.
 */
function conditionalUpdate() {
  const calendarId = "primary";
  const start = getRelativeDate(1, 12);
  const end = getRelativeDate(1, 13);
  let event = {
    summary: "Lunch Meeting",
    location: "The Deli",
    description: "To discuss our plans for the presentation next week.",
    start: {
      dateTime: start.toISOString(),
    },
    end: {
      dateTime: end.toISOString(),
    },
    attendees: [
      { email: "gduser1@workspacesample.dev" },
      { email: "gduser2@workspacesample.dev" },
    ],
    // Red background. Use Calendar.Colors.get() for the full list.
    colorId: 11,
  };
  event = Calendar.Events.insert(event, calendarId);
  console.log(`Event ID: ${event.getId()}`);
  // Wait 30 seconds to see if the event has been updated outside this script.
  Utilities.sleep(30 * 1000);
  // Try to update the event, on the condition that the event state has not
  // changed since the event was created.
  event.location = "The Coffee Shop";
  try {
    event = Calendar.Events.update(
      event,
      calendarId,
      event.id,
      {},
      { "If-Match": event.etag },
    );
    console.log(`Successfully updated event: ${event.id}`);
  } catch (e) {
    console.log(`Fetch threw an exception: ${e}`);
  }
}
// [END calendar_conditional_update]

// [START calendar_conditional_fetch]
/**
 * Creates an event in the user's default calendar, then re-fetches the event
 * every second, on the condition that the event has changed since the last
 * fetch.
 *
 * The conditional fetch is accomplished by setting the 'If-None-Match' header
 * to the etag of the last known state of the event.
 */
function conditionalFetch() {
  const calendarId = "primary";
  const start = getRelativeDate(1, 12);
  const end = getRelativeDate(1, 13);
  let event = {
    summary: "Lunch Meeting",
    location: "The Deli",
    description: "To discuss our plans for the presentation next week.",
    start: {
      dateTime: start.toISOString(),
    },
    end: {
      dateTime: end.toISOString(),
    },
    attendees: [
      { email: "gduser1@workspacesample.dev" },
      { email: "gduser2@workspacesample.dev" },
    ],
    // Red background. Use Calendar.Colors.get() for the full list.
    colorId: 11,
  };
  try {
    // insert event
    event = Calendar.Events.insert(event, calendarId);
    console.log(`Event ID: ${event.getId()}`);
    // Re-fetch the event each second, but only get a result if it has changed.
    for (let i = 0; i < 30; i++) {
      Utilities.sleep(1000);
      event = Calendar.Events.get(
        calendarId,
        event.id,
        {},
        { "If-None-Match": event.etag },
      );
      console.log(`New event description: ${event.start.dateTime}`);
    }
  } catch (e) {
    console.log(`Fetch threw an exception: ${e}`);
  }
}
// [END calendar_conditional_fetch]


================================================
FILE: advanced/chat.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// [START chat_post_message_with_user_credentials]
/**
 * Posts a new message to the specified space on behalf of the user.
 * @param {string} spaceName The resource name of the space.
 */
function postMessageWithUserCredentials(spaceName) {
  try {
    const message = { text: "Hello world!" };
    Chat.Spaces.Messages.create(message, spaceName);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to create message with error %s", err.message);
  }
}
// [END chat_post_message_with_user_credentials]

// [START chat_post_message_with_app_credentials]
/**
 * Posts a new message to the specified space on behalf of the app.
 * @param {string} spaceName The resource name of the space.
 */
function postMessageWithAppCredentials(spaceName) {
  try {
    // See https://developers.google.com/chat/api/guides/auth/service-accounts
    // for details on how to obtain a service account OAuth token.
    const appToken = getToken_();
    const message = { text: "Hello world!" };
    Chat.Spaces.Messages.create(
      message,
      spaceName,
      {},
      // Authenticate with the service account token.
      { Authorization: `Bearer ${appToken}` },
    );
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to create message with error %s", err.message);
  }
}
// [END chat_post_message_with_app_credentials]

// [START chat_get_space]
/**
 * Gets information about a Chat space.
 * @param {string} spaceName The resource name of the space.
 */
function getSpace(spaceName) {
  try {
    const space = Chat.Spaces.get(spaceName);
    console.log("Space display name: %s", space.displayName);
    console.log("Space type: %s", space.spaceType);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to get space with error %s", err.message);
  }
}
// [END chat_get_space]

// [START chat_create_space]
/**
 * Creates a new Chat space.
 */
function createSpace() {
  try {
    const space = { displayName: "New Space", spaceType: "SPACE" };
    Chat.Spaces.create(space);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to create space with error %s", err.message);
  }
}
// [END chat_create_space]

// [START chat_list_memberships]
/**
 * Lists all the members of a Chat space.
 * @param {string} spaceName The resource name of the space.
 */
function listMemberships(spaceName) {
  let response;
  let pageToken = null;
  try {
    do {
      response = Chat.Spaces.Members.list(spaceName, {
        pageSize: 10,
        pageToken: pageToken,
      });
      if (!response.memberships || response.memberships.length === 0) {
        pageToken = response.nextPageToken;
        continue;
      }
      for (const membership of response.memberships) {
        console.log(
          "Member: %s, Role: %s",
          membership.member.displayName,
          membership.role,
        );
      }
      pageToken = response.nextPageToken;
    } while (pageToken);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", err.message);
  }
}
// [END chat_list_memberships]


================================================
FILE: advanced/classroom.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_classroom_list_courses]
/**
 * Lists 10 course names and IDs.
 */
function listCourses() {
  /**
   * @see https://developers.google.com/classroom/reference/rest/v1/courses/list
   */
  const optionalArgs = {
    pageSize: 10,
    // Use other query parameters here if needed.
  };
  try {
    const response = Classroom.Courses.list(optionalArgs);
    const courses = response.courses;
    if (!courses || courses.length === 0) {
      console.log("No courses found.");
      return;
    }
    // Print the course names and IDs of the available courses.
    for (const course in courses) {
      console.log("%s (%s)", courses[course].name, courses[course].id);
    }
  } catch (err) {
    // TODO (developer)- Handle Courses.list() exception from Classroom API
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_classroom_list_courses]


================================================
FILE: advanced/displayvideo.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_dv360_list_partners]
/**
 * Logs all of the partners available in the account.
 */
function listPartners() {
  // Retrieve the list of available partners
  try {
    const partners = DisplayVideo.Partners.list();

    if (partners.partners) {
      // Print out the ID and name of each
      for (let i = 0; i < partners.partners.length; i++) {
        const partner = partners.partners[i];
        console.log(
          'Found partner with ID %s and name "%s".',
          partner.partnerId,
          partner.displayName,
        );
      }
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_dv360_list_partners]

// [START apps_script_dv360_list_active_campaigns]
/**
 * Logs names and ID's of all active campaigns.
 * Note the use of paging tokens to retrieve the whole list.
 */
function listActiveCampaigns() {
  const advertiserId = "1234567"; // Replace with your advertiser ID.
  let result;
  let pageToken;
  try {
    do {
      result = DisplayVideo.Advertisers.Campaigns.list(advertiserId, {
        filter: 'entityStatus="ENTITY_STATUS_ACTIVE"',
        pageToken: pageToken,
      });
      if (result.campaigns) {
        for (let i = 0; i < result.campaigns.length; i++) {
          const campaign = result.campaigns[i];
          console.log(
            'Found campaign with ID %s and name "%s".',
            campaign.campaignId,
            campaign.displayName,
          );
        }
      }
      pageToken = result.nextPageToken;
    } while (pageToken);
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_dv360_list_active_campaigns]

// [START apps_script_dv360_update_line_item_name]
/**
 * Updates the display name of a line item
 */
function updateLineItemName() {
  const advertiserId = "1234567"; // Replace with your advertiser ID.
  const lineItemId = "123456789"; //Replace with your line item ID.
  const updateMask = "displayName";

  const lineItemDef = {
    displayName: "New Line Item Name (updated from Apps Script!)",
  };

  try {
    const lineItem = DisplayVideo.Advertisers.LineItems.patch(
      lineItemDef,
      advertiserId,
      lineItemId,
      { updateMask: updateMask },
    );
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_dv360_update_line_item_name]


================================================
FILE: advanced/docs.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// [START docs_create_document]
/**
 * Create a new document.
 * @see https://developers.google.com/docs/api/reference/rest/v1/documents/create
 * @return {string} documentId
 */
function createDocument() {
  // Create document with title
  const document = Docs.Documents.create({ title: "My New Document" });
  console.log(`Created document with ID: ${document.documentId}`);
  return document.documentId;
}
// [END docs_create_document]

// [START docs_find_and_replace_text]
/**
 * Performs "replace all".
 * @param {string} documentId The document to perform the replace text operations on.
 * @param {Object} findTextToReplacementMap A map from the "find text" to the "replace text".
 * @return {Object} replies
 * @see https://developers.google.com/docs/api/reference/rest/v1/documents/batchUpdate
 */
function findAndReplace(documentId, findTextToReplacementMap) {
  const requests = [];
  for (const findText in findTextToReplacementMap) {
    const replaceText = findTextToReplacementMap[findText];

    // Replace all text across all tabs.
    const replaceAllTextRequest = {
      replaceAllText: {
        containsText: {
          text: findText,
          matchCase: true,
        },
        replaceText: replaceText,
      },
    };

    // Replace all text across specific tabs.
    const _replaceAllTextWithTabsCriteria = {
      replaceAllText: {
        ...replaceAllTextRequest.replaceAllText,
        tabsCriteria: {
          tabIds: [TAB_ID_1, TAB_ID_2, TAB_ID_3],
        },
      },
    };
    requests.push(replaceAllTextRequest);
  }
  const response = Docs.Documents.batchUpdate(
    { requests: requests },
    documentId,
  );
  const replies = response.replies;
  for (const [index] of replies.entries()) {
    const numReplacements =
      replies[index].replaceAllText.occurrencesChanged || 0;
    console.log(
      "Request %s performed %s replacements.",
      index,
      numReplacements,
    );
  }
  return replies;
}
// [END docs_find_and_replace_text]

// [START docs_insert_and_style_text]
/**
 * Insert text at the beginning of the first tab in the document and then style
 * the inserted text.
 * @param {string} documentId The document the text is inserted into.
 * @param {string} text The text to insert into the document.
 * @return {Object} replies
 * @see https://developers.google.com/docs/api/reference/rest/v1/documents/batchUpdate
 */
function insertAndStyleText(documentId, text) {
  const requests = [
    {
      insertText: {
        location: {
          index: 1,
          // A tab can be specified using its ID. When omitted, the request is
          // applied to the first tab.
          // tabId: TAB_ID
        },
        text: text,
      },
    },
    {
      updateTextStyle: {
        range: {
          startIndex: 1,
          endIndex: text.length + 1,
        },
        textStyle: {
          fontSize: {
            magnitude: 12,
            unit: "PT",
          },
          weightedFontFamily: {
            fontFamily: "Calibri",
          },
        },
        fields: "weightedFontFamily, fontSize",
      },
    },
  ];
  const response = Docs.Documents.batchUpdate(
    { requests: requests },
    documentId,
  );
  return response.replies;
}
// [END docs_insert_and_style_text]

// [START docs_read_first_paragraph]
/**
 * Read the first paragraph of the first tab in a document.
 * @param {string} documentId The ID of the document to read.
 * @return {Object} paragraphText
 * @see https://developers.google.com/docs/api/reference/rest/v1/documents/get
 */
function readFirstParagraph(documentId) {
  // Get the document using document ID
  const document = Docs.Documents.get(documentId, {
    includeTabsContent: true,
  });
  const firstTab = document.tabs[0];
  const bodyElements = firstTab.documentTab.body.content;
  for (let i = 0; i < bodyElements.length; i++) {
    const structuralElement = bodyElements[i];
    // Print the first paragraph text present in document
    if (structuralElement.paragraph) {
      const paragraphElements = structuralElement.paragraph.elements;
      let paragraphText = "";

      for (let j = 0; j < paragraphElements.length; j++) {
        const paragraphElement = paragraphElements[j];
        if (paragraphElement.textRun !== null) {
          paragraphText += paragraphElement.textRun.content;
        }
      }
      console.log(paragraphText);
      return paragraphText;
    }
  }
}
// [END docs_read_first_paragraph]


================================================
FILE: advanced/doubleclick.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_doubleclick_list_user_profiles]
/**
 * Logs all of the user profiles available in the account.
 */
function listUserProfiles() {
  // Retrieve the list of available user profiles
  try {
    const profiles = DoubleClickCampaigns.UserProfiles.list();

    if (profiles.items) {
      // Print out the user ID and name of each
      for (let i = 0; i < profiles.items.length; i++) {
        const profile = profiles.items[i];
        console.log(
          'Found profile with ID %s and name "%s".',
          profile.profileId,
          profile.userName,
        );
      }
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_doubleclick_list_user_profiles]

// [START apps_script_doubleclick_list_active_campaigns]
/**
 * Logs names and ID's of all active campaigns.
 * Note the use of paging tokens to retrieve the whole list.
 */
function listActiveCampaigns() {
  const profileId = "1234567"; // Replace with your profile ID.
  const fields = "nextPageToken,campaigns(id,name)";
  let result;
  let pageToken;
  try {
    do {
      result = DoubleClickCampaigns.Campaigns.list(profileId, {
        archived: false,
        fields: fields,
        pageToken: pageToken,
      });
      if (result.campaigns) {
        for (let i = 0; i < result.campaigns.length; i++) {
          const campaign = result.campaigns[i];
          console.log(
            'Found campaign with ID %s and name "%s".',
            campaign.id,
            campaign.name,
          );
        }
      }
      pageToken = result.nextPageToken;
    } while (pageToken);
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_doubleclick_list_active_campaigns]

// [START apps_script_doubleclick_create_advertiser_and_campaign]
/**
 * Creates a new advertiser, and creates a new campaign with that advertiser.
 * The campaign is set to last for one month.
 */
function createAdvertiserAndCampaign() {
  const profileId = "1234567"; // Replace with your profile ID.

  const advertiser = {
    name: "Example Advertiser",
    status: "APPROVED",
  };

  try {
    const advertiserId = DoubleClickCampaigns.Advertisers.insert(
      advertiser,
      profileId,
    ).id;

    const landingPage = {
      advertiserId: advertiserId,
      archived: false,
      name: "Example landing page",
      url: "https://www.google.com",
    };
    const landingPageId = DoubleClickCampaigns.AdvertiserLandingPages.insert(
      landingPage,
      profileId,
    ).id;

    const campaignStart = new Date();
    // End campaign after 1 month.
    const campaignEnd = new Date();
    campaignEnd.setMonth(campaignEnd.getMonth() + 1);

    const campaign = {
      advertiserId: advertiserId,
      defaultLandingPageId: landingPageId,
      name: "Example campaign",
      startDate: Utilities.formatDate(campaignStart, "GMT", "yyyy-MM-dd"),
      endDate: Utilities.formatDate(campaignEnd, "GMT", "yyyy-MM-dd"),
    };
    DoubleClickCampaigns.Campaigns.insert(campaign, profileId);
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_doubleclick_create_advertiser_and_campaign]


================================================
FILE: advanced/doubleclickbidmanager.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_dcbm_list_queries]
/**
 * Logs all of the queries available in the account.
 */
function listQueries() {
  // Retrieve the list of available queries
  try {
    const queries = DoubleClickBidManager.Queries.list();

    if (queries.queries) {
      // Print out the ID and name of each
      for (let i = 0; i < queries.queries.length; i++) {
        const query = queries.queries[i];
        console.log(
          'Found query with ID %s and name "%s".',
          query.queryId,
          query.metadata.title,
        );
      }
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_dcbm_list_queries]

// [START apps_script_dcbm_create_and_run_query]
/**
 * Create and run a new DBM Query
 */
function createAndRunQuery() {
  let result;
  let execution;
  //We leave the default date range blank for the report run to
  //use the value defined during query creation
  const defaultDateRange = {};
  const partnerId = "1234567"; //Replace with your Partner ID
  const query = {
    metadata: {
      title: "Apps Script Example Report",
      dataRange: {
        range: "YEAR_TO_DATE",
      },
      format: "CSV",
    },
    params: {
      type: "STANDARD",
      groupBys: [
        "FILTER_PARTNER",
        "FILTER_PARTNER_NAME",
        "FILTER_ADVERTISER",
        "FILTER_ADVERTISER_NAME",
      ],
      filters: [{ type: "FILTER_PARTNER", value: partnerId }],
      metrics: ["METRIC_IMPRESSIONS"],
    },
    schedule: {
      frequency: "ONE_TIME",
    },
  };

  try {
    result = DoubleClickBidManager.Queries.create(query);
    if (result.queryId) {
      console.log(
        'Created query with ID %s and name "%s".',
        result.queryId,
        result.metadata.title,
      );
      execution = DoubleClickBidManager.Queries.run(
        defaultDateRange,
        result.queryId,
      );
      if (execution.key) {
        console.log(
          'Created query report with query ID %s and report ID "%s".',
          execution.key.queryId,
          execution.key.reportId,
        );
      }
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log(e);
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_dcbm_create_and_run_query]

// [START apps_script_dcbm_fetch_report]
/**
 * Fetches a report file
 */
function fetchReport() {
  const queryId = "1234567"; // Replace with your query ID.
  const orderBy = "key.reportId desc";

  try {
    const report = DoubleClickBidManager.Queries.Reports.list(queryId, {
      orderBy: orderBy,
    });
    if (report.reports) {
      const firstReport = report.reports[0];
      if (firstReport.metadata.status.state === "DONE") {
        const reportFile = UrlFetchApp.fetch(
          firstReport.metadata.googleCloudStoragePath,
        );
        console.log("Printing report content to log...");
        console.log(reportFile.getContentText());
      } else {
        console.log(
          "Report status is %s, and is not available for download",
          firstReport.metadata.status.state,
        );
      }
    }
  } catch (e) {
    // TODO (Developer) - Handle exception
    console.log(e);
    console.log("Failed with error: %s", e.error);
  }
}
// [END apps_script_dcbm_fetch_report]


================================================
FILE: advanced/drive.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// [START drive_upload_file]
/**
 * Uploads a new file to the user's Drive.
 */
function uploadFile() {
  try {
    // Makes a request to fetch a URL.
    const image = UrlFetchApp.fetch("http://goo.gl/nd7zjB").getBlob();
    let file = {
      name: "google_logo.png",
      mimeType: "image/png",
    };
    // Create a file in the user's Drive.
    file = Drive.Files.create(file, image, { fields: "id,size" });
    console.log("ID: %s, File size (bytes): %s", file.id, file.size);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to upload file with error %s", err.message);
  }
}
// [END drive_upload_file]

// [START drive_list_root_folders]
/**
 * Lists the top-level folders in the user's Drive.
 */
function listRootFolders() {
  const query =
    '"root" in parents and trashed = false and ' +
    'mimeType = "application/vnd.google-apps.folder"';
  let folders;
  let pageToken = null;
  do {
    try {
      folders = Drive.Files.list({
        q: query,
        pageSize: 100,
        pageToken: pageToken,
      });
      if (!folders.files || folders.files.length === 0) {
        console.log("All folders found.");
        return;
      }
      for (let i = 0; i < folders.files.length; i++) {
        const folder = folders.files[i];
        console.log("%s (ID: %s)", folder.name, folder.id);
      }
      pageToken = folders.nextPageToken;
    } catch (err) {
      // TODO (developer) - Handle exception
      console.log("Failed with error %s", err.message);
    }
  } while (pageToken);
}
// [END drive_list_root_folders]

// [START drive_add_custom_property]
/**
 * Adds a custom app property to a file. Unlike Apps Script's DocumentProperties,
 * Drive's custom file properties can be accessed outside of Apps Script and
 * by other applications; however, appProperties are only visible to the script.
 * @param {string} fileId The ID of the file to add the app property to.
 */
function addAppProperty(fileId) {
  try {
    let file = {
      appProperties: {
        department: "Sales",
      },
    };
    // Updates a file to add an app property.
    file = Drive.Files.update(file, fileId, null, {
      fields: "id,appProperties",
    });
    console.log(
      "ID: %s, appProperties: %s",
      file.id,
      JSON.stringify(file.appProperties, null, 2),
    );
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", err.message);
  }
}
// [END drive_add_custom_property]

// [START drive_list_revisions]
/**
 * Lists the revisions of a given file.
 * @param {string} fileId The ID of the file to list revisions for.
 */
function listRevisions(fileId) {
  let revisions;
  let pageToken = null;
  do {
    try {
      revisions = Drive.Revisions.list(fileId, {
        fields: "revisions(modifiedTime,size),nextPageToken",
      });
      if (!revisions.revisions || revisions.revisions.length === 0) {
        console.log("All revisions found.");
        return;
      }
      for (let i = 0; i < revisions.revisions.length; i++) {
        const revision = revisions.revisions[i];
        const date = new Date(revision.modifiedTime);
        console.log(
          "Date: %s, File size (bytes): %s",
          date.toLocaleString(),
          revision.size,
        );
      }
      pageToken = revisions.nextPageToken;
    } catch (err) {
      // TODO (developer) - Handle exception
      console.log("Failed with error %s", err.message);
    }
  } while (pageToken);
}

// [END drive_list_revisions]


================================================
FILE: advanced/driveActivity.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_drive_activity_get_users_activity]
/**
 * Gets a file's activity and logs the list of
 * unique users that performed the activity.
 */
function getUsersActivity() {
  const fileId = "YOUR_FILE_ID_HERE";

  let pageToken;
  const users = {};
  do {
    const result = AppsActivity.Activities.list({
      "drive.fileId": fileId,
      source: "drive.google.com",
      pageToken: pageToken,
    });
    const activities = result.activities;
    for (let i = 0; i < activities.length; i++) {
      const events = activities[i].singleEvents;
      for (let j = 0; j < events.length; j++) {
        const event = events[j];
        users[event.user.name] = true;
      }
    }
    pageToken = result.nextPageToken;
  } while (pageToken);
  console.log(Object.keys(users));
}
// [END apps_script_drive_activity_get_users_activity]


================================================
FILE: advanced/driveLabels.gs
================================================
/**
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// [START apps_script_drive_labels_list_labels]
/**
 * List labels available to the user.
 */
function listLabels() {
  let pageToken = null;
  let labels = [];
  do {
    try {
      const response = DriveLabels.Labels.list({
        publishedOnly: true,
        pageToken: pageToken,
      });
      pageToken = response.nextPageToken;
      labels = labels.concat(response.labels);
    } catch (err) {
      // TODO (developer) - Handle exception
      console.log("Failed to list labels with error %s", err.message);
    }
  } while (pageToken != null);

  console.log("Found %d labels", labels.length);
}
// [END apps_script_drive_labels_list_labels]

// [START apps_script_drive_labels_get_label]
/**
 * Get a label by name.
 * @param {string} labelName The label name.
 */
function getLabel(labelName) {
  try {
    const label = DriveLabels.Labels.get(labelName, {
      view: "LABEL_VIEW_FULL",
    });
    const title = label.properties.title;
    const fieldsLength = label.fields.length;
    console.log(
      `Fetched label with title: '${title}' and ${fieldsLength} fields.`,
    );
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to get label with error %s", err.message);
  }
}
// [END apps_script_drive_labels_get_label]

// [START apps_script_drive_labels_list_labels_on_drive_item]
/**
 * List Labels on a Drive Item
 * Fetches a Drive Item and prints all applied values along with their to their
 * human-readable names.
 *
 * @param {string} fileId The Drive File ID
 */
function listLabelsOnDriveItem(fileId) {
  try {
    const appliedLabels = Drive.Files.listLabels(fileId);

    console.log(
      "%d label(s) are applied to this file",
      appliedLabels.labels.length,
    );

    for (const appliedLabel of appliedLabels.labels) {
      // Resource name of the label at the applied revision.
      const labelName = `labels/${appliedLabel.id}@${appliedLabel.revisionId}`;

      console.log("Fetching Label: %s", labelName);
      const label = DriveLabels.Labels.get(labelName, {
        view: "LABEL_VIEW_FULL",
      });

      console.log("Label Title: %s", label.properties.title);

      for (const fieldId of Object.keys(appliedLabel.fields)) {
        const fieldValue = appliedLabel.fields[fieldId];
        const field = label.fields.find((f) => f.id === fieldId);

        console.log(
          `Field ID: ${field.id}, Display Name: ${field.properties.displayName}`,
        );
        switch (fieldValue.valueType) {
          case "text":
            console.log("Text: %s", fieldValue.text[0]);
            break;
          case "integer":
            console.log("Integer: %d", fieldValue.integer[0]);
            break;
          case "dateString":
            console.log("Date: %s", fieldValue.dateString[0]);
            break;
          case "user": {
            const user = fieldValue.user
              .map((user) => {
                return `${user.emailAddress}: ${user.displayName}`;
              })
              .join(", ");
            console.log(`User: ${user}`);
            break;
          }
          case "selection": {
            const choices = fieldValue.selection.map((choiceId) => {
              return field.selectionOptions.choices.find(
                (choice) => choice.id === choiceId,
              );
            });
            const selection = choices
              .map((choice) => {
                return `${choice.id}: ${choice.properties.displayName}`;
              })
              .join(", ");
            console.log(`Selection: ${selection}`);
            break;
          }
          default:
            console.log("Unknown: %s", fieldValue.valueType);
            console.log(fieldValue.value);
        }
      }
    }
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", err.message);
  }
}
// [END apps_script_drive_labels_list_labels_on_drive_item]


================================================
FILE: advanced/events.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// [START events_create_subscription]
/**
 * Creates a subscription to receive events about a Google Workspace resource.
 * For a list of supported resources and event types, see the
 * [Google Workspace Events API Overview](https://developers.google.com/workspace/events#supported-events).
 * For additional information, see the
 * [subscriptions.create](https://developers.google.com/workspace/events/reference/rest/v1/subscriptions/create)
 * method reference.
 * @param {!string} targetResource The full resource name of the Google Workspace resource to subscribe to.
 * @param {!string|!Array<string>} eventTypes The types of events to receive about the resource.
 * @param {!string} pubsubTopic The resource name of the Pub/Sub topic that receives events from the subscription.
 */
function createSubscription(targetResource, eventTypes, pubsubTopic) {
  try {
    const operation = WorkspaceEvents.Subscriptions.create({
      targetResource: targetResource,
      eventTypes: eventTypes,
      notificationEndpoint: {
        pubsubTopic: pubsubTopic,
      },
    });
    console.log(operation);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to create subscription with error %s", err.message);
  }
}
// [END events_create_subscription]

// [START events_list_subscriptions]
/**
 * Lists subscriptions created by the calling app filtered by one or more event types and optionally by a target resource.
 * For additional information, see the
 * [subscriptions.list](https://developers.google.com/workspace/events/reference/rest/v1/subscriptions/list)
 * method reference.
 * @param {!string} filter The query filter.
 */
function listSubscriptions(filter) {
  try {
    const response = WorkspaceEvents.Subscriptions.list({ filter });
    console.log(response);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to list subscriptions with error %s", err.message);
  }
}
// [END events_list_subscriptions]

// [START events_get_subscription]
/**
 * Gets details about a subscription.
 * For additional information, see the
 * [subscriptions.get](https://developers.google.com/workspace/events/reference/rest/v1/subscriptions/get)
 * method reference.
 * @param {!string} name The resource name of the subscription.
 */
function getSubscription(name) {
  try {
    const subscription = WorkspaceEvents.Subscriptions.get(name);
    console.log(subscription);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to get subscription with error %s", err.message);
  }
}
// [END events_get_subscription]

// [START events_patch_subscription]
/**
 * Updates an existing subscription.
 * This can be used to renew a subscription that is about to expire.
 * For additional information, see the
 * [subscriptions.patch](https://developers.google.com/workspace/events/reference/rest/v1/subscriptions/patch)
 * method reference.
 * @param {!string} name The resource name of the subscription.
 */
function patchSubscription(name) {
  try {
    const operation = WorkspaceEvents.Subscriptions.patch(
      {
        // Setting the TTL to 0 seconds extends the subscription to its maximum expiration time.
        ttl: "0s",
      },
      name,
    );
    console.log(operation);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to update subscription with error %s", err.message);
  }
}
// [END events_patch_subscription]

// [START events_reactivate_subscription]
/**
 * Reactivates a suspended subscription.
 * Before reactivating, you must resolve any errors with the subscription.
 * For additional information, see the
 * [subscriptions.reactivate](https://developers.google.com/workspace/events/reference/rest/v1/subscriptions/reactivate)
 * method reference.
 * @param {!string} name The resource name of the subscription.
 */
function reactivateSubscription(name) {
  try {
    const operation = WorkspaceEvents.Subscriptions.reactivate({}, name);
    console.log(operation);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to reactivate subscription with error %s", err.message);
  }
}
// [END events_reactivate_subscription]

// [START events_delete_subscription]
/**
 * Deletes a subscription.
 * For additional information, see the
 * [subscriptions.delete](https://developers.google.com/workspace/events/reference/rest/v1/subscriptions/delete)
 * method reference.
 * @param {!string} name The resource name of the subscription.
 */
function deleteSubscription(name) {
  try {
    const operation = WorkspaceEvents.Subscriptions.remove(name);
    console.log(operation);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to delete subscription with error %s", err.message);
  }
}
// [END events_delete_subscription]

// [START events_get_operation]
/**
 * Gets details about an operation returned by one of the methods on the subscription
 * resource of the Google Workspace Events API.
 * For additional information, see the
 * [operations.get](https://developers.google.com/workspace/events/reference/rest/v1/operations/get)
 * method reference.
 * @param {!string} name The resource name of the operation.
 */
function getOperation(name) {
  try {
    const operation = WorkspaceEvents.Operations.get(name);
    console.log(operation);
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to get operation with error %s", err.message);
  }
}
// [END events_get_operation]


================================================
FILE: advanced/gmail.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START gmail_label]
/**
 * Lists the user's labels, including name, type,
 * ID and visibility information.
 */
function listLabelInfo() {
  try {
    const response = Gmail.Users.Labels.list("me");
    for (let i = 0; i < response.labels.length; i++) {
      const label = response.labels[i];
      console.log(JSON.stringify(label));
    }
  } catch (err) {
    console.log(err);
  }
}
// [END gmail_label]

// [START gmail_inbox_snippets]
/**
 * Lists, for each thread in the user's Inbox, a
 * snippet associated with that thread.
 */
function listInboxSnippets() {
  try {
    let pageToken;
    do {
      const threadList = Gmail.Users.Threads.list("me", {
        q: "label:inbox",
        pageToken: pageToken,
      });
      if (threadList.threads && threadList.threads.length > 0) {
        for (const thread of threadList.threads) {
          console.log(`Snippet: ${thread.snippet}`);
        }
      }
      pageToken = threadList.nextPageToken;
    } while (pageToken);
  } catch (err) {
    console.log(err);
  }
}
// [END gmail_inbox_snippets]

// [START gmail_history]
/**
 * Gets a history record ID associated with the most
 * recently sent message, then logs all the message IDs
 * that have changed since that message was sent.
 */
function logRecentHistory() {
  try {
    // Get the history ID associated with the most recent
    // sent message.
    const sent = Gmail.Users.Threads.list("me", {
      q: "label:sent",
      maxResults: 1,
    });
    if (!sent.threads || !sent.threads[0]) {
      console.log("No sent threads found.");
      return;
    }
    const historyId = sent.threads[0].historyId;

    // Log the ID of each message changed since the most
    // recent message was sent.
    let pageToken;
    const changed = [];
    do {
      const recordList = Gmail.Users.History.list("me", {
        startHistoryId: historyId,
        pageToken: pageToken,
      });
      const history = recordList.history;
      if (history && history.length > 0) {
        for (const record of history) {
          for (const message of record.messages) {
            if (changed.indexOf(message.id) === -1) {
              changed.push(message.id);
            }
          }
        }
      }
      pageToken = recordList.nextPageToken;
    } while (pageToken);

    for (const id of changed) {
      console.log("Message Changed: %s", id);
    }
  } catch (err) {
    console.log(err);
  }
}
// [END gmail_history]

// [START gmail_raw]
/**
 * Logs the raw message content for the most recent message in gmail.
 */
function getRawMessage() {
  try {
    const messageId = Gmail.Users.Messages.list("me").messages[0].id;
    console.log(messageId);
    const message = Gmail.Users.Messages.get("me", messageId, {
      format: "raw",
    });

    // Get raw content as base64url encoded string.
    const encodedMessage = Utilities.base64Encode(message.raw);
    console.log(encodedMessage);
  } catch (err) {
    console.log(err);
  }
}
// [END gmail_raw]

// [START gmail_list_messages]
/**
 * Lists unread messages in the user's inbox using the advanced Gmail service.
 */
function listMessages() {
  // The special value 'me' indicates the authenticated user.
  const userId = "me";

  // Define optional parameters for the request.
  const options = {
    maxResults: 10, // Limit the number of messages returned.
    q: "is:unread", // Search for unread messages.
  };

  try {
    // Call the Gmail.Users.Messages.list method.
    const response = Gmail.Users.Messages.list(userId, options);
    const messages = response.messages;
    console.log("Unread Messages:");

    for (const message of messages) {
      console.log(`- Message ID: ${message.id}`);
    }
  } catch (err) {
    // Log any errors to the Apps Script execution log.
    console.log(`Failed with error: ${err.message}`);
  }
}
// [END gmail_list_messages]


================================================
FILE: advanced/iot.gs
================================================
/**
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_iot_list_registries]
/**
 * Lists the registries for the configured project and region.
 */
function listRegistries() {
  const projectId = "your-project-id";
  const cloudRegion = "us-central1";
  const parent = `projects/${projectId}/locations/${cloudRegion}`;

  const response = CloudIoT.Projects.Locations.Registries.list(parent);
  console.log(response);
  if (response.deviceRegistries) {
    for (const registry of response.deviceRegistries) {
      console.log(registry.id);
    }
  }
}
// [END apps_script_iot_list_registries]

// [START apps_script_iot_create_registry]
/**
 * Creates a registry.
 */
function createRegistry() {
  const cloudRegion = "us-central1";
  const name = "your-registry-name";
  const projectId = "your-project-id";
  const topic = "your-pubsub-topic";

  const pubsubTopic = `projects/${projectId}/topics/${topic}`;

  const registry = {
    eventNotificationConfigs: [
      {
        // From - https://console.cloud.google.com/cloudpubsub
        pubsubTopicName: pubsubTopic,
      },
    ],
    id: name,
  };
  const parent = `projects/${projectId}/locations/${cloudRegion}`;

  const response = CloudIoT.Projects.Locations.Registries.create(
    registry,
    parent,
  );
  console.log(`Created registry: ${response.id}`);
}
// [END apps_script_iot_create_registry]

// [START apps_script_iot_get_registry]
/**
 * Describes a registry.
 */
function getRegistry() {
  const cloudRegion = "us-central1";
  const name = "your-registry-name";
  const projectId = "your-project-id";

  const parent = `projects/${projectId}/locations/${cloudRegion}`;
  const registryName = `${parent}/registries/${name}`;

  const response = CloudIoT.Projects.Locations.Registries.get(registryName);
  console.log(`Retrieved registry: ${response.id}`);
}
// [END apps_script_iot_get_registry]

// [START apps_script_iot_delete_registry]
/**
 * Deletes a registry.
 */
function deleteRegistry() {
  const cloudRegion = "us-central1";
  const name = "your-registry-name";
  const projectId = "your-project-id";

  const parent = `projects/${projectId}/locations/${cloudRegion}`;
  const registryName = `${parent}/registries/${name}`;

  const response = CloudIoT.Projects.Locations.Registries.remove(registryName);
  // Successfully removed registry if exception was not thrown.
  console.log(`Deleted registry: ${name}`);
}
// [END apps_script_iot_delete_registry]

// [START apps_script_iot_list_devices]
/**
 * Lists the devices in the given registry.
 */
function listDevicesForRegistry() {
  const cloudRegion = "us-central1";
  const name = "your-registry-name";
  const projectId = "your-project-id";

  const parent = `projects/${projectId}/locations/${cloudRegion}`;
  const registryName = `${parent}/registries/${name}`;

  const response =
    CloudIoT.Projects.Locations.Registries.Devices.list(registryName);

  console.log("Registry contains the following devices: ");
  if (response.devices) {
    for (const device of response.devices) {
      console.log(`\t${device.id}`);
    }
  }
}
// [END apps_script_iot_list_devices]

// [START apps_script_iot_create_unauth_device]
/**
 * Creates a device without credentials.
 */
function createDevice() {
  const cloudRegion = "us-central1";
  const name = "your-device-name";
  const projectId = "your-project-id";
  const registry = "your-registry-name";

  console.log(`Creating device: ${name} in Registry: ${registry}`);
  const parent = `projects/${projectId}/locations/${cloudRegion}/registries/${registry}`;

  const device = {
    id: name,
    gatewayConfig: {
      gatewayType: "NON_GATEWAY",
      gatewayAuthMethod: "ASSOCIATION_ONLY",
    },
  };

  const response = CloudIoT.Projects.Locations.Registries.Devices.create(
    device,
    parent,
  );
  console.log(`Created device:${response.name}`);
}
// [END apps_script_iot_create_unauth_device]

// [START apps_script_iot_create_rsa_device]
/**
 * Creates a device with RSA credentials.
 */
function createRsaDevice() {
  // Create the RSA public/private keypair with the following OpenSSL command:
  //    openssl req -x509 -newkey rsa:2048 -days 3650 -keyout rsa_private.pem \
  //      -nodes -out rsa_cert.pem -subj "/CN=unused"
  //
  // **NOTE** Be sure to insert the newline charaters in the string constant.
  const cert =
    "-----BEGIN CERTIFICATE-----\n" +
    "your-PUBLIC-certificate-b64-bytes\n" +
    "...\n" +
    "more-PUBLIC-certificate-b64-bytes==\n" +
    "-----END CERTIFICATE-----\n";

  const cloudRegion = "us-central1";
  const name = "your-device-name";
  const projectId = "your-project-id";
  const registry = "your-registry-name";

  const parent = `projects/${projectId}/locations/${cloudRegion}/registries/${registry}`;
  const device = {
    id: name,
    gatewayConfig: {
      gatewayType: "NON_GATEWAY",
      gatewayAuthMethod: "ASSOCIATION_ONLY",
    },
    credentials: [
      {
        publicKey: {
          format: "RSA_X509_PEM",
          key: cert,
        },
      },
    ],
  };

  const response = CloudIoT.Projects.Locations.Registries.Devices.create(
    device,
    parent,
  );
  console.log(`Created device:${response.name}`);
}
// [END apps_script_iot_create_rsa_device]

// [START apps_script_iot_delete_device]
/**
 * Deletes a device from the given registry.
 */
function deleteDevice() {
  const cloudRegion = "us-central1";
  const name = "your-device-name";
  const projectId = "your-project-id";
  const registry = "your-registry-name";

  const parent = `projects/${projectId}/locations/${cloudRegion}/registries/${registry}`;
  const deviceName = `${parent}/devices/${name}`;

  const response =
    CloudIoT.Projects.Locations.Registries.Devices.remove(deviceName);
  // If no exception thrown, device was successfully removed
  console.log(`Successfully deleted device: ${deviceName}`);
}
// [END apps_script_iot_delete_device]


================================================
FILE: advanced/people.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START people_get_connections]
/**
 * Gets a list of people in the user's contacts.
 * @see https://developers.google.com/people/api/rest/v1/people.connections/list
 */
function getConnections() {
  try {
    // Get the list of connections/contacts of user's profile
    const people = People.People.Connections.list("people/me", {
      personFields: "names,emailAddresses",
    });
    // Print the connections/contacts
    console.log("Connections: %s", JSON.stringify(people, null, 2));
  } catch (err) {
    // TODO (developers) - Handle exception here
    console.log("Failed to get the connection with an error %s", err.message);
  }
}
// [END people_get_connections]

// [START people_get_self_profile]
/**
 * Gets the own user's profile.
 * @see https://developers.google.com/people/api/rest/v1/people/getBatchGet
 */
function getSelf() {
  try {
    // Get own user's profile using People.getBatchGet() method
    const people = People.People.getBatchGet({
      resourceNames: ["people/me"],
      personFields: "names,emailAddresses",
      // Use other query parameter here if needed
    });
    console.log("Myself: %s", JSON.stringify(people, null, 2));
  } catch (err) {
    // TODO (developer) -Handle exception
    console.log("Failed to get own profile with an error %s", err.message);
  }
}
// [END people_get_self_profile]

// [START people_get_account]
/**
 * Gets the person information for any Google Account.
 * @param {string} accountId The account ID.
 * @see https://developers.google.com/people/api/rest/v1/people/get
 */
function getAccount(accountId) {
  try {
    // Get the Account details using account ID.
    const people = People.People.get(`people/${accountId}`, {
      personFields: "names,emailAddresses",
    });
    // Print the profile details of Account.
    console.log("Public Profile: %s", JSON.stringify(people, null, 2));
  } catch (err) {
    // TODO (developer) - Handle exception
    console.log("Failed to get account with an error %s", err.message);
  }
}
// [END people_get_account]

// [START people_get_group]

/**
 * Gets a contact group with the given name
 * @param {string} name The group name.
 * @see https://developers.google.com/people/api/rest/v1/contactGroups/list
 */
function getContactGroup(name) {
  try {
    const people = People.ContactGroups.list();
    // Finds the contact group for the person where the name matches.
    const group = people.contactGroups.find((group) => group.name === name);
    // Prints the contact group
    console.log("Group: %s", JSON.stringify(group, null, 2));
  } catch (err) {
    // TODO (developers) - Handle exception
    console.log(
      "Failed to get the contact group with an error %s",
      err.message,
    );
  }
}

// [END people_get_group]

// [START people_get_contact_by_email]

/**
 * Gets a contact by the email address.
 * @param {string} email The email address.
 * @see https://developers.google.com/people/api/rest/v1/people.connections/list
 */
function getContactByEmail(email) {
  try {
    // Gets the person with that email address by iterating over all contacts.
    const people = People.People.Connections.list("people/me", {
      personFields: "names,emailAddresses",
    });
    const contact = people.connections.find((connection) => {
      return connection.emailAddresses.some(
        (emailAddress) => emailAddress.value === email,
      );
    });
    // Prints the contact.
    console.log("Contact: %s", JSON.stringify(contact, null, 2));
  } catch (err) {
    // TODO (developers) - Handle exception
    console.log("Failed to get the connection with an error %s", err.message);
  }
}

// [END people_get_contact_by_email]

// [START people_get_full_name]
/**
 * Gets the full name (given name and last name) of the contact as a string.
 * @see https://developers.google.com/people/api/rest/v1/people/get
 */
function getFullName() {
  try {
    // Gets the person by specifying resource name/account ID
    // in the first parameter of People.People.get.
    // This example gets the person for the user running the script.
    const people = People.People.get("people/me", { personFields: "names" });
    // Prints the full name (given name + family name)
    console.log(`${people.names[0].givenName} ${people.names[0].familyName}`);
  } catch (err) {
    // TODO (developers) - Handle exception
    console.log("Failed to get the connection with an error %s", err.message);
  }
}

// [END people_get_full_name]

// [START people_get_phone_numbers]
/**
 * Gets all the phone numbers for this contact.
 * @see https://developers.google.com/people/api/rest/v1/people/get
 */
function getPhoneNumbers() {
  try {
    // Gets the person by specifying resource name/account ID
    // in the first parameter of People.People.get.
    // This example gets the person for the user running the script.
    const people = People.People.get("people/me", {
      personFields: "phoneNumbers",
    });
    // Prints the phone numbers.
    console.log(people.phoneNumbers);
  } catch (err) {
    // TODO (developers) - Handle exception
    console.log("Failed to get the connection with an error %s", err.message);
  }
}

// [END people_get_phone_numbers]

// [START people_get_single_phone_number]
/**
 * Gets a phone number by type, such as work or home.
 * @see https://developers.google.com/people/api/rest/v1/people/get
 */
function getPhone() {
  try {
    // Gets the person by specifying resource name/account ID
    // in the first parameter of People.People.get.
    // This example gets the person for the user running the script.
    const people = People.People.get("people/me", {
      personFields: "phoneNumbers",
    });
    // Gets phone number by type, such as home or work.
    const phoneNumber = people.phoneNumbers.find(
      (phone) => phone.type === "home",
    ).value;
    // Prints the phone numbers.
    console.log(phoneNumber);
  } catch (err) {
    // TODO (developers) - Handle exception
    console.log("Failed to get the connection with an error %s", err.message);
  }
}

// [END people_get_single_phone_number]


================================================
FILE: advanced/sheets.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// TODO (developer)- Replace the spreadsheet ID and sheet ID with yours values.
const yourspreadsheetId = "1YdrrmXSjpi4Tz-UuQ0eUKtdzQuvpzRLMoPEz3niTTVU";
const yourpivotSourceDataSheetId = 635809130;
const yourdestinationSheetId = 83410180;
// [START sheets_read_range]
/**
 * Read a range (A1:D5) of data values. Logs the values.
 * @param {string} spreadsheetId The spreadsheet ID to read from.
 * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get
 */
function readRange(spreadsheetId = yourspreadsheetId) {
  try {
    const response = Sheets.Spreadsheets.Values.get(
      spreadsheetId,
      "Sheet1!A1:D5",
    );
    if (response.values) {
      console.log(response.values);
      return;
    }
    console.log("Failed to get range of values from spreadsheet");
  } catch (e) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", e.message);
  }
}
// [END sheets_read_range]

// [START sheets_write_range]
/**
 * Write to multiple, disjoint data ranges.
 * @param {string} spreadsheetId The spreadsheet ID to write to.
 * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate
 */
function writeToMultipleRanges(spreadsheetId = yourspreadsheetId) {
  // Specify some values to write to the sheet.
  const columnAValues = [["Item", "Wheel", "Door", "Engine"]];
  const rowValues = [
    ["Cost", "Stocked", "Ship Date"],
    ["$20.50", "4", "3/1/2016"],
  ];

  const request = {
    valueInputOption: "USER_ENTERED",
    data: [
      {
        range: "Sheet1!A1:A4",
        majorDimension: "COLUMNS",
        values: columnAValues,
      },
      {
        range: "Sheet1!B1:D2",
        majorDimension: "ROWS",
        values: rowValues,
      },
    ],
  };
  try {
    const response = Sheets.Spreadsheets.Values.batchUpdate(
      request,
      spreadsheetId,
    );
    if (response) {
      console.log(response);
      return;
    }
    console.log("response null");
  } catch (e) {
    // TODO (developer) - Handle  exception
    console.log("Failed with error %s", e.message);
  }
}
// [END sheets_write_range]

// [START sheets_add_new_sheet]
/**
 * Add a new sheet with some properties.
 * @param {string} spreadsheetId The spreadsheet ID.
 * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate
 */
function addSheet(spreadsheetId = yourspreadsheetId) {
  const requests = [
    {
      addSheet: {
        properties: {
          title: "Deposits",
          gridProperties: {
            rowCount: 20,
            columnCount: 12,
          },
          tabColor: {
            red: 1.0,
            green: 0.3,
            blue: 0.4,
          },
        },
      },
    },
  ];
  try {
    const response = Sheets.Spreadsheets.batchUpdate(
      { requests: requests },
      spreadsheetId,
    );
    console.log(
      `Created sheet with ID: ${response.replies[0].addSheet.properties.sheetId}`,
    );
  } catch (e) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", e.message);
  }
}
// [END sheets_add_new_sheet]

// [START sheets_add_pivot_table]
/**
 * Add a pivot table.
 * @param {string} spreadsheetId The spreadsheet ID to add the pivot table to.
 * @param {string} pivotSourceDataSheetId The sheet ID to get the data from.
 * @param {string} destinationSheetId The sheet ID to add the pivot table to.
 * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate
 */
function addPivotTable(
  spreadsheetId = yourspreadsheetId,
  pivotSourceDataSheetId = yourpivotSourceDataSheetId,
  destinationSheetId = yourdestinationSheetId,
) {
  const requests = [
    {
      updateCells: {
        rows: {
          values: [
            {
              pivotTable: {
                source: {
                  sheetId: pivotSourceDataSheetId,
                  startRowIndex: 0,
                  startColumnIndex: 0,
                  endRowIndex: 20,
                  endColumnIndex: 7,
                },
                rows: [
                  {
                    sourceColumnOffset: 0,
                    showTotals: true,
                    sortOrder: "ASCENDING",
                    valueBucket: {
                      buckets: [
                        {
                          stringValue: "West",
                        },
                      ],
                    },
                  },
                  {
                    sourceColumnOffset: 1,
                    showTotals: true,
                    sortOrder: "DESCENDING",
                    valueBucket: {},
                  },
                ],
                columns: [
                  {
                    sourceColumnOffset: 4,
                    sortOrder: "ASCENDING",
                    showTotals: true,
                    valueBucket: {},
                  },
                ],
                values: [
                  {
                    summarizeFunction: "SUM",
                    sourceColumnOffset: 3,
                  },
                ],
                valueLayout: "HORIZONTAL",
              },
            },
          ],
        },
        start: {
          sheetId: destinationSheetId,
          rowIndex: 49,
          columnIndex: 0,
        },
        fields: "pivotTable",
      },
    },
  ];
  try {
    const response = Sheets.Spreadsheets.batchUpdate(
      { requests: requests },
      spreadsheetId,
    );
    // The Pivot table will appear anchored to cell A50 of the destination sheet.
  } catch (e) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", e.message);
  }
}
// [END sheets_add_pivot_table]


================================================
FILE: advanced/shoppingContent.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// [START apps_script_shopping_product_insert]
/**
 * Inserts a product into the products list. Logs the API response.
 */
function productInsert() {
  const merchantId = 123456; // Replace this with your Merchant Center ID.
  // Create a product resource and insert it
  const productResource = {
    offerId: "book123",
    title: "A Tale of Two Cities",
    description: "A classic novel about the French Revolution",
    link: "http://my-book-shop.com/tale-of-two-cities.html",
    imageLink: "http://my-book-shop.com/tale-of-two-cities.jpg",
    contentLanguage: "en",
    targetCountry: "US",
    channel: "online",
    availability: "in stock",
    condition: "new",
    googleProductCategory: "Media > Books",
    productType: "Media > Books",
    gtin: "9780007350896",
    price: {
      value: "2.50",
      currency: "USD",
    },
    shipping: [
      {
        country: "US",
        service: "Standard shipping",
        price: {
          value: "0.99",
          currency: "USD",
        },
      },
    ],
    shippingWeight: {
      value: "2",
      unit: "pounds",
    },
  };

  try {
    response = ShoppingContent.Products.insert(productResource, merchantId);
    // RESTful insert returns the JSON object as a response.
    console.log(response);
  } catch (e) {
    // TODO (Developer) - Handle exceptions
    console.log("Failed with error: $s", e.error);
  }
}
// [END apps_script_shopping_product_insert]

// [START apps_script_shopping_product_list]
/**
 * Lists the products for a given merchant.
 */
function productList() {
  const merchantId = 123456; // Replace this with your Merchant Center ID.
  let pageToken;
  let pageNum = 1;
  const maxResults = 10;
  try {
    do {
      const products = ShoppingContent.Products.list(merchantId, {
        pageToken: pageToken,
        maxResults: maxResults,
      });
      console.log(`Page ${pageNum}`);
      if (products.resources) {
        for (let i = 0; i < products.resources.length; i++) {
          console.log(`Item [${i}] ==> ${products.resources[i]}`);
        }
      } else {
        console.log(`No more products in account ${merchantId}`);
      }
      pageToken = products.nextPageToken;
      pageNum++;
    } while (pageToken);
  } catch (e) {
    // TODO (Developer) - Handle exceptions
    console.log("Failed with error: $s", e.error);
  }
}
// [END apps_script_shopping_product_list]

// [START apps_script_shopping_product_batch_insert]
/**
 * Batch updates products. Logs the response.
 * @param  {object} productResource1 The first product resource.
 * @param  {object} productResource2 The second product resource.
 * @param  {object} productResource3 The third product resource.
 */
function custombatch(productResource1, productResource2, productResource3) {
  const merchantId = 123456; // Replace this with your Merchant Center ID.
  custombatchResource = {
    entries: [
      {
        batchId: 1,
        merchantId: merchantId,
        method: "insert",
        productId: "book124",
        product: productResource1,
      },
      {
        batchId: 2,
        merchantId: merchantId,
        method: "insert",
        productId: "book125",
        product: productResource2,
      },
      {
        batchId: 3,
        merchantId: merchantId,
        method: "insert",
        productId: "book126",
        product: productResource3,
      },
    ],
  };
  try {
    const response = ShoppingContent.Products.custombatch(custombatchResource);
    console.log(response);
  } catch (e) {
    // TODO (Developer) - Handle exceptions
    console.log("Failed with error: $s", e.error);
  }
}
// [END apps_script_shopping_product_batch_insert]

// [START apps_script_shopping_account_info]
/**
 * Updates content account tax information.
 * Logs the API response.
 */
function updateAccountTax() {
  // Replace this with your Merchant Center ID.
  const merchantId = 123456;

  // Replace this with the account that you are updating taxes for.
  const accountId = 123456;

  try {
    const accounttax = ShoppingContent.Accounttax.get(merchantId, accountId);
    console.log(accounttax);

    const taxInfo = {
      accountId: accountId,
      rules: [
        {
          useGlobalRate: true,
          locationId: 21135,
          shippingTaxed: true,
          country: "US",
        },
        {
          ratePercent: 3,
          locationId: 21136,
          country: "US",
        },
        {
          ratePercent: 2,
          locationId: 21160,
          shippingTaxed: true,
          country: "US",
        },
      ],
    };

    console.log(
      ShoppingContent.Accounttax.update(taxInfo, merchantId, accountId),
    );
  } catch (e) {
    // TODO (Developer) - Handle exceptions
    console.log("Failed with error: $s", e.error);
  }
}
// [END apps_script_shopping_account_info]


================================================
FILE: advanced/slides.gs
================================================
/**
 * Copyright Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// [START apps_script_slides_create_presentation]
/**
 * Create a new presentation.
 * @return {string} presentation Id.
 * @see https://developers.google.com/slides/api/reference/rest/v1/presentations/create
 */
function createPresentation() {
  try {
    const presentation = Slides.Presentations.create({
      title: "MyNewPresentation",
    });
    console.log(`Created presentation with ID: ${presentation.presentationId}`);
    return presentation.presentationId;
  } catch (e) {
    // TODO (developer) - Handle exception
    console.log("Failed with error %s", e.message);
  }
}
// [END apps_script_slides_create_presentation]

// [START apps_script_slides_create_slide]
/**
 * Create a new slide.
 * @param {string} presentationId The presentation to add the slide to.
 * @return {Object} slide
 * @see https://developers.google.com/slides/api/reference/rest/v1/presentations/batchUpdate
 */
function cr
Download .txt
gitextract_1q8uhr3u/

├── .gemini/
│   ├── GEMINI.md
│   ├── config.yaml
│   └── settings.json
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE.md
│   ├── linters/
│   │   ├── .htmlhintrc
│   │   ├── .yaml-lint.yml
│   │   └── sun_checks.xml
│   ├── pull_request_template.md
│   ├── scripts/
│   │   ├── biome-gs.ts
│   │   ├── check-gs.ts
│   │   └── clasp_push.sh
│   ├── snippet-bot.yml
│   ├── sync-repo-settings.yaml
│   └── workflows/
│       ├── automation.yml
│       ├── lint.yml
│       ├── publish.yaml
│       └── test.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CONTRIBUTING.md
├── GEMINI.md
├── LICENSE
├── README.md
├── SECURITY.md
├── adminSDK/
│   ├── directory/
│   │   └── quickstart.gs
│   ├── reports/
│   │   └── quickstart.gs
│   └── reseller/
│       └── quickstart.gs
├── advanced/
│   ├── README.md
│   ├── adminSDK.gs
│   ├── adsense.gs
│   ├── analytics.gs
│   ├── analyticsAdmin.gs
│   ├── analyticsData.gs
│   ├── bigquery.gs
│   ├── calendar.gs
│   ├── chat.gs
│   ├── classroom.gs
│   ├── displayvideo.gs
│   ├── docs.gs
│   ├── doubleclick.gs
│   ├── doubleclickbidmanager.gs
│   ├── drive.gs
│   ├── driveActivity.gs
│   ├── driveLabels.gs
│   ├── events.gs
│   ├── gmail.gs
│   ├── iot.gs
│   ├── people.gs
│   ├── sheets.gs
│   ├── shoppingContent.gs
│   ├── slides.gs
│   ├── tagManager.gs
│   ├── tasks.gs
│   ├── test_adminSDK.gs
│   ├── test_adsense.gs
│   ├── test_analytics.gs
│   ├── test_bigquery.gs
│   ├── test_calendar.gs
│   ├── test_classroom.gs
│   ├── test_displayvideo.gs
│   ├── test_docs.gs
│   ├── test_doubleclick.gs
│   ├── test_doubleclickbidmanager.gs
│   ├── test_drive.gs
│   ├── test_gmail.gs
│   ├── test_people.gs
│   ├── test_sheets.gs
│   ├── test_shoppingContent.gs
│   ├── test_slides.gs
│   ├── test_tagManager.gs
│   ├── test_tasks.gs
│   ├── test_youtube.gs
│   ├── test_youtubeAnalytics.gs
│   ├── test_youtubeContentId.gs
│   ├── youtube.gs
│   ├── youtubeAnalytics.gs
│   └── youtubeContentId.gs
├── ai/
│   ├── autosummarize/
│   │   ├── README.md
│   │   ├── appsscript.json
│   │   ├── gemini.js
│   │   ├── main.js
│   │   ├── sidebar.html
│   │   └── summarize.js
│   ├── custom-func-ai-agent/
│   │   ├── AiVertex.js
│   │   ├── Code.js
│   │   ├── README.md
│   │   └── appsscript.json
│   ├── custom-func-ai-studio/
│   │   ├── Code.js
│   │   ├── README.md
│   │   ├── appsscript.json
│   │   └── gemini.js
│   ├── custom_func_vertex/
│   │   ├── Code.js
│   │   ├── README.md
│   │   ├── aiVertex.js
│   │   └── appsscript.json
│   ├── devdocs-link-preview/
│   │   ├── Cards.js
│   │   ├── Helpers.js
│   │   ├── Main.js
│   │   ├── README.md
│   │   ├── Vertex.js
│   │   └── appsscript.json
│   ├── drive-rename/
│   │   ├── README.md
│   │   ├── ai.js
│   │   ├── appsscript.json
│   │   ├── drive.js
│   │   ├── main.js
│   │   └── ui.js
│   ├── email-classifier/
│   │   ├── Cards.gs
│   │   ├── ClassifyEmail.gs
│   │   ├── Code.gs
│   │   ├── Constants.gs
│   │   ├── DraftEmail.gs
│   │   ├── Labels.gs
│   │   ├── README.md
│   │   ├── Sheet.gs
│   │   └── appsscript.json
│   ├── gmail-sentiment-analysis/
│   │   ├── Cards.gs
│   │   ├── Code.gs
│   │   ├── Gmail.gs
│   │   ├── README.md
│   │   ├── Vertex.gs
│   │   └── appsscript.json
│   └── standup-chat-app/
│       ├── README.md
│       ├── appsscript.json
│       ├── db.js
│       ├── gemini.js
│       ├── main.js
│       └── memoize.js
├── apps-script/
│   └── execute/
│       └── target.js
├── biome.json
├── calendar/
│   └── quickstart/
│       └── quickstart.gs
├── chat/
│   ├── advanced-service/
│   │   ├── AppAuthenticationUtils.gs
│   │   ├── Main.gs
│   │   ├── README.md
│   │   └── appsscript.json
│   └── quickstart/
│       ├── Code.gs
│       ├── README.md
│       └── appsscript.json
├── classroom/
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── snippets/
│       ├── addAlias.gs
│       ├── courseUpdate.gs
│       ├── createAlias.gs
│       ├── createCourse.gs
│       ├── getCourse.gs
│       ├── listCourses.gs
│       ├── patchCourse.gs
│       └── test_classroom_snippets.gs
├── data-studio/
│   ├── appsscript.json
│   ├── appsscript2.json
│   ├── auth.gs
│   ├── build.gs
│   ├── caas.gs
│   ├── data-source.gs
│   ├── errors.gs
│   ├── links.gs
│   ├── manifest.gs
│   └── semantics.gs
├── docs/
│   ├── README.md
│   ├── cursorInspector/
│   │   ├── README.md
│   │   ├── cursorInspector.gs
│   │   ├── sidebar.css.html
│   │   ├── sidebar.html
│   │   └── sidebar.js.html
│   ├── dialog2sidebar/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── Intercom.js.html
│   │   ├── README.md
│   │   └── Sidebar.html
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── translate/
│       ├── README.md
│       ├── sidebar.html
│       └── translate.gs
├── drive/
│   ├── activity/
│   │   └── quickstart.gs
│   ├── activity-v2/
│   │   └── quickstart.gs
│   └── quickstart/
│       └── quickstart.gs
├── forms/
│   ├── README.md
│   └── notifications/
│       ├── README.md
│       ├── about.html
│       ├── authorizationEmail.html
│       ├── creatorNotification.html
│       ├── notification.gs
│       ├── respondentNotification.html
│       └── sidebar.html
├── forms-api/
│   ├── demos/
│   │   └── AppsScriptFormsAPIWebApp/
│   │       ├── Code.gs
│   │       ├── FormsAPI.gs
│   │       ├── Main.html
│   │       ├── README.md
│   │       └── appsscript.json
│   └── snippets/
│       ├── README.md
│       └── retrieve_all_responses.gs
├── gmail/
│   ├── README.md
│   ├── add-ons/
│   │   ├── appsscript.json
│   │   └── quickstart.gs
│   ├── inlineimage/
│   │   └── inlineimage.gs
│   ├── markup/
│   │   ├── Code.gs
│   │   └── mail_template.html
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── sendingEmails/
│       └── sendingEmails.gs
├── gmail-sentiment-analysis/
│   ├── .clasp.json
│   ├── Cards.gs
│   ├── Code.gs
│   ├── Gmail.gs
│   ├── README.md
│   ├── Vertex.gs
│   └── appsscript.json
├── mashups/
│   ├── sheets2calendar.gs
│   ├── sheets2chat.gs
│   ├── sheets2contacts.gs
│   ├── sheets2docs.gs
│   ├── sheets2drive.gs
│   ├── sheets2forms.gs
│   ├── sheets2gmail.gs
│   ├── sheets2maps.gs
│   ├── sheets2slides.gs
│   └── sheets2translate.gs
├── package.json
├── people/
│   └── quickstart/
│       └── quickstart.gs
├── picker/
│   ├── README.md
│   ├── appsscript.json
│   ├── code.gs
│   └── dialog.html
├── pnpm-workspace.yaml
├── service/
│   ├── jdbc.gs
│   ├── propertyService.gs
│   ├── test_jdbc.gs
│   └── test_propertyServices.gs
├── sheets/
│   ├── README.md
│   ├── api/
│   │   ├── helpers.gs
│   │   ├── spreadsheet_snippets.gs
│   │   └── test_spreadsheet_snippets.gs
│   ├── customFunctions/
│   │   ├── btc.gs
│   │   └── customFunctions.gs
│   ├── dateAddAndSubtract/
│   │   ├── README.md
│   │   ├── dateAddAndSubtract.gs
│   │   └── moment.gs
│   ├── forms/
│   │   └── forms.gs
│   ├── maps/
│   │   └── maps.gs
│   ├── next18/
│   │   ├── .claspignore
│   │   ├── Constants.gs
│   │   ├── Invoice.gs
│   │   ├── LinkDialog.html
│   │   ├── README.md
│   │   ├── Salesforce.gs
│   │   └── appsscript.json
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── removingDuplicates/
│       └── removingDuplicates.gs
├── slides/
│   ├── README.md
│   ├── SpeakerNotesScript/
│   │   ├── README.md
│   │   ├── appscript.json
│   │   └── scriptGen.gs
│   ├── api/
│   │   ├── Helpers.gs
│   │   ├── Snippets.gs
│   │   └── Tests.gs
│   ├── imageSlides/
│   │   ├── add_image/
│   │   │   └── add_image.gs
│   │   ├── add_image_slide/
│   │   │   └── add_image_slide.gs
│   │   ├── create/
│   │   │   └── create.gs
│   │   ├── full/
│   │   │   └── full.gs
│   │   └── main/
│   │       └── main.gs
│   ├── progress/
│   │   └── progress.gs
│   ├── quickstart/
│   │   └── quickstart.gs
│   ├── selection/
│   │   └── selection.gs
│   ├── style/
│   │   ├── style.gs
│   │   └── test_style.gs
│   └── translate/
│       ├── sidebar.html
│       └── translate.gs
├── solutions/
│   ├── add-on/
│   │   ├── book-smartchip/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   └── share-macro/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── README.md
│   │       ├── UI.js
│   │       └── appsscript.json
│   ├── automations/
│   │   ├── agenda-maker/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── aggregate-document-content/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── Menu.js
│   │   │   ├── README.md
│   │   │   ├── Setup.js
│   │   │   ├── Utilities.js
│   │   │   └── appsscript.json
│   │   ├── bracket-maker/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── calendar-timesheet/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── Page.html
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── content-signup/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── course-feedback-response/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── employee-certificate/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── equipment-requests/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   ├── appsscript.json
│   │   │   ├── new-equipment-request.html
│   │   │   └── request-complete.html
│   │   ├── event-session-signup/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── feedback-sentiment-analysis/
│   │   │   ├── .clasp.json
│   │   │   ├── README.md
│   │   │   ├── appsscript.json
│   │   │   └── code.js
│   │   ├── folder-creation/
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appscript.json
│   │   ├── generate-pdfs/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── Menu.js
│   │   │   ├── README.md
│   │   │   ├── Utilities.js
│   │   │   └── appsscript.json
│   │   ├── import-csv-sheets/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   ├── SampleData.js
│   │   │   ├── SetupSample.js
│   │   │   ├── Utilities.js
│   │   │   └── appsscript.json
│   │   ├── mail-merge/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── news-sentiment/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── offsite-activity-signup/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── tax-loss-harvest-alerts/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── timesheets/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── upload-files/
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   ├── Setup.js
│   │   │   └── appsscript.json
│   │   ├── vacation-calendar/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   └── youtube-tracker/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── README.md
│   │       ├── appsscript.json
│   │       └── email.html
│   ├── custom-functions/
│   │   ├── calculate-driving-distance/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   ├── summarize-sheets-data/
│   │   │   ├── .clasp.json
│   │   │   ├── Code.js
│   │   │   ├── README.md
│   │   │   └── appsscript.json
│   │   └── tier-pricing/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── README.md
│   │       └── appsscript.json
│   ├── editor-add-on/
│   │   └── clean-sheet/
│   │       ├── .clasp.json
│   │       ├── Code.js
│   │       ├── Menu.js
│   │       ├── README.md
│   │       └── appsscript.json
│   ├── ooo-assistant/
│   │   ├── .clasp.json
│   │   ├── Chat.gs
│   │   ├── Common.gs
│   │   ├── README.md
│   │   └── appsscript.json
│   └── webhook-chat-app/
│       ├── README.md
│       ├── thread-reply.gs
│       └── webhook.gs
├── tasks/
│   ├── quickstart/
│   │   └── quickstart.gs
│   └── simpleTasks/
│       ├── README.md
│       ├── appsscript.json
│       ├── javascript.html
│       ├── page.html
│       ├── simpleTasks.gs
│       └── stylesheet.html
├── templates/
│   ├── README.md
│   ├── custom-functions/
│   │   ├── Code.gs
│   │   └── README.md
│   ├── docs-addon/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── DialogJavaScript.html
│   │   ├── README.md
│   │   ├── Sidebar.html
│   │   ├── SidebarJavaScript.html
│   │   └── Stylesheet.html
│   ├── forms-addon/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── DialogJavaScript.html
│   │   ├── README.md
│   │   ├── Sidebar.html
│   │   ├── SidebarJavaScript.html
│   │   └── Stylesheet.html
│   ├── sheets-addon/
│   │   ├── Code.gs
│   │   ├── Dialog.html
│   │   ├── DialogJavaScript.html
│   │   ├── README.md
│   │   ├── Sidebar.html
│   │   ├── SidebarJavaScript.html
│   │   └── Stylesheet.html
│   ├── sheets-import/
│   │   ├── APICode.gs
│   │   ├── Auth.gs
│   │   ├── AuthCallbackView.html
│   │   ├── AuthorizationEmail.html
│   │   ├── Configurations.gs
│   │   ├── JavaScript.html
│   │   ├── README.md
│   │   ├── Server.gs
│   │   ├── Sidebar.html
│   │   ├── Stylesheet.html
│   │   ├── Utilities.gs
│   │   └── intercom.js.html
│   ├── standalone/
│   │   └── helloWorld.gs
│   └── web-app/
│       ├── Code.gs
│       ├── Index.html
│       ├── JavaScript.html
│       ├── README.md
│       └── Stylesheet.html
├── triggers/
│   ├── form/
│   │   ├── AuthorizationEmail.html
│   │   └── Code.gs
│   ├── test_triggers.gs
│   └── triggers.gs
├── tsconfig.json
├── ui/
│   ├── communication/
│   │   ├── basic/
│   │   │   ├── code.gs
│   │   │   └── index.html
│   │   ├── failure/
│   │   │   ├── code.gs
│   │   │   └── index.html
│   │   ├── private/
│   │   │   ├── code.gs
│   │   │   └── index.html
│   │   ├── runner.gs
│   │   └── success/
│   │       ├── code.gs
│   │       └── index.html
│   ├── dialogs/
│   │   ├── alert/
│   │   │   └── alert.gs
│   │   ├── custom_dialog/
│   │   │   ├── Page.html
│   │   │   └── custom_dialog.gs
│   │   ├── custom_sidebar/
│   │   │   ├── Page.html
│   │   │   └── custom_sidebar.gs
│   │   ├── menus.gs
│   │   └── prompt/
│   │       └── prompt.gs
│   ├── forms/
│   │   ├── code.gs
│   │   └── index.html
│   ├── html/
│   │   ├── printing_scriptlet.html
│   │   ├── scriptlet.html
│   │   └── standard_scriptlet.html
│   ├── sidebar/
│   │   ├── code.gs
│   │   └── index.html
│   ├── user/
│   │   ├── code.gs
│   │   └── index.html
│   └── webapp/
│       ├── code.gs
│       └── index.html
├── utils/
│   ├── logging.gs
│   └── test_logging.gs
└── wasm/
    ├── README.md
    ├── hello-world/
    │   ├── .clasp.json
    │   ├── .gitattributes
    │   ├── .gitignore
    │   ├── Cargo.toml
    │   ├── README.md
    │   ├── build.js
    │   ├── package.json
    │   ├── polyfill.js
    │   └── src/
    │       ├── appsscript.json
    │       ├── lib.rs
    │       ├── main.js
    │       ├── test.js
    │       └── wasm.js
    ├── image-add-on/
    │   ├── .clasp.json
    │   ├── .gitattributes
    │   ├── .gitignore
    │   ├── Cargo.toml
    │   ├── README.md
    │   ├── build.js
    │   ├── package.json
    │   ├── polyfill.js
    │   └── src/
    │       ├── add-on.js
    │       ├── appsscript.json
    │       ├── lib.rs
    │       ├── main.js
    │       ├── test.js
    │       └── wasm.js
    └── python/
        ├── .clasp.json
        ├── .gitattributes
        ├── .gitignore
        ├── Cargo.toml
        ├── README.md
        ├── build.js
        ├── package.json
        ├── polyfill.js
        └── src/
            ├── appsscript.json
            ├── lib.rs
            ├── main.js
            ├── test.js
            └── wasm.js
Download .txt
SYMBOL INDEX (357 symbols across 75 files)

FILE: .github/scripts/biome-gs.ts
  function findGsFiles (line 24) | async function findGsFiles(
  function main (line 50) | async function main() {

FILE: .github/scripts/check-gs.ts
  constant TEMP_ROOT (line 33) | const TEMP_ROOT = ".tsc_check";
  type Project (line 35) | interface Project {
  type CheckResult (line 41) | interface CheckResult {
  function findFiles (line 48) | function findFiles(
  function findProjectRoots (line 70) | function findProjectRoots(rootDir: string): string[] {
  function createProjects (line 74) | function createProjects(
  function checkProject (line 135) | async function checkProject(
  function main (line 195) | async function main() {

FILE: ai/autosummarize/gemini.js
  function scriptPropertyWithDefault (line 17) | function scriptPropertyWithDefault(key, defaultValue = undefined) {
  constant VERTEX_AI_LOCATION (line 26) | const VERTEX_AI_LOCATION = scriptPropertyWithDefault(
  constant MODEL_ID (line 30) | const MODEL_ID = scriptPropertyWithDefault("model_id", "gemini-pro-visio...
  constant SERVICE_ACCOUNT_KEY (line 31) | const SERVICE_ACCOUNT_KEY = scriptPropertyWithDefault("service_account_k...
  function getAiSummary (line 42) | function getAiSummary(parts, options = {}) {
  function credentialsForVertexAI (line 100) | function credentialsForVertexAI() {

FILE: ai/autosummarize/main.js
  function onOpen (line 22) | function onOpen(e) {
  function onInstall (line 39) | function onInstall(e) {
  function showSidebar (line 46) | function showSidebar() {
  function removeAllSummaries (line 58) | function removeAllSummaries() {
  function doAutoSummarizeAI (line 74) | function doAutoSummarizeAI(

FILE: ai/autosummarize/summarize.js
  function exportFile (line 24) | function exportFile(fileId, targetType = "application/pdf") {
  function downloadFile (line 45) | function downloadFile(fileId) {
  function summarizeFiles (line 62) | function summarizeFiles(

FILE: ai/custom-func-ai-agent/AiVertex.js
  constant LOCATION (line 17) | const LOCATION =
  constant GEMINI_MODEL_ID (line 19) | const GEMINI_MODEL_ID =
  constant REASONING_ENGINE_ID (line 21) | const REASONING_ENGINE_ID = PropertiesService.getScriptProperties().getP...
  constant SERVICE_ACCOUNT_KEY (line 24) | const SERVICE_ACCOUNT_KEY = PropertiesService.getScriptProperties().getP...
  function requestLlmAuditorAdkAiAgent (line 33) | function requestLlmAuditorAdkAiAgent(statement) {
  function requestOutputFormatting (line 55) | function requestOutputFormatting(prompt) {
  function credentialsForVertexAI (line 101) | function credentialsForVertexAI() {

FILE: ai/custom-func-ai-agent/Code.js
  constant DEFAULT_OUTPUT_FORMAT (line 17) | const DEFAULT_OUTPUT_FORMAT =
  function FACT_CHECK (line 34) | function FACT_CHECK(statement, outputFormat = DEFAULT_OUTPUT_FORMAT) {

FILE: ai/custom-func-ai-studio/Code.js
  function gemini (line 25) | function gemini(range, prompt) {

FILE: ai/custom-func-ai-studio/gemini.js
  function getAiSummary (line 25) | function getAiSummary(prompt) {

FILE: ai/custom_func_vertex/Code.js
  function gemini (line 25) | function gemini(range, prompt) {

FILE: ai/custom_func_vertex/aiVertex.js
  constant VERTEX_AI_LOCATION (line 17) | const VERTEX_AI_LOCATION =
  constant MODEL_ID (line 19) | const MODEL_ID =
  constant SERVICE_ACCOUNT_KEY (line 21) | const SERVICE_ACCOUNT_KEY = PropertiesService.getScriptProperties().getP...
  function getAiSummary (line 32) | function getAiSummary(prompt) {
  function credentialsForVertexAI (line 98) | function credentialsForVertexAI() {

FILE: ai/devdocs-link-preview/Cards.js
  function buildCard (line 24) | function buildCard(pageTitle, summary, showRating = true) {
  function buildErrorCard (line 88) | function buildErrorCard() {

FILE: ai/devdocs-link-preview/Helpers.js
  function scriptPropertyWithDefault (line 20) | function scriptPropertyWithDefault(key, defaultValue = undefined) {

FILE: ai/devdocs-link-preview/Main.js
  function onLinkPreview (line 23) | function onLinkPreview(event) {
  function onRatingClicked (line 53) | function onRatingClicked(e) {

FILE: ai/devdocs-link-preview/Vertex.js
  constant VERTEX_AI_LOCATION (line 17) | const VERTEX_AI_LOCATION = scriptPropertyWithDefault(
  constant MODEL_ID (line 21) | const MODEL_ID = scriptPropertyWithDefault("model_id", "gemini-2.5-flash");
  constant SERVICE_ACCOUNT_KEY (line 22) | const SERVICE_ACCOUNT_KEY = scriptPropertyWithDefault("service_account_k...
  function getPageSummary (line 27) | function getPageSummary(targetUrl) {
  function credentialsForVertexAI (line 98) | function credentialsForVertexAI() {

FILE: ai/drive-rename/ai.js
  constant VERTEX_AI_LOCATION (line 17) | const VERTEX_AI_LOCATION =
  constant MODEL_ID (line 19) | const MODEL_ID =
  constant SERVICE_ACCOUNT_KEY (line 21) | const SERVICE_ACCOUNT_KEY = PropertiesService.getScriptProperties().getP...
  constant STANDARD_PROMPT (line 25) | const STANDARD_PROMPT = `
  function getAiSummary (line 44) | function getAiSummary(prompt) {
  function credentialsForVertexAI (line 92) | function credentialsForVertexAI() {

FILE: ai/drive-rename/drive.js
  function renameFile (line 23) | function renameFile(e) {
  function updateCard (line 52) | function updateCard(e) {
  function getDocumentBody (line 79) | function getDocumentBody(id) {
  function getDocAPIBody (line 93) | function getDocAPIBody(id) {
  function moveFileToTrash (line 122) | function moveFileToTrash(e) {

FILE: ai/drive-rename/main.js
  function onHomepageOpened (line 22) | function onHomepageOpened(e) {
  function onDriveItemsSelected (line 41) | function onDriveItemsSelected(e) {
  function onCardUpdate (line 58) | function onCardUpdate(e) {

FILE: ai/drive-rename/ui.js
  constant ICO_HEADER (line 17) | const ICO_HEADER =
  constant ICON_RENAME (line 19) | const ICON_RENAME =
  constant ICON_RETRY (line 21) | const ICON_RETRY =
  constant ICON_DELETE (line 23) | const ICON_DELETE =
  function buildSelectionPage (line 31) | function buildSelectionPage(e) {
  function buildHeader (line 218) | function buildHeader() {
  function buildHomePage (line 231) | function buildHomePage() {

FILE: ai/standup-chat-app/db.js
  class DB (line 37) | class DB {
    method constructor (line 41) | constructor(spreadsheetId) {
    method sheet (line 49) | get sheet() {
    method last (line 65) | get last() {
    method append (line 74) | append(message) {
  function testDB (line 82) | function testDB() {

FILE: ai/standup-chat-app/gemini.js
  function generateContent (line 25) | function generateContent(text, API_KEY) {

FILE: ai/standup-chat-app/main.js
  constant API_KEY (line 20) | const API_KEY = PropertiesService.getScriptProperties().getProperty("API...
  constant SPREADSHEET_ID (line 21) | const SPREADSHEET_ID =
  constant SPACE_NAME (line 23) | const SPACE_NAME =
  constant SUMMARY_HEADER (line 26) | const SUMMARY_HEADER = "\n\n*Gemini Generated Summary*\n\n";
  function standup (line 34) | function standup() {
  function summarize (line 64) | function summarize() {
  function getSenderDisplayName (line 113) | function getSenderDisplayName(sender) {
  function linkToThread (line 132) | function linkToThread(message) {

FILE: ai/standup-chat-app/memoize.js
  function hash (line 26) | function hash(str, algorithm = Utilities.DigestAlgorithm.MD5) {
  function memoize (line 49) | function memoize(func, ttl = 600, cache = CacheService.getScriptCache()) {

FILE: apps-script/execute/target.js
  function getFoldersUnderRoot (line 22) | function getFoldersUnderRoot() {

FILE: solutions/add-on/book-smartchip/Code.js
  function getBook (line 17) | function getBook(id) {
  function bookLinkPreview (line 24) | function bookLinkPreview(event) {

FILE: solutions/add-on/share-macro/Code.js
  function shareMacro_ (line 27) | function shareMacro_(sourceScriptId, targetSpreadsheetUrl) {
  constant APPS_SCRIPT_API (line 48) | const APPS_SCRIPT_API = {

FILE: solutions/add-on/share-macro/UI.js
  constant ADDON_LOGO (line 18) | const ADDON_LOGO =
  function onHomepage (line 25) | function onHomepage(e) {
  function createSelectionCard (line 39) | function createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, er...
  function onClickFunction_ (line 164) | function onClickFunction_(e) {
  function buildSuccessCard (line 213) | function buildSuccessCard(e, targetSpreadsheetUrl) {

FILE: solutions/automations/agenda-maker/Code.js
  function checkFolder (line 25) | function checkFolder() {
  function getTemplateId (line 47) | function getTemplateId(folderId) {
  function onCalendarChange (line 117) | function onCalendarChange() {
  function setUp (line 189) | function setUp() {

FILE: solutions/automations/aggregate-document-content/Code.js
  constant APP_TITLE (line 26) | const APP_TITLE = "Document summary importer";
  constant PROJECT_FOLDER_NAME (line 27) | const PROJECT_FOLDER_NAME = "Project statuses";
  constant FIND_TEXT_KEYWORDS (line 31) | const FIND_TEXT_KEYWORDS = "Summary";
  constant APP_STYLE (line 32) | const APP_STYLE = DocumentApp.ParagraphHeading.HEADING3;
  constant TEXT_COLOR (line 33) | const TEXT_COLOR = "#2e7d32";
  function performImport (line 41) | function performImport() {
  function getContent (line 124) | function getContent(body) {
  function getFiles (line 172) | function getFiles(folder) {

FILE: solutions/automations/aggregate-document-content/Menu.js
  constant MENU (line 21) | const MENU = {
  function onOpen (line 33) | function onOpen() {
  function aboutApp (line 54) | function aboutApp() {

FILE: solutions/automations/aggregate-document-content/Setup.js
  function setupConfig (line 28) | function setupConfig(includeSamples) {
  function createSampleFile (line 54) | function createSampleFile() {
  function setupWithSamples (line 99) | function setupWithSamples() {
  function createGoogleDoc (line 140) | function createGoogleDoc(document, folder, duplicate) {

FILE: solutions/automations/aggregate-document-content/Utilities.js
  function getFolderByName_ (line 28) | function getFolderByName_(folderName) {
  function test_getFolderByName (line 55) | function test_getFolderByName() {

FILE: solutions/automations/bracket-maker/Code.js
  constant RANGE_PLAYER1 (line 20) | const RANGE_PLAYER1 = "FirstPlayer";
  constant SHEET_PLAYERS (line 21) | const SHEET_PLAYERS = "Players";
  constant SHEET_BRACKET (line 22) | const SHEET_BRACKET = "Bracket";
  constant CONNECTOR_WIDTH (line 23) | const CONNECTOR_WIDTH = 15;
  function onOpen (line 28) | function onOpen() {
  function createBracket (line 38) | function createBracket() {
  function setBracketItem_ (line 127) | function setBracketItem_(rng, players) {
  function setConnector_ (line 140) | function setConnector_(sheet, rng) {

FILE: solutions/automations/calendar-timesheet/Code.js
  function run (line 238) | function run() {

FILE: solutions/automations/content-signup/Code.js
  constant EMAIL_TEMPLATE_DOC_URL (line 22) | const EMAIL_TEMPLATE_DOC_URL =
  constant EMAIL_SUBJECT (line 25) | const EMAIL_SUBJECT = "Hello, here is the content you requested";
  function installTrigger (line 42) | function installTrigger() {
  function onFormSubmit (line 54) | function onFormSubmit(e) {
  function createEmailBody (line 101) | function createEmailBody(name, topics) {
  function docToHtml (line 124) | function docToHtml(docId) {

FILE: solutions/automations/course-feedback-response/Code.js
  function onOpen (line 23) | function onOpen() {
  function installTrigger (line 33) | function installTrigger() {
  function onFormSubmit (line 45) | function onFormSubmit(e) {
  function createEmailBody (line 65) | function createEmailBody(responses) {
  function createDraft (line 89) | function createDraft(timestamp, email, emailBody) {

FILE: solutions/automations/employee-certificate/Code.js
  function onOpen (line 27) | function onOpen() {
  function createCertificates (line 40) | function createCertificates() {
  function sendCertificates (line 96) | function sendCertificates() {

FILE: solutions/automations/equipment-requests/Code.js
  constant REQUEST_NOTIFICATION_EMAIL (line 21) | const REQUEST_NOTIFICATION_EMAIL = "request_intake@example.com";
  constant AVAILABLE_LAPTOPS (line 24) | const AVAILABLE_LAPTOPS = [
  constant AVAILABLE_DESKTOPS (line 30) | const AVAILABLE_DESKTOPS = [
  constant AVAILABLE_MONITORS (line 37) | const AVAILABLE_MONITORS = ['Single 27"', 'Single 32"', 'Dual 24"'];
  function onOpen (line 44) | function onOpen() {
  function setup_ (line 55) | function setup_() {
  function cleanup_ (line 91) | function cleanup_() {
  function onFormSubmit_ (line 107) | function onFormSubmit_(event) {
  function processCompletedItems_ (line 136) | function processCompletedItems_() {
  function sendNewEquipmentRequestEmail_ (line 162) | function sendNewEquipmentRequestEmail_(request) {
  function sendEquipmentRequestCompletedEmail_ (line 181) | function sendEquipmentRequestCompletedEmail_(request) {
  function mapResponse_ (line 199) | function mapResponse_(response) {

FILE: solutions/automations/event-session-signup/Code.js
  function onOpen (line 23) | function onOpen() {
  function setUpConference_ (line 35) | function setUpConference_() {
  function setUpCalendar_ (line 62) | function setUpCalendar_(values, range) {
  function joinDateAndTime_ (line 90) | function joinDateAndTime_(date_, time) {
  function setUpForm_ (line 105) | function setUpForm_(ss, values) {
  function onFormSubmit (line 145) | function onFormSubmit(e) {
  function sendInvites_ (line 179) | function sendInvites_(user, response) {
  function sendDoc_ (line 192) | function sendDoc_(user, response) {
  function resetProperties (line 225) | function resetProperties() {

FILE: solutions/automations/feedback-sentiment-analysis/code.js
  constant COLUMN_NAME (line 24) | const COLUMN_NAME = {
  function onOpen (line 33) | function onOpen() {
  function markEntitySentiment (line 45) | function markEntitySentiment() {
  function retrieveEntitySentiment (line 133) | function retrieveEntitySentiment(line) {

FILE: solutions/automations/folder-creation/Code.js
  function createNewFolder (line 21) | function createNewFolder(project) {

FILE: solutions/automations/generate-pdfs/Code.js
  constant EMAIL_OVERRIDE (line 21) | const EMAIL_OVERRIDE = false;
  constant EMAIL_ADDRESS_OVERRIDE (line 22) | const EMAIL_ADDRESS_OVERRIDE = "test@example.com";
  constant APP_TITLE (line 25) | const APP_TITLE = "Generate and send PDFs";
  constant OUTPUT_FOLDER_NAME (line 26) | const OUTPUT_FOLDER_NAME = "Customer PDFs";
  constant DUE_DATE_NUM_DAYS (line 27) | const DUE_DATE_NUM_DAYS = 15;
  constant CUSTOMERS_SHEET_NAME (line 30) | const CUSTOMERS_SHEET_NAME = "Customers";
  constant PRODUCTS_SHEET_NAME (line 31) | const PRODUCTS_SHEET_NAME = "Products";
  constant TRANSACTIONS_SHEET_NAME (line 32) | const TRANSACTIONS_SHEET_NAME = "Transactions";
  constant INVOICES_SHEET_NAME (line 33) | const INVOICES_SHEET_NAME = "Invoices";
  constant INVOICE_TEMPLATE_SHEET_NAME (line 34) | const INVOICE_TEMPLATE_SHEET_NAME = "Invoice Template";
  constant EMAIL_SUBJECT (line 37) | const EMAIL_SUBJECT = "Invoice Notification";
  constant EMAIL_BODY (line 38) | const EMAIL_BODY = "Hello!\rPlease see the attached PDF document.";
  function processDocuments (line 46) | function processDocuments() {
  function createInvoiceForCustomer (line 90) | function createInvoiceForCustomer(
  function clearTemplateSheet (line 169) | function clearTemplateSheet() {
  function createPDF (line 190) | function createPDF(ssId, sheet, pdfName) {
  function sendEmails (line 218) | function sendEmails() {
  function dataRangeToObject (line 254) | function dataRangeToObject(sheet) {
  function getObjects (line 265) | function getObjects(data, keys) {
  function createObjectKeys (line 285) | function createObjectKeys(keys) {
  function isCellEmpty (line 289) | function isCellEmpty(cellData) {

FILE: solutions/automations/generate-pdfs/Menu.js
  function onOpen (line 31) | function onOpen(e) {

FILE: solutions/automations/generate-pdfs/Utilities.js
  function getFolderByName_ (line 27) | function getFolderByName_(folderName) {
  function test_getFolderByName (line 54) | function test_getFolderByName() {

FILE: solutions/automations/import-csv-sheets/Code.js
  constant APP_TITLE (line 25) | const APP_TITLE = "Trigger-driven CSV import [App Script Sample]";
  constant APP_FOLDER (line 26) | const APP_FOLDER = "[App Script sample] Import CSVs";
  constant SOURCE_FOLDER (line 27) | const SOURCE_FOLDER = "Inbound CSV Files";
  constant PROCESSED_FOLDER (line 28) | const PROCESSED_FOLDER = "Processed CSV Files";
  constant SHEET_REPORT_NAME (line 29) | const SHEET_REPORT_NAME = "Import CSVs";
  constant CSV_HEADER_EXIST (line 32) | const CSV_HEADER_EXIST = true;
  constant HANDLER_FUNCTION (line 33) | const HANDLER_FUNCTION = "updateApplicationSheet";
  function installTrigger (line 41) | function installTrigger() {
  function updateApplicationSheet (line 73) | function updateApplicationSheet() {
  function processCsv_ (line 155) | function processCsv_(objSpreadSheet, csvFile) {

FILE: solutions/automations/import-csv-sheets/SampleData.js
  constant SAMPLE_DATA (line 24) | const SAMPLE_DATA = {
  function getHeadings (line 136) | function getHeadings() {
  function getCSVFilesData (line 147) | function getCSVFilesData() {
  function test_getHeadings (line 174) | function test_getHeadings() {
  function test_getCSVFilesData (line 180) | function test_getCSVFilesData() {

FILE: solutions/automations/import-csv-sheets/SetupSample.js
  constant INCLUDE_SAMPLE_DATA_FILES (line 24) | const INCLUDE_SAMPLE_DATA_FILES = true;
  function setupSample (line 36) | function setupSample() {
  function setupPrimarySpreadsheet_ (line 79) | function setupPrimarySpreadsheet_(folderAppPrimary) {
  function removeSample (line 106) | function removeSample() {

FILE: solutions/automations/import-csv-sheets/Utilities.js
  function getSpreadSheet_ (line 29) | function getSpreadSheet_(fileName, objFolder) {
  function fileExists_ (line 53) | function fileExists_(fileName, objFolder) {
  function getFolder_ (line 71) | function getFolder_(folderName) {
  function getApplicationFolder_ (line 98) | function getApplicationFolder_() {
  function test_getFolderByName (line 122) | function test_getFolderByName() {

FILE: solutions/automations/mail-merge/Code.js
  constant RECIPIENT_COL (line 28) | const RECIPIENT_COL = "Recipient";
  constant EMAIL_SENT_COL (line 29) | const EMAIL_SENT_COL = "Email Sent";
  function onOpen (line 34) | function onOpen() {
  function sendEmails (line 44) | function sendEmails(subjectLine, sheet = SpreadsheetApp.getActiveSheet()) {

FILE: solutions/automations/news-sentiment/Code.js
  function onOpen (line 54) | function onOpen() {
  function showNewsPrompt (line 65) | function showNewsPrompt() {
  function analyzeNewsHeadlines (line 93) | function analyzeNewsHeadlines() {
  function getHeadlinesArray (line 115) | function getHeadlinesArray() {
  function getSentiments (line 145) | function getSentiments() {
  function retrieveSentiment (line 187) | function retrieveSentiment(text) {
  function reformatSheet (line 215) | function reformatSheet() {
  function getFace (line 228) | function getFace(value) {
  function getColor (line 243) | function getColor(value) {
  function scrub (line 259) | function scrub(text) {

FILE: solutions/automations/offsite-activity-signup/Code.js
  constant NUM_ITEMS_TO_RANK (line 20) | const NUM_ITEMS_TO_RANK = 5;
  constant ACTIVITIES_PER_PERSON (line 21) | const ACTIVITIES_PER_PERSON = 2;
  constant NUM_TEST_USERS (line 22) | const NUM_TEST_USERS = 150;
  function onOpen (line 27) | function onOpen() {
  function buildForm_ (line 40) | function buildForm_() {
  function assignActivities_ (line 91) | function assignActivities_() {
  function assignWithRandomPriority_ (line 108) | function assignWithRandomPriority_(
  function makeChoice_ (line 131) | function makeChoice_(attendee, activitiesById) {
  function checkAvailability_ (line 154) | function checkAvailability_(attendee, activity) {
  function writeAttendeeAssignments_ (line 174) | function writeAttendeeAssignments_(ss, attendees) {
  function writeActivityRosters_ (line 201) | function writeActivityRosters_(ss, activities) {
  function loadActivitySchedule_ (line 222) | function loadActivitySchedule_(ss) {
  function loadAttendeeResponses_ (line 268) | function loadAttendeeResponses_(ss, allActivityIds) {
  function generateTestData_ (line 315) | function generateTestData_() {
  function findOrCreateSheetByName_ (line 351) | function findOrCreateSheetByName_(ss, name) {
  function bulkAppendRows_ (line 365) | function bulkAppendRows_(sheet, rows) {
  function shuffleArray_ (line 379) | function shuffleArray_(array) {
  function toOrdinal_ (line 398) | function toOrdinal_(i) {
  function findResponseSheetForForm_ (line 419) | function findResponseSheetForForm_(ss) {
  function fillArray_ (line 441) | function fillArray_(arr, length, value) {
  function range_ (line 455) | function range_(start, end) {
  function transpose_ (line 474) | function transpose_(arr, fillValue) {

FILE: solutions/automations/tax-loss-harvest-alerts/Code.js
  function checkLosses (line 23) | function checkLosses() {

FILE: solutions/automations/timesheets/Code.js
  constant COLUMN_NUMBER (line 21) | const COLUMN_NUMBER = {
  constant APPROVED_EMAIL_SUBJECT (line 33) | const APPROVED_EMAIL_SUBJECT = "Weekly Timesheet APPROVED";
  constant REJECTED_EMAIL_SUBJECT (line 34) | const REJECTED_EMAIL_SUBJECT = "Weekly Timesheet NOT APPROVED";
  constant APPROVED_EMAIL_MESSAGE (line 35) | const APPROVED_EMAIL_MESSAGE = "Your timesheet has been approved.";
  constant REJECTED_EMAIL_MESSAGE (line 36) | const REJECTED_EMAIL_MESSAGE = "Your timesheet has not been approved.";
  function onOpen (line 41) | function onOpen() {
  function columnSetup (line 57) | function columnSetup() {
  function addCalculatePayColumn (line 78) | function addCalculatePayColumn(sheet, beginningRow) {
  function addApprovalColumn (line 101) | function addApprovalColumn(sheet, beginningRow, numRows) {
  function addNotifiedColumn (line 128) | function addNotifiedColumn(sheet, beginningRow, numRows) {
  function updateNotifiedStatus (line 156) | function updateNotifiedStatus(sheet, notifiedValues, i, beginningRow) {
  function checkApprovedStatusToNotify (line 166) | function checkApprovedStatusToNotify() {
  function setUpForm (line 219) | function setUpForm() {
  function onFormSubmit (line 249) | function onFormSubmit(event) {
  function getResponsesByName (line 276) | function getResponsesByName(response) {

FILE: solutions/automations/upload-files/Code.js
  constant APP_TITLE (line 25) | const APP_TITLE = "Upload files to Drive from Forms";
  constant APP_FOLDER_NAME (line 26) | const APP_FOLDER_NAME = "Upload files to Drive (File responses)";
  constant APP_SUBFOLDER_ITEM (line 29) | const APP_SUBFOLDER_ITEM = "Subfolder";
  constant APP_SUBFOLDER_NONE (line 30) | const APP_SUBFOLDER_NONE = "<None>";
  function onFormSubmit (line 37) | function onFormSubmit(e) {
  function getSubFolder_ (line 97) | function getSubFolder_(objParentFolder, subFolderName) {

FILE: solutions/automations/upload-files/Setup.js
  function setUp (line 26) | function setUp() {
  function getFolder_ (line 49) | function getFolder_(folderName) {
  function installTrigger_ (line 78) | function installTrigger_() {
  function removeTriggersAndScriptProperties (line 112) | function removeTriggersAndScriptProperties() {
  function deleteAllResponses (line 124) | function deleteAllResponses() {

FILE: solutions/automations/vacation-calendar/Code.js
  constant TEAM_CALENDAR_ID (line 22) | const TEAM_CALENDAR_ID = "ENTER_TEAM_CALENDAR_ID_HERE";
  constant GROUP_EMAIL (line 27) | const GROUP_EMAIL = "ENTER_GOOGLE_GROUP_EMAIL_HERE";
  constant ONLY_DIRECT_MEMBERS (line 29) | const ONLY_DIRECT_MEMBERS = false;
  constant KEYWORDS (line 31) | const KEYWORDS = ["vacation", "ooo", "out of office", "offline"];
  constant MONTHS_IN_ADVANCE (line 32) | const MONTHS_IN_ADVANCE = 3;
  function setup (line 37) | function setup() {
  function sync (line 51) | function sync() {
  function importEvent (line 92) | function importEvent(username, event) {
  function findEvents (line 129) | function findEvents(user, start, end, optSince) {
  function formatDateAsRFC3339 (line 170) | function formatDateAsRFC3339(date) {
  function getAllMembers (line 179) | function getAllMembers(groupEmail) {
  function getUsersFromGroups (line 205) | function getUsersFromGroups(groupEmails) {

FILE: solutions/automations/youtube-tracker/Code.js
  constant EMAIL_ON (line 21) | const EMAIL_ON = "Y";
  constant COLUMN_NAME (line 24) | const COLUMN_NAME = {
  function markVideos (line 35) | function markVideos() {
  function getVideoDetails (line 106) | function getVideoDetails(videoId) {
  function extractVideoIdFromUrl (line 116) | function extractVideoIdFromUrl(url) {
  function sendEmailNotificationTemplate (line 129) | function sendEmailNotificationTemplate(content, emailAddress) {

FILE: solutions/custom-functions/calculate-driving-distance/Code.js
  function onOpen (line 28) | function onOpen() {
  function metersToMiles (line 48) | function metersToMiles(meters) {
  function drivingDistance (line 62) | function drivingDistance(origin, destination) {
  function prepareSheet_ (line 70) | function prepareSheet_() {
  function generateStepByStep_ (line 97) | function generateStepByStep_() {
  function setAlternatingRowBackgroundColors_ (line 211) | function setAlternatingRowBackgroundColors_(range, oddColor, evenColor) {
  function getDirections_ (line 235) | function getDirections_(origin, destination) {

FILE: solutions/custom-functions/summarize-sheets-data/Code.js
  constant READ_ME_SHEET_NAME (line 28) | const READ_ME_SHEET_NAME = "ReadMe";
  constant PM_SHEET_NAME (line 29) | const PM_SHEET_NAME = "Summary";
  function getSheetsData (line 35) | function getSheetsData() {
  function filterByPosition (line 102) | function filterByPosition(array, find, position) {

FILE: solutions/custom-functions/tier-pricing/Code.js
  function tierPrice (line 36) | function tierPrice(value, table) {

FILE: solutions/editor-add-on/clean-sheet/Code.js
  constant APP_TITLE (line 21) | const APP_TITLE = "Clean sheet";
  function deleteEmptyRows (line 32) | function deleteEmptyRows() {
  function deleteEmptyColumns (line 102) | function deleteEmptyColumns() {
  function cropSheet (line 173) | function cropSheet() {
  function fillDownData (line 205) | function fillDownData() {
  function showMessage (line 255) | function showMessage(message, caller) {

FILE: solutions/editor-add-on/clean-sheet/Menu.js
  function onOpen (line 22) | function onOpen(e) {
  function onInstall (line 47) | function onInstall(e) {
  function aboutApp (line 55) | function aboutApp() {

FILE: wasm/hello-world/src/lib.rs
  function hello (line 18) | pub fn hello(name: &str) -> JsValue {

FILE: wasm/hello-world/src/main.js
  function main (line 17) | async function main() {

FILE: wasm/hello-world/src/test.js
  function test (line 17) | async function test() {
  function assert (line 21) | async function assert(a, b, message) {
  function latency (line 30) | async function latency(func, iterations, argsFunc = () => []) {
  function benchmark (line 66) | async function benchmark() {
  function generateRandomString (line 77) | function generateRandomString(length = 1024) {

FILE: wasm/hello-world/src/wasm.js
  function hello_ (line 22) | async function hello_(name) {

FILE: wasm/image-add-on/src/add-on.js
  constant COLORS (line 17) | const COLORS = {
  function card (line 23) | async function card(items) {
  function onItemsSelectedTrigger (line 150) | async function onItemsSelectedTrigger(e) {
  function onRequestFileScopeButtonClicked (line 167) | function onRequestFileScopeButtonClicked(e) {
  function onFileScopeGrantedTrigger (line 174) | function onFileScopeGrantedTrigger(e) {
  function onHomePageTrigger (line 181) | function onHomePageTrigger() {
  function bytesToText (line 194) | function bytesToText(bytes) {
  function save (line 201) | async function save(...args) {
  function updateSettings (line 208) | async function updateSettings(e) {
  function persistSettings (line 223) | function persistSettings(settings) {
  function loadSettings (line 233) | function loadSettings() {
  function qualityToInt (line 244) | function qualityToInt(quality) {

FILE: wasm/image-add-on/src/lib.rs
  function compress (line 18) | pub fn compress(data: &[u8], quality: u8, width: u32, height: u32) -> Js...

FILE: wasm/image-add-on/src/main.js
  constant QUALITY (line 17) | const QUALITY = 80;
  function main (line 19) | async function main() {

FILE: wasm/image-add-on/src/test.js
  function test (line 17) | async function test() {}
  function assert (line 19) | async function assert(a, b, message) {
  function latency (line 28) | async function latency(func, iterations, argsFunc = () => []) {
  function benchmark (line 64) | async function benchmark() {
  function generateRandomString (line 75) | function generateRandomString(length = 1024) {

FILE: wasm/image-add-on/src/wasm.js
  function compress_ (line 17) | async function compress_(bytes, { quality, format, width, height }) {

FILE: wasm/python/src/lib.rs
  function python (line 22) | pub fn python(source: &str, data: &JsValue) -> JsValue {
  function new (line 61) | fn new(message: JsValue) -> PyError;
  function serialize_exception (line 64) | fn serialize_exception(err: PyRef<PyBaseException>, vm: &VirtualMachine)...
  function serialize (line 68) | fn serialize(py_obj: &PyObjectRef, vm: &VirtualMachine) -> JsValue {
  function deserialize (line 73) | fn deserialize(

FILE: wasm/python/src/main.js
  function PYTHON (line 25) | async function PYTHON(code = "args", ...args) {

FILE: wasm/python/src/test.js
  function test (line 17) | async function test() {}
  function assert (line 19) | async function assert(a, b, message) {

FILE: wasm/python/src/wasm.js
  function python_ (line 26) | async function python_(source, ...args) {
Condensed preview — 520 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,378K chars).
[
  {
    "path": ".gemini/GEMINI.md",
    "chars": 382,
    "preview": "# Overview\n\nThis codebase is part of the Google Workspace GitHub organization, https://github.com/googleworkspace.\n\n## S"
  },
  {
    "path": ".gemini/config.yaml",
    "chars": 884,
    "preview": "# Copyright 2025 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".gemini/settings.json",
    "chars": 374,
    "preview": "{\n  \"mcpServers\": {\n    \"workspace-developer\": {\n      \"httpUrl\": \"https://workspace-developer.goog/mcp\",\n      \"trust\":"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 722,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 130,
    "preview": "# Summary\n\nTODO\n\n## Expected Behavior\n\nSample URL:\nDescription:\n\n## Actual Behavior\n\n\n## Steps to Reproduce the Problem\n"
  },
  {
    "path": ".github/linters/.htmlhintrc",
    "chars": 665,
    "preview": "{\n  \"tagname-lowercase\": true,\n  \"attr-lowercase\": true,\n  \"attr-value-double-quotes\": true,\n  \"attr-value-not-empty\": f"
  },
  {
    "path": ".github/linters/.yaml-lint.yml",
    "chars": 1983,
    "preview": "# Copyright 2025 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/linters/sun_checks.xml",
    "chars": 19783,
    "preview": "<?xml version=\"1.0\"?>\n<!--\n Copyright 2022 Google LLC\n Licensed under the Apache License, Version 2.0 (the \"License\");\n "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 703,
    "preview": "# Description\n\nPlease include a summary of the change and which issue is fixed. Please also include relevant motivation "
  },
  {
    "path": ".github/scripts/biome-gs.ts",
    "chars": 3553,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": ".github/scripts/check-gs.ts",
    "chars": 6888,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": ".github/scripts/clasp_push.sh",
    "chars": 1392,
    "preview": "#! /bin/bash\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": ".github/snippet-bot.yml",
    "chars": 576,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/sync-repo-settings.yaml",
    "chars": 1434,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/workflows/automation.yml",
    "chars": 2784,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 1191,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/workflows/publish.yaml",
    "chars": 1624,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1852,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".gitignore",
    "chars": 78,
    "preview": ".DS_Store\nnode_modules\n.gradle\n**/dist\n**/node_modules\n**/target\n**/.tsc_check"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 79,
    "preview": "{\n  \"recommendations\": [\"google-workspace.google-workspace-developer-tools\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 59,
    "preview": "{\n  \"files.associations\": {\n    \"*.gs\": \"javascript\"\n  }\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1662,
    "preview": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your samp"
  },
  {
    "path": "GEMINI.md",
    "chars": 11536,
    "preview": "# Apps Script Sample Development Guide\n\nThis guide outlines best practices for developing Google Apps Script projects, f"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 4595,
    "preview": "# Google Apps Script Samples\n\nVarious sample code and projects for the Google Apps Script platform, a JavaScript platfor"
  },
  {
    "path": "SECURITY.md",
    "chars": 386,
    "preview": "# Report a security issue\n\nTo report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz). We use\n[http"
  },
  {
    "path": "adminSDK/directory/quickstart.gs",
    "chars": 1560,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "adminSDK/reports/quickstart.gs",
    "chars": 1667,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "adminSDK/reseller/quickstart.gs",
    "chars": 1687,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/README.md",
    "chars": 377,
    "preview": "# Advanced Services Samples\n\nThis directory contains samples for using Apps Script Advanced Services.\n\n> Note: These ser"
  },
  {
    "path": "advanced/adminSDK.gs",
    "chars": 15133,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/adsense.gs",
    "chars": 5283,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/analytics.gs",
    "chars": 4521,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/analyticsAdmin.gs",
    "chars": 1289,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "advanced/analyticsData.gs",
    "chars": 2749,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "advanced/bigquery.gs",
    "chars": 4495,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/calendar.gs",
    "chars": 9482,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/chat.gs",
    "chars": 3744,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/classroom.gs",
    "chars": 1480,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/displayvideo.gs",
    "chars": 3092,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/docs.gs",
    "chars": 5051,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/doubleclick.gs",
    "chars": 3914,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/doubleclickbidmanager.gs",
    "chars": 3925,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/drive.gs",
    "chars": 4114,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/driveActivity.gs",
    "chars": 1439,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/driveLabels.gs",
    "chars": 4537,
    "preview": "/**\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "advanced/events.gs",
    "chars": 6122,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/gmail.gs",
    "chars": 4457,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/iot.gs",
    "chars": 6463,
    "preview": "/**\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "advanced/people.gs",
    "chars": 6683,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/sheets.gs",
    "chars": 6315,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/shoppingContent.gs",
    "chars": 5408,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/slides.gs",
    "chars": 7087,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/tagManager.gs",
    "chars": 6395,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/tasks.gs",
    "chars": 3269,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_adminSDK.gs",
    "chars": 3395,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_adsense.gs",
    "chars": 1297,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_analytics.gs",
    "chars": 1065,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_bigquery.gs",
    "chars": 958,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_calendar.gs",
    "chars": 2267,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_classroom.gs",
    "chars": 823,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_displayvideo.gs",
    "chars": 1251,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_docs.gs",
    "chars": 2928,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_doubleclick.gs",
    "chars": 1313,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_doubleclickbidmanager.gs",
    "chars": 1228,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_drive.gs",
    "chars": 3003,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_gmail.gs",
    "chars": 949,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_people.gs",
    "chars": 930,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_sheets.gs",
    "chars": 2562,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_shoppingContent.gs",
    "chars": 1603,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_slides.gs",
    "chars": 4653,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_tagManager.gs",
    "chars": 2391,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_tasks.gs",
    "chars": 1320,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_youtube.gs",
    "chars": 904,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_youtubeAnalytics.gs",
    "chars": 830,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/test_youtubeContentId.gs",
    "chars": 1326,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/youtube.gs",
    "chars": 6382,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/youtubeAnalytics.gs",
    "chars": 2828,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "advanced/youtubeContentId.gs",
    "chars": 4215,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "ai/autosummarize/README.md",
    "chars": 1613,
    "preview": "# Editor Add-on: Sheets - AutoSummarize AI\n\n## Project Description\n\nGoogle Workspace Editor Add-on for Google Sheets tha"
  },
  {
    "path": "ai/autosummarize/appsscript.json",
    "chars": 477,
    "preview": "{\n  \"timeZone\": \"America/Denver\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"dependencies\": {\n   "
  },
  {
    "path": "ai/autosummarize/gemini.js",
    "chars": 3697,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/autosummarize/main.js",
    "chars": 4576,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/autosummarize/sidebar.html",
    "chars": 10954,
    "preview": "<!-- \nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this fi"
  },
  {
    "path": "ai/autosummarize/summarize.js",
    "chars": 5319,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/custom-func-ai-agent/AiVertex.js",
    "chars": 3926,
    "preview": "/*\nCopyright 2025 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/custom-func-ai-agent/Code.js",
    "chars": 1486,
    "preview": "/*\nCopyright 2025 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/custom-func-ai-agent/README.md",
    "chars": 2603,
    "preview": "# Google Sheets Custom Function relying on ADK AI Agent and Gemini model\n\nA [Vertex AI](https://cloud.google.com/vertex-"
  },
  {
    "path": "ai/custom-func-ai-agent/appsscript.json",
    "chars": 307,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"dependencies\": {\n    \"libraries\": [\n      {\n        \"userSymbol\": \"OAuth2\",\n  "
  },
  {
    "path": "ai/custom-func-ai-studio/Code.js",
    "chars": 899,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/custom-func-ai-studio/README.md",
    "chars": 1114,
    "preview": "# Google Sheets Custom Function with AI Studio\n\n## Project Description\n\nGoogle Sheets Custom Function to be used as a bo"
  },
  {
    "path": "ai/custom-func-ai-studio/appsscript.json",
    "chars": 125,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"dependencies\": {},\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V"
  },
  {
    "path": "ai/custom-func-ai-studio/gemini.js",
    "chars": 2173,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/custom_func_vertex/Code.js",
    "chars": 1011,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/custom_func_vertex/README.md",
    "chars": 1385,
    "preview": "# Google Sheets Custom Function with AI Studio\n\n## Project Description\n\nGoogle Sheets Custom Function to be used as a bo"
  },
  {
    "path": "ai/custom_func_vertex/aiVertex.js",
    "chars": 3372,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/custom_func_vertex/appsscript.json",
    "chars": 307,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"dependencies\": {\n    \"libraries\": [\n      {\n        \"userSymbol\": \"OAuth2\",\n  "
  },
  {
    "path": "ai/devdocs-link-preview/Cards.js",
    "chars": 3478,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/devdocs-link-preview/Helpers.js",
    "chars": 890,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/devdocs-link-preview/Main.js",
    "chars": 2041,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/devdocs-link-preview/README.md",
    "chars": 1047,
    "preview": "# Google Workspace Add-on - Developer Docs Link previews\n\n\n## Project Description\n\nA Google Workspace Add-on that create"
  },
  {
    "path": "ai/devdocs-link-preview/Vertex.js",
    "chars": 4072,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/devdocs-link-preview/appsscript.json",
    "chars": 2083,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"dependencies\": "
  },
  {
    "path": "ai/drive-rename/README.md",
    "chars": 1399,
    "preview": "# Google Workspace Add-on Drive - Name with Intelligence\n\n## Project Description\n\nGoogle Workspace Add-on for Google Dri"
  },
  {
    "path": "ai/drive-rename/ai.js",
    "chars": 3253,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/drive-rename/appsscript.json",
    "chars": 1264,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"dependencies\": "
  },
  {
    "path": "ai/drive-rename/drive.js",
    "chars": 3225,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/drive-rename/main.js",
    "chars": 1357,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/drive-rename/ui.js",
    "chars": 6338,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/email-classifier/Cards.gs",
    "chars": 5967,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/ClassifyEmail.gs",
    "chars": 5987,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/Code.gs",
    "chars": 2565,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/Constants.gs",
    "chars": 971,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/DraftEmail.gs",
    "chars": 4549,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/Labels.gs",
    "chars": 3281,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/README.md",
    "chars": 5949,
    "preview": "# Email Classifier\n\nThis Apps Script project provides a Gmail add-on that classifies emails based on\ntheir content and s"
  },
  {
    "path": "ai/email-classifier/Sheet.gs",
    "chars": 2930,
    "preview": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "ai/email-classifier/appsscript.json",
    "chars": 1019,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"dependencies\": {\n    \"enabledAdvancedServices\": [\n      {\n        \"userSymbol\""
  },
  {
    "path": "ai/gmail-sentiment-analysis/Cards.gs",
    "chars": 2101,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/gmail-sentiment-analysis/Code.gs",
    "chars": 731,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/gmail-sentiment-analysis/Gmail.gs",
    "chars": 1578,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/gmail-sentiment-analysis/README.md",
    "chars": 1563,
    "preview": "# Gmail sentiment analysis with Vertex AI\n\n## Project Description\n\nGoogle Workspace Add-on that extends Gmail and adds s"
  },
  {
    "path": "ai/gmail-sentiment-analysis/Vertex.gs",
    "chars": 3259,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/gmail-sentiment-analysis/appsscript.json",
    "chars": 627,
    "preview": "{\n  \"timeZone\": \"Europe/Madrid\",\n  \"dependencies\": {\n    \"libraries\": [\n      {\n        \"userSymbol\": \"OAuth2\",\n        "
  },
  {
    "path": "ai/standup-chat-app/README.md",
    "chars": 1865,
    "preview": "# Chat API - Stand up with AI\n\n## Project Description\n\nGoogle Chat application that creates AI summaries of a consolidat"
  },
  {
    "path": "ai/standup-chat-app/appsscript.json",
    "chars": 1172,
    "preview": "{\n  \"timeZone\": \"America/Los_Angeles\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"dependencies\": "
  },
  {
    "path": "ai/standup-chat-app/db.js",
    "chars": 2393,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/standup-chat-app/gemini.js",
    "chars": 1250,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/standup-chat-app/main.js",
    "chars": 4081,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "ai/standup-chat-app/memoize.js",
    "chars": 2195,
    "preview": "/*\nCopyright 2024 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "apps-script/execute/target.js",
    "chars": 1118,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "biome.json",
    "chars": 452,
    "preview": "{\n  \"$schema\": \"https://biomejs.dev/schemas/1.9.4/schema.json\",\n  \"vcs\": {\n    \"enabled\": true,\n    \"clientKind\": \"git\","
  },
  {
    "path": "calendar/quickstart/quickstart.gs",
    "chars": 1787,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "chat/advanced-service/AppAuthenticationUtils.gs",
    "chars": 2149,
    "preview": "/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "chat/advanced-service/Main.gs",
    "chars": 27181,
    "preview": "/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "chat/advanced-service/README.md",
    "chars": 1045,
    "preview": "# Google Chat API - Advanced Service samples\n\n## Set up\n\n1. Follow the Google Chat app quickstart for Apps Script\n   htt"
  },
  {
    "path": "chat/advanced-service/appsscript.json",
    "chars": 1005,
    "preview": "{\n  \"timeZone\": \"America/New_York\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"oauthScopes\": [\n  "
  },
  {
    "path": "chat/quickstart/Code.gs",
    "chars": 1549,
    "preview": "/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "chat/quickstart/README.md",
    "chars": 421,
    "preview": "# Google Chat Apps Script Quickstart\n\nComplete the steps described in the [quickstart instructions](\nhttps://developers."
  },
  {
    "path": "chat/quickstart/appsscript.json",
    "chars": 352,
    "preview": "{\n  \"timeZone\": \"America/New_York\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"oauthScopes\": [\"ht"
  },
  {
    "path": "classroom/quickstart/quickstart.gs",
    "chars": 1630,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/addAlias.gs",
    "chars": 1252,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/courseUpdate.gs",
    "chars": 1291,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/createAlias.gs",
    "chars": 1506,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/createCourse.gs",
    "chars": 1627,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/getCourse.gs",
    "chars": 1198,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/listCourses.gs",
    "chars": 1428,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/patchCourse.gs",
    "chars": 1158,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "classroom/snippets/test_classroom_snippets.gs",
    "chars": 2209,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/appsscript.json",
    "chars": 1065,
    "preview": "{\n  \"dataStudio\": {\n    \"name\": \"Nucleus by Hooli\",\n    \"company\": \"Hooli Inc.\",\n    \"companyUrl\": \"https://hooli.xyz\",\n"
  },
  {
    "path": "data-studio/appsscript2.json",
    "chars": 541,
    "preview": "{\n  \"dataStudio\": {\n    \"name\": \"npm Downloads - Build Guide\",\n    \"logoUrl\": \"https://raw.githubusercontent.com/npm/log"
  },
  {
    "path": "data-studio/auth.gs",
    "chars": 12559,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/build.gs",
    "chars": 4582,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/caas.gs",
    "chars": 3890,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/data-source.gs",
    "chars": 1293,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/errors.gs",
    "chars": 2895,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/links.gs",
    "chars": 1166,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "data-studio/manifest.gs",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "data-studio/semantics.gs",
    "chars": 1124,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "docs/README.md",
    "chars": 360,
    "preview": "# Google Docs Add-ons\n\n## Cursor Inspector\n\nThis add-on allows you to inspect the current state of the cursor or selecti"
  },
  {
    "path": "docs/cursorInspector/README.md",
    "chars": 790,
    "preview": "# Cursor Inspector\n\nCursor Inspector is a sample script for Google Docs that allows you to inspect\nthe current state of "
  },
  {
    "path": "docs/cursorInspector/cursorInspector.gs",
    "chars": 2479,
    "preview": "// Copyright 2013 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");"
  },
  {
    "path": "docs/cursorInspector/sidebar.css.html",
    "chars": 1703,
    "preview": "<!--\n Copyright 2022 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this "
  },
  {
    "path": "docs/cursorInspector/sidebar.html",
    "chars": 1885,
    "preview": "<!--\n Copyright 2022 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this "
  },
  {
    "path": "docs/cursorInspector/sidebar.js.html",
    "chars": 5176,
    "preview": "<!--\n Copyright 2022 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this "
  },
  {
    "path": "docs/dialog2sidebar/Code.gs",
    "chars": 1638,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "docs/dialog2sidebar/Dialog.html",
    "chars": 2457,
    "preview": "<!DOCTYPE html>\n<!--\n  Copyright 2015 Google Inc. All rights reserved.\n\n  Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "docs/dialog2sidebar/Intercom.js.html",
    "chars": 5974,
    "preview": "<!--\n  Copyright © 2012 DIY Co\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this"
  },
  {
    "path": "docs/dialog2sidebar/README.md",
    "chars": 1393,
    "preview": "# Dialog to Sidebar Communication in Apps Script\n\nThis script demonstrates a method of setting up a communication channe"
  },
  {
    "path": "docs/dialog2sidebar/Sidebar.html",
    "chars": 3426,
    "preview": "<!DOCTYPE html>\n<!--\n  Copyright 2015 Google Inc. All rights reserved.\n\n  Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "docs/quickstart/quickstart.gs",
    "chars": 1092,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "docs/translate/README.md",
    "chars": 471,
    "preview": "# Translate\n\nTranslate is a sample script for Google Docs that allows you to translate\nselected text from a set of sourc"
  },
  {
    "path": "docs/translate/sidebar.html",
    "chars": 7225,
    "preview": "<!--\nCopyright 2018 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this fil"
  },
  {
    "path": "docs/translate/translate.gs",
    "chars": 9488,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "drive/activity/quickstart.gs",
    "chars": 1603,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "drive/activity-v2/quickstart.gs",
    "chars": 3966,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "drive/quickstart/quickstart.gs",
    "chars": 1191,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "forms/README.md",
    "chars": 420,
    "preview": "# Google Forms Add-ons\n\n## [Notification Add-on](https://developers.google.com/apps-script/quickstart/forms-add-on)\n\nThi"
  },
  {
    "path": "forms/notifications/README.md",
    "chars": 1698,
    "preview": "# Form Notifications Add-on for Google Forms\n\nA sample Google Apps Script add-on for Google Forms.\n\n## Introduction\n\nGoo"
  },
  {
    "path": "forms/notifications/about.html",
    "chars": 1592,
    "preview": "<!--Copyright Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file exce"
  },
  {
    "path": "forms/notifications/authorizationEmail.html",
    "chars": 1493,
    "preview": "<!--Copyright Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file exce"
  },
  {
    "path": "forms/notifications/creatorNotification.html",
    "chars": 1484,
    "preview": "<!--Copyright Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file exce"
  },
  {
    "path": "forms/notifications/notification.gs",
    "chars": 12709,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "forms/notifications/respondentNotification.html",
    "chars": 912,
    "preview": "<!--Copyright Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file exce"
  },
  {
    "path": "forms/notifications/sidebar.html",
    "chars": 9177,
    "preview": "<!--Copyright Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file exce"
  },
  {
    "path": "forms-api/demos/AppsScriptFormsAPIWebApp/Code.gs",
    "chars": 674,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "forms-api/demos/AppsScriptFormsAPIWebApp/FormsAPI.gs",
    "chars": 7398,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "forms-api/demos/AppsScriptFormsAPIWebApp/Main.html",
    "chars": 12365,
    "preview": "<!DOCTYPE html>\n<!--\n Copyright 2022 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you m"
  },
  {
    "path": "forms-api/demos/AppsScriptFormsAPIWebApp/README.md",
    "chars": 1162,
    "preview": "# Google Forms API Apps Script web app\n\nThis solution demonstrates how to interact with the new Google Forms API directl"
  },
  {
    "path": "forms-api/demos/AppsScriptFormsAPIWebApp/appsscript.json",
    "chars": 611,
    "preview": "{\n  \"timeZone\": \"America/New_York\",\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\",\n  \"dependencies\": {},"
  },
  {
    "path": "forms-api/snippets/README.md",
    "chars": 135,
    "preview": "# Forms API\n\nTo run, you must set up your GCP project to use the Forms API.\nSee: [Forms API](https://developers.google.c"
  },
  {
    "path": "forms-api/snippets/retrieve_all_responses.gs",
    "chars": 1302,
    "preview": "/**\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "gmail/README.md",
    "chars": 640,
    "preview": "# Apps Scripts for Gmail\n\nSample Google Apps Script functions for Gmail.\n\n## [Mail Merge](https://developers.google.com/"
  },
  {
    "path": "gmail/add-ons/appsscript.json",
    "chars": 626,
    "preview": "{\n  \"oauthScopes\": [\n    \"https://www.googleapis.com/auth/gmail.addons.execute\",\n    \"https://www.googleapis.com/auth/gm"
  },
  {
    "path": "gmail/add-ons/quickstart.gs",
    "chars": 4033,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  },
  {
    "path": "gmail/inlineimage/inlineimage.gs",
    "chars": 2881,
    "preview": "/**\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "gmail/markup/Code.gs",
    "chars": 486,
    "preview": "// [START gmail_send_email_with_markup]\n/**\n * Send an email with schemas in order to test email markup.\n */\nfunction te"
  },
  {
    "path": "gmail/markup/mail_template.html",
    "chars": 995,
    "preview": "<!--\n Copyright 2022 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this "
  },
  {
    "path": "gmail/quickstart/quickstart.gs",
    "chars": 1455,
    "preview": "/**\n * Copyright  Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
  },
  {
    "path": "gmail/sendingEmails/sendingEmails.gs",
    "chars": 2898,
    "preview": "/**\n * Copyright Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use thi"
  }
]

// ... and 320 more files (download for full content)

About this extraction

This page contains the full source code of the googlesamples/apps-script GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 520 files (1.2 MB), approximately 327.5k tokens, and a symbol index with 357 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!