Flatmmo Keybinds Improved

remappable keybindings for flatmmo

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Flatmmo Keybinds Improved
// @namespace    Joshu FlatMMO Scripts
// @description  remappable keybindings for flatmmo
// @license      MIT 
// @match        https://flatmmo.com/play.php*
// @version      1773961479750
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM.setValue
// @grant        GM.registerMenuCommand
// ==/UserScript==


// packages/userscripts/keybinds-improved/ACTIONS.ts
var ACTIONS = {
  run: {
    originalKey: "F1",
    description: "Run",
    socketCommand: "SHORTCUT_KEY=F1"
  },
  eat: {
    originalKey: "F2",
    description: "Consumes a piece of food",
    socketCommand: "SHORTCUT_KEY=F2"
  },
  lightFire: {
    originalKey: "F3",
    description: "Lights a fire",
    socketCommand: "SHORTCUT_KEY=F3"
  },
  equip1: {
    originalKey: "F6",
    description: "Equipment Auto equips items that you've configured",
    socketCommand: "SHORTCUT_KEY=F6"
  },
  equip2: {
    originalKey: "F7",
    description: "Equipment - Auto equips items that you've configured",
    socketCommand: "SHORTCUT_KEY=F7"
  },
  equip3: {
    originalKey: "F8",
    description: "Equipment - Auto equips items that you've configured",
    socketCommand: "SHORTCUT_KEY=F8"
  },
  badge1: {
    originalKey: "F9",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F9"
  },
  badge2: {
    originalKey: "F10",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F10"
  },
  badge3: {
    originalKey: "F11",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F11"
  },
  badge4: {
    originalKey: "F12",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F12"
  },
  teleport_everbrook: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_everbrook"
  },
  remote_sell: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=remote_sell"
  },
  dig: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=dig"
  },
  teleport_mysticvale: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_mysticvale"
  },
  timers: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=timers"
  },
  teleport_omboko: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_omboko"
  },
  teleport_dock_haven: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_dock_haven"
  },
  auto_hell_burying: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=auto_hell_burying"
  },
  teleport_jafa_outpost: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_jafa_outpost"
  },
  teleport_frostvale: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_frostvale"
  },
  hunting_contact: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=hunting_contact"
  },
  mass_pickup: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=mass_pickup"
  },
  focus: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=focus"
  },
  clarity: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=clarity"
  }
};

