Last active
January 8, 2026 14:00
-
-
Save Krytos/fcb435878b9ee214c8ef9c5d9a685861 to your computer and use it in GitHub Desktop.
Adds a button to the PoE2 Trade page that allows you to copy the item description and affixes to your clipboard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name PoE2 Trade Copy Item Button | |
| // @version 2.0 | |
| // @description Adds a button to the PoE2 Trade page that allows you to copy the item description and affixes to your clipboard. | |
| // @author Kevin M | |
| // @match https://www.pathofexile.com/trade2/* | |
| // @icon https://www.google.com/s2/favicons?domain=pathofexile.com | |
| // @downloadURL https://gist.github.com/Krytos/fcb435878b9ee214c8ef9c5d9a685861/raw/poe2trade.user.js | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const processedRows = new Set(); | |
| function processRow(row) { | |
| if (processedRows.has(row)) return; | |
| try { | |
| const leftDiv = row.querySelector('div.left'); | |
| if (!leftDiv || !leftDiv.children || leftDiv.children.length < 2) { | |
| return; | |
| } | |
| var copy_button = leftDiv.children[1]; | |
| copy_button.className = "copy"; | |
| copy_button.removeAttribute("style"); | |
| copy_button.addEventListener('click', function () { | |
| const itemHeader = row.querySelector('div.itemHeader.doubleLine') ?? row.querySelector('div.itemHeader'); | |
| const content = row.querySelector('div.content'); | |
| if (!itemHeader || !content) { | |
| console.log('Item header or content not found'); | |
| return; | |
| } | |
| let outputText = ''; | |
| const typeLineElem = itemHeader.querySelector('.itemName.typeLine .lc'); | |
| const itemNameElem = itemHeader.querySelector('.itemName:not(.typeLine) .lc'); | |
| const normal = row.querySelector('.normalPopup'); | |
| const magic = row.querySelector('.magicPopup'); | |
| const rare = row.querySelector('.rarePopup'); | |
| const unique = row.querySelector('.uniquePopup'); | |
| if (normal) { | |
| console.log('normal'); | |
| outputText += `Rarity: Normal\n`; | |
| } | |
| else if (magic) { | |
| console.log('magic'); | |
| outputText += `Rarity: Magic\n`; | |
| } | |
| else if (rare) { | |
| console.log('rare'); | |
| outputText += `Rarity: Rare\n`; | |
| } | |
| else if (unique) { | |
| console.log('unique'); | |
| outputText += `Rarity: unique\n`; | |
| } | |
| if (itemNameElem) { | |
| outputText += `${itemNameElem.innerText}\n`; | |
| } | |
| // Add type line | |
| if (typeLineElem) { | |
| outputText += `${typeLineElem.innerText}\n`; | |
| } | |
| //Quality | |
| const quality = content.querySelector('span[data-field="quality"] .colourAugmented'); | |
| if (quality) { | |
| outputText += '--------\n'; | |
| outputText += `Quality: ` + quality.innerText + ' (augmented)\n'; | |
| } | |
| // Requirements | |
| const requirements = content.querySelector('.requirements'); | |
| if (requirements) { | |
| outputText += '--------\n'; | |
| outputText += 'Requirements:\n'; | |
| let level = requirements.querySelector('span[data-field="lvl"] .colourDefault').innerText; | |
| let str = requirements.querySelector('span[data-field="str"] .colourDefault'); | |
| let int = requirements.querySelector('span[data-field="int"] .colourDefault'); | |
| let dex = requirements.querySelector('span[data-field="dex"] .colourDefault'); | |
| outputText += `Level: ${level}\n`; | |
| if (str) { | |
| str = 'Str: ' + str.innerText; | |
| outputText += str + '\n'; | |
| } | |
| if (int) { | |
| int = 'Int: ' + int.innerText; | |
| outputText += int + '\n'; | |
| } | |
| if (dex) { | |
| dex = 'Dex: ' + dex.innerText; | |
| outputText += dex + '\n'; | |
| } | |
| outputText += '--------\n'; | |
| } | |
| // Sockets | |
| const socketsDiv = leftDiv.querySelector('.sockets'); | |
| if (socketsDiv && socketsDiv.children.length > 0) { | |
| outputText += `Sockets: ${"S ".repeat(socketsDiv.childElementCount)}\n`; | |
| outputText += '--------\n'; | |
| } | |
| // Item Level | |
| const itemLevel = content.querySelector('.itemLevel'); | |
| if (itemLevel) { | |
| outputText += itemLevel.innerText.trim() + '\n'; | |
| outputText += '--------\n'; | |
| } | |
| // Enchantments | |
| const enchantments = content.querySelectorAll('.enchantMod'); | |
| if (enchantments.length > 0) { | |
| let enchantSection = []; | |
| enchantments.forEach(enchant => { | |
| let text = enchant.innerText.trim(); | |
| enchantSection.push(text + ' (enchant)'); | |
| }); | |
| outputText += enchantSection.join('\n') + '\n'; | |
| outputText += '--------\n'; | |
| } | |
| // Implicit mods | |
| const runeMods = content.querySelectorAll('.runeMod'); | |
| if (runeMods.length > 0) { | |
| let implicitSection = []; | |
| runeMods.forEach(rune => { | |
| let text = rune.innerText.trim(); | |
| implicitSection.push(text + ' (rune)'); | |
| }); | |
| outputText += implicitSection.join('\n') + '\n'; | |
| outputText += '--------\n'; | |
| } | |
| // Implicit mods | |
| const implicitMods = content.querySelectorAll('.implicitMod'); | |
| if (implicitMods.length > 0) { | |
| let implicitSection = []; | |
| // outputText += `Implicits: ${implicitMods.length}\n`; | |
| implicitMods.forEach(implicit => { | |
| let text = implicit.innerText.trim(); | |
| implicitSection.push(text + ' (implicit)'); | |
| }); | |
| outputText += implicitSection.join('\n') + '\n'; | |
| outputText += '--------\n'; | |
| } | |
| // Explicit mods | |
| const explicitMods = content.querySelectorAll('.explicitMod'); | |
| if (explicitMods.length > 0) { | |
| let modSection = []; | |
| explicitMods.forEach(mod => { | |
| let text = mod.querySelector('.lc.s').innerText.trim(); | |
| modSection.push(text); | |
| }); | |
| outputText += modSection.join('\n') + '\n'; | |
| } | |
| // Corrupted state | |
| const corrupted = content.querySelector('.unmet'); | |
| if (corrupted) { | |
| outputText += '--------\n'; | |
| outputText += corrupted.innerText + '\n'; | |
| } | |
| // Desecrated mods | |
| const desecratedMods = content.querySelectorAll('.desecratedMod'); | |
| if (desecratedMods.length > 0) { | |
| let modSection = []; | |
| desecratedMods.forEach(mod => { | |
| let text = mod.querySelector('.lc.s').innerText.trim(); | |
| modSection.push(text); | |
| }); | |
| outputText += modSection.join('\n') + '\n'; | |
| } | |
| const priceNote = content.querySelector('.textCurrency'); | |
| if (priceNote && priceNote.innerText.includes('~price')) { | |
| outputText += '--------\n'; | |
| outputText += `Note: ${priceNote.innerText.replace('~price', '').trim()}`; | |
| outputText += '\n'; | |
| } | |
| navigator.clipboard.writeText(outputText); | |
| }); | |
| processedRows.add(row); | |
| } catch (e) { | |
| console.error('Error processing row:', e); | |
| } | |
| } | |
| const observer = new MutationObserver((mutations) => { | |
| for (const mutation of mutations) { | |
| for (const node of mutation.addedNodes) { | |
| if (node.nodeType === 1 && node.classList.contains('row')) { | |
| if (!node.querySelector('div.itemHeader')) continue; | |
| processRow(node); | |
| } | |
| } | |
| } | |
| }); | |
| document.querySelectorAll('div.row').forEach(row => { | |
| if (row.querySelector('div.itemHeader')) { | |
| processRow(row); | |
| } | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| })(); |
@Fahrengeit nice addition but the code breaks on multi line fractured mods!
@Krytos would you be so kindly to add the code below on line 162? Moving it above the Explicit mods keeps the typical Fractured -> Explicit -> Desecrated format.
// Fractured mods const fracturedMods = content.querySelectorAll('.fracturedMod'); if (fracturedMods.length > 0) { let modSection = []; fracturedMods.forEach(mod => { let text = mod.querySelector('.lc.s').innerText.trim(); modSection.push(text + ' (fractured)'); }); outputText += modSection.join('\n') + '\n'; }Could you also change in
// Desecrated modsthe linemodSection.push(text);to bemodSection.push(text + ' (desecrated)');so it retains the desecrated modifier even in PoB? It will show correctly in PoB.
Thank you for this update! I added it to mine manually and it works great
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

@Fahrengeit nice addition but the code breaks on multi line fractured mods!
@Krytos would you be so kindly to add the code below on line 162? Moving it above the Explicit mods keeps the typical Fractured -> Explicit -> Desecrated format.
Could you also change in
// Desecrated modsthe linemodSection.push(text);to bemodSection.push(text + ' (desecrated)');so it retains the desecrated modifier even in PoB? It will show correctly in PoB.Example how it will look like:
