diff --git a/README.md b/README.md index b2eddf5..ef48984 100644 --- a/README.md +++ b/README.md @@ -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` 中填入需管理的銀行代碼(請參考上述所支援銀行)。 @@ -88,7 +92,11 @@ 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/` 中找到。 + +5. 在 Google AppSript 專案頁面中左側找到「資料庫」,按下他旁邊的「+」,在彈出式視窗輸入指令碼 ID `1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0`,按下「新增」按鈕 Cheerio + +6. 選擇「觸發條件」點選「新增觸發條件」。 | 執行的功能 | 部署作業 | 活動來源 | 時間型觸發條件類型 | 小時間隔 | | :--------------: | :------: | :------: | :----------------: | :------------: | @@ -96,7 +104,8 @@ MailCat 是一個以 Google Apps Script 開發的一套規則管理器。不同 | Bank_AutoRemove | 上端 | 時間驅動 | 日計時器 | 午夜到上午一點 | | Bank_AutoArchive | 上端 | 時間驅動 | 小時計時器 | 每小時 | | Bank_AutoSave | 上端 | 時間驅動 | 日計時器 | 午夜到上午一點 | + | Bank_Parse | 上端 | 時間驅動 | 日計時器 | 午夜到上午一點 | > 參考設定,可自行調整。 -5. 如果尚未支援您的銀行,歡迎來發個 PR ~(=^‥^)/ +7. 如果尚未支援您的銀行,歡迎來發個 PR ~(=^‥^)/ diff --git a/bank.gs b/bank.gs index 24d03f2..449d1b5 100644 --- a/bank.gs +++ b/bank.gs @@ -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]; @@ -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]); @@ -29,10 +33,10 @@ 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]); } @@ -40,7 +44,7 @@ function Bank_Label(){ } // 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++) { @@ -49,7 +53,7 @@ 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]); @@ -57,9 +61,51 @@ function Bank_AutoArchive(){ } // 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); -} \ No newline at end of file +} + +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) + } +} diff --git a/parse.gs b/parse.gs new file mode 100644 index 0000000..5c9d144 --- /dev/null +++ b/parse.gs @@ -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 +}