Compare commits

..

11 Commits

Author SHA1 Message Date
Zhuym
f8a7da1959 更新index.html,重构API调用函数,增强错误处理逻辑以兼容非JSON响应,并优化日志记录 2025-01-30 22:28:40 +08:00
Zhuym
7a28bdb453 更新index.html,优化API调用函数,增强错误处理逻辑并兼容非JSON响应,修改认证提示文本以更清晰地说明身份认证要求 2025-01-30 21:52:26 +08:00
Zhuym
efc0c7cb50 更新index.html,优化API调用函数,简化请求处理逻辑并增强错误处理 2025-01-30 21:18:53 +08:00
Zhuym
f923ed0695 更新index.html,优化API调用函数,增强请求处理逻辑并添加错误处理 2025-01-30 21:11:32 +08:00
Zhuym
9ca6cde4b0 更新index.html,修改认证提示文本以更清晰地说明身份认证要求 2025-01-30 20:42:34 +08:00
Zhuym
fc7c93de91 更新index.html,简化认证按钮文本 2025-01-30 20:41:18 +08:00
Zhuym
c7cb992208 更新index.html,添加地址格式化函数并增强后端地址输入的提示和验证 2025-01-30 20:35:29 +08:00
Zhuym
bfbab6be5d 更新index.html,修正OpenGraph标签中的图片路径并添加Twitter卡片支持 2025-01-29 23:25:52 +08:00
Zhuym
7a596c7850 更新label.json,添加确认提交的消息和计数;更新index.html,动态设置OpenGraph标签的图片路径 2025-01-29 22:47:54 +08:00
Zhuym
8738f1992e 更新label.json,修改地图相关标签和描述;修正index.html中的OpenGraph标签图片路径 2025-01-29 22:23:54 +08:00
Zhuym
339383f852 更新label.json,添加地图和印章相关的API支持;修正index.html中的OpenGraph标签图片路径 2025-01-29 22:16:28 +08:00
4 changed files with 251 additions and 79 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
# 忽略UserDataDir文件夹
UserDataDir
.vscode/settings.json
.vscode

View File

@@ -41,7 +41,7 @@
"mapstock": {
"tab": {
"index": 2,
"label": "🗺存入Stock",
"label": "存入Stock",
"description": "保存 99km Stocks 到账户"
},
"endpoint": "/mapstock",
@@ -55,11 +55,48 @@
"required": true
}
],
"confirmBeforeSubmit": true
"confirmBeforeSubmit": true,
"confirmCount": 2,
"confirmMessages": [
"警告(1/2)\n\n这将修改ID={music.musicId}账号内的数据",
"警告(2/2)\n\n您是否确认修改这些数据?"
]
},
"map": {
"tab": {
"index": 3,
"label": "🏃‍➡️一键跑图",
"description": "## 一键完成区域\n\n可以领取的地图列表:\n-\n\n输入地图ID和用户ID即可一键领取该区域所有收藏品。"
},
"endpoint": "/map",
"method": "GET",
"queryParams": ["userid", "mapid"],
"fields": [
{
"id": "userid",
"label": "🪪UserID",
"type": "text",
"required": true
},
{
"id": "mapid",
"label": "🗺地图ID",
"type": "number",
"required": true,
"placeholder": "10001"
}
],
"confirmBeforeSubmit": true,
"confirmCount": 3,
"confirmMessages": [
"警告(1/3)\n\n这将永久修改ID={music.musicId}账号内的数据",
"警告(2/3)\n\n即将修改的数据:\n地图ID: {mapid}\n\n原始请求:\n{rawData}",
"警告(3/3)\n\n您是否确认修改这些数据?"
]
},
"unlock": {
"tab": {
"index": 3,
"index": 4,
"label": "🔓解锁紫铺",
"description": "解锁所有 DX Master 谱面"
},
@@ -74,11 +111,35 @@
"required": true
}
],
"confirmBeforeSubmit": true,
"confirmCount": 2,
"confirmMessages": [
"警告(1/2)\n\n这将永久修改ID={music.musicId}账号内的数据",
"警告(2/2)\n\n您是否确认修改这些数据?"
]
},
"bonus": {
"tab": {
"index": 5,
"label": "☑️一键印章",
"description": "为所有现有收藏品的集章卡各盖至9/10个章"
},
"endpoint": "/bonus",
"method": "GET",
"queryParams": ["userid"],
"fields": [
{
"id": "userid",
"label": "🪪UserID",
"type": "text",
"required": true
}
],
"confirmBeforeSubmit": true
},
"music": {
"tab": {
"index": 4,
"index": 6,
"label": "✏️修改成绩",
"description": "⚠警告\n这将[覆写]账户中的成绩数据"
},
@@ -160,7 +221,12 @@
}
],
"confirmBeforeSubmit": true,
"confirmTemplate": "即将发送的数据:\nUserID: {userId}\n歌曲ID: {music.musicId}\nachievement: {music.achievement}\n难度: {music.level}\nFC状态: {music.comboStatus}\nsyncStatus: {music.syncStatus}\nFS状态:{music.syncStatus}\n\n原始请求:\n{rawData}"
"confirmCount": 3,
"confirmMessages": [
"警告(1/3)\n\n这将永久修改ID={music.musicId}账号内的数据",
"警告(2/3)\n\n即将修改的数据:\nUserID: {userId}\n歌曲ID: {music.musicId}\n分数: {music.achievement}\n难度: {music.level}\nFC状态: {music.comboStatus}\nFS状态:{music.syncStatus}\n\n原始请求:\n{rawData}",
"警告(3/3)\n\n您是否确认修改这些数据?"
]
}
}
}

