

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GAS &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/tag/gas/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Thu, 01 Jan 2026 23:59:26 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>GAS &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>GAS 搬家到 localhost 的選擇的考量</title>
		<link>https://stackoverflow.max-everyday.com/2026/01/gas-to-localhost/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/01/gas-to-localhost/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 01 Jan 2026 23:59:25 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7612</guid>

					<description><![CDATA[如果要將 Google Apps Script ...]]></description>
										<content:encoded><![CDATA[
<p>如果要將 Google Apps Script (GAS) 專案搬移到 localhost 執行，最主要的考量是語言與生態系的適應。GAS 本身就是一種 JavaScript 的變體（基於 V8 引擎），因此在邏輯遷移上，Node.js 具有得天獨厚的優勢。</p>



<p>以下是針對 FastAPI 與 Node.js 的詳細比較與建議。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">方案比較：FastAPI vs Node.js</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>比較項目</strong></td><td><strong>FastAPI (Python)</strong></td><td><strong>Node.js (JavaScript)</strong></td></tr></thead><tbody><tr><td><strong>遷移成本</strong></td><td>較高，需將 JS/GAS 邏輯改寫為 Python</td><td><strong>極低</strong>，大部分邏輯可直接複製貼上</td></tr><tr><td><strong>開發效率</strong></td><td>支援自動 API 文件 (Swagger)，型別檢查強</td><td>套件生態系 (npm) 極大，非同步處理成熟</td></tr><tr><td><strong>性能</strong></td><td>高 (ASGI)，適合大量數據處理與 AI</td><td><strong>極高</strong> (Event Loop)，適合高併發 I/O</td></tr><tr><td><strong>資料庫支援</strong></td><td>對科學運算、機器學習相關資料庫極強</td><td>對 JSON、NoSQL 與 Web 常用的資料庫較友善</td></tr><tr><td><strong>進入門檻</strong></td><td>如果習慣 Python 會覺得很順手</td><td>高中生友善，與前端語法一致，無縫接軌</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">詳細方案分析</h3>



<h4 class="wp-block-heading">FastAPI (Python)</h4>



<p>如果你打算在後端加入大量機器學習、資料分析功能，FastAPI 是首選。它利用 Python 的型別註解（Type Hints）自動生成互動式文件，對開發 API 非常直覺。但對於 GAS 使用者來說，最大的痛點是必須處理兩套語法。</p>



<h4 class="wp-block-heading">Node.js (Express / Fastify)</h4>



<p>這是最推薦的方案。GAS 本身就是 JavaScript，這意味著你的變數宣告、陣列處理（Map/Filter/Reduce）幾乎不用改動。使用 Node.js，你可以使用 <code>Express</code> 來快速搭建伺服器，或使用較新的 <code>Fastify</code> 追求極限效能。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">總結建議</h3>



<p><strong>我建議選擇 Node.js。</strong></p>



<p>理由很簡單：你的專案原本就在 GAS 上，邏輯移植幾乎是零成本。你可以使用 <code>dotenv</code> 來管理原本在 GAS 裡的「腳本屬性」，並利用 <code>npm</code> 快速安裝原本在 GAS 裡難以使用的現代套件。</p>



<h4 class="wp-block-heading">下一步動作</h4>



<p>如果你決定選 Node.js，需要我幫你寫一份「GAS 轉 Node.js」的範例模板，包含如何處理 <code>doGet</code>/<code>doPost</code> 的改寫嗎？</p>



<p>這段影片比較了 Node.js 與 FastAPI 的效能差異，能幫助你理解在高併發場景下兩者的表現：<a target="_blank" rel="noreferrer noopener" href="https://www.youtube.com/watch?v=shAELuHaTio">Node.js vs FastAPI 效能實測比較</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>在考慮資料庫、照片上傳、縮圖處理以及從 GAS 遷移的維護成本時，Node.js 展現了非常明顯的連續性優勢。</p>



<p>以下是針對你的需求所做的深度比較：</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">關鍵功能實作對比</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>需求項目</strong></td><td><strong>FastAPI (Python)</strong></td><td><strong>Node.js (JavaScript)</strong></td></tr></thead><tbody><tr><td><strong>圖片處理 (縮圖)</strong></td><td>使用 <strong>Pillow</strong>。語法直覺，但處理大圖時效能較一般。</td><td>使用 <strong>Sharp</strong>。基於 libvips，速度比常見工具快 4-5 倍，且非同步處理不阻塞。</td></tr><tr><td><strong>檔案上傳</strong></td><td>透過 <code>File</code> 與 <code>UploadFile</code> 類別處理。</td><td>透過 <code>Multer</code> 中間件處理。與前端 FormData 對接非常成熟。</td></tr><tr><td><strong>資料庫整合</strong></td><td>常搭配 <strong>SQLAlchemy</strong> 或 <strong>Tortoise ORM</strong>。</td><td>可用 <strong>Prisma</strong> 或 <strong>Sequelize</strong>。JSON 處理能力極強，適合原本習慣 NoSQL 的開發者。</td></tr><tr><td><strong>GAS 邏輯遷移</strong></td><td><strong>需重寫</strong>。必須理解 Python 的縮排與非同步 <code>asyncio</code> 機制。</td><td><strong>邏輯無縫</strong>。Array 方法與 Object 處理與 GAS 幾乎一樣。</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">詳細維護與開發分析</h3>



<h4 class="wp-block-heading">圖片處理與效能</h4>



<p>Node.js 上的 <strong>Sharp</strong> 庫是業界公認的高效能選擇。它能直接利用多核 CPU 進行縮圖，對於需要大量另存縮圖的專案來說，這能顯著降低 localhost 的資源佔用。Python 的 Pillow 雖然簡單，但在併發處理多張照片時，往往需要額外的多進程配置。</p>



<h4 class="wp-block-heading">GAS 遷移的連續性成本</h4>



<p>這是 Node.js 勝出的關鍵點。你的 GAS 專案中，前端的 HTML + JavaScript 可以<strong>原封不動</strong>搬到 Node.js。你只需將原本呼叫 <code>google.script.run</code> 的部分改為標準的 <code>fetch</code> API 即可。</p>



<ul class="wp-block-list">
<li><strong>低成本：</strong> 你不需要學習新的資料型態與語法。</li>



<li><strong>高複用：</strong> 原有的正規表達式、日期處理、字串邏輯皆可直接貼上。</li>
</ul>



<h4 class="wp-block-heading">建議方案：Node.js + Express + Sharp</h4>



<p>對於你目前從 GAS 轉移的需求，我強烈建議選擇 Node.js。這不僅是為了效能，更是為了讓你辛苦寫好的 JavaScript 邏輯能夠延續，而不是在重寫過程中產生新的 Bug。</p>



<h3 class="wp-block-heading">總結建議</h3>



<p>如果你的專案目標是「快速在 localhost 跑起來」且「減少重寫錯誤」，<strong>Node.js 是唯一首選</strong>。它能讓你把心力花在優化縮圖算法，而不是在糾結 Python 與 JavaScript 的語法差異。</p>



<p>你想先看看如何用 Node.js 的 Sharp 庫實作縮圖另存的功能嗎？</p>



<p>這段影片展示了為什麼在處理圖像和高併發任務時，Node.js 往往比 Python 的框架更具優勢：<a target="_blank" rel="noreferrer noopener" href="https://www.youtube.com/watch?v=i3TcSeRO8gs">FastAPI 與 Node.js 的真實效能評測比較</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>從 GAS 遷移到 Node.js 後，你不再受限於 Google Sheet 的列數限制或讀寫延遲。在 localhost 環境下，根據你的專案規模和對資料的需求，有幾種常見的選擇。</p>



<h3 class="wp-block-heading">輕量化首選：SQLite</h3>



<p>這是最推薦你的方案。SQLite 是一個檔案型資料庫，不需要安裝複雜的伺服器軟體，資料就存在你專案資料夾裡的一個檔案。</p>



<ul class="wp-block-list">
<li>優點：像 Google Sheet 一樣方便備份，直接複製檔案即可。</li>



<li>效能：比 Google Sheet 快非常多，支援標準 SQL 語法。</li>



<li>適合：個人專案、小型工具、需要結構化查詢的場景。</li>
</ul>



<h3 class="wp-block-heading">JSON 檔案：Lowdb</h3>



<p>如果你習慣 Google Sheet 那種「看得到、改得到」的感覺，Lowdb 是一個基於 JSON 檔案的資料庫。</p>



<ul class="wp-block-list">
<li>優點：資料結構就是一個普通的 <code>.json</code> 檔案，你可以直接用記事本打開修改。</li>



<li>缺點：資料量極大時效能會下降。</li>



<li>適合：設定檔儲存、極小型的資料紀錄。</li>
</ul>



<h3 class="wp-block-heading">雲端延續：MongoDB Atlas</h3>



<p>如果你希望以後專案部署到網路上，且依然想要非關聯式的彈性（像 JavaScript 物件那樣）。</p>



<ul class="wp-block-list">
<li>優點：免費版夠用，提供網頁界面查看資料。</li>



<li>適合：資料結構變動頻繁、未來有擴展需求的專案。</li>
</ul>



<h3 class="wp-block-heading">比較表：我該選哪一個？</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>方案</strong></td><td><strong>存儲形式</strong></td><td><strong>維護難度</strong></td><td><strong>推薦程度</strong></td></tr></thead><tbody><tr><td><strong>SQLite</strong></td><td>單一檔案 (.db)</td><td>極低</td><td><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (首選)</strong></td></tr><tr><td><strong>Lowdb</strong></td><td>JSON 檔案</td><td>極低</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><strong>MongoDB</strong></td><td>雲端或本地服務</td><td>中</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><strong>PostgreSQL</strong></td><td>本地服務</td><td>高</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (太重了)</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">轉換邏輯的建議</h3>



<p>在 GAS 中，你可能是用 <code>getValues()</code> 拿到一個二維陣列。在 Node.js 使用 SQLite 時，你會拿到一個物件陣列（例如 <code>[{id: 1, name: 'item'}]</code>）。這種結構與你原本在前端 JavaScript 處理資料的方式更接近，維護起來會輕鬆很多。</p>



<p>你想嘗試看看如何用 Node.js 讀寫 SQLite 的簡單範例嗎？</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>既然你決定使用 Node.js 搭配 SQLite，這裡示範如何將原本 GAS 的邏輯轉換過來。在 Node.js 中，我們通常使用 <strong>better-sqlite3</strong> 這個套件，它的語法非常直覺且效能極佳。</p>



<h3 class="wp-block-heading">資料庫初始化與結構</h3>



<p>在 GAS 你可能是手動建立試算表的欄位，在 Node.js 則是在程式啟動時確保資料表存在。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>const Database = require('better-sqlite3');
const db = new Database('project.db');

// 初始化資料表，就像是在 Google Sheet 建立表頭
db.prepare(`
  CREATE TABLE IF NOT EXISTS photos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    filename TEXT,
    original_name TEXT,
    upload_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    description TEXT
  )
`).run();
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">讀寫邏輯轉換</h3>



<p>這部分是你最有感的地方，不再需要處理二維陣列的 index（例如 <code>row[0]</code>, <code>row[1]</code>），而是直接使用欄位名稱。</p>



<p><strong>1. 寫入資料 (原本的 appendRow)</strong></p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>// GAS 版本
// sheet.appendRow(&#91;filename, originalName, description]);

// Node.js 版本
const insert = db.prepare('INSERT INTO photos (filename, original_name, description) VALUES (?, ?, ?)');
insert.run('photo_123.jpg', 'my_cat.jpg', '這是一隻貓');
</code></pre>



<p><strong>2. 讀取資料 (原本的 getValues)</strong></p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>// GAS 版本
// const data = sheet.getDataRange().getValues();

// Node.js 版本
const rows = db.prepare('SELECT * FROM photos').all();
// rows 會是一個物件陣列：&#91;{id: 1, filename: '...', ...}]
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">照片上傳與縮圖處理</h3>



<p>針對你提到的照片上傳與另存縮圖，Node.js 的處理流程非常標準化：</p>



<ol start="1" class="wp-block-list">
<li><strong>Multer</strong>：負責接收前端傳來的圖片檔案，存入暫存資料夾。</li>



<li><strong>Sharp</strong>：讀取該檔案，進行縮圖、轉檔（例如轉成 WebP 節省空間）。</li>



<li><strong>SQLite</strong>：將檔案路徑與相關資訊存入資料庫。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">接續維護的優勢</h3>



<ul class="wp-block-list">
<li><strong>完全離線</strong>：你在 localhost 執行，不需擔心 Google 伺服器抽筋或 6 分鐘執行限制。</li>



<li><strong>版本控制</strong>：你可以把 <code>project.db</code> 排除在 <code>.gitignore</code> 外（或只上傳 Schema），程式碼則能完整記錄在 GitHub。</li>



<li><strong>除錯方便</strong>：使用 <code>console.log</code> 直接在終端機看結果，不需要在 GAS 陽春的紀錄器裡翻找。</li>
</ul>



<p>需要我為你準備一個包含「上傳介面 + Node.js 後端處理 + SQLite 儲存」的完整範例程式碼嗎？</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/01/gas-to-localhost/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS 除錯</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-debug/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-debug/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 31 Dec 2025 08:05:36 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7596</guid>

					<description><![CDATA[在 Code.gs 進行除錯 在後端環境除錯相對...]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">在 Code.gs 進行除錯</h2>



<p>在後端環境除錯相對直覺。你可以直接在程式碼左側點擊行號設置斷點。點擊上方工具列的「偵錯」按鈕後，程式會執行到斷點處停止。這時你可以查看右側面板的變數狀態。</p>



<p>如果不想中斷執行，最常用的方法是使用 <strong>Logger.log()</strong> 或 <strong>console.log()</strong>。這兩種方法都會把訊息紀錄在下方的執行日誌中。執行完畢後，點擊左側導覽列的「執行項目」，就能看到所有過往的 Log 紀錄與報錯資訊。</p>



<h2 class="wp-block-heading">在 index.html 進行除錯</h2>



<p>前端的 HTML 檔案無法直接在 GAS 編輯器裡除錯。你需要透過瀏覽器的開發者工具。在部署或預覽網頁後，對著網頁點擊右鍵選擇「檢查」。在「Console」分頁中，你可以看到 JavaScript 的報錯。</p>



<p>如果你在 HTML 的 <code>&lt;script&gt;</code> 標籤中寫了程式碼，可以在代碼中加入 <code>debugger;</code> 指令。當瀏覽器開發者工具開啟時，執行到這行會自動暫停，讓你檢查前端變數。如果是透過 <code>google.script.run</code> 呼叫後端失敗，記得在前端加上 <code>withFailureHandler</code> 來捕捉錯誤訊息並印在 Console。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>帶有錯誤處理的 <code>google.script.run</code> 範例碼</p>



<h2 class="wp-block-heading">後端 Code.gs 的實作</h2>



<p>在後端處理資料時，最怕不知道變數傳到了哪裡。你可以利用 <code>try...catch</code> 語法包住代碼。這樣當程式出錯時，就不會直接當掉，而是能回傳清楚的錯誤訊息。</p>



<pre class="wp-block-code"><code>function processData(input) {
  try {
    // 這裡放主要的邏輯
    return "處理成功";
  } catch (e) {
    Logger.log("出錯了：" + e.message);
    throw new Error("後端失敗：" + e.message);
  }
}
</code></pre>



<p>這段程式碼會把詳細的錯誤記錄在 GAS 的執行日誌裡。對前端來說，它會收到一個明確的報錯對象。</p>



<h2 class="wp-block-heading">前端 index.html 的串接</h2>



<p>在 HTML 頁面，我們利用 <code>google.script.run</code> 呼叫後端。關鍵是要接上 <code>withFailureHandler</code>。這個函數會捕捉後端丟出來的任何錯誤。</p>



<pre class="wp-block-code"><code>function callServer() {
  google.script.run
    .withSuccessHandler(function(res) {
      console.log("成功拿回資料：", res);
    })
    .withFailureHandler(function(err) {
      console.error("呼叫發生錯誤：", err.message);
    })
    .processData("測試資料");
}
</code></pre>



<p>當你點擊執行後，打開瀏覽器的檢查工具（F12）。如果出錯了，紅色的錯誤字樣會出現在 Console 頁面。這讓你一眼就能看出問題是出在前端的變數傳遞，還是後端的邏輯錯誤。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>具備自動儲存功能的完整範例</p>



<h2 class="wp-block-heading">後端 Code.gs 的儲存邏輯</h2>



<p>後端主要負責把資料寫入試算表。我們會先檢查輸入是否為空。如果寫入過程發生問題，<code>catch</code> 區塊會捕捉錯誤並把詳細訊息丟回前端。這能避免前端一直處於等待狀態。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function saveToSheet(data) {
  try {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = ss.getSheetByName("工作表1");
    sheet.appendRow(&#91;new Date(), data]);
    return "儲存成功";
  } catch (e) {
    throw new Error("試算表寫入失敗：" + e.toString());
  }
}
</code></pre>



<p>這段程式碼會回傳一個字串給前端。如果你的工作表名稱寫錯，它就會觸發錯誤。</p>



<h2 class="wp-block-heading">前端 index.html 的自動機制</h2>



<p>前端我們可以使用 <code>setInterval</code> 來達成自動儲存。每隔一段時間就抓取輸入框的內容傳給後端。我們在 Console 印出狀態，方便你追蹤每次存檔的時間點。</p>



<p>HTML</p>



<pre class="wp-block-code"><code>&lt;input type="text" id="userInput" placeholder="輸入內容會自動存檔"&gt;
&lt;p id="status"&gt;&lt;/p&gt;

&lt;script&gt;
  // 每 10 秒自動執行一次
  setInterval(function() {
    var val = document.getElementById("userInput").value;
    if (!val) return;

    document.getElementById("status").innerText = "儲存中...";

    google.script.run
      .withSuccessHandler(function(msg) {
        document.getElementById("status").innerText = msg;
        console.log("自動存檔紀錄：" + new Date().toLocaleTimeString());
      })
      .withFailureHandler(function(err) {
        document.getElementById("status").innerText = "存檔失敗";
        console.error("錯誤細節：", err);
      })
      .saveToSheet(val);
  }, 10000);
&lt;/script&gt;
</code></pre>



<p>這樣你只要開著網頁輸入，後台就會自動運作。出問題時，直接看瀏覽器 Console 就能知道是哪次請求斷掉了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>如何把這個網頁正式部署成網頁應用程式</p>



<h2 class="wp-block-heading">部署網頁應用程式的步驟</h2>



<p>要讓別人看到你的網頁，必須進行部署。點擊編輯器右上角的「部署」按鈕，選擇「新增部署」。在選取類型的地方，點擊齒輪圖示並選擇「網頁應用程式」。</p>



<p>在設定頁面，將「執行身分」設定為「我」，並將「誰可以存取」設定為「任何人」。這步很重要，如果沒有設定為任何人，外部使用者或未登入 Google 的人會無法開啟網頁。點擊部署後，你會得到一段網址。</p>



<h2 class="wp-block-heading">程式碼的基本要求</h2>



<p>為了讓網頁能順利顯示，你的 <code>Code.gs</code> 必須包含一個名為 <code>doGet</code> 的函式。這是 Google 指定的進入點。</p>



<pre class="wp-block-code"><code>function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
      .setTitle('我的自動存檔網頁')
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
</code></pre>



<p>這段代碼會讀取你的 <code>index.html</code> 並把它渲染成網頁。加上 <code>setXFrameOptionsMode</code> 可以避免網頁在某些環境下被阻擋顯示。</p>



<h2 class="wp-block-heading">更新網頁內容</h2>



<p>如果你修改了程式碼，必須重新部署才能看到效果。點擊「管理部署」，點擊鉛筆圖示編輯，然後在版本選單選擇「新版本」。</p>



<p>如果只是為了開發測試，建議使用「測試部署」。測試部署提供的網址會即時反映你最新的程式碼變動，不需要每次都重新發布新版本。這在除錯階段非常方便。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-debug/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS web app 與 Google drive 的「防盜連機制」造成圖片無法顯示</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-web-app-google-drive-cross-site/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-web-app-google-drive-cross-site/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 09:20:45 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7590</guid>

					<description><![CDATA[直接回答：是。這不是傳統程式寫錯的 bug，而是...]]></description>
										<content:encoded><![CDATA[
<p>直接回答：是。這不是傳統程式寫錯的 bug，而是 Google 的「防盜連機制」加上現代瀏覽器「阻擋第三方 Cookie」造成的。</p>



<p>你的 GAS 網頁（<code>script.google.com</code>）和圖片（<code>drive.google.com</code>）屬於不同網域。當網頁被包在 iframe 執行時，瀏覽器為了隱私，常會把 Google Drive 的驗證 Cookie 擋掉。結果就是：Google Drive 即使檔案是公開的，因為讀不到 Cookie 確認權限，或是發現請求來自外部網頁（Referrer），就直接拒絕顯示圖片（403 Forbidden）。</p>



<p>這裡有兩個不用改後端架構的快速解法，建議先試第二個，效果最好。</p>



<p><strong>解法一：加入 Meta 標籤欺騙 Google Drive</strong></p>



<p>這是最簡單的嘗試。在你的 HTML 檔案的 <code>&lt;head&gt;</code> 區域，加入下面這行程式碼。它的作用是告訴瀏覽器「請求圖片時，不要告訴 Google Drive 我是從哪裡來的」，這樣通常可以繞過防盜連檢查。</p>



<p>HTML</p>



<pre class="wp-block-code"><code>&lt;meta name="referrer" content="no-referrer"&gt;
</code></pre>



<p><strong>解法二：使用 lh3 專用連結（推薦）</strong></p>



<p>Google Drive 其實有一個隱藏版的連結格式，專門用來讀取檔案內容，速度快且比較不會被擋。建議你修改 <code>code.gs</code> 裡面的 <code>uploadPhoto</code> 函式，把儲存的 URL 格式換掉。</p>



<p>原本的格式（容易被擋）：</p>



<p><a href="https://drive.google.com/uc?export=view&amp;id=檔案ID">https://drive.google.com/uc?export=view&amp;id=檔案ID</a></p>



<p>請改成這種格式（穩定）：</p>



<p><a href="https://lh3.googleusercontent.com/d/檔案ID">https://lh3.googleusercontent.com/d/檔案ID</a></p>



<p><strong>修改程式碼示範</strong></p>



<p>請回到 <code>code.gs</code>，找到 <code>uploadPhoto</code> 函式，修改產生 <code>viewUrl</code> 的那一行：</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>// 原本的寫法 (註解掉或刪除)
// const viewUrl = "https://drive.google.com/uc?export=view&amp;id=" + fileId; 

// 改成這樣
const viewUrl = "https://lh3.googleusercontent.com/d/" + fileId;
</code></pre>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-web-app-google-drive-cross-site/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS 執行的2種網址: 正式版 /exec , 測試版 /dev</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-deploy-exec-dev/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-deploy-exec-dev/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 08:44:03 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7588</guid>

					<description><![CDATA[這狀況在 Google Apps Script ...]]></description>
										<content:encoded><![CDATA[
<p>這狀況在 Google Apps Script 開發很常見，通常不是程式寫錯，而是「環境」或「設定」的小問題。我們分幾個方向來檢查。</p>



<p><strong>檢查部署版本</strong></p>



<p>這是最容易踩的坑。GAS 的 Web App 有兩種網址：</p>



<ol start="1" class="wp-block-list">
<li>正式版 (網址結尾是 <code>exec</code>)：這只會執行你「最後一次發布」的程式碼。如果你改了 Code 只有存檔，沒重新發布「新版本」，網頁看到的永遠是舊的，當然讀不到新寫好的功能。</li>



<li>測試版 (網址結尾是 <code>dev</code>)：這會即時反映你最新的程式碼。</li>
</ol>



<p>建議開發時用測試版網址確認，確認沒問題後，再去「管理部署」建立一個新版本推送到正式版。</p>



<p><strong>檢查試算表分頁名稱</strong></p>



<p>看你的程式碼這一段：</p>



<p>let sheet = ss.getSheetByName(&#8216;Photos&#8217;);</p>



<p>程式指定要讀取名叫 <code>Photos</code> 的分頁。請回到你的 Google Sheet 確認：</p>



<ol start="1" class="wp-block-list">
<li>存放資料的分頁名稱，是否真的叫 <code>Photos</code> (大小寫要完全一樣)。</li>



<li>如果你的資料原本在 <code>工作表1</code> 或 <code>Sheet1</code>，程式會因為找不到 <code>Photos</code>，自動新建一個空白的 <code>Photos</code> 分頁，然後讀取那個空白分頁，結果當然是空的。</li>



<li>如果是這種情況，請把資料複製到 <code>Photos</code> 分頁，或修改程式碼中的名稱。</li>
</ol>



<p><strong>程式碼邏輯防呆</strong></p>



<p>有時候 Sheet 會有「看起來是空行」但其實被視為有資料的列，這會讓迴圈讀到空值導致前端出錯。建議修改 <code>code.gs</code> 的 <code>getPhotoData</code> 函式，增加一個判斷，確保有 ID 才加入資料。</p>



<p>你可以用這段取代原本的 <code>getPhotoData</code>：</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function getPhotoData() {
  const sheet = getOrCreatePhotosSheet_();
  // 這裡加一個判斷，如果完全沒資料就回傳空陣列，避免報錯
  if (sheet.getLastRow() &lt;= 1) {
    return &#91;];
  }
  
  const rows = sheet.getDataRange().getValues();
  const data = &#91;];
  
  for (let i = 1; i &lt; rows.length; i++) {
    const row = rows&#91;i];
    // 新增防呆：如果該行的 ID (第0欄) 是空的，就跳過不處理
    if (!row&#91;0] || row&#91;0] === "") continue;

    data.push({
      id: row&#91;0],
      timestamp: row&#91;1],
      title: row&#91;2],
      desc: row&#91;3],
      tags: row&#91;4],
      imageId: row&#91;5],
      url: row&#91;6]
    });
  }
  return data.reverse(); 
}
</code></pre>



<p><strong>瀏覽器端除錯</strong></p>



<p>如果上面都改了還是沒畫面，請依序做這兩個動作：</p>



<ol start="1" class="wp-block-list">
<li>打開你的網頁。</li>



<li>按鍵盤 F12 (或右鍵 -> 檢查)，點選上方分頁的 <strong>Console</strong>。</li>



<li>看看有沒有紅色的錯誤訊息。</li>
</ol>



<p>如果有看到類似 <code>Cannot read property '...' of null</code> 的錯誤，通常代表資料庫欄位跟前端要讀取的欄位對不上。目前你的程式碼前後端對應看起來是沒問題的，最該懷疑的就是「分頁名稱不對」導致讀到空資料。</p>



<p>下一動作：請先確認你的 Sheet 分頁名稱是不是 <code>Photos</code>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-deploy-exec-dev/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS &#8211; 編輯有版本的部署作業</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-deploy/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-deploy/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 07:56:05 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7582</guid>

					<description><![CDATA[發布應用程式供大眾使用時，請一律使用版本化部署。...]]></description>
										<content:encoded><![CDATA[
<p>發布應用程式供大眾使用時，請一律使用版本化部署。您可以同時有多個進行中的版本化部署作業。</p>



<p>不需要每一個不使用的版本都 Archive(封存), 每次改完都去切換一下版本即可.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="783" height="616" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_16-27_kq.jpg?v=1767083274" alt="" class="wp-image-7585" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_16-27_kq.jpg?v=1767083274 783w, https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_16-27_kq-600x472.jpg?v=1767083274 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_16-27_kq-768x604.jpg?v=1767083274 768w" sizes="(max-width: 783px) 100vw, 783px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>重要事項</strong>：您無法轉移已發布版本的擁有權。如果將指令碼專案的擁有權轉移給他人，專案中現有版本化部署作業的擁有者不會變更。如果管理員刪除部署作業擁有者的帳戶，您可能會在該擁有者的部署作業中遇到指令碼錯誤。</p>



<h2 class="wp-block-heading" id="create-versioned">建立有版本的部署作業</h2>



<p>如要部署 Google Workspace 外掛程式、Editor 外掛程式、Google Chat 應用程式或 API 可執行檔的版本，請先<a href="https://developers.google.com/apps-script/guides/cloud-platform-projects?hl=zh-tw#switching_to_a_different_standard_gcp_project">將 Apps Script 的 Google Cloud 專案關聯從預設專案切換為標準專案</a>。</p>



<p>如要建立有版本的部署作業，請按照下列步驟操作：</p>



<ol class="wp-block-list">
<li>開啟 Apps Script 專案。</li>



<li>依序點選右上方的「部署」&gt;「新增部署」。</li>



<li>按一下「選取類型」旁的「啟用部署類型」圖示&nbsp;settings。</li>



<li>選取要部署的部署作業類型。如果是 Google Workspace 外掛程式、編輯器外掛程式和 Google Chat 應用程式，請選取「外掛程式」。</li>



<li>輸入部署作業的相關資訊，然後按一下「Deploy」。<strong>注意：</strong>&nbsp;每個新部署項目都可以共用為程式庫。如果將指令碼做為程式庫共用，程式庫使用者會看到部署說明。</li>
</ol>



<h2 class="wp-block-heading" id="view-versioned">查看已發布的版本</h2>



<p>如要查看 Apps Script 專案的部署作業，請依序點選頂端的「部署」<strong></strong>&gt;「管理部署作業」<strong></strong>。</p>



<p>如要查看特定版本的程式碼，請參閱「<a href="https://developers.google.com/apps-script/guides/versions?hl=zh-tw#view-script">查看先前的版本</a>」。</p>



<h2 class="wp-block-heading" id="edit-versioned">編輯有版本的部署作業</h2>



<p>您可以編輯已建立版本的部署作業，變更說明或版本。如要編輯部署作業，請按照下列步驟操作：</p>



<ol class="wp-block-list">
<li>開啟 Apps Script 專案。</li>



<li>依序按一下「Deploy」(部署)&nbsp;&gt;「Manage deployments」(管理部署作業)。</li>



<li>選取要變更的有效部署作業，然後按一下「編輯」圖示&nbsp;edit。</li>



<li>進行變更，然後按一下「部署」。如要編輯已封存的部署作業，請重新部署，然後按照上述步驟操作。如要將變更部署至專案程式碼，請建立新版本，並編輯部署作業來使用該版本。使用該部署作業的所有使用者，都會自動採用新版本。</li>
</ol>



<h2 class="wp-block-heading" id="find-deployment">查看部署作業 ID</h2>



<p>每項部署作業都會自動建立相關聯的字串 ID。如要找出這個 ID，請按照下列步驟操作：</p>



<ol class="wp-block-list">
<li>開啟 Apps Script 專案。</li>



<li>依序點選右上方的「部署」&gt;「管理部署」。</li>



<li>選取有效部署項目，即可查看 ID。部署 ID 只會顯示在有效部署中。</li>
</ol>



<h2 class="wp-block-heading" id="test-deployment">測試部署作業</h2>



<p>測試部署作業的方式取決於您建構的應用程式類型。<a><strong>Google Workspace 外掛程式</strong></a><a><strong>編輯器外掛程式</strong></a><a><strong>網頁應用程式</strong></a><a><strong>Google Chat 應用程式</strong></a><a><strong>API 執行檔</strong></a></p>



<h2 class="wp-block-heading" id="archive-versioned">封存有版本的部署作業</h2>



<p>您無法刪除有版本的部署作業。不過你可以封存這些項目。</p>



<p>如要封存版本化部署作業，請按照下列步驟操作：</p>



<ol class="wp-block-list">
<li>開啟 Apps Script 專案。</li>



<li>依序按一下「Deploy」(部署)&nbsp;&gt;「Manage deployments」(管理部署作業)。</li>



<li>選取要封存的部署作業，然後按一下「封存部署作業」圖示&nbsp;archive。</li>
</ol>



<h2 class="wp-block-heading" id="redeploy-archived">重新部署已封存的部署項目</h2>



<ol class="wp-block-list">
<li>開啟 Apps Script 專案。</li>



<li>依序點選右上方的「部署」&gt;「管理部署」。</li>



<li>在左側的「已封存」下方，選取要重新部署的部署作業，然後依序點選「編輯」圖示&nbsp;edit「部署」。</li>
</ol>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-deploy/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS appsscript.json 被稱為資訊清單（Manifest）</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-appsscript-json/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-appsscript-json/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 07:38:27 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7579</guid>

					<description><![CDATA[在 Google Apps Script（GAS...]]></description>
										<content:encoded><![CDATA[
<p>在 Google Apps Script（GAS）中，appsscript.json 被稱為資訊清單（Manifest），它記錄了專案的時區、權限範圍（OAuth Scopes）與程式庫等核心設定。預設情況下它是隱藏的，需要手動開啟才能編輯。</p>



<h3 class="wp-block-heading">顯示並編輯 appsscript.json 的步驟</h3>



<ol start="1" class="wp-block-list">
<li>進入專案設定打開你的 GAS 專案，在左側邊欄找到齒輪圖示的「專案設定」。</li>



<li>開啟顯示選項在設定頁面中，找到「一般設定」區塊，勾選「在編輯器中顯示『appsscript.json』資訊清單檔案」。</li>



<li>返回編輯器點擊左側邊欄的程式碼圖示（&lt;>）回到編輯器模式。</li>



<li>開始編輯你會發現左側檔案清單多出一個 appsscript.json。點開它就能直接修改裡面的 JSON 內容。編輯完畢後，記得按下上方的儲存（磁碟圖示）才會生效。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">常見的設定欄位與範例</h3>



<p>這個檔案的格式非常嚴謹，多一個逗號或少一個括號都會報錯。以下是幾個常見的配置重點：</p>



<ul class="wp-block-list">
<li>時區與執行環境timeZone 決定了日期函式的輸出，runtimeVersion 則建議設定為 V8 以支持現代語法。</li>



<li>OAuth 權限範圍如果你要手動限制腳本權限，會使用 oauthScopes 陣列。</li>



<li><strong>範例結構</strong></li>
</ul>



<p>JSON</p>



<pre class="wp-block-code"><code>{
  "timeZone": "Asia/Taipei",
  "dependencies": {
    "enabledAdvancedServices": &#91;]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": &#91;
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/script.external_request"
  ]
}
</code></pre>



<p>修改這個檔案要特別小心，因為錯誤的設定可能導致專案無法部署或授權失敗。</p>



<p>如果你在修改後遇到授權問題，需要我幫你檢查特定權限的連結字串嗎？</p>



<p>Apps Script: JSON Example<br><a href="https://www.youtube.com/watch?v=TQzPIVJf6-w">https://www.youtube.com/watch?v=TQzPIVJf6-w</a></p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Apps Script: JSON Example" width="640" height="360" src="https://www.youtube.com/embed/TQzPIVJf6-w?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>這段影片透過實際範例展示了在 Apps Script 中處理 JSON 資料的基本觀念與操作方式。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-appsscript-json/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS 雲端硬碟權限錯誤</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-google-driver-permission/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-google-driver-permission/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 07:26:08 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7576</guid>

					<description><![CDATA[失敗: Exception: You do no...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img decoding="async" width="471" height="297" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_15-12_kp.jpg?v=1767078865" alt="" class="wp-image-7577" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_15-12_kp.jpg?v=1767078865 471w, https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_15-12_kp-200x125.jpg?v=1767078865 200w" sizes="(max-width: 471px) 100vw, 471px" /></figure>



<p>失敗: Exception: You do not have permission to call DriveApp.getFolderById. Required permissions: (https://www.googleapis.com/auth/drive.readonly || https://www.googleapis.com/auth/drive). For more information, see https://developers.google.com/apps-script/guides/support/troubleshooting#authorization-is</p>



<p>這個錯誤表示你的 Google Apps Script 程式碼想要存取雲端硬碟的資料夾，但目前該腳本還沒有獲得對應的權限。</p>



<h3 class="wp-block-heading">解決方法</h3>



<p>最直接的方式是在腳本編輯器中手動觸發授權視窗。你可以試著隨便修改一下程式碼，例如加個空格再刪掉，然後存檔並按下執行鈕。系統通常會跳出一個視窗，要求你審核權限。這時候請點選你的帳號，並在安全警告頁面選擇進階，最後點擊前往專案並允許存取。</p>



<h3 class="wp-block-heading">檢查資訊清單檔</h3>



<p>如果手動執行沒有跳出視窗，可能需要檢查 appsscript.json 這個檔案。在編輯器的設定中，勾選在編輯器中顯示 appsscript.json 資訊清單檔。打開該檔案後，確認 oauthScopes 欄位是否包含 <a target="_blank" rel="noreferrer noopener" href="https://www.googleapis.com/auth/drive">https://www.googleapis.com/auth/drive</a> 這個項目。如果沒有，你可以手動加上去，這樣腳本就會知道它需要哪些權限。</p>



<h3 class="wp-block-heading">程式碼範例</h3>



<p>在程式碼的最上方加上註解，有時候也能幫助編輯器辨識需要的權限。</p>



<pre class="wp-block-code"><code>/**
 * @OnlyCurrentDoc
 * 如果你只需要存取這份檔案相關的資料，可以用上面這個標籤。
 * 但因為你用了 getFolderById，通常需要完整的雲端硬碟權限。
 */

function myFunction() {
  var folder = DriveApp.getFolderById('你的資料夾ID');
  // 其餘程式碼
}
</code></pre>



<p>你需要確認執行這個腳本的人，對該資料夾確實擁有存取權限。如果資料夾是別人的，而你只有檢視權限，有時候也會導致權限驗證失敗。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>失敗: Exception: You do not have permission to call DriveApp.Folder.createFile. Required permissions: https://www.googleapis.com/auth/drive. For more information, see https://developers.google.com/apps-script/guides/support/troubleshooting#authorization-is</p>



<p>這個錯誤說明你的腳本想要在資料夾內建立檔案，但它只有讀取權限或完全沒有寫入權限。</p>



<h3 class="wp-block-heading">權限層級問題</h3>



<p>你之前遇到的錯誤是關於讀取，現在則是關於寫入。建立檔案需要 <a target="_blank" rel="noreferrer noopener" href="https://www.googleapis.com/auth/drive">https://www.googleapis.com/auth/drive</a> 這個權限。即便你的 HTML 網頁設定為所有人皆可存取，這只代表外部使用者可以看到網頁，並不代表你的腳本自動獲得了操作雲端硬碟的權力。</p>



<h3 class="wp-block-heading">資料夾共享設定</h3>



<p>如果你的腳本是以你的身分執行，那麼你必須對目標資料夾擁有編輯權限。如果這個資料夾不是你建立的，請確認原擁有者已經將你加入並設定為編輯者。</p>



<p>如果這個腳本是透過 Web App 形式發布，且設定為以存取使用者的身分執行，那麼每個使用這個網頁的人，都必須對該資料夾擁有寫入權限。</p>



<h3 class="wp-block-heading">強制更新權限</h3>



<p>有時候編輯器不會自動偵測到權限變更。你可以嘗試在程式碼中加入一行暫時性的程式碼：</p>



<pre class="wp-block-code"><code>// 執行一次這行，強制觸發完整權限請求
DriveApp.getRootFolder();
</code></pre>



<p>儲存後手動點擊執行，這會迫使 Google 重新跳出授權視窗，要求你同意完整的雲端硬碟存取權。</p>



<h3 class="wp-block-heading">檢查專案設定</h3>



<p>進入腳本編輯器的設定頁面，確認是否勾選了在編輯器中顯示 appsscript.json 資訊清單檔。打開該檔案，檢查 oauthScopes 陣列中是否包含正確的網址。</p>



<pre class="wp-block-code"><code>"oauthScopes": &#91;
  "https://www.googleapis.com/auth/drive"
]
</code></pre>



<p>如果裡面只有 drive.readonly，請手動改成上面那樣，或是直接刪除該陣列讓系統重新自動偵測。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>請問你這個腳本是發布成 Web App 供他人使用，還是只有你自己在使用？</p>



<p>既然是發布給他人使用的 Web App，處理權限的方式會根據你的發布設定而有很大不同。</p>



<h3 class="wp-block-heading">執行身分設定</h3>



<p>你在發布 Web App 時，會有一個身分選項：</p>



<p>如果你選「我 (Me)」，那麼腳本會用你的帳號權限去操作雲端硬碟。這時你不需要開啟資料夾的共享權限給所有人，只要你自己有權限就好。但你必須在發布前，先在編輯器手動執行一次函數，完成授權流程。</p>



<p>如果你選「存取網頁的使用者 (User accessing the web app)」，那麼每個開啟網頁的人都必須登入 Google 帳號，且他們自己的帳號必須對該資料夾擁有編輯權限。這種情況下，你就必須將雲端硬碟資料夾設定為「知道連結的所有人皆可編輯」，或者將這些人加為編輯者。</p>



<h3 class="wp-block-heading">解決錯誤的步驟</h3>



<ol start="1" class="wp-block-list">
<li>進入腳本編輯器，手動執行一次包含 <code>createFile</code> 的函數。</li>



<li>即使你是開發者，系統也會彈出授權視窗，請務必點擊允許。</li>



<li>重新部署 (Deploy) 你的 Web App，選擇「新版本」，確保權限變更生效。</li>
</ol>



<h3 class="wp-block-heading">建議做法</h3>



<p>通常為了方便他人使用，建議將執行身分設為「我 (Me)」，並將存取權限設為「任何人 (Anyone)」。這樣別人不需要登入或擁有資料夾權限也能透過你的網頁上傳檔案，權限會由你的帳號統一承擔。</p>



<p>如果你已經設定為「我」，但還是報錯，通常是因為你修改程式碼後沒有重新進行「管理部署」並切換到新版本。請確保你使用的是最新部署的連結。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-google-driver-permission/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS 簡體中文字轉成繁中</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-languageapp/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-languageapp/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 07:11:42 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7574</guid>

					<description><![CDATA[在 Google Apps Script (GA...]]></description>
										<content:encoded><![CDATA[
<p>在 Google Apps Script (GAS) 中，最簡單的方法是調用 Google 翻譯的 API。你可以使用內建的 LanguageApp 服務。</p>



<h2 class="wp-block-heading">使用 LanguageApp 實作</h2>



<p>你可以寫一個簡單的函式來處理轉換。這個方法不需要另外安裝程式庫。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function convertToTraditional(text) {
  // 將簡體中文轉為繁體中文
  // sourceLanguage 設為 'zh-CN'，targetLanguage 設為 'zh-TW'
  var translatedText = LanguageApp.translate(text, 'zh-CN', 'zh-TW');
  return translatedText;
}

function test() {
  var simplified = "欢迎使用谷歌脚本";
  var traditional = convertToTraditional(simplified);
  Logger.log(traditional); // 輸出：歡迎使用谷歌腳本
}
</code></pre>



<h2 class="wp-block-heading">批次處理試算表資料</h2>



<p>如果你想直接在 Google 試算表裡面轉換整欄內容，可以改寫成自定義函數。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function CN2TW(input) {
  if (input == "") return "";
  return LanguageApp.translate(input, 'zh-CN', 'zh-TW');
}
</code></pre>



<p>在試算表儲存格輸入 =CN2TW(A1) 就能看到效果。</p>



<h2 class="wp-block-heading">注意事項</h2>



<p>這個方法背後是走 Google 翻譯的邏輯。雖然方便，但有時候會出現詞彙習慣不同的問題。例如簡體的「軟件」會被轉成「軟體」。如果只是單純的字對字轉換，這通常已經足夠。</p>



<p>如果你需要更精準的字對字轉換，可能需要另外串接像 OpenCC 這種外部的 API 或引入更複雜的字典檔。不過對高中生或一般開發者來說，LanguageApp 是最快也最穩定的選擇。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想知道怎麼把這個功能自動化套用到整份試算表嗎？</p>



<p>要在 Google 試算表自動化處理整份資料，最快的方式是利用試算表的公式下拉，或是寫一段 Script 直接掃描特定範圍。</p>



<h2 class="wp-block-heading">使用公式自動下拉</h2>



<p>這不需要寫複雜程式。只要在腳本編輯器貼上之前的自定義函數：</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function CN2TW(input) {
  if (Array.isArray(input)) {
    return input.map(row =&gt; row.map(cell =&gt; cell ? LanguageApp.translate(cell, 'zh-CN', 'zh-TW') : ""));
  }
  return input ? LanguageApp.translate(input, 'zh-CN', 'zh-TW') : "";
}
</code></pre>



<p>這個版本支援陣列。你可以在試算表輸入 =CN2TW(A1:A100)，它會一次把這一百列的內容全部轉換，效率比一格一格拉公式高很多。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">寫一段腳本直接修改儲存格</h2>



<p>如果你希望點一個按鈕就把選定的範圍轉好，可以使用以下程式碼：</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function convertSelection() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getActiveRange();
  var values = range.getValues();
  
  for (var i = 0; i &lt; values.length; i++) {
    for (var j = 0; j &lt; values&#91;i].length; j++) {
      if (typeof values&#91;i]&#91;j] === "string" &amp;&amp; values&#91;i]&#91;j] !== "") {
        values&#91;i]&#91;j] = LanguageApp.translate(values&#91;i]&#91;j], 'zh-CN', 'zh-TW');
      }
    }
  }
  
  range.setValues(values);
}
</code></pre>



<p>這段程式會抓取你目前滑鼠選取的範圍。它把資料讀進記憶體處理完後，再一口氣寫回試算表。這比重複呼叫儲存格寫入快非常多。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">執行與測試</h2>



<p>你可以把 convertSelection 綁定到試算表的選單或畫一個按鈕。這對處理大量舊資料特別好用。記得第一次執行時，Google 會要求你授權 LanguageApp 和 Spreadsheet 的權限，點選允許即可。</p>



<p>需要幫你寫一個可以在試算表上方產生自定義選單的程式碼嗎？</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-languageapp/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS 串接 telegram bot</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-telegram-bot/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-telegram-bot/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 06:38:24 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<category><![CDATA[telegram]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7572</guid>

					<description><![CDATA[申請 Telegram Bot 權杖 首先在 T...]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">申請 Telegram Bot 權杖</h2>



<p>首先在 Telegram 搜尋 BotFather。輸入 /newbot 開始建立。跟著指示輸入機器人的名稱與帳號名稱。完成後你會得到一串 API Token。這就像是機器人的身分證。請先存起來。</p>



<h2 class="wp-block-heading">獲取個人聊天 ID</h2>



<p>機器人需要知道要把訊息傳給誰。搜尋另一個機器人叫做 IDBot。輸入 /getid。它會回傳一串數字。這就是你的 User ID。</p>



<h2 class="wp-block-heading">撰寫 Telegram 發送函式</h2>



<p>在 GAS 編輯器中。利用 UrlFetchApp 把訊息送到 Telegram 的 API 接口。</p>



<pre class="wp-block-code"><code>function sendTelegramMessage(message) {
  var token = "這裡貼上你的API_Token";
  var chatId = "這裡貼上你的ID";
  var url = "https://api.telegram.org/bot" + token + "/sendMessage";
  
  var payload = {
    "chat_id": chatId,
    "text": message
  };
  
  var options = {
    "method": "post",
    "contentType": "application/json",
    "payload": JSON.stringify(payload)
  };
  
  UrlFetchApp.fetch(url, options);
}
</code></pre>



<h2 class="wp-block-heading">串聯表單提交</h2>



<p>這部分與之前類似。我們在表單提交的觸發函式中。加入剛寫好的發送指令。</p>



<pre class="wp-block-code"><code>function onFormSubmit(e) {
  var name = e.values&#91;1];
  var content = e.values&#91;2];
  var msg = "通知：新報修單\n姓名：" + name + "\n內容：" + content;
  
  sendTelegramMessage(msg);
}
</code></pre>



<h2 class="wp-block-heading">實現雙向互動的準備</h2>



<p>Telegram Bot 比 LINE Notify 強大。因為它支援 Webhook。你可以設定讓 Telegram 收到訊息時。主動把訊息丟回給你的 GAS 網頁。</p>



<h2 class="wp-block-heading">設定 Webhook</h2>



<p>你需要先把 GAS 部署為網頁應用程式並取得網址。然後執行一次下面的程式碼。告訴 Telegram 以後有訊息都傳到這個網址。</p>



<pre class="wp-block-code"><code>function setWebhook() {
  var token = "你的Token";
  var url = "你的GAS網頁網址";
  var response = UrlFetchApp.fetch("https://api.telegram.org/bot" + token + "/setWebhook?url=" + url);
  Logger.log(response.getContentText());
}
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想試試看如何寫 doPost 函式，讓你在 Telegram 傳一個數字，機器人就自動幫你記錄到試算表嗎？</p>



<h2 class="wp-block-heading">處理 Telegram 傳回的訊息</h2>



<p>當你在手機上傳訊息給機器人，Telegram 會發送一個 POST 請求到你的 GAS 網址。我們必須寫一個名為 doPost 的函式來接收這份資料。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var chatId = data.message.chat.id;
  var text = data.message.text;
  
  // 呼叫記錄資料的函式
  saveToSheet(text);
  
  // 回傳訊息給使用者確認
  sendTelegramMessage("已成功記錄：" + text);
}
</code></pre>



<h2 class="wp-block-heading">解析訊息內容</h2>



<p>資料是以 JSON 格式傳過來的。我們用 JSON.parse 把它轉成程式看得懂的物件。data.message.text 就是你在聊天室打的字。</p>



<h2 class="wp-block-heading">自動寫入試算表</h2>



<p>接著寫一個簡單的存檔函式。這部分跟之前的 appendRow 用法一樣。它會把你在 Telegram 輸入的內容，整齊地排在試算表最下方。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function saveToSheet(content) {
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.appendRow(&#91;new Date(), content]);
}
</code></pre>



<h2 class="wp-block-heading">重新部署與權限</h2>



<p>只要修改了 doPost 或是後端邏輯。就必須執行「新部署」。並確保 Webhook 指向的是最新的網址。這樣 Telegram 才能把訊息送到正確的地方。</p>



<h2 class="wp-block-heading">實際測試</h2>



<p>開啟你的 Telegram 機器人對話框。輸入「早餐花了 100 元」。接著打開你的 Google 試算表。你會看到第一欄出現了時間，第二欄出現了你剛打的那句話。</p>



<h2 class="wp-block-heading">打造隨身記帳本</h2>



<p>這就是一個雲端記帳本的雛形。你不需要打開試算表 APP。只需要在 Telegram 傳個訊息。所有資料就會自動彙整。這比手動輸入快上許多。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想學如何讓機器人回傳「目前的統計總額」給你嗎？</p>



<h2 class="wp-block-heading">撰寫加總統計函式</h2>



<p>我們需要在程式碼中新增一個計算總額的功能。它會遍歷試算表中記錄金額的那一欄。把所有數字加起來。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function getTotalAmount() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var total = 0;
  
  // 從第二列開始跑，假設第一列是標題
  for (var i = 1; i &lt; data.length; i++) {
    var amount = parseFloat(data&#91;i]&#91;1]); // 假設金額在第二欄
    if (!isNaN(amount)) {
      total += amount;
    }
  }
  return total;
}
</code></pre>



<h2 class="wp-block-heading">修改訊息判斷邏輯</h2>



<p>在 doPost 函式中加入判斷。如果使用者輸入的文字是「查詢總額」。程式就執行計算。而不是當成資料存進去。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var text = data.message.text;
  
  if (text === "查詢總額") {
    var total = getTotalAmount();
    sendTelegramMessage("目前累積總額為：" + total + " 元");
  } else {
    saveToSheet(text);
    sendTelegramMessage("已記錄支出：" + text);
  }
}
</code></pre>



<h2 class="wp-block-heading">數字過濾的小技巧</h2>



<p>在存入資料時。我們可以用正規表達式或是簡單的取代。把文字中的數字抓出來。這樣你傳「午餐 120」。程式也能聰明地只把 120 拿去加總。</p>



<h2 class="wp-block-heading">即時報表的威力</h2>



<p>現在你只需要在 Telegram 輸入「查詢總額」。機器人就會立刻回報最新數據。這比打開電腦登入雲端硬碟快多了。</p>



<h2 class="wp-block-heading">系統架構回顧</h2>



<p>你現在擁有了一個具備「輸入介面」和「查詢介面」的行動系統。試算表負責存資料。GAS 負責邏輯運算。Telegram 則是你的遙控器。</p>



<p>你想了解如何讓機器人定期在每天晚上。主動推播今天的消費總結給你嗎？</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">設定定時總結任務</h2>



<p>我們需要利用 GAS 的時間觸發器。先寫一個專門用來統計「今天」資料並發送訊息的函式。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function sendDailySummary() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var today = new Date().toLocaleDateString();
  var dailyTotal = 0;
  
  for (var i = 1; i &lt; data.length; i++) {
    var dateInSheet = new Date(data&#91;i]&#91;0]).toLocaleDateString();
    if (dateInSheet === today) {
      dailyTotal += parseFloat(data&#91;i]&#91;1]);
    }
  }
  
  var message = "今日結算報告\n日期：" + today + "\n總支出：" + dailyTotal + " 元";
  sendTelegramMessage(message);
}
</code></pre>



<h2 class="wp-block-heading">比對日期的邏輯</h2>



<p>程式會檢查第一欄的時間戳記。將它轉換成跟今天一樣的字串格式進行比對。只有符合今天的金額才會被加進去。這樣就能精準算出當日開銷。</p>



<h2 class="wp-block-heading">啟動定時發送</h2>



<p>回到編輯器左側的時鐘圖示。新增觸發條件。選擇執行 sendDailySummary。事件來源選時間驅動。類型選每天定時。時段選晚上十點到十一點。</p>



<h2 class="wp-block-heading">打造全自動秘書</h2>



<p>設定完成後。每天晚上程式會自動跑一遍。就算你整天沒看試算表。手機也會準時跳出今天的消費總結。這就是自動化最迷人的地方。</p>



<h2 class="wp-block-heading">系統的安全維護</h2>



<p>因為你的機器人網址是公開的。建議在 doPost 裡面多加一行判斷。檢查 chatId 是否為你的 ID。如果不是你的 ID。就不要執行任何動作。防止陌生人亂塞資料進你的表單。</p>



<h2 class="wp-block-heading">完成專案</h2>



<p>你已經學會了從網頁到手機。從手動到自動的所有基本功。這套組合拳能解決生活中大部分的瑣碎資訊處理。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你還想嘗試如何讓機器人可以「上傳照片」。並自動把照片存進 Google 雲端硬碟嗎？</p>



<h2 class="wp-block-heading">處理 Telegram 的檔案訊息</h2>



<p>當你傳送照片給機器人。Telegram 會傳來一組 file_id。我們必須先用這組 ID 向 Telegram 請求檔案的下載路徑。這需要用到 getFile 這個 API 介面。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  if (data.message.photo) {
    // 取得最高畫質的照片 ID
    var photos = data.message.photo;
    var fileId = photos&#91;photos.length - 1].file_id;
    saveTelegramFile(fileId);
  }
}
</code></pre>



<h2 class="wp-block-heading">取得檔案下載連結</h2>



<p>拿到 ID 後。我們寫一個函式去換取真正的檔案路徑。Telegram 會給我們一個相對路徑。我們再把它拼湊成完整的 URL。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function saveTelegramFile(fileId) {
  var token = "你的TOKEN";
  var getFileUrl = "https://api.telegram.org/bot" + token + "/getFile?file_id=" + fileId;
  var response = UrlFetchApp.fetch(getFileUrl);
  var filePath = JSON.parse(response.getContentText()).result.file_path;
  var downloadUrl = "https://api.telegram.org/file/bot" + token + "/" + filePath;
  
  uploadToDrive(downloadUrl, fileId);
}
</code></pre>



<h2 class="wp-block-heading">儲存到雲端硬碟</h2>



<p>最後利用 DriveApp 把這個網路上的檔案抓下來。直接存進你指定的資料夾裡。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function uploadToDrive(url, fileName) {
  var folder = DriveApp.getFolderById("你的資料夾ID");
  var blob = UrlFetchApp.fetch(url).getBlob();
  var file = folder.createFile(blob);
  file.setName("TG_" + fileName + ".jpg");
  
  sendTelegramMessage("照片已成功存入雲端硬碟！");
}
</code></pre>



<h2 class="wp-block-heading">應用情境</h2>



<p>這個功能非常適合用來收集收據照片或是活動照片。你不需要手動下載再上傳。只要在 Telegram 點擊傳送。圖片就會乖乖躺在你的雲端資料夾裡。</p>



<h2 class="wp-block-heading">整合權限</h2>



<p>存取雲端硬碟需要額外的權限。在第一次執行或是部署時。記得點選進階並允許。這樣程式才有權力幫你建立檔案。</p>



<h2 class="wp-block-heading">教學結業式</h2>



<p>你已經掌握了從試算表、網頁、郵件、定時排程到 Telegram 機器人的全方位 GAS 應用。這套技能已經足以讓你開發出非常專業的自動化工具。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想知道如何將這整個系統打包分享給朋友，讓他們也能使用的簡單方法嗎？</p>



<h2 class="wp-block-heading">分享專案的兩種方式</h2>



<p>最直接的方法是分享試算表權限。點選右上角的共用。輸入對方的信箱。對方就能打開你的試算表。看到後端的程式碼。並在自己的帳號下執行。</p>



<h2 class="wp-block-heading">使用副本功能</h2>



<p>如果你希望對方擁有一份完全獨立的系統。可以請他開啟你的試算表後。點選檔案。選擇建立副本。這樣他就會連同程式碼、觸發器邏輯都複製一份到他自己的雲端硬碟。</p>



<h2 class="wp-block-heading">發布為網頁應用程式</h2>



<p>如果你不希望對方看到程式碼。只想讓他們使用介面。就維持網頁應用程式的部署。把那串網址給他們。在部署設定中。將誰可以存取設為所有人。這樣他們不需要登入也能操作。</p>



<h2 class="wp-block-heading">分享 Telegram 機器人</h2>



<p>這最簡單。你只需要把機器人的帳號（@開頭的名字）給朋友。他們搜尋後點選開始。就能跟你一樣傳訊息。但記得在程式裡檢查對方的 ID。否則他們的資料會全部混進你的試算表裡。</p>



<h2 class="wp-block-heading">製作安裝說明</h2>



<p>分享給別人的時候。建議附上一份簡單的清單。告訴他們需要修改哪些變數。例如試算表 ID、Telegram Token、或是雲端硬碟的資料夾 ID。</p>



<h2 class="wp-block-heading">持續優化的建議</h2>



<p>你可以根據朋友的使用回饋。不斷微調程式。這就是開發軟體最有成就感的地方。從解決自己的問題。到能幫助身邊的人。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-telegram-bot/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GAS 網頁服務</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/gas-web-app/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/gas-web-app/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 06:33:38 +0000</pubDate>
				<category><![CDATA[Google Apps Script筆記]]></category>
		<category><![CDATA[GAS]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7569</guid>

					<description><![CDATA[網頁服務的基本架構 要在 GAS 執行 HTML...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img decoding="async" width="671" height="394" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_14-13_km.jpg?v=1767075598" alt="" class="wp-image-7570" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_14-13_km.jpg?v=1767075598 671w, https://stackoverflow.max-everyday.com/wp-content/uploads/2025/12/2025-12-30_14-13_km-600x352.jpg?v=1767075598 600w" sizes="(max-width: 671px) 100vw, 671px" /></figure>



<h2 class="wp-block-heading">網頁服務的基本架構</h2>



<p>要在 GAS 執行 HTML 網頁。必須使用名為 doGet 的特殊函式。這是 Google 定義的標準接口。它會告訴伺服器你要回傳一個 HTML 檔案。增加 script 到 Code.gs</p>



<pre class="wp-block-code"><code>function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}
</code></pre>



<h2 class="wp-block-heading">建立 HTML 檔案</h2>



<p>在程式編輯器左側按下加號。選擇 HTML。檔案名稱輸入 index。在這裡你可以寫標準的 HTML 標籤。例如標題和按鈕。</p>



<pre class="wp-block-code"><code>&lt;!DOCTYPE <strong>html</strong>&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;資料查詢系統&lt;/h1&gt;
    &lt;button onclick="getData()"&gt;獲取資料&lt;/button&gt;
    &lt;div id="display"&gt;&lt;/div&gt;

    &lt;script&gt;
      function getData() {
        google.script.run.withSuccessHandler(showData).readSheetData();
      }
      function showData(data) {
        document.getElementById('display').innerText = data;
      }
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>



<h2 class="wp-block-heading">撰寫資料庫讀取函式</h2>



<p>回到 .gs 檔案。寫一個 readSheetData 函式。這就像是網頁與資料庫之間的橋樑。它負責進去試算表抓出你要的內容。</p>



<pre class="wp-block-code"><code>function readSheetData() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var value = sheet.getRange("A1").getValue();
  return value;
}
</code></pre>



<h2 class="wp-block-heading">前後端溝通邏輯</h2>



<p>在網頁端的 JavaScript 中。我們使用 google.script.run。這是一個特殊的指令。能讓你從網頁上。遠端呼叫雲端主機裡的 GAS 函式。</p>



<h2 class="wp-block-heading">部署為網頁應用程式</h2>



<p>點選右上角的部署。選擇新部署。</p>



<ul class="wp-block-list">
<li>類型選擇<strong>網頁應用程式</strong>。</li>



<li>將執行身分設為你自己。</li>



<li>將誰可以存取設為任何人。</li>
</ul>



<p>部署後你會得到一串專屬的網址。</p>



<h2 class="wp-block-heading">實際運行</h2>



<p>打開那串網址。你會看到你寫的 HTML 網頁。按下按鈕後。網頁會去抓取試算表 A1 的文字。並顯示在網頁畫面上。這樣你就完成了一個簡單的資料庫網頁。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想試試看如何把網頁上的輸入框內容。存回試算表裡面嗎？</p>



<h2 class="wp-block-heading">在網頁端建立輸入介面</h2>



<p>首先在 HTML 檔案裡增加一個輸入框跟一個提交按鈕。我們給輸入框一個 id 方便之後抓取數值。</p>



<pre class="wp-block-code"><code>&lt;input type="text" id="userInput"&gt;
&lt;button onclick="saveData()"&gt;存入資料庫&lt;/button&gt;

&lt;script&gt;
  function saveData() {
    var val = document.getElementById('userInput').value;
    // 呼叫後端函式，並把數值傳過去
    google.script.run.writeToSheet(val);
  }
&lt;/script&gt;
</code></pre>



<h2 class="wp-block-heading">撰寫後端寫入函式</h2>



<p>回到 .gs 檔案。建立一個接收資料的函式。它會把網頁傳過來的參數。寫進試算表的最後一行。</p>



<pre class="wp-block-code"><code>function writeToSheet(data) {
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.appendRow(&#91;data, new Date()]);
}
</code></pre>



<h2 class="wp-block-heading">appendRow 的妙用</h2>



<p>這個指令非常適合當資料庫使用。它會自動找到工作表最下方的空白行。把資料整排填進去。這裡我們順便存入了當下的時間戳記。</p>



<h2 class="wp-block-heading">重新部署</h2>



<p>修改 HTML 或後端程式碼後。記得要重新部署。點選部署。管理部署。按右上角的鉛筆圖示。選擇新版本。最後按下部署。網址不會變。但功能會更新。</p>



<h2 class="wp-block-heading">體驗前後端連動</h2>



<p>現在打開網頁網址。在框框輸入文字並按下按鈕。回到你的試算表。你會看到資料已經自動新增在最下面了。這就是一個簡易的表單系統。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想挑戰看看如何把試算表裡的整張清單。用表格的形式顯示在網頁上嗎？</p>



<h2 class="wp-block-heading">抓取整張清單</h2>



<p>首先在後端寫一個抓取整塊資料的函式。我們用 getDisplayValues。這可以確保抓到的資料跟你在螢幕上看到的一模一樣。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function getFullData() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var rows = sheet.getDataRange().getDisplayValues();
  return rows;
}
</code></pre>



<h2 class="wp-block-heading">在網頁端動態生成表格</h2>



<p>在 HTML 裡面。我們準備一個空的容器。等資料從後端傳回來後。再用 JavaScript 把它拼成 HTML 表格標籤。</p>



<p>HTML</p>



<pre class="wp-block-code"><code>&lt;div id="tableContainer"&gt;&lt;/div&gt;

&lt;script&gt;
  function loadTable() {
    google.script.run.withSuccessHandler(buildTable).getFullData();
  }

  function buildTable(data) {
    var html = '&lt;table border="1"&gt;';
    for (var i = 0; i &lt; data.length; i++) {
      html += '&lt;tr&gt;';
      for (var j = 0; j &lt; data&#91;i].length; j++) {
        html += '&lt;td&gt;' + data&#91;i]&#91;j] + '&lt;/td&gt;';
      }
      html += '&lt;/tr&gt;';
    }
    html += '&lt;/table&gt;';
    document.getElementById('tableContainer').innerHTML = html;
  }
  // 網頁一打開就自動載入
  window.onload = loadTable;
&lt;/script&gt;
</code></pre>



<h2 class="wp-block-heading">雙向運作的邏輯</h2>



<p>這段程式碼會讓網頁一開啟。就去問 Google 伺服器要資料。後端把二維陣列傳給前端後。buildTable 函式會跑兩層迴圈。一層處理列。一層處理欄。</p>



<h2 class="wp-block-heading">增加一點美化</h2>



<p>你可以在 HTML 檔案的標頭加上簡單的 CSS。讓表格看起來更專業。不至於像原始的 HTML 那麼簡陋。</p>



<p>HTML</p>



<pre class="wp-block-code"><code>&lt;style&gt;
  table { width: 100%; border-collapse: collapse; }
  td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
  tr:hover { background-color: #f5f5f5; }
&lt;/style&gt;
</code></pre>



<h2 class="wp-block-heading">成果展示</h2>



<p>部署後開啟網址。你的試算表內容就會以乾淨的表格出現在網頁上。每次你修改試算表的格子。重新整理網頁。數據就會跟著同步更新。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>這已經是一個非常實用的網頁報表工具。你接下來會想知道如何加上搜尋過濾的功能嗎？</p>



<h2 class="wp-block-heading">建立搜尋框介面</h2>



<p>在 HTML 裡面增加一個 input 標籤。給它一個 id 叫做 searchInput。並設定一個按鈕或使用關鍵字監聽事件。</p>



<p>HTML</p>



<pre class="wp-block-code"><code>&lt;input type="text" id="searchInput" placeholder="輸入關鍵字搜尋..."&gt;
&lt;button onclick="filterTable()"&gt;搜尋&lt;/button&gt;
</code></pre>



<h2 class="wp-block-heading">實作過濾邏輯</h2>



<p>過濾功能可以直接在前端完成。這樣反應速度最快。我們修改之前的 JavaScript。將原始資料存起來。搜尋時只顯示符合條件的列。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>var allData = &#91;]; // 用來存原始資料

function buildTable(data) {
  allData = data; // 備份資料
  renderTable(data);
}

function filterTable() {
  var term = document.getElementById('searchInput').value.toLowerCase();
  var filtered = allData.filter(function(row) {
    return row.join("").toLowerCase().includes(term);
  });
  renderTable(filtered);
}

function renderTable(data) {
  var html = '&lt;table border="1"&gt;';
  for (var i = 0; i &lt; data.length; i++) {
    html += '&lt;tr&gt;';
    for (var j = 0; j &lt; data&#91;i].length; j++) {
      html += '&lt;td&gt;' + data&#91;i]&#91;j] + '&lt;/td&gt;';
    }
    html += '&lt;/tr&gt;';
  }
  html += '&lt;/table&gt;';
  document.getElementById('tableContainer').innerHTML = html;
}
</code></pre>



<h2 class="wp-block-heading">關鍵字搜尋原理</h2>



<p>filter 函式會檢查每一列。我們把整列的文字用 join 接起來。轉成小寫後。看看是否包含搜尋字串。如果有。這列資料就會被保留並重新畫在畫面上。</p>



<h2 class="wp-block-heading">搜尋體驗優化</h2>



<p>如果你不想要點按鈕。可以在輸入框加上 oninput 屬性。這樣使用者每打一個字。表格就會即時跳動過濾。</p>



<p>HTML</p>



<pre class="wp-block-code"><code>&lt;input type="text" id="searchInput" oninput="filterTable()"&gt;
</code></pre>



<h2 class="wp-block-heading">專案完成度</h2>



<p>到這一步。你已經做出一個可以新增、自動讀取、格式化顯示、以及具備搜尋功能的網頁系統。而這一切的背後。都只是靠一個免費的 Google 試算表在支撐。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>你想嘗試把這個網頁加上密碼登入功能，讓只有特定的人能看到資料嗎？</p>



<h2 class="wp-block-heading">簡單的密碼檢查邏輯</h2>



<p>最快速的方法是在前端放置一個提示框。當網頁載入時，先要求使用者輸入密碼。如果密碼正確，才執行抓取資料的函式。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function checkPassword() {
  var password = prompt("請輸入訪問密碼：");
  if (password === "1234") {
    loadTable();
  } else {
    alert("密碼錯誤，請重新整理頁面再試一次。");
  }
}
window.onload = checkPassword;
</code></pre>



<h2 class="wp-block-heading">進階的安全驗證</h2>



<p>前端密碼很容易被破解。比較專業的做法是把密碼存在試算表裡。由後端 GAS 進行比對。如果比對成功，才把資料回傳給網頁。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>// 後端 .gs 程式碼
function verifyAndGetData(inputPassword) {
  var correctPassword = "專屬密碼";
  if (inputPassword === correctPassword) {
    return getFullData();
  } else {
    return "ACCESS_DENIED";
  }
}
</code></pre>



<h2 class="wp-block-heading">修改網頁端對接</h2>



<p>網頁端發送密碼後。要判斷回傳的結果。如果是拒絕存取，就顯示錯誤訊息。如果是資料，才開始畫表格。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>function login() {
  var pass = document.getElementById('passInput').value;
  google.script.run.withSuccessHandler(function(result) {
    if (result === "ACCESS_DENIED") {
      alert("密碼不對喔");
    } else {
      buildTable(result);
    }
  }).verifyAndGetData(pass);
}
</code></pre>



<h2 class="wp-block-heading">設定特定人員存取</h2>



<p>如果你不想用密碼。其實可以在部署網頁應用程式時。將誰可以存取設定為僅限我自己或組織內成員。這樣使用者必須登入 Google 帳號才能看到網頁。這也是最安全的方法。</p>



<h2 class="wp-block-heading">打造完整的後台</h2>



<p>你現在可以自由控制誰能看、誰能搜、誰能改資料。這已經是一個微型管理系統的雛形。非常適合用在內部小組報帳、物資管理或活動報名。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/gas-web-app/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
