Kb+ - cuberealm.io

Cuberealm extender Kb+, adds helpful features like Zoom and friend/enemy list

当前为 2025-06-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Kb+ - cuberealm.io
// @namespace   https://github.com/Thibb1
// @match       https://cuberealm.io/*
// @match       https://www.cuberealm.io/*
// @run-at      document-start
// @grant       none
// @version     1.2
// @author      Thibb1
// @description Cuberealm extender Kb+, adds helpful features like Zoom and friend/enemy list
// @license     GPL
// ==/UserScript==

let loaded = false;
console.log("Kb+ started, waiting to load...");

Object.defineProperty(Object.prototype, "_eventEmitter", {
  get() { return this.__eventEmitter },
  set(v) {
    this.__eventEmitter = v;
    window.__eventEmitter = v;
    this.__eventEmitter.emit = new Proxy(this.__eventEmitter.emit, {
      apply(target, thisArg, args) {
        try {
          const type = Number(args[0]);
          switch (type) {
            case Event.Tick:
              if (!loaded) {
                loaded = true;
                console.log("Kb+ loaded");
              }
              break;
            case Events.InitPlayer:
              if (!settings.welcomeText) break;
              sendMessage("Kb+ loaded <3 Made by Thibb1", Colors.MAGENTA);
              sendMessage(
                Colors.ORANGE.convert() + "Send '" +
                Colors.CYAN.convert() + settings.commandPrefix + "help" +
                Colors.ORANGE.convert() + "' in chat to see available commands"
              );
              sendMessage(
                Colors.ORANGE.convert() + "Pres '" +
                Colors.CYAN.convert() + settings.zoomKey +
                Colors.ORANGE.convert() + "' to zoom"
              );
              break;
            case Events.Message:
              args[1] = handleMessage(args[1]);
              if (args[1] == "") args[0] = Events.Disable;
              break;
            case Events.SendMessage:
              const send = args[1];
              if (settings.keepHistory) saveHistory(send);
              if (send.startsWith(settings.commandPrefix)) {
                handleCommand(send.slice(settings.commandPrefix.length));
                args[0] = Events.Disable;
              }
              break;
            case Events.TabValues:
              handleTabValues(args[1]);
              break;
            default:
              if (settings.debug) console.log(`Event ${type} emitted with args:`, args.slice(1));
              break;
          }
        } catch (error) {
          console.error('Error in event emitter:', error);
        } finally {
          return target.apply(thisArg, args);
        }
      }
    });
  }
})

const Events = {
  Tick: 0,
  JoinRoom: 1,
  InitPlayer: 2,
  Disconnect: 4,
  Keyboard: 9,
  ChunkData: 10,
  // 11 load/unload chunk ?
  UnlockMouse: 15,
  LockMouse: 16,
  // 20 remove player/entity?
  ChangeSlot: 24,
  HoldingItem: 32,
  Message: 33,
  SendMessage: 34,
  TabValues: 44,
  Disable: 99999
}

const createColor = (code) => ({
  code,
  convert() { return "∁" + this.code.slice(1); }
});

const Colors = {
  DARK_RED: createColor("#c43535"),
  RED: createColor("#ff5050"),
  PINK: createColor("#ff89e9"),
  BROWN: createColor("#de660f"),
  ORANGE: createColor("#ffa540"),
  GOLD: createColor("#ffd700"),
  YELLOW: createColor("#ffff40"),
  DARK_GREEN: createColor("#40aa40"),
  GREEN: createColor("#40ff40"),
  DARK_CYAN: createColor("#40a5a5"),
  CYAN: createColor("#40ffff"),
  DARK_BLUE: createColor("#1b7dff"),
  BLUE: createColor("#6ab4ff"),
  DARK_PURPLE: createColor("#c04eff"),
  PURPLE: createColor("#c28fff"),
  MAGENTA: createColor("#ff40ff"),
  WHITE: createColor("#ffffff"),
  GRAY: createColor("#a9a9a9"),
  DARK_GRAY: createColor("#808080"),
  BLACK: createColor("#565656")
}
const Modes = ["survival", "creative", "peaceful", "custom"];

function getData(key, defaultValue = {}) {
  const raw = localStorage.getItem(key);
  if (raw) {
    const data = JSON.parse(raw);
    if (data.version && data.version !== defaultValue.version) {
      const mergedSettings = { ...defaultValue, ...data };
      mergedSettings.version = defaultValue.version;
      setData(key, mergedSettings);
      return mergedSettings;
    }
    return data;
  }
  return defaultValue;
}

function setData(key, data) {
  localStorage.setItem(key, JSON.stringify(data));
}

const defaultSettings = {
  version: "1.2",
  commandPrefix: '?',
  zoomKey: 'x',
  welcomeText: true,
  keepHistory: true,
  disableTips: true,
  disableCantBreak: true,
  disableChunkInChat: true,
  disableAds: true,
  debug: false,
  gameVersion: 23
}
const settings = getData('Kb+', defaultSettings);
const tabList = [];

function saveSettings() {
  setData('Kb+', settings);
}

const friends = getData('Kb+_friends', []);
const enemies = getData('Kb+_enemies', []);

const history = getData("Kb+_hst", []);
let historyIndex = -1;
const MAX_HISTORY = 50;

function saveHistory(message) {
  history.push(message);
  if (history.length > MAX_HISTORY) {
    history.shift();
  }
  setData('Kb+_hst', history);
  historyIndex = history.length - 1;
}

CanvasRenderingContext2D.prototype.fillText = new Proxy(CanvasRenderingContext2D.prototype.fillText, {
  apply(target, thisArg, args) {
    try {
      const text = args[0];
      if (friends.includes(text)) {
        thisArg.fillStyle = Colors.GREEN.code;
      } else if (enemies.includes(text)) {
        thisArg.fillStyle = Colors.RED.code;
      }
    } catch (error) {
      console.error('Error in fillText proxy:', error);
    } finally {
      return target.apply(thisArg, args);
    }
  }
});

Object.defineProperty(Object.prototype, "generalFOV", {
  get() { return this._generalFOV; },
  set(v) {
    this._generalFOV = v;
    window.__cbSettings = this;
    setTimeout(() => {
      for (const key of Object.keys(this)) {
        if (typeof this[key] === 'function' && this[key].toString().includes('generalFOV')) {
          this.setGeneralFOV = this[key];
          break;
        }
      }
    }, 0)
  }
});

if (settings.disableAds) {
  // needs refining
  Object.defineProperty(Object.prototype, "adplayer", {
    get() {
      if (window.adsLoadedPromiseResolve) window.adsLoadedPromiseResolve();
      return null;
    },
    set(v) {}
  });
  Object.defineProperty(Object.prototype, "requestAds", {
    get() {
      if (window.adsLoadedPromiseResolve) window.adsLoadedPromiseResolve();
      return () => {};
    },
    set(v) {}
  });
}

let previousFOV = 100;
let zoomOn = false;
document.addEventListener('keydown', (event) => {
  if (event.key.toLowerCase() === settings.zoomKey.toLowerCase() && window.__cbSettings && !zoomOn) {
    zoomOn = true;
    const CBsettings = JSON.parse(localStorage.getItem("settings"));
    previousFOV = CBsettings.state._generalFOV ?? CBsettings.state.generalFOV ?? previousFOV;
    window.__cbSettings?.setGeneralFOV(40);
  }
});
document.addEventListener('keyup', (event) => {
  if (event.key.toLowerCase() === settings.zoomKey.toLowerCase() && window.__cbSettings && zoomOn) {
    zoomOn = false;
    window.__cbSettings?.setGeneralFOV(previousFOV);
  }
});

function sendMessage(message, color) {
  const text = (color ? color.convert() : "") + message;
  window.__eventEmitter.emit(Events.Message, text);
}

const commands = {
  "help": {
    description: "[command] - Shows help menu or details about a command"
  },
  "friends": {
    description: "- Prints your friend list"
  },
  "addfriend": {
    description: "[name] - Add a friend to your friend list"
  },
  "delfriend": {
    description: "[name] - Remove a friend from your friend list"
  },
  "enemies": {
    description: "- Prints your enemy list"
  },
  "addenemy": {
    description: "[name] - Add an enemy to your enemy list"
  },
  "delenemy": {
    description: "[name] - Remove an enemy from your enemy list"
  },
  "toggle": {
    description: "[setting] - Toggle a setting on or off",
    settings: ["welcomeText", "keepHistory", "disableTips", "disableCantBreak", "disableChunkInChat", "disableAds", "debug"]
  },
  "toggles": {
    description: "- List available toggle settings"
  },
  "join": {
    description: "[type] [region] - Join a specific region (e.g., 'join survival eu-1' or 'join custom ws://<ip-address>:<port>')"
  },
  "leave": {
    description: "- Leave the current game"
  }
}

function handleCommand(cmd) {
  const parts = cmd.split(" ").filter(Boolean);
  const partsLen = parts.length;
  switch (parts[0]) {
    case "help":
    case "?":
      if (partsLen == 2) {
        const command = parts[1];
        if (commands[command]) {
          sendMessage(`${settings.commandPrefix}${command} ${commands[command].description}`, Colors.ORANGE);
          if (commands[command].settings) {
            sendMessage("Available settings: " + commands[command].settings.join(", "), Colors.ORANGE);
          }
        } else {
          sendMessage("Command not found: " + command, Colors.RED);
        }
        break;
      }
      sendMessage("=".repeat(20) + "Help Menu" + "=".repeat(20), Colors.CYAN);
      sendMessage(settings.commandPrefix + "help - Show this menu", Colors.ORANGE);
      const friendsHelp = ["friends", "addfriend [name]", "delfriend [name]"].map(cmd => settings.commandPrefix + cmd).join(" ");
      sendMessage(friendsHelp + " - Manage friends", Colors.ORANGE);
      const enemiesHelp = ["enemies", "addenemy [name]", "delenemy [name]"].map(cmd => settings.commandPrefix + cmd).join(" ");
      sendMessage(enemiesHelp + " - Manage ennemies", Colors.ORANGE);
      const toggleHelp = ["toggle [setting]", "toggles"].map(cmd => settings.commandPrefix + cmd).join(" ");
      sendMessage(toggleHelp + " - Manage Kb+ toggles", Colors.ORANGE);
      sendMessage(settings.commandPrefix + "join <mode> <region> - Join a specific region", Colors.ORANGE);
      sendMessage(settings.commandPrefix + "leave - Leave the current game", Colors.ORANGE);
      sendMessage("=".repeat(49), Colors.CYAN);
      break;
    case "friends":
      sendMessage("Friends: " + friends.join(", "), Colors.BLUE);
      break;
    case "addfriend":
      if (partsLen == 2) {
        addFriend(parts[1]);
      } else {
        sendMessage("Invalid usage: " + settings.commandPrefix + "addfriend <name>", Colors.RED);
      }
      break;
    case "delfriend":
      if (partsLen == 2) {
        delFriend(parts[1]);
      } else {
        sendMessage("Invalid usage: " + settings.commandPrefix + "delfriend <name>", Colors.RED);
      }
      break;
    case "enemies":
      sendMessage("Enemies: " + enemies.join(", "), Colors.BLUE);
      break;
    case "addenemy":
      if (partsLen == 2) {
        addEnemy(parts[1]);
      } else {
        sendMessage("Invalid usage: " + settings.commandPrefix + "addenemy <name>", Colors.RED);
      }
      break;
    case "delenemy":
      if (partsLen == 2) {
        delEnemy(parts[1]);
      } else {
        sendMessage("Invalid usage: " + settings.commandPrefix + "delenemy <name>", Colors.RED);
      }
      break;
    case "toggle":
      if (partsLen == 2) {
        const setting = parts[1];
        if (commands.toggle.settings.includes(setting)) {
          settings[setting] = !settings[setting];
          saveSettings();
          sendMessage(`Toggled ${setting} to ${settings[setting]}`, Colors.GREEN);
        } else {
          sendMessage("Unknown setting: " + setting, Colors.RED);
        }
      } else {
        sendMessage("[Help] " + settings.commandPrefix + "toggle <setting>", Colors.RED);
      }
      break;
    case "toggles":
      sendMessage("Available toggles: " + commands.toggle.settings.join(", "), Colors.YELLOW);
      break;
    case "join":
      if (partsLen == 3) {
        const mode = parts[1];
        const region = parts[2];
        joinGame(mode, region);
      } else {
        sendMessage("[Help] " + settings.commandPrefix + "join <mode> <region>")
      }
      break;
    case "leave":
      sendMessage("Leaving game...", Colors.YELLOW);
      leave();
      break;
    default:
      sendMessage("Unknown command: " + parts[0], Colors.RED);
      break;
  }
}

function leave() {
  __eventEmitter.emit(Events.Disconnect);
}

function joinGame(mode, region) {
  if (!Modes.includes(mode)) {
    sendMessage("Invalid mode: " + mode + ". Available modes: " + Modes.join(", "), Colors.RED);
    return;
  }
  if (mode == "custom") {
    sendMessage(`Attempting to join ${region}...`, Colors.YELLOW);
    const secure = region.startsWith("wss://") ? true : false;
    region = region.slice(secure ? 6 : 5); // ws or wss
    const parts = region.split(":");
    const hostname = parts[0];
    const port = parts[1];
    leave();
    setTimeout(() => {__eventEmitter.emit(Events.JoinRoom, hostname, port, secure, "battle", "custom");}, 1000);
    return;
  }
  sendMessage(`Attempting to join ${mode}-${region}...`, Colors.YELLOW);
  fetch("https://cuberealm.io/v1/matchmake", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ mode: mode, room: `${mode}-${region}`, version: String(settings.gameVersion) })
  }).then(response => response.json()).then(data => {
    if (settings.debug) console.log("Matchmake response:", data);
    if (data.hostname && data.port) {
      leave();
      __eventEmitter.emit(Events.JoinRoom, data.hostname, data.port, data.isSecure, mode, data.room);
    } else {
      sendMessage(`Failed to join ${mode}-${region}. ${data.message || ''}`, Colors.RED);
    }
  }).catch(error => {
    console.error("Matchmake error:", error);
    sendMessage(`Error joining ${mode}-${region}: ${error.message}`, Colors.RED);
  });

}

function checkList(list, name) {
  if (!list.includes(name)) {
    const matchingPlayers = list.filter(player => player.startsWith(name));
    if (matchingPlayers.length > 0) {
      return matchingPlayers[0];
    }
    sendMessage("Player not found: " + name, Colors.RED);
    return "";
  }
  return name;
}

function addFriend(name) {
  name = checkList(tabList, name);
  if (name === "") return;
  if (!friends.includes(name)) {
    friends.push(name);
    if (enemies.includes(name)) delEnemy(name);
    setData("Kb+_friends", friends);
    sendMessage(`Added ${name} to friends list`, Colors.GREEN);
  } else {
    sendMessage(`${name} is already in your friends list`, Colors.YELLOW);
  }
}

function delFriend(name) {
  name = checkList(friends, name);
  if (name === "") return;
  const index = friends.indexOf(name);
  if (index > -1) {
    friends.splice(index, 1);
    setData("Kb+_friends", enemies);
    sendMessage(`Removed ${name} from friends list`, Colors.GREEN);
  } else {
    sendMessage(`${name} is not in your friends list`, Colors.YELLOW);
  }
}

function addEnemy(name) {
  name = checkList(tabList, name);
  if (name === "") return;
  if (!enemies.includes(name)) {
    enemies.push(name);
    if (friends.includes(name)) delFriend(name);
    setData("Kb+_enemies", enemies);
    sendMessage(`Added ${name} to enemies list`, Colors.GREEN);
  } else {
    sendMessage(`${name} is already in your enemies list`, Colors.YELLOW);
  }
}

function delEnemy(name) {
  name = checkList(enemies, name);
  if (name === "") return;
  const index = enemies.indexOf(name);
  if (index > -1) {
    enemies.splice(index, 1);
    setData("Kb+_enemies", friends);
    sendMessage(`Removed ${name} from enemies list`, Colors.GREEN);
  } else {
    sendMessage(`${name} is not in your enemies list`, Colors.YELLOW);
  }
}

function handleMessage(message) {
  if (message.startsWith("∁6ab4ff[∁ffd700Tip")) {
    if (settings.disableTips) return "";
  }
  if (message.startsWith(Colors.RED.convert())) {
    const error = message.slice(7);
    if (error.startsWith("You can't build") && settings.disableCantBreak) return "";
  }
  if (message.startsWith(Colors.GREEN.convert())) {
    const success = message.slice(7);
    if (success.startsWith("Entering") && settings.disableChunkInChat) return "";
    if (success.startsWith("Leaving") && settings.disableChunkInChat) return "";
  }
  if (message.startsWith(Colors.GOLD.convert())) {
    const parts = message.slice(7).split(" ");
    const name = parts[0];
    const text = " " + Colors.GOLD.convert() + parts.slice(1).join(" ");
    if (friends.includes(name)) {
      return Colors.GREEN.convert() + name + text;
    } else if (enemies.includes(name)) {
      return Colors.RED.convert() + name + text;
    }
    return message;
  }
  if (!message.includes("∁") && message.includes(":")) {
    const parts = message.split(":");
    const name = parts[0];
    const text = parts.slice(1).join(":");
    if (friends.includes(name)) {
      return Colors.GREEN.convert() + name + Colors.WHITE.convert() + ":" + text;
    } else if (enemies.includes(name)) {
      return Colors.RED.convert() + name + Colors.WHITE.convert() + ":" + text;
    }
  }
  return message;
}

function setInputValue(input, newValue) {
  // You need to use this to update react state or it wont register the change
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
    window.HTMLInputElement.prototype,
    'value').set;
  nativeInputValueSetter.call(input, newValue);
  const event = new Event('input', { bubbles: true });
  input.dispatchEvent(event);
}

function handleKeydownInput(event, input) {
  if (event.key === 'Tab' && input.value.startsWith(settings.commandPrefix)) {
    event.preventDefault();
    const currentValue = input.value;
    const commandPrefix = settings.commandPrefix;
    const command = currentValue.slice(commandPrefix.length);

    const autocomplete = (baseCommand, list) => {
      const settingVar = currentValue.slice(commandPrefix.length + baseCommand.length + 1);
      const matchs = list.filter(el => el.startsWith(settingVar));
      if (matchs.length > 0) {
        setInputValue(input, commandPrefix + baseCommand + " " + matchs[0]);
      }
    };

    const availableCommands = Object.keys(commands);
    const matchingCommands = availableCommands.filter(cmd => cmd.startsWith(command));

    if (matchingCommands.length > 0) {
      setInputValue(input, commandPrefix + matchingCommands[0]);
    } else if (currentValue.startsWith(commandPrefix + "help ")) {
      autocomplete("help", Object.keys(commands));
    } else if (currentValue.startsWith(commandPrefix + "addfriend ")) {
      autocomplete("addfriend", tabList);
    } else if (currentValue.startsWith(commandPrefix + "delfriend ")) {
      autocomplete("delfriend", friends);
    } else if (currentValue.startsWith(commandPrefix + "addenemy ")) {
      autocomplete("addenemy", tabList);
    } else if (currentValue.startsWith(commandPrefix + "delenemy ")) {
      autocomplete("delenemy", enemies);
    } else if (currentValue.startsWith(commandPrefix + "toggle ")) {
      autocomplete("toggle", commands.toggle.settings);
    } else if (currentValue.startsWith(commandPrefix + "join ")) {
      const joinParts = currentValue.slice(commandPrefix.length + "join ".length).split(" ");
      if (joinParts.length === 1) {
        autocomplete("join", Modes);
      }
    }
  } else if (event.key === 'ArrowUp') {
    event.preventDefault();
    if (historyIndex >= 0) {
      setInputValue(input, history[historyIndex]);
      historyIndex = Math.max(historyIndex - 1, 0);
    }
  } else if (event.key === 'ArrowDown') {
    event.preventDefault();
    if (historyIndex < history.length - 1) {
      historyIndex++;
      setInputValue(input, history[historyIndex]);
    } else {
      setInputValue(input, "");
      historyIndex = history.length - 1;
    }
  }
}

function findFirstString(obj) {
  for (const key in obj) {
    if (typeof obj[key] === 'string') {
      return obj[key];
    } else if (Array.isArray(obj[key])) {
      for (const item of obj[key]) {
        if (typeof item === 'object') {
          const result = findFirstString(item);
          if (result) return result;
        }
      }
    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
      const result = findFirstString(obj[key]);
      if (result) return result;
    }
  }
  return null;
}


function handleTabValues(object) {
  const playerName = findFirstString(object);
  if (playerName) {
    if (!tabList.includes(playerName)) {
      tabList.push(playerName);
    }
  }
}

document.addEventListener("DOMContentLoaded", () => {
  if (settings.disableAds) window.adSDKType = '';
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.addedNodes.length > 0) {
        mutation.addedNodes.forEach((addedNode) => {
          if (addedNode.tagName === 'INPUT' && addedNode.getAttribute('maxlength') === '100') {
            historyIndex = history.length - 1;
            addedNode.addEventListener('keydown', (event) => handleKeydownInput(event, addedNode));
          }
          if (!addedNode.querySelectorAll) return;
          addedNode.querySelectorAll('span').forEach(span => {
            const player = span.innerText;
            if (friends.includes(player)) {
              span.style.color = Colors.GREEN.code;
            } else if (enemies.includes(player)) {
              span.style.color = Colors.RED.code;
            }
          });
        });
      }
    });
  });
  try {
    observer.observe(document.body, { childList: true, subtree: true });
    setTimeout(() => {
      const appUI = document.querySelector('#app > div > div');
      observer.observe(appUI, { childList: true, subtree: true });
    }, 10000)
  } catch (error) {
    console.error("Couldn't hook input / document body, features like tab autocomplete and name coloring won't work. Try reloading the page.");
  }
});