Files
kubejs/clockin.js
T

1052 lines
21 KiB
JavaScript

/*
* Copyright (c) 2026 @ZareMate
* Original author: @ZareMate
*
* Licensed under the MIT License.
*
* If you modify this script, you may additionally identify yourself as:
*
* Modified by: @YourUsername
*
* The original author attribution and copyright notice must remain.
*/
// Dependencies: LuckPerms
(function () {
var CLOCKIN_FILE = "kubejs/data/clockin.json";
var PERMISSION_LOGIN = "clockin.login";
var PERMISSION_STATUS = "clockin.command.status";
var PERMISSION_IN = "clockin.command.in";
var PERMISSION_OUT = "clockin.command.out";
var PERMISSION_LEADERBOARD = "clockin.command.leaderboard";
var PERMISSION_INFO = "clockin.command.info";
var LuckPermsApiProvider = Java.loadClass(
"net.luckperms.api.LuckPermsProvider",
);
var luckPermsApi = null;
function getLuckPerms() {
if (luckPermsApi) {
return luckPermsApi;
}
try {
luckPermsApi = LuckPermsApiProvider.get();
console.log("[ClockIn] LuckPerms API loaded.");
return luckPermsApi;
} catch (error) {
console.error(
"[ClockIn] Failed to load LuckPerms API: " + String(error),
);
return null;
}
}
function hasPermission(player, permission) {
var api = getLuckPerms();
if (!api || !player) {
return false;
}
try {
var user = api.getUserManager().getUser(player.uuid);
if (!user) {
return false;
}
return user
.getCachedData()
.getPermissionData()
.checkPermission(permission)
.asBoolean();
} catch (error) {
console.error(
"[ClockIn] Permission check failed for " +
String(player.username) +
": " +
String(error),
);
return false;
}
}
function sourceHasPermission(source, permission) {
if (!source) {
return false;
}
if (source.hasPermission(3)) {
return true;
}
if (!source.isPlayer()) {
return false;
}
return hasPermission(source.player, permission);
}
function sourceHasAnyCommandPermission(source) {
return (
sourceHasPermission(source, PERMISSION_STATUS) ||
sourceHasPermission(source, PERMISSION_IN) ||
sourceHasPermission(source, PERMISSION_OUT) ||
sourceHasPermission(source, PERMISSION_LEADERBOARD) ||
sourceHasPermission(source, PERMISSION_INFO)
);
}
var data = {
version: 1,
players: {},
};
function createEmptyData() {
return {
version: 1,
players: {},
};
}
function createPlayerData(uuid, name) {
return {
name: String(name),
totalSeconds: 0,
clockedIn: false,
clockInAt: 0,
autoClockedOut: false,
};
}
function getMapValue(object, key) {
if (!object) {
return null;
}
try {
if (typeof object.get === "function") {
var value = object.get(key);
if (value !== null && value !== undefined) {
return value;
}
}
} catch (error) {}
try {
if (object[key] !== undefined && object[key] !== null) {
return object[key];
}
} catch (error) {}
return null;
}
function toNumber(value, fallback) {
if (value === null || value === undefined) {
return fallback;
}
try {
var number = Number(value);
if (isFinite(number)) {
return number;
}
} catch (error) {}
return fallback;
}
function toBoolean(value, fallback) {
if (value === true || value === false) {
return value;
}
var text = String(value).toLowerCase();
if (text === "true") {
return true;
}
if (text === "false") {
return false;
}
return fallback;
}
function normalisePlayerData(input) {
var playerData = {
name: "Unknown",
totalSeconds: 0,
clockedIn: false,
clockInAt: 0,
autoClockedOut: false,
};
if (!input) {
return playerData;
}
var name = getMapValue(input, "name");
var totalSeconds = getMapValue(input, "totalSeconds");
var clockedIn = getMapValue(input, "clockedIn");
var clockInAt = getMapValue(input, "clockInAt");
var autoClockedOut = getMapValue(input, "autoClockedOut");
if (name !== null && name !== undefined && String(name).length > 0) {
playerData.name = String(name);
}
playerData.totalSeconds = Math.max(
0,
Math.floor(toNumber(totalSeconds, 0)),
);
playerData.clockInAt = Math.max(0, Math.floor(toNumber(clockInAt, 0)));
playerData.clockedIn = toBoolean(clockedIn, false);
playerData.autoClockedOut = toBoolean(autoClockedOut, false);
return playerData;
}
function loadData() {
try {
var loaded = JsonIO.read(CLOCKIN_FILE);
console.log(loaded);
data = createEmptyData();
if (!loaded) {
console.log("[ClockIn] No existing data file found.");
saveData();
return;
}
var version = getMapValue(loaded, "version");
if (version !== null && version !== undefined) {
data.version = Math.floor(toNumber(version, 1));
}
var loadedPlayers = getMapValue(loaded, "players");
if (!loadedPlayers) {
saveData();
return;
}
try {
if (typeof loadedPlayers.entrySet === "function") {
var entries = loadedPlayers.entrySet().iterator();
while (entries.hasNext()) {
var entry = entries.next();
var uuid = String(entry.getKey());
var playerData = normalisePlayerData(entry.getValue());
data.players[uuid] = playerData;
}
} else {
for (var uuid in loadedPlayers) {
var input = loadedPlayers[uuid];
if (!input) {
continue;
}
data.players[String(uuid)] = normalisePlayerData(input);
}
}
} catch (error) {
console.error(
"[ClockIn] Failed to load player records: " + String(error),
);
}
console.log(
"[ClockIn] Loaded " + countPlayers() + " player record(s).",
);
saveData();
} catch (error) {
console.error(
"[ClockIn] Failed to load clock-in data: " + String(error),
);
data = createEmptyData();
saveData();
}
}
function saveData() {
try {
var output = {
version: 1,
players: {},
};
for (var uuid in data.players) {
var playerData = data.players[uuid];
if (!playerData) {
continue;
}
var totalSeconds = toNumber(playerData.totalSeconds, 0);
if (!isFinite(totalSeconds) || totalSeconds < 0) {
totalSeconds = 0;
}
var clockInAt = toNumber(playerData.clockInAt, 0);
if (!isFinite(clockInAt) || clockInAt < 0) {
clockInAt = 0;
}
output.players[String(uuid)] = {
name: String(playerData.name || "Unknown"),
totalSeconds: Math.floor(totalSeconds),
clockedIn: playerData.clockedIn === true,
clockInAt: Math.floor(clockInAt),
autoClockedOut: playerData.autoClockedOut === true,
};
}
JsonIO.write(CLOCKIN_FILE, output);
console.log(
"[ClockIn] Saved " + countPlayers() + " player record(s).",
);
} catch (error) {
console.error("[ClockIn] FAILED TO SAVE DATA: " + String(error));
}
}
function countPlayers() {
var count = 0;
for (var uuid in data.players) {
count++;
}
return count;
}
function getPlayerUuid(player) {
return String(player.uuid);
}
function getOrCreatePlayerData(player) {
var uuid = getPlayerUuid(player);
var playerData = data.players[uuid];
if (!playerData) {
playerData = createPlayerData(uuid, player.username);
data.players[uuid] = playerData;
saveData();
} else {
playerData.name = String(player.username);
playerData.totalSeconds = Math.max(
0,
Math.floor(toNumber(playerData.totalSeconds, 0)),
);
playerData.clockInAt = Math.max(
0,
Math.floor(toNumber(playerData.clockInAt, 0)),
);
}
return playerData;
}
function findPlayerDataByUuid(uuid) {
return data.players[String(uuid)] || null;
}
function findPlayerDataByName(name) {
var target = String(name).toLowerCase();
for (var uuid in data.players) {
var playerData = data.players[uuid];
if (!playerData) {
continue;
}
if (String(playerData.name).toLowerCase() === target) {
return playerData;
}
}
return null;
}
function getCurrentSessionSeconds(playerData) {
if (
!playerData ||
playerData.clockedIn !== true ||
!playerData.clockInAt
) {
return 0;
}
var clockInAt = toNumber(playerData.clockInAt, 0);
if (!isFinite(clockInAt) || clockInAt <= 0) {
return 0;
}
var elapsed = Math.floor((Date.now() - clockInAt) / 1000);
if (!isFinite(elapsed) || elapsed < 0) {
return 0;
}
return elapsed;
}
function getTotalSeconds(playerData) {
if (!playerData) {
return 0;
}
var totalSeconds = toNumber(playerData.totalSeconds, 0);
if (!isFinite(totalSeconds) || totalSeconds < 0) {
totalSeconds = 0;
}
var currentSession = getCurrentSessionSeconds(playerData);
return Math.max(0, Math.floor(totalSeconds) + currentSession);
}
function formatDuration(totalSeconds) {
var seconds = toNumber(totalSeconds, 0);
if (!isFinite(seconds) || seconds < 0) {
seconds = 0;
}
seconds = Math.floor(seconds);
var days = Math.floor(seconds / 86400);
seconds %= 86400;
var hours = Math.floor(seconds / 3600);
seconds %= 3600;
var minutes = Math.floor(seconds / 60);
seconds %= 60;
if (days > 0) {
return (
days +
"d " +
String(hours).padStart(2, "0") +
"h " +
String(minutes).padStart(2, "0") +
"m " +
String(seconds).padStart(2, "0") +
"s"
);
}
if (hours > 0) {
return (
hours +
"h " +
String(minutes).padStart(2, "0") +
"m " +
String(seconds).padStart(2, "0") +
"s"
);
}
if (minutes > 0) {
return minutes + "m " + String(seconds).padStart(2, "0") + "s";
}
return seconds + "s";
}
function sendSeparator(player) {
player.tell(Text.of("────────────────────────────────────").darkGray());
}
function sendLoginPrompt(player) {
player.tell("");
player.tell(Text.of("ADMINISTRATION TIME TRACKING").gold().bold());
player.tell(
Text.of("You can track your administration help time.").gray(),
);
player.tell(
Text.of("[ CLOCK IN ]")
.green()
.bold()
.clickRunCommand("/clockin in"),
);
player.tell(Text.of("Use /clockin to view your tracked time.").gray());
player.tell("");
}
function clockIn(player) {
var playerData = getOrCreatePlayerData(player);
if (playerData.clockedIn) {
player.tell(Text.of("You are already clocked in.").yellow());
return false;
}
playerData.clockedIn = true;
playerData.clockInAt = Date.now();
playerData.autoClockedOut = false;
saveData();
player.tell(Text.of("You are now clocked in.").green());
player.tell(
Text.of("Administration time tracking has started.").gray(),
);
return true;
}
function clockOut(player, automatic) {
var playerData = getOrCreatePlayerData(player);
if (!playerData.clockedIn) {
player.tell(Text.of("You are not currently clocked in.").yellow());
return false;
}
var sessionSeconds = getCurrentSessionSeconds(playerData);
var totalSeconds = toNumber(playerData.totalSeconds, 0);
if (!isFinite(totalSeconds) || totalSeconds < 0) {
totalSeconds = 0;
}
playerData.totalSeconds =
Math.floor(totalSeconds) + Math.floor(sessionSeconds);
playerData.clockedIn = false;
playerData.clockInAt = 0;
playerData.autoClockedOut = automatic === true;
saveData();
if (automatic) {
player.tell(
Text.of(
"You were clocked out because you left the server.",
).yellow(),
);
player.tell(
Text.of("Session time: ")
.gray()
.append(Text.of(formatDuration(sessionSeconds)).aqua()),
);
return true;
}
player.tell(Text.of("You are now clocked out.").green());
player.tell(
Text.of("Session time: ")
.gray()
.append(Text.of(formatDuration(sessionSeconds)).aqua()),
);
player.tell(
Text.of("Total administration time: ")
.gray()
.append(
Text.of(formatDuration(playerData.totalSeconds)).aqua(),
),
);
return true;
}
function showStatus(player) {
var playerData = getOrCreatePlayerData(player);
var total = getTotalSeconds(playerData);
sendSeparator(player);
player.tell(Text.of("ADMIN CLOCK-IN").gold().bold());
if (playerData.clockedIn) {
player.tell(
Text.of("Status: ")
.gray()
.append(Text.of("CLOCKED IN").green().bold()),
);
player.tell(
Text.of("Current session: ")
.gray()
.append(
Text.of(
formatDuration(
getCurrentSessionSeconds(playerData),
),
).aqua(),
),
);
player.tell(
Text.of("Total time: ")
.gray()
.append(Text.of(formatDuration(total)).aqua()),
);
player.tell("");
player.tell(
Text.of("[ CLOCK OUT ]")
.red()
.bold()
.clickRunCommand("/clockin out"),
);
} else {
player.tell(
Text.of("Status: ")
.gray()
.append(Text.of("CLOCKED OUT").red().bold()),
);
player.tell(
Text.of("Total time: ")
.gray()
.append(Text.of(formatDuration(total)).aqua()),
);
player.tell("");
player.tell(
Text.of("[ CLOCK IN ]")
.green()
.bold()
.clickRunCommand("/clockin in"),
);
}
sendSeparator(player);
}
function getLeaderboard() {
var entries = [];
for (var uuid in data.players) {
var playerData = data.players[uuid];
if (!playerData) {
continue;
}
entries.push({
name: String(playerData.name || "Unknown"),
time: getTotalSeconds(playerData),
});
}
entries.sort(function (a, b) {
if (b.time !== a.time) {
return b.time - a.time;
}
return a.name.localeCompare(b.name);
});
return entries;
}
function showLeaderboard(source) {
var entries = getLeaderboard();
source.sendSuccess(
Text.of("────────────────────────────────────").darkGray(),
false,
);
source.sendSuccess(
Text.of("ADMIN CLOCK-IN LEADERBOARD").gold().bold(),
false,
);
if (entries.length === 0) {
source.sendSuccess(
Text.of("No administration time has been recorded yet.").gray(),
false,
);
source.sendSuccess(
Text.of("────────────────────────────────────").darkGray(),
false,
);
return;
}
var limit = Math.min(entries.length, 10);
for (var i = 0; i < limit; i++) {
var entry = entries[i];
source.sendSuccess(
Text.of("#" + (i + 1) + " ")
.gold()
.append(Text.of(entry.name).white())
.append(Text.of(" — ").darkGray())
.append(Text.of(formatDuration(entry.time)).aqua()),
false,
);
}
source.sendSuccess(
Text.of("Showing top " + limit + " players.").gray(),
false,
);
source.sendSuccess(
Text.of("────────────────────────────────────").darkGray(),
false,
);
}
function showPlayerInfo(source, targetName) {
var playerData = findPlayerDataByName(targetName);
if (!playerData) {
source.sendFailure(
Text.of("No clock-in data found for " + targetName + ".").red(),
);
return false;
}
var total = getTotalSeconds(playerData);
source.sendSuccess(
Text.of("────────────────────────────────────").darkGray(),
false,
);
source.sendSuccess(Text.of("ADMIN CLOCK-IN INFO").gold().bold(), false);
source.sendSuccess(
Text.of("Player: ").gray().append(Text.of(playerData.name).white()),
false,
);
source.sendSuccess(
Text.of("Status: ")
.gray()
.append(
Text.of(
playerData.clockedIn ? "CLOCKED IN" : "CLOCKED OUT",
).color(playerData.clockedIn ? "green" : "red"),
),
false,
);
if (playerData.clockedIn) {
source.sendSuccess(
Text.of("Current session: ")
.gray()
.append(
Text.of(
formatDuration(
getCurrentSessionSeconds(playerData),
),
).aqua(),
),
false,
);
}
source.sendSuccess(
Text.of("Total time: ")
.gray()
.append(Text.of(formatDuration(total)).aqua()),
false,
);
source.sendSuccess(
Text.of("────────────────────────────────────").darkGray(),
false,
);
return true;
}
ServerEvents.commandRegistry(function (event) {
var Commands = event.commands;
var Arguments = event.arguments;
var clockInCommand = Commands.literal("in")
.requires(function (source) {
return (
source.isPlayer() &&
sourceHasPermission(source, PERMISSION_IN)
);
})
.executes(function (ctx) {
try {
return clockIn(ctx.source.player) ? 1 : 0;
} catch (error) {
console.error(
"[ClockIn] /clockin in error: " + String(error),
);
ctx.source.sendFailure(
Text.of("Clock-in error: " + String(error)).red(),
);
return 0;
}
});
var clockOutCommand = Commands.literal("out")
.requires(function (source) {
return (
source.isPlayer() &&
sourceHasPermission(source, PERMISSION_OUT)
);
})
.executes(function (ctx) {
try {
return clockOut(ctx.source.player, false) ? 1 : 0;
} catch (error) {
console.error(
"[ClockIn] /clockin out error: " + String(error),
);
ctx.source.sendFailure(
Text.of("Clock-out error: " + String(error)).red(),
);
return 0;
}
});
var leaderboardCommand = Commands.literal("leaderboard")
.requires(function (source) {
return sourceHasPermission(source, PERMISSION_LEADERBOARD);
})
.executes(function (ctx) {
try {
showLeaderboard(ctx.source);
return 1;
} catch (error) {
console.error(
"[ClockIn] /clockin leaderboard error: " +
String(error),
);
ctx.source.sendFailure(
Text.of("Leaderboard error: " + String(error)).red(),
);
return 0;
}
});
var infoCommand = Commands.literal("info")
.requires(function (source) {
return sourceHasPermission(source, PERMISSION_INFO);
})
.then(
Commands.argument(
"player",
Arguments.STRING.create(event),
).executes(function (ctx) {
try {
var targetName = Arguments.STRING.getResult(
ctx,
"player",
);
return showPlayerInfo(ctx.source, targetName) ? 1 : 0;
} catch (error) {
console.error(
"[ClockIn] /clockin info error: " + String(error),
);
ctx.source.sendFailure(
Text.of("Info error: " + String(error)).red(),
);
return 0;
}
}),
);
var clockInRoot = Commands.literal("clockin")
.requires(function (source) {
return sourceHasAnyCommandPermission(source);
})
.executes(function (ctx) {
try {
if (!ctx.source.isPlayer()) {
ctx.source.sendFailure(
Text.of("Only players can use /clockin.").red(),
);
return 0;
}
if (!sourceHasPermission(ctx.source, PERMISSION_STATUS)) {
ctx.source.sendFailure(
Text.of(
"You do not have permission to use /clockin.",
).red(),
);
return 0;
}
showStatus(ctx.source.player);
return 1;
} catch (error) {
console.error("[ClockIn] /clockin error: " + String(error));
ctx.source.sendFailure(
Text.of(
"Clock-in status error: " + String(error),
).red(),
);
return 0;
}
});
clockInRoot.then(clockInCommand);
clockInRoot.then(clockOutCommand);
clockInRoot.then(leaderboardCommand);
clockInRoot.then(infoCommand);
event.register(clockInRoot);
});
PlayerEvents.loggedIn(function (event) {
try {
var player = event.player;
var uuid = String(player.uuid);
var playerData = findPlayerDataByUuid(uuid);
if (playerData && playerData.clockedIn) {
var sessionSeconds = getCurrentSessionSeconds(playerData);
var totalSeconds = toNumber(playerData.totalSeconds, 0);
if (!isFinite(totalSeconds) || totalSeconds < 0) {
totalSeconds = 0;
}
playerData.totalSeconds =
Math.floor(totalSeconds) + Math.floor(sessionSeconds);
playerData.clockedIn = false;
playerData.clockInAt = 0;
playerData.autoClockedOut = true;
playerData.name = String(player.username);
saveData();
}
if (playerData && playerData.autoClockedOut) {
player.tell(
Text.of(
"You were clocked out because you left the server.",
).yellow(),
);
player.tell(
Text.of(
"Your previous administration session has been closed.",
).gray(),
);
playerData.autoClockedOut = false;
saveData();
}
if (hasPermission(player, PERMISSION_LOGIN)) {
sendLoginPrompt(player);
}
} catch (error) {
console.error("[ClockIn] Login handler error: " + String(error));
}
});
PlayerEvents.loggedOut(function (event) {
try {
var player = event.player;
var uuid = String(player.uuid);
var playerData = findPlayerDataByUuid(uuid);
if (!playerData || !playerData.clockedIn) {
return;
}
var sessionSeconds = getCurrentSessionSeconds(playerData);
var totalSeconds = toNumber(playerData.totalSeconds, 0);
if (!isFinite(totalSeconds) || totalSeconds < 0) {
totalSeconds = 0;
}
playerData.totalSeconds =
Math.floor(totalSeconds) + Math.floor(sessionSeconds);
playerData.clockedIn = false;
playerData.clockInAt = 0;
playerData.autoClockedOut = true;
playerData.name = String(player.username);
saveData();
console.log(
"[ClockIn] Automatically clocked out " +
String(player.username) +
" after " +
formatDuration(sessionSeconds) +
".",
);
} catch (error) {
console.error("[ClockIn] Logout handler error: " + String(error));
}
});
loadData();
console.log("[ClockIn] Loaded successfully.");
})();