Skip to content

Commit f2769c6

Browse files
committed
feat: 在 _worker.js 中添加 CF 账户使用量统计功能
1 parent 664896b commit f2769c6

3 files changed

Lines changed: 202 additions & 4 deletions

File tree

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ Telegram交流群:[@CMLiussss](https://t.me/CMLiussss)
181181
| URL302 | `https://t.me/CMLiussss` | 主页302跳转(支持多url, url之间使用`,``换行`作间隔, 小白别用) |
182182
| URL | `https://blog.cmliussss.com` | 主页反代伪装(支持多url, url之间使用`,``换行`作间隔, 乱设容易触发反诈) |
183183
| CFPORTS | `2053`,`2096`,`8443` | CF账户标准端口列表 |
184+
| CF_EMAIL | `admin@google.com` | CF账户的邮箱,用于获取 Workers/Pages 请求数 |
185+
| CF_APIKEY | `1234567890abcdef1234567890abcdef` | CF账户的`Global API Key`,用于获取 Workers/Pages 请求数 |
186+
187+
> **注意:** 只有 `CF_EMAIL``CF_APIKEY` 变量同时存在时,订阅时才会返回 CF Workers/Pages 的请求数用量信息。
184188

185189
## ❗ 注意事项
186190

@@ -285,4 +289,5 @@ Telegram交流群:[@CMLiussss](https://t.me/CMLiussss)
285289
- [emn178](https://github.com/emn178/js-sha256)
286290
- [ACL4SSR](https://github.com/ACL4SSR/ACL4SSR/tree/master/Clash/config)
287291
- [SHIJS1999](https://github.com/SHIJS1999/cloudflare-worker-vless-ip)
288-
- [股神](https://t.me/CF_NAT/38889)
292+
- [股神](https://t.me/CF_NAT/38889)
293+
- [Workers/Pages Metrics](https://t.me/zhetengsha/3382)

_worker.js

Lines changed: 194 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,12 @@ export default {
167167
let pagesSum = UD;
168168
let workersSum = UD;
169169
let total = 24 * 1099511627776;
170-
170+
if (env.CF_EMAIL && env.CF_APIKEY) {
171+
const usage = await getUsage(env.CF_ID, env.CF_EMAIL, env.CF_APIKEY, env.CF_ALL);
172+
pagesSum = usage[1];
173+
workersSum = usage[2];
174+
total = env.CF_ALL ? Number(env.CF_ALL) : (1024 * 100); // 100K
175+
}
171176
if (userAgent && (userAgent.includes('mozilla') || userAgent.includes('subconverter'))) {
172177
return new Response(特洛伊Config, {
173178
status: 200,
@@ -3614,4 +3619,192 @@ async function bestIP(request, env, txt = 'ADD.txt') {
36143619
'Content-Type': 'text/html; charset=UTF-8',
36153620
},
36163621
});
3622+
}
3623+
3624+
/**
3625+
* 获取 Cloudflare 账户今日使用量统计
3626+
* @param {string} accountId - 账户ID(可选,如果没有会自动获取)
3627+
* @param {string} email - Cloudflare 账户邮箱
3628+
* @param {string} apikey - Cloudflare API 密钥
3629+
* @param {number} all - 总限额,默认10万次
3630+
* @returns {Array} [总限额, Pages请求数, Workers请求数, 总请求数]
3631+
*/
3632+
async function getUsage(accountId, email, apikey, all = 100000) {
3633+
/**
3634+
* 获取 Cloudflare 账户ID
3635+
* @param {string} email - 账户邮箱
3636+
* @param {string} apikey - API密钥
3637+
* @param {number} accountIndex - 取第几个账户,默认第0个
3638+
* @returns {string} 账户ID
3639+
*/
3640+
async function getAccountId(email, apikey) {
3641+
console.log('正在获取账户信息...');
3642+
3643+
const response = await fetch("https://api.cloudflare.com/client/v4/accounts", {
3644+
method: "GET",
3645+
headers: {
3646+
"Content-Type": "application/json",
3647+
"X-AUTH-EMAIL": email,
3648+
"X-AUTH-KEY": apikey,
3649+
}
3650+
});
3651+
3652+
if (!response.ok) {
3653+
const errorText = await response.text();
3654+
console.error(`获取账户信息失败: ${response.status} ${response.statusText}`, errorText);
3655+
throw new Error(`Cloudflare API 请求失败: ${response.status} ${response.statusText} - ${errorText}`);
3656+
}
3657+
3658+
const res = await response.json();
3659+
//console.log(res);
3660+
3661+
let accountIndex = 0; // 默认取第一个账户
3662+
let foundMatch = false; // 标记是否找到匹配的账户
3663+
3664+
// 如果有多个账户,智能匹配包含邮箱前缀的账户
3665+
if (res?.result && res.result.length > 1) {
3666+
console.log(`发现 ${res.result.length} 个账户,正在智能匹配...`);
3667+
3668+
// 提取邮箱前缀并转为小写
3669+
const emailPrefix = email.toLowerCase();
3670+
console.log(`邮箱: ${emailPrefix}`);
3671+
3672+
// 遍历所有账户,寻找名称开头包含邮箱前缀的账户
3673+
for (let i = 0; i < res.result.length; i++) {
3674+
const accountName = res.result[i]?.name?.toLowerCase() || '';
3675+
console.log(`检查账户 ${i}: ${res.result[i]?.name}`);
3676+
3677+
// 检查账户名称开头是否包含邮箱前缀
3678+
if (accountName.startsWith(emailPrefix)) {
3679+
accountIndex = i;
3680+
foundMatch = true;
3681+
console.log(`✅ 找到匹配账户,使用第 ${i} 个账户`);
3682+
break;
3683+
}
3684+
}
3685+
3686+
// 如果遍历完还没找到匹配的,使用默认值0
3687+
if (!foundMatch) {
3688+
console.log('❌ 未找到匹配的账户,使用默认第 0 个账户');
3689+
}
3690+
} else if (res?.result && res.result.length === 1) {
3691+
console.log('只有一个账户,使用第 0 个账户');
3692+
foundMatch = true;
3693+
}
3694+
3695+
const name = res?.result?.[accountIndex]?.name;
3696+
const id = res?.result?.[accountIndex]?.id;
3697+
3698+
console.log(`最终选择账户 ${accountIndex} - 名称: ${name}, ID: ${id}`);
3699+
3700+
if (!id) {
3701+
throw new Error("找不到有效的账户ID,请检查API权限");
3702+
}
3703+
3704+
return id;
3705+
}
3706+
3707+
try {
3708+
// 如果没有提供账户ID,就自动获取
3709+
if (!accountId) {
3710+
console.log('未提供账户ID,正在自动获取...');
3711+
accountId = await getAccountId(email, apikey);
3712+
}
3713+
3714+
// 设置查询时间范围:今天0点到现在
3715+
const now = new Date();
3716+
const endDate = now.toISOString(); // 结束时间:现在
3717+
3718+
// 设置开始时间为今天凌晨0点
3719+
now.setUTCHours(0, 0, 0, 0);
3720+
const startDate = now.toISOString(); // 开始时间:今天0点
3721+
3722+
console.log(`查询时间范围: ${startDate}${endDate}`);
3723+
3724+
// 向 Cloudflare GraphQL API 发送请求,获取今日使用量
3725+
const response = await fetch("https://api.cloudflare.com/client/v4/graphql", {
3726+
method: "POST",
3727+
headers: {
3728+
"Content-Type": "application/json",
3729+
"X-AUTH-EMAIL": email,
3730+
"X-AUTH-KEY": apikey,
3731+
},
3732+
body: JSON.stringify({
3733+
// GraphQL 查询语句:获取 Pages 和 Workers 的请求数统计
3734+
query: `query getBillingMetrics($accountId: String!, $filter: AccountWorkersInvocationsAdaptiveFilter_InputObject) {
3735+
viewer {
3736+
accounts(filter: {accountTag: $accountId}) {
3737+
pagesFunctionsInvocationsAdaptiveGroups(limit: 1000, filter: $filter) {
3738+
sum {
3739+
requests
3740+
}
3741+
}
3742+
workersInvocationsAdaptive(limit: 10000, filter: $filter) {
3743+
sum {
3744+
requests
3745+
}
3746+
}
3747+
}
3748+
}
3749+
}`,
3750+
variables: {
3751+
accountId: accountId,
3752+
filter: {
3753+
datetime_geq: startDate, // 大于等于开始时间
3754+
datetime_leq: endDate // 小于等于结束时间
3755+
},
3756+
},
3757+
}),
3758+
});
3759+
3760+
// 检查API请求是否成功
3761+
if (!response.ok) {
3762+
const errorText = await response.text();
3763+
console.error(`GraphQL查询失败: ${response.status} ${response.statusText}`, errorText);
3764+
console.log('返回默认值:全部为0');
3765+
return [all, 0, 0, 0];
3766+
}
3767+
3768+
const res = await response.json();
3769+
3770+
// 检查GraphQL响应是否有错误
3771+
if (res.errors && res.errors.length > 0) {
3772+
console.error('GraphQL查询错误:', res.errors[0].message);
3773+
console.log('返回默认值:全部为0');
3774+
return [all, 0, 0, 0];
3775+
}
3776+
3777+
// 从响应中提取账户数据
3778+
const accounts = res?.data?.viewer?.accounts?.[0];
3779+
3780+
if (!accounts) {
3781+
console.warn('未找到账户数据');
3782+
return [all, 0, 0, 0];
3783+
}
3784+
3785+
// 计算 Pages 请求数(Cloudflare Pages 的请求统计)
3786+
const pagesArray = accounts?.pagesFunctionsInvocationsAdaptiveGroups || [];
3787+
const pages = pagesArray.reduce((total, item) => {
3788+
return total + (item?.sum?.requests || 0);
3789+
}, 0);
3790+
3791+
// 计算 Workers 请求数(Cloudflare Workers 的请求统计)
3792+
const workersArray = accounts?.workersInvocationsAdaptive || [];
3793+
const workers = workersArray.reduce((total, item) => {
3794+
return total + (item?.sum?.requests || 0);
3795+
}, 0);
3796+
3797+
// 计算总请求数
3798+
const total = pages + workers;
3799+
3800+
console.log(`统计结果 - Pages: ${pages}, Workers: ${workers}, 总计: ${total}`);
3801+
3802+
// 返回格式:[总限额, Pages请求数, Workers请求数, 总请求数]
3803+
return [all, pages || 0, workers || 0, total || 0];
3804+
3805+
} catch (error) {
3806+
console.error('获取使用量时发生错误:', error.message);
3807+
// 发生错误时返回默认值
3808+
return [all, 0, 0, 0];
3809+
}
36173810
}

wrangler.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name = "t20250705"
1+
name = "t20250706"
22
main = "_worker.js"
3-
compatibility_date = "2025-07-05"
3+
compatibility_date = "2025-07-06"
44
keep_vars = true

0 commit comments

Comments
 (0)