diff --git a/.github/linters/.eslintrc.yml b/.eslintrc.yml similarity index 100% rename from .github/linters/.eslintrc.yml rename to .eslintrc.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..09270b6 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: 'CodeQL' + +on: + push: + branches: [main, release] + pull_request: + # The branches below must be a subset of the branches above + branches: [release] + # schedule: + # - cron: '20 18 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript'] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 6426ea0..b2b34b1 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -53,5 +53,7 @@ jobs: env: VALIDATE_ALL_CODEBASE: true VALIDATE_JAVASCRIPT_ES: true + LINTER_RULES_PATH: / + JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.yml DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 57bab2a..36b504f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ -# Website Status Monitoring using Google Apps Script & Google Sheets +# Website Status Monitoring -[![clasp](https://img.shields.io/badge/built%20with-clasp-4285f4.svg)](https://github.com/google/clasp) +[![GitHub Super-Linter](https://github.com/ttsukagoshi/website-monitoring-by-gas/workflows/Lint%20Code%20Base/badge.svg)](https://github.com/marketplace/actions/super-linter) [![clasp](https://img.shields.io/badge/built%20with-clasp-4285f4.svg?style=flat-square)](https://github.com/google/clasp) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) + +Website status monitoring using Google Sheets and Google Apps Script. + +## About + +A Spreadsheet-bound apps script solution to conduct automated status monitoring on websites listed by the user in a Google Sheets management file. A separate status log file in Google Sheets will be created so that users can easily integrate data with BI services such as Google Data Studio. + +## Install + +Copy [this sample spreadsheet](https://docs.google.com/spreadsheets/d/1JvO090VcgvF-WwciNnzRb1_nonKJC5QHN73h_CXS1Cw/edit#gid=0) to your Google Drive. + +## How to Use + +### Setup + +#### `01_Dashboard` Worksheet + +Replace the `WEBSITE NAME` and `TARGET URL` columns with those of the website(s) that you want monitor. + +#### `90_Spreadsheets` Worksheet + +Delete everything **except the first row**. + +#### `99_Options` Worksheet + +Go over the parameters that you can set for this status monitoring and edit the `VALUE` items to suit you needs. + +#### Set Triggers + +From the spreadsheet menu, select `Web Status` > `Triggers` > `Set Status Check Trigger`/`Set Log Extraction Trigger` to set up time-based triggers to conduct automated status checks. The latest results will be shown in the `01_Dashboard` worksheet. + +You will be asked to authorized the script the first time you execute it. Users of free Gmail account should expect to see the `Unverified` warning during this authorization process. Note that the owner of the script is yourself, and that this solution will not send or receive any information to any other Google accounts or services outside the Google ecosystem (except for checking the HTTP response codes of the websites that you designated because, well, that's what it does for status monitoring) unless you explicitly share the spreadsheet. + +## Updates + +Updates will be distributed via [@ttsukagoshi/website-monitoring-by-gas (GitHub)](https://github.com/ttsukagoshi/website-monitoring-by-gas). + +## Terms and Conditions + +You must agree to the [Terms and Conditions](https://www.scriptable-assets.page/terms-and-conditions/) to use this solution. diff --git a/package-lock.json b/package-lock.json index 5c5a34c..a1b7068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "devDependencies": { "@google/clasp": "^2.4.0", "@types/google-apps-script": "^1.0.37", - "eslint": "^7.31.0", + "eslint": "^7.32.0", + "@typescript-eslint/parser": "^4.28.5", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.0", "prettier": "^2.3.2", @@ -196,6 +197,41 @@ "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@sindresorhus/is": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", @@ -280,6 +316,122 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/parser": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.5.tgz", + "integrity": "sha512-NPCOGhTnkXGMqTznqgVbA5LqVsnw+i3+XA1UKLnAb+MG1Y1rP4ZSK9GX0kJBmAZTMIktf+dTwXToT6kFwyimbw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.28.5", + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/typescript-estree": "4.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz", + "integrity": "sha512-PHLq6n9nTMrLYcVcIZ7v0VY1X7dK309NM8ya9oL/yG8syFINIMHxyr2GzGoBYUdv3NUfCOqtuqps0ZmcgnZTfQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/visitor-keys": "4.28.5" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.5.tgz", + "integrity": "sha512-MruOu4ZaDOLOhw4f/6iudyks/obuvvZUAHBDSW80Trnc5+ovmViLT2ZMDXhUV66ozcl6z0LJfKs1Usldgi/WCA==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.5.tgz", + "integrity": "sha512-FzJUKsBX8poCCdve7iV7ShirP8V+ys2t1fvamVeD1rWpiAnIm550a+BX/fmTHrjEpQJ7ZAn+Z7ZZwJjytk9rZw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/visitor-keys": "4.28.5", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.5.tgz", + "integrity": "sha512-dva/7Rr+EkxNWdJWau26xU/0slnFlkh88v3TsyTgRS/IIYFi5iIfpCFM4ikw0vQTFUR9FYSSyqgK4w64gsgxhg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.28.5", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -907,6 +1059,18 @@ "node": ">=8" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -987,9 +1151,9 @@ } }, "node_modules/eslint": { - "version": "7.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.31.0.tgz", - "integrity": "sha512-vafgJpSh2ia8tnTkNUkwxGmnumgckLh5aAbLa1xRmIn9+owi8qBNGKL+B881kNKNTy7FFqTEkpNkUvmw0n6PkA==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", @@ -1295,6 +1459,22 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1313,6 +1493,15 @@ "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", "dev": true }, + "node_modules/fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -1559,6 +1748,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/google-auth-library": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.3.0.tgz", @@ -2344,6 +2562,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -2823,6 +3063,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -2926,6 +3175,26 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -3121,6 +3390,16 @@ "node": ">=8" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3154,6 +3433,29 @@ "node": ">=0.12.0" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rxjs": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.2.0.tgz", @@ -3239,6 +3541,15 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -3497,6 +3808,27 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "dev": true }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3815,6 +4147,32 @@ "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@sindresorhus/is": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", @@ -3890,6 +4248,70 @@ "@types/node": "*" } }, + "@typescript-eslint/parser": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.5.tgz", + "integrity": "sha512-NPCOGhTnkXGMqTznqgVbA5LqVsnw+i3+XA1UKLnAb+MG1Y1rP4ZSK9GX0kJBmAZTMIktf+dTwXToT6kFwyimbw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.28.5", + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/typescript-estree": "4.28.5", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz", + "integrity": "sha512-PHLq6n9nTMrLYcVcIZ7v0VY1X7dK309NM8ya9oL/yG8syFINIMHxyr2GzGoBYUdv3NUfCOqtuqps0ZmcgnZTfQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/visitor-keys": "4.28.5" + } + }, + "@typescript-eslint/types": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.5.tgz", + "integrity": "sha512-MruOu4ZaDOLOhw4f/6iudyks/obuvvZUAHBDSW80Trnc5+ovmViLT2ZMDXhUV66ozcl6z0LJfKs1Usldgi/WCA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.5.tgz", + "integrity": "sha512-FzJUKsBX8poCCdve7iV7ShirP8V+ys2t1fvamVeD1rWpiAnIm550a+BX/fmTHrjEpQJ7ZAn+Z7ZZwJjytk9rZw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/visitor-keys": "4.28.5", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.5.tgz", + "integrity": "sha512-dva/7Rr+EkxNWdJWau26xU/0slnFlkh88v3TsyTgRS/IIYFi5iIfpCFM4ikw0vQTFUR9FYSSyqgK4w64gsgxhg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.28.5", + "eslint-visitor-keys": "^2.0.0" + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -4330,6 +4752,15 @@ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4398,9 +4829,9 @@ "dev": true }, "eslint": { - "version": "7.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.31.0.tgz", - "integrity": "sha512-vafgJpSh2ia8tnTkNUkwxGmnumgckLh5aAbLa1xRmIn9+owi8qBNGKL+B881kNKNTy7FFqTEkpNkUvmw0n6PkA==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", @@ -4626,6 +5057,19 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4644,6 +5088,15 @@ "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", "dev": true }, + "fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4822,6 +5275,28 @@ } } }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "google-auth-library": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.3.0.tgz", @@ -5419,6 +5894,22 @@ "semver": "^6.0.0" } }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5754,6 +6245,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -5824,6 +6321,12 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -5963,6 +6466,12 @@ "signal-exit": "^3.0.2" } }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5984,6 +6493,15 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "rxjs": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.2.0.tgz", @@ -6043,6 +6561,12 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -6247,6 +6771,23 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "dev": true }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 3bd6c87..fb4227c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "devDependencies": { "@google/clasp": "^2.4.0", "@types/google-apps-script": "^1.0.37", - "eslint": "^7.31.0", + "@typescript-eslint/parser": "^4.28.5", + "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.0", "prettier": "^2.3.2", diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..2e932eb --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,425 @@ +// Copyright 2021 Taro Tsukagoshi +// +// 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. + +/* exported LocalizedMessage */ + +const MESSAGES = { + en: { + menuTitle: 'Web Status', + menuTriggers: 'Triggers', + menuSetStatusCheckTrigger: 'Set Status Check Trigger', + menuSetLogExtractionTrigger: 'Set Log Extraction Trigger', + menuDeleteTriggers: 'Delete Triggers', + menuCheckStatus: 'Check Status', + menuExtractStatusLogs: 'Extract Status Logs', + errorInvalidFrequencyValue: + 'Invalid {{frequencyKey}} value. Check the "{{sheetNameOptions}}" worksheet for its value.', + functionDescWebsiteMonitoringTriggered: ' to check website status', + functionDescExtractStatusLogsTriggered: + ' to extract status logs into the managing spreadsheet', + alertMessageContinueTriggerSetup: + 'Setting up new trigger{{functionDesc}}. \nThis process will delete existing trigger for this function that was set by {{myEmail}}. Are you sure you want to continue?', + alertTitleContinueTriggerSetup: 'Trigger Setup', + errorTriggerSetupCanceled: 'Trigger setup has been canceled.', + errorInvalidFrequencyUnit: 'Invalid frequency unit: {{frequencyUnit}}', + alertTitleCompleteTriggerSetup: 'Complete ({{handlerFunction}})', + alertMessageCompleteTriggerSetup: + 'Trigger set at {{frequency}}-{{frequencyUnit}} interval.', + alertTitleContinueTriggerDelete: 'Deleting All Triggers', + alertMessageContinueTriggerDelete: + 'Deleting all existing trigger(s) on this spreadsheet/script set by {{myEmail}}. Are you sure you want to continue?', + errorTriggerDeleteCanceled: 'Trigger deletion has been canceled.', + alertTitleComplete: 'Complete', + alertMessageTriggerDelete: 'Trigger(s) deleted.', + mailSubNewDown: '[Website Status] Alert: Site DOWN', + mailBodyNewDown: + 'The following website(s) are DOWN:\n\n{{newDownList}}\n\n-----\nThis notice is managed by the following spreadsheet:\n{{spreadsheetUrl}}', + mailSubResolved: '[Website Status] Notice: Site UP (Resolved)', + mailBodyResolved: + 'The following website(s) that were DOWN are now UP:\n\n{{resolvedList}}\n\n-----\nThis notice is managed by the following spreadsheet:\n{{spreadsheetUrl}}', + alertMessageCompleteStatusCheck: 'Website status check is complete.', + alertMessageCompleteStatusCheckAdd: + '\nChanges to website status have been emailed to {{myEmail}}', + alertTitleCompleteStatusCheck: '[Website Status] Complete: Status Check', + mailSubErrorStatusCheck: '[Website Status] Error: Status Check', + mailBodyErrorStatusCheck: + '{{errorStack}}\n\n-----\nThis notice is managed by the following spreadsheet:\n{{spreadsheetUrl}}', + alertTitleError: 'ERROR', + errorHeaderNameTargetUrlNotFound: + '"{{headerNameTargetUrl}}" is not found. Check the "{{sheetNameDashboard}}" worksheet for the header name of the target websites\' URL.', + errorAddTriggerWillBeDeleted: + "\nThis function's trigger has been deleted. Fix the error and set up the trigger again.", + errorInconsistencyInHeader: + 'There seems to be an inconsistency in the header row between the status log files. Edit the header(s) so that they match and retry.', + alertMessageLogExtractionComplete: + 'Status Log for the last {{extractStatusLogDays}} days have been copied to the "{{sheetNameStatusLogsExtracted}}" worksheet.', + alertMessageErrorInLogExtraction: + '[Website Status] Error in Status Log Extraction:\n{{errorStack}}', + errorInvalidResponseCode: + 'Invalid response code "{{code}}" at ALLOWED_RESPONSE_CODES or ERROR_RESPONSE_CODES.', + }, + ja_JP: { + menuTitle: 'サイト公開ステータス', + menuTriggers: 'トリガー', + menuSetStatusCheckTrigger: 'トリガー設定(ステータス確認)', + menuSetLogExtractionTrigger: 'トリガー設定(ログ抽出)', + menuDeleteTriggers: 'トリガー削除', + menuCheckStatus: 'ステータス確認', + menuExtractStatusLogs: '確認ログを抽出', + errorInvalidFrequencyValue: + '{{frequencyKey}}の値が無効です。シート「{{sheetNameOptions}}」で値を確認してください。', + functionDescWebsiteMonitoringTriggered: + 'ウェブサイトのステータス確認のための', + functionDescExtractStatusLogsTriggered: + 'この管理シートにステータスログを抽出してくるための', + alertMessageContinueTriggerSetup: + '{{functionDesc}}新規のトリガーを設定します。\n{{myEmail}} によって設定された既存のトリガーは削除・上書きされます。このまま設定を続けますか?', + alertTitleContinueTriggerSetup: '確認:トリガー設定', + errorTriggerSetupCanceled: 'トリガー設定がキャンセルされました。', + errorInvalidFrequencyUnit: '無効なトリガー間隔単位: {{frequencyUnit}}', + alertTitleCompleteTriggerSetup: '完了({{handlerFunction}})', + alertMessageCompleteTriggerSetup: + 'トリガー設定完了:{{frequency}}-{{frequencyUnit}}間隔.', + alertTitleContinueTriggerDelete: '全てのトリガーを削除します', + alertMessageContinueTriggerDelete: + 'このスプレッドシート/スクリプトで {{myEmail}} によって設定された全てのトリガーを削除します。このまま続けますか?', + errorTriggerDeleteCanceled: 'トリガー削除がキャンセルされました。', + alertTitleComplete: '完了', + alertMessageTriggerDelete: '既存のトリガーが全て削除されました。', + mailSubNewDown: '[サイト公開ステータス] 警告:サイトが DOWN しました。', + mailBodyNewDown: + '次のウェブサイトが DOWN しています:\n\n{{newDownList}}\n\n-----\nこの通知は次のGoogleスプレッドシートによって管理されています:\n{{spreadsheetUrl}}', + mailSubResolved: '[サイト公開ステータス] 復旧:サイトが復旧(UP)しました', + mailBodyResolved: + '次のウェブサイトが復旧しました:\n\n{{resolvedList}}\n\n-----\nこの通知は次のGoogleスプレッドシートによって管理されています:\n{{spreadsheetUrl}}', + alertMessageCompleteStatusCheck: + 'サイト公開ステータスの確認が完了しました。', + alertMessageCompleteStatusCheckAdd: + '\nステータスに変化があったサイトの情報は {{myEmail}} 宛にメールで通知されます。', + alertTitleCompleteStatusCheck: + '[サイト公開ステータス] 完了:ステータス確認', + mailSubErrorStatusCheck: '[サイト公開ステータス] エラー:ステータス確認', + mailBodyErrorStatusCheck: + '{{errorStack}}\n\n-----\nこの通知は次のGoogleスプレッドシートによって管理されています:\n{{spreadsheetUrl}}', + alertTitleError: 'エラー', + errorHeaderNameTargetUrlNotFound: + '列「{{headerNameTargetUrl}}」が見つかりません。シート「{{sheetNameDashboard}}」にて、ステータス確認対象のウェブサイトURLが記載された列のヘッダ名を確認してください。', + errorAddTriggerWillBeDeleted: + '\nこの処理のトリガーはいったん削除されます。エラーを解決した上で、再度トリガーを設定し直してください。', + errorInconsistencyInHeader: + 'ステータス確認のログファイルの間で、ヘッダ行に不整合があるようです。ヘッダ行が同一となるよう関係ファイルを編集・整形した上で、再度実行してください。', + alertMessageLogExtractionComplete: + '過去{{extractStatusLogDays}}日分のステータス確認ログがシート「{{sheetNameStatusLogsExtracted}}」に転記されました。', + alertMessageErrorInLogExtraction: + '[サイト公開ステータス] ログ抽出エラー:\n{{errorStack}}', + errorInvalidResponseCode: + '無効なレスポンスコード「{{code}}」が ALLOWED_RESPONSE_CODES または ERROR_RESPONSE_CODES にて指定されています。', + }, +}; + +class LocalizedMessage { + constructor(userLocale) { + this.DEFAULT_LOCALE = 'en'; + this.locale = MESSAGES[userLocale] ? userLocale : this.DEFAULT_LOCALE; + this.messageList = MESSAGES[this.locale]; + Object.keys(MESSAGES[this.DEFAULT_LOCALE]).forEach((key) => { + if (!this.messageList[key]) { + this.messageList[key] = MESSAGES[this.DEFAULT_LOCALE][key]; + } + }); + } + /** + * Replace placeholder values in the designated text. String.prototype.replace() is executed using regular expressions with the 'global' flag on. + * @param {String} text + * @param {Array} placeholderValues Array of objects containing a placeholder string expressed in regular expression and its corresponding value. + * @returns {String} The replaced text. + */ + replacePlaceholders_(text, placeholderValues = []) { + let replacedText = placeholderValues.reduce( + (acc, cur) => acc.replace(new RegExp(cur.regexp, 'g'), cur.value), + text + ); + return replacedText; + } + /** + * Replace placeholder string in this.messageList.errorInvalidFrequencyValue + * @param {String} frequencyKey + * @param {String} sheetNameOptions Name of options worksheet + * @returns {String} The replaced text. + */ + replaceErrorInvalidFrequencyValue(frequencyKey, sheetNameOptions) { + let text = this.messageList.errorInvalidFrequencyValue; + let placeholderValues = [ + { + regexp: '{{frequencyKey}}', + value: frequencyKey, + }, + { + regexp: '{{sheetNameOptions}}', + value: sheetNameOptions, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertMessageContinueTriggerSetup + * @param {String} functionDesc + * @param {String} myEmail + * @returns {String} The replaced text. + */ + replaceAlertMessageContinueTriggerSetup(functionDesc, myEmail) { + let text = this.messageList.alertMessageContinueTriggerSetup; + let placeholderValues = [ + { + regexp: '{{functionDesc}}', + value: functionDesc, + }, + { + regexp: '{{myEmail}}', + value: myEmail, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.errorInvalidFrequencyUnit + * @param {String} frequencyUnit + * @returns {String} The replaced text. + */ + replaceErrorInvalidFrequencyUnit(frequencyUnit) { + let text = this.messageList.errorInvalidFrequencyUnit; + let placeholderValues = [ + { + regexp: '{{frequencyUnit}}', + value: frequencyUnit, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertTitleCompleteTriggerSetup + * @param {String} handlerFunction + * @returns {String} The replaced text. + */ + replaceAlertTitleCompleteTriggerSetup(handlerFunction) { + let text = this.messageList.alertTitleCompleteTriggerSetup; + let placeholderValues = [ + { + regexp: '{{handlerFunction}}', + value: handlerFunction, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertMessageCompleteTriggerSetup + * @param {Number} frequency + * @param {String} frequencyUnit + * @returns {String} The replaced text. + */ + replaceAlertMessageCompleteTriggerSetup(frequency, frequencyUnit) { + let text = this.messageList.alertMessageCompleteTriggerSetup; + let placeholderValues = [ + { + regexp: '{{frequency}}', + value: frequency, + }, + { + regexp: '{{frequencyUnit}}', + value: frequencyUnit, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertMessageContinueTriggerDelete + * @param {String} myEmail + * @returns {String} The replaced text. + */ + replaceAlertMessageContinueTriggerDelete(myEmail) { + let text = this.messageList.alertMessageContinueTriggerDelete; + let placeholderValues = [ + { + regexp: '{{myEmail}}', + value: myEmail, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.mailBodyNewDown + * @param {String} newDownList + * @param {String} spreadsheetUrl + * @returns {String} The replaced text. + */ + replaceMailBodyNewDown(newDownList, spreadsheetUrl) { + let text = this.messageList.mailBodyNewDown; + let placeholderValues = [ + { + regexp: '{{newDownList}}', + value: newDownList, + }, + { + regexp: '{{spreadsheetUrl}}', + value: spreadsheetUrl, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.mailBodyResolved + * @param {String} resolvedList + * @param {String} spreadsheetUrl + * @returns {String} The replaced text. + */ + replaceMailBodyResolved(resolvedList, spreadsheetUrl) { + let text = this.messageList.mailBodyResolved; + let placeholderValues = [ + { + regexp: '{{resolvedList}}', + value: resolvedList, + }, + { + regexp: '{{spreadsheetUrl}}', + value: spreadsheetUrl, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertMessageCompleteStatusCheckAdd + * @param {String} myEmail + * @returns {String} The replaced text. + */ + replaceAlertMessageCompleteStatusCheckAdd(myEmail) { + let text = this.messageList.alertMessageCompleteStatusCheckAdd; + let placeholderValues = [ + { + regexp: '{{myEmail}}', + value: myEmail, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.mailBodyErrorStatusCheck + * @param {String} errorStack + * @param {String} spreadsheetUrl + * @returns {String} The replaced text. + */ + replaceMailBodyErrorStatusCheck(errorStack, spreadsheetUrl) { + let text = this.messageList.mailBodyErrorStatusCheck; + let placeholderValues = [ + { + regexp: '{{errorStack}}', + value: errorStack, + }, + { + regexp: '{{spreadsheetUrl}}', + value: spreadsheetUrl, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.errorHeaderNameTargetUrlNotFound + * @param {String} headerNameTargetUrl + * @param {String} sheetNameDashboard + * @returns {String} The replaced text. + */ + replaceErrorHeaderNameTargetUrlNotFound( + headerNameTargetUrl, + sheetNameDashboard + ) { + let text = this.messageList.errorHeaderNameTargetUrlNotFound; + let placeholderValues = [ + { + regexp: '{{headerNameTargetUrl}}', + value: headerNameTargetUrl, + }, + { + regexp: '{{sheetNameDashboard}}', + value: sheetNameDashboard, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertMessageLogExtractionComplete + * @param {Number} extractStatusLogDays + * @param {String} sheetNameStatusLogsExtracted + * @returns {String} The replaced text. + */ + replaceAlertMessageLogExtractionComplete( + extractStatusLogDays, + sheetNameStatusLogsExtracted + ) { + let text = this.messageList.alertMessageLogExtractionComplete; + let placeholderValues = [ + { + regexp: '{{extractStatusLogDays}}', + value: extractStatusLogDays, + }, + { + regexp: '{{sheetNameStatusLogsExtracted}}', + value: sheetNameStatusLogsExtracted, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.alertMessageErrorInLogExtraction + * @param {String} errorStack + * @returns {String} The replaced text. + */ + replaceAlertMessageErrorInLogExtraction(errorStack) { + let text = this.messageList.alertMessageErrorInLogExtraction; + let placeholderValues = [ + { + regexp: '{{errorStack}}', + value: errorStack, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } + /** + * Replace placeholder string in this.messageList.errorInvalidResponseCode + * @param {String} code + * @returns {String} The replaced text. + */ + replaceErrorInvalidResponseCode(code) { + let text = this.messageList.errorInvalidResponseCode; + let placeholderValues = [ + { + regexp: '{{code}}', + value: code, + }, + ]; + text = this.replacePlaceholders_(text, placeholderValues); + return text; + } +} diff --git a/src/websiteMonitoring.js b/src/websiteMonitoring.js index e630ea3..f058c8e 100644 --- a/src/websiteMonitoring.js +++ b/src/websiteMonitoring.js @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +/* global LocalizedMessage */ /* exported deleteTimeBasedTriggers, extractStatusLogsTriggered, @@ -39,21 +40,38 @@ const OPTIONS_CONVERT_TO_ARRAY_KEYS = [ ]; // Document property key(s) const DP_KEY_SAVED_STATUS = 'savedStatus'; +// Wildcard value for response codes, e.g., 30x +const RESPONSE_CODE_WILDCARD = 'x'; function onOpen() { + const localMessage = new LocalizedMessage( + SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetLocale() + ); const ui = SpreadsheetApp.getUi(); - ui.createMenu('Web Status') + ui.createMenu(localMessage.messageList.menuTitle) .addSubMenu( ui - .createMenu('Triggers') - .addItem('Set Status Check Trigger', 'setupStatusCheckTrigger') - .addItem('Set Log Extraction Trigger', 'setupLogExtractionTrigger') + .createMenu(localMessage.messageList.menuTriggers) + .addItem( + localMessage.messageList.menuSetStatusCheckTrigger, + 'setupStatusCheckTrigger' + ) + .addItem( + localMessage.messageList.menuSetLogExtractionTrigger, + 'setupLogExtractionTrigger' + ) .addSeparator() - .addItem('Delete Triggers', 'deleteTimeBasedTriggers') + .addItem( + localMessage.messageList.menuDeleteTriggers, + 'deleteTimeBasedTriggers' + ) ) .addSeparator() - .addItem('Check Status', 'websiteMonitoring') - .addItem('Extract Status Logs', 'extractStatusLogs') + .addItem(localMessage.messageList.menuCheckStatus, 'websiteMonitoring') + .addItem( + localMessage.messageList.menuExtractStatusLogs, + 'extractStatusLogs' + ) .addToUi(); } @@ -88,8 +106,10 @@ function setupLogExtractionTrigger() { function setupTrigger(handlerFunction, frequencyKey, frequencyUnit) { const ui = SpreadsheetApp.getUi(); const myEmail = Session.getActiveUser().getEmail(); + const ss = SpreadsheetApp.getActiveSpreadsheet(); + const localMessage = new LocalizedMessage(ss.getSpreadsheetLocale()); // Parse options data from spreadsheet - const optionsArr = SpreadsheetApp.getActiveSpreadsheet() + const optionsArr = ss .getSheetByName(SHEET_NAME_OPTIONS) .getDataRange() .getValues(); @@ -108,26 +128,30 @@ function setupTrigger(handlerFunction, frequencyKey, frequencyUnit) { !Number.isInteger(options[frequencyKey]) ) { throw new Error( - `Invalid ${frequencyKey} value. Check the "${SHEET_NAME_OPTIONS}" worksheet for its value.` + localMessage.replaceErrorInvalidFrequencyValue( + frequencyKey, + SHEET_NAME_OPTIONS + ) ); } // Brief descriptions of the handler functions const functionDesc = { - websiteMonitoringTriggered: ' to check website status', + websiteMonitoringTriggered: + localMessage.messageList.functionDescWebsiteMonitoringTriggered, extractStatusLogsTriggered: - ' to extract status logs into the managing spreadsheet', + localMessage.messageList.functionDescExtractStatusLogsTriggered, }; // Confirm the user if they want to continue with the trigger setup. - const continueAlert = `Setting up new trigger${ - functionDesc[handlerFunction] ? functionDesc[handlerFunction] : '' - }. \nThis process will delete existing trigger for this function that was set by ${myEmail}. Are you sure you want to continue?`; const continueResponse = ui.alert( - 'Trigger Setup', - continueAlert, + localMessage.messageList.alertTitleContinueTriggerSetup, + localMessage.replaceAlertMessageContinueTriggerSetup( + functionDesc[handlerFunction] ? functionDesc[handlerFunction] : '', + myEmail + ), ui.ButtonSet.YES_NO_CANCEL ); if (continueResponse !== ui.Button.YES) { - throw new Error('Trigger setup has been canceled.'); + throw new Error(localMessage.messageList.errorTriggerSetupCanceled); } // Delete existing trigger for this function set by the user. ScriptApp.getProjectTriggers().forEach((trigger) => { @@ -157,11 +181,16 @@ function setupTrigger(handlerFunction, frequencyKey, frequencyUnit) { .everyWeeks(options[frequencyKey]) .create(); } else { - throw new Error(`Invalid frequency unit: ${frequencyUnit}`); + throw new Error( + localMessage.replaceErrorInvalidFrequencyUnit(frequencyUnit) + ); } ui.alert( - `Complete (${handlerFunction})`, - `Trigger set at ${options[frequencyKey]}-${frequencyUnit} interval.`, + localMessage.replaceAlertTitleCompleteTriggerSetup(handlerFunction), + localMessage.replaceAlertMessageCompleteTriggerSetup( + options[frequencyKey], + frequencyUnit + ), ui.ButtonSet.OK ); } catch (e) { @@ -175,21 +204,27 @@ function setupTrigger(handlerFunction, frequencyKey, frequencyUnit) { function deleteTimeBasedTriggers() { const ui = SpreadsheetApp.getUi(); const myEmail = Session.getActiveUser().getEmail(); + const localMessage = new LocalizedMessage( + SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetLocale() + ); try { - const continueAlert = `Deleting all existing trigger(s) on this spreadsheet/script set by ${myEmail}. Are you sure you want to continue?`; const continueResponse = ui.alert( - 'Deleting All Triggers', - continueAlert, + localMessage.messageList.alertTitleContinueTriggerDelete, + localMessage.replaceAlertMessageContinueTriggerDelete(myEmail), ui.ButtonSet.YES_NO_CANCEL ); if (continueResponse !== ui.Button.YES) { - throw new Error('Trigger deletion has been canceled.'); + throw new Error(localMessage.messageList.errorTriggerDeleteCanceled); } // Delete all existing triggers set by the user. ScriptApp.getProjectTriggers().forEach((trigger) => ScriptApp.deleteTrigger(trigger) ); - ui.alert('Complete', `Trigger(s) deleted.`, ui.ButtonSet.OK); + ui.alert( + localMessage.messageList.alertTitleComplete, + localMessage.messageList.alertMessageTriggerDelete, + ui.ButtonSet.OK + ); } catch (e) { ui.alert(e.stack); } @@ -211,6 +246,8 @@ function websiteMonitoring(triggered = false) { const myEmail = Session.getActiveUser().getEmail(); const ss = SpreadsheetApp.getActiveSpreadsheet(); const timeZone = ss.getSpreadsheetTimeZone(); + const spreadsheetLocale = ss.getSpreadsheetLocale(); + const localMessage = new LocalizedMessage(spreadsheetLocale); const currentYear = Utilities.formatDate(new Date(), timeZone, 'yyyy'); const dp = PropertiesService.getDocumentProperties(); const savedStatus = dp.getProperty(DP_KEY_SAVED_STATUS) @@ -317,13 +354,17 @@ function websiteMonitoring(triggered = false) { try { // Replace wildcards in options.ALLOWED_RESPONSE_CODES and options.ERROR_RESPONSE_CODES to actual codes options.ALLOWED_RESPONSE_CODES = parseResponseCodes_( - options.ALLOWED_RESPONSE_CODES + options.ALLOWED_RESPONSE_CODES, + RESPONSE_CODE_WILDCARD, + spreadsheetLocale ); if (!options.ALLOWED_RESPONSE_CODES.includes('200')) { options.ALLOWED_RESPONSE_CODES.push('200'); } options.ERROR_RESPONSE_CODES = parseResponseCodes_( - options.ERROR_RESPONSE_CODES + options.ERROR_RESPONSE_CODES, + RESPONSE_CODE_WILDCARD, + spreadsheetLocale ); console.log(JSON.stringify(options)); ////////////////// // Get the actual HTTP response codes @@ -408,34 +449,38 @@ function websiteMonitoring(triggered = false) { if (statusChange.newErrors.length > 0) { MailApp.sendEmail( myEmail, - '[Website Status] Alert: Site DOWN', - `The following website(s) are DOWN:\n\n${statusChange.newErrors - .map( - (errorResponse) => - `Site Name: ${errorResponse.websiteName}\nURL: ${errorResponse.targetUrl}\nResponse Code: ${errorResponse.responseCode}\nResponse Time: ${errorResponse.responseTime}\n` - ) - .join( - '\n' - )}\n\n-----\nThis notice is managed by the following spreadsheet:\n${ss.getUrl()}` + localMessage.messageList.mailSubNewDown, + localMessage.replaceMailBodyNewDown( + statusChange.newErrors + .map( + (errorResponse) => + `Site Name: ${errorResponse.websiteName}\nURL: ${errorResponse.targetUrl}\nResponse Code: ${errorResponse.responseCode}\nResponse Time: ${errorResponse.responseTime}\n` + ) + .join('\n'), + ss.getUrl() + ) ); } if (statusChange.resolved.length > 0) { MailApp.sendEmail( myEmail, - '[Website Status] Notice: Site UP (Resolved)', - `The following website(s) that were DOWN are now UP:\n\n${statusChange.resolved - .map((resolvedResponse) => { - `Site Name: ${resolvedResponse.websiteName}\nURL: ${resolvedResponse.targetUrl}\nResponse Code: ${resolvedResponse.responseCode}\nResponse Time: ${resolvedResponse.responseTime}\n`; - }) - .join( - '\n' - )}\n\n-----\nThis notice is managed by the following spreadsheet:\n${ss.getUrl()}` + localMessage.messageList.mailSubResolved, + localMessage.replaceMailBodyResolved( + statusChange.resolved + .map((resolvedResponse) => { + `Site Name: ${resolvedResponse.websiteName}\nURL: ${resolvedResponse.targetUrl}\nResponse Code: ${resolvedResponse.responseCode}\nResponse Time: ${resolvedResponse.responseTime}\n`; + }) + .join('\n'), + ss.getUrl() + ) ); } // Log message - let completeMessage = 'Website status check is complete.'; + let completeMessage = + localMessage.messageList.alertMessageCompleteStatusCheck; if (statusChange.newErrors.length > 0 || statusChange.resolved.length > 0) { - completeMessage += `\nChanges to website status have been emailed to ${myEmail}`; + completeMessage += + localMessage.replaceAlertMessageCompleteStatusCheckAdd(myEmail); // Set a one-time trigger to update extracted status logs on the managing spreadsheet // that will fire 30 secs later. ScriptApp.newTrigger('extractStatusLogsTriggered') @@ -454,7 +499,7 @@ function websiteMonitoring(triggered = false) { if (!triggered) { // Show UI message, if triggered = false, i.e., this function is executed manually. ui.alert( - '[Website Status] Complete: Status Check', + localMessage.messageList.alertTitleCompleteStatusCheck, completeMessage, ui.ButtonSet.OK ); @@ -471,13 +516,15 @@ function websiteMonitoring(triggered = false) { ]); MailApp.sendEmail( myEmail, - '[Website Status] Error: Status Check', - `${ - e.stack - }\n\n-----\nThis notice is managed by the following spreadsheet:\n${ss.getUrl()}` + localMessage.messageList.mailSubErrorStatusCheck, + localMessage.replaceMailBodyErrorStatusCheck(e.stack, ss.getUrl()) ); if (!triggered) { - ui.alert('ERROR', e.stack, ui.ButtonSet.OK); + ui.alert( + localMessage.messageList.alertTitleError, + e.stack, + ui.ButtonSet.OK + ); } } } @@ -498,6 +545,7 @@ function extractStatusLogsTriggered() { */ function extractStatusLogs(triggered = false) { const ss = SpreadsheetApp.getActiveSpreadsheet(); + const localMessage = new LocalizedMessage(ss.getSpreadsheetLocale()); const timeZone = ss.getSpreadsheetTimeZone(); if (!triggered) { var ui = SpreadsheetApp.getUi(); @@ -524,9 +572,21 @@ function extractStatusLogs(triggered = false) { const targetWebsiteUrls = targetWebsitesArr.map((row) => { let urlIndex = targetWebsitesHeader.indexOf(HEADER_NAME_TARGET_URL); if (urlIndex < 0) { - throw new Error( - `"${HEADER_NAME_TARGET_URL}" is not found. Check the "${SHEET_NAME_DASHBOARD}" worksheet for the header name of the target websites\' URL` + let errorMessage = localMessage.replaceErrorHeaderNameTargetUrlNotFound( + HEADER_NAME_TARGET_URL, + SHEET_NAME_DASHBOARD ); + if (triggered === true) { + ScriptApp.getProjectTriggers().forEach((trigger) => { + if ( + ScriptApp.getHandlerFunction() === 'extractStatusLogsTriggered' + ) { + ScriptApp.deleteTrigger(trigger); + } + }); + errorMessage += localMessage.messageList.errorAddTriggerWillBeDeleted; + } + throw new Error(errorMessage); } return row[urlIndex]; }); @@ -600,9 +660,20 @@ function extractStatusLogs(triggered = false) { headersArr.forEach((headers) => { headers.forEach((header, i) => { if (header !== controlHeader[i]) { - throw new Error( - 'There seems to be an inconsistency in the header row between the status log files. Edit the header(s) so that they match and retry.' - ); + let errorMessage = + localMessage.messageList.errorInconsistencyInHeader; + if (triggered === true) { + ScriptApp.getProjectTriggers().forEach((trigger) => { + if ( + ScriptApp.getHandlerFunction() === 'extractStatusLogsTriggered' + ) { + ScriptApp.deleteTrigger(trigger); + } + }); + errorMessage += + localMessage.messageList.errorAddTriggerWillBeDeleted; + } + throw new Error(errorMessage); } }); }); @@ -612,12 +683,19 @@ function extractStatusLogs(triggered = false) { .getRange(1, 1, statusLogs.length, statusLogs[0].length) .setValues(statusLogs); if (!triggered) { - ui.alert('Complete', 'Status Log Extraction', ui.ButtonSet.OK); + ui.alert( + localMessage.messageList.alertTitleComplete, + localMessage.replaceAlertMessageLogExtractionComplete( + options.EXTRACT_STATUS_LOGS_DAYS, + SHEET_NAME_STATUS_LOGS_EXTRACTED + ), + ui.ButtonSet.OK + ); } } catch (e) { console.error(e.stack); if (!triggered) { - ui.alert(`[Website Status] Error: ${e.stack}`); + ui.alert(localMessage.replaceAlertMessageErrorInLogExtraction(e.stack)); } } } @@ -628,15 +706,19 @@ function extractStatusLogs(triggered = false) { * ["201", "300", "301", "302", "303", "304", "305", "306", "307", "308", "309"] * @param {Array} codes An array of HTTP response codes in strings. Wildcards can be used to replace digits. * @param {String} wildcard Placeholder value to denote the digits from 0 to 9. Defaults to "x". + * @param {String} locale Locale of the user or spreadsheet. Defaults to the user's locale settings. * @returns {Array} An array of replaced codes. */ -function parseResponseCodes_(codes, wildcard = 'x') { +function parseResponseCodes_( + codes, + wildcard = 'x', + locale = Session.getActiveUserLocale() +) { + let localMessage = new LocalizedMessage(locale); return codes .map((code) => { if (code.length !== 3 && code.length !== 0) { - throw new Error( - `Invalid response code "${code}" at ALLOWED_RESPONSE_CODES` - ); + throw new Error(localMessage.replaceErrorInvalidResponseCode(code)); } let parsedCodes = []; let remainingWildcard = 0; @@ -652,7 +734,7 @@ function parseResponseCodes_(codes, wildcard = 'x') { parsedCodes.push(code); } if (remainingWildcard > 0) { - parsedCodes = parseResponseCodes_(parsedCodes, wildcard); + parsedCodes = parseResponseCodes_(parsedCodes, wildcard, locale); } return parsedCodes.flat(); })