2023-07-22 00:47:43 +08:00

530 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Oapi余额查询</title>
<meta name="description" content="查询Oapi的余额">
<style>
:root {
--color-primary: #1E3B7A;
--color-primary-dark: #1E3B7A;
--color-primary-alpha: #01847f;
--body-color: #dadada;
--body-bg: #000000;
--border-color: #f8f8f800;
}
body {
background: url('https://cdn-img.czl.net/2023/05/23/pjbczr.webp') no-repeat center center fixed;
/* 自定义背景图 */
background-size: cover;
width: 90%;
max-width: 980px;
margin-left: auto;
margin-right: auto;
padding-left: 2rem;
padding-right: 2rem;
color: var(--body-color);
/* background: var(--body-bg); */
font-family: system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
line-height: 1.5;
-webkit-tap-highlight-color: rgb(252, 247, 247);
text-rendering: optimizelegibility;
-webkit-font-smoothing: antialiased;
}
.query {
max-width: 50rem;
margin: auto;
}
a {
color: var(--color-primary);
text-decoration: none;
transition: color .3s;
}
a:hover {
color: var(--color-primary);
text-decoration: underline;
}
h1 {
font-size: 2.5rem;
text-align: center;
}
main[x-cloak] {
opacity: 0;
}
main:not([x-cloak]) {
opacity: 1;
transition: opacity .3s;
}
textarea {
-webkit-appearance: none;
appearance: none;
display: block;
width: 100%;
padding: .5rem 1rem;
border: 1px solid var(--border-color);
border-radius: .25rem;
box-sizing: border-box;
color: #33404d;
line-height: inherit;
font-size: 1rem;
transition: border .3s, box-shadow .3s;
}
textarea:focus {
box-shadow: 0 0 0 .25rem var(--color-primary-alpha);
border-color: var(--color-primary);
outline: 0;
}
input {
-webkit-appearance: none;
appearance: none;
display: block;
width: 100%;
padding: .5rem 1rem;
border: 1px solid var(--border-color);
border-radius: .25rem;
box-sizing: border-box;
color: #33404d;
line-height: inherit;
font-size: 1rem;
transition: border .3s, box-shadow .3s;
}
input:focus {
box-shadow: 0 0 0 .25rem var(--color-primary-alpha);
border-color: var(--color-primary);
outline: 0;
}
details {
margin: 1rem 0 2rem;
border: 1px solid var(--border-color);
border-radius: .25rem;
transition: background .3s;
}
details[open] {
background: #fff;
}
details summary {
padding: .5rem 1rem;
font-weight: 500;
user-select: none;
cursor: pointer;
opacity: .8;
outline: 0;
}
details div {
padding: 1rem;
border-top: 1px solid var(--border-color);
}
details small {
margin: 0;
font-size: .875rem;
line-height: 2;
}
button {
appearance: none;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
margin: auto;
max-width: 50rem;
margin-bottom: 1rem;
padding: .5rem .75rem;
border: 1px solid var(--color-primary);
border-radius: .25rem;
background: var(--color-primary);
color: #fff;
font-size: 1rem;
font-weight: 500;
line-height: inherit;
cursor: pointer;
user-select: none;
transition: border .3s, background .3s, ;
}
button:hover {
border-color: var(--color-primary-dark);
background: var(--color-primary-dark);
}
button:focus {
box-shadow: 0 0 0 .25rem var(--color-primary-alpha);
border-color: var(--color-primary);
outline: 0;
}
button:disabled {
background: var(--color-primary);
border-color: var(--color-primary);
opacity: .6;
cursor: not-allowed;
}
button.loading::before {
content: '';
display: inline-block;
margin-right: .5rem;
border: 2px solid #fff;
border-top-color: transparent;
border-bottom-color: transparent;
border-radius: 50%;
width: .75rem;
height: .75rem;
animation: rotate .5s linear infinite;
}
footer {
padding: 1rem;
border-top: 1px solid var(--border-color);
text-align: center;
opacity: .5;
}
footer i {
font-style: normal;
color: #b32142;
}
.success,
.error {
margin-bottom: 1rem;
padding: .5rem 1rem;
border-radius: .25rem;
color: #fff;
text-align: center;
opacity: 1;
transition: opacity .3s;
}
.success {
/* border: 1px solid #12b886; */
background: #38d9a9;
}
.error {
/* border: 1px solid #b32142; */
background: #b32142;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
/* 设置API URL选择器和自定义URL输入框的样式 */
select#api-url-select,
input#custom-url-input {
width: 100%;
height: 2.4rem;
font-size: 1rem;
background-color: #212121;
margin-bottom: .5rem;
padding: .5rem .75rem;
color: #ffffff;
border: none;
padding: .5rem .75rem;
border-radius: .25rem;
margin-right: 10px;
margin-top: 2vhpx;
/* 添加了顶部边距 */
}
/* 下面的代码定义了结果表格的样式 */
#result-table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
font-size: 14px;
border-radius: 5px;
border: none;
overflow: hidden;
}
/* 下面的代码定义了结果表格表头的样式 */
#result-table th {
background-color: var(--color-primary);
color: #ffffff;
font-weight: 400;
height: 20px;
padding: 10px 25px;
text-align: left;
/* border: 1px solid #dcdcdc; */
}
/* 下面的代码定义了结果表格数据单元格的样式 */
#result-table td {
height: 20px;
text-align: left;
padding: 10px 25px;
/* border: 1px solid #d3d3d3; */
}
/* 下面的代码定义了结果表格奇数行的背景颜色 */
#result-table tbody tr:nth-child(odd) {
background-color: #252422;
}
/* 下面的代码定义了结果表格偶数行的背景颜色 */
#result-table tbody tr:nth-child(even) {
background-color: #31302d;
}
/* 下面的代码定义了结果表格表头的宽度 */
#result-table .table-header {
width: 25%;
}
/* 下面的代码定义了结果表格数据单元格的宽度、字体加粗和颜色 */
#result-table .table-data {
width: 25%;
font-weight: bold;
color: #1c248b;
}
/* 下面的代码定义了结果表格数据单元格的样式 */
#result-table .status-error {
height: 20px;
text-align: center;
padding: 10px 25px;
/* border: 1px solid #dcdcdc; */
}
/* 下面的代码定义了一个类名为status-ok的样式用于设置成功状态的文本颜色 */
.status-ok {
color: #2d8d2d;
}
/* 下面的代码定义了一个类名为status-error的样式用于设置错误状态的文本颜色 */
.status-error {
color: #ed0808;
}
#api-key-input {
width: 100%;
height: 100px;
/* border: 1px solid #999; */
background-color: #fff;
}
</style>
</head>
<body>
<header>
<h1 class="text-3xl font-semibold text-center mb-8 text-gradient">Oapi余额查询</h1>
<a href="index.html" style="text-align: center;color:antiquewhite;text-decoration: none;font-weight: 700;"><p>OPENAI的余额查询</p></a>
</header>
<div class="query">
<div>
<h2 style="display: inline-block;">输入 API KEY</h2>
<p style="display: inline-block; font-size: smaller;">本站不保存 KEY 信息,查询后请自行保存</p>
</div>
<textarea id="api-key-input" placeholder="请输入 API-KEY必须包含 sk-,多个可直接粘贴"></textarea></p>
<!-- API链接选择框 -->
<div class="api-url-container"></div>
<!-- API链接选择 -->
<div>
<h2 style="display: inline-block;">选择查询线路</h2>
<p style="display: inline-block; font-size: smaller;">只支持Oapi的余额查询</p>
</div>
<select id="api-url-select" style="width:100%;">
<option value="https://oapi.czl.net">【Oapi】余额查询</option>
</select>
</div>
<button :class="{ loading }" :disabled="loading" onclick="sendRequest()">查询</button> </p>
</div>
<h2 id="result-head" style="visibility:hidden">查询结果</h2>
<table id="result-table" style="visibility:hidden">
<thead>
<tr>
<th style="width:35px">序号</th>
<th>API KEY</th>
<th style="width: 50px">余额</th>
</tr>
</thead>
<tbody></tbody>
</table>
<footer>
<a href="https://chat.czl.net" style="color: white;" target="_blank">CZLChat</a> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://oapi.czl.net" style="color: white;" target="_blank">CZLOapi</a>
</footer>
<script>
let queriedApiKeys = [];
let serialNumber = 1;
async function checkBilling(apiKey, apiUrl) {
const headers = {
"Authorization": "Bearer " + apiKey,
"Content-Type": "application/json"
};
const urlSubscription = `${apiUrl}/v1/dashboard/billing/subscription`;
try {
let response = await fetch(urlSubscription, { headers });
if (!response.ok) {
console.log("APIKEY 错误或账号被封,请登录 OpenAI 查看。");
return;
}
const subscriptionData = await response.json();
const totalAmount = subscriptionData.system_hard_limit_usd;
return [totalAmount];
} catch (error) {
console.error(error);
return ["Error"];
}
}
function sendRequest() {
let apiKeyInput = document.getElementById("api-key-input");
let apiUrlSelect = document.getElementById("api-url-select");
let customUrlInput = document.getElementById("custom-url-input");
let table = document.getElementById("result-table");
let h2 = document.getElementById("result-head");
h2.style.visibility = "visible";
table.style.visibility = "visible";
if (apiKeyInput.value.trim() === "") {
alert("请填写API KEY");
return;
}
document.getElementById("result-table").getElementsByTagName('tbody')[0].innerHTML = "";
let apiUrl = "";
if (apiUrlSelect.value === "custom") {
if (customUrlInput.value.trim() === "") {
alert("请设置API链接");
return;
} else {
apiUrl = customUrlInput.value.trim();
if (!apiUrl.startsWith("http://") && !apiUrl.startsWith("https://")) {
apiUrl = "https://" + apiUrl;
}
}
} else {
apiUrl = apiUrlSelect.value;
}
let apiKeys = apiKeyInput.value.split(/[,\s\n]+/).filter(key => key.startsWith("sk-"));
if (apiKeys.length === 0) {
alert("未匹配到 API-KEY请检查输入内容");
return;
}
alert("成功匹配到 API Key确认后开始查询" + apiKeys);
let tableBody = document.querySelector("#result-table tbody");
for (let i = 0; i < apiKeys.length; i++) {
let apiKey = apiKeys[i].trim();
if (queriedApiKeys.includes(apiKey)) {
console.log(`API KEY ${apiKey} 已查询过,跳过此次查询`);
continue;
}
queriedApiKeys.push(apiKey);
checkBilling(apiKey, apiUrl).then((data) => {
let row = document.createElement("tr");
let serialNumberCell = document.createElement("td");
serialNumberCell.textContent = serialNumber;
row.appendChild(serialNumberCell);
let apiKeyCell = document.createElement("td");
apiKeyCell.textContent = apiKey;
row.appendChild(apiKeyCell);
console.log(data);
if (data?.[0] === "Error" || data === undefined) {
let errorMessageCell = document.createElement("td");
errorMessageCell.colSpan = "3";
errorMessageCell.classList.add("status-error");
errorMessageCell.textContent = "不正确或已失效的API-KEY";
row.appendChild(errorMessageCell);
} else {
let totalGrantedCell = document.createElement("td");
totalGrantedCell.textContent = data[0].toFixed(2);
row.appendChild(totalGrantedCell);
}
tableBody.appendChild(row);
if (i === apiKeys.length - 1) {
queriedApiKeys = [];
}
serialNumber++;
h2.style.display = 'block';
table.style.display = 'table';
}).catch((error) => {
console.error(error);
let row = document.createElement("tr");
let serialNumberCell = document.createElement("td");
serialNumberCell.textContent = serialNumber;
row.appendChild(serialNumberCell);
let apiKeyCell = document.createElement("td");
apiKeyCell.textContent = apiKey;
row.appendChild(apiKeyCell);
let errorMessageCell = document.createElement("td");
errorMessageCell.colSpan = "3";
errorMessageCell.style.width = "90px";
errorMessageCell.classList.add("status-error");
errorMessageCell.textContent = "账户疑似被封禁请登录Oapi官网确认";
row.appendChild(errorMessageCell);
tableBody.appendChild(row);
if (i === apiKeys.length - 1) {
queriedApiKeys = [];
}
serialNumber++;
});
}
}
let apiUrlSelect = document.getElementById("api-url-select");
let customUrlInput = document.getElementById("custom-url-input");
apiUrlSelect.addEventListener("change", function () {
if (apiUrlSelect.value === "custom") {
customUrlInput.style.display = "inline-block";
customUrlInput.style.marginTop = "5px";
} else {
customUrlInput.style.display = "none";
}
});
</script>
</body>
</html>