Compare commits

..

2 Commits

Author SHA1 Message Date
BRanulf
651e7f69f7 小修改 2025-06-02 13:08:59 +08:00
BRanulf
550894e8e4 更新许可证 2025-06-02 13:08:41 +08:00
4 changed files with 34 additions and 241 deletions

View File

@ -1,7 +1,24 @@
Copyright (c) 2025 BRanulf
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -631,8 +648,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> {one line to give the program's name and a brief idea of what it does.}
Copyright (C) <year> <name of author> Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -645,14 +662,14 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author> playerOnlineTimeWeb Copyright (C) 2025 BRanulf
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.
@ -664,11 +681,12 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>. <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>. <http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -176,6 +176,7 @@ public class PlayerTimeMod implements ModInitializer {
} }
footer.append(Text.literal("§7共 " + lines.size() + " 位玩家")); footer.append(Text.literal("§7共 " + lines.size() + " 位玩家"));
// 死了
if (page < totalPages) { if (page < totalPages) {
int finalPage1 = page; int finalPage1 = page;

View File

@ -4,9 +4,14 @@
"version": "${version}", "version": "${version}",
"name": "玩家在线时长统计Mod", "name": "玩家在线时长统计Mod",
"description": "", "description": "",
"authors": [], "authors": [
"contact": {}, "BRanulf"
"license": "MIT", ],
"contact": {
"homepage": "https://git.branulf.top/Branulf",
"sources": "https://git.branulf.top/Branulf/playerOnlineTimeWeb"
},
"license": "GPL",
"environment": "server", "environment": "server",
"entrypoints": { "entrypoints": {
"main": [ "main": [

View File

@ -1,231 +0,0 @@
<!--下面全是嵌入式卡片的代码复制后找个位置直接粘贴就行个人建议body的最下面-->
<!--记得更改serverUrl地址端口后面建议别动-->
<div id="minecraft-card-container"></div>
<script>
// 感谢deepseek大爹
// ===== 配置 =====
const config = {
serverUrl: "http://localhost:60048/api/widget-data",
updateInterval: 5000,
cardWidth: "300px"
};
(function initCard() {
if(localStorage.getItem('mcCardClosed')) return;
const card = createCard();
document.getElementById('minecraft-card-container').appendChild(card);
setupDragAndClose(card);
fetchData(card).then(() => {
setInterval(() => fetchData(card), config.updateInterval);
});
setTimeout(() => card.style.display = 'block', 100);
})();
function createCard() {
const card = document.createElement('div');
card.id = 'mc-status-card';
card.style.cssText = `
position: fixed; width: ${config.cardWidth}; z-index: 99999;
right: 20px; bottom: 20px; border-radius: 12px; background: white;
box-shadow: 0 4px 20px rgba(0,0,0,0.15); font-family: 'Segoe UI', sans-serif;
overflow: hidden; transition: all 0.2s; display: none;
`;
card.innerHTML = `
<div class="card-header" style="cursor:move;background:linear-gradient(135deg,#6a11cb 0%,#2575fc 100%);
color:white;padding:12px 15px;position:relative;">
<span>服务器玩家状态</span>
<span class="close-btn" style="position:absolute;right:10px;top:10px;color:white;
opacity:0.8;cursor:pointer;transition:opacity 0.2s;">×</span>
</div>
<div class="card-body" style="padding:0;">
<div class="loading" style="padding:20px;text-align:center;">
<div style="width:20px;height:20px;margin:0 auto 10px;border:3px solid rgba(0,0,0,0.1);
border-radius:50%;border-top-color:#2575fc;animation:spin 1s linear infinite;"></div>
<p>连接服务器中...</p>
</div>
</div>
`;
return card;
}
async function fetchData(card) {
const body = card.querySelector('.card-body');
try {
// 方案1尝试直接请求如果同源
let data = await tryDirectFetch();
// 方案2如果失败则使用服务器端代理
if(!data) data = await tryServerProxy();
if(data) {
renderCard(body, data);
} else {
throw new Error('所有数据获取方式均失败');
}
} catch(error) {
body.innerHTML = `
<div style="padding:20px;text-align:center;">
<p style="color:#dc3545;">⚠️ 数据加载失败</p>
<small style="color:#6c757d;">${error.message}</small>
<button onclick="location.reload()" style="margin-top:10px;padding:5px 10px;
background:#f8f9fa;border:1px solid #ddd;border-radius:4px;cursor:pointer;">
重试
</button>
</div>
`;
console.error('数据获取失败:', error);
}
}
async function tryDirectFetch() {
try {
const response = await fetch(config.serverUrl, {
headers: { 'Accept': 'application/json' }
});
return await fetchWidgetData();
} catch {
return null;
}
}
async function fetchWidgetData() {
try {
const response = await fetch('http://localhost:60048/api/widget-data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('获取到的数据:', data);
return data;
} catch (error) {
console.error('获取widget数据时出错:', error);
return {
onlineCount: 0,
whitelistPlayers: [],
topPlayers: [],
timestamp: Date.now()
};
}
}
async function tryServerProxy() {
try {
// 备用的暂时不用deepseek建议留着
const proxyUrl = 'http:// ' +
encodeURIComponent(config.serverUrl);
const response = await fetch(proxyUrl);
return await response.json();
} catch {
return null;
}
}
function renderCard(container, data) {
container.innerHTML = `
<div style="display:flex;align-items:center;padding:10px 15px;border-bottom:1px solid rgba(0,0,0,0.05);">
<span style="font-size:1.2rem;margin-right:12px;color:#2575fc;">👥</span>
<span>在线玩家</span>
<span style="margin-left:auto;background:#0d6efd;color:white;
padding:0.25em 0.6em;border-radius:50rem;font-size:0.75em;">
${data.onlineCount}
</span>
</div>
${data.topPlayers?.length ? `
<div style="padding:8px 15px;background:#f8f9fa;">
<small><span style="color:#ffc107">🏆</span> 时长前三</small>
</div>
${data.topPlayers.map(p => renderPlayer(p)).join('')}
` : ''}
<div style="padding:8px 15px;background:#f8f9fa;">
<small><span style="color:#2575fc">ⓘ</span> 在线玩家</small>
</div>
<div style="max-height:150px;overflow-y:auto;">
${data.whitelistPlayers?.length ?
data.whitelistPlayers.map(p => renderPlayer(p)).join('') :
'<div style="padding:15px;text-align:center;color:#6c757d;font-style:italic">无在线玩家</div>'
}
</div>
<div style="padding:8px;text-align:center;color:#6c757d;font-size:0.8em;">
更新: ${formatTime(data.timestamp)}
</div>
`;
}
function renderPlayer(player) {
return `
<div style="display:flex;align-items:center;padding:8px 15px;border-bottom:1px solid rgba(0,0,0,0.05);">
<div style="width:24px;height:24px;border-radius:50%;background:#e9ecef;margin-right:10px;
display:flex;align-items:center;justify-content:center;font-size:12px;color:#495057;">
${player.name.charAt(0).toUpperCase()}
</div>
<span style="flex-grow:1;">${player.name}</span>
<span style="font-size:0.9rem;color:#6c757d;">${player.time}</span>
</div>
`;
}
function setupDragAndClose(card) {
const header = card.querySelector('.card-header');
let isDragging = false, offsetX, offsetY;
header.addEventListener('mousedown', (e) => {
if(e.target.classList.contains('close-btn')) return;
isDragging = true;
offsetX = e.clientX - card.getBoundingClientRect().left;
offsetY = e.clientY - card.getBoundingClientRect().top;
card.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if(!isDragging) return;
card.style.left = (e.clientX - offsetX) + 'px';
card.style.top = (e.clientY - offsetY) + 'px';
card.style.right = 'auto';
card.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
isDragging = false;
card.style.cursor = 'grab';
});
card.querySelector('.close-btn').addEventListener('click', () => {
card.style.display = 'none';
localStorage.setItem('mcCardClosed', 'true');
});
}
function formatTime(timestamp) {
const now = Date.now();
const diff = (now - timestamp) / 1000;
if(diff < 60) return '刚刚';
if(diff < 3600) return `${Math.floor(diff/60)}分钟前`;
return `${Math.floor(diff/3600)}小时前`;
}
document.head.insertAdjacentHTML('beforeend', `
<style>
@keyframes spin { to { transform: rotate(360deg); } }
#mc-status-card:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(0,0,0,0.2); }
</style>
`);
</script>