// packages/userscripts/keybinds-improved/DEFAULT_HOTKEYS.ts
var DEFAULT_HOTKEYS = {
  run: {
    key: "r",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  eat: {
    key: "f",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  lightFire: {
    key: "4",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  equip1: {
    key: "1",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  equip2: {
    key: "2",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  equip3: {
    key: "3",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge1: {
    key: "a",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge2: {
    key: "s",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge3: {
    key: "d",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge4: {
    key: "v",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  teleport_everbrook: {
    key: "e",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  remote_sell: {
    key: "s",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  dig: {
    key: "l",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  teleport_mysticvale: {
    key: "m",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  timers: {
    key: "0",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  teleport_omboko: {
    key: "o",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  teleport_dock_haven: {
    key: "d",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  auto_hell_burying: {
    key: "b",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  teleport_jafa_outpost: {
    key: "j",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  teleport_frostvale: {
    key: "f",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  hunting_contact: {
    key: "h",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  mass_pickup: {
    key: "p",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  focus: {
    key: "k",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  clarity: {
    key: "c",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  }
};

// packages/userscripts/keybinds-improved/hotkeys.ts
var keypressToHashableString = (keypress) => {
  return `${keypress.key}-${keypress.altKey}-${keypress.ctrlKey}-${keypress.metaKey}-${keypress.shiftKey}`.toLowerCase();
};
if (GM_getValue("hotkeys", null) === null) {
  GM_setValue("hotkeys", {});
}
var usersHotkeys = GM_getValue("hotkeys", {});
var mergeHotkeys = () => {
  return { ...DEFAULT_HOTKEYS, ...usersHotkeys };
};
var hashKeymap = () => {
  return Object.entries(mergeHotkeys()).reduce((result, [action, kp]) => {
    const hashed = keypressToHashableString(kp);
    result[hashed] = { action, hotkey: kp };
    return result;
  }, {});
};
var hashedHotkeyMap = hashKeymap();
var setHotkeys = (updatedHotkeys) => {
  usersHotkeys = { ...usersHotkeys, ...updatedHotkeys };
  GM.setValue("hotkeys", usersHotkeys);
  hashedHotkeyMap = hashKeymap();
};

// packages/userscripts/keybinds-improved/settings.ts
var formatKeypress = (kp) => {
  const parts = [];
  if (kp.ctrlKey)
    parts.push("Ctrl");
  if (kp.altKey)
    parts.push("Alt");
  if (kp.shiftKey)
    parts.push("Shift");
  if (kp.metaKey)
    parts.push("Meta");
  parts.push(kp.key.toUpperCase());
  return parts.join(" + ");
};
var formatKeypressFromEvent = (e) => {
  const parts = [];
  if (e.ctrlKey)
    parts.push("Ctrl");
  if (e.altKey)
    parts.push("Alt");
  if (e.shiftKey)
    parts.push("Shift");
  if (e.metaKey)
    parts.push("Meta");
  parts.push(e.key.toUpperCase());
  return parts.join(" + ");
};
var formatActionName = (action) => {
  return action.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
};
var CATEGORIES = {
  "Basic Actions": ["run", "eat", "lightFire"],
  Equipment: ["equip1", "equip2", "equip3"],
  Badges: ["badge1", "badge2", "badge3", "badge4"],
  Teleports: [
    "teleport_everbrook",
    "teleport_mysticvale",
    "teleport_omboko",
    "teleport_dock_haven",
    "teleport_jafa_outpost",
    "teleport_frostvale"
  ],
  "Worship Skills": [
    "remote_sell",
    "dig",
    "timers",
    "auto_hell_burying",
    "hunting_contact",
    "mass_pickup",
    "focus",
    "clarity"
  ]
};
var createModalStyles = () => {
  const style = document.createElement("style");
  style.textContent = `
		#hotkeys-modal-overlay {
			position: fixed;
			top: 0;
			left: 0;
			width: 100vw;
			height: 100vh;
			background: rgba(0, 0, 0, 0.7);
			display: none;
			justify-content: center;
			z-index: 10000;
			padding-top: 5vh;
		}

		#hotkeys-modal-overlay.visible {
			display: flex;
		}

		#hotkeys-modal {
			background: #1a1a2e;
			border: 2px solid #4a4a6a;
			border-radius: 8px;
			max-width: 1000px;
			max-height: 80vh;
			width: 90%;
			overflow: hidden;
			box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
			height: fit-content;
			padding-bottom: 20px;
		}

		#hotkeys-modal-header {
			display: flex;
			justify-content: space-between;
			align-items: center;
			padding: 12px 16px;
			background: #252540;
			border-bottom: 1px solid #4a4a6a;
		}

		#hotkeys-modal-header h2 {
			margin: 0;
			color: #e0e0e0;
			font-size: 18px;
		}

		#hotkeys-modal-close {
			background: none;
			border: none;
			color: #888;
			font-size: 24px;
			cursor: pointer;
			padding: 0;
			line-height: 1;
		}

		#hotkeys-modal-close:hover {
			color: #fff;
		}

		#hotkeys-modal-content {
			overflow-y: auto;
			max-height: calc(80vh - 60px);
			padding: 16px;
		}

		.hotkey-category {
			font-size: 16px;
			font-weight: 600;
			color: #e0e0e0;
			padding: 12px 0 8px 0;
			border-bottom: 2px solid #4a4a6a;
			margin-bottom: 8px;
		}

		#hotkeys-grid {
			display: flex;
			flex-direction: column;
			gap: 16px;
			color: #e0e0e0;
		}

		.category-section {
			display: contents;
		}

		.category-section.full-width {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.small-categories-row {
			display: grid;
			grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
			gap: 16px;
		}

		.small-category-container {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.full-width .items-grid {
			display: grid;
			grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
			gap: 8px;
		}

		.small-category-container .items-grid {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.hotkey-item {
			display: grid;
			grid-template-columns: 1fr auto;
			align-items: center;
			gap: 12px;
			padding: 8px 12px;
			border: 1px solid #333;
			border-radius: 4px;
			transition: background 0.15s ease;
		}

		.hotkey-item:hover {
			background: #252540;
		}

		.hotkey-key {
			display: inline-block;
			background: #333;
			border: 1px solid #555;
			border-radius: 4px;
			padding: 4px 12px;
			font-family: monospace;
			font-size: 14px;
			cursor: pointer;
			transition: all 0.15s ease;
		}

		.hotkey-key:hover {
			background: #444;
			border-color: #777;
		}

		.hotkey-key.recording {
			background: #4a3a2e;
			border-color: #f0a050;
		}

		.hotkey-key.conflict {
			background: #4a2a2a;
			border-color: #c55;
		}
	`;
  return style;
};
var currentlyRecording = null;
var recordingAction = null;
var stopRecording = (restoreText = false) => {
  if (currentlyRecording) {
    if (restoreText && recordingAction) {
      const currentHotkeys = mergeHotkeys();
      currentlyRecording.textContent = formatKeypress(currentHotkeys[recordingAction]);
    }
    currentlyRecording.classList.remove("recording");
    currentlyRecording = null;
    recordingAction = null;
  }
};
var allSpans = [];
var updateConflicts = () => {
  const currentHotkeys = mergeHotkeys();
  const hashCounts = Object.values(currentHotkeys).map(keypressToHashableString).reduce((acc, hash) => {
    acc[hash] = (acc[hash] || 0) + 1;
    return acc;
  }, {});
  for (const span of allSpans) {
    const action = span.dataset.action;
    const kp = currentHotkeys[action];
    const hash = keypressToHashableString(kp);
    const count = hashCounts[hash];
    if (count > 1) {
      span.classList.add("conflict");
    } else {
      span.classList.remove("conflict");
    }
  }
};
var startRecording = (element, action) => {
  if (currentlyRecording === element) {
    stopRecording(true);
    return;
  }
  if (currentlyRecording) {
    stopRecording(true);
  }
  currentlyRecording = element;
  recordingAction = action;
  element.classList.add("recording");
  element.textContent = "Press a key...";
};
var getModifierText = (e) => {
  const parts = [];
  if (e.ctrlKey)
    parts.push("Ctrl");
  if (e.altKey)
    parts.push("Alt");
  if (e.shiftKey)
    parts.push("Shift");
  if (e.metaKey)
    parts.push("Meta");
  return parts.length > 0 ? `${parts.join(" + ")} + ...` : "Press a key...";
};
var updateRecordingDisplay = (e) => {
  if (!currentlyRecording)
    return;
  currentlyRecording.textContent = getModifierText(e);
};
var handleRecordingKeypress = (e) => {
  if (!currentlyRecording || !recordingAction)
    return;
  if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) {
    updateRecordingDisplay(e);
    return;
  }
  e.preventDefault();
  e.stopPropagation();
  const newKeypress = {
    key: e.key,
    altKey: e.altKey,
    ctrlKey: e.ctrlKey,
    metaKey: e.metaKey,
    shiftKey: e.shiftKey
  };
  setHotkeys({ [recordingAction]: newKeypress });
  currentlyRecording.textContent = formatKeypressFromEvent(e);
  currentlyRecording.classList.remove("recording");
  updateConflicts();
  stopRecording();
};
var handleRecordingKeyup = (e) => {
  if (!currentlyRecording)
    return;
  if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) {
    updateRecordingDisplay(e);
  }
};
var createModal = () => {
  const overlay = document.createElement("div");
  overlay.id = "hotkeys-modal-overlay";
  const modal = document.createElement("div");
  modal.id = "hotkeys-modal";
  const header = document.createElement("div");
  header.id = "hotkeys-modal-header";
  const title = document.createElement("h2");
  title.textContent = "Hotkey Bindings";
  const closeBtn = document.createElement("button");
  closeBtn.id = "hotkeys-modal-close";
  closeBtn.textContent = "×";
  closeBtn.addEventListener("click", () => hideModal());
  header.appendChild(title);
  header.appendChild(closeBtn);
  const content = document.createElement("div");
  content.id = "hotkeys-modal-content";
  const grid = document.createElement("div");
  grid.id = "hotkeys-grid";
  const hotkeys = mergeHotkeys();
  const smallCategories = [];
  const largeCategories = [];
  for (const [categoryName, categoryActions] of Object.entries(CATEGORIES)) {
    if (categoryActions.length <= 4) {
      smallCategories.push([categoryName, categoryActions]);
    } else {
      largeCategories.push([categoryName, categoryActions]);
    }
  }
  const createCategoryItems = (categoryActions) => {
    const items = [];
    for (const action of categoryActions) {
      const kp = hotkeys[action];
      const actionInfo = ACTIONS[action];
      if (!actionInfo || !kp)
        continue;
      const item = document.createElement("div");
      item.className = "hotkey-item";
      const actionName = document.createElement("span");
      actionName.className = "hotkey-action";
      actionName.textContent = formatActionName(action);
      const hotkeySpan = document.createElement("span");
      hotkeySpan.className = "hotkey-key";
      hotkeySpan.textContent = formatKeypress(kp);
      hotkeySpan.dataset.action = action;
      allSpans.push(hotkeySpan);
      hotkeySpan.addEventListener("click", () => {
        startRecording(hotkeySpan, action);
      });
      item.appendChild(actionName);
      item.appendChild(hotkeySpan);
      items.push(item);
    }
    return items;
  };
  if (smallCategories.length > 0) {
    const smallCategoriesRow = document.createElement("div");
    smallCategoriesRow.className = "small-categories-row";
    for (const [categoryName, categoryActions] of smallCategories) {
      const container = document.createElement("div");
      container.className = "small-category-container";
      const categoryHeader = document.createElement("div");
      categoryHeader.className = "hotkey-category";
      categoryHeader.textContent = categoryName;
      container.appendChild(categoryHeader);
      const itemsGrid = document.createElement("div");
      itemsGrid.className = "items-grid";
      const items = createCategoryItems(categoryActions);
      items.forEach((item) => itemsGrid.appendChild(item));
      container.appendChild(itemsGrid);
      smallCategoriesRow.appendChild(container);
    }
    grid.appendChild(smallCategoriesRow);
  }
  for (const [categoryName, categoryActions] of largeCategories) {
    const section = document.createElement("div");
    section.className = "category-section full-width";
    const categoryHeader = document.createElement("div");
    categoryHeader.className = "hotkey-category";
    categoryHeader.textContent = categoryName;
    section.appendChild(categoryHeader);
    const itemsGrid = document.createElement("div");
    itemsGrid.className = "items-grid";
    const items = createCategoryItems(categoryActions);
    items.forEach((item) => itemsGrid.appendChild(item));
    section.appendChild(itemsGrid);
    grid.appendChild(section);
  }
  updateConflicts();
  content.appendChild(grid);
  modal.appendChild(header);
  modal.appendChild(content);
  overlay.appendChild(modal);
  overlay.addEventListener("click", (e) => {
    if (e.target === overlay) {
      hideModal();
    }
  });
  modal.addEventListener("click", (e) => {
    const target = e.target;
    if (!target.classList.contains("hotkey-key") && currentlyRecording) {
      stopRecording(true);
    }
  });
  return overlay;
};
var modalElement = null;
var initModal = () => {
  if (modalElement)
    return;
  document.head.appendChild(createModalStyles());
  modalElement = createModal();
  document.body.appendChild(modalElement);
};
var showModal = () => {
  initModal();
  modalElement?.classList.add("visible");
};
var hideModal = () => {
  modalElement?.classList.remove("visible");
};
var toggleModal = () => {
  initModal();
  if (modalElement?.classList.contains("visible")) {
    hideModal();
  } else {
    showModal();
  }
};
document.addEventListener("keydown", (e) => {
  if (!modalElement?.classList.contains("visible"))
    return;
  if (currentlyRecording) {
    if (e.key === "Escape") {
      stopRecording(true);
      e.preventDefault();
      return;
    }
    handleRecordingKeypress(e);
    return;
  }
  if (e.key === "Escape") {
    hideModal();
    e.preventDefault();
  }
});
document.addEventListener("keyup", (e) => {
  if (!modalElement?.classList.contains("visible"))
    return;
  handleRecordingKeyup(e);
});

// packages/userscripts/keybinds-improved/index.ts
var focusOrSendChat = () => {
  const value = chat_ele.value.trim();
  if (document.activeElement !== chat_ele) {
    request_focus_chatbox();
    return;
  }
  if (value !== "") {
    Globals.websocket.send(`CHAT=${value}`);
    chat_ele.value = "";
  }
  request_unfocus_chatbox();
};
var handleNpcChatModal = (e) => {
  const keyCode = e.keyCode;
  if (has_npc_chat_message_modal_open()) {
    if (keyCode === 32) {
      document.getElementById("npc-chat-message-modal-continue-btn")?.click();
      e.preventDefault();
    }
    return;
  }
  if (has_npc_chat_options_modal_open()) {
    switch (keyCode) {
      case 49:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[0].style.display !== "none") {
            options[0].click();
          }
        }
        break;
      case 50:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[1].style.display !== "none") {
            options[1].click();
          }
        }
        break;
      case 51:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[2].style.display !== "none") {
            options[2].click();
          }
        }
        break;
      case 52:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[3].style.display !== "none") {
            options[3].click();
          }
        }
        break;
    }
  }
};
var hotkeyListener = (e) => {
  if (e.repeat)
    return;
  if (Globals.local_username == null)
    return;
  if (has_npc_chat_message_modal_open()) {
    handleNpcChatModal(e);
    return;
  }
  if (has_modal_open())
    return;
  if (e.key === "Enter") {
    focusOrSendChat();
    e.preventDefault();
  }
  if (document.activeElement?.id !== "body") {
    return;
  }
  if (e.key === "/") {
    chat_ele.value = "/";
    request_focus_chatbox();
    e.preventDefault();
  }
  const keypressString = keypressToHashableString(e);
  if (keypressString in hashedHotkeyMap) {
    const pressedHotkey = hashedHotkeyMap[keypressString];
    const action = ACTIONS[pressedHotkey.action];
    if (action) {
      Globals.websocket.send(action.socketCommand);
      e.preventDefault();
    }
  }
};
GM.registerMenuCommand("Toggle Settings menu", toggleModal);
window.removeEventListener("keypress", keypress_listener);
window.addEventListener("keydown", hotkeyListener, false);