New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

alipay-bills

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

alipay-bills - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

libs/utils/create-driver.d.ts

36

libs/index.d.ts

@@ -1,4 +0,32 @@

import query from './public/query';
import setUser from './public/set-user';
import options from './public/options';
export { query, setUser, options };
export declare const options: {
debug: boolean;
interval: number;
params: {};
};
export interface Bill {
createTime?: number;
memo: string;
name: string;
orderNo?: string;
tradeNo?: string;
target: string;
amount: number;
status: string;
}
/**
* 监听事件
* @param {string} name
* @param {function} handler
*/
export declare function on(name: string, handler: (...args: any[]) => any): void;
/**
* 使用指定的用户名和密码开始循环读取数据
* @param {string} user - 用户名
* @param {string} pwd - 密码
*/
export declare function start(user: string, pwd: string): Promise<void>;
/**
* 停止刷新
* @return {Promise<void>}
*/
export declare function stop(): Promise<void>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var query_1 = require("./public/query");
exports.query = query_1.default;
var set_user_1 = require("./public/set-user");
exports.setUser = set_user_1.default;
var options_1 = require("./public/options");
exports.options = options_1.default;
var tslib_1 = require("tslib");
var EventEmitter = require("events");
var querystring_1 = require("querystring");
var selenium_webdriver_1 = require("selenium-webdriver");
var create_driver_1 = require("./utils/create-driver");
var constants_1 = require("./utils/constants");
var titleIs = selenium_webdriver_1.until.titleIs, elementLocated = selenium_webdriver_1.until.elementLocated;
var driver;
var logged = false;
var timeId;
var stopped = true;
var lastTradeNo;
var refreshing;
var event = new EventEmitter();
exports.options = {
debug: false,
interval: 60 * 1000,
params: {}
};
/**
* 在控制台输出日志
* @param msg
*/
function log(msg) {
if (exports.options.debug) {
console.log("[" + new Date().toLocaleString() + "]", msg);
}
}
/**
* 模拟人类的输入行为,输入每个字符之间间隔一段时间
* @param {WebElementPromise} ele
* @param {string} str
* @return {Promise<void>}
*/
function humanInput(ele, str) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var i;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, ele.clear()];
case 1:
_a.sent();
i = 0;
_a.label = 2;
case 2:
if (!(i < str.length)) return [3 /*break*/, 6];
return [4 /*yield*/, ele.sendKeys(str[i])];
case 3:
_a.sent();
return [4 /*yield*/, driver.sleep(500)];
case 4:
_a.sent();
_a.label = 5;
case 5:
i++;
return [3 /*break*/, 2];
case 6: return [2 /*return*/];
}
});
});
}
/**
* 使用当前的账号与密码登录并将 driver 跳转到「我的账户」高级版
* @param {string} user - 用户名
* @param {string} pwd - 密码
* @return {Promise<void>}
*/
function login(user, pwd) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var elements, e_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!driver)
driver = create_driver_1.default();
return [4 /*yield*/, logout()];
case 1:
_a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 19, , 21]);
// 跳转到登陆页
return [4 /*yield*/, log('正在跳转到登陆页……')];
case 3:
// 跳转到登陆页
_a.sent();
return [4 /*yield*/, driver.get(constants_1.URLs.login + '?goto=' + encodeURIComponent(constants_1.URLs.billsIndex))
// 输入用户名
];
case 4:
_a.sent();
// 输入用户名
return [4 /*yield*/, log('正在输入用户名……')];
case 5:
// 输入用户名
_a.sent();
return [4 /*yield*/, humanInput(driver.findElement({ id: 'J-input-user' }), user)
// 输入密码
];
case 6:
_a.sent();
// 输入密码
return [4 /*yield*/, log('正在输入密码……')];
case 7:
// 输入密码
_a.sent();
return [4 /*yield*/, humanInput(driver.findElement({ id: 'password_rsainput' }), pwd)
// 点击登陆按钮
];
case 8:
_a.sent();
// 点击登陆按钮
return [4 /*yield*/, log('正在点击登陆按钮……')];
case 9:
// 点击登陆按钮
_a.sent();
return [4 /*yield*/, driver.sleep(500)
// 故意没有 await 下面的语句,这样 5 秒内检测到没跳转到账单页就重试
];
case 10:
_a.sent();
// 故意没有 await 下面的语句,这样 5 秒内检测到没跳转到账单页就重试
driver.findElement({ id: 'J-login-btn' }).click();
// 确认浏览器跳转到了账单页
return [4 /*yield*/, log('正在等待浏览器跳转到账单页……')];
case 11:
// 确认浏览器跳转到了账单页
_a.sent();
return [4 /*yield*/, driver.wait(titleIs(constants_1.BillPageTitle), 5000, '5 秒内没有跳转到账单页')
// 判断是否在「我的账单」高级版页面
];
case 12:
_a.sent();
return [4 /*yield*/, driver.findElements({ id: 'main' })];
case 13:
elements = _a.sent();
if (!!elements.length) return [3 /*break*/, 17];
return [4 /*yield*/, log('检测到当前是标准版账单页,正在跳转到高级版账单页……')];
case 14:
_a.sent();
return [4 /*yield*/, driver.get(constants_1.URLs.billsSwitch)];
case 15:
_a.sent();
return [4 /*yield*/, driver.wait(elementLocated({ id: 'main' }), 5000, '5 秒内没有跳转到高级版账单页')];
case 16:
_a.sent();
_a.label = 17;
case 17:
logged = true;
return [4 /*yield*/, log('登陆成功')];
case 18:
_a.sent();
return [3 /*break*/, 21];
case 19:
e_1 = _a.sent();
return [4 /*yield*/, log('尝试登陆时失败:「' + e_1.message + '」,正在重试……')];
case 20:
_a.sent();
return [2 /*return*/, login(user, pwd)];
case 21: return [2 /*return*/];
}
});
});
}
/**
* 停止循环并退出登录
* @return {Promise<void>}
*/
function logout() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(driver && logged)) return [3 /*break*/, 2];
log('准备登出账号……');
logged = false;
clearTimeout(timeId);
return [4 /*yield*/, driver.get(constants_1.URLs.logout)];
case 1:
_a.sent();
log('已登出账号。');
_a.label = 2;
case 2: return [2 /*return*/];
}
});
});
}
/**
* 循环刷新页面并读取数据
* @return {Promise<void>}
*/
function refresh() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _this = this;
return tslib_1.__generator(this, function (_a) {
if (stopped)
return [2 /*return*/];
refreshing = new Promise(function (resolve) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var bills;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, query(lastTradeNo)];
case 1:
bills = _a.sent();
if (bills.length) {
lastTradeNo = bills[0].tradeNo;
event.emit('new bills', bills);
}
resolve();
return [2 /*return*/];
}
});
}); });
timeId = setTimeout(refresh, exports.options.interval);
return [2 /*return*/];
});
});
}
/**
* 查询账单数据
* @param {string} tradeNo - 根据流水号判断订单查询应该何时结束
* @param params - 页面参数
* @return {Promise<Bill[]>}
*/
function query(tradeNo, params) {
if (params === void 0) { params = exports.options.params; }
return tslib_1.__awaiter(this, void 0, void 0, function () {
function queryPage() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var pageBills, tradeIndex, els;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!driver)
return [2 /*return*/];
return [4 /*yield*/, driver.get(constants_1.URLs.billsAdvanced + '?' + querystring_1.stringify(Object.assign({ page: page }, params)))];
case 1:
_a.sent();
return [4 /*yield*/, driver.wait(elementLocated({ id: 'tradeRecordsIndex' }), 5000)];
case 2:
_a.sent();
return [4 /*yield*/, driver.executeScript(getBills)
// 如果没有设置终点流水号,则只返回第一页的数据
];
case 3:
pageBills = _a.sent();
// 如果没有设置终点流水号,则只返回第一页的数据
if (!tradeNo) {
bills.push.apply(bills, pageBills);
return [2 /*return*/];
}
pageBills.some(function (bill, index) {
if (bill.tradeNo === tradeNo) {
tradeIndex = index;
return true;
}
return false;
});
// 如果找到了匹配的流水号,则将前面的账单数据推入数组中并中断查询
if (typeof tradeIndex === 'number') {
bills.push.apply(bills, pageBills.slice(0, tradeIndex));
return [2 /*return*/];
}
// 没有找到匹配的流水号,则把这页数据全都推入数组
bills.push.apply(bills, pageBills);
return [4 /*yield*/, driver.findElements({ className: 'page-next' })];
case 4:
els = _a.sent();
if (els.length) {
page += 1;
return [2 /*return*/, queryPage()];
}
return [2 /*return*/];
}
});
});
}
var bills, page;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!logged)
return [2 /*return*/, Promise.reject(new Error('Login first.'))];
bills = [];
page = 1;
return [4 /*yield*/, queryPage()];
case 1:
_a.sent();
return [2 /*return*/, bills];
}
});
});
}
/**
* 这个函数会运行在浏览器里通过 DOM 分析出账单数据
*/
function getBills() {
var bills = [];
// 支付宝网站用了 jQuery 所以这里可以使用
jQuery('#tradeRecordsIndex tbody tr').each(function () {
var $tr = jQuery(this);
var bill = {
memo: $tr.find('td.memo .memo-info').text().trim(),
name: $tr.find('td.name a').text().trim(),
target: $tr.find('td.other .name').text().trim(),
amount: Number($tr.find('td.amount .amount-pay').text().trim().replace(/\s+/g, '')),
status: $tr.find('td.status').text().trim()
};
bills.push(bill);
// 通过「操作」里的「备注」链接获取到精确度到秒的时间戳
var link = $tr.find('.action [data-action="edit-memo"]').attr('data-link');
var match = link && link.match(/&createDate=\s*(\d+)/);
var createDateStr = match ? match[1] : null;
if (createDateStr) {
var year = Number(createDateStr.slice(0, 4));
var month = Number(createDateStr.slice(4, 6)) - 1;
var day = Number(createDateStr.slice(6, 8));
var hour = Number(createDateStr.slice(8, 10));
var minute = Number(createDateStr.slice(10, 12));
var second = Number(createDateStr.slice(12, 14));
bill.createTime = new Date(year, month, day, hour, minute, second).getTime();
}
// 获取账单的订单号、交易号或流水号。
// 个人对个人的账单只有流水号,
// 个人与商户之间的交易会有订单号和交易号。
// 支付宝对交易号和流水号是同等对待的。
var tradeStr = $tr.find('td.tradeNo').text().trim();
var nos = tradeStr.split('|');
nos.forEach(function (noStr) {
var _a = noStr.trim().split(':'), key = _a[0], value = _a[1];
switch (key) {
case '交易号':
case '流水号':
bill.tradeNo = value;
break;
case '订单号':
bill.orderNo = value;
break;
}
});
});
return bills;
}
/**
* 监听事件
* @param {string} name
* @param {function} handler
*/
function on(name, handler) {
event.on(name, handler);
}
exports.on = on;
/**
* 使用指定的用户名和密码开始循环读取数据
* @param {string} user - 用户名
* @param {string} pwd - 密码
*/
function start(user, pwd) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var loginPromise;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!stopped) return [3 /*break*/, 2];
return [4 /*yield*/, stop()];
case 1:
_a.sent();
_a.label = 2;
case 2:
loginPromise = login(user, pwd);
loginPromise.then(refresh);
return [2 /*return*/, loginPromise];
}
});
});
}
exports.start = start;
/**
* 停止刷新
* @return {Promise<void>}
*/
function stop() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!driver)
return [2 /*return*/];
stopped = true;
return [2 /*return*/, refreshing];
});
});
}
exports.stop = stop;

