Multi Transfer Artifacts

Мультипередача артефактов

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name           Multi Transfer Artifacts
// @author         Neleus
// @namespace      Neleus
// @description    Мультипередача артефактов
// @version        1.1
// @match          https://www.heroeswm.ru/inventory.php*
// @match          https://mirror.heroeswm.ru/inventory.php*
// @match          https://lordswm.com/inventory.php*
// @match          https://my.lordswm.com/inventory.php*
// @grant          none
// @license        GNU GPLv3
// @run-at         document-end
// ==/UserScript==

;(function () {
  "use strict"

  // ==================== CONSTANTS ====================
  const DATA = document.documentElement.innerHTML

  // Find player ID
  let userID = null

  // Desktop: pl_hunter_stat link
  const idMatch = /pl_hunter_stat\.php\?id=(\d+)/.exec(DATA)
  if (idMatch) userID = idMatch[1]

  // Mobile fallback: cookie
  if (!userID) {
    const cookieMatch = document.cookie.match(/pl_id=(\d+)/)
    if (cookieMatch) userID = cookieMatch[1]
  }

  if (!userID) return

  const STORAGE_KEY = userID + "_translist"

  let IMG_LINK = "https://dcdn.heroeswm.ru/i/"
  if (/lordswm/.test(location.origin)) {
    IMG_LINK = "https://cfcdn.lordswm.com/i/"
  } else if (/mirror/.test(location.origin)) {
    IMG_LINK = "https://qcdn.heroeswm.ru/i/"
  }

  // ==================== STYLES ====================
  const styles = `
    .mtrans-container {
      width: 100%;
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 5px;
      box-sizing: border-box;
    }
    .mtrans-btn-anim {
      border-radius: 5px;
      animation: .5s linear infinite alternate mtrans-btn-anim;
    }
    @keyframes mtrans-btn-anim {
      from { outline: 4px dashed #aac7f500; }
      to { outline: 4px dashed #aac7f5ff; }
    }
    .mtrans-footer input, .mtrans-footer select { margin: 5px 0; }
    #btn_transfer { margin-top: 10px; padding: 4px; width: 100%; }
    .mtrans-header { width: 235px; text-align: center; }
    #art-name { height: 30px; font-weight: bold; word-wrap: break-word; font-size: 12px; }
    .mtrans-arts {
      width: 455px;
      height: 192px;
      overflow-y: scroll;
      padding: 2px;
      border: 1px dashed #aaa;
      box-sizing: border-box;
    }
    .mtrans-pool { font-size: 9pt; border: 1px dashed #aaa; }
    .mtrans-poolarts { padding: 0 4px; height: 156px; overflow-y: scroll; }
    .pool-element { display: grid; grid-template-columns: 1fr 45px 50px; }
    .pool-element > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .pool-header { text-align: center; padding: 2px; background: #dde; }
    .pool-selected { outline: 1px dotted #aaa; }
    .mtrans-item { display: inline-block; margin: 2px !important; }
    .mtrans-selected { outline: 1px solid #000; }
    .mtrans-dur { position: absolute; font-size: 90%; top: 1px; left: 1px; z-index: 2; background: #fff8; pointer-events: none; }
    .dur-warn { color: #fff; background: #f008; }
    .ppb-warn { border: 2px solid #f00; }
    .mtrans-chk { position: absolute; top: 1px; right: 1px; z-index: 2; }
    .mtrans-pbar { width: 100%; display: none; }
    .mtrans-badge { outline: 3px dashed #ACE; }
    .no-events { pointer-events: none; }
    #inv_menu_mtrans { display: none; }
    #inv_menu_mtrans img { filter: hue-rotate(90deg) saturate(2); }
    .thin-scrollbar::-webkit-scrollbar { width: 7px; }
    .thin-scrollbar::-webkit-scrollbar-thumb { background: #fff; border-radius: 5px; }
    .thin-scrollbar::-webkit-scrollbar-track { background: #5554; }

    @media (max-width: 600px) {
      .mtrans-container { grid-template-columns: 1fr; padding: 5px; }
      .mtrans-header { width: 100%; margin-bottom: 10px; }
      .mtrans-arts { width: 100%; height: 150px; }
      .mtrans-footer { width: 100%; }
      .mtrans-footer input[type="text"] { width: 50px !important; }
      #renter { width: 100% !important; max-width: 200px !important; }
      .mtrans-pool { width: 100%; }
      .pool-element { grid-template-columns: 1fr 40px 45px; font-size: 8pt; }
      .mtrans-poolarts { height: 120px; }
      #art-name { font-size: 11px; height: auto; min-height: 30px; }
    }
  `

  const styleElement = document.createElement("style")
  styleElement.textContent = styles
  document.head.appendChild(styleElement)

  // ==================== UTILITY FUNCTIONS ====================
  const $ = (selector, context = document) => context.querySelector(selector)

  const loadPage = async (url) => {
    const response = await fetch(url)
    const buffer = await response.arrayBuffer()
    return new TextDecoder("windows-1251").decode(buffer)
  }

  const getStorage = (key, defaultValue = null) => {
    try {
      const stored = localStorage.getItem(key)
      return stored ? JSON.parse(stored) : defaultValue
    } catch {
      return defaultValue
    }
  }

  const setStorage = (key, value) => {
    try {
      localStorage.setItem(key, JSON.stringify(value))
    } catch {}
  }

  const getTranslist = () => getStorage(STORAGE_KEY, {})
  const saveTranslist = (list) => setStorage(STORAGE_KEY, list)

  // ==================== ARTS EXTRACTION ====================
  let INV_ARTS_OBJ = {}

  const getArtsFromPage = (attempt = 1) => {
    return new Promise((resolve) => {
      const script = document.createElement("script")
      script.textContent = `
        if (typeof arts !== 'undefined') {
          const obj = {};
          arts.forEach((s, t) => obj[t] = Object.values(s));
          window.postMessage({ type: 'HWM_ARTS', data: obj }, '*');
        }
      `
      document.body.appendChild(script)
      script.remove()

      let resolved = false
      const handler = (e) => {
        if (resolved) return
        if (e.data?.type === "HWM_ARTS") {
          resolved = true
          window.removeEventListener("message", handler)
          resolve(e.data.data)
        }
      }
      window.addEventListener("message", handler)

      setTimeout(() => {
        if (resolved) return
        window.removeEventListener("message", handler)
        if (attempt < 3) {
          setTimeout(() => resolve(getArtsFromPage(attempt + 1)), 500)
        } else {
          resolve({})
        }
      }, 2000)
    })
  }

  // ==================== HELPER FUNCTIONS ====================
  const getFriendsList = async () => {
    const page = await loadPage("friends.php")
    const matches = [...page.matchAll(/([\wа-яё\-\(\) ]+) \[/gi)]
    return matches.map(m => `<option value="${m[1]}">${m[1]}</option>`).join("")
  }

  const getIndex = (artId) => Object.keys(INV_ARTS_OBJ).find(t => INV_ARTS_OBJ[t][0] == artId) || -1

  const parseSuffix = (mods) => {
    return [...mods.matchAll(/\w\d+/g)].map(m => `<img src="${IMG_LINK}mods_png/24/${m[0]}.png">`).join("")
  }

  const urlencode = (str) => {
    let result = ""
    for (let i = 0; i < str.length; i++) {
      let code = str.charCodeAt(i)
      if (code >= 1040 && code <= 1103) code -= 848
      if (code === 1025) code = 168
      if (code === 1105) code = 184
      result += /[A-Za-z0-9\-_.~]/.test(String.fromCharCode(code))
        ? String.fromCharCode(code)
        : "%" + code.toString(16).toUpperCase().padStart(2, "0")
    }
    return result
  }

  const setMTransferBadges = (translist, container) => {
    document.querySelectorAll(".mtrans-badge").forEach(el => el.classList.remove("mtrans-badge"))
    for (let artId in translist) {
      const idx = getIndex(artId)
      const el = $(`[art_idx="${idx}"]`, container)
      if (el) el.classList.add("mtrans-badge")
    }
  }

  // ==================== POOL FUNCTIONS ====================
  const poolToSession = (poolData) => sessionStorage.setItem("mtrans", JSON.stringify(poolData))

  const checkBattlesCount = (poolData) => {
    for (let artId in poolData.arts) {
      const el = $(`[data-id="${artId}"]`)
      if (!el) continue
      const durDiv = el.firstChild
      const warn = poolData.arts[artId].dur1 < poolData.battles && poolData.selected.includes(artId)
      durDiv.classList.toggle("dur-warn", warn)
    }
  }

  const checkPPB = (poolData, artId) => {
    const ppbInput = $("#ppb")
    if (!ppbInput || !poolData.arts[artId]) return
    const warn = poolData.battles > 0 && poolData.arts[artId].ppb === 0 && poolData.selected.includes(artId)
    ppbInput.classList.toggle("ppb-warn", warn)
  }

  const checkTransEnable = (poolData, button) => {
    const hasZeroPPB = poolData.battles > 0 && poolData.selected.some(id => poolData.arts[id]?.ppb === 0)
    button.disabled = poolData.selected.length === 0 || poolData.days === 0 || !poolData.renter || hasZeroPPB
  }

  const showPool = (poolData, selectedItem) => {
    let html = '<div class="pool-element pool-header"><div>Артефакт</div><div>Сумма</div><div>Комиссия</div></div><div class="mtrans-poolarts thin-scrollbar">'
    let num = 1, totalSum = 0, totalComm = 0

    for (let artId of poolData.selected) {
      const art = poolData.arts[artId]
      const sum = art.ppb * Math.min(poolData.battles, art.dur1)
      const comm = sum < 50 && sum > 0 ? 1 : Math.round(sum / 100)
      totalSum += sum
      totalComm += comm
      art.summ = sum
      const isSelected = selectedItem?.dataset.id === artId
      html += `<div class="pool-element${isSelected ? " pool-selected" : ""}"><div>${num++}. ${art.name} [${art.dur1}/${art.dur2}]</div><div>${sum}</div><div>${comm}</div></div>`
    }

    html += "</div>"
    $("#pool").innerHTML = html
    $("#summ").textContent = totalSum
    $("#comm").textContent = totalComm
  }

  const setSelected = (poolData, selectedItem) => {
    const ppbInput = $("#ppb"), artNameDiv = $("#art-name")
    const removeBtn = $("#btn_remove"), saveBtn = $("#btn_save")

    if (!selectedItem) {
      artNameDiv.textContent = "Перетащите артефакты на эту вкладку"
      ppbInput.value = 0
      ppbInput.disabled = removeBtn.disabled = saveBtn.disabled = true
      return
    }

    $(".mtrans-selected")?.classList.remove("mtrans-selected")
    selectedItem.classList.add("mtrans-selected")

    const artId = selectedItem.dataset.id
    const art = poolData.arts[artId]
    checkPPB(poolData, artId)
    artNameDiv.textContent = `${art.name} [${art.dur1}/${art.dur2}]`
    ppbInput.value = art.ppb
    ppbInput.disabled = removeBtn.disabled = saveBtn.disabled = false
  }

  // ==================== TRANSFER EXECUTION ====================
  const transferPool = async (poolData) => {
    const errorDiv = $("#mtrans-error"), progressBar = $(".mtrans-pbar")
    progressBar.style.display = "block"
    errorDiv.innerHTML = "<br>"

    const sign = /sign='(\w+)/.exec(DATA)[1]
    const step = 100 / poolData.selected.length

    for (let artId of poolData.selected) {
      const response = await fetch("art_transfer.php", {
        method: "POST",
        redirect: "manual",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: `id=${artId}&nick=${urlencode(poolData.renter)}&gold=${poolData.arts[artId].summ}&sendtype=2&dtime=${poolData.days}&bcount=${poolData.battles}&rep_price=0&art_id=&sign=${sign}`,
      })

      if (response.ok) {
        progressBar.style.display = "none"
        const buffer = await response.arrayBuffer()
        const html = new TextDecoder("windows-1251").decode(buffer)
        const doc = new DOMParser().parseFromString(html, "text/html")
        errorDiv.append($("td>font", doc))
        $("#btn_transfer").disabled = false
        return
      }
      progressBar.value += step
    }

    sessionStorage.setItem("redirect", "true")
    location.reload()
  }

  // ==================== MULTI TRANSFER PANEL ====================
  const MTPanel = async (container, friendsOptions, translist) => {
    const poolData = { arts: {}, selected: [], days: 0, hours: 0, battles: 0, renter: "" }

    let html = '<div class="mtrans-container"><div class="mtrans-header"><div id="art-name"></div>'
    html += '<br>Стоимость боя <input id="ppb" type="text" maxlength="4" size="4" placeholder="0">'
    html += "<br><br><button id=btn_save>Сохранить цену</button><br><br><button id=btn_remove>Убрать артефакт</button>"
    html += '</div><div class="mtrans-arts thin-scrollbar">'

    const sortedIds = Object.keys(translist).sort((a, b) => getIndex(a) - getIndex(b))
    for (let artId of sortedIds) {
      const idx = getIndex(artId)
      if (idx === -1 || !INV_ARTS_OBJ[idx] || INV_ARTS_OBJ[idx][12] !== 0 || INV_ARTS_OBJ[idx][20] === 1) continue

      const art = INV_ARTS_OBJ[idx]
      const imgMatch = /artifacts\/((?:\w+\/)*[\w-]+)/.exec(art[7])
      poolData.arts[artId] = { name: art[3] + art[8], ppb: translist[artId], dur1: art[5], dur2: art[6] }

      html += `<div class="inventory_item_div mtrans-item" data-id="${artId}" art_idx="${idx}">`
      html += `<div class="mtrans-dur">${art[5]}/${art[6]}</div><input type="checkbox" class="mtrans-chk">`
      html += `<img src="${IMG_LINK}art_fon_100x100.png" height="100%">`
      html += `<img src="${IMG_LINK}artifacts/${imgMatch?.[1] || ""}.png" height="100%" class="cre_mon_image2">`
      html += `<div class="art_mods no-events">${parseSuffix(art[8])}</div></div>`
    }

    html += '</div><div class="mtrans-footer">'
    html += `<select style="width:100%" id="friends"><option selected disabled>Выбрать получателя</option>${friendsOptions}</select><br>`
    html += 'Получатель <input id="renter" type="text" style="width:155px"><br>Передать через:<br>'
    html += '<input style="width:42px" id="hours" type="text" maxlength="3" placeholder="0"> час.'
    html += ' <input style="width:42px" id="days" type="text" maxlength="3" placeholder="0"> дн.'
    html += ' <input style="width:24px" id="bcount" type="text" maxlength="2" placeholder="0"> боёв<br>'
    html += 'Стоимость: <span id="summ">0</span> Комиссия: <span id="comm">0</span>'
    html += '<button id="btn_transfer">Передать</button></div>'
    html += '<div id="pool" class="mtrans-pool"></div></div><progress class="mtrans-pbar" max="100" value="0"></progress><div id="mtrans-error"></div>'

    container.innerHTML = html

    const renterInput = $("#renter"), bcountInput = $("#bcount")
    const daysInput = $("#days"), hoursInput = $("#hours"), transferBtn = $("#btn_transfer")
    let currentItem = $(".mtrans-item")

    // Restore session
    const saved = sessionStorage.getItem("mtrans")
    if (saved) {
      const s = JSON.parse(saved)
      poolData.renter = renterInput.value = s.renter || ""
      poolData.battles = s.battles || 0
      poolData.days = s.days || 0
      poolData.hours = s.hours || 0
      bcountInput.value = poolData.battles || ""
      daysInput.value = poolData.days || ""
      hoursInput.value = poolData.hours || ""
      poolData.selected = s.selected?.filter(id => id in poolData.arts) || []
      poolData.selected.forEach(id => {
        const cb = $(`[data-id="${id}"] .mtrans-chk`)
        if (cb) cb.checked = true
      })
    }

    showPool(poolData, currentItem)
    poolToSession(poolData)
    setSelected(poolData, currentItem)
    checkTransEnable(poolData, transferBtn)
    checkBattlesCount(poolData)

    // Events
    transferBtn.onclick = () => { transferBtn.disabled = true; transferPool(poolData) }

    $(".mtrans-arts").onclick = (e) => {
      if (e.target.tagName === "IMG") {
        currentItem = e.target.parentNode
        setSelected(poolData, currentItem)
        showPool(poolData, currentItem)
      }
      if (e.target.tagName === "INPUT") {
        const artId = e.target.parentNode.dataset.id
        if (e.target.checked) poolData.selected.push(artId)
        else poolData.selected = poolData.selected.filter(id => id !== artId)
        currentItem = e.target.parentNode
        setSelected(poolData, currentItem)
        showPool(poolData, currentItem)
        poolToSession(poolData)
        checkTransEnable(poolData, transferBtn)
        checkBattlesCount(poolData)
      }
    }

    $("#btn_save").onclick = () => {
      const artId = currentItem.dataset.id
      poolData.arts[artId].ppb = translist[artId] = +$("#ppb").value
      saveTranslist(translist)
      showPool(poolData, currentItem)
      poolToSession(poolData)
      checkPPB(poolData, artId)
      checkTransEnable(poolData, transferBtn)
    }

    $("#btn_remove").onclick = () => {
      const artId = currentItem.dataset.id
      delete translist[artId]
      delete poolData.arts[artId]
      poolData.selected = poolData.selected.filter(id => id !== artId)
      currentItem.remove()
      saveTranslist(translist)
      currentItem = $(".mtrans-item")
      setSelected(poolData, currentItem)
      showPool(poolData, currentItem)
      poolToSession(poolData)
      checkTransEnable(poolData, transferBtn)
    }

    renterInput.oninput = () => {
      poolData.renter = renterInput.value.trim()
      poolToSession(poolData)
      checkTransEnable(poolData, transferBtn)
    }

    $("#friends").onchange = (e) => {
      poolData.renter = renterInput.value = e.target.value
      poolToSession(poolData)
      checkTransEnable(poolData, transferBtn)
    }

    daysInput.oninput = () => {
      poolData.days = Math.min(365, +daysInput.value) || 0
      poolData.hours = poolData.days * 24
      hoursInput.value = poolData.hours || ""
      poolToSession(poolData)
      checkTransEnable(poolData, transferBtn)
    }

    hoursInput.oninput = () => {
      const hours = +hoursInput.value
      if (!hours || hours < 0.1) return
      poolData.hours = hours
      poolData.days = +(hours / 24).toFixed(3)
      daysInput.value = poolData.days
      poolToSession(poolData)
      checkTransEnable(poolData, transferBtn)
    }

    bcountInput.oninput = () => {
      poolData.battles = +bcountInput.value || 0
      showPool(poolData, currentItem)
      poolToSession(poolData)
      checkTransEnable(poolData, transferBtn)
      checkBattlesCount(poolData)
      if (currentItem) checkPPB(poolData, currentItem.dataset.id)
    }
  }

  // ==================== MOBILE SUPPORT ====================
  const setupMobileSupport = (container) => {
    const invMenu = $("#inv_menu")
    if (!invMenu) return

    const buttonsContainer = $("#inv_item_buttons")
    if (!buttonsContainer || $("#inv_menu_mtrans")) return

    const btn = document.createElement("div")
    btn.className = "inv_item_select"
    btn.id = "inv_menu_mtrans"
    btn.innerHTML = `<img class="inv_item_select_img show_hint" hint="В мультипередачу" src="${IMG_LINK}inv_im/btn_art_transfer.png">`
    buttonsContainer.insertBefore(btn, buttonsContainer.lastElementChild)

    btn.onclick = (e) => {
      e.stopPropagation()
      const artIdx = invMenu.getAttribute("art_idx")
      if (!artIdx || !INV_ARTS_OBJ[artIdx]) return

      const artId = INV_ARTS_OBJ[artIdx][0]
      if (INV_ARTS_OBJ[artIdx][23] === 0) return alert("Этот артефакт нельзя передать")

      const translist = getTranslist()
      if (artId in translist) return alert("Уже в списке мультипередачи")

      translist[artId] = 0
      saveTranslist(translist)
      setMTransferBadges(translist, container)

      const s = document.createElement("script")
      s.textContent = "if(typeof inv_menu_hide==='function')inv_menu_hide();"
      document.body.appendChild(s)
      s.remove()
    }

    const updateVisibility = () => {
      const artIdx = invMenu.getAttribute("art_idx")
      if (!artIdx || artIdx === "-1" || !INV_ARTS_OBJ[artIdx]) {
        btn.style.display = "none"
        return
      }
      const artId = INV_ARTS_OBJ[artIdx][0]
      const canTransfer = INV_ARTS_OBJ[artIdx][23] !== 0
      btn.style.display = (canTransfer && !(artId in getTranslist())) ? "block" : "none"
    }

    new MutationObserver(updateVisibility).observe(invMenu, { attributes: true, attributeFilter: ["art_idx", "style"] })
  }

  // ==================== MAIN ====================
  const multiTransfer = async (container) => {
    const tabsBlock = $(".filter_tabs_block")
    if (!tabsBlock) return

    const mtransIcon = `data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2" stroke-linecap="round"><path d="M5 9h14l-4-4M5 15h14l-4 4"/></svg>')}`
    tabsBlock.insertAdjacentHTML("beforeend",
      `<div id="mtrans_btn" hint="mtrans" title="Мультипередача" style="background:url('${mtransIcon}') no-repeat center, #fff; background-size: 20px;" class="filter_tab filter_tab_for_hover"></div>`)

    const mtransBtn = $("#mtrans_btn")
    let translist = getTranslist()
    setMTransferBadges(translist, container)

    const friendsOptions = await getFriendsList()

    mtransBtn.onclick = () => {
      const active = $(".filter_tab_active")
      if (active !== mtransBtn) {
        active?.classList.replace("filter_tab_active", "filter_tab_for_hover")
        mtransBtn.classList.replace("filter_tab_for_hover", "filter_tab_active")
        $("#return_all_rents")?.style.setProperty("display", "none")
        translist = getTranslist()
        MTPanel(container, friendsOptions, translist)
      }
    }

    let draggedIndex = null, draggedArtId = null

    container.ondragstart = (e) => {
      let target = e.target
      while (target && !(draggedIndex = target.getAttribute("art_idx"))) target = target.parentNode
      if (!draggedIndex || !INV_ARTS_OBJ[draggedIndex]) return
      draggedArtId = INV_ARTS_OBJ[draggedIndex][0]
      if (INV_ARTS_OBJ[draggedIndex][23] !== 0 && !(draggedArtId in translist)) {
        mtransBtn.classList.add("mtrans-btn-anim")
      }
    }

    mtransBtn.ondragover = (e) => {
      if (draggedIndex && INV_ARTS_OBJ[draggedIndex]?.[23] !== 0 && !(draggedArtId in translist)) {
        e.preventDefault()
      }
    }

    document.ondragend = () => mtransBtn.classList.remove("mtrans-btn-anim")
    tabsBlock.ondrop = () => mtransBtn.classList.remove("mtrans-btn-anim")

    mtransBtn.ondrop = () => {
      if (draggedArtId && !(draggedArtId in translist)) {
        translist[draggedArtId] = 0
        saveTranslist(translist)
        setMTransferBadges(translist, container)
      }
    }

    if (sessionStorage.getItem("redirect")) {
      sessionStorage.removeItem("redirect")
      mtransBtn.click()
    }
  }

  // ==================== INIT ====================
  const init = async () => {
    if (document.readyState !== "complete") {
      await new Promise(r => window.addEventListener("load", r, { once: true }))
    }

    INV_ARTS_OBJ = await getArtsFromPage()
    if (!Object.keys(INV_ARTS_OBJ).length) return

    const container = $("#inventory_block") || $(".inventory_items_block") || document.body

    setupMobileSupport(container)
    await multiTransfer(container)

    const invContainer = $("#inventory_block") || $(".inventory_items_block")
    if (invContainer) {
      new MutationObserver(async () => {
        INV_ARTS_OBJ = await getArtsFromPage()
        if ($(".filter_tab_active")?.getAttribute("hint") !== "mtrans") {
          setMTransferBadges(getTranslist(), invContainer)
        }
      }).observe(invContainer, { childList: true, subtree: true })
    }
  }

  init()
})()