Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 替换雪球源为腾讯自选股 #484

Merged
merged 2 commits into from
Oct 13, 2024
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "leek-fund",
"displayName": "韭菜盒子",
"description": "韭菜盒子,VSCode 里也可以看股票 & 基金实时数据,做最好用的投资插件",
"version": "3.11.6",
"version": "3.11.7",
"author": "giscafer <[email protected]>",
"repository": {
"type": "git",
Expand Down
153 changes: 50 additions & 103 deletions src/explorer/stockService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getXueQiuToken } from '../shared/xueqiu-helper';
import { LeekService } from './leekService';
import moment = require('moment');
import Log from '../shared/log';
import { getTencentHKStockData, searchStockList } from '../shared/tencentStock';

export default class StockService extends LeekService {
public stockList: Array<LeekTreeItem> = [];
Expand Down Expand Up @@ -405,89 +406,57 @@ export default class StockService extends LeekService {
let hkStockCount = 0;
let stockList: Array<LeekTreeItem> = [];

const url = `https://stock.xueqiu.com/v5/stock/batch/quote.json?symbol=${codes.join(',')}`;
try {
const resp = await Axios.get(url, {
responseType: 'text',
transformResponse: [
(data) => {
const body = JSON.parse(data);
return body;
},
],
headers: {
...randHeader(),
Referer: 'https://stock.xueqiu.com/',
Cookie: await this.getToken()
},
});
const { data, error_code, error_description } = resp.data;
if (error_code) {
window.showErrorMessage(
`fail: a HK Stock request error has occured. (${error_code}, ${error_description})`
);
const stockData = await getTencentHKStockData(codes.map((code) => `hk${code}`));
if (!stockData) {
return [];
} else {
const stocks = data.items || [];
stocks.forEach((item: any, index: number) => {
if (item.quote) {
const quote = item.quote;
let open = quote.open?.toString() || '0';
let yestclose = quote.last_close?.toString() || '0';
let price = quote.current?.toString() || '0';
let high = quote.high?.toString() || '0';
let low = quote.low?.toString() || '0';
const fixedNumber = calcFixedPriceNumber(open, yestclose, price, high, low);
const stockItem: any = {
code: quote.symbol.startsWith('HK')
? quote.symbol.replace('HK', 'hk')
: 'hk' + quote.symbol,
name: quote.name,
open: formatNumber(open, fixedNumber, false),
yestclose: formatNumber(yestclose, fixedNumber, false),
price: formatNumber(price, fixedNumber, false),
low: formatNumber(low, fixedNumber, false),
high: formatNumber(high, fixedNumber, false),
volume: formatNumber(quote.volume || 0, 2),
amount: formatNumber(quote.amount || 0, 2),
percent: '',
time: `${moment(quote.time).format('YYYY-MM-DD HH:mm:ss')}`,
};
hkStockCount += 1;
if (stockItem) {
const { yestclose, open } = stockItem;
let { price } = stockItem;
// 竞价阶段部分开盘和价格为0.00导致显示 -100%
if (Number(open) <= 0) {
price = yestclose;
}
stockItem.showLabel = this.showLabel;
stockItem.isStock = true;
stockItem.type = 'hk';
stockItem.symbol = quote.code;
stockItem.updown = formatNumber(+price - +yestclose, fixedNumber, false);
stockItem.percent =
(stockItem.updown >= 0 ? '+' : '-') +
formatNumber((Math.abs(stockItem.updown) / +yestclose) * 100, 2, false);

const treeItem = new LeekTreeItem(stockItem, this.context);
stockList.push(treeItem);
const stocks = stockData;
stocks.forEach((item: any) => {
const { open, yestclose, price, high, low, volume, amount, time } = item;
const fixedNumber = calcFixedPriceNumber(open, yestclose, price, high, low);
const stockItem: any = {
...item,
open: formatNumber(open, fixedNumber, false),
yestclose: formatNumber(yestclose, fixedNumber, false),
price: formatNumber(price, fixedNumber, false),
low: formatNumber(low, fixedNumber, false),
high: formatNumber(high, fixedNumber, false),
volume: formatNumber(volume || 0, 2),
amount: formatNumber(amount || 0, 2),
percent: '',
time: `${moment(time).format('YYYY-MM-DD HH:mm:ss')}`,
};
hkStockCount += 1;
if (stockItem) {
const { yestclose, open } = stockItem;
let { price } = stockItem;
// 竞价阶段部分开盘和价格为0.00导致显示 -100%
if (Number(open) <= 0) {
price = yestclose;
}
} else {
window.showErrorMessage(
`fail: error Stock code in ${codes[index]}, please delete error Stock code.`
);
stockItem.showLabel = this.showLabel;
stockItem.isStock = true;
stockItem.type = 'hk';
stockItem.symbol = stockItem.code.replace('hk', '');
stockItem.updown = formatNumber(+price - +yestclose, fixedNumber, false);
stockItem.percent =
(stockItem.updown >= 0 ? '+' : '-') +
formatNumber((Math.abs(stockItem.updown) / +yestclose) * 100, 2, false);

const treeItem = new LeekTreeItem(stockItem, this.context);
stockList.push(treeItem);
}
});
}
} catch (err) {
console.info(url);
console.info(codes);
console.error(err);
if (globalState.showStockErrorInfo) {
window.showErrorMessage(`fail: HK Stock error ` + url);
window.showErrorMessage(`fail: HK Stock error ` + codes);
globalState.showStockErrorInfo = false;
globalState.telemetry.sendEvent('error: stockService', {
url,
codes,
error: err,
});
}
Expand Down Expand Up @@ -576,56 +545,34 @@ export default class StockService extends LeekService {
return [{ label: '期货查询失败,请重试' }];
}
} else {
//股票使用雪球数据源
const stockUrl = `https://xueqiu.com/stock/search.json?code=${encodeURIComponent(
searchText
)}`;
// 改为腾讯数据源
try {
Log.info('getStockSuggestList: getting...');
const stockResponse = await Axios.get(stockUrl, {
responseType: 'text',
transformResponse: [
(data) => {
const body = JSON.parse(data);
return body;
},
],
headers: {
...randHeader(),
Referer: 'https://stock.xueqiu.com/',
Cookie: await this.getToken(),
},
}).catch(() => {
this.token = '';
return { data: {} };
});
const stocks = stockResponse.data?.stocks || [];
const stocks = await searchStockList(searchText);
stocks.forEach((item: any) => {
const { code, name } = item;
if (code.startsWith('SH') || code.startsWith('SZ') || code.startsWith('BJ')) {
const _code = code.toLowerCase();
const { code, name, market } = item;
const _code = `${market}${code}`;
if (['sz', 'sh', 'bj'].includes(market)) {
result.push({
label: `${_code} | ${name}`,
description: `A股`,
});
} else if (/^0\d{4}$/.test(code) || /^HK[A-Z].*/.test(code)) {
} else if (['hk'].includes(market)) {
// 港股个股 || 港股指数
const _code = code.startsWith('HK') ? code.replace('HK', 'hk') : 'hk' + code;
result.push({
label: `${_code} | ${name}`,
description: `港股`,
});
} else if (/\.?[A-Z]*[A-Z]$/.test(code)) {
const _code = 'us' + code.toLowerCase().replace('.', ''); // 去除美股指数前面的'.'
} else if (['us'].includes(market)) {
const usCode = _code.split('.')[0]; // 去除美股指数.后的内容
result.push({
label: `${_code} | ${name}`,
label: `${usCode} | ${name}`,
description: `美股`,
});
}
});
return result;
} catch (err) {
Log.info(stockUrl);
Log.info('searchStockList error: ', searchText);
console.error(err);
return [{ label: '股票查询失败,请重试' }];
}
Expand Down
63 changes: 63 additions & 0 deletions src/shared/tencentStock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Axios from 'axios';
import Log from './log';
import { decode } from 'iconv-lite';

const searchUrl = 'https://proxy.finance.qq.com/ifzqgtimg/appstock/smartbox/search/get';
const stockDataUrl = 'https://qt.gtimg.cn/q=';
export const searchStockList = async (keyword: string) => {
Log.info('searchStockList keyword: ', keyword);
const stockResponse = await Axios.get(searchUrl, {
params: {
q: keyword,
},
});
Log.info('stockResponse: ', stockResponse.data);
const stockListArray = stockResponse?.data?.data?.stock || [];
Log.info('stockListStr: ', stockListArray, keyword);
const stockList = stockListArray.map((stockItemArr: string[]) => {
return {
code: stockItemArr[1].toLowerCase(),
name: stockItemArr[2],
market: stockItemArr[0],
abbreviation: stockItemArr[3],
};
});
Log.info('stockList: ', stockList, keyword);
return stockList;
};

export const getTencentHKStockData = async (codes: string[]) => {
Log.info('getStockData codes: ', codes);
const stockDataResponse = await Axios.get(stockDataUrl, {
responseType: 'arraybuffer',
transformResponse: [
(data) => {
const body = decode(data, 'GB18030');
return body;
},
],
params: {
q: codes.join(','),
},
});
Log.info('stockDataResponse: ', stockDataResponse.data);
const stockDataList = (stockDataResponse.data || '').split(';').map((stockItemStr: string) => {
const stockItemArr = stockItemStr.split('~');
return {
code: 'hk' + stockItemArr[2],
name: stockItemArr[1],
price: stockItemArr[3],
yestclose: stockItemArr[4],
open: stockItemArr[5],
high: stockItemArr[33],
low: stockItemArr[34],
volume: stockItemArr[36],
amount: stockItemArr[37],
buy1: stockItemArr[9],
sell1: stockItemArr[19],
time: stockItemArr[30],
};
});
Log.info('stockDataList: ', stockDataList, codes);
return stockDataList;
};
Loading