Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
GilHogan committed Sep 4, 2024
2 parents 20a6670 + 17a8171 commit 83e53c4
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 133 deletions.
59 changes: 9 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
- 除了一口价抢购方式外,加价抢购方式均以他人的出价为基础进行加价
- 关闭抢购商品所在的浏览器即为结束抢购;抢购还未开始时,最小化浏览器或离开商品页面不会进行后续的出价
- telegram配置中,bot token和chat id参数的获取[参考](https://medium.com/@ManHay_Hong/how-to-create-a-telegram-bot-and-send-messages-with-python-4cf314d9fa3e),或自行搜索解决
- auction.detail请求的curl参数获取方式:

1. 打开系统的chrome浏览器(不带有“受控软件”的相关提示),进入要抢购的商品页面
2. 按F12打开调试窗口,点击调试窗口中的Network(网络)选项卡
3. 复制auction.detail请求的curl参考如下图,如果没有看到auction.detail请求,则刷新页面再试:
4. 把复制的curl粘贴到“auction.detail请求的curl”参数的输入框中进行查询、抢购即可

<img alt='review' src="assets\images\get_curl.png" width="60%" style="">


## 功能🎉

Expand Down Expand Up @@ -50,53 +59,3 @@
```

- arm架构的系统构建方式参考,[参考1](https://github.com/jordansissel/fpm/issues/1801#issuecomment-919877499)[参考2](https://www.beekeeperstudio.io/blog/electron-apps-for-arm-and-raspberry-pi)

## 更新日志📆

### 0.3.5
- 修复商品列表图片加载失败问题
- 添加自动更新功能(MacOS暂不支持)

### 0.3.4
- 桌面通知开关
- 新增后台出价模式
- 可配置出价接口eid参数
- 菜单栏添加项目信息

### 0.3.3
- 减少:循环刷新页面导致的围观次数无意义的增长
- 优化:频繁发送请求,导致京东接口限流的处理
- 商品搜索列表排序优化
- 查询单个商品时仅校验商品id
- 京东出价接口可配置代理
- 可配置默认抢购方式,默认加价幅度和默认出价时间

### 0.3.2
- 添加自动登录功能
- 商品信息部分ui优化
- 2023年即将过去,预祝大家元旦快乐 🎉🎉🎉

### 0.3.1
- arm架构下,主进程console.log抛异常修复
- 添加Telegram通知功能
- 修复商品列表起拍价数据错误问题
- 添加低于起拍价的校验

### 0.3.0

- 模块升级 vue2->vue3
- 主进程与渲染进程通信方式优化,提升安全性
- 抢购方式分类
- 新增暗黑主题;ui 排版优化
- 切换分页页码后列表自动滚动到顶部
- 支持不输入商品名称进行全商品浏览
- 点击商品搜索列表中的商品 id 可直接查询商品详情

### 0.2.15

- 修复 linux 应用图标不显示问题
- 添加 linux-arm64 release 包

### v0.2.14

- 初版
Binary file added assets/images/get_curl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/images/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions constant/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
image_url: "https://img10.360buyimg.com/",
get_js_token_url: "https://gia.jd.com/jsTk.do",
get_user_info_url: "https://used-api.jd.com/common/user/info",
detail_function_param: "functionId=paipai.auction.detail"
},
BiddingMethodOptions: [
{
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "JDTreasureGrabber",
"version": "0.3.5",
"version": "0.4.0",
"author": "hogan <hogan01.id@gmail.com> (https://github.com/GilHogan/JDTreasureGrabber)",
"description": "京东夺宝岛助手",
"private": true,
Expand All @@ -22,9 +22,11 @@
"@vueuse/core": "^10.7.0",
"carlo": "^0.9.46",
"core-js": "^3.16.3",
"curlconverter": "3.21.0",
"dayjs": "^1.11.7",
"electron-updater": "^6.1.8",
"puppeteer-core": "^20.7.2",
"request": "^2.88.2",
"vue": "^3.3.11"
},
"devDependencies": {
Expand Down
97 changes: 56 additions & 41 deletions server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const API = require("../constant/constants").API;
const { getUserDataProperty } = require("./utils/storeUtil");
const http = require('http');
const Constants = require("../constant/constants");
const curlconverterUtil = require("curlconverter/util");
const request = require("request");
const { sendNotice } = require('./utils/noticeUtil');

/**
* 发出出价的请求
Expand Down Expand Up @@ -152,53 +155,57 @@ function fetchBatchInfo(bidId) {
});
}

/**
* 校验商品详情接口的curl,返回商品id
*/
function checkAuctionDetailCurl(auctionDetailCurl) {
let auctionId = null;
if (!auctionDetailCurl) {
sendNotice(`请先设置商品详情接口的curl`);
return auctionId;
}
// curl解析为request请求的传参
const curlOptions = curlconverterUtil.parseCurlCommand(auctionDetailCurl);

if (!curlOptions || !curlOptions.query || !curlOptions.query.body) {
sendNotice(`商品详情接口的curl解析失败`);
return auctionId;
}
const curlBody = JSON.parse(decodeURI(curlOptions.query.body));
if (!curlBody || !curlBody.auctionId) {
sendNotice(`商品详情接口的curl解析失败`);
return auctionId;
}
auctionId = curlBody.auctionId;
return auctionId;
}

/**
* 获得竞拍标的信息请求
* */
function fetchBidDetail(bidId) {
function fetchBidDetail(auctionDetailCurl) {
// 校验商品详情接口的curl
if (!checkAuctionDetailCurl(auctionDetailCurl)) {
return;
}
// curl解析为request请求的传参
const curlOptions = curlconverterUtil.parseCurlCommand(auctionDetailCurl);

return new Promise((resolve, reject) => {

const path = `${API.api_jd_path}?functionId=paipai.auction.detail&t=${new Date().getTime()}&appid=paipai_sale_pc&client=pc&loginType=3&body=${encodeURI("{\"auctionId\":" + bidId + "}")}`;
const options = {
hostname: API.api_jd_hostname,
port: 443,
path: path,
method: "GET",
headers: {
"referer": API.web_api_header_referer
}
};

const req = https.request(options, (res) => {
let rawData = "";

res.setEncoding('utf8');

res.on('data', (chunk) => {
rawData += chunk;
});

res.on('end', () => {
let data = null;
try {
const parsedData = JSON.parse(rawData);
if (parsedData.result && parsedData.result.data) {
data = parsedData.result.data;
}
} catch (e) {
consoleUtil.error("fetchBidDetail error:", e.message);
request(curlOptions, (error, response, body) => {
if (error) {
consoleUtil.error(`fetchBidDetail 请求遇到问题: ${error}`);
reject(error);
} else {
const jsonBody = JSON.parse(body);
if (response.statusCode == 200) {
resolve(jsonBody);
} else {
consoleUtil.error(`fetchBidDetail 请求遇到问题 statusCode: ${response.statusCode}`);
resolve(jsonBody);
}
resolve(data);
});
});

req.on('error', (e) => {
consoleUtil.error(`getBidDetail 请求遇到问题: ${e.message}`);
reject(e);
}
});

req.end();
});
}

Expand Down Expand Up @@ -304,4 +311,12 @@ function sleep(milliseconds = 10) {
});
}

module.exports = { postOfferPrice, fetchBatchInfo, fetchBidDetail, fetchProduct, loopRequestAvoidCurrentLimiting, sleep };
module.exports = {
postOfferPrice,
fetchBatchInfo,
fetchBidDetail,
fetchProduct,
loopRequestAvoidCurrentLimiting,
sleep,
checkAuctionDetailCurl
};
42 changes: 34 additions & 8 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
const puppeteer = require('puppeteer-core');
const findChrome = require('../node_modules/carlo/lib/find_chrome.js');
const querystring = require("querystring");
const { postOfferPrice, fetchBatchInfo, fetchBidDetail, fetchProduct, loopRequestAvoidCurrentLimiting, sleep } = require("./api");
const { postOfferPrice, fetchBatchInfo, fetchBidDetail, fetchProduct,
loopRequestAvoidCurrentLimiting, sleep, checkAuctionDetailCurl } = require("./api");
const dayjs = require('dayjs');
const Constants = require("../constant/constants");
const consoleUtil = require('./utils/consoleLogUtil');
const { sendNotice } = require('./utils/noticeUtil');
const { getUserData, setUserDataProperty } = require('./utils/storeUtil');
const API = Constants.API;
const { interceptHandler } = require('./utils/requestIntercept');

// 商品的ID
let Item_ID;
let AuctionDetailCurl;

// 最高能接受的价格
let MaxPrice;
Expand Down Expand Up @@ -49,18 +52,22 @@ let OfferPriceBack;
* 启动浏览器,加载页面
* */
function goToBid(params) {
const { id, price, bidder, markup, lastBidCountdownTime, biddingMethod, minPrice, offerPriceBack } = params;
const { auctionDetailCurl, price, bidder, markup, lastBidCountdownTime, biddingMethod, minPrice, offerPriceBack } = params;
if (Browser && Browser.isConnected()) {
if (isLogin) {
handleSendNotice("抢购已开始");
return;
}
}

// 校验商品详情接口的curl,返回商品id
const id = checkAuctionDetailCurl(auctionDetailCurl);

// 初始化参数
resetData();

Item_ID = id;
AuctionDetailCurl = auctionDetailCurl;
MaxPrice = price;
MinPrice = minPrice;
Item_URL = API.item_url + Item_ID;
Expand All @@ -77,19 +84,23 @@ function goToBid(params) {
* 更新
* */
async function updateBid(params) {
const { id, price, bidder, markup, lastBidCountdownTime, biddingMethod, minPrice, offerPriceBack } = params;
const { auctionDetailCurl, price, bidder, markup, lastBidCountdownTime, biddingMethod, minPrice, offerPriceBack } = params;

if (!isLogin) {
handleSendNotice("请先点击开始抢购,并登录");
return;
}

// 校验商品详情接口的curl,返回商品id
const id = checkAuctionDetailCurl(auctionDetailCurl);

if (page && Browser) {
// 初始化参数
resetData();

// 更新参数
Item_ID = id;
AuctionDetailCurl = auctionDetailCurl;
MaxPrice = price;
MinPrice = minPrice;
Item_URL = API.item_url + Item_ID;
Expand All @@ -100,6 +111,8 @@ async function updateBid(params) {
OfferPriceBack = offerPriceBack;

consoleUtil.log("updateBid Item_URL = ", Item_URL, Item_ID, MaxPrice, MinPrice, Bidder, Markup, LastBidCountdownTime, BiddingMethod, OfferPriceBack);
// 因为页面会查询商品失败,所以通过内部接口请求商品详情
global.AuctionDetailRes = await fetchBidDetail(AuctionDetailCurl);

if (!page.isClosed()) {
// 关闭之前的页面
Expand All @@ -108,6 +121,8 @@ async function updateBid(params) {
await page.close();
}
page = await Browser.newPage();
// 拦截页面请求的处理
interceptHandler(page);

consoleUtil.log("updateBid page = ", page)

Expand All @@ -123,9 +138,12 @@ async function updateBid(params) {
* */
async function initBid() {

// 因为页面会查询商品失败,所以通过内部接口请求商品详情
global.AuctionDetailRes = await fetchBidDetail(AuctionDetailCurl);
await initBrowser();

page = await Browser.newPage();
// 拦截页面请求的处理
interceptHandler(page);

consoleUtil.log("initBid page = ", page);

Expand Down Expand Up @@ -205,7 +223,10 @@ async function initBrowser() {
Browser = await puppeteer.launch({
executablePath,
headless: false,
defaultViewport: null
defaultViewport: null,
args: [
'--disable-web-security',
],
});

Browser.on("disconnected", () => {
Expand Down Expand Up @@ -261,7 +282,7 @@ async function handleGoToTargetPage() {
setUserDataProperty(Constants.StoreKeys.COOKIES_KEY, page_cookie);

// 查询商品详情
ProductDetail = await loopRequestAvoidCurrentLimiting(() => getBidDetail(Item_ID));
ProductDetail = await loopRequestAvoidCurrentLimiting(() => getBidDetail(AuctionDetailCurl));
if (!ProductDetail || !ProductDetail.auctionInfo || !ProductDetail.auctionInfo.startTime || !ProductDetail.currentTime) {
handleSendNotice(`商品详情获取失败,请检查`, true);
resetData();
Expand Down Expand Up @@ -365,6 +386,8 @@ async function waitForProductStart() {
}, waitMilliseconds)
});
if (page) {
// 更新商品详情接口请求结果
global.AuctionDetailRes = await fetchBidDetail(AuctionDetailCurl);
consoleUtil.log("page start reload.", dayjs().format('YYYY-MM-DD HH:mm:ss'));
// 商品开始抢购时刷新页面,解决页面倒计时不准确,导致出价按钮更新不及时的问题
await page.reload();
Expand Down Expand Up @@ -688,10 +711,13 @@ function mergeCookie(cookie_one, cookie_two) {
/**
* 获得竞拍标的信息
* */
async function getBidDetail(bidId) {
async function getBidDetail(auctionDetailCurl) {
let data = null;
try {
data = await fetchBidDetail(bidId);
const res = await fetchBidDetail(auctionDetailCurl);
if (res.result && res.result.data) {
data = res.result.data;
}
} catch (e) {
consoleUtil.error("getBidDetail error:", e.message);
}
Expand Down
3 changes: 3 additions & 0 deletions server/ipcHandle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

const { goToBid, updateBid, getBidDetail, searchProduct, goToProductPage } = require("./index");
const { getUserDataProperty, setUserDataJsonProperty } = require("./utils/storeUtil");
const { openLinkInBrowser } = require("./utils/commonUtil");

async function ipcHandle(e, args) {
if (!args || !args.event) {
Expand All @@ -25,6 +26,8 @@ async function ipcHandle(e, args) {
data = getUserDataProperty(params);
} else if (event == "setUserDataJsonProperty") {
setUserDataJsonProperty(params.key, params.value);
} else if (event == "openLink") {
openLinkInBrowser(params);
}


Expand Down
Loading

0 comments on commit 83e53c4

Please sign in to comment.