# 111年 - Google Apps Script雲端程式設計實務班
###### tags: `Google Apps Script` `產業人才投資方案` `授課資料`
> 此項筆記供111年產業人才投資方案-Google Apps Script雲端程式設計實務班課程使用,講師會將上課補充重點、參考資料與程式碼留在此頁面 :books:
---
>課程日期:111-12-03(六)、111-12-10 (六) 、111-12-17 (六) 、111-12-24 (六)
>課程時間:09:00 ~ 12:00、13:30 ~ 16:30
>上課地點:崑山科技大學 民生應用學院二館 H2605教室 (電競教室)
>授課教師:[鄭郁翰](https://web.ksu.edu.tw/DTCECGD/teacher/BPm5DRXmVhiAEs76y8PalQ--) 講師 [[前往教師簡介]](https://web.ksu.edu.tw/DTCECGD/teacher/BPm5DRXmVhiAEs76y8PalQ--)
>課程大綱:https://cee.ksu.edu.tw/2517
>
>本課程使用之輔助教材:[Google Apps Script雲端自動化與動態網頁實戰(第二版)](http://books.gotop.com.tw/e_ACU084300)
>書籍影音教學、範例程式檔下載:http://books.gotop.com.tw/download/ACU084300
## :memo: 課前準備
### Step 1: 上課前應準備的
- [x] 完成 Google 帳號申請
- [x] 安裝 Google Chrome 瀏覽器
:::info
:pushpin: 因為是使用 Google 的雲端服務,所以使用 Google Chrome 瀏覽器具有最佳體驗
:::
### Step 2: 課前能力檢核
- [x] Excel使用經驗 - 我曾使用過各項函數完成我的工作
- [x] Excel使用經驗 - 我曾使用過合併列印來產生大量Word檔案
- [x] Excel使用經驗 - 我曾使用過VBA來撰寫巨集程式
- [ ] Google雲端服務使用經驗 - 我曾使用過Google試算表來處理工作
- [ ] Google雲端服務使用經驗 - 我曾使用過Google雲端硬碟分享檔案
- [ ] 我具有程式設計的能力或實際工作經驗
- [ ] 我具有網頁設計的經驗,理解HTML、CSS的架構
- [ ] 我具有網頁設計的經驗,理解JavaSript程式語言
- [ ] 我具有實際使用過 Google Apps Script (GAS) 的經驗
:::info
HTML參考資料:
https://developer.mozilla.org/zh-TW/docs/Learn/Getting_started_with_the_web/HTML_basics
:::
## 一、Google 試算表
![](https://hackmd.io/_uploads/rJy3OYyy3.png)
- 網路共用的建議:如果希望試算表不公開,只提供特定人士存取,應該採個別授權方式。
參考資料:https://www.isecurity.com.tw/news-and-events/20200611-accidental-exposure-in-google-link-sharing/
- Google試算表提供的函數大多數與Microsoft Excel相同,更完整的函數可參考Google網站的參考文件
- Google試算表常用函數
https://support.google.com/docs/table/25273?hl=zh-Hant
### Google Sheet 的 GAS API
先認識「試算表」的結構,以Microsoft Excel來說,整個檔案就是一個「活頁簿」(workbook),活頁簿下可以有多個「工作表」(worksheet),每個表內又有多個「列」(row)或「欄」(column),以及其下的「儲存格」(cell),
![](https://hackmd.io/_uploads/BkbpuYyy2.png)
#### 名稱對照
|項目 | Microsoft Excel | Google Sheet|
|------| -------- | -------- |
|活頁簿 | Workbook (活頁簿) | Spreadsheet (試算表) |
|工作表 | Worksheet (工作頁) | Sheet (工作表) |
### GAS開啟試算表
1. 可以使用openById()或openByUrl()
2. 如果是把GAS附加在試算表上,還可以使用getActiveSpreadsheet()來取得目前「試算表」
3. 取得試算表後,就要決定要開啟哪一個「工作表」,試算表物件下有 getSheetByName('name') 以工作表名稱來取得該工作表物件
* 另外,如果是把GAS附加在試算表上,也可以直接使用getActiveSheet()取得目前試算表中的「第一個工作表」(這樣就可以跳過第3步驟)
```javascript=
function myFunction() {
var book = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxx/');
var sheet = book.getSheetByName("工作表1");
var range = sheet.getRange(2,1)
Logger.log(range.getValue());
}
```
```javascript=
function myFunction() {
var val = SpreadsheetApp.getActiveSheet().getRange(3,1).getValue()
Logger.log(val);
}
```
### 如何在試算表使用Google翻譯?
直接使用公式中的函數「==GOOGLETRANSLATE()==」
參考資料:https://support.google.com/docs/answer/3093331?hl=zh-Hant
語系對照表:https://cloud.google.com/translate/docs/languages
```javascript=
function onEdit(e) {
var c = e.range.getColumn()
var r = e.range.getRow()
var sheet = SpreadsheetApp.getActiveSheet();
var cn = LanguageApp.translate(e.value,'zh-TW','zh-CN');
var en = LanguageApp.translate(e.value,'zh-TW','en');
var ja = LanguageApp.translate(e.value,'zh-TW','ja');
sheet.getRange(r,c+1).setValue(cn);
sheet.getRange(r,c+2).setValue(en);
sheet.getRange(r,c+3).setValue(ja);
}
```
![](https://i.imgur.com/V2O4b4L.png)
### Google試算表自訂函數
如果希望在試算表輸入公式時會有自動完成的提示功能,要依格式加入註解
```javascript=
/**
* Multiplies the input value by 2.
*
* @param {number} input The value to multiply.
* @return The input multiplied by 2.
* @customfunction
*/
function DOUBLE(input) {
return input * 2;
}
```
https://developers.google.com/apps-script/guides/sheets/functions?hl=zh-tw#naming
輸入參數也可以輸入一段「範圍」,例如A1:B5
```javascript=
/**
* Multiplies the input value by 2.
*
* @param {number|Array<Array<number>>} input The value or range of cells
* to multiply.
* @return The input multiplied by 2.
* @customfunction
*/
function DOUBLE(input) {
return Array.isArray(input) ?
input.map(row => row.map(cell => cell * 2)) :
input * 2;
}
```
https://developers.google.com/apps-script/guides/sheets/functions?hl=zh-tw#naming
無論如何,自訂函式呼叫必須在 30 秒內傳回;否則,儲存格會顯示錯誤訊息:Internal error executing the custom function.
## 二、Google Apps Script 與網頁應用程式
GAS的網頁應用程式可以接受輸出結果為HTML或JSON格式,寫完程式後存檔,必需使用「部署」功能來發布為網頁應用程式,取得網址後才能使用
### 如何從GAS顯示資料為HTML?
1. 使用 ContentService.createTextOutput() - 直接輸出文字
```javascript=
function doGet() {
return ContentService.createTextOutput("Hello World");
}
```
2. 使用 HtmlService.createHtmlOutput() - 直接輸出HTML
```javascript=
function doGet() {
return HtmlService.createHtmlOutput('<b>Hello, world!</b>');
}
```
https://developers.google.com/apps-script/reference/content/content-service
3. 使用 HtmlService.createHtmlOutputFromFile() - 直接使用HTML檔案回傳
```javascript=
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
```
https://developers.google.com/apps-script/guides/html#code.gs
4. 使用 HtmlService.createTemplateFromFile() - 直接使用HTML檔案回傳,並且將其以模板方式設計,可使用<? ... ?> 嵌入GAS程式碼,並且也從GAS端傳入變數,呼叫evaluate()後就會開始執行
Code.js
```javascript=
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
```
Index.html
```htmlembedded=
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Hello, World! The time is <?= new Date() ?>.
</body>
</html>
```
https://developers.google.com/apps-script/reference/html/html-template
#### 練習一:將變數傳遞到HTML端
code.gs
```javascript=
function doGet(e){
var n = 'Tom';
var html = HtmlService.createTemplateFromFile('Index');
html.name = n;
return html.evaluate();
}
```
Index.html
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>
通訊錄
</h1>
<div>
查詢時間:<?= new Date() ?>
</div>
<div>
維護人員:<?=name ?>
</div>
</body>
</html>
```
![](https://i.imgur.com/x2BjdGj.png)
#### 練習二:結合試算表資料及迴圈呈現結果
code.gs
```javascript=
function doGet(e){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("編輯人員");
var n = sheet.getRange("A1").getValue();
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("工作表1");
var data = sheet2.getRange(2,1, sheet2.getLastRow()-1 ,sheet2.getLastColumn()).getValues();
var html = HtmlService.createTemplateFromFile('Index');
html.name = n;
html.data = data;
return html.evaluate();
}
```
Index.html
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>
通訊錄
</h1>
<div>
查詢時間:<?= new Date() ?>
</div>
<div>
維護人員:<?=name ?>
</div>
<table border="1">
<tr>
<th>編號</th>
<th>姓名</th>
<th>電話</th>
<th>地址</th>
</tr>
<?
for(var i = 0 ; i< data.length ; i++ ){
?>
<tr>
<td><?=data[i][0] ?></td>
<td><?=data[i][1] ?></td>
<td><?=data[i][2] ?></td>
<td><?=data[i][3] ?></td>
</tr>
<?
}
?>
</table>
</body>
</html>
```
#### 練習三:分割成不同函式(function)來執行
code.gs
```javascript=
function doGet(e){
var html = HtmlService.createTemplateFromFile('Index');
return html.evaluate();
}
function getPersonName(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("編輯人員");
return sheet.getRange("A1").getValue();
}
function getDataTable(){
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("工作表1");
var data = sheet2.getRange(2,1, sheet2.getLastRow()-1 ,sheet2.getLastColumn()).getValues();
return data;
}
```
Index.html
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>
通訊錄
</h1>
<div>
查詢時間:<?= new Date() ?>
</div>
<div>
維護人員:<?=getPersonName() ?>
</div>
<table border="1">
<tr>
<th>編號</th>
<th>姓名</th>
<th>電話</th>
<th>地址</th>
</tr>
<?
var data = getDataTable();
for(var i = 0 ; i< data.length ; i++ ){
?>
<tr>
<td><?=data[i][0] ?></td>
<td><?=data[i][1] ?></td>
<td><?=data[i][2] ?></td>
<td><?=data[i][3] ?></td>
</tr>
<?
}
?>
</table>
</body>
</html>
```
### 設計具有表單互動的GAS的網頁應用程式
:::info
GAS參考資料:https://developers.google.com/apps-script/guides/web
HTML表單介紹 : https://www.fooish.com/html/form.html (網路資料)
:::
程式中主要其中包含 ==doGet(e)== 或 ==doPost(e)== 函式,並回傳HTML內容,可以利用函式中的傳入參數 ==e== 來取得使用者的輸入並與其互動。
傳入參數(e)中常見的成員屬性:
- e.queryString
- e.parameter
- e.parameters
- e.pathInfo
- e.postData
:::danger
:exclamation: 請特別注意:在 Google Apps Script 中,參數(parameter)的名稱請不要使用 ==c== 及 ==sid== ,這兩個名稱為系統所保留作為其他用途,請不要在程式中使用這些參數名稱。
:::
如果網頁想要求使用者輸入資料,並由後端接收並處理,可以使用HTML建立表單(Form)後傳到後端處理,但因為GAS的網頁應用程式實際上是執行在iframe框架內,所以設計上稍有差異,請依據以下網址進行設計:https://developers.google.com/apps-script/guides/html/communication#forms
HTML表單(Form)的重要屬性
- method:可以設定成GET或POST,分別由GAS的 doGet(e) 及 doPost(e) 處理
- name:表單名稱
- action:表單資料送往的目的地
- onsubmit : 指定表單提交前要執行的JavaScript function
有關 action ,如果要送給目前程式處理,可以簡單使用 ==ScriptApp.getService().getUrl()== 取得目前頁面網址。如果希望在表單中嵌入,可以參考以下方式:
```htmlmixed==
<form method="POST" action="<?= ScriptApp.getService().getUrl() ?>">
姓名:<input type="text" name="name" id="name" ></input>
<input type="submit" value="送出" />
</form>
```
在Google官方文件中,更推薦使用 ==google.script.run== 來達到非同步的函式呼叫(如同AJAX的方式使用),使用 ==google.script.run== 可以從前端JavaScript程式碼執行指定的後端(.gs)函式。
### 資料查詢的程式範例
https://docs.google.com/spreadsheets/d/1aHY7BZlcI4_jh8KcEl-9R6wXljVbrtqpnKziLZfEwuE/edit?usp=sharing
![](https://i.imgur.com/YyiGnby.png)
![](https://i.imgur.com/TwF1yf1.png)
#### 其他常見方法:
- HtmlOutput.setTitle(title) : 設定網頁標題
- HtmlTemplate.evaluate() : 評估回傳的HtmlOutput物件
- 網頁客戶端也可以使用JavaScript呼叫一些專屬功能,名稱皆以「google.script」開頭,例如「google.script.host.close()」是把目前對話方塊關閉。
-- google.script.history 用於GAS的網頁應用程式,
-- google.script.host 用於Google服務的側邊欄或對話方塊
-- google.script.run 用於非同步執行GAS伺服器端方法時
-- google.script.url 可以查詢網址來取得目前的網址參數和片段,只能使用在網頁應用程式
- 更多基本API參考 : https://developers.google.com/apps-script/reference/html
## 三、Gmail 電子郵件的應用
在Google Apps Script內建的服務呼叫函式中,分別有 ==Mail Service== 及 ==Gmail Service== 最為常見,其中 Mail Service 這項服務可讓指令碼代表使用者傳送電子郵件。
==若與 Gmail Service 比較,Mail Service 只用來做「傳送」電子郵件==,無法存取使用者的 Gmail 帳戶;所以,如果需要涉及 Gmail 更完整的控制,例如讀取信件清單這類型的複雜工作,請改採用 Gmail Service 來操作。
:::info
:pushpin: 有關電子郵件發送的詳細使用方式及函式清單,可以參考以下網址:
Mail Service : https://developers.google.com/apps-script/reference/mail
Gmail Service : https://developers.google.com/apps-script/reference/gmail?authuser=1
:::
### 起手式:簡單的電子郵件發送功能
1. 使用 Mail App 發送電子郵件
```javascript=
var subject = "信件標題";
var message = "您好,這裡是信件內容";
var email = "xxx@gmail.com"
MailApp.sendEmail (email, subject, message);
```
2. 另外一種呼叫方式:
```javascript=
MailApp.sendEmail(
{
to: "xxx@gmail.com",
subject: "信件標題",
htmlBody: "信件內文<hr />也可以包含HTML語法喔!<br />"
}
);
```
3. 此外,也可以使用 Gmail App 來發送電子郵件
```javascript=
var subject = "信件標題";
var message = "您好,這裡是信件內容";
var email = "xxx@gmail.com"
GmailApp.sendEmail (email, subject, message);
```
:::info
:pushpin: 使用 Google Apps Script 發送郵件具有配額限制,如果想知道剩餘多少發送數量,可以呼叫 ==getRemainingDailyQuota()== 這個函式來查詢剩餘額度。
* 詳細配額限制資訊 : https://developers.google.com/apps-script/guides/services/quotas
:::
### 發送具有附件的電子郵件
1. 發送電子郵件也可以夾帶附件,可以參考以下兩個官方範例教學
- MailApp : https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String,Object)
- GmailApp : https://developers.google.com/apps-script/reference/gmail/gmail-app?authuser=1#sendEmail(String,String,String,Object)
```javascript=
var blob = DriveApp.getFileById("換成你的ID");
var subject = "信件標題(MailApp)";
var message = "您好,這裡是信件內容";
var email = "xxx@xxx.xxx.edu.tw"
MailApp.sendEmail(email, subject, message, {
attachments: [blob]
});
```
:::info
:pushpin: 請注意:電子郵件夾帶附件也具有相關限制(例如檔案大小),使用時也要留意
:::
2. 如果檔案放在外部,也可以使用 ==UrlFetchApp== 下載回來,但是 UrlFetchApp 也有配額限制,且下載過程可能需要等待,不建議大量呼叫。
```javascript=
var f = UrlFetchApp.fetch("https://xx.xx/x.jpg").getBlob().setName('檔案名稱');
```
UrlFetchApp呼叫fetch()後,幾個常見的函數:
- getContentText() 取得文字資料
- getBlob() 取得二進制大型物件
- getContent() 取得原始二進制資料
- getResponseCode() 取得HTTP Status Code
```javascript=
var file = UrlFetchApp.fetch("https://cee.ksu.edu.tw/CourseImage.ashx?id=2517&type=2").getBlob().setName('課程海報');
var subject = "信件標題(MailApp)";
var message = "您好,這裡是信件內容";
var email = "xxxx@xxx.tw"
MailApp.sendEmail(email, subject, message, {
attachments: [file]
});
```
## 四、Google 表單
:::info
:pushpin: 參考資料 : https://developers.google.com/apps-script/reference/forms
:::
可以使用 ==JSON.stringify()== 函式將物件轉換為JSON格式的字串,以利於開發與偵錯。
```javascript=
Logger.log( JSON.stringify(e) );
```
可以使用 https://jsonformatter.curiousconcept.com/ 快速格式化JSON內容
Google表單「表單提交時」條件觸發時所傳入參數的格式
![](https://i.imgur.com/fmT8XxG.png)
- 當表單提交時,自動發信的程式碼範例
```javascript=
function SendMail(e) {
var subject = "【活動報名成功通知】" + e.values[2] + "您好,已成功報名活動!" ;
var message = e.values[2] + "您好,已於" + e.values[0] + "成功報名活動!";
var email = e.values[3];
MailApp.sendEmail (email, subject, message);
}
```
- 建立電子郵件的「收信確認」功能
```javascript=
function doGet(e){
var id = e.parameter.id;
var email = e.parameter.email;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("表單回應 1");
var data = sheet.getRange(2,1,sheet.getLastRow()-1,sheet.getLastColumn()).getValues();
Logger.log(email);
Logger.log(id);
var atIndex = data.findIndex(function(item, index, array){
return item[3] === email && item[6] === id;
});
if(atIndex<0){
return ContentService.createTextOutput( "驗證失敗");
}
else{
atIndex += 2 ;
if(!sheet.getRange(atIndex,8).getValue()){
return ContentService.createTextOutput( "已完成驗證過,請勿重複執行!");
}
var now = new Date();
sheet.getRange(atIndex,8).setValue(now);
return ContentService.createTextOutput( "完成信件開啟之驗證");
}
}
function SendMail(e) {
var random = Math.random().toString(36);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("表單回應 1");
sheet.getRange(e.range.rowStart ,7).setValue(random);
var email = e.values[3];
var subject = "【活動報名成功通知】" + e.values[2] + "您好,已成功報名活動!" ;
var message = e.values[2] + "您好,已於" + e.values[0] + "成功報名活動!請開啟以下網址完成收信確認:" +
"https://script.google.com/xxxxxxxxxxxx/exec?email=" + email + "&id=" + random ;
MailApp.sendEmail (email, subject, message);
}
```
Forms Service 可讓指令碼建立、存取及修改 Google 表單
### 開啟現存Google表單
開啟一個現有的 Google 表單,可以發現其實跟Google試算表、Google雲端硬碟等服務都一樣,幾乎都是使用 ==openById()== 即可開啟。也可以使用 ==openByUrl()== 喔!
```javascript=
var myForm = FormApp.openById('xxxxIdxxxx');
```
### 建立一個新的Google表單
```javascript=
var myForm = FormApp.create('表單名稱');
```
:::info
:pushpin: 無論是開啟現有或建立一個新的表單,最終都會回傳一個Form類別的物件回來,相關資料可參考: https://developers.google.com/apps-script/reference/forms/form
:::
### 使用GAS程式碼依據試算表內容自訂建立Google表單(線上測驗)
https://docs.google.com/spreadsheets/d/1_oTXQM9okAc-7UF3TmBt1cFf3njcu4ZECdnkFflvpzQ/edit?usp=sharing
### GAS 中 FormApp 常用的表單元件與函式對照
新增表單輸入項目:
- addTextItem() 簡答(提供填寫單行文字)
- addParagraphTextItem() 詳答(提供填寫多行文字)
- addMultipleChoiceItem() 選擇題(圓形勾選鈕)
- addCheckboxItem() 核取方塊(方形勾選鈕)
- addListItem() 下拉式選單
- addScaleItem() 線性刻度
- addGridItem() 單選方格
- addCheckboxGridItem() 核取方塊格
- addDateItem() 日期(提供日期輸入)
- addDurationItem() 時間(僅供時間輸入)
- addDateTimeItem() 日期與時間
新增表單非輸入項目:
- addImageItem() 新增圖片配置
- addVideoItem() 影片項目
## 五、Google Workspace 自訂選單
:::info
:pushpin: 參考資料 : https://developers.google.com/apps-script/guides/menus
:::
可以透過指令碼擴充 Google Workspace 應用程式(例如文件、試算表)的功能選單。若希望在使用者開啟檔案時就在工具列上顯示自訂選單,可在 ==onOpen()== 函式中撰寫擴充自訂選單的程式碼。
### 整合自訂選單的線上測驗產生器
https://docs.google.com/spreadsheets/d/10ZKHNKXpPVxm31bxH3ds8yDIYOs4ah9JAOo4CJ5puto/edit?usp=sharing
## 六、Google 雲端硬碟
:::info
:pushpin: 有關透過Google Apps Script操作雲端硬碟之詳細使用方式及函式清單,可以參考以下網址:
Drive Sevice : https://developers.google.com/apps-script/reference/drive
:::
### 範例程式 : 建立檔案
```javascript=
var fileName = "xxx.txt";
var content = "1234";
DriveApp.createFile(fileName,content);
```
小提示:把createFile()換成createFolder(name),就是建立資料夾的方法
### 在指定資料夾建立檔案的方法
```javascript=
function myFunction() {
var fileName = "xxx.txt";
var content = "1234";
var folder = DriveApp.getFolderById("輸入你的資料夾ID");
folder.createFile(fileName,content);
}
```
### DriveApp 常用函數
- createFile() 建立檔案
- createFolder() 建立資料夾
- getFileById() 取得指定ID的檔案
- getFilesByName() 取得指定名稱的檔案(可能取得多個檔案)
- getFilesByType() 取得指定MIME Type的檔案(可能取得多個檔案)
- getFiles() 取得所有檔案的集合
- getFolderById() 取得指定ID資料夾
- getFolders() 取得所有資料夾的集合
- getFoldersByName(name) 取得指定名稱的資料夾(可能取得多個資料夾)
- getRootFolder() 取得根目錄(最上層)的資料夾
### 取得檔案基本資料
```javascript=
function getFileInfo() {
var file = DriveApp.getFileById("你的檔案ID");
Logger.log(file.getName())
Logger.log(file.getSize())
Logger.log(file.getDescription())
Logger.log(file.getUrl())
Logger.log(file.getDownloadUrl())
Logger.log(file.getId())
Logger.log(file.getMimeType())
Logger.log(file.getLastUpdated())
}
```
### Class File 常用函數
- makeCopy 複製檔案
- moveTo 移動檔案
- getId 取得檔案ID
- getUrl 取得檔案URL(可用於在Google應用程式中開啟 File 的網址)
- getDownloadUrl 取得檔案下載網址 *
- getBlob 以blob形式取得檔案
- getName 取得檔案名稱
- getMimeType 取得檔案MIME Type
- setTrashed 設定檔案是否在垃圾桶
- setContent 設定檔案內容(或覆蓋)
- setDescription 設定檔案說明內容
資料夾的操作方式類似,請參見Class Folder的介紹:
https://developers.google.com/apps-script/reference/drive/folder
### 將雲端硬碟檔案清單寫入試算表
```javascript=
function getFileInfo() {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.clear();
var headerRow = ['檔案名稱','檔案大小','檔案描述','檔案網址', '實際下載網址'];
sheet.appendRow(headerRow)
var folder = DriveApp.getFolderById("資料夾ID");
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
var data = [];
data.push(file.getName())
data.push(file.getSize())
data.push(file.getDescription())
data.push(file.getUrl())
data.push(file.getDownloadUrl())
sheet.appendRow(data);
}
}
```
### 完整程式碼範例(參考書籍實作)
GAS
```javascrip=
function onOpen() {
var sheet = SpreadsheetApp.getActive();
var menuItems = [
{name: '載入清單', functionName: 'getFileInfo'}
];
sheet.addMenu('雲端硬碟', menuItems);
}
function getFileInfo() {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.clear();
var headerRow = ['檔案名稱','檔案大小','檔案描述','檔案網址', '實際下載網址'];
sheet.appendRow(headerRow)
var folder = DriveApp.getFolderById("資料夾ID");
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
//file.setSharing(DriveApp.Access.ANYONE,DriveApp.Permission.VIEW);
var data = [];
data.push(file.getName())
data.push(file.getSize())
data.push(file.getDescription())
data.push(file.getUrl())
data.push(file.getDownloadUrl())
sheet.appendRow(data);
}
}
function getData(){
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
return data;
}
function doGet(e) {
return HtmlService.createTemplateFromFile('index').evaluate().setTitle('檔案下載');
}
```
HTML:
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>檔案下載</h1>
<? var data = getData();
for (var i = 1; i< data.length; ++i) {
?>
檔案名稱:<?= data[i][0] ?> <a href="<?= data[i][4] ?>">[下載]</a>
<br />
<? } ?>
</body>
</html>
```
HTML(書上版本):
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initialscale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>檔案下載</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<base target="_top">
<style>
body{
font-family: 'Microsoft JhengHei';
}
.download-list{
padding: 0;
list-style: none;
}
.download-list li{
padding: 15px 5px;
border-bottom: 1px solid #999;
}
.download-list li:nth-last-child(1){
border-bottom: 0;
}
.download-list li a{
margin-left: 10px;
padding: 5px 15px;
background: #333;
color: #fff;
border-radius: 20px;
text-decoration: none;
}
.download-list li a:hover{
background: #666;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12">
<h1 class="text-primary text-center mt-5 mb-3">檔案下載</h1>
<ul class="download-list">
<? var data = getData();
for (var i = 1; i< data.length; ++i) {
?>
<li>
<span><?= data[i][0] ?></span>
<a href="<?= data[i][4] ?>"> 下載</a>
<div>
<?= data[i][2] ?>
</div>
</li>
<? } ?>
</ul>
</div>
</div>
</div>
</body>
</html>
```
### 改為直接讀取雲端硬碟
```javascript=
function getData(){
var folder = DriveApp.getFolderById("資料夾ID");
var files = folder.getFiles();
var table = [];
while (files.hasNext()) {
var file = files.next();
var data = [];
data.push(file.getName())
data.push(file.getSize())
data.push(file.getDescription())
data.push(file.getUrl())
data.push(file.getDownloadUrl())
table.push(data);
}
Logger.log(table);
return table;
}
```
## 七、Google 文件
:::info
:pushpin: 有關透過Google Apps Script操作文件之詳細使用方式及函式清單,可以參考以下網址:
Document Sevice : https://developers.google.com/apps-script/reference/document
:::
### 開啟及建立文件
開啟檔案的方法 (可用ID或URL開啟)
```javascript=
var doc1 = DocumentApp.openById('文件的ID');
var doc2 = DocumentApp.openByUrl('文件的URL');
```
建立檔案的方法
```javascript=
doc = DocumentApp.create('檔案名稱');
```
### Google 文件的結構
參考資料:https://developers.google.com/apps-script/guides/docs
Document→Body→....
對於內容的操作,大多從getBody()開始
### Class Document 常用函數
- getBody 取得文件中的內文
- getAs 取回指定類型的blob內容,僅支援轉換為PDF,請傳入「application/pdf」參數
-
### Class Body 常用函數
#### 範例程式:新增段落
```javascript=
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
body.appendParagraph("A paragraph.");
body.appendPageBreak();
//參考來源: https://developers.google.com/apps-script/reference/document/body
```
#### 範例程式:文字內容取代
基本的文字替換
```javascript=
var body = DocumentApp.getActiveDocument().getBody();
body.replaceText("{{name}}", "Tom");
```
帶有運算規則的作法
```javascript=
var body = DocumentApp.getActiveDocument().getBody();
body.replaceText("^.*Apps ?Script.*$", "Apps Script");
```
備註:這不是JavaScript的正規表達式,而是Google的RE2規則,若要進一步認識可參考 https://github.com/google/re2 / https://github.com/google/re2/wiki/Syntax / https://support.google.com/a/answer/1371417?hl=zh-Hant
#### 範例程式、文字取代及PDF產生
:::info
:pushpin: 請先使用 Google文件 來設計文件範本格式,並將要替換的部分使用 =={{...}}== 來標註(也可以用其他符號標註,重點是要使用程式碼來取代對應的標籤部分),也可以使用 Microsoft Word 設計完後在上傳到 Google雲端硬碟 ,然後將其轉換為 Google文件 的格式。
:::
```javascript=
function createDocumentForTemplater() {
var templater_id = "範本檔案的ID";
var templater_file = DriveApp.getFileById(templater_id).makeCopy();
var doc = DocumentApp.openById(templater_file.getId());
var body = doc.getBody();
body.replaceText("{{name}}" , "王曉明");
body.replaceText("{{id}}", "12345678");
doc.saveAndClose();
var folder_id = "目標存放資料夾的ID";
var folder = DriveApp.getFolderById(folder_id);
templater_file.moveTo(folder).setName("202212170001");
var pdf = templater_file.getAs('application/pdf');
folder.createFile(pdf).setName("202212170001.pdf");
}
```
#### 再改善程式碼:產生PDF後寄信
```javascript=
function createDocumentForTemplater() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for(var i = 1; i < data.length; i++ ){
var templater_id = "範本ID";
var templater_file = DriveApp.getFileById(templater_id).makeCopy();
var doc = DocumentApp.openById(templater_file.getId());
var body = doc.getBody();
var name = data[i][0];
var person = data[i][1];
var date = data[i][2];
var email = data[i][3];
body.replaceText("{{name}}" , name );
body.replaceText("{{日期}}" , date );
body.replaceText("{{頒獎人}}" , person );
doc.saveAndClose();
var folder_id = "存放資料夾ID";
var folder = DriveApp.getFolderById(folder_id);
templater_file.moveTo(folder).setName(name + "證書");
var pdf = templater_file.getAs('application/pdf');
folder.createFile(pdf).setName(name + "證書.pdf");
var subject = "恭喜你成績及格,請下載電子證書";
var message = name + "您好,這裡是信件內容";
MailApp.sendEmail(email, subject, message, {
attachments: [pdf]
});
}
```
### 擴充選單:靈活的文件合併功能
```javascript=
function onOpen(e) {
var ui = SpreadsheetApp.getUi();
ui.createMenu('合併檔案')
.addItem('開始合併', 'start')
.addToUi();
}
function start(){
var ui = SpreadsheetApp.getUi();
var tid;
var fid;
var filename;
var response1 = ui.prompt(
'文件範本',
'請設定文件範本的ID',
ui.ButtonSet.OK_CANCEL);
if(response1.getSelectedButton() == ui.Button.OK){
tid = response1.getResponseText();
}
else {
return;
}
var response2 = ui.prompt(
'產生文件存放區',
'請設定文件產生後要儲存的資料夾的ID',
ui.ButtonSet.OK_CANCEL);
if(response2.getSelectedButton() == ui.Button.OK){
fid = response2.getResponseText();
}
else {
return;
}
var response3 = ui.prompt(
'檔案名稱',
'請設定所產生的檔案名稱',
ui.ButtonSet.OK_CANCEL);
if(response3.getSelectedButton() == ui.Button.OK){
filename = response3.getResponseText();
}
else {
return;
}
createDocumentForTemplater(tid,fid,filename);
}
function createDocumentForTemplater( tid , fid , filename) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for(var i = 1; i < data.length; i++ ){
var templater_id = tid;
var templater_file = DriveApp.getFileById(templater_id).makeCopy();
var doc = DocumentApp.openById(templater_file.getId());
var body = doc.getBody();
for(var x = 0 ; x < data[i].length ; x++ ){
var h = data[0][x];
var v = data[i][x];
body.replaceText( "{{" + h + "}}" , v );
}
doc.saveAndClose();
var folder_id = fid;
var folder = DriveApp.getFolderById(folder_id);
templater_file.moveTo(folder).setName(filename + "_" + i);
}
}
```
### 送出表單測驗後,判斷及格就寄發證書
:::info
:pushpin: 這個證書範本是使用 Microsoft Word 2019 中的範本產生,然後上傳到 Google雲端硬碟並將其轉換為 Google文件 的格式。
:::
```javascript=
function sendReport(e) {
var templater_id = "範本ID";
var score = parseInt(e.values[1].split("/")[0]);
var name = e.values[3];
var email = e.values[14];
if(score >= 60){
var templater_file = DriveApp.getFileById(templater_id).makeCopy();
var doc = DocumentApp.openById(templater_file.getId());
var body = doc.getBody();
body.replaceText("{{name}}" , name );
doc.saveAndClose();
var folder_id = "存放資料ID";
var folder = DriveApp.getFolderById(folder_id);
templater_file.moveTo(folder).setName(name + "-證書");
var pdf = templater_file.getAs('application/pdf');
folder.createFile(pdf).setName(name + "-證書.pdf");
var subject = "恭喜你成績及格,請下載電子證書";
var message = name + "您好,這裡是信件內容";
MailApp.sendEmail(email, subject, message, {
attachments: [pdf]
});
}
else{
var subject = "考試成績不及格";
var message = name + "您好.....";
MailApp.sendEmail(email, subject, message,);
}
}
```
**及格通知信**
![](https://i.imgur.com/rcZYOQ2.png)
**證書檔案成果**
![](https://i.imgur.com/cGbiAP1.png)
**不及格通知信**
![](https://i.imgur.com/UY7HUVq.png)
## 八、LINE Notify 整合
:::info
:pushpin: 如果需要 LINE Notify 更完整的功能整合,請參考 LINE Notify API 官方文件 : https://notify-bot.line.me/doc/
:::
LINE Notify是LINE所提供的一項服務,請先開啟以下 LINE Notify 網站網址,並加入 LINE Notify 的好友 https://notify-bot.line.me/zh_TW/
LINE Notify的 LINE ID: @linenotify
![](https://i.imgur.com/8voeeyw.png)
LINE Notify 可以個別一對一傳送訊息給目標,也可以將訊息傳送到特定群組上
1. 將 LINE Notify 帳號加入好友清單後,請開啟 https://notify-bot.line.me/zh_TW/ 網站並從右上角的「登入」按鈕開啟登入頁面,並登入您的帳號
2. 登入後,從右上角的選單開啟「個人頁面」
![](https://i.imgur.com/PvMpoPY.png)
3. 進入後,在==「發行存取權杖(開發人員用)」==點選==「發行權杖」==
![](https://i.imgur.com/gol1rVT.png)
4. 選擇「群組」或「1對1模式」
![](https://i.imgur.com/YU7FB8a.png)
5.取得 LINE Notify 權杖,請務必==先複製==再關閉喔!
![](https://i.imgur.com/kAuZj8U.png)
6.發行結果如下,如要取消可點選「解除」
![](https://i.imgur.com/8iIhKYb.png)
權杖發行或解除後,也會透過LINE訊息通知喔!
![](https://i.imgur.com/oRcRjYY.png)
### LINE Notify 的限制
- 單一個人申請的發行權杖==每小時最多能發送1000則訊息==
- 每則訊息不可超過==1000個字==
- 每個基本使用者最多可以申請==100個權杖==
- 圖片大小也有限制,更詳細的限制可以參考API文件
![](https://i.imgur.com/5zoqZuF.png)
### 最基本的 LINE Notify 訊息發送程式範例
```javascript=
function sendLineNotifyMessage() {
var token = '請在這裡貼入 LINE Notify 發行權杖';
var options =
{
"method" : "POST",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': 'Hello World!'
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
:::success
:pushpin: UrlFetchApp可以用來擷取、連結外部服務或資源,讓 Google Apps Script 程式碼可以與其他應用程式通訊(發出HTTP請求),詳細參考文件 : https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app
:::
**程式碼執行後的通知訊息發送結果:**
請注意,發送的訊息前方會加上 ==【...】== 來標示建立時所設定的權杖名稱,
![](https://i.imgur.com/8Da8l1S.png)
### 透過 LINE Notify 訊息發送圖片
LINE Notify 也可以發送圖片,可採用以下2種方式,將參數添加到原本的JSON結構上傳遞:
1. 加上imageFullsize (完整圖片,最大尺寸為 2048×2048px JPEG) 及imageThumbnail (縮圖, 最大尺寸為 240×240px JPEG),分別傳入URL即可。
2. 加上imageFile,傳入照片檔案的二進制格式,支援JPEG及PNG格式;這個動作會將圖片上傳到LINE伺服器上。(具有每小時上傳次數的限制)
:::danger
備註: 如果imageFullsize、imageThumbnail、imageFile三個屬性都一起傳送,則會LINE Notify優先使用imageFile。
> 附帶一提,LINE Notify也可以傳遞貼圖,只要照API文件內的說明傳入stickerPackageId及stickerId參數即可。
:::
#### 將雲端硬碟上的圖片上傳至 LINE Notify
```javascript=
function sendLineNotifyMessage() {
var token = '你的權杖';
var imageFile = DriveApp.getFileById("你雲端硬碟檔案上的ID").getBlob();
var options =
{
"method" : "POST",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': 'GAS訊息測試_只傳圖片' ,
'imageFile' : imageFile,
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
#### LINE Notify 傳遞貼圖
```javascript=
function sendLineNotifyMessage() {
var token = '你的權杖';
var options =
{
"method" : "POST",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': 'GAS訊息測試_只傳圖片' ,
'stickerPackageId' : '789' ,
'stickerId' : '10855',
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
#### 行程通知範例
:::success
:pushpin: 底下用於Date日期/時間格式化的函數format取自網路上的範例,來源如下網址: https://blog.scottchayaa.com/post/2019/05/27/javascript_date_memo/
:::
```javascript=
var token = '你的權杖';
Date.prototype.format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小時
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
function main() {
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var now = new Date();
for(var i = 1; i < data.length; i++ ){
if(now.toDateString() == data[i][2].toDateString() ){
var msg = "\n---今日行程通知---\n";
msg += "活動名稱:" + data[i][0] + "\n";
msg += "活動地點:" + data[i][1]+ "\n";
msg += "活動日期:" + data[i][2].format("yyyy-MM-dd");
sendLineNotifyMessage(msg)
}
}
}
function sendLineNotifyMessage(str) {
var options =
{
"method" : "POST",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': str
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
### 開放資料 (Open Data)
:::info
:pushpin: 常見的政府部門 Open Data 平臺
1. 氣象資料開放平臺 https://opendata.cwb.gov.tw/
2. 政府資料開放平臺 https://data.gov.tw/
3. 經濟部水利署水利資料開放平台 https://opendata.wra.gov.tw/
:::
#### 台南市36小時天氣範例程式碼
```javascript=
var token = '你的權杖';
var url = '天氣的URL';
Date.prototype.format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小時
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
function main() {
var response = UrlFetchApp.fetch(url);
var data = JSON.parse(response);
var locationData = data["cwbopendata"]["dataset"]["location"];
var tainanData = locationData.find(function(item, index, array){
return item["locationName"] == "臺南市"
});
var data_wx = tainanData["weatherElement"].find(function(item, index, array){
return item["elementName"] == "Wx"
});
var data_pop = tainanData["weatherElement"].find(function(item, index, array){
return item["elementName"] == "PoP"
});
var data_ci = tainanData["weatherElement"].find(function(item, index, array){
return item["elementName"] == "CI"
});
var msg = "臺南市天氣36小時預報\n\n";
for(var i = 0 ; i < 3 ; i++ ){
var start = new Date(data_wx["time"][i]["startTime"])
var end = new Date(data_wx["time"][i]["endTime"])
var wx = data_wx["time"][i]["parameter"]["parameterName"]
var pop = data_pop["time"][i]["parameter"]["parameterName"]
var ci = data_ci["time"][i]["parameter"]["parameterName"]
msg += start.format("yyyy-MM-dd hh:mm") + "~" + end.format("yyyy-MM-dd hh:mm")
msg += "\n天氣概況:" + wx
msg += "\n降雨機率:" + pop + "%"
msg += "\n舒適程度:" + ci
msg += "\n------------\n"
}
sendLineNotifyMessage(msg);
}
function sendLineNotifyMessage(str) {
var options =
{
"method" : "POST",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': str
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
![](https://i.imgur.com/FNRP6xB.png)
#### 解析XML格式 - 以中央通訊社政治新聞為例
```javascript=
var token = '你的權杖';
var url = 'https://feeds.feedburner.com/rsscna/politics';
function main() {
var response = UrlFetchApp.fetch(url);
var data = XmlService.parse(response);
var root = data.getRootElement();
var channel = root.getChild("channel");
var items = channel.getChildren("item");
var msg = "中央通訊社政治新聞 \n\n";
for(var i = 0 ; i < items.length ; i++ ){
msg += items[i].getChild("title").getText();
msg += "\n";
msg += items[i].getChild("link").getText();
msg += "\n------------\n"
}
sendLineNotifyMessage(msg);
}
function sendLineNotifyMessage(str) {
var options =
{
"method" : "POST",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': str
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
![](https://i.imgur.com/0WeFl47.png)
### LINE Notify 登錄服務
前面已經學到如何發送 LINE Nofity 訊息的基本技能了,但卻只能發送給開發者自己的帳號或指定的聊天室。
如果需要發送給不特定人,就需要完整的整合登入機制,首先要先將你的服務登錄到 LINE Notify 後台,並依據其所提供的 OAuth 機制設計一個網頁介面提供登入認證,登入後取得權杖後即可發送訊息給特定LINE帳號。
先體驗一下:https://script.google.com/a/macros/g.ksu.edu.tw/s/AKfycbyad4UTDy94iORdObOSqsRXgkFz6CBj5ehfEx5j3lq4QnLsON-pW5YJl6uKG69NtdNqPQ/exec
#### 1. 基本流程概觀:
1. 呼叫 https://notify-bot.line.me/oauth/authorize ,取得使用者的授權碼。如果使用者沒有登入LINE,則會跳到LINE的登入介面。
2. 呼叫 https://notify-bot.line.me/oauth/token ,取得對應的權杖(Token),此權杖就是日後要發送訊息給這位使用者的依據,所以程式後端應該將這個權杖儲存起來,以供日後訊息發送之用。
3. 接下來,就可以使用上述取得的權杖進行訊息發送。
#### 2. 連結功能的具體開發流程:
步驟一、到 LINE Nofity 網頁完成服務登錄並取得相關資訊
請前往 : https://notify-bot.line.me/my/services/
![](https://i.imgur.com/PsbJXYS.png)
步驟三、建立提供使用者登錄的網頁介面
重要小提示:重要且不對外開放呼叫的函式應該設為私有函式,也就是要在函式名稱後加上下底線(_)
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base target="_top">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<script>
function linkLineNotify() {
var URL = 'https://notify-bot.line.me/oauth/authorize?';
URL += 'response_type=code';
URL += '&client_id=<?=client_id ?>';
URL += '&redirect_uri=<?=redirect_uri ?>';
URL += '&scope=notify&state=NO_STATE';
window.open(URL, "帳號驗證", "top");
}
</script>
<title>LINE Notify 測試</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12">
<div class="d-grid">
<button onclick="linkLineNotify();" class="btn btn-primary"> 連結 LINE Notify 通知服務 </button>
</div>
</div>
</div>
</div>
</body>
</html>
```
步驟三、接收Callback處理函式(用GET接收)
```javascript=
var redirect_uri = "URL";
var client_id = "你的client_id";
var client_secret = "你的client_secret";
function doGet(e){
if(e.parameter.code){
var data = JSON.parse(getAccessToken_(e.parameter.code));
var str = "連結成功!你的權杖是: " + data.access_token;
SpreadsheetApp.getActiveSheet().appendRow([data.access_token]);
return HtmlService.createHtmlOutput(str);
}
else{
return HtmlService.createTemplateFromFile("index").evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
}
function sendMessageToAll(){
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
for(var i = 0; i < data.length ; i++ ){
sendLineNotifyMessage_("Hello" , data[i][0])
}
}
function getAccessToken_(code){
var options =
{
"method" : "post",
"Content-Type" : "application/x-www-form-urlencoded",
"payload" : {
"grant_type" : "authorization_code",
"code" : code,
"redirect_uri": redirect_uri,
"client_id" : client_id,
"client_secret" : client_secret
}
};
var response = UrlFetchApp.fetch("https://notify-bot.line.me/oauth/token", options);
return response;
}
function sendLineNotifyMessage_(str,token) {
var options =
{
"method" : "post",
"headers" : {"Authorization" : "Bearer "+ token},
"payload":{
'message': str ,
'notificationDisabled' : true,
}
};
var response = UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
小提示:加上 ==.addMetaTag('viewport', 'width=device-width, initial-scale=1')== 是因為 Google Apps Script的網頁被放置在iframe中,為了達到RWD的效果,必須在外層網頁加上這段Meta Tag。
網頁顯示結果:
![](https://i.imgur.com/6I653Cf.png)
點選連結按鈕並登入後:
![](https://i.imgur.com/r4HPNwL.png)
![](https://i.imgur.com/uYP0wXC.png)
![](https://i.imgur.com/Q1GDRYI.png)
---
# 課外補充:HTML常用元素
Web入門: HTML 基礎
https://developer.mozilla.org/zh-TW/docs/Learn/Getting_started_with_the_web/HTML_basics
### 圖片
```HTML==
<img src="images/firefox-icon.png" alt="My test image" />
```
### 標題
```HTML==
<h1>My main title</h1>
<h2>My top level heading</h2>
<h3>My subheading</h3>
<h4>My sub-subheading</h4>
```
### 段落
```htmlembedded==
<p>This is a single paragraph</p>
```
### 清單
```htmlembedded=
<ul>
<li>technologists</li>
<li>thinkers</li>
<li>builders</li>
</ul>
```
備註: ol=有序清單 ul=無序清單
### 表格
```htmlembedded=
<table border="1">
<tr>
<th>這裡是第一行的第一個欄位</th>
<th>這裡是第一行的第二個欄位</th>
</tr>
<tr>
<td>這裡是第二行的第一個欄位</td>
<td>這裡是第二行的第二個欄位</td>
</tr>
</table>
<table border="1">
<tr>
<td>這裡是第一行的第一個欄位</td>
<td>這裡是第一行的第二個欄位</td>
</tr>
<tr>
<td>這裡是第二行的第一個欄位</td>
<td>這裡是第二行的第二個欄位</td>
</tr>
</table>
<table border="1">
<thead>
<tr>
<th>這裡是第一行的第一個欄位</th>
<th>這裡是第一行的第二個欄位</th>
</tr>
</thead>
<tbody>
<tr>
<td>這裡是第二行的第一個欄位</td>
<td>這裡是第二行的第二個欄位</td>
</tr>
</tbody>
</table>
```
### 超連結
```htmlembedded==
<a href="https://cee.ksu.edu.tw">崑山科技大學推廣教育課程網</a>
<br />
<a href="https://www.ksu.edu.tw">崑山科技大學首頁</a>
```
> 備註:上面的 br 標籤代表換行
---
# 其他參考資料
## 課程使用網址
1. Google Apps Script : https://script.google.com/
2. Google Drive (雲端硬碟) : https://drive.google.com/
3. Google Docs (文件) : https://docs.google.com/
4. LINE Notify : https://notify-bot.line.me/zh_TW/
5. YAMM (Yet Another Mail Merge) : https://yamm.com/
6. JSFiddle (輔助你製作網頁及看到結果) : https://jsfiddle.net/
7. HTML.cafe (另一個輔助製作網頁的工具) : https://html.cafe/
### 參考資料 :bookmark_tabs:
1. 課程大綱:https://cee.ksu.edu.tw/2517
2. 輔助教材書籍網址:http://books.gotop.com.tw/v_ACU084300
3. Google官方參考資料 - GAS指南 : https://developers.google.com/apps-script/overview?authuser=1
(可以從這邊了解到一些 Google Apps Script 基本介紹、開發架構等資料,如果你想把VBA轉移到GAS,這邊也會有資料)
4. Google官方參考資料 - 參考資料 : https://developers.google.com/apps-script/reference?authuser=1
(當你想知道特定服務如何呼叫,例如想利用GAS存取試算表、想利用GAS建立文件、想利用GAS建立行事曆...,來這邊找就對了!)
5. Google官方參考資料 - 其他範例 : https://developers.google.com/apps-script/samples?authuser=1
6. 服務斷線記錄 : https://developers.google.com/apps-script/guides/support/outage-log?authuser=1
7. W3Schools - HTML Tutorial : https://www.w3schools.com/html/
8. Google官方參考資料 - 將GAS發布為外掛程式 : https://developers.google.com/apps-script/add-ons/how-tos/publish-add-on-overview
9. html表單教學: https://www.fooish.com/html/form.html
10. Google Apps Script 免費電子書(PDF)資源 : https://riptutorial.com/ebook/google-apps-script
11. https://blog.miniasp.com/post/2020/02/17/Go-Through-LINE-Notify-Without-Any-Code
### JavaScript Array(陣列)處理方法
常見的方法:filter(), find(), forEach(), map(), every(), some(), reduce()
參考資料:
1. https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array
2. https://www.casper.tw/javascript/2017/06/29/es6-native-array/
### JavaScript 資料型態強制轉型
參考資料:
- https://hackmd.io/@s_jpXuNwRQiUuGCOQAOZuA/BkJlweeIF
- https://www.cythilya.tw/2018/10/15/coercion/
字串轉數值
- parseInt()
- parseFloat()
- Number()
轉換為字串
- String()
- toString() - 各項變數可呼叫的函式
### 利用 .new 網域名稱來快速開啟Google服務
:::info
:pushpin: 參考報導與部落格介紹:
- https://www.ithome.com.tw/news/139155
- https://free.com.tw/google-docs-new-domain-trick/
:::
Google 透過 .new 網域名稱整合了許多常見的服務,透過簡短網址就可以直達各式服務入口,目前已有上百個 .new 網址提供使用,詳細內容請參考 Google 的網站介紹與說明 : https://whats.new/
- doc.new 建立文件
- sheet.new 建立試算表
- slide.new 建立簡報
- form.new 建立表單
- script.new 建立Google Apps Script
- site.new 建立協作平台(Goolge Site)
-
### 參考實例
1. 使用 Gmail 合併郵件和 Google 試算表
https://developers.google.com/apps-script/samples/automations/mail-merge
2. 自訂函式:計算行車距離
https://developers.google.com/apps-script/samples/custom-functions/calculate-driving-distance
3. 自訂函式:計算分級折扣
https://developers.google.com/apps-script/samples/custom-functions/tier-pricing
### 其他網路參考資源
1. https://github.com/googleworkspace/apps-script-samples
2. https://spreadsheet.dev/google-apps-script-tutorial / https://spreadsheet.dev/
3. https://docs.google.com/spreadsheets/d/1Lk6OClOPA8p94fspQrs8-M-W080tb244U-fWGqvnApk/edit#gid=1018260646
4. https://www.wfublog.com/search/label/%E9%9B%BB%E8%85%A6-%20Google-Apps%20Script?&max-results=5
5. https://www.labnol.org/code/google-quiz-score-200301
6. 將多欄位整合為不同形式呈現(20221217範例) https://docs.google.com/spreadsheets/d/1b0I5qN_afALt5c3YjtY32Sy_iXF4vc-X2yPJPdo13zY/edit?usp=sharing
7. cheerio for Google Apps Script https://github.com/tani/cheeriogs
8. https://tanaikech.github.io/tags/google-apps-script/
9. LINE BOT 管理自動化實務分享 - 臺灣學術網路 https://noc.tanet.edu.tw/index.php/download/seminar/seminar-1080121?download=558:1080121-03
10. apps-script-oauth2 https://github.com/googleworkspace/apps-script-oauth2
11. https://percy10442.pixnet.net/blog/post/479711174-%E7%B5%90%E5%90%88googleg%E8%A9%A6%E7%AE%97%E8%A1%A8%E8%88%87line-notify%EF%BC%8C%E5%AE%9A%E6%99%82%E9%80%9A%E7%9F%A5%E8%B3%87%E7%94%A2%E7%8B%80
12. https://blog.scottchayaa.com/post/2019/05/27/javascript_date_memo/
### 如何在 Google Apps Script 使用 Google BigQuery ?
參考資料
1. https://developers.google.com/apps-script/advanced/bigquery
2. https://greenido.wordpress.com/2013/12/16/big-query-and-google-spreadsheet-intergration/
---