7

package.json
{
"name": "alipay-bills",
"version": "0.2.0",
"version": "0.3.0",
"description": "自动获取支付宝的账单信息。",

@@ -11,4 +11,4 @@ "main": "libs/index.js",

"scripts": {
"build": "tsc",
"prepublish": "rm -rf libs && npm run build"
"build": "rm -rf libs && tsc",
"prepublish": "npm run build"
},

@@ -19,3 +19,2 @@ "dependencies": {

"@types/selenium-webdriver": "^3.0.4",
"noshjs": "^0.1.3",
"phantomjs-prebuilt": "^2.1.15",

@@ -22,0 +21,0 @@ "selenium-webdriver": "^3.5.0",

@@ -13,3 +13,3 @@ # alipay-bills

alipay-bills 只有两个方法:
alipay-bills 只有三个方法:

@@ -20,20 +20,37 @@ ```js

alipayBills.options.debug = true // 在控制台输出日志
alipayBills.options.interval = 5000 // 间隔多久刷新一次,默认是 60000 毫秒(即一分钟)
alipayBills.options.params = { // 自定义「我的账单」页面的查询参数,默认没有任何参数。具体有哪些参数可以通过 Chrome 开发者工具分析得出。
// 部分参数举例:
status: 'success', // 交易状态:成功
fundFlow: 'out' // 资金流向:支出
}
// alipayBills 的接口都是异步的,强烈推荐使用 async/await 语法
;(async function () {
// 请确保你的用户名和密码是正确的,否则会无限重试登录。
// 如果你已经登录过了,重新调用这个方法会先将已经登录的账号退出。
await alipayBills.setUser('用户名', '密码')
await alipayBills.query().then(bills => {
// 先监听 `new bills` 事件,每次检测到新账单时会触发这个事件
alipayBills.on('new bills', bills => {
// bills 是一个数组,数组中每一项的结构为:
//{
// day: string - 日期,如 '2017.08.15'
// time: string - 具体时间,如 '04:10'
// name: string - 对应「我的账单高级版」的「名称」
// orderNo: string - 对应「我的账单高级版」的「商户订单号|交易号」
// createTime: number - 精确到「秒」的创建时间戳
// memo: string - 对应「我的账单高级版」中的「备注」
// name: string - 对应「我的账单高级版」中的「名称」
// orderNo: string - 对应「我的账单高级版」的「订单号」,可能为空
// tradeNo: string - 对应「我的账单高级版」的「交易号」或者「流水号」
// target: string - 对应「我的账单高级版」的「对方」
// amount: string - 对应「我的账单高级版」的「金额」
// amount: number - 对应「我的账单高级版」的「金额」,正数代表收入,负数代表支出
// status: string - 对应「我的账单高级版」的「状态」
//}
})
// 请确保你的用户名和密码是正确的,否则会无限重试登录。
// 调用 start 方法后每隔一段时间(具体时间为前面设置的 `options.interval`)就会刷新一次并检测是否有新账单。
// 第一次检测只会将「我的账单」高级版第一页的账单数据传给 `new bills` 事件,
// 后续会循环查询每一页的账单数据,直到碰到上一次查询时的第一个账单信息为止。
await alipayBills.start('用户名', '密码')
// 停止刷新
await alipayBills.stop()
// 重新调用 start 方法会先退出前面登录过的账号
await alipayBills.start('另一个用户名', '另一个密码')
}())

@@ -46,3 +63,3 @@ ```

代码参考了[利用『爬虫』 折衷解决 个人支付宝支付系统 ---- 获取账单信息](https://www.v2ex.com/t/383179)这篇帖子。
部分代码参考了[利用『爬虫』 折衷解决 个人支付宝支付系统 ---- 获取账单信息](https://www.v2ex.com/t/383179)这篇帖子。

@@ -49,0 +66,0 @@ ## 许可

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc