Kb+ - cuberealm.io

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

// ==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.1
// @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) {
    if (!loaded) {
      loaded = true;
      console.log("Kb+ loaded");
    }
    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:
              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() + "Press '" +
                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")
}
Colors.ENEMY = Colors.RED;
Colors.FRIEND = Colors.GREEN;
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 marked = getData('Kb+_marked', {});

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;
      } else if (marked[text]) {
        thisArg.fillStyle = Colors[marked[text]].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"
  },
  "addmark": {
    description: "[color] [name] - Mark a player with a specific color",
  },
  "delmark": {
    description: "[name] - Remove a mark from a player"
  },
  "marks":{
    description: "- Prints your marked player 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;
  if (partsLen === 0) {
    sendMessage("Please enter a command.", Colors.RED);
    return;
  }
  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 [<command>] - Help menu or details on a command", 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 enemies", Colors.ORANGE);
      const markHelp = ["marks", "addmark [color] [name]", "delmark [name]"].map(cmd => settings.commandPrefix + cmd).join(" ");
      sendMessage(markHelp + " - Manage marked players", 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 "marks":
      let markedPlayers = Colors.BLUE.convert() + "Marked players:";
      for (const name in marked) {
        markedPlayers += ` ${Colors[marked[name]].convert()}${name}`;
      }
      sendMessage(markedPlayers, Colors.BLUE);
      break;
    case "addmark":
      if (partsLen >= 3) {
        const color = parts[1].toUpperCase();
        const name = parts.slice(2).join(" ");
        addMark(color, name);
      } else {
        sendMessage("[Help] " + settings.commandPrefix + "addmark <color> <name>", Colors.RED);
        sendMessage("Available colors: " + Object.keys(Colors).map(color => Colors[color].convert() + color).join(", "), Colors.RED);
      }
      break;
    case "delmark":
      if (partsLen == 2){
        const name = parts[1];
        delMark(name);
      } else {
        sendMessage("[Help] " + settings.commandPrefix + "delmark <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);
    if (marked[name]) delMark(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);
    if (marked[name]) delMark(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 addMark(color, name) {
  if (Colors[color]) {
    const playerName = checkList(tabList, name);
    if (playerName === "") return;
    if (friends.includes(name)) delFriend(name);
    if (enemies.includes(name)) delEnemy(name);
    marked[playerName] = color;
    setData("Kb+_marked", marked);
    sendMessage(`Marked ${playerName} with color ${Colors[color].convert()}${color}`, Colors.GREEN);
  } else {
    sendMessage("Invalid color: " + color + ". Available colors: " + Object.keys(Colors).map(color => Colors[color].convert() + color).join(", "), Colors.RED);
  }
}

function delMark(name) {
  if (marked[name]) {
    delete marked[name];
    setData("Kb+_marked", marked);
    sendMessage(`Removed mark from ${name}`, Colors.GREEN);
  } else {
    sendMessage(`${name} is not marked`, 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;
    } else if (marked[name]) {
      return Colors[marked[name]].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;
    } else if (marked[name]) {
      return Colors[marked[name]].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, commandPrefix = settings.commandPrefix) => {
      if (!currentValue.startsWith(commandPrefix + baseCommand + " ")) return;
      const settingVar = currentValue.slice(commandPrefix.length + baseCommand.length + 1).toLowerCase();
      const matchs = list.filter(el => el.toLowerCase().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 {
      autocomplete("help", Object.keys(commands));
      autocomplete("addfriend", tabList);
      autocomplete("delfriend", friends);
      autocomplete("addenemy", tabList);
      autocomplete("delenemy", enemies);
      autocomplete("toggle", commands.toggle.settings);
      const joinParts = currentValue.slice(commandPrefix.length + "join ".length).split(" ");
      if (joinParts.length === 1) autocomplete("join", Modes);
      autocomplete("delmark", Object.keys(marked));
      const markParts = currentValue.slice(commandPrefix.length + "addmark ".length).split(" ");
      if (markParts.length === 1) {
        autocomplete("addmark", Object.keys(Colors));
      } else if (markParts.length === 2) {
        autocomplete("addmark " + markParts[0], tabList);
      }
    }
  } 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 findStringInObject(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 = findStringInObject(item);
          if (result) return result;
        }
      }
    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
      const result = findStringInObject(obj[key]);
      if (result) return result;
    }
  }
  return null;
}


function handleTabValues(object) {
  const playerName = findStringInObject(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;
            } else if (marked[player]) {
              span.style.color = Colors[marked[player]].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.");
  }
});