AutoX 腳本導出微信賬單
AutoX 腳本
- GitHub:AutoX
- 開發文檔:Doc
- VSCode插件:Auto.js-Autox.js-VSCodeExt
一個支持無障礙服務的 Android 平臺上的 JavaScript 運行環境 和 開發環境,其發展目標是類似 JsBox 和 Workflow。
前提條件
- 你需要一個安卓手機
- 你需要安裝 AutoX apk
- 你需要一點動手能力
- 手機需要打開無障礙功能才能執行腳本
如果以上條件不能滿足也沒關系,網上沖浪也是可以的。
微信賬單
微信是支持賬單導出的(錢包->賬單->下載賬單->用于個人對賬),有時間和格式限制。使用腳本沒有時間和格式限制,需要指定賬單截止日期即可。
功能列表
- 導出格式
- csv (默認)
- json
- txt (微信默認格式)
- md
- 導出時間范圍
- 示例:2024年10月,和賬單篩選時間范圍一致
使用說明
- 執行腳本前先打開微信賬單頁面
- 再返回
autox頁面執行腳本 - 執行結束會返回到
autox頁面 - 默認保存文件在
/sdcard/Download/目錄
實現思路
使用 autox 獲取頁面布局信息一般常用 id 或 className 獲取頁面布局信息,但是微信賬單頁面都沒有 id 和 className,只能獲取最外面的容器 android.webkit.WebView,一層一層剝洋蔥了。下面是頁面布局的大概結構
布局結構
<view classNam="android.webkit.WebView">
<view >
<!-- 篩選條件 -->
<view >
<view >
<button>全部賬單</button>
<button>統計</button>
<view />
<view />
<!-- 賬單列表 -->
<view >
<view >
<!-- 兩行 view 是一個整體,第一個月份統計,第二月份收入明細 -->
<view >
<view >
<view >
<button>2024-11</button>
<view class="android.widget.TextView">支出<view />
<view class="android.widget.TextView">¥200<view />
<view class="android.widget.TextView">收入<view />
<view class="android.widget.TextView">¥200<view />
<view />
<view />
<view />
<view >
<!-- 明細 -->
<view >
二維碼收款-來自計xxxx,11月00日00點00分,收入0.00元,按鈕。點按兩次并按住可長按
<view />
<view >
二維碼收款-來自計xxxx,11月00日00點00分,收入0.00元,按鈕。點按兩次并按住可長按
<view />
<view >
二維碼收款-來自計xxxx,11月00日00點00分,收入0.00元,按鈕。點按兩次并按住可長按
<view />
<!-- ......更多 -->
<view />
<view />
<view />
<view />
<view classNam="android.app.Dialog" ><view />
<view />
/**
* @description wx 賬單導出腳本
* 1、執行腳本前先打開微信賬單頁面
* 2、返回 autox 頁面執行腳本
* 3、執行結束會返回到 autox 頁面
* 4、默認保存文件在 Download 目錄
* @author Mr.Fang
* @time 2024年11月4日12:01:39
*/
// 等待打開無障礙繼續執行
auto.waitFor();
let endFlag = false; // 結束標識
let format = "csv"; // 默認導出格式
let endCondition = '2024年7月'; // 默認截止日期
const savePath = '/sdcard/Download/'; // 本地路徑
const options = ["json", "md", "txt", "csv"]; // 導出格式
const listData = []; // 賬單數據
const keyMap = new Map(); // 存放日期
/**
* 字符串日期轉標準日期
* @param {*} inputDate 2024年10月22日7時8分
* @returns
*/
function convertDateTime(inputDate) {
// 解析輸入的日期字符串
const match = inputDate.match(/(\d+)年(\d+)月(\d+)日(\d+)點(\d+)分/);
if (!match) {
return '輸入的日期格式不正確';
}
if (match) {
const year = match[1];
const month = String(match[2]).padStart(2, '0'); // 確保月份為兩位
const day = String(match[3]).padStart(2, '0'); // 確保日期為兩位
const hour = String(match[4]).padStart(2, '0'); // 轉換為24小時制
const minute = String(match[5]).padStart(2, '0'); // 確保分鐘為兩位
// 構建標準時間格式
const standardTime = `${year}-${month}-${day} ${hour}:${minute}:00`;
return standardTime;
} else {
console.log('輸入格式不正確');
}
return "";
}
/**
* 驗證截止日期
* @param {*} inputDate 2024年10月
* @returns
*/
function verifyDateTime(inputDate) {
// 解析輸入的日期字符串
const dateParts = inputDate.match(/(\d+)年(\d+)月/);
if (!dateParts) {
return '輸入的截止日期不正確';
}
return '';
}
/**
*
* @returns 返回最大長度
*/
function calcMaxLength() {
const mergedArray = listData.reduce((acc, item) => { return acc.concat(item.list) }, []);
return mergedArray.reduce((max, item) => Math.max(max, (item.type ? item.type.length : 0) + item.user.length), 10);
}
// 轉 csv
function textConvertCSV() {
// 輸出 csv 格式
let text = '時間,用戶名,類型,資金\n';
const mergedArray = listData.reduce((acc, item) => { return acc.concat(item.list) }, []);
for (let item of mergedArray) {
let { time, user, type, amount } = item;
text += `${time},${user},${type},${amount}\n`;
}
return text;
}
// 轉 md
function textConvertMD() {
let text = "| 時間 | 用戶名 | 類型 | 金額 |\n";
text += "| ------------------ | ------ | ---- | ---- |\n";
const mergedArray = listData.reduce((acc, item) => { return acc.concat(item.list) }, []);
for (let item of mergedArray) {
let { time, user, type, amount } = item;
if (user.includes("*")) {
user = user.replace("*", "\\*");
}
text += `|${time}|${user}|${type}|${amount}|\n`;
}
return text;
}
// 轉 txt
function textConvertTxt() {
let text = "";
const maxlength = calcMaxLength();
for (let item of listData) {
let { key, list } = item;
let keys = key.split('|');
text += `${keys[0]}\t\t支出¥${keys[1]} 收入¥${keys[2]}\n`;
list.forEach(detail => {
// 構造空字符
let padding = ' '.repeat(maxlength - (detail.type ? detail.type.length : 0) - detail.user.length);
text += `${detail.type}-${detail.user}${padding}${detail.amount}\n`;
text += `${detail.time}\n`;
})
text += '\n\n';
}
return text;
}
/**
* 保存到本地
*/
function saveLocal() {
// 輸出 csv 格式
let content = '';
switch (format) {
case 'csv': content = textConvertCSV(); break;
case 'md': content = textConvertMD(); break;
case 'txt': content = textConvertTxt(); break;
default: content = JSON.stringify(listData);
}
const fullPath = savePath + endCondition + '.' + format
files.write(fullPath, content)
console.log('文件寫入成功:', fullPath);
return fullPath;
}
/**
* 字符串金額轉金額
* @param {*} transaction
* @returns
*/
function convertAmount(transaction) {
// 使用正則表達式提取金額
const amountMatch = transaction.match(/(\d+\.?\d*)元/);
if (!amountMatch) return '無效的交易格式';
// 提取金額并轉換為浮點數
const amount = parseFloat(amountMatch[1]);
// 根據交易類型添加正負號
if (transaction.includes('收入')) {
return `+${amount.toFixed(2)}`;
} else if (transaction.includes('支出')) {
return `-${amount.toFixed(2)}`;
} else {
return '無效的交易類型';
}
}
// 定義一個函數來比較兩個對象是否相等
function isSameEntry(entryA, entryB) {
return entryA.user === entryB.user && entryA.time === entryB.time && entryA.amount === entryB.amount;
}
/**
* 添加賬單數據
* @param {*} key 日期
* @param {*} data 明細
*/
function addBill(key, data) {
if (keyMap.has(key)) {
let index = keyMap.get(key);
let { list } = listData[index];
// 過濾掉arrayA中存在于arrayB的項
let filtered = list.filter(a => !data.some(b => isSameEntry(a, b)));
// 合并 filtered 和 data
listData[index].list = filtered.concat(data);
} else {
let index = listData.length;
listData.push({ key: key, list: data });
keyMap.set(key, index);
}
}
function loadData() {
// 獲取頁面根賬單節點
let webView = className("android.webkit.WebView").findOne(1000);
// 賬單列表節點
let elements = webView.children()[0].children()[1].children()[0].children();
// 節點數量
let length = elements.length;
console.log("賬單數量:", length);
for (let i = 0; i < length; i += 2) {
console.log(i);
if (i + 1 >= length) {
console.log('提前結束')
break;
}
// 月份統計數據 示例:2024年11月 支出$00.00收入$00.00
let firstChildrens = elements[i].children();
// 賬單列表 示例:二維碼收款-來自*M,10月18日7點44分,+6.00,……
let lastChildrens = elements[i + 1].children();
if (firstChildrens.length === 0) {
console.log('跳過空節點')
continue;
}
console.log('firstChildrens:', firstChildrens.length);
console.log('lastChildrens:', lastChildrens.length);
let months = firstChildrens[0].children().length === 1 ? firstChildrens[0].children()[0].children() : firstChildrens[0].children();
console.log('months', months.length)
if (months.length === 0) {
continue;
}
let year = months[0].text();
if (year === endCondition) {
endFlag = true;
console.log("提前結束")
return true;
}
let output = months[2].text();
let input = months[4].text();
console.log(`時間:${year} 支出:${output} 收入:${input}`)
let listTemp = [];
lastChildrens.forEach(item => {
// 二維碼收款-來自計*xxx,10月30日8點48分,收入1.00元,按鈕。點按兩次并按住可長按
let text = item.text();
if (text) {
let list = text.split(',');
list.pop();
// 收入類型-用戶 時間 收入 支出
let user = list[0], type = "";
let firstIndex = user.indexOf('-');
if (firstIndex != -1) {
type = user.substring(0, firstIndex);
user = user.substring(firstIndex + 1);
}
let time = convertDateTime(year.substring(0, 5) + list[1]);
let amount = convertAmount(list[2]);
console.log(time, '\t', type, '\t', user, '\t', amount);
listTemp.push({ time, type, user, amount })
}
})
addBill(year + "|" + output + "|" + input, listTemp);
}
// 結束標識
if (length >= 2) { // 倒數第二個結束標識
const end = elements[length - 2];
const entText = end.children()[0].text();
console.log('entText', entText);
if (entText === "暫無更多記錄") {
return true;
}
}
// 滾動頁面
webView.scrollForward();
return false;
}
/**
* 定時器,每隔 5 秒執行一次
*/
function startInterval() {
launch("com.tencent.mm");
sleep(2000);
const button = text("全部賬單").findOne(1000);
if (button) {
const interval = setInterval(() => {
if (endFlag) {
clearInterval(interval);
const local = saveLocal();
launch("org.autojs.autoxjs.v7");
sleep(1000);
alert("保存路徑" + local)
} else {
endFlag = loadData();
}
}, 1000)
} else {
launch("org.autojs.autoxjs.v7");
sleep(1000);
toast("請打開賬單頁面")
}
}
/**
* 開始方法
*/
function start() {
const input = dialogs.rawInput("請輸入截止年月", endCondition);
console.log('input:', input);
const result = verifyDateTime(input);
if (result) {
toastLog(result);
} else {
endCondition = input
const selectIndex = dialogs.singleChoice("請選擇導出格式", options, 3);
format = options[selectIndex];
console.log(format);
toastLog("開始執行");
startInterval();
}
}
start();
導出示例
txt
2024年10月 支出¥200.00元 收入¥245.89元
二維碼收款-來自計*g +10.00
2024-10-30 08:48:00
二維碼收款-來自B*m +1.00
2024-10-24 10:53:00
二維碼收款-來自L*S +5.00
2024-10-22 16:56:00
二維碼收款-來自*鹿 +6.00
2024-10-18 07:44:00
轉賬-來來xxxx- yo +100.00
2024-10-17 16:00:00
二維碼收款-來xxxx- yo +18.88
2024-10-17 14:46:00
…………省略
md
| 時間 | 用戶名 | 類型 | 金額 |
| ------------------- | ------------ | ---------- | ------- |
| 2024-10-30 08:48:00 | 來自計\*g | 二維碼收款 | +10.00 |
| 2024-10-24 10:53:00 | 來自B\*m | 二維碼收款 | +1.00 |
| 2024-10-22 16:56:00 | 來自L\*S | 二維碼收款 | +5.00 |
| 2024-10-18 07:44:00 | 來自\*鹿 | 二維碼收款 | +6.00 |
| 2024-10-17 16:00:00 | 來自xxxx- yo | 轉賬 | +100.00 |
| 2024-10-17 14:46:00 | 來自xxxx- yo | 二維碼收款 | +18.88 |
…………省略
csv
時間,用戶名,類型,資金
2024-10-30 08:48:00,來自計*g,二維碼收款,+10.00
2024-10-24 10:53:00,來自B*m,二維碼收款,+1.00
2024-10-22 16:56:00,來自L*S,二維碼收款,+5.00
2024-10-18 07:44:00,來自*鹿,二維碼收款,+6.00
2024-10-17 16:00:00,來自xxxx- yo,轉賬,+100.00
2024-10-17 14:46:00,來自xxxx- yo,二維碼收款,+18.88
…………省略
哇!又賺了一天人民幣

浙公網安備 33010602011771號