Flatmmo Keybinds Improved

remappable keybindings for flatmmo

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);