Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@ MailCat 是一個以 Google Apps Script 開發的一套規則管理器。不同
- 主要針對電子帳單的信件。
- MailCat 會將資料夾建立在 Google Drive 的根目錄下。

### 6. 解析消費通知
- 目前只支援國泰銀行
- MailCat 會把新的消費通知的內容統整到一個 Google Spreadsheet 內

## 使用方法

1. 登入 Google 帳戶並且開啟 [Apps Script](https://script.google.com/home/start) 頁面。

2. 建立新專案,然後將 [`main.gs`](https://github.com/HeiTang/MailCat/blob/main/main.gs) [`bank.gs`](https://github.com/HeiTang/MailCat/blob/main/bank.gs) 檔案複製進去並存檔。
2. 建立新專案,然後將 [`main.gs`](https://github.com/HeiTang/MailCat/blob/main/main.gs) [`bank.gs`](https://github.com/HeiTang/MailCat/blob/main/bank.gs) 和 [`parse.gs`](https://github.com/HeiTang/MailCat/blob/main/parse.gs) 檔案複製進去並存檔。

3. 在 `bank.gs` 中的 `BankList_Own` 中填入需管理的銀行代碼(請參考上述所支援銀行)。

Expand All @@ -88,15 +92,20 @@ MailCat 是一個以 Google Apps Script 開發的一套規則管理器。不同
var BankList_Own = ['008', '013', '803', '805', '807', '812', '822', '824'];
```

4. 選擇「觸發條件」點選「新增觸發條件」。
4. 在 Google 建立一個新的試算表,修改 `bank.gs` 中的 `SheetID`,改成試算表的 ID。ID 可以在網址 `https://docs.google.com/spreadsheets/d/<SheetID>` 中找到。

5. 在 Google AppSript 專案頁面中左側找到「資料庫」,按下他旁邊的「+」,在彈出式視窗輸入指令碼 ID `1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0`,按下「新增」按鈕 Cheerio

6. 選擇「觸發條件」點選「新增觸發條件」。

| 執行的功能 | 部署作業 | 活動來源 | 時間型觸發條件類型 | 小時間隔 |
| :--------------: | :------: | :------: | :----------------: | :------------: |
| Bank_Label | 上端 | 時間驅動 | 小時計時器 | 每小時 |
| Bank_AutoRemove | 上端 | 時間驅動 | 日計時器 | 午夜到上午一點 |
| Bank_AutoArchive | 上端 | 時間驅動 | 小時計時器 | 每小時 |
| Bank_AutoSave | 上端 | 時間驅動 | 日計時器 | 午夜到上午一點 |
| Bank_Parse | 上端 | 時間驅動 | 日計時器 | 午夜到上午一點 |

> 參考設定,可自行調整。

5. 如果尚未支援您的銀行,歡迎來發個 PR ~(=^‥^)/
7. 如果尚未支援您的銀行,歡迎來發個 PR ~(=^‥^)/
64 changes: 55 additions & 9 deletions bank.gs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// --- Settings --- //
var BankList_Own = []
var BankList_Own = ['009', '013', '700']
var BankList_Url = "https://raw.githubusercontent.com/HeiTang/MailCat/main/bank_list.json";
var BankRule_Url = "https://raw.githubusercontent.com/HeiTang/MailCat/main/bank_rule.json";

const SheetID = '1GjPuvsMa1OsEcIUwqhpDM1DWWiCIx-VoIhXQuUqf8OI'
// --- Settings --- //

// 0. Initial
var BankList_JSON = GetJSON(BankList_Url); // 1-取得銀行資料 JSON
var BankRule_JSON = GetJSON(BankRule_Url);

// IMPORTANT !!!
// load library 1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0 to use Cheerio to parse mail

// 1. MailLabelManage
function Bank_Label(){
function Bank_Label() {
for (var bankIndex = 0; bankIndex < BankList_Own.length; bankIndex++) {
// 01. BankListLabel
var isImportant = [0, 0, 0];
Expand All @@ -20,7 +24,7 @@ function Bank_Label(){
var bank_email = BankList_JSON[index]['email'];

// 檢查&建立標籤
CheckLabel(bank_label_name);
CheckLabel(bank_label_name);
// 銀行信件標記
MarkLabel(bank_label_name, bank_email, data_type, isImportant[bankIndex]);

Expand All @@ -29,18 +33,18 @@ function Bank_Label(){
data_type = 2;
for (var ruleIndex = 0; ruleIndex < BankRule_JSON.length; ruleIndex++) {
var label_name = BankRule_JSON[ruleIndex]['label_name'];
var bank_rule = [Utilities.formatString("label:%s %s", bank_label_name , BankRule_JSON[ruleIndex]['rule'])];
var bank_rule = [Utilities.formatString("label:%s %s", bank_label_name, BankRule_JSON[ruleIndex]['rule'])];

// 檢查&建立標籤
CheckLabel(label_name);
CheckLabel(label_name);
// 特定信件標記
MarkLabel(label_name, bank_rule, data_type, isImportant[ruleIndex]);
}
}
}

// 2. 定時刪除信件(登入通知)
function Bank_AutoRemove(){
function Bank_AutoRemove() {
var delete_days = [7];
var label_name = [BankRule_JSON[0]['label_name']];
for (var i = 0; i < label_name.length; i++) {
Expand All @@ -49,17 +53,59 @@ function Bank_AutoRemove(){
}

// 3. 自動封存信件(登入通知、交易通知)
function Bank_AutoArchive(){
function Bank_AutoArchive() {
var label_name = [BankRule_JSON[0]['label_name'], BankRule_JSON[1]['label_name']];
for (var i = 0; i < label_name.length; i++) {
AutoArchive(label_name[i]);
}
}

// 4. 備份附件(電子帳單)
function Bank_AutoSave(){
function Bank_AutoSave() {
var folder_name = '銀行電子帳單';
var label_name = BankRule_JSON[2]['label_name'];
var rule = Utilities.formatString("has:attachment is:important label:%s", label_name);
AutoSave(folder_name, label_name, rule);
}
}

function ParseMoney(money) {
// TODO: 其他貨幣沒測試過
let s = money.split('$')
return {
currency: s[0],
amount: Number(s[1].replaceAll(',', '')),
}
}

const supportBanks = { '013-國泰世華': Parse013 }

function Bank_Parse() {
const parsedLabel = '銀行/已建檔'
CheckLabel(parsedLabel)
for (const [bank, parser] of Object.entries(supportBanks)) {
const rule = Utilities.formatString("label:銀行-%s label:銀行-1.交易通知 subject: 消費彙整通知 NOT label:銀行/已建檔", bank)

/**
* @param {string[]} header
* @return {SpreadsheetApp.Sheet}
*/
function initSheet(header) {
// ensure sheet is open
const ss = SpreadsheetApp.openById(SheetID)
let sheet = ss.getSheetByName(bank);
if (sheet == null) {
sheet = ss.insertSheet(bank)
}

if (sheet.getLastRow() != 0) return sheet// already inited

sheet.appendRow(header)

return sheet
}

console.log('開始尋找 %s', bank)
const results = parser(initSheet, rule, parsedLabel)
console.log("%s 找到 %d 筆未登錄交易紀錄", bank, results.length)
}
}
75 changes: 75 additions & 0 deletions parse.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @param {SpreadsheetApp.Sheet} sheet
* @param {any[][]} rows
*/
function appendRows(sheet, rows) {
if (rows.length == 0) return
if (rows.length == 1) {
sheet.appendRow(rows[0])
return
}

sheet.getRange(
sheet.getLastRow() + 1,
1,
rows.length,
rows[0].length
).setValues(rows)

}

// 國泰世華
/**
* @param {(string[]) => SpreadsheetApp.Sheet} initSheet
* @param {string} rule
* @param {string} parsedLabel
* @return {string[][]} Transactions
*/
function Parse013(initSheet, rule, parsedLabel) {
// parse data
const keys = [
"卡別",
"行動卡號後4碼",
"授權日期",
"授權時間",
"消費地區",
"消費金額",
"商店名稱",
"消費類別",
"備註",
]

const sheet = initSheet(keys)

let results = []

for (let thread of GmailApp.search(rule)) {
let body = thread.getMessages()[0].getBody()
const $ = Cheerio.load(body)

$('tbody').each(function () {
let t = $(this)

// 過濾掉嵌套的 table 和不包含刷卡訊息的 tbody
if (t.find('table').length != 0 || !t.text().includes('卡別')) return

let result = {}
let tr = t.find('tr')
for (let i = 0; i < tr.length; i += 2) {
let labels = tr.eq(i).children()
let values = tr.eq(i + 1).children()
for (let j = 0; j < labels.length; j++) {
result[labels.eq(j).text()] = values.eq(j).text().trim()
}
}
results.push(keys.map(key => result[key]))
})

const label = GmailApp.getUserLabelByName(parsedLabel);
thread.addLabel(label)
}

appendRows(sheet, results)

return results
}