BIN
image/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -13,7 +13,9 @@
<!-- OpenGraph 标签 -->
<meta property="og:title" content="TsumugiBoshi|纺星">
<meta property="og:description" content="神秘API调试工具">
<meta property="og:image" content="image\og-image.png">
<meta property="og:image" content="https://s2.loli.net/2025/01/29/1KPvmysVrAdLW3C.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://s2.loli.net/2025/01/29/1KPvmysVrAdLW3C.png" />
<meta property="og:type" content="website">
<title>TsumugiBoshi|纺星</title>
@@ -334,14 +336,22 @@
const handleSubmit = async () => {
if (formConfig.confirmBeforeSubmit) {
let confirmMessage = formConfig.confirmTemplate || "确认提交数据?";
if (formConfig.confirmTemplate) {
const confirmCount = formConfig.confirmCount || 1;
const messages = formConfig.confirmMessages || Array(confirmCount).fill("确认提交数据?");
for (let i = 0; i < confirmCount; i++) {
let confirmMessage = messages[i] || "确认提交数据?";
if (confirmMessage) {
confirmMessage = confirmMessage.replace(/\{([^}]+)\}/g, (_, key) => {
return key === 'rawData' ? JSON.stringify(formData, null, 2) :
key.split('.').reduce((obj, k) => obj && obj[k], formData) || '';
});
}
if (!confirm(confirmMessage)) return;
if (!confirm(confirmMessage)) {
return;
}
}
}
onSubmit(formData);
};
@@ -395,6 +405,27 @@
);
});
// 在 App 组件外部添加地址格式化函数
const formatApiUrl = (url) => {
if (!url) return '';
// 移除末尾的斜杠
url = url.replace(/\/+$/, '');
// 如果不包含协议,添加 http://
if (!/^https?:\/\//i.test(url)) {
url = 'http://' + url;
}
try {
// 使用 URL API 解析和规范化地址
const urlObj = new URL(url);
return urlObj.toString().replace(/\/+$/, '');
} catch (e) {
return url;
}
};
function App() {
const [tabValue, setTabValue] = React.useState(0);
const [qrInput, setQrInput] = React.useState('');
@@ -421,7 +452,7 @@
...JSON.parse(localStorage.getItem('customBackends') || '[]')
]);
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const [newBackend, setNewBackend] = React.useState({ label: '', value: '' });
const [newBackend, setNewBackend] = React.useState({ label: '', value: '', formattedUrl: '' });
const [snackbarOpen, setSnackbarOpen] = React.useState(false);
const [snackbarMessage, setSnackbarMessage] = React.useState('');
const [menuAnchorEl, setMenuAnchorEl] = React.useState(null);
@@ -431,6 +462,98 @@
const [isLogExpanded, setIsLogExpanded] = React.useState(false);
const formRefs = React.useRef({});
const handleApiCall = async (endpoint, method = "GET", formData = null, config) => {
try {
const options = {
method,
headers: { "Content-Type": "application/json" },
credentials: "include"
};
let url = `${apiBase}${endpoint}`;
if (method === "GET" && config?.queryParams) {
const params = new URLSearchParams();
config.queryParams.forEach(param => {
if (formData?.[param]) params.append(param, formData[param]);
});
url += `?${params.toString()}`;
}
if (method === "POST" && config?.requestFormat === "json") {
options.body = JSON.stringify(formData);
}
const res = await fetch(url, options);
const rawText = await res.text();
let responseData;
try {
responseData = JSON.parse(rawText);
} catch {
responseData = {
status: res.status,
statusText: res.statusText,
rawResponse: rawText
};
}
// 将状态码添加到显示结果中
const displayResponse = {
status: res.status,
statusText: res.statusText,
...responseData
};
setResponse(JSON.stringify(displayResponse, null, 2));
// 保存日志
saveLog(endpoint, {
method,
url,
...formData,
status: res.status
}, displayResponse);
// 特殊处理登录成功的情况
if (config?.endpoint === "/qr" && responseData.userId) {
setUserId(responseData.userId.toString());
setCookie("userId", responseData.userId.toString(), 7);
Object.entries(formConfigs).forEach(([key, formConfig]) => {
const userIdField = formConfig.fields.find(
field => field.id.toLowerCase().includes("userid")
);
if (userIdField && formRefs.current[key]) {
formRefs.current[key].updateField(userIdField.id, responseData.userId.toString());
}
});
}
// 显示消息提示
setSnackbarMessage(responseData.info || `请求完成 (${res.status} ${res.statusText})`);
setSnackbarOpen(true);
} catch (error) {
const errorResponse = {
error: true,
message: error.message,
type: error.name
};
setResponse(JSON.stringify(errorResponse, null, 2));
setSnackbarMessage(`请求发生错误: ${error.message}`);
setSnackbarOpen(true);
// 保存错误日志
saveLog(endpoint, {
method,
url: `${apiBase}${endpoint}`,
...formData
}, errorResponse);
}
};
// Cookie操作函数
function setCookie(name, value, days) {
const d = new Date();
@@ -462,65 +585,6 @@
localStorage.setItem('apiLogs', JSON.stringify(updatedLogs));
};
const handleApiCall = async (endpoint, method = 'GET', formData = null, config) => {
try {
const options = {
method,
headers: { 'Content-Type': 'application/json' }
};
let url = `${apiBase}${endpoint}`;
// 处理GET请求参数
if (method === 'GET' && config.queryParams) {
const params = new URLSearchParams();
config.queryParams.forEach(param => {
if (formData[param]) params.append(param, formData[param]);
});
url += `?${params.toString()}`;
}
// 处理POST请求体
if (method === 'POST') {
if (config.requestFormat === 'json') {
options.body = JSON.stringify(formData);
}
}
const res = await fetch(url, options);
const data = await res.json();
setResponse(JSON.stringify(data, null, 2));
// 保存日志
saveLog(endpoint, formData, data);
// 处理登录响应
if (config.endpoint === '/qr' && data.userId) {
setUserId(data.userId.toString());
setCookie('userId', data.userId.toString(), 7);
// 遍历所有表单配置更新包含userid/userId字段的表单
Object.entries(formConfigs).forEach(([key, formConfig]) => {
const userIdField = formConfig.fields.find(
field => field.id.toLowerCase().includes('userid')
);
if (userIdField && formRefs.current[key]) {
formRefs.current[key].updateField(userIdField.id, data.userId.toString());
}
});
}
// 根据状态设置提示消息
setSnackbarMessage(data.info || '操作成功');
setSnackbarOpen(true);
} catch (error) {
setResponse(`Error: ${error.message}`);
setSnackbarMessage(`错误: ${error.message}`);
setSnackbarOpen(true);
}
};
const handleMusicChange = (e) => {
const { name, value } = e.target;
setMusicData(prev => ({
@@ -544,7 +608,7 @@
const handleDialogClose = () => {
setIsDialogOpen(false);
setNewBackend({ label: '', value: '' });
setNewBackend({ label: '', value: '', formattedUrl: '' });
};
const handleSaveBackend = () => {
@@ -600,6 +664,12 @@
handleMenuClose();
};
const handleNewBackendChange = (e) => {
const value = e.target.value;
const formattedUrl = formatApiUrl(value);
setNewBackend(prev => ({...prev, value: value, formattedUrl: formattedUrl}));
};
// 加载表单配置
const [formConfigs, setFormConfigs] = React.useState(null);
@@ -705,7 +775,7 @@
{/* 移动后端选择器到底部 */}
<Paper elevation={3} className="backend-selector">
<Typography variant="h6" gutterBottom>
后端设置
后端设置
</Typography>
<FormControl fullWidth>
<InputLabel>选择后端地址</InputLabel>
@@ -749,6 +819,7 @@
</MenuItem>
))}
</Select>
<Button
variant="outlined"
color="primary"
@@ -757,6 +828,20 @@
>
添加自定义后端
</Button>
{/* 新增认证提示区域 */}
<Box display="flex" justifyContent="space-between" alignItems="center" mt={2}>
<Typography variant="body2" color="textSecondary">
部分后端地址受认证保护 使用前可能需要身份认证
</Typography>
<Button
variant="text"
color="primary"
onClick={() => window.open(apiBase, '_blank')}
>
认证
</Button>
</Box>
</FormControl>
</Paper>
@@ -812,12 +897,33 @@
label="后端地址"
fullWidth
value={newBackend.value}
onChange={(e) => setNewBackend(prev => ({...prev, value: e.target.value}))}
onChange={handleNewBackendChange}
placeholder="example.com:2333"
helperText={
<div style={{fontSize: '0.75rem', color: '#666'}}>
支持的格式:<br/>
- example.com<br/>
- example.com:2333<br/>
- http(s)://example.com<br/>
{newBackend.value && (
<>
<br/>
将被格式化为: <span style={{color: '#1976d2'}}>{newBackend.formattedUrl}</span>
</>
)}
</div>
}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose}>取消</Button>
<Button onClick={handleSaveBackend} color="primary">保存</Button>
<Button
onClick={() => handleSaveBackend()}
color="primary"
disabled={!newBackend.label || !newBackend.value}
>
保存
</Button>
</DialogActions>
</Dialog>