mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-10-13 05:41:28 +08:00
211 lines
7.3 KiB
JavaScript
211 lines
7.3 KiB
JavaScript
import { SETTINGS_KEY } from '@/constants';
|
|
import { HTTP, ENV } from '@/vendor/open-api';
|
|
import { getPolicyDescriptor } from '@/utils';
|
|
import $ from '@/core/app';
|
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
|
|
|
export function getFlowField(headers) {
|
|
const subkey = Object.keys(headers).filter((k) =>
|
|
/SUBSCRIPTION-USERINFO/i.test(k),
|
|
)[0];
|
|
return headers[subkey];
|
|
}
|
|
export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
|
let url = rawUrl;
|
|
let $arguments = {};
|
|
const rawArgs = url.split('#');
|
|
url = url.split('#')[0];
|
|
if (rawArgs.length > 1) {
|
|
try {
|
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
|
} catch (e) {
|
|
for (const pair of rawArgs[1].split('&')) {
|
|
const key = pair.split('=')[0];
|
|
const value = pair.split('=')[1];
|
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
|
$arguments[key] =
|
|
value == null || value === ''
|
|
? true
|
|
: decodeURIComponent(value);
|
|
}
|
|
}
|
|
}
|
|
if ($arguments?.noFlow) {
|
|
return;
|
|
}
|
|
const { isStash } = ENV();
|
|
const cached = headersResourceCache.get(url);
|
|
let flowInfo;
|
|
if (!$arguments?.noCache && cached) {
|
|
// $.info(`使用缓存的流量信息: ${url}`);
|
|
flowInfo = cached;
|
|
} else {
|
|
const { defaultFlowUserAgent, defaultTimeout } = $.read(SETTINGS_KEY);
|
|
const userAgent =
|
|
ua ||
|
|
defaultFlowUserAgent ||
|
|
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
|
const requestTimeout = timeout || defaultTimeout;
|
|
const http = HTTP();
|
|
try {
|
|
// $.info(`使用 HEAD 方法获取流量信息: ${url}`);
|
|
const { headers } = await http.head({
|
|
url: url
|
|
.split(/[\r\n]+/)
|
|
.map((i) => i.trim())
|
|
.filter((i) => i.length)[0],
|
|
headers: {
|
|
'User-Agent': userAgent,
|
|
'X-Stash-Selected-Proxy':
|
|
isStash && proxy
|
|
? encodeURIComponent(proxy)
|
|
: undefined,
|
|
},
|
|
timeout: requestTimeout,
|
|
proxy,
|
|
...getPolicyDescriptor(proxy),
|
|
});
|
|
flowInfo = getFlowField(headers);
|
|
} catch (e) {
|
|
$.error(
|
|
`使用 HEAD 方法获取流量信息失败: ${url}: ${e.message ?? e}`,
|
|
);
|
|
}
|
|
if (!flowInfo) {
|
|
$.info(`使用 GET 方法获取流量信息: ${url}`);
|
|
const { headers } = await http.get({
|
|
url: url
|
|
.split(/[\r\n]+/)
|
|
.map((i) => i.trim())
|
|
.filter((i) => i.length)[0],
|
|
headers: {
|
|
'User-Agent': userAgent,
|
|
},
|
|
timeout: requestTimeout,
|
|
});
|
|
flowInfo = getFlowField(headers);
|
|
}
|
|
if (flowInfo) {
|
|
headersResourceCache.set(url, flowInfo);
|
|
}
|
|
}
|
|
|
|
return flowInfo;
|
|
}
|
|
export function parseFlowHeaders(flowHeaders) {
|
|
if (!flowHeaders) return;
|
|
// unit is KB
|
|
const uploadMatch = flowHeaders.match(
|
|
/upload=([-+]?)([0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)/,
|
|
);
|
|
const upload = Number(uploadMatch[1] + uploadMatch[2]);
|
|
|
|
const downloadMatch = flowHeaders.match(
|
|
/download=([-+]?)([0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)/,
|
|
);
|
|
const download = Number(downloadMatch[1] + downloadMatch[2]);
|
|
const totalMatch = flowHeaders.match(
|
|
/total=([-+]?)([0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)/,
|
|
);
|
|
const total = Number(totalMatch[1] + totalMatch[2]);
|
|
|
|
// optional expire timestamp
|
|
const expireMatch = flowHeaders.match(
|
|
/expire=([-+]?)([0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)/,
|
|
);
|
|
const expires = expireMatch
|
|
? Number(expireMatch[1] + expireMatch[2])
|
|
: undefined;
|
|
|
|
return { expires, total, usage: { upload, download } };
|
|
}
|
|
export function flowTransfer(flow, unit = 'B') {
|
|
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
|
let unitIndex = unitList.indexOf(unit);
|
|
|
|
return flow < 1024
|
|
? { value: flow.toFixed(1), unit: unit }
|
|
: flowTransfer(flow / 1024, unitList[++unitIndex]);
|
|
}
|
|
|
|
export function validCheck(flow) {
|
|
if (!flow) {
|
|
throw new Error('没有流量信息');
|
|
}
|
|
if (flow?.expires && flow.expires * 1000 < Date.now()) {
|
|
const date = new Date(flow.expires * 1000).toLocaleDateString();
|
|
throw new Error(`订阅已过期: ${date}`);
|
|
}
|
|
if (flow?.total) {
|
|
const upload = flow.usage?.upload || 0;
|
|
const download = flow.usage?.download || 0;
|
|
if (flow.total - upload - download < 0) {
|
|
const current = upload + download;
|
|
const currT = flowTransfer(Math.abs(current));
|
|
currT.value = current < 0 ? '-' + currT.value : currT.value;
|
|
const totalT = flowTransfer(flow.total);
|
|
throw new Error(
|
|
`流量已用完: ${currT.value} ${currT.unit} / ${totalT.value} ${totalT.unit}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getRmainingDays(opt = {}) {
|
|
try {
|
|
let { resetDay, startDate, cycleDays } = opt;
|
|
if (['string', 'number'].includes(typeof opt)) {
|
|
resetDay = opt;
|
|
}
|
|
|
|
if (startDate && cycleDays) {
|
|
cycleDays = parseInt(cycleDays);
|
|
if (isNaN(cycleDays) || cycleDays <= 0)
|
|
throw new Error('重置周期应为正整数');
|
|
if (!startDate || !Date.parse(startDate))
|
|
throw new Error('开始日期不合法');
|
|
|
|
const start = new Date(startDate);
|
|
const today = new Date();
|
|
start.setHours(0, 0, 0, 0);
|
|
today.setHours(0, 0, 0, 0);
|
|
if (start.getTime() > today.getTime())
|
|
throw new Error('开始日期应早于现在');
|
|
|
|
let resetDate = new Date(startDate);
|
|
resetDate.setDate(resetDate.getDate() + cycleDays);
|
|
|
|
while (resetDate < today) {
|
|
resetDate.setDate(resetDate.getDate() + cycleDays);
|
|
}
|
|
|
|
resetDate.setHours(0, 0, 0, 0);
|
|
const timeDiff = resetDate.getTime() - today.getTime();
|
|
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
|
|
|
return daysDiff;
|
|
} else {
|
|
if (!resetDay) return;
|
|
resetDay = parseInt(resetDay);
|
|
if (isNaN(resetDay) || resetDay <= 0 || resetDay > 31)
|
|
throw new Error('月重置日应为 1-31 之间的整数');
|
|
let now = new Date();
|
|
let today = now.getDate();
|
|
let month = now.getMonth();
|
|
let year = now.getFullYear();
|
|
let daysInMonth;
|
|
|
|
if (resetDay > today) {
|
|
daysInMonth = 0;
|
|
} else {
|
|
daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
}
|
|
|
|
return daysInMonth - today + resetDay;
|
|
}
|
|
} catch (e) {
|
|
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
|
}
|
|
}
|