POE Trade Improvements

Improvements to the trade site. Adds a button for "add resistances" and "add attributes" to create stat weight groups

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         POE Trade Improvements
// @namespace    Kylixen
// @version      2025-01-27
// @description  Improvements to the trade site. Adds a button for "add resistances" and "add attributes" to create stat weight groups
// @author       Kylixen
// @match        https://www.pathofexile.com/trade2/search/poe2/Standard
// @match        https://www.pathofexile.com/trade2/search/poe2/Standard/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pathofexile.com
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

// debugger;

var typingSpeed = 100; // milliseconds per character
async function simulateTyping(element, text, delay = 100) {
  let index = 0;

  function fireEvent(eventType, key) {
    const event = new KeyboardEvent(eventType, {
      key: key,
      code: key.charCodeAt(0),
      bubbles: true,
      cancelable: true
    });
    element.dispatchEvent(event);
  }

  function fireInputEvent() {
    const inputEvent = new InputEvent("input", {
      bubbles: true,
      cancelable: true
    });
    element.dispatchEvent(inputEvent);
  }

  function fireChangeEvent() {
    const inputEvent = new InputEvent("change", {
      bubbles: true,
      cancelable: true
    });
    element.dispatchEvent(inputEvent);
  }

  async function typeNextChar() {
    if (index < text.length) {
      const char = text[index];
      fireEvent("keydown", char);
      fireEvent("keypress", char);

      if (element.isContentEditable) {
        element.textContent += char;
      } else {
        element.value += char;
      }

      fireInputEvent();
      fireEvent("keyup", char);

      index++;
      await setTimeout(null, delay)
      await typeNextChar();
    } else {
      fireChangeEvent();
    }
  }

  await typeNextChar();
}

function errorOnUndef(f, errorMessage) {
  return function (...args) { 
    var res = f(...args)
    if(res === undefined)
      throw errorMessage
    return res;
  }
}

function errorHandled(f) {
  return function(...args) {
    try {
      return f(...args);
    } catch(error) {
      console.error(error);
    }
  }
}

function asyncErrorOnUndef(f, errorMessage) {
  return async function (...args) { 
    var res = await f(...args)
    if(res === undefined)
      throw errorMessage
    return res;
  }
}

function asyncErrorHandled(f) {
  return async function(...args) {
    try {
      return await f(...args);
    } catch(error) {
      console.error(error);
    }
  }
}

function first(iterable) {
  if(!iterable || iterable.length == 0)
    return undefined;
  return iterable[0];
}

function last(iterable) {
  if(!iterable || iterable.length == 0)
    return undefined; 
  return iterable[iterable.length -1 ];
}

const getAddStatGroupDiv = errorOnUndef(function() {
  return document.querySelector(".filter-group-select")
}, "Couldn't find the +Add Stat Group <div>");

async function addNewStatGroup(statGroupType) {

  var divAddStatGroup = getAddStatGroupDiv();

  var options = Array.from(divAddStatGroup.querySelectorAll("li>span") || []);

  if(!options)
    throw "Couldn't find options in Add Stat Group div";

  var groupMatch = new RegExp(`^\s*${statGroupType}\s*$`, "i");

  var option = first(options.filter(function(opt) {
    if(groupMatch.test(opt.textContent))
      return true;
    return false;
  }))

  if(!option)
    throw "Couldn't find the stat group type " + statGroupType;

  option.click();

  await setTimeout(null, 3);

  return last(document.querySelectorAll("div.filter-group"));
};

const addWeightedResistances = asyncErrorHandled(async function() {

  // Add the new panel
  const statGroupDiv = await addNewStatGroup("weighted sum v2");

  // type "resistance" into the input so we get all the elements we need)
  const statFilterDiv = last(document.querySelectorAll("div.filter-select-mutate"));
  const statInput = statFilterDiv.querySelector("input");

  statInput.focus();
  await simulateTyping(statInput, "resistance", 0);

  // click all the resistances
  statFilterDiv.querySelectorAll("span.multiselect__option").forEach((optionSpan) => {
    if(!((optionSpan.textContent || "").match(/^(explicit|implicit) #% to (Cold|Fire|Lightning|Chaos|All Elemental) resistances?$/i)))
      return;
    console.debug(optionSpan.textContent);
    optionSpan.click();
  });

  await setTimeout(null, 6);

  // Set the weight for all/chaos
  statGroupDiv.querySelectorAll("span.filter-body:has(.weight)").forEach(async (optionSpan) => {
    if((optionSpan.innerText || "").match(/All Elemental/i)) {
      const weightInput = optionSpan.querySelector("input.weight");
      weightInput.focus();
      await simulateTyping(weightInput, "3", 0);
      await setTimeout(null, 6);
      weightInput.blur();
    }
  });
});

const addWeightedAttributes = asyncErrorHandled(async function() {

  // Add the new panel
  const statGroupDiv = await addNewStatGroup("weighted sum v2");

  // type "resistance" into the input so we get all the elements we need)
  const statFilterDiv = last(document.querySelectorAll("div.filter-select-mutate"));
  const statInput = statFilterDiv.querySelector("input");

  // statInput.focus();
  // await simulateTyping(statInput, "resistance", 0);

  // click all the resistances
  statFilterDiv.querySelectorAll("span.multiselect__option").forEach((optionSpan) => {
    if(!((optionSpan.textContent || "").match(/^(explicit|implicit) #% to (Cold|Fire|Lightning|Chaos|All Elemental) resistances?$/i)))
      return;
    console.debug(optionSpan.textContent);
    optionSpan.click();
  });

  await setTimeout(null, 6);

  // Set the weight for all/chaos
  statGroupDiv.querySelectorAll("span.filter-body:has(.weight)").forEach(async (optionSpan) => {
    if((optionSpan.innerText || "").match(/All Elemental/i)) {
      const weightInput = optionSpan.querySelector("input.weight");
      weightInput.focus();
      await simulateTyping(weightInput, "3", 0);
      await setTimeout(null, 6);
      weightInput.blur();
    }
  });

});
;
function addResistancesClick() {
  addWeightedResistances();
  // Clean up button
}

function addAttributesClick() {
  addWeightedAttributes();
  // Clean up button
}

function waitFor(waitForFunction, successCallback) {
  (new MutationObserver(function (changes, observer) {
    if (!waitForFunction())
      return;
    observer.disconnect();
    successCallback();
  })).observe(document, { childList: true, subtree: true });
}

(function () {
  'use strict';
  (new MutationObserver(function (changes, observer) {
    if (!document.querySelector('.filter-group-select'))
      return;
    observer.disconnect();
    onLoad();
  })).observe(document, { childList: true, subtree: true });

  function onLoad() {
    const searchDiv = document.querySelector("div.search-advanced-pane");
    const newElement = document.createElement('div');
    newElement.className = "filter-group expanded";
    newElement.innerHTML = `
    <div class="filter-group-body">
      <button id='add-resistance-button' type="button" class='btn search-btn' onclick='addResistancesClick()'>
          <span>Add Resistances</span>
      </button>
      <button id='add-attributes-button' type="button" class='btn search-btn' onclick='addAttributesClick()'>
          <span>Add Attributes</span>
      </button>
    </div>
    `;
    searchDiv.prepend(newElement);
  }
})();

window.addResistancesClick = addResistancesClick;
window.addAttributesClick = addAttributesClick;

window.ky = {
  addResistancesClick,
  addWeightedResistances,
  addWeightedAttributes,
  addNewStatGroup,
  simulateTyping,
  last,
  first,
}