---
# System prepended metadata

title: Line Notify的復活(Line BOT)

---

## 前言
由於Line Notify過期，突然想到用Line BOT本來就能達成相近的目的
因此嘗試實作，並搭配網路上的Google行事曆教學
# 前置作業
## Line Bot
[申請網站Line](https://account.line.biz/login?redirectUri=https%3A%2F%2Fdevelopers.line.biz%2Fconsole%2Fchannel%2F2007174340%2Fmessaging-api%3Fstatus%3Dsuccess) Line Developers
建立provider跟Messaging API（Line BOT)
![image](https://hackmd.io/_uploads/SJegdzsKZee.png)
![image](https://hackmd.io/_uploads/Sk2ymitblx.png)
![image](https://hackmd.io/_uploads/SybofoKblx.png)
帳號不用認證也沒關係
進入管理Line帳號頁面=>設定=>右側的Messaging API
![image](https://hackmd.io/_uploads/BkrM4jFWee.png)
啟用Messaging API =>選擇自己建立的provider=>同意=>確定=>確定
產生的<font color="#f00">**Channel secret**</font>會用到
![image](https://hackmd.io/_uploads/H1mhNoYZle.png)
回到Line Developers(非Line BOT管理頁面)
重新整理並從Channel找到自己的BOT
![image](https://hackmd.io/_uploads/BkDZHotWgx.png)
選擇後切到Messaging API
![image](https://hackmd.io/_uploads/ryGNSoYWeg.png)
滑到最下方有個Channel access token=>按下Issue
![image](https://hackmd.io/_uploads/SyDSHjYZll.png)
產生新的<font color="#f00">**Channel access token**</font>會用到

## Google 行事曆
左側我的日曆
選擇自己想要通知的項目
設定和共用
![image](https://hackmd.io/_uploads/By_ApdtWle.png)
整合日曆內可以看到自己的<font color="#f00">**日曆ID**</font>，也會用到
![image](https://hackmd.io/_uploads/BJq88jKWxg.png)

## Apps Script
![image](https://hackmd.io/_uploads/HyryvjYbll.png)
從Google試算表中建立Apps Script=>建立新的試算表=>擴充功能=>Apps Script
試算表不能關掉(關掉後你的Apps Script也會關掉)
![image](https://hackmd.io/_uploads/rkEHwjK-ge.png)

程式碼
```
/**
 * 取得接下來一小時內的日曆事件
 * @returns {GoogleAppsScript.Calendar.CalendarEvent[]} 接下來一小時的日曆事件列表
 */
function getCalendarEventsForNextHour() {
  var now = new Date(); // 獲取現在的時間
  var oneHourLater = new Date(now.getTime() + 60 * 60 * 1000); // 計算一小時後的時間（60分鐘 * 60秒 * 1000毫秒）
  var events = calendar.getEvents(now, oneHourLater); // 獲取在此時間範圍內的所有事件
  return events;
}

// LINE Bot 的認證資訊
var CHANNEL_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('CHANNEL_ACCESS_TOKEN'); // 從腳本屬性中獲取 LINE Channel 訪問令牌

// Google Calendar API 設定
var calendar = CalendarApp.getCalendarById(PropertiesService.getScriptProperties().getProperty('CALENDAR_ID')); // 從腳本屬性中獲取要使用的日曆 ID


/**
 * 獲取已發送提醒的記錄
 * @returns {Array} 已發送提醒的記錄列表
 */
function getSentReminders() {
  var sentRemindersData = PropertiesService.getScriptProperties().getProperty('sentRemindersData');
  return sentRemindersData ? JSON.parse(sentRemindersData) : []; // 如果有資料則解析，否則返回空陣列
}

/**
 * 保存已發送提醒的記錄
 * @param {Array} reminders - 提醒記錄列表
 */
function saveSentReminders(reminders) {
  PropertiesService.getScriptProperties().setProperty('sentRemindersData', JSON.stringify(reminders));
}

/**
 * 檢查並發送日曆事件提醒
 * 此函數應該被設置為定時觸發器來定期執行
 */
function sendCalendarReminders() {
  var events = getCalendarEventsForNextHour(); // 獲取接下來一小時內的事件
  var sentReminders = getSentReminders(); // 獲取已發送的提醒記錄
  var now = new Date().getTime(); // 當前時間戳
  var twoHoursAgo = now - 2 * 60 * 60 * 1000; // 兩小時前的時間戳

  // 清理過期的提醒記錄（兩小時前發送的）
  sentReminders = sentReminders.filter(function(reminder) {
    return reminder.sentTime > twoHoursAgo;
  });

  var remindersToSend = []; // 待發送的提醒列表

  events.forEach(function(event) {
    var eventId = event.getId(); // 獲取事件 ID
    var eventStartTime = event.getStartTime().getTime(); // 獲取事件開始時間的時間戳
    var oneHourBefore = eventStartTime - 60 * 60 * 1000; // 計算事件開始前一小時的時間戳

    // 檢查是否已發送過提醒
    var alreadySent = sentReminders.some(function(reminder) {
      return reminder.eventId === eventId;
    });

    // 檢查事件是否即將在一小時內開始，且尚未發送提醒
    if (eventStartTime > now && !alreadySent) {
      // 添加到待發送提醒列表
      remindersToSend.push({
        eventId: eventId,
        title: event.getTitle(),
        startTime: eventStartTime
      });
      // 記錄此提醒已發送
      sentReminders.push({ eventId: eventId, sentTime: now });
    }
  });

  // 如果有需要發送的提醒
  if (remindersToSend.length > 0) {
    // 格式化每個提醒訊息
    var reminderMessages = remindersToSend.map(function(reminder) {
      return '活動 "' + reminder.title + '" 將於 ' + 
        Utilities.formatDate(new Date(reminder.startTime), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm") + 
        ' 開始！';
    });
    
    // 組合完整的提醒訊息
    var message = '行事曆提醒：\n\n' + reminderMessages.join('\n\n');
    
    // 使用廣播功能發送提醒給所有用戶
    var result = broadcastToRecentInteractions(message);
    Logger.log('已發送行事曆提醒: ' + result); // 記錄到日誌
  }

  // 保存更新後的提醒記錄
  saveSentReminders(sentReminders);
}

/**
 * 向所有用戶廣播 LINE 訊息
 * @param {string} message - 要廣播的訊息內容
 * @returns {string} 廣播結果訊息
 */
function broadcastToRecentInteractions(message) {
  var url = 'https://api.line.me/v2/bot/message/broadcast'; // LINE 廣播 API 端點
  var headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN // 設置認證標頭
  };
  var payload = {
    'messages': [{
      'type': 'text',
      'text': message || '這是一則廣播訊息！' // 如果未提供訊息內容，則使用預設訊息
    }]
  };
  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload) // 將負載轉換為 JSON 字串
  };

  var response = UrlFetchApp.fetch(url, options); // 發送 HTTP 請求到 LINE API
  Logger.log('廣播訊息已成功發送！'); // 記錄訊息到日誌
  return '廣播訊息已成功發送！';
}


/**
 * 測試廣播功能
 * @returns {string} 廣播結果訊息
 */
function testBroadcast() {
  var message = "這是一則測試廣播訊息，發送時間: " + new Date().toLocaleString();
  var result = broadcastToRecentInteractions(message);
  Logger.log("廣播測試結果: " + result); // 記錄到日誌
  return result;
}
```

專案設定=>指令碼屬性=>編輯指令碼屬性=>新增指令碼屬性
![image](https://hackmd.io/_uploads/rJ-O_oKWge.png)

CALENDAR_ID(建議用自己的信箱，能不能用他人的信箱待驗證)
CHANNEL_ACCESS_TOKEN
CHANNEL_SECRET

觸發條件=>新增觸發條件
![image](https://hackmd.io/_uploads/BJLp_otbgx.png)


## Note!!
如果之後如下發現開不起來的話(Apps Script)得用無痕視窗，刪除cookie沒用
![image](https://hackmd.io/_uploads/BJ0-qoF-gx.png)


[參考教學](https://weijutu.github.io/2020/04/13/javascript/calendar-line-notify/)