---
# System prepended metadata

title: Google Apps Script 能開發什麼？深入淺出完整應用指南

---

# Google Apps Script 能開發什麼？深入淺出完整應用指南

![Google Apps Script 自動化工作流程](https://hackmd.io/_uploads/rylsP7OJOge.jpg)

## 前言：一個改變工作效率的真實故事

小華是一名行銷專員，每週都要花費 3 小時整理客戶回饋資料：從 Gmail 收集客戶意見、整理到 Google Sheets、更新客戶資料庫、然後發送感謝信件。這個重複性工作不僅耗時，還容易出錯。

直到他發現了 Google Apps Script（GAS），一切都改變了。現在，這個過程完全自動化：當客戶回饋信件進入 Gmail 時，系統自動擷取內容、分類整理到試算表、更新客戶資料、甚至自動發送個人化的感謝信件。原本 3 小時的工作，現在只需要 5 分鐘檢查結果。

這就是 Google Apps Script 的威力 —— 讓你的 Google Workspace 變成一個強大的自動化工作站。

## 目錄

1. [什麼是 Google Apps Script？](#什麼是-google-apps-script)
2. [環境設置與入門](#環境設置與入門)
3. [六個實用開發範例](#六個實用開發範例)
   - [範例 1：自動發送 Gmail 郵件](#範例-1自動發送-gmail-郵件)
   - [範例 2：Google Sheets 資料處理與分析](#範例-2google-sheets-資料處理與分析)
   - [範例 3：Google Calendar 自動化排程](#範例-3google-calendar-自動化排程)
   - [範例 4：多服務串聯自動化](#範例-4多服務串聯自動化)
   - [範例 5：網頁爬蟲與資料蒐集](#範例-5網頁爬蟲與資料蒐集)
   - [範例 6：AI 服務整合（Gemini API）](#範例-6ai-服務整合gemini-api)
4. [性能優化技巧](#性能優化技巧)
5. [與其他自動化工具比較](#與其他自動化工具比較)
6. [2024-2025 最新趨勢](#2024-2025-最新趨勢)
7. [實用資源與學習路徑](#實用資源與學習路徑)
8. [總結與未來展望](#總結與未來展望)

## 什麼是 Google Apps Script？

Google Apps Script（GAS）是 Google 提供的雲端腳本平台，基於 JavaScript 語言，讓你能夠自動化和擴展 Google Workspace 應用程式的功能。簡單來說，它就像是 Google 服務之間的「膠水」，能夠串聯 Gmail、Google Sheets、Google Drive、Google Calendar 等服務。

![Google Apps Script 服務整合架構](https://hackmd.io/_uploads/rkxY7dkdxg.jpg)

### 核心特色

- **雲端執行**：無需安裝任何軟體，直接在瀏覽器中開發
- **深度整合**：與 Google Workspace 服務無縫整合
- **自動觸發**：支援時間觸發、事件觸發等多種執行方式
- **免費使用**：在合理使用範圍內完全免費
- **JavaScript 基礎**：使用熟悉的 JavaScript 語法

### 適用場景

- 報表自動化生成
- 郵件批量處理
- 資料同步與備份
- 工作流程自動化
- API 整合與資料擷取
- 自訂功能擴展

## 環境設置與入門

### 開始使用 Google Apps Script

1. **訪問 Google Apps Script**
   - 前往 [script.google.com](https://script.google.com)
   - 使用 Google 帳號登入

2. **建立新專案**
   - 點選「新增專案」
   - 選擇適當的專案名稱

3. **基本介面介紹**
   - 程式碼編輯器：撰寫 JavaScript 程式碼
   - 執行功能：測試和除錯程式碼
   - 觸發器管理：設定自動執行條件
   - 權限管理：管理 API 存取權限

### Hello World 範例

```javascript
/**
 * 第一個 Google Apps Script 程式
 * 功能：在日誌中顯示 "Hello, World!"
 */
function helloWorld() {
  // 使用 console.log 在執行日誌中顯示訊息
  console.log("Hello, World!");
  
  // 使用 Logger.log 也可以記錄訊息（舊版方法）
  Logger.log("這是我的第一個 GAS 程式！");
}
```

## 六個實用開發範例

### 範例 1：自動發送 Gmail 郵件

這個範例展示如何使用 GAS 自動發送個人化郵件，適合用於客戶通知、生日祝福、或定期報告發送。

```javascript
/**
 * 自動發送 Gmail 郵件
 * 功能：從 Google Sheets 讀取收件人資料，發送個人化郵件
 */
function sendAutomatedEmails() {
  // 開啟指定的 Google Sheets
  const spreadsheetId = 'YOUR_SPREADSHEET_ID';
  const sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet();
  
  // 取得資料範圍（假設 A 欄是姓名，B 欄是郵件地址，C 欄是狀態）
  const dataRange = sheet.getRange('A2:C');
  const data = dataRange.getValues();
  
  // 郵件模板設定
  const emailTemplate = {
    subject: '感謝您的支持 - 個人化通知',
    htmlBody: `
      <div style="font-family: Arial, sans-serif; max-width: 600px;">
        <h2 style="color: #4285f4;">親愛的 {{name}}，</h2>
        <p>感謝您一直以來的支持！</p>
        <p>這是一封透過 Google Apps Script 自動發送的個人化郵件。</p>
        <hr>
        <p style="color: #666; font-size: 12px;">
          此郵件由系統自動發送，請勿直接回覆。
        </p>
      </div>
    `
  };
  
  // 遍歷每一列資料
  data.forEach((row, index) => {
    const [name, email, status] = row;
    
    // 檢查是否已發送（避免重複發送）
    if (status !== '已發送' && email && name) {
      try {
        // 替換郵件模板中的個人化內容
        const personalizedBody = emailTemplate.htmlBody.replace('{{name}}', name);
        
        // 發送郵件
        GmailApp.sendEmail(
          email,
          emailTemplate.subject,
          '', // 純文字內容（可為空）
          {
            htmlBody: personalizedBody,
            name: '您的名稱或公司名稱' // 寄件者顯示名稱
          }
        );
        
        // 更新狀態為已發送
        sheet.getRange(index + 2, 3).setValue('已發送');
        console.log(`郵件已發送給：${name} (${email})`);
        
        // 加入延遲避免觸發 Gmail 限制
        Utilities.sleep(1000);
        
      } catch (error) {
        console.error(`發送郵件給 ${name} 時出錯：`, error);
        sheet.getRange(index + 2, 3).setValue('發送失敗');
      }
    }
  });
  
  console.log('批量郵件發送完成！');
}

/**
 * 設定定時觸發器
 * 每天上午 9 點自動執行郵件發送
 */
function createEmailTrigger() {
  ScriptApp.newTrigger('sendAutomatedEmails')
    .timeBased()
    .everyDays(1)
    .atHour(9)
    .create();
}
```

**使用步驟：**
1. 建立 Google Sheets，包含姓名、郵件地址、狀態三欄
2. 替換程式碼中的 `YOUR_SPREADSHEET_ID`
3. 執行 `createEmailTrigger()` 設定自動觸發
4. 測試 `sendAutomatedEmails()` 功能

### 範例 2：Google Sheets 資料處理與分析

這個範例展示如何使用 GAS 進行資料清理、分析和報表生成。

```javascript
/**
 * Google Sheets 資料處理與分析
 * 功能：自動清理資料、計算統計資料、生成報表
 */
function processSpreadsheetData() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const dataSheet = spreadsheet.getSheetByName('原始資料');
  const reportSheet = getOrCreateSheet(spreadsheet, '分析報表');
  
  // 取得原始資料
  const dataRange = dataSheet.getDataRange();
  const data = dataRange.getValues();
  const headers = data[0];
  const rows = data.slice(1);
  
  console.log(`開始處理 ${rows.length} 筆資料...`);
  
  // 資料清理
  const cleanedData = cleanData(rows);
  console.log(`資料清理完成，有效資料：${cleanedData.length} 筆`);
  
  // 生成分析報表
  generateReport(reportSheet, cleanedData, headers);
  
  console.log('資料處理與分析完成！');
}

/**
 * 資料清理函數
 * @param {Array} data - 原始資料陣列
 * @return {Array} - 清理後的資料
 */
function cleanData(data) {
  return data.filter(row => {
    // 移除空白列
    if (row.every(cell => !cell)) return false;
    
    // 移除無效的郵件地址（假設第二欄是郵件）
    const email = row[1];
    if (email && !isValidEmail(email)) return false;
    
    return true;
  }).map(row => {
    // 資料標準化
    return row.map(cell => {
      if (typeof cell === 'string') {
        // 移除多餘空白
        return cell.trim();
      }
      return cell;
    });
  });
}

/**
 * 郵件格式驗證
 * @param {string} email - 郵件地址
 * @return {boolean} - 是否為有效郵件
 */
function isValidEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

/**
 * 生成分析報表
 * @param {Sheet} sheet - 報表工作表
 * @param {Array} data - 清理後的資料
 * @param {Array} headers - 欄位標題
 */
function generateReport(sheet, data, headers) {
  // 清除現有內容
  sheet.clear();
  
  // 設定報表標題
  sheet.getRange('A1').setValue('資料分析報表');
  sheet.getRange('A1').setFontSize(16).setFontWeight('bold');
  
  // 基本統計資訊
  const stats = [
    ['生成時間', new Date()],
    ['總資料筆數', data.length],
    ['資料完整性', `${(data.length / (data.length + 100)) * 100}%`] // 假設統計
  ];
  
  // 寫入統計資訊
  let currentRow = 3;
  stats.forEach(([label, value]) => {
    sheet.getRange(currentRow, 1).setValue(label);
    sheet.getRange(currentRow, 2).setValue(value);
    currentRow++;
  });
  
  // 如果資料包含數值欄位，進行進階分析
  if (data.length > 0) {
    currentRow += 2;
    sheet.getRange(currentRow, 1).setValue('詳細分析');
    sheet.getRange(currentRow, 1).setFontWeight('bold');
    currentRow++;
    
    // 假設第三欄是數值資料進行分析
    const numericData = data
      .map(row => parseFloat(row[2]))
      .filter(num => !isNaN(num));
    
    if (numericData.length > 0) {
      const average = numericData.reduce((sum, num) => sum + num, 0) / numericData.length;
      const max = Math.max(...numericData);
      const min = Math.min(...numericData);
      
      const analyticsData = [
        ['平均值', average.toFixed(2)],
        ['最大值', max],
        ['最小值', min],
        ['資料範圍', `${min} - ${max}`]
      ];
      
      analyticsData.forEach(([label, value]) => {
        sheet.getRange(currentRow, 1).setValue(label);
        sheet.getRange(currentRow, 2).setValue(value);
        currentRow++;
      });
    }
  }
  
  // 美化報表格式
  formatReport(sheet, currentRow);
}

/**
 * 報表格式美化
 * @param {Sheet} sheet - 工作表
 * @param {number} lastRow - 最後一列
 */
function formatReport(sheet, lastRow) {
  // 設定欄寬
  sheet.autoResizeColumns(1, 2);
  
  // 設定邊框
  const range = sheet.getRange(1, 1, lastRow, 2);
  range.setBorder(true, true, true, true, true, true);
  
  // 設定交替列顏色
  for (let i = 3; i <= lastRow; i += 2) {
    sheet.getRange(i, 1, 1, 2).setBackground('#f8f9fa');
  }
}

/**
 * 取得或建立工作表
 * @param {Spreadsheet} spreadsheet - 試算表物件
 * @param {string} sheetName - 工作表名稱
 * @return {Sheet} - 工作表物件
 */
function getOrCreateSheet(spreadsheet, sheetName) {
  let sheet = spreadsheet.getSheetByName(sheetName);
  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
  }
  return sheet;
}

/**
 * 設定定期資料處理觸發器
 */
function setupDataProcessingTrigger() {
  // 每週一上午 8 點執行
  ScriptApp.newTrigger('processSpreadsheetData')
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(8)
    .create();
}
```

### 範例 3：Google Calendar 自動化排程

這個範例展示如何自動建立行事曆事件、管理會議室預約、發送提醒等功能。

```javascript
/**
 * Google Calendar 自動化排程
 * 功能：從 Sheets 讀取會議資料，自動建立行事曆事件
 */
function createCalendarEvents() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('會議安排');
  
  // 取得會議資料（假設格式：標題、開始時間、結束時間、參與者、地點、說明）
  const dataRange = sheet.getRange('A2:G');
  const meetings = dataRange.getValues();
  
  // 取得預設行事曆
  const calendar = CalendarApp.getDefaultCalendar();
  
  meetings.forEach((meeting, index) => {
    const [title, startTime, endTime, attendees, location, description, status] = meeting;
    
    // 跳過空白列或已處理的會議
    if (!title || status === '已建立') return;
    
    try {
      // 建立行事曆事件
      const event = calendar.createEvent(
        title,
        new Date(startTime),
        new Date(endTime),
        {
          description: description || '',
          location: location || '',
          guests: attendees || '',
          sendInvites: true // 自動發送邀請
        }
      );
      
      // 設定提醒
      event.addEmailReminder(30); // 30 分鐘前郵件提醒
      event.addPopupReminder(15);  // 15 分鐘前彈出提醒
      
      // 更新狀態
      sheet.getRange(index + 2, 7).setValue('已建立');
      
      console.log(`已建立會議：${title}`);
      
    } catch (error) {
      console.error(`建立會議 "${title}" 時出錯：`, error);
      sheet.getRange(index + 2, 7).setValue('建立失敗');
    }
  });
}

/**
 * 會議室可用性檢查
 * @param {string} roomEmail - 會議室郵件地址
 * @param {Date} startTime - 開始時間
 * @param {Date} endTime - 結束時間
 * @return {boolean} - 是否可用
 */
function checkRoomAvailability(roomEmail, startTime, endTime) {
  try {
    const calendar = CalendarApp.getCalendarById(roomEmail);
    if (!calendar) return false;
    
    const events = calendar.getEvents(startTime, endTime);
    return events.length === 0; // 沒有事件表示可用
    
  } catch (error) {
    console.error(`檢查會議室 ${roomEmail} 可用性時出錯：`, error);
    return false;
  }
}

/**
 * 智能會議室預約
 * 功能：自動尋找可用會議室並預約
 */
function smartRoomBooking() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const dataRange = sheet.getRange('A2:H');
  const meetings = dataRange.getValues();
  
  // 會議室清單（實際使用時請替換為真實的會議室郵件）
  const meetingRooms = [
    { name: '小會議室A', email: 'room-a@company.com', capacity: 6 },
    { name: '大會議室B', email: 'room-b@company.com', capacity: 12 },
    { name: '視訊會議室C', email: 'room-c@company.com', capacity: 8 }
  ];
  
  meetings.forEach((meeting, index) => {
    const [title, startTime, endTime, attendees, requiredCapacity, , , status] = meeting;
    
    if (!title || status === '已預約') return;
    
    const participantCount = attendees ? attendees.split(',').length : 1;
    const capacity = requiredCapacity || participantCount;
    
    // 尋找合適的會議室
    const availableRoom = findAvailableRoom(
      meetingRooms,
      new Date(startTime),
      new Date(endTime),
      capacity
    );
    
    if (availableRoom) {
      try {
        // 預約會議室
        const roomCalendar = CalendarApp.getCalendarById(availableRoom.email);
        roomCalendar.createEvent(
          `[預約] ${title}`,
          new Date(startTime),
          new Date(endTime),
          {
            description: `會議：${title}\n參與人數：${participantCount}`,
            guests: attendees || ''
          }
        );
        
        // 更新會議室資訊
        sheet.getRange(index + 2, 6).setValue(availableRoom.name);
        sheet.getRange(index + 2, 8).setValue('已預約');
        
        console.log(`已為 "${title}" 預約 ${availableRoom.name}`);
        
      } catch (error) {
        console.error(`預約會議室失敗：`, error);
        sheet.getRange(index + 2, 8).setValue('預約失敗');
      }
    } else {
      sheet.getRange(index + 2, 8).setValue('無可用會議室');
    }
  });
}

/**
 * 尋找可用會議室
 * @param {Array} rooms - 會議室清單
 * @param {Date} startTime - 開始時間
 * @param {Date} endTime - 結束時間
 * @param {number} requiredCapacity - 所需容量
 * @return {Object|null} - 可用的會議室或 null
 */
function findAvailableRoom(rooms, startTime, endTime, requiredCapacity) {
  // 按容量排序，優先使用剛好符合需求的會議室
  const suitableRooms = rooms
    .filter(room => room.capacity >= requiredCapacity)
    .sort((a, b) => a.capacity - b.capacity);
  
  for (const room of suitableRooms) {
    if (checkRoomAvailability(room.email, startTime, endTime)) {
      return room;
    }
  }
  
  return null;
}

/**
 * 會議提醒系統
 * 功能：在會議前一天發送提醒郵件
 */
function sendMeetingReminders() {
  const calendar = CalendarApp.getDefaultCalendar();
  const tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate() + 1);
  tomorrow.setHours(0, 0, 0, 0);
  
  const dayAfterTomorrow = new Date(tomorrow);
  dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1);
  
  // 取得明天的會議
  const events = calendar.getEvents(tomorrow, dayAfterTomorrow);
  
  events.forEach(event => {
    const attendees = event.getGuestList();
    if (attendees.length === 0) return;
    
    const reminderEmail = {
      subject: `會議提醒：${event.getTitle()}`,
      htmlBody: `
        <div style="font-family: Arial, sans-serif;">
          <h2>會議提醒</h2>
          <p><strong>會議主題：</strong>${event.getTitle()}</p>
          <p><strong>時間：</strong>${event.getStartTime().toLocaleDateString()} 
             ${event.getStartTime().toLocaleTimeString()} - 
             ${event.getEndTime().toLocaleTimeString()}</p>
          <p><strong>地點：</strong>${event.getLocation() || '未指定'}</p>
          <p><strong>說明：</strong></p>
          <p>${event.getDescription() || '無'}</p>
          <hr>
          <p style="color: #666; font-size: 12px;">
            此提醒由系統自動發送
          </p>
        </div>
      `
    };
    
    // 發送給所有參與者
    attendees.forEach(guest => {
      try {
        GmailApp.sendEmail(
          guest.getEmail(),
          reminderEmail.subject,
          '',
          { htmlBody: reminderEmail.htmlBody }
        );
      } catch (error) {
        console.error(`發送提醒給 ${guest.getEmail()} 失敗：`, error);
      }
    });
  });
  
  console.log(`已發送 ${events.length} 個會議的提醒`);
}
```

### 範例 4：多服務串聯自動化

這個範例展示如何整合多個 Google 服務，建立完整的工作流程自動化。

```javascript
/**
 * 多服務串聯自動化：客戶回饋處理系統
 * 整合 Gmail、Sheets、Drive、Calendar 的完整工作流程
 */

/**
 * 主要工作流程
 * 1. 監控 Gmail 新郵件
 * 2. 提取客戶回饋內容
 * 3. 分類並儲存到 Sheets
 * 4. 生成回覆郵件
 * 5. 安排後續追蹤行程
 * 6. 備份到 Drive
 */
function processCustomerFeedbackWorkflow() {
  console.log('開始執行客戶回饋處理工作流程...');
  
  // 步驟 1: 監控並處理新的客戶回饋郵件
  const newFeedback = monitorGmailFeedback();
  
  if (newFeedback.length === 0) {
    console.log('沒有新的客戶回饋郵件');
    return;
  }
  
  // 步驟 2: 處理每個回饋
  newFeedback.forEach(feedback => {
    try {
      // 步驟 3: 儲存到試算表
      const recordId = saveFeedbackToSheet(feedback);
      
      // 步驟 4: 分析情感並分類
      const analysis = analyzeFeedbackSentiment(feedback.body);
      updateFeedbackAnalysis(recordId, analysis);
      
      // 步驟 5: 生成並發送回覆
      if (analysis.requiresResponse) {
        sendAutoReply(feedback, analysis);
      }
      
      // 步驟 6: 安排後續追蹤
      if (analysis.priority === 'high') {
        scheduleFollowUp(feedback, analysis);
      }
      
      // 步驟 7: 標記郵件為已處理
      markEmailAsProcessed(feedback.thread);
      
      console.log(`成功處理來自 ${feedback.sender} 的回饋`);
      
    } catch (error) {
      console.error(`處理回饋時出錯：`, error);
    }
  });
  
  // 步驟 8: 生成日報表
  generateDailyReport();
  
  console.log('客戶回饋處理工作流程完成');
}

/**
 * 監控 Gmail 中的新客戶回饋
 * @return {Array} 新的回饋郵件陣列
 */
function monitorGmailFeedback() {
  // 搜尋特定標籤或主題的未讀郵件
  const searchQuery = 'label:customer-feedback is:unread';
  const threads = GmailApp.search(searchQuery, 0, 50);
  
  const feedbackEmails = [];
  
  threads.forEach(thread => {
    const messages = thread.getMessages();
    messages.forEach(message => {
      if (message.isUnread()) {
        feedbackEmails.push({
          thread: thread,
          message: message,
          sender: message.getFrom(),
          subject: message.getSubject(),
          body: message.getPlainBody(),
          receivedTime: message.getDate(),
          attachments: message.getAttachments()
        });
      }
    });
  });
  
  return feedbackEmails;
}

/**
 * 儲存回饋到 Google Sheets
 * @param {Object} feedback - 回饋物件
 * @return {string} 記錄 ID
 */
function saveFeedbackToSheet(feedback) {
  const sheet = getOrCreateFeedbackSheet();
  const recordId = Utilities.getUuid();
  
  const rowData = [
    recordId,
    feedback.receivedTime,
    feedback.sender,
    feedback.subject,
    feedback.body,
    '', // 情感分析結果
    '', // 優先級
    '', // 處理狀態
    '', // 回覆時間
    '', // 備註
  ];
  
  sheet.appendRow(rowData);
  return recordId;
}

/**
 * 取得或建立客戶回饋工作表
 * @return {Sheet} 工作表物件
 */
function getOrCreateFeedbackSheet() {
  const spreadsheet = SpreadsheetApp.openById('YOUR_FEEDBACK_SPREADSHEET_ID');
  let sheet = spreadsheet.getSheetByName('客戶回饋');
  
  if (!sheet) {
    sheet = spreadsheet.insertSheet('客戶回饋');
    // 設定標題列
    const headers = [
      '記錄ID', '接收時間', '寄件者', '主題', '內容',
      '情感分析', '優先級', '處理狀態', '回覆時間', '備註'
    ];
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
    sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');
  }
  
  return sheet;
}

/**
 * 分析回饋情感和優先級
 * @param {string} feedbackText - 回饋文本
 * @return {Object} 分析結果
 */
function analyzeFeedbackSentiment(feedbackText) {
  // 簡單的關鍵字分析（實際應用中可整合 AI API）
  const positiveKeywords = ['滿意', '好', '棒', '感謝', '讚', '優秀'];
  const negativeKeywords = ['不滿', '差', '爛', '問題', '錯誤', '投訴'];
  const urgentKeywords = ['緊急', '立即', '急', '重要', '嚴重'];
  
  const text = feedbackText.toLowerCase();
  
  let sentiment = 'neutral';
  let priority = 'normal';
  let requiresResponse = false;
  
  // 情感分析
  const positiveScore = positiveKeywords.filter(keyword => text.includes(keyword)).length;
  const negativeScore = negativeKeywords.filter(keyword => text.includes(keyword)).length;
  
  if (negativeScore > positiveScore) {
    sentiment = 'negative';
    requiresResponse = true;
  } else if (positiveScore > negativeScore) {
    sentiment = 'positive';
  }
  
  // 優先級判斷
  if (urgentKeywords.some(keyword => text.includes(keyword)) || sentiment === 'negative') {
    priority = 'high';
    requiresResponse = true;
  }
  
  return {
    sentiment,
    priority,
    requiresResponse,
    analysis: `情感：${sentiment}, 優先級：${priority}`
  };
}

/**
 * 更新回饋分析結果
 * @param {string} recordId - 記錄 ID
 * @param {Object} analysis - 分析結果
 */
function updateFeedbackAnalysis(recordId, analysis) {
  const sheet = getOrCreateFeedbackSheet();
  const data = sheet.getDataRange().getValues();
  
  // 尋找對應的記錄
  for (let i = 0; i < data.length; i++) {
    if (data[i][0] === recordId) {
      sheet.getRange(i + 1, 6).setValue(analysis.sentiment);
      sheet.getRange(i + 1, 7).setValue(analysis.priority);
      sheet.getRange(i + 1, 8).setValue('已分析');
      break;
    }
  }
}

/**
 * 發送自動回覆
 * @param {Object} feedback - 原始回饋
 * @param {Object} analysis - 分析結果
 */
function sendAutoReply(feedback, analysis) {
  let replyTemplate;
  
  if (analysis.sentiment === 'positive') {
    replyTemplate = `
      <div style="font-family: Arial, sans-serif;">
        <p>親愛的客戶，您好：</p>
        <p>感謝您給予我們正面的回饋！您的支持是我們持續改進的動力。</p>
        <p>如果您有任何其他建議，歡迎隨時與我們聯繫。</p>
        <br>
        <p>祝您順心！</p>
        <p>客服團隊</p>
      </div>
    `;
  } else if (analysis.sentiment === 'negative') {
    replyTemplate = `
      <div style="font-family: Arial, sans-serif;">
        <p>親愛的客戶，您好：</p>
        <p>非常感謝您的回饋，我們對造成您的不便深感抱歉。</p>
        <p>我們已將您的意見轉交給相關部門處理，會在 24 小時內有專人與您聯繫。</p>
        <p>再次為您帶來的困擾致歉，我們會持續改善服務品質。</p>
        <br>
        <p>祝您順心！</p>
        <p>客服團隊</p>
      </div>
    `;
  }
  
  if (replyTemplate) {
    feedback.thread.reply('', {
      htmlBody: replyTemplate,
      subject: `Re: ${feedback.subject}`
    });
    
    console.log(`已發送自動回覆給：${feedback.sender}`);
  }
}

/**
 * 安排後續追蹤行程
 * @param {Object} feedback - 回饋物件
 * @param {Object} analysis - 分析結果
 */
function scheduleFollowUp(feedback, analysis) {
  const calendar = CalendarApp.getDefaultCalendar();
  const followUpDate = new Date();
  followUpDate.setHours(followUpDate.getHours() + 24); // 24 小時後追蹤
  
  const endTime = new Date(followUpDate);
  endTime.setHours(endTime.getHours() + 1);
  
  calendar.createEvent(
    `追蹤客戶回饋：${feedback.sender}`,
    followUpDate,
    endTime,
    {
      description: `
        客戶：${feedback.sender}
        原始主題：${feedback.subject}
        優先級：${analysis.priority}
        情感：${analysis.sentiment}
        
        回饋內容：
        ${feedback.body.substring(0, 200)}...
      `,
      reminders: [
        { method: 'email', minutes: 60 },
        { method: 'popup', minutes: 15 }
      ]
    }
  );
  
  console.log(`已安排追蹤行程：${feedback.sender}`);
}

/**
 * 標記郵件為已處理
 * @param {GmailThread} thread - Gmail 對話串
 */
function markEmailAsProcessed(thread) {
  // 加上「已處理」標籤
  const processedLabel = GmailApp.getUserLabelByName('已處理') || 
                        GmailApp.createLabel('已處理');
  thread.addLabel(processedLabel);
  
  // 移除「customer-feedback」標籤以避免重複處理
  const feedbackLabel = GmailApp.getUserLabelByName('customer-feedback');
  if (feedbackLabel) {
    thread.removeLabel(feedbackLabel);
  }
  
  // 標記為已讀
  thread.markRead();
}

/**
 * 生成每日報表
 */
function generateDailyReport() {
  const sheet = getOrCreateFeedbackSheet();
  const today = new Date();
  const todayStr = Utilities.formatDate(today, Session.getScriptTimeZone(), 'yyyy-MM-dd');
  
  // 統計今日處理的回饋
  const data = sheet.getDataRange().getValues();
  const todayFeedback = data.filter(row => {
    if (row[1] instanceof Date) {
      const dateStr = Utilities.formatDate(row[1], Session.getScriptTimeZone(), 'yyyy-MM-dd');
      return dateStr === todayStr;
    }
    return false;
  });
  
  const stats = {
    total: todayFeedback.length,
    positive: todayFeedback.filter(row => row[5] === 'positive').length,
    negative: todayFeedback.filter(row => row[5] === 'negative').length,
    high_priority: todayFeedback.filter(row => row[6] === 'high').length
  };
  
  // 發送報表給管理者
  const reportEmail = `
    <div style="font-family: Arial, sans-serif;">
      <h2>客戶回饋日報表 - ${todayStr}</h2>
      <table border="1" style="border-collapse: collapse;">
        <tr><td>總計</td><td>${stats.total}</td></tr>
        <tr><td>正面回饋</td><td>${stats.positive}</td></tr>
        <tr><td>負面回饋</td><td>${stats.negative}</td></tr>
        <tr><td>高優先級</td><td>${stats.high_priority}</td></tr>
      </table>
      <p>詳細資料請查看 <a href="${sheet.getParent().getUrl()}">客戶回饋試算表</a></p>
    </div>
  `;
  
  GmailApp.sendEmail(
    'manager@company.com', // 請替換為實際的管理者郵件
    `客戶回饋日報表 - ${todayStr}`,
    '',
    { htmlBody: reportEmail }
  );
}

/**
 * 設定工作流程觸發器
 */
function setupWorkflowTriggers() {
  // 每 30 分鐘檢查一次新郵件
  ScriptApp.newTrigger('processCustomerFeedbackWorkflow')
    .timeBased()
    .everyMinutes(30)
    .create();
  
  // 每天下午 6 點生成日報表
  ScriptApp.newTrigger('generateDailyReport')
    .timeBased()
    .everyDays(1)
    .atHour(18)
    .create();
}
```

### 範例 5：網頁爬蟲與資料蒐集

這個範例展示如何使用 GAS 進行網頁資料擷取和 API 整合。

```javascript
/**
 * 網頁爬蟲與資料蒐集
 * 功能：自動擷取網站資料、API 整合、資料清理與儲存
 */

/**
 * 主要資料蒐集工作流程
 */
function dataCollectionWorkflow() {
  console.log('開始執行資料蒐集工作流程...');
  
  try {
    // 1. 蒐集新聞資料
    const newsData = collectNewsData();
    
    // 2. 蒐集股價資料
    const stockData = collectStockData();
    
    // 3. 蒐集天氣資料
    const weatherData = collectWeatherData();
    
    // 4. 整合資料並儲存
    const consolidatedData = {
      timestamp: new Date(),
      news: newsData,
      stocks: stockData,
      weather: weatherData
    };
    
    saveDataToSheet(consolidatedData);
    
    // 5. 生成分析報告
    generateAnalysisReport(consolidatedData);
    
    console.log('資料蒐集工作流程完成');
    
  } catch (error) {
    console.error('資料蒐集過程中發生錯誤：', error);
    sendErrorNotification(error);
  }
}

/**
 * 蒐集新聞資料
 * @return {Array} 新聞資料陣列
 */
function collectNewsData() {
  const newsUrls = [
    'https://jsonplaceholder.typicode.com/posts', // 示例 API
    // 實際使用時請替換為真實的新聞 API
  ];
  
  const allNewsData = [];
  
  newsUrls.forEach(url => {
    try {
      const response = UrlFetchApp.fetch(url, {
        method: 'GET',
        headers: {
          'User-Agent': 'Google Apps Script Data Collector',
          'Accept': 'application/json'
        },
        muteHttpExceptions: true
      });
      
      if (response.getResponseCode() === 200) {
        const data = JSON.parse(response.getContentText());
        
        // 資料處理和清理
        const processedData = data.slice(0, 5).map(item => ({
          id: item.id,
          title: item.title,
          content: item.body,
          source: 'JSON Placeholder',
          timestamp: new Date()
        }));
        
        allNewsData.push(...processedData);
      } else {
        console.error(`取得新聞資料失敗，HTTP 狀態碼：${response.getResponseCode()}`);
      }
      
    } catch (error) {
      console.error('蒐集新聞資料時出錯：', error);
    }
    
    // 避免過於頻繁的請求
    Utilities.sleep(1000);
  });
  
  console.log(`成功蒐集 ${allNewsData.length} 筆新聞資料`);
  return allNewsData;
}

/**
 * 蒐集股價資料
 * @return {Array} 股價資料陣列
 */
function collectStockData() {
  // 使用 Google Finance 函數或外部 API
  const stockSymbols = ['AAPL', 'GOOGL', 'MSFT', 'TSLA'];
  const stockData = [];
  
  stockSymbols.forEach(symbol => {
    try {
      // 這裡使用模擬資料，實際使用時可整合真實的股價 API
      const simulatedPrice = Math.random() * 1000 + 100;
      const simulatedChange = (Math.random() - 0.5) * 20;
      
      stockData.push({
        symbol: symbol,
        price: simulatedPrice.toFixed(2),
        change: simulatedChange.toFixed(2),
        changePercent: ((simulatedChange / simulatedPrice) * 100).toFixed(2),
        timestamp: new Date()
      });
      
      // 實際的股價 API 呼叫範例：
      /*
      const apiKey = 'YOUR_API_KEY';
      const url = `https://api.example.com/stock/${symbol}?apikey=${apiKey}`;
      const response = UrlFetchApp.fetch(url);
      const data = JSON.parse(response.getContentText());
      // 處理回傳的資料...
      */
      
    } catch (error) {
      console.error(`取得 ${symbol} 股價資料時出錯：`, error);
    }
  });
  
  console.log(`成功蒐集 ${stockData.length} 檔股票資料`);
  return stockData;
}

/**
 * 蒐集天氣資料
 * @return {Object} 天氣資料物件
 */
function collectWeatherData() {
  const cities = ['台北', '台中', '高雄'];
  const weatherData = {};
  
  cities.forEach(city => {
    try {
      // 這裡使用模擬資料，實際使用時可整合氣象 API
      weatherData[city] = {
        temperature: Math.floor(Math.random() * 15) + 20, // 20-35度
        humidity: Math.floor(Math.random() * 30) + 60,    // 60-90%
        condition: ['晴天', '多雲', '雨天'][Math.floor(Math.random() * 3)],
        timestamp: new Date()
      };
      
      // 實際的天氣 API 呼叫範例：
      /*
      const apiKey = 'YOUR_WEATHER_API_KEY';
      const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`;
      const response = UrlFetchApp.fetch(url);
      const data = JSON.parse(response.getContentText());
      // 處理回傳的資料...
      */
      
    } catch (error) {
      console.error(`取得 ${city} 天氣資料時出錯：`, error);
    }
  });
  
  console.log(`成功蒐集 ${Object.keys(weatherData).length} 個城市的天氣資料`);
  return weatherData;
}

/**
 * 網頁內容擷取器
 * @param {string} url - 目標網址
 * @return {string} 擷取的內容
 */
function scrapeWebContent(url) {
  try {
    const response = UrlFetchApp.fetch(url, {
      method: 'GET',
      headers: {
        'User-Agent': 'Mozilla/5.0 (compatible; Google Apps Script)'
      },
      followRedirects: true,
      muteHttpExceptions: true
    });
    
    if (response.getResponseCode() !== 200) {
      throw new Error(`HTTP 錯誤：${response.getResponseCode()}`);
    }
    
    const htmlContent = response.getContentText();
    
    // 簡單的 HTML 內容清理（移除標籤）
    const cleanContent = htmlContent
      .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // 移除 script
      .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')   // 移除 style
      .replace(/<[^>]*>/g, '')                                            // 移除 HTML 標籤
      .replace(/\s+/g, ' ')                                               // 合併空白字元
      .trim();
    
    return cleanContent;
    
  } catch (error) {
    console.error(`擷取網頁內容失敗（${url}）：`, error);
    return null;
  }
}

/**
 * RSS Feed 解析器
 * @param {string} rssUrl - RSS Feed URL
 * @return {Array} 解析後的文章陣列
 */
function parseRSSFeed(rssUrl) {
  try {
    const response = UrlFetchApp.fetch(rssUrl);
    const xmlContent = response.getContentText();
    
    // 使用正規表達式解析 RSS（簡化版本）
    const items = [];
    const itemRegex = /<item>(.*?)<\/item>/gs;
    const titleRegex = /<title><!\[CDATA\[(.*?)\]\]><\/title>|<title>(.*?)<\/title>/;
    const linkRegex = /<link>(.*?)<\/link>/;
    const descRegex = /<description><!\[CDATA\[(.*?)\]\]><\/description>|<description>(.*?)<\/description>/;
    const pubDateRegex = /<pubDate>(.*?)<\/pubDate>/;
    
    let match;
    while ((match = itemRegex.exec(xmlContent)) !== null) {
      const itemContent = match[1];
      
      const titleMatch = titleRegex.exec(itemContent);
      const linkMatch = linkRegex.exec(itemContent);
      const descMatch = descRegex.exec(itemContent);
      const dateMatch = pubDateRegex.exec(itemContent);
      
      if (titleMatch && linkMatch) {
        items.push({
          title: titleMatch[1] || titleMatch[2] || '',
          link: linkMatch[1] || '',
          description: descMatch ? (descMatch[1] || descMatch[2] || '') : '',
          pubDate: dateMatch ? new Date(dateMatch[1]) : new Date(),
          source: rssUrl
        });
      }
    }
    
    return items;
    
  } catch (error) {
    console.error(`解析 RSS Feed 失敗（${rssUrl}）：`, error);
    return [];
  }
}

/**
 * 儲存資料到 Google Sheets
 * @param {Object} data - 要儲存的資料物件
 */
function saveDataToSheet(data) {
  const spreadsheet = SpreadsheetApp.openById('YOUR_DATA_SPREADSHEET_ID');
  
  // 儲存新聞資料
  if (data.news && data.news.length > 0) {
    const newsSheet = getOrCreateSheet(spreadsheet, '新聞資料');
    data.news.forEach(news => {
      newsSheet.appendRow([
        news.timestamp,
        news.source,
        news.title,
        news.content,
        news.id
      ]);
    });
  }
  
  // 儲存股價資料
  if (data.stocks && data.stocks.length > 0) {
    const stockSheet = getOrCreateSheet(spreadsheet, '股價資料');
    data.stocks.forEach(stock => {
      stockSheet.appendRow([
        stock.timestamp,
        stock.symbol,
        stock.price,
        stock.change,
        stock.changePercent
      ]);
    });
  }
  
  // 儲存天氣資料
  if (data.weather) {
    const weatherSheet = getOrCreateSheet(spreadsheet, '天氣資料');
    Object.keys(data.weather).forEach(city => {
      const weather = data.weather[city];
      weatherSheet.appendRow([
        weather.timestamp,
        city,
        weather.temperature,
        weather.humidity,
        weather.condition
      ]);
    });
  }
  
  console.log('資料已成功儲存到試算表');
}

/**
 * 生成分析報告
 * @param {Object} data - 資料物件
 */
function generateAnalysisReport(data) {
  const reportContent = `
    <div style="font-family: Arial, sans-serif;">
      <h2>每日資料蒐集報告</h2>
      <p><strong>報告時間：</strong>${data.timestamp.toLocaleString()}</p>
      
      <h3>資料統計</h3>
      <ul>
        <li>新聞資料：${data.news ? data.news.length : 0} 筆</li>
        <li>股價資料：${data.stocks ? data.stocks.length : 0} 檔</li>
        <li>天氣資料：${data.weather ? Object.keys(data.weather).length : 0} 個城市</li>
      </ul>
      
      <h3>重點摘要</h3>
      ${generateSummary(data)}
      
      <hr>
      <p style="color: #666; font-size: 12px;">
        此報告由 Google Apps Script 自動生成
      </p>
    </div>
  `;
  
  // 發送報告郵件
  GmailApp.sendEmail(
    'data-team@company.com', // 請替換為實際郵件地址
    `每日資料蒐集報告 - ${Utilities.formatDate(data.timestamp, Session.getScriptTimeZone(), 'yyyy-MM-dd')}`,
    '',
    { htmlBody: reportContent }
  );
}

/**
 * 生成資料摘要
 * @param {Object} data - 資料物件
 * @return {string} HTML 格式的摘要
 */
function generateSummary(data) {
  let summary = '';
  
  // 股價摘要
  if (data.stocks && data.stocks.length > 0) {
    const gainers = data.stocks.filter(stock => parseFloat(stock.change) > 0);
    const losers = data.stocks.filter(stock => parseFloat(stock.change) < 0);
    
    summary += `
      <h4>股市概況</h4>
      <p>上漲：${gainers.length} 檔，下跌：${losers.length} 檔</p>
    `;
  }
  
  // 天氣摘要
  if (data.weather) {
    const temperatures = Object.values(data.weather).map(w => w.temperature);
    const avgTemp = temperatures.reduce((sum, temp) => sum + temp, 0) / temperatures.length;
    
    summary += `
      <h4>天氣概況</h4>
      <p>平均溫度：${avgTemp.toFixed(1)}°C</p>
    `;
  }
  
  return summary;
}

/**
 * 取得或建立工作表
 * @param {Spreadsheet} spreadsheet - 試算表物件
 * @param {string} sheetName - 工作表名稱
 * @return {Sheet} 工作表物件
 */
function getOrCreateSheet(spreadsheet, sheetName) {
  let sheet = spreadsheet.getSheetByName(sheetName);
  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
    
    // 根據不同類型設定標題列
    let headers = [];
    switch (sheetName) {
      case '新聞資料':
        headers = ['時間戳記', '來源', '標題', '內容', 'ID'];
        break;
      case '股價資料':
        headers = ['時間戳記', '股票代碼', '價格', '變動', '變動%'];
        break;
      case '天氣資料':
        headers = ['時間戳記', '城市', '溫度', '濕度', '天氣狀況'];
        break;
    }
    
    if (headers.length > 0) {
      sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
      sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');
    }
  }
  
  return sheet;
}

/**
 * 錯誤通知
 * @param {Error} error - 錯誤物件
 */
function sendErrorNotification(error) {
  const errorReport = `
    <div style="font-family: Arial, sans-serif;">
      <h2 style="color: red;">資料蒐集錯誤通知</h2>
      <p><strong>錯誤時間：</strong>${new Date().toLocaleString()}</p>
      <p><strong>錯誤訊息：</strong>${error.message}</p>
      <p><strong>錯誤堆疊：</strong></p>
      <pre style="background: #f5f5f5; padding: 10px;">${error.stack}</pre>
    </div>
  `;
  
  GmailApp.sendEmail(
    'admin@company.com', // 請替換為實際管理者郵件
    '資料蒐集系統錯誤通知',
    '',
    { htmlBody: errorReport }
  );
}

/**
 * 設定資料蒐集觸發器
 */
function setupDataCollectionTriggers() {
  // 每小時執行一次資料蒐集
  ScriptApp.newTrigger('dataCollectionWorkflow')
    .timeBased()
    .everyHours(1)
    .create();
}
```

### 範例 6：AI 服務整合（Gemini API）

![Google Apps Script 開發場景](https://hackmd.io/_uploads/rywIQu1ull.jpg)

這個範例展示如何整合最新的 Gemini AI 服務，實現智能內容生成和分析功能。

```javascript
/**
 * Google Apps Script 整合 Gemini AI
 * 功能：智能內容生成、文本分析、自動化決策
 */

// Gemini API 設定
const GEMINI_API_KEY = 'YOUR_GEMINI_API_KEY'; // 請到 Google AI Studio 取得 API Key
const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent';

/**
 * 智能郵件回覆系統
 * 使用 Gemini AI 分析郵件內容並生成個人化回覆
 */
function intelligentEmailReplySystem() {
  console.log('開始執行智能郵件回覆系統...');
  
  // 取得需要回覆的郵件
  const threads = GmailApp.search('label:needs-reply is:unread', 0, 10);
  
  threads.forEach(thread => {
    const messages = thread.getMessages();
    const latestMessage = messages[messages.length - 1];
    
    if (latestMessage.isUnread()) {
      try {
        // 分析郵件內容
        const emailAnalysis = analyzeEmailWithGemini(latestMessage);
        
        // 生成回覆
        const reply = generateReplyWithGemini(latestMessage, emailAnalysis);
        
        // 發送回覆（可選擇手動審核）
        if (emailAnalysis.confidence > 0.8) {
          thread.reply(reply.content, {
            htmlBody: reply.htmlContent
          });
          
          // 加上已回覆標籤
          const repliedLabel = GmailApp.getUserLabelByName('AI-已回覆') || 
                              GmailApp.createLabel('AI-已回覆');
          thread.addLabel(repliedLabel);
          
          console.log(`已自動回覆：${latestMessage.getSubject()}`);
        } else {
          // 信心度不足，標記為需要人工審核
          const reviewLabel = GmailApp.getUserLabelByName('需要審核') || 
                             GmailApp.createLabel('需要審核');
          thread.addLabel(reviewLabel);
          
          // 將建議回覆儲存到草稿
          saveSuggestedReplyToDraft(thread, reply);
          
          console.log(`已準備草稿回覆：${latestMessage.getSubject()}`);
        }
        
      } catch (error) {
        console.error(`處理郵件回覆時出錯：`, error);
      }
    }
  });
}

/**
 * 使用 Gemini AI 分析郵件內容
 * @param {GmailMessage} message - Gmail 訊息物件
 * @return {Object} 分析結果
 */
function analyzeEmailWithGemini(message) {
  const prompt = `
    請分析以下郵件內容，並回傳 JSON 格式的分析結果：
    
    寄件者：${message.getFrom()}
    主旨：${message.getSubject()}
    內容：${message.getPlainBody()}
    
    請分析：
    1. 郵件類型（inquiry、complaint、praise、request、other）
    2. 緊急程度（1-5，5為最緊急）
    3. 情感極性（positive、neutral、negative）
    4. 主要議題
    5. 建議的回覆語調（formal、friendly、apologetic、enthusiastic）
    6. AI回覆信心度（0-1，1為最有信心）
    
    請回傳格式如下的 JSON：
    {
      "type": "inquiry",
      "urgency": 3,
      "sentiment": "neutral",
      "topics": ["產品詢問", "價格"],
      "tone": "friendly",
      "confidence": 0.85
    }
  `;
  
  try {
    const response = callGeminiAPI(prompt);
    const analysisText = response.candidates[0].content.parts[0].text;
    
    // 提取 JSON 內容
    const jsonMatch = analysisText.match(/\{[\s\S]*\}/);
    if (jsonMatch) {
      return JSON.parse(jsonMatch[0]);
    } else {
      throw new Error('無法解析 AI 回應');
    }
    
  } catch (error) {
    console.error('郵件分析失敗：', error);
    return {
      type: 'other',
      urgency: 3,
      sentiment: 'neutral',
      topics: [],
      tone: 'friendly',
      confidence: 0.5
    };
  }
}

/**
 * 使用 Gemini AI 生成回覆內容
 * @param {GmailMessage} message - 原始郵件
 * @param {Object} analysis - 分析結果
 * @return {Object} 回覆內容
 */
function generateReplyWithGemini(message, analysis) {
  const prompt = `
    基於以下郵件和分析結果，生成一個專業的回覆：
    
    原始郵件：
    寄件者：${message.getFrom()}
    主旨：${message.getSubject()}
    內容：${message.getPlainBody()}
    
    分析結果：
    類型：${analysis.type}
    情感：${analysis.sentiment}
    語調：${analysis.tone}
    主要議題：${analysis.topics.join(', ')}
    
    請生成一個${analysis.tone}語調的回覆，內容應該：
    1. 回應所有提到的議題
    2. 保持專業但友善的態度
    3. 如果是投訴，要表達歉意並提供解決方案
    4. 如果是詢問，要提供有用的資訊
    5. 包含適當的結尾和簽名
    
    請同時提供純文字版本和 HTML 版本。
  `;
  
  try {
    const response = callGeminiAPI(prompt);
    const replyText = response.candidates[0].content.parts[0].text;
    
    // 簡單的 HTML 格式化
    const htmlContent = replyText
      .replace(/\n/g, '<br>')
      .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
    
    return {
      content: replyText,
      htmlContent: `<div style="font-family: Arial, sans-serif;">${htmlContent}</div>`
    };
    
  } catch (error) {
    console.error('生成回覆失敗：', error);
    return {
      content: '感謝您的來信，我們會儘快回覆您。',
      htmlContent: '<div style="font-family: Arial, sans-serif;">感謝您的來信，我們會儘快回覆您。</div>'
    };
  }
}

/**
 * 智能內容摘要工具
 * 自動摘要長篇文章或報告
 */
function intelligentContentSummarizer() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('待摘要內容');
  
  if (!sheet) {
    console.log('找不到"待摘要內容"工作表');
    return;
  }
  
  const data = sheet.getDataRange().getValues();
  const headers = data[0];
  const rows = data.slice(1);
  
  rows.forEach((row, index) => {
    const [title, content, summary, status] = row;
    
    // 跳過已處理的內容
    if (status === '已摘要' || !content) return;
    
    try {
      console.log(`正在摘要：${title}`);
      
      const prompt = `
        請為以下內容生成一個簡潔的摘要（150-200字）：
        
        標題：${title}
        內容：${content}
        
        摘要應該：
        1. 包含主要重點
        2. 保持客觀中立
        3. 使用繁體中文
        4. 結構清晰
      `;
      
      const response = callGeminiAPI(prompt);
      const summary = response.candidates[0].content.parts[0].text;
      
      // 更新摘要到試算表
      sheet.getRange(index + 2, 3).setValue(summary);
      sheet.getRange(index + 2, 4).setValue('已摘要');
      
      console.log(`完成摘要：${title}`);
      
      // 避免 API 限制
      Utilities.sleep(1000);
      
    } catch (error) {
      console.error(`摘要"${title}"時出錯：`, error);
      sheet.getRange(index + 2, 4).setValue('摘要失敗');
    }
  });
}

/**
 * 智能數據分析助手
 * 分析試算表數據並生成洞察報告
 */
function intelligentDataAnalyst() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const dataSheet = spreadsheet.getSheetByName('分析數據');
  
  if (!dataSheet) {
    console.log('找不到"分析數據"工作表');
    return;
  }
  
  // 取得數據
  const data = dataSheet.getDataRange().getValues();
  const headers = data[0];
  const rows = data.slice(1);
  
  // 準備數據摘要
  const dataStats = generateDataStatistics(data);
  
  const prompt = `
    請分析以下數據並生成洞察報告：
    
    數據欄位：${headers.join(', ')}
    數據筆數：${rows.length}
    
    統計摘要：
    ${JSON.stringify(dataStats, null, 2)}
    
    請提供：
    1. 數據概覽
    2. 主要趨勢和模式
    3. 異常值或有趣的發現  
    4. 商業洞察和建議
    5. 後續分析建議
    
    報告應該專業但易懂，適合商業決策參考。
  `;
  
  try {
    const response = callGeminiAPI(prompt);
    const analysisReport = response.candidates[0].content.parts[0].text;
    
    // 建立分析報告工作表
    const reportSheet = getOrCreateSheet(spreadsheet, 'AI分析報告');
    reportSheet.clear();
    
    // 寫入報告
    reportSheet.getRange('A1').setValue('AI 數據分析報告');
    reportSheet.getRange('A1').setFontSize(16).setFontWeight('bold');
    reportSheet.getRange('A3').setValue(analysisReport);
    reportSheet.getRange('A3').setWrap(true);
    
    // 美化格式
    reportSheet.autoResizeColumns(1, 1);
    reportSheet.setColumnWidth(1, 800);
    
    console.log('AI 數據分析報告已生成');
    
  } catch (error) {
    console.error('生成數據分析報告時出錯：', error);
  }
}

/**
 * 生成數據統計摘要
 * @param {Array} data - 數據陣列
 * @return {Object} 統計摘要
 */
function generateDataStatistics(data) {
  const headers = data[0];
  const rows = data.slice(1);
  const stats = {};
  
  headers.forEach((header, colIndex) => {
    const values = rows.map(row => row[colIndex]).filter(val => val !== '');
    
    if (values.length === 0) {
      stats[header] = { type: 'empty' };
      return;
    }
    
    // 檢查是否為數值欄位
    const numericValues = values.map(val => parseFloat(val)).filter(val => !isNaN(val));
    
    if (numericValues.length > values.length * 0.8) {
      // 數值欄位統計
      stats[header] = {
        type: 'numeric',
        count: numericValues.length,
        min: Math.min(...numericValues),
        max: Math.max(...numericValues),
        average: numericValues.reduce((sum, val) => sum + val, 0) / numericValues.length,
        median: getMedian(numericValues)
      };
    } else {
      // 文字欄位統計
      const uniqueValues = [...new Set(values)];
      const valueCounts = {};
      values.forEach(val => {
        valueCounts[val] = (valueCounts[val] || 0) + 1;
      });
      
      stats[header] = {
        type: 'text',
        count: values.length,
        unique: uniqueValues.length,
        mostCommon: Object.keys(valueCounts).reduce((a, b) => 
          valueCounts[a] > valueCounts[b] ? a : b
        )
      };
    }
  });
  
  return stats;
}

/**
 * 計算中位數
 * @param {Array} numbers - 數值陣列
 * @return {number} 中位數
 */
function getMedian(numbers) {
  const sorted = numbers.sort((a, b) => a - b);
  const middle = Math.floor(sorted.length / 2);
  
  if (sorted.length % 2 === 0) {
    return (sorted[middle - 1] + sorted[middle]) / 2;
  } else {
    return sorted[middle];
  }
}

/**
 * 呼叫 Gemini API
 * @param {string} prompt - 提示內容
 * @return {Object} API 回應
 */
function callGeminiAPI(prompt) {
  const payload = {
    contents: [{
      parts: [{
        text: prompt
      }]
    }],
    generationConfig: {
      temperature: 0.7,
      topK: 40,
      topP: 0.95,
      maxOutputTokens: 2048,
    }
  };
  
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
  
  const response = UrlFetchApp.fetch(`${GEMINI_API_URL}?key=${GEMINI_API_KEY}`, options);
  const responseCode = response.getResponseCode();
  
  if (responseCode !== 200) {
    throw new Error(`Gemini API 錯誤：${responseCode} - ${response.getContentText()}`);
  }
  
  return JSON.parse(response.getContentText());
}

/**
 * 將建議回覆儲存為草稿
 * @param {GmailThread} thread - Gmail 對話串
 * @param {Object} reply - 回覆內容
 */
function saveSuggestedReplyToDraft(thread, reply) {
  const draft = thread.createDraftReply(reply.content, {
    htmlBody: reply.htmlContent
  });
  
  console.log(`已建立草稿回覆：${thread.getFirstMessageSubject()}`);
}

/**
 * 取得或建立工作表
 * @param {Spreadsheet} spreadsheet - 試算表物件
 * @param {string} sheetName - 工作表名稱
 * @return {Sheet} 工作表物件
 */
function getOrCreateSheet(spreadsheet, sheetName) {
  let sheet = spreadsheet.getSheetByName(sheetName);
  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
  }
  return sheet;
}

/**
 * 設定 AI 功能觸發器
 */
function setupAITriggers() {
  // 每 2 小時檢查需要回覆的郵件
  ScriptApp.newTrigger('intelligentEmailReplySystem')
    .timeBased()
    .everyHours(2)
    .create();
  
  // 每天凌晨 2 點執行內容摘要
  ScriptApp.newTrigger('intelligentContentSummarizer')
    .timeBased()
    .everyDays(1)
    .atHour(2)
    .create();
}
```

## 性能優化技巧

在開發 Google Apps Script 應用時，性能優化是非常重要的考量。以下是一些實用的優化技巧：

### 1. 批量操作優化

```javascript
/**
 * 錯誤示範：逐一操作（效率低下）
 */
function inefficientMethod() {
  const sheet = SpreadsheetApp.getActiveSheet();
  
  // 每次操作都會觸發一次 API 呼叫
  for (let i = 1; i <= 100; i++) {
    sheet.getRange(i, 1).setValue(`資料 ${i}`);
    const value = sheet.getRange(i, 2).getValue();
    sheet.getRange(i, 3).setValue(value * 2);
  }
}

/**
 * 正確示範：批量操作（高效率）
 */
function efficientMethod() {
  const sheet = SpreadsheetApp.getActiveSheet();
  
  // 一次取得所有需要的資料
  const range = sheet.getRange(1, 1, 100, 3);
  const values = range.getValues();
  
  // 在記憶體中處理資料
  for (let i = 0; i < values.length; i++) {
    values[i][0] = `資料 ${i + 1}`;
    values[i][2] = values[i][1] * 2;
  }
  
  // 一次寫入所有資料
  range.setValues(values);
}
```

### 2. 快取機制

```javascript
/**
 * 使用快取減少重複計算和 API 呼叫
 */
function useCache() {
  const cache = CacheService.getScriptCache();
  const cacheKey = 'expensive_calculation_result';
  
  // 嘗試從快取取得結果
  let result = cache.get(cacheKey);
  
  if (result === null) {
    // 快取中沒有資料，執行計算
    console.log('從快取中未找到資料，執行計算...');
    result = performExpensiveCalculation();
    
    // 將結果儲存到快取（有效時間 10 分鐘）
    cache.put(cacheKey, result, 600);
  } else {
    console.log('從快取中取得資料');
  }
  
  return result;
}

function performExpensiveCalculation() {
  // 模擬耗時的計算
  Utilities.sleep(2000);
  return Math.random().toString();
}
```

### 3. 錯誤處理與重試機制

```javascript
/**
 * 強化的錯誤處理和重試機制
 */
function robustOperation() {
  const maxRetries = 3;
  let retryCount = 0;
  
  while (retryCount < maxRetries) {
    try {
      // 執行可能失敗的操作
      const result = riskyOperation();
      return result;
      
    } catch (error) {
      retryCount++;
      console.error(`操作失敗（第 ${retryCount} 次嘗試）：`, error.message);
      
      if (retryCount >= maxRetries) {
        // 達到最大重試次數，記錄錯誤並處理
        console.error('操作最終失敗，已達到最大重試次數');
        handleFinalError(error);
        throw error;
      }
      
      // 等待後重試（指數退避）
      const waitTime = Math.pow(2, retryCount) * 1000;
      console.log(`等待 ${waitTime}ms 後重試...`);
      Utilities.sleep(waitTime);
    }
  }
}

function riskyOperation() {
  // 模擬可能失敗的操作
  if (Math.random() < 0.3) {
    throw new Error('隨機失敗');
  }
  return '成功';
}

function handleFinalError(error) {
  // 發送錯誤通知
  GmailApp.sendEmail(
    'admin@company.com',
    '系統錯誤通知',
    `操作失敗：${error.message}\n時間：${new Date()}`
  );
}
```

### 4. 執行時間管理

```javascript
/**
 * 管理執行時間，避免觸發 6 分鐘限制
 */
function longRunningTask() {
  const startTime = new Date().getTime();
  const maxRunTime = 5 * 60 * 1000; // 5 分鐘限制
  
  const tasks = generateTasks(); // 假設有很多任務要執行
  const processingQueue = PropertiesService.getScriptProperties();
  
  let currentIndex = parseInt(processingQueue.getProperty('currentIndex') || '0');
  
  while (currentIndex < tasks.length) {
    // 檢查執行時間
    if (new Date().getTime() - startTime > maxRunTime) {
      console.log('接近執行時間限制，儲存進度並安排下次執行');
      processingQueue.setProperty('currentIndex', currentIndex.toString());
      
      // 安排觸發器繼續執行
      ScriptApp.newTrigger('longRunningTask')
        .timeBased()
        .after(60000) // 1 分鐘後繼續
        .create();
      
      return;
    }
    
    // 處理目前任務
    processTask(tasks[currentIndex]);
    currentIndex++;
  }
  
  // 所有任務完成，清除進度
  processingQueue.deleteProperty('currentIndex');
  console.log('所有任務完成');
}

function generateTasks() {
  return Array.from({ length: 1000 }, (_, i) => `任務 ${i + 1}`);
}

function processTask(task) {
  console.log(`處理：${task}`);
  Utilities.sleep(100); // 模擬處理時間
}
```

## 與其他自動化工具比較

### Google Apps Script vs. 其他自動化平台

| 特性 | Google Apps Script | Zapier | n8n | Power Automate |
|------|-------------------|---------|-----|----------------|
| **成本** | 免費（有限制） | 付費（免費版功能少） | 開源免費 | 付費 |
| **Google 整合** | 原生深度整合 | 良好 | 良好 | 一般 |
| **程式彈性** | 高（JavaScript） | 低（GUI操作） | 高（視覺化+程式碼） | 中（GUI操作） |
| **學習曲線** | 中等 | 簡單 | 中等 | 簡單 |
| **自訂能力** | 非常高 | 低 | 高 | 中 |
| **執行限制** | 6分鐘/日配額 | 依方案 | 無限制 | 依方案 |

### 選擇建議

**選擇 Google Apps Script 當：**
- 主要使用 Google Workspace 服務
- 需要深度自訂功能
- 預算有限
- 團隊有程式開發能力

**選擇其他工具當：**
- 需要整合大量第三方服務
- 團隊缺乏程式開發經驗
- 需要複雜的工作流程視覺化
- 有充足的軟體預算

## 2024-2025 最新趨勢

### 1. Gemini AI 整合

Google 正在將 Gemini AI 深度整合到各種服務中，包括：

- **Gemini CLI**：命令列工具，可與 Apps Script 配合使用
- **AI-powered 自動化**：智能郵件回覆、內容生成
- **多模態處理**：處理文字、圖片、影片的綜合自動化

```javascript
// 2025 年趨勢：智能工作流程
function futureWorkflow() {
  // 1. AI 驅動的郵件分類
  classifyEmailsWithAI();
  
  // 2. 自動生成報告
  generateReportsWithAI();
  
  // 3. 智能資料洞察
  getDataInsightsWithAI();
  
  // 4. 多語言自動翻譯
  translateContentWithAI();
}
```

### 2. 低程式碼/無程式碼趨勢

- **Google Workspace 外掛程式**更加視覺化
- **Apps Script 編輯器**增強的自動完成和建議功能
- **模板市場**提供更多預製解決方案

### 3. 安全性增強

```javascript
// 新的安全性最佳實踐（2025）
function secureScriptPractices() {
  // 1. 使用 PropertiesService 儲存敏感資料
  const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
  
  // 2. 實作權限檢查
  if (!hasPermission(Session.getActiveUser().getEmail())) {
    throw new Error('權限不足');
  }
  
  // 3. 資料加密處理
  const encryptedData = encryptSensitiveData(data);
  
  // 4. 審計日誌
  logActivity('data_access', Session.getActiveUser().getEmail());
}
```

### 4. 雲端整合深化

- **BigQuery 整合**：直接查詢大數據
- **Cloud Functions 互動**：更強大的後端處理
- **Firebase 整合**：即時資料庫和認證

## 實用資源與學習路徑

### 官方資源

1. **Google Apps Script 官方文件**
   - [developers.google.com/apps-script](https://developers.google.com/apps-script)
   - 最權威的學習資源

2. **Google AI Studio**
   - [aistudio.google.com](https://aistudio.google.com)
   - 取得 Gemini API 金鑰

3. **Google Workspace 開發者中心**
   - [developers.google.com/workspace](https://developers.google.com/workspace)

### 學習路徑建議

#### 初學者（0-3個月）
1. JavaScript 基礎語法
2. Google Apps Script 環境熟悉
3. 基本的 Sheets 和 Gmail 操作
4. 簡單的自動化腳本

#### 中級（3-6個月）
1. 進階 API 使用（Calendar、Drive、Forms）
2. 錯誤處理和除錯技巧
3. 觸發器和工作流程設計
4. 第三方 API 整合

#### 高級（6個月以上）
1. 性能優化技巧
2. 大型專案架構設計
3. AI 服務整合
4. 企業級解決方案開發

### 社群資源

1. **Stack Overflow**
   - 標籤：`google-apps-script`
   - 豐富的問題解答

2. **Reddit 社群**
   - r/GoogleAppsScript
   - 經驗分享和討論

3. **GitHub 專案**
   - 開源的 GAS 專案和程式庫
   - 學習最佳實踐

### 推薦工具

1. **clasp**：命令列工具，本地開發 GAS
2. **Apps Script Dashboard**：專案管理和監控
3. **Google Cloud Console**：進階功能和配置

## 總結與未來展望

Google Apps Script 作為一個強大的雲端自動化平台，正在經歷快速的發展和進化。從基本的工作表操作到複雜的 AI 整合，GAS 為個人和企業提供了無限的可能性。

### 核心價值

1. **無縫整合**：與 Google Workspace 的深度整合無人能及
2. **成本效益**：免費使用額度對中小型應用足夠
3. **靈活性**：JavaScript 基礎提供無限自訂可能
4. **易於部署**：雲端執行，無需伺服器管理

### 未來發展方向

1. **AI 驅動自動化**：Gemini 等 AI 服務的深度整合
2. **低程式碼發展**：更多視覺化開發工具
3. **企業級功能**：增強的安全性和管理功能
4. **生態系統擴展**：更多第三方整合和模板

### 建議行動

對於想要提升工作效率的人：
- 從小型自動化專案開始
- 逐步學習 JavaScript 和 GAS API
- 關注最新功能和最佳實踐
- 加入開發者社群交流經驗

對於企業組織：
- 評估現有工作流程的自動化潛力
- 投資員工的 GAS 技能培訓
- 建立內部知識庫和最佳實踐
- 考慮與專業開發者合作

### 最後的話

Google Apps Script 不僅僅是一個工具，它是連接想法與實現之間的橋樑。無論你是想要節省日常工作時間的上班族，還是要建立複雜自動化系統的開發者，GAS 都能提供你所需的能力。

隨著 AI 技術的發展，我們正站在自動化新時代的門檻上。現在學習和掌握 Google Apps Script，就是為未來的智能工作方式做準備。

記住：最好的自動化不是替代人類的判斷，而是釋放人類的創造力。讓 Google Apps Script 處理重複性工作，你則專注於更有價值的創新和策略思考。

---

**相關標籤：** #GoogleAppsScript #GAS #自動化 #GoogleWorkspace #JavaScript #工作流程 #生產力工具 #程式開發

**更新日期：** 2025年1月

**作者簡介：** 專業的技術內容創作者，專注於雲端自動化和工作效率提升解決方案。