GGn Upload Templator

Auto-fill upload forms using torrent file data with configurable templates

当前为 2025-09-28 提交的版本,查看 最新版本

// ==UserScript==
// @name        GGn Upload Templator
// @version     0.4
// @description Auto-fill upload forms using torrent file data with configurable templates
// @author      leveldesigner
// @license     Unlicense
// @source      https://github.com/lvldesigner/userscripts/tree/main/ggn-upload-templator
// @icon        https://gazellegames.net/favicon.ico
// @match       https://*.gazellegames.net/upload.php*
// @grant       GM_addStyle
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function(){"use strict";const C={TARGET_FORM_SELECTOR:"#upload_table",SUBMIT_KEYBINDING:!0,CUSTOM_FIELD_SELECTORS:[],IGNORED_FIELDS_BY_DEFAULT:["linkgroup","groupid","apikey","type","amazonuri","googleplaybooksuri","goodreadsuri","isbn","scan_dpi","other_dpi","release_desc","anonymous","dont_check_rules","title","tags","image","gameswebsiteuri","wikipediauri","album_desc","submit_upload"]},$=(...n)=>{console.debug("%c[GGn Upload Templator]","color: #4dd0e1; font-weight: 900;",...n)};function z(n){const e={},a=n.TARGET_FORM_SELECTOR||"form",t=document.querySelector(a),o="input[name], select[name], textarea[name]",s=n.CUSTOM_FIELD_SELECTORS||[],r=s.length>0?`${o}, ${s.join(", ")}`:o;return(t?t.querySelectorAll(r):document.querySelectorAll(r)).forEach(l=>{const c=q(l,n);if(!(c?l.name||l.id||l.getAttribute("data-field")||l.getAttribute("data-name"):l.name)||!c&&(l.type==="file"||l.type==="button"||l.type==="submit"))return;const b=l.name||l.id||l.getAttribute("data-field")||l.getAttribute("data-name");if(b){if(l.type==="radio"&&e[b])return;const d={value:c?l.value||l.textContent||l.getAttribute("data-value")||"":l.type==="checkbox"||l.type==="radio"?l.checked:l.value||"",label:D(l,n),type:l.tagName.toLowerCase(),inputType:l.type||"custom"};if(l.type==="radio"){const g=document.querySelectorAll(`input[name="${b}"][type="radio"]`);d.radioOptions=Array.from(g).map(h=>({value:h.value,checked:h.checked,label:D(h,n)||h.value}));const u=Array.from(g).find(h=>h.checked);d.selectedValue=u?u.value:"",d.value=d.selectedValue}l.tagName.toLowerCase()==="select"&&(d.options=Array.from(l.options).map(g=>({value:g.value,text:g.textContent.trim(),selected:g.selected})),d.selectedValue=l.value),e[b]=d}}),e}function q(n,e){const a=e.CUSTOM_FIELD_SELECTORS||[];return a.length===0?!1:a.some(t=>{try{return n.matches(t)}catch(o){return console.warn(`Invalid custom selector: ${t}`,o),!1}})}function F(n){if(!n)return n;const e=document.createElement("div");e.innerHTML=n,e.querySelectorAll("a").forEach(o=>{o.remove()});let t=e.textContent||e.innerText||"";return t=t.trim(),t.endsWith(":")&&(t=t.slice(0,-1).trim()),t}function D(n,e){if(q(n,e)){const o=n.parentElement;if(o){const s=o.querySelector("label");if(s){const i=s.innerHTML||s.textContent||"";return F(i)||n.id||n.name||"Custom Field"}const r=o.querySelector('*[class*="label"]');if(r){const i=r.innerHTML||r.textContent||"";return F(i)||n.id||n.name||"Custom Field"}}return n.id||n.name||"Custom Field"}if(n.type==="radio"&&n.id){const o=n.closest("td");if(o){const s=o.querySelector(`label[for="${n.id}"]`);if(s){const r=s.innerHTML||s.textContent||"";return F(r)||n.value}}}const t=n.closest("tr");if(t){const o=t.querySelector("td.label");if(o){const s=o.innerHTML||o.textContent||"",r=F(s);return r?`${r} (${n.name})`:n.name}}return n.name}function V(n,e){e.TARGET_FORM_SELECTOR&&`${e.TARGET_FORM_SELECTOR}`;const a="input[name], select[name], textarea[name]",t=e.CUSTOM_FIELD_SELECTORS||[],o=t.length>0?`${a}, ${t.join(", ")}`:a,s=e.TARGET_FORM_SELECTOR?document.querySelector(e.TARGET_FORM_SELECTOR):null,r=s?s.querySelectorAll(o):document.querySelectorAll(o);for(const i of r){const l=q(i,e);if(!(l?i.name||i.id||i.getAttribute("data-field")||i.getAttribute("data-name"):i.name)||!l&&(i.type==="file"||i.type==="button"||i.type==="submit"))continue;if((i.name||i.id||i.getAttribute("data-field")||i.getAttribute("data-name"))===n)return i}return null}class w{static async parseTorrentFile(e){const a=await e.arrayBuffer(),t=new Uint8Array(a);try{const[o]=w.decodeBencode(t);return{name:o.info?.name||e.name,files:o.info?.files?.map(s=>({path:s.path.join("/"),length:s.length}))||[{path:o.info?.name||e.name,length:o.info?.length}]}}catch(o){return console.warn("Could not parse torrent file:",o),{name:e.name,files:[]}}}static decodeBencode(e,a=0){const t=String.fromCharCode(e[a]);if(t==="d"){const o={};for(a++;e[a]!==101;){const[s,r]=w.decodeBencode(e,a),[i,l]=w.decodeBencode(e,r);o[s]=i,a=l}return[o,a+1]}if(t==="l"){const o=[];for(a++;e[a]!==101;){const[s,r]=w.decodeBencode(e,a);o.push(s),a=r}return[o,a+1]}if(t==="i"){a++;let o="";for(;e[a]!==101;)o+=String.fromCharCode(e[a]),a++;return[parseInt(o),a+1]}if(t>="0"&&t<="9"){let o="";for(;e[a]!==58;)o+=String.fromCharCode(e[a]),a++;const s=parseInt(o);return a++,[new TextDecoder("utf-8",{fatal:!1}).decode(e.slice(a,a+s)),a+s]}throw new Error("Invalid bencode data")}}function B(n,e,a=!0){if(!n||!e)return{};let t=n.replace(/\\\$/g,"___ESCAPED_DOLLAR___").replace(/\\\{/g,"___ESCAPED_LBRACE___").replace(/\\\}/g,"___ESCAPED_RBRACE___").replace(/\\\\/g,"___ESCAPED_BACKSLASH___").replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\$\\\{([^}]+)\\\}/g,(o,s,r,i)=>{if(a)return`(?<${s}>.+)`;{const l=i.slice(r+o.length);return/\\\$\\\{[^}]+\\\}/.test(l)?`(?<${s}>.*?)`:`(?<${s}>.+)`}}).replace(/___ESCAPED_DOLLAR___/g,"\\$").replace(/___ESCAPED_LBRACE___/g,"\\{").replace(/___ESCAPED_RBRACE___/g,"\\}").replace(/___ESCAPED_BACKSLASH___/g,"\\\\");try{const o=new RegExp(t,"i");return e.match(o)?.groups||{}}catch(o){return console.warn("Invalid template regex:",o),{}}}function O(n,e){return!n||!e?n:n.replace(/\$\{([^}]+)\}/g,(a,t)=>e[t]||a)}function P(n,e,a){if(!n||!e)return null;const t=e.toLowerCase();for(const o of n){const s=o.textContent?o.textContent.toLowerCase():o.text.toLowerCase(),r=o.value.toLowerCase();let i=!1;switch(a){case"exact":i=s===t||r===t;break;case"contains":i=s.includes(t)||r.includes(t);break;case"starts":i=s.startsWith(t)||r.startsWith(t);break;case"ends":i=s.endsWith(t)||r.endsWith(t);break}if(i)return{value:o.value,text:o.textContent||o.text}}return null}const Y=n=>`
  <div class="gut-modal-content">
    <div class="gut-modal-tabs">
      <button class="gut-tab-btn active" data-tab="templates">Templates</button>
      <button class="gut-tab-btn" data-tab="settings">Settings</button>
    </div>

    <div class="gut-tab-content active" id="templates-tab">
      ${Object.keys(n.templates).length===0?'<div style="padding: 20px; text-align: center; color: #888;">No templates found. Create a template first.</div>':`<div class="gut-template-list">
            ${Object.keys(n.templates).map(e=>`
                <div class="gut-template-item">
                  <span class="gut-template-name">${n.escapeHtml(e)}</span>
                  <div class="gut-template-actions">
                    <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="edit" data-template="${n.escapeHtml(e)}">Edit</button>
                    <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="clone" data-template="${n.escapeHtml(e)}">Clone</button>
                    <button class="gut-btn gut-btn-danger gut-btn-small" data-action="delete" data-template="${n.escapeHtml(e)}">Delete</button>
                  </div>
                </div>
              `).join("")}
          </div>`}
    </div>

    <div class="gut-tab-content" id="settings-tab">
      <div class="gut-form-group">
        <label for="setting-form-selector">Target Form Selector:</label>
        <input type="text" id="setting-form-selector" value="${n.escapeHtml(n.config.TARGET_FORM_SELECTOR)}" placeholder="#upload_table">
      </div>

      <div class="gut-form-group">
        <label class="gut-checkbox-label">
          <input type="checkbox" id="setting-submit-keybinding" ${n.config.SUBMIT_KEYBINDING?"checked":""}>
          <span class="gut-checkbox-text">\u26A1 Enable Ctrl+Enter form submission</span>
        </label>
      </div>

      <div class="gut-form-group">
        <label for="setting-custom-selectors">Custom Field Selectors (one per line):</label>
        <textarea id="setting-custom-selectors" rows="4" placeholder="div[data-field]
.custom-input[name]
button[data-value]">${(n.config.CUSTOM_FIELD_SELECTORS||[]).join(`
`)}</textarea>
        <div style="font-size: 12px; color: #888; margin-top: 5px;">
          Additional CSS selectors to find form fields. e.g: <a href="#" id="ggn-infobox-link" class="gut-link">GGn Infobox</a>
        </div>
      </div>

      <div class="gut-form-group" id="custom-selectors-preview-group" style="display: none;">
        <label id="matched-elements-label">Matched Elements:</label>
        <div id="custom-selectors-matched" class="gut-extracted-vars">
          <div class="gut-no-variables">No elements matched by custom selectors.</div>
        </div>
      </div>

      <div class="gut-form-group">
        <label for="setting-ignored-fields">Ignored Fields (one per line):</label>
        <textarea id="setting-ignored-fields" rows="6" placeholder="linkgroup
groupid
apikey">${n.config.IGNORED_FIELDS_BY_DEFAULT.join(`
`)}</textarea>
      </div>

      <div class="gut-form-group">
        <div style="display: flex; justify-content: space-between; align-items: center;">
          <div style="display: flex; gap: 10px;">
            <button class="gut-btn gut-btn-primary" id="save-settings">Save Settings</button>
            <button class="gut-btn gut-btn-secondary" id="reset-settings">Reset to Defaults</button>
          </div>
          <button class="gut-btn gut-btn-danger" id="delete-all-config">Delete All Local Config</button>
        </div>
      </div>
    </div>

    <div class="gut-modal-actions">
      <button class="gut-btn" id="close-manager">Close</button>
    </div>
  </div>
`,K=n=>`
  <option value="">Select Template</option>
  ${Object.keys(n.templates).map(e=>`<option value="${e}" ${e===n.selectedTemplate?"selected":""}>${e}</option>`).join("")}
`,W=n=>Object.keys(n.templates).length===0?'<div style="padding: 20px; text-align: center; color: #888;">No templates found. Close this dialog and create a template first.</div>':`<div class="gut-template-list">
         ${Object.keys(n.templates).map(e=>`
             <div class="gut-template-item">
               <span class="gut-template-name">${n.escapeHtml(e)}</span>
               <div class="gut-template-actions">
                 <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="edit" data-template="${n.escapeHtml(e)}">Edit</button>
                 <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="clone" data-template="${n.escapeHtml(e)}">Clone</button>
                 <button class="gut-btn gut-btn-danger gut-btn-small" data-action="delete" data-template="${n.escapeHtml(e)}">Delete</button>
               </div>
             </div>
           `).join("")}
       </div>`,J=(n,e,a,t,o)=>`
  <div class="gut-modal-content">
    <h2>
      ${a?'<button class="gut-modal-back-btn" id="back-to-manager" title="Back to Template Manager">&lt;</button>':""}
      ${a?"Edit Template":"Create Template"}
    </h2>

    <div class="gut-form-group">
      <label for="template-name">Template Name:</label>
      <input type="text" id="template-name" placeholder="e.g., Magazine Template" value="${a?e.escapeHtml(a):""}">
    </div>

    <div class="gut-form-group">
      <label for="sample-torrent">Sample Torrent Name (for preview):</label>
      <input type="text" id="sample-torrent" value="${e.escapeHtml(o)}" placeholder="e.g., PCWorld - Issue 05 - 01-2024.zip">
    </div>

    <div class="gut-form-group" style="margin-bottom: 8px;">
      <label for="torrent-mask">Torrent Name Mask:</label>
      <input type="text" id="torrent-mask" placeholder="e.g., \${magazine} - Issue \${issue} - \${month}-\${year}.\${ext}" value="${t?e.escapeHtml(t.mask):""}">
    </div>

    <div class="gut-form-group">
      <label style="display: inline-flex; align-items: center; gap: 8px; margin: 0; font-size: 13px; color: #888888; font-weight: normal;" title="When enabled, patterns capture as much text as possible. When disabled, uses smart matching that's usually more precise.">
        <input type="checkbox" id="greedy-matching" ${t?t.greedyMatching!==!1?"checked":"":"checked"} style="margin: 0; accent-color: #0d7377; width: auto;">
        <span>Greedy matching</span>
      </label>
    </div>

    <div class="gut-form-group">
      <label>Extracted Variables:</label>
      <div id="extracted-variables" class="gut-extracted-vars">
        <div class="gut-no-variables">No variables defined yet. Add variables like \${name} to your mask.</div>
      </div>
    </div>

    <div class="gut-form-group">
      <div style="display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px;">
        <label style="margin: 0;">Form Fields:</label>
        <div style="display: flex; align-items: center; gap: 10px;">
          <input type="text" id="field-filter" placeholder="Filter fields..." style="padding: 6px 8px; border: 1px solid #404040; border-radius: 3px; background: #2a2a2a; color: #e0e0e0; font-size: 12px; min-width: 150px;">
          <button type="button" class="gut-btn gut-btn-secondary" id="toggle-unselected" style="padding: 6px 12px; font-size: 12px; white-space: nowrap;">Show Unselected</button>
        </div>
      </div>
      <div class="gut-field-list">
        ${Object.entries(n).map(([s,r])=>{const i=e.config.IGNORED_FIELDS_BY_DEFAULT.includes(s.toLowerCase()),l=t&&t.fieldMappings.hasOwnProperty(s),c=l?t.fieldMappings[s]:null;let m=l||!i;if(t&&t.customUnselectedFields){const b=t.customUnselectedFields.find(d=>d.field===s);b&&(m=b.selected)}return`
               <div class="gut-field-row ${i&&!l&&!m?"gut-hidden":""}">
                 ${r.type==="select"?(()=>{const b=t&&t.variableMatching&&t.variableMatching[s];b&&t.variableMatching[s];const d=b;return`<div style="display: flex; align-items: flex-start; width: 100%;">
                           <a href="#" class="gut-link gut-variable-toggle" data-field="${s}" data-state="${d?"on":"off"}">Match from variable: ${d?"ON":"OFF"}</a>
                         </div>`})():""}
                 <input type="checkbox" ${m?"checked":""} data-field="${s}">
                 <label title="${s}">${r.label}:</label>
                 ${r.type==="select"?(()=>{const b=t&&t.variableMatching&&t.variableMatching[s],d=b?t.variableMatching[s]:null,g=b;return`<div class="gut-select-container" style="display: flex; flex-direction: column; gap: 4px; flex: 1;">
                             <div style="display: flex; flex-direction: column; align-items: flex-end;">
                               <select data-template="${s}" class="template-input gut-select select-static-mode" style="width: 100%; ${g?"display: none;":""}">
                                 ${r.options.map(u=>{let h=u.selected;return c&&c===u.value&&(h=!0),`<option value="${e.escapeHtml(u.value)}" ${h?"selected":""}>${e.escapeHtml(u.text)}</option>`}).join("")}
                               </select>
                             </div>
                            <div class="gut-variable-controls" data-field="${s}" style="display: ${g?"flex":"none"}; gap: 8px;">
                              <select class="gut-match-type" data-field="${s}" style="padding: 6px 8px; border: 1px solid #404040; border-radius: 3px; background: #1a1a1a; color: #e0e0e0; font-size: 12px;">
                              <option value="exact" ${d&&d.matchType==="exact"?"selected":""}>Is exactly</option>
                              <option value="contains" ${d&&d.matchType==="contains"?"selected":""}>Contains</option>
                              <option value="starts" ${d&&d.matchType==="starts"?"selected":""}>Starts with</option>
                              <option value="ends" ${d&&d.matchType==="ends"?"selected":""}>Ends with</option>
                            </select>
                            <input type="text" class="gut-variable-input" data-field="${s}" placeholder="\${variable_name}" value="${d?e.escapeHtml(d.variableName):""}" style="flex: 1; padding: 6px 8px; border: 1px solid #404040; border-radius: 3px; background: #1a1a1a; color: #e0e0e0; font-size: 12px;">
                            </div>
                          </div>`})():r.inputType==="checkbox"?`<input type="checkbox" ${c!==null?c?"checked":"":r.value?"checked":""} data-template="${s}" class="template-input">`:r.inputType==="radio"?`<select data-template="${s}" class="template-input gut-select">
                             ${r.radioOptions.map(b=>{let d=b.checked;return c&&c===b.value&&(d=!0),`<option value="${e.escapeHtml(b.value)}" ${d?"selected":""}>${e.escapeHtml(b.label)}</option>`}).join("")}
                           </select>`:`<input type="text" value="${c!==null?e.escapeHtml(String(c)):e.escapeHtml(String(r.value))}" data-template="${s}" class="template-input">`}
                 <span class="gut-preview" data-preview="${s}"></span>
               </div>
             `}).join("")}
      </div>
    </div>

    <div class="gut-modal-actions">
      <button class="gut-btn" id="cancel-template">Cancel</button>
      <button class="gut-btn gut-btn-primary" id="save-template">${a?"Update Template":"Save Template"}</button>
    </div>
  </div>
`,X=n=>`
  <div class="ggn-upload-templator-controls" style="align-items: flex-end;">
    <div style="display: flex; flex-direction: column; gap: 5px;">
      <div style="display: flex; justify-content: space-between; align-items: center;">
        <label for="template-selector" style="font-size: 12px; color: #b0b0b0; margin: 0;">Select template</label>
        <a href="#" id="edit-selected-template-btn" class="gut-link" style="${n.selectedTemplate&&n.selectedTemplate!=="none"&&n.templates[n.selectedTemplate]?"":"display: none;"}">Edit</a>
      </div>
      <div style="display: flex; gap: 10px; align-items: center;">
        <select id="template-selector" class="gut-select">
          <option value="">Select Template</option>
          ${Object.keys(n.templates).map(e=>`<option value="${e}" ${e===n.selectedTemplate?"selected":""}>${e}</option>`).join("")}
        </select>
      </div>
    </div>
    <button type="button" id="create-template-btn" class="gut-btn gut-btn-primary">+ Create Template</button>
    <button id="manage-templates-btn" type="button" class="gut-btn gut-btn-secondary" title="Manage Templates & Settings">Settings</button>
  </div>
`;function Q(n){const e=document.querySelector('input[type="file"]');if(!e){console.warn("No file input found on page, UI injection aborted");return}const a=document.getElementById("ggn-upload-templator-ui");a&&a.remove();const t=document.createElement("div");t.id="ggn-upload-templator-ui",t.innerHTML=X(n);try{e.parentNode.insertBefore(t,e)}catch(o){console.error("Failed to insert UI container:",o);return}try{const o=document.getElementById("create-template-btn"),s=document.getElementById("template-selector"),r=document.getElementById("manage-templates-btn"),i=document.getElementById("edit-selected-template-btn");o&&o.addEventListener("click",async()=>await n.showTemplateCreator()),s&&s.addEventListener("change",l=>n.selectTemplate(l.target.value)),r&&r.addEventListener("click",()=>n.showTemplateAndSettingsManager()),i&&i.addEventListener("click",l=>{l.preventDefault(),n.editTemplate(n.selectedTemplate)})}catch(o){console.error("Failed to bind UI events:",o)}}async function Z(n,e=null,a=null){const t=z(n.config);if(Object.keys(t).length===0){alert("No form fields found on this page.");return}let o="";const s=n.config.TARGET_FORM_SELECTOR?document.querySelectorAll(`${n.config.TARGET_FORM_SELECTOR} input[type="file"]`):document.querySelectorAll('input[type="file"]');for(const p of s)if(p.files&&p.files[0]&&p.files[0].name.toLowerCase().endsWith(".torrent"))try{o=(await w.parseTorrentFile(p.files[0])).name||"";break}catch(E){console.warn("Could not parse selected torrent file:",E)}const r=document.createElement("div");r.className="gut-modal",r.innerHTML=J(t,n,e,a,o),document.body.appendChild(r);const i=r.querySelector("#torrent-mask"),l=r.querySelector("#sample-torrent"),c=r.querySelectorAll(".template-input"),m=r.querySelector("#toggle-unselected"),b=r.querySelector("#field-filter"),d=()=>{const p=b.value.toLowerCase(),E=r.querySelectorAll(".gut-field-row"),_=r.querySelector(".gut-field-list");let x=0;const T=_.querySelector(".gut-no-results");if(T&&T.remove(),E.forEach(y=>{const k=y.querySelector('input[type="checkbox"]'),f=y.querySelector("label"),L=k.dataset.field.toLowerCase(),M=f.textContent.toLowerCase(),A=!p||L.includes(p)||M.includes(p),R=k.checked||!n.hideUnselectedFields;A&&R?(y.classList.remove("gut-hidden"),x++):y.classList.add("gut-hidden")}),p&&x===0){const y=document.createElement("div");y.className="gut-no-results",y.style.cssText="padding: 20px; text-align: center; color: #888; font-style: italic;",y.textContent=`No fields found matching "${p}"`,_.appendChild(y)}},g=()=>{n.hideUnselectedFields=!n.hideUnselectedFields,localStorage.setItem("ggn-upload-templator-hide-unselected",JSON.stringify(n.hideUnselectedFields)),m.textContent=n.hideUnselectedFields?"Show Unselected":"Hide Unselected",d()};m.textContent=n.hideUnselectedFields?"Show Unselected":"Hide Unselected",d(),m.addEventListener("click",g),b.addEventListener("input",d);const u=()=>{const p=i.value,E=l.value,_=r.querySelector("#greedy-matching").checked,x=B(p,E,_),T=r.querySelector("#extracted-variables");Object.keys(x).length===0?T.innerHTML='<div class="gut-no-variables">No variables defined yet. Add variables like ${name} to your mask.</div>':T.innerHTML=Object.entries(x).map(([y,k])=>`
            <div class="gut-variable-item">
              <span class="gut-variable-name">\${${H(y)}}</span>
              <span class="gut-variable-value ${k?"":"empty"}">${k?H(k):"(empty)"}</span>
            </div>
          `).join(""),c.forEach(y=>{const k=y.dataset.template,f=r.querySelector(`[data-preview="${k}"]`);if(y.type==="checkbox")f.textContent=y.checked?"\u2713 checked":"\u2717 unchecked",f.className="gut-preview";else if(y.tagName.toLowerCase()==="select"){const L=r.querySelector(`.gut-variable-toggle[data-field="${k}"]`);if(L&&L.dataset.state==="on"){const A=r.querySelector(`.gut-variable-input[data-field="${k}"]`),R=r.querySelector(`.gut-match-type[data-field="${k}"]`),I=A?A.value.trim():"",U=R?R.value:"exact";if(I&&x[I.replace(/^\$\{|\}$/g,"")]){const N=x[I.replace(/^\$\{|\}$/g,"")],j=P(y.options,N,U);j?(f.textContent=`\u2192 "${j.text}" (matched "${N}" using ${U})`,f.className="gut-preview active",f.style.display="block"):(f.textContent=`\u2192 No match found for "${N}" using ${U}`,f.className="gut-preview",f.style.display="block")}else I?(f.textContent=`\u2192 Variable ${I} not found in extracted data`,f.className="gut-preview",f.style.display="block"):(f.textContent="",f.className="gut-preview",f.style.display="none")}else f.textContent="",f.className="gut-preview",f.style.display="none"}else{const L=y.value||"",M=O(L,x);L.includes("${")&&Object.keys(x).length>0?(f.textContent=`\u2192 ${M}`,f.className="gut-preview active",f.style.display="block"):(f.textContent="",f.className="gut-preview",f.style.display="none")}})};[i,l,...c].forEach(p=>{p.addEventListener("input",u),p.addEventListener("change",u)}),u(),r.addEventListener("change",p=>{p.target.type==="checkbox"&&(p.target.id==="greedy-matching"?u():d())}),r.querySelector("#cancel-template").addEventListener("click",()=>{document.body.removeChild(r)}),r.querySelector("#save-template").addEventListener("click",()=>{n.saveTemplate(r,e)}),r.addEventListener("click",p=>{p.target===r&&document.body.removeChild(r)});const h=p=>{p.key==="Escape"&&document.body.contains(r)&&(document.body.removeChild(r),document.removeEventListener("keydown",h))};document.addEventListener("keydown",h),r.addEventListener("click",p=>{if(p.target.classList.contains("gut-variable-toggle")){p.preventDefault();const E=p.target.dataset.field,x=p.target.dataset.state==="off"?"on":"off";p.target.dataset.state=x,p.target.textContent=`Match from variable: ${x.toUpperCase()}`;const T=r.querySelector(`select.select-static-mode[data-template="${E}"]`),y=r.querySelector(`.gut-variable-controls[data-field="${E}"]`);x==="on"?(T.style.display="none",y.style.display="flex"):(T.style.display="block",y.style.display="none"),u()}}),r.querySelectorAll(".gut-variable-input, .gut-match-type").forEach(p=>{p.addEventListener("input",u),p.addEventListener("change",u)});const S=r.querySelector("#back-to-manager");S&&S.addEventListener("click",()=>{document.body.removeChild(r),n.showTemplateAndSettingsManager()})}function H(n){const e=document.createElement("div");return e.textContent=n,e.innerHTML}GM_addStyle(`#ggn-upload-templator-ui {
  background: #1a1a1a;
  border: 1px solid #404040;
  border-radius: 6px;
  padding: 15px;
  margin: 15px 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: #e0e0e0;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.ggn-upload-templator-controls {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

.gut-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease;
  text-decoration: none;
  outline: none;
  box-sizing: border-box;
  height: auto;
}

.gut-btn-primary {
  background: #0d7377;
  color: #ffffff;
  border: 1px solid #0d7377;
}

.gut-btn-primary:hover {
  background: #0a5d61;
  border-color: #0a5d61;
  transform: translateY(-1px);
}

.gut-btn-danger {
  background: #d32f2f;
  color: #ffffff;
  border: 1px solid #d32f2f;
}

.gut-btn-danger:hover:not(:disabled) {
  background: #b71c1c;
  border-color: #b71c1c;
  transform: translateY(-1px);
}

.gut-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

.gut-btn:not(:disabled):active {
  transform: translateY(0);
}

.gut-select {
  padding: 8px 12px;
  border: 1px solid #404040;
  border-radius: 4px;
  font-size: 14px;
  min-width: 200px;
  background: #2a2a2a;
  color: #e0e0e0;
  box-sizing: border-box;
  outline: none;
  height: auto;
  margin: 0 !important;
}

.gut-select:focus {
  border-color: #0d7377;
  box-shadow: 0 0 0 2px rgba(13, 115, 119, 0.2);
}

.gut-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  padding: 20px;
  box-sizing: border-box;
}

.gut-modal-content {
  background: #1a1a1a;
  border: 1px solid #404040;
  border-radius: 8px;
  padding: 24px;
  max-width: 800px;
  max-height: 80vh;
  overflow-y: auto;
  width: 90%;
  color: #e0e0e0;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  box-sizing: border-box;
}

.gut-modal h2 {
  margin: 0 0 20px 0;
  color: #ffffff;
  font-size: 24px;
  font-weight: 600;
  text-align: left;
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
}

.gut-modal-back-btn {
  background: none;
  border: none;
  color: #e0e0e0;
  font-size: 16px;
  cursor: pointer;
  padding: 8px;
  border-radius: 4px;
  transition:
    color 0.2s ease,
    background-color 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  font-family: monospace;
  font-weight: bold;
}

.gut-modal-back-btn:hover {
  color: #ffffff;
  background-color: #333333;
}

.gut-form-group {
  margin-bottom: 15px;
}

.gut-form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: 500;
  color: #b0b0b0;
  font-size: 14px;
}

.gut-form-group input,
.gut-form-group textarea {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid #404040;
  border-radius: 4px;
  font-size: 14px;
  box-sizing: border-box;
  background: #2a2a2a;
  color: #e0e0e0;
  outline: none;
  transition: border-color 0.2s ease;
  height: auto;
}

.gut-form-group input:focus,
.gut-form-group textarea:focus {
  border-color: #0d7377;
  box-shadow: 0 0 0 2px rgba(13, 115, 119, 0.2);
}

.gut-form-group input::placeholder,
.gut-form-group textarea::placeholder {
  color: #666666;
}

.gut-field-list {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #404040;
  border-radius: 4px;
  padding: 10px;
  background: #0f0f0f;
}

.gut-field-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  padding: 8px;
  background: #2a2a2a;
  border-radius: 4px;
  border: 1px solid #404040;
  flex-wrap: wrap;
}

.gut-field-row:hover {
  background: #333333;
}

.gut-field-row:not(:has(input[type="checkbox"]:checked)) {
  opacity: 0.6;
}

.gut-field-row.gut-hidden {
  display: none;
}

.gut-field-row input[type="checkbox"] {
  width: auto;
  margin: 0;
  accent-color: #0d7377;
  cursor: pointer;
}

.gut-field-row label {
  min-width: 150px;
  margin: 0;
  font-size: 13px;
  color: #b0b0b0;
  cursor: help;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gut-field-row input[type="text"],
.gut-field-row select {
  flex: 1;
  margin: 0;
  padding: 6px 8px;
  border: 1px solid #404040;
  border-radius: 3px;
  background: #1a1a1a;
  color: #e0e0e0;
  font-size: 12px;
  outline: none;
  height: auto;
}

.gut-field-row input[type="text"]:focus {
  border-color: #0d7377;
  box-shadow: 0 0 0 1px rgba(13, 115, 119, 0.3);
}

.gut-preview {
  color: #888888;
  font-style: italic;
  font-size: 11px;
  word-break: break-all;
  flex-basis: 100%;
  margin-top: 4px;
  padding-left: 20px;
}

.gut-preview.active {
  color: #4dd0e1;
  font-weight: bold;
  font-style: normal;
}

.gut-modal-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #404040;
}

.gut-status {
  position: fixed;
  top: 20px;
  right: 20px;
  background: #2e7d32;
  color: #ffffff;
  padding: 12px 20px;
  border-radius: 6px;
  z-index: 10001;
  font-size: 14px;
  font-weight: 500;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  border: 1px solid #4caf50;
  animation: slideInRight 0.3s ease-out;
}

.gut-status.error {
  background: #d32f2f;
  border-color: #f44336;
}

@keyframes slideInRight {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.gut-template-list {
  max-height: 400px;
  overflow-y: auto;
  border: 1px solid #404040;
  border-radius: 4px;
  background: #0f0f0f;
}

.gut-template-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid #404040;
  background: #2a2a2a;
  transition: background-color 0.2s ease;
}

.gut-template-item:hover {
  background: #333333;
}

.gut-template-item:last-child {
  border-bottom: none;
}

.gut-template-name {
  font-weight: 500;
  color: #e0e0e0;
  flex: 1;
  margin-right: 10px;
}

.gut-template-actions {
  display: flex;
  gap: 8px;
}

.gut-btn-small {
  padding: 6px 12px;
  font-size: 12px;
  min-width: auto;
}

.gut-btn-secondary {
  background: #555555;
  color: #ffffff;
  border: 1px solid #555555;
}

.gut-btn-secondary:hover:not(:disabled) {
  background: #666666;
  border-color: #666666;
  transform: translateY(-1px);
}

/* Tab styles for modal */
.gut-modal-tabs {
  display: flex;
  border-bottom: 1px solid #404040;
  margin-bottom: 20px;
}

.gut-tab-btn {
  padding: 12px 20px;
  background: transparent;
  border: none;
  color: #b0b0b0;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  border-bottom: 2px solid transparent;
  transition: all 0.2s ease;
  height: auto;
}

.gut-tab-btn:hover {
  color: #e0e0e0;
  background: #2a2a2a;
}

.gut-tab-btn.active {
  color: #ffffff;
  border-bottom-color: #0d7377;
}

.gut-tab-content {
  display: none;
}

.gut-tab-content.active {
  display: block;
}

/* Checkbox label styling */
.gut-checkbox-label {
  display: flex !important;
  align-items: center !important;
  gap: 10px !important;
  padding: 8px 12px !important;
  background: #2a2a2a !important;
  border: 1px solid #404040 !important;
  border-radius: 4px !important;
  cursor: pointer !important;
  transition: border-color 0.2s ease !important;
  margin: 0 !important;
}

.gut-checkbox-label:hover {
  border-color: #0d7377 !important;
}

.gut-checkbox-label input[type="checkbox"] {
  width: auto !important;
  margin: 0 !important;
  accent-color: #0d7377 !important;
  cursor: pointer !important;
}

.gut-checkbox-text {
  font-size: 14px !important;
  font-weight: 500 !important;
  color: #b0b0b0 !important;
  user-select: none !important;
}

.gut-variable-toggle {
  font-size: 11px !important;
  padding: 2px 6px !important;
  white-space: nowrap !important;
}

/* Scrollbar styling for webkit browsers */
.gut-field-list::-webkit-scrollbar,
.gut-modal-content::-webkit-scrollbar {
  width: 8px;
}

.gut-field-list::-webkit-scrollbar-track,
.gut-modal-content::-webkit-scrollbar-track {
  background: #0f0f0f;
  border-radius: 4px;
}

.gut-field-list::-webkit-scrollbar-thumb,
.gut-modal-content::-webkit-scrollbar-thumb {
  background: #404040;
  border-radius: 4px;
}

.gut-field-list::-webkit-scrollbar-thumb:hover,
.gut-modal-content::-webkit-scrollbar-thumb:hover {
  background: #555555;
}

/* Extracted variables section */
.gut-extracted-vars {
  border: 1px solid #404040;
  border-radius: 4px;
  background: #0f0f0f;
  padding: 12px;
  min-height: 80px;
  max-height: 300px;
  overflow-y: auto;
}

.gut-extracted-vars:has(.gut-no-variables) {
  display: flex;
  align-items: center;
  justify-content: center;
}

.gut-no-variables {
  color: #666666;
  font-style: italic;
  text-align: center;
  padding: 20px 10px;
}

.gut-variable-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  margin-bottom: 6px;
  background: #2a2a2a;
  border: 1px solid #404040;
  border-radius: 4px;
  transition: background-color 0.2s ease;
}

.gut-variable-item:last-child {
  margin-bottom: 0;
}

.gut-variable-item:hover {
  background: #333333;
}

.gut-variable-name {
  font-weight: 500;
  color: #4dd0e1;
  font-family: monospace;
  font-size: 13px;
}

.gut-variable-value {
  color: #e0e0e0;
  font-size: 12px;
  max-width: 60%;
  word-break: break-all;
  text-align: right;
}

.gut-variable-value.empty {
  color: #888888;
  font-style: italic;
}

/* Generic hyperlink style for secondary links */
.gut-link {
  font-size: 12px !important;
  color: #b0b0b0 !important;
  text-decoration: underline !important;
  text-underline-offset: 2px !important;
  cursor: pointer !important;
  transition: color 0.2s ease !important;
}

.gut-link:hover {
  color: #4dd0e1 !important;
}

.gut-variable-toggle {
  font-size: 11px !important;
  padding: 2px 6px !important;
  margin-left: auto !important;
  align-self: flex-start !important;
  white-space: nowrap !important;
}
`);class G{constructor(){try{this.templates=JSON.parse(localStorage.getItem("ggn-upload-templator-templates")||"{}")}catch(e){console.error("Failed to load templates:",e),this.templates={}}try{this.selectedTemplate=localStorage.getItem("ggn-upload-templator-selected")||null}catch(e){console.error("Failed to load selected template:",e),this.selectedTemplate=null}try{this.hideUnselectedFields=JSON.parse(localStorage.getItem("ggn-upload-templator-hide-unselected")||"true")}catch(e){console.error("Failed to load hide unselected setting:",e),this.hideUnselectedFields=!0}try{this.config={...C,...JSON.parse(localStorage.getItem("ggn-upload-templator-settings")||"{}")}}catch(e){console.error("Failed to load config:",e),this.config={...C}}$("Initialized core state",{templates:Object.keys(this.templates),selectedTemplate:this.selectedTemplate,hideUnselectedFields:this.hideUnselectedFields,config:this.config}),this.init()}init(){$("Initializing...");try{Q(this)}catch(e){console.error("UI injection failed:",e)}try{this.watchFileInputs()}catch(e){console.error("File input watching setup failed:",e)}if(this.config.SUBMIT_KEYBINDING)try{this.setupSubmitKeybinding()}catch(e){console.error("Submit keybinding setup failed:",e)}$("Initialized")}async showTemplateCreator(e=null,a=null){await Z(this,e,a)}saveTemplate(e,a=null){const t=e.querySelector("#template-name").value.trim(),o=e.querySelector("#torrent-mask").value.trim();if(!t||!o){alert("Please provide both template name and torrent mask.");return}if((a&&t!==a&&this.templates[t]||!a&&this.templates[t])&&!confirm(`Template "${t}" already exists. Overwrite?`))return;const s={},r={};e.querySelectorAll('.gut-field-row input[type="checkbox"]:checked').forEach(d=>{const g=d.dataset.field,u=e.querySelector(`[data-template="${g}"]`);if(u)if(u.type==="checkbox")s[g]=u.checked;else if(u.tagName.toLowerCase()==="select"){const h=e.querySelector(`.gut-variable-toggle[data-field="${g}"]`);if(h&&h.dataset.state==="on"){const S=e.querySelector(`.gut-variable-input[data-field="${g}"]`),p=e.querySelector(`.gut-match-type[data-field="${g}"]`);r[g]={variableName:S?S.value.trim():"",matchType:p?p.value:"exact"},s[g]=S?S.value.trim():""}else s[g]=u.value}else s[g]=u.value});const l=e.querySelectorAll(".gut-field-row"),c=[];l.forEach(d=>{const g=d.querySelector('input[type="checkbox"]');if(g){const u=g.dataset.field,h=this.config.IGNORED_FIELDS_BY_DEFAULT.includes(u.toLowerCase()),v=g.checked;(h&&v||!h&&!v)&&c.push({field:u,selected:v})}}),a&&t!==a&&(delete this.templates[a],this.selectedTemplate===a&&(this.selectedTemplate=t,localStorage.setItem("ggn-upload-templator-selected",t)));const m=e.querySelector("#greedy-matching").checked;this.templates[t]={mask:o,fieldMappings:s,greedyMatching:m,customUnselectedFields:c.length>0?c:void 0,variableMatching:Object.keys(r).length>0?r:void 0},localStorage.setItem("ggn-upload-templator-templates",JSON.stringify(this.templates)),this.updateTemplateSelector();const b=a?"updated":"saved";this.showStatus(`Template "${t}" ${b} successfully!`),document.body.removeChild(e)}updateTemplateSelector(){const e=document.getElementById("template-selector");e&&(e.innerHTML=K(this),this.updateEditButtonVisibility())}updateEditButtonVisibility(){const e=document.getElementById("edit-selected-template-btn");if(!e)return;const a=this.selectedTemplate&&this.selectedTemplate!=="none"&&this.templates[this.selectedTemplate];e.style.display=a?"":"none"}selectTemplate(e){this.selectedTemplate=e||null,e?localStorage.setItem("ggn-upload-templator-selected",e):localStorage.removeItem("ggn-upload-templator-selected"),this.updateEditButtonVisibility(),e==="none"?this.showStatus("No template selected - auto-fill disabled"):e&&(this.showStatus(`Template "${e}" selected`),this.checkAndApplyToExistingTorrent(e))}applyTemplate(e,a){const t=this.templates[e];if(!t)return;const o=B(t.mask,a,t.greedyMatching!==!1);let s=0;Object.entries(t.fieldMappings).forEach(([r,i])=>{const l=V(r,this.config);if(l&&l.type==="radio"){const c=this.config.TARGET_FORM_SELECTOR?`${this.config.TARGET_FORM_SELECTOR} `:"",m=document.querySelectorAll(`${c}input[name="${r}"][type="radio"]`),b=O(String(i),o);m.forEach(d=>{d.hasAttribute("disabled")&&d.removeAttribute("disabled");const g=d.value===b;g!==d.checked&&(d.checked=g,g&&(d.dispatchEvent(new Event("input",{bubbles:!0})),d.dispatchEvent(new Event("change",{bubbles:!0})),s++))})}else if(l)if(l.hasAttribute("disabled")&&l.removeAttribute("disabled"),l.type==="checkbox"){let c;if(typeof i=="boolean")c=i;else{const m=O(String(i),o);c=/^(true|1|yes|on)$/i.test(m)}c!==l.checked&&(l.checked=c,l.dispatchEvent(new Event("input",{bubbles:!0})),l.dispatchEvent(new Event("change",{bubbles:!0})),s++)}else{const c=O(String(i),o);l.value!==c&&(l.value=c,l.dispatchEvent(new Event("input",{bubbles:!0})),l.dispatchEvent(new Event("change",{bubbles:!0})),s++)}}),s>0&&this.showStatus(`Template "${e}" applied to ${s} field(s)`)}async checkAndApplyToExistingTorrent(e){if(!e||e==="none")return;const a=this.config.TARGET_FORM_SELECTOR?document.querySelectorAll(`${this.config.TARGET_FORM_SELECTOR} input[type="file"]`):document.querySelectorAll('input[type="file"]');for(const t of a)if(t.files&&t.files[0]&&t.files[0].name.toLowerCase().endsWith(".torrent"))try{const o=await w.parseTorrentFile(t.files[0]);this.applyTemplate(e,o.name);return}catch(o){console.warn("Could not parse existing torrent file:",o)}}watchFileInputs(){(this.config.TARGET_FORM_SELECTOR?document.querySelectorAll(`${this.config.TARGET_FORM_SELECTOR} input[type="file"]`):document.querySelectorAll('input[type="file"]')).forEach(a=>{a.addEventListener("change",async t=>{if(!this.selectedTemplate||this.selectedTemplate==="none"||!t.target.files[0])return;const o=t.target.files[0];if(o.name.toLowerCase().endsWith(".torrent"))try{const s=await w.parseTorrentFile(o);this.applyTemplate(this.selectedTemplate,s.name)}catch(s){console.error("Error processing torrent file:",s),this.showStatus("Error processing torrent file","error")}})})}setupSubmitKeybinding(){document.addEventListener("keydown",e=>{if((e.ctrlKey||e.metaKey)&&e.key==="Enter"){e.preventDefault();const a=document.querySelector(this.config.TARGET_FORM_SELECTOR);if(a){const t=a.querySelector('input[type="submit"], button[type="submit"]')||a.querySelector('input[name*="submit"], button[name*="submit"]')||a.querySelector(".submit-btn, #submit-btn");t?(this.showStatus("Form submitted via Ctrl+Enter"),t.click()):(this.showStatus("Form submitted via Ctrl+Enter"),a.submit())}}})}showTemplateAndSettingsManager(){const e=document.createElement("div");e.className="gut-modal",e.innerHTML=Y(this),document.body.appendChild(e),e.querySelectorAll(".gut-tab-btn").forEach(i=>{i.addEventListener("click",l=>{const c=l.target.dataset.tab;e.querySelectorAll(".gut-tab-btn").forEach(m=>m.classList.remove("active")),l.target.classList.add("active"),e.querySelectorAll(".gut-tab-content").forEach(m=>m.classList.remove("active")),e.querySelector(`#${c}-tab`).classList.add("active")})});const a=e.querySelector("#setting-custom-selectors"),t=e.querySelector("#custom-selectors-preview-group"),o=e.querySelector("#custom-selectors-matched"),s=()=>{const l=a.value.trim().split(`
`).map(u=>u.trim()).filter(u=>u),c=this.config.CUSTOM_FIELD_SELECTORS;if(this.config.CUSTOM_FIELD_SELECTORS=l,l.length===0){t.style.display="none",this.config.CUSTOM_FIELD_SELECTORS=c;return}t.style.display="block";let m=[];const b=e.querySelector("#setting-form-selector").value.trim()||this.config.TARGET_FORM_SELECTOR,d=document.querySelector(b);l.forEach(u=>{try{const h=d?d.querySelectorAll(u):document.querySelectorAll(u);Array.from(h).forEach(v=>{const S=v.tagName.toLowerCase(),p=v.id,E=v.name||v.getAttribute("name"),_=v.className||"",x=D(v,this.config),T=v.id||v.name||`${S}-${Array.from(v.parentNode.children).indexOf(v)}`;m.find(y=>y.elementId===T)||m.push({elementId:T,element:v,tagName:S,id:p,name:E,classes:_,label:x,selector:u})})}catch(h){console.warn(`Invalid custom selector: ${u}`,h)}});const g=e.querySelector("#matched-elements-label");m.length===0?(g.textContent="Matched Elements:",o.innerHTML='<div class="gut-no-variables">No elements matched by custom selectors.</div>'):(g.textContent=`Matched Elements (${m.length}):`,o.innerHTML=m.map(u=>{const h=u.label||u.name||u.id||`${u.tagName}`,v=[u.tagName.toUpperCase(),u.id?`#${u.id}`:"",u.name?`name="${u.name}"`:"",u.classes?`.${u.classes.split(" ").filter(S=>S).join(".")}`:""].filter(S=>S).join(" ");return`
              <div class="gut-variable-item">
                <span class="gut-variable-name">${this.escapeHtml(h)}</span>
                <span class="gut-variable-value">${this.escapeHtml(v)}</span>
              </div>
            `}).join("")),this.config.CUSTOM_FIELD_SELECTORS=c};s(),a.addEventListener("input",s),e.querySelector("#setting-form-selector").addEventListener("input",s),e.querySelector("#ggn-infobox-link")?.addEventListener("click",i=>{i.preventDefault();const l=a.value.trim(),c=".infobox-input-holder input";if(!l.includes(c)){const m=l?`${l}
${c}`:c;a.value=m,s()}}),e.querySelector("#save-settings")?.addEventListener("click",()=>{this.saveSettings(e)}),e.querySelector("#reset-settings")?.addEventListener("click",()=>{confirm("Reset all settings to defaults? This will require a page reload.")&&this.resetSettings(e)}),e.querySelector("#delete-all-config")?.addEventListener("click",()=>{confirm(`\u26A0\uFE0F WARNING: This will permanently delete ALL GGn Upload Templator data including templates, settings, and selected template.

This action CANNOT be undone!

Are you sure you want to continue?`)&&this.deleteAllConfig()}),e.addEventListener("click",i=>{if(i.target===e){document.body.removeChild(e);return}const l=i.target.dataset.action,c=i.target.dataset.template;if(l&&c)switch(l){case"edit":document.body.removeChild(e),this.editTemplate(c);break;case"clone":this.cloneTemplate(c),this.refreshTemplateManager(e);break;case"delete":confirm(`Delete template "${c}"?`)&&(this.deleteTemplate(c),this.refreshTemplateManager(e));break}}),e.querySelector("#close-manager").addEventListener("click",()=>{document.body.removeChild(e)});const r=i=>{i.key==="Escape"&&document.body.contains(e)&&(document.body.removeChild(e),document.removeEventListener("keydown",r))};document.addEventListener("keydown",r)}saveSettings(e){const a=e.querySelector("#setting-form-selector").value.trim(),t=e.querySelector("#setting-submit-keybinding").checked,s=e.querySelector("#setting-custom-selectors").value.trim().split(`
`).map(l=>l.trim()).filter(l=>l),i=e.querySelector("#setting-ignored-fields").value.trim().split(`
`).map(l=>l.trim()).filter(l=>l);this.config={TARGET_FORM_SELECTOR:a||C.TARGET_FORM_SELECTOR,SUBMIT_KEYBINDING:t,CUSTOM_FIELD_SELECTORS:s.length>0?s:C.CUSTOM_FIELD_SELECTORS,IGNORED_FIELDS_BY_DEFAULT:i.length>0?i:C.IGNORED_FIELDS_BY_DEFAULT},localStorage.setItem("ggn-upload-templator-settings",JSON.stringify(this.config)),this.showStatus("Settings saved successfully! Reload the page for some changes to take effect.")}resetSettings(e){localStorage.removeItem("ggn-upload-templator-settings"),this.config={...C},e.querySelector("#setting-form-selector").value=this.config.TARGET_FORM_SELECTOR,e.querySelector("#setting-submit-keybinding").checked=this.config.SUBMIT_KEYBINDING,e.querySelector("#setting-custom-selectors").value=this.config.CUSTOM_FIELD_SELECTORS.join(`
`),e.querySelector("#setting-ignored-fields").value=this.config.IGNORED_FIELDS_BY_DEFAULT.join(`
`),this.showStatus("Settings reset to defaults! Reload the page for changes to take effect.")}deleteAllConfig(){localStorage.removeItem("ggn-upload-templator-templates"),localStorage.removeItem("ggn-upload-templator-selected"),localStorage.removeItem("ggn-upload-templator-hide-unselected"),localStorage.removeItem("ggn-upload-templator-settings"),this.templates={},this.selectedTemplate=null,this.hideUnselectedFields=!0,this.config={...C},this.updateTemplateSelector(),this.showStatus("All local configuration deleted! Reload the page for changes to take full effect.","success")}deleteTemplate(e){delete this.templates[e],localStorage.setItem("ggn-upload-templator-templates",JSON.stringify(this.templates)),this.selectedTemplate===e&&(this.selectedTemplate=null,localStorage.removeItem("ggn-upload-templator-selected")),this.updateTemplateSelector(),this.showStatus(`Template "${e}" deleted`)}cloneTemplate(e){const a=this.templates[e];if(!a)return;const t=`${e} (Clone)`;this.templates[t]={mask:a.mask,fieldMappings:{...a.fieldMappings},customUnselectedFields:a.customUnselectedFields?[...a.customUnselectedFields]:void 0},localStorage.setItem("ggn-upload-templator-templates",JSON.stringify(this.templates)),this.updateTemplateSelector(),this.showStatus(`Template "${t}" created`)}editTemplate(e){const a=this.templates[e];a&&this.showTemplateCreator(e,a)}refreshTemplateManager(e){const a=e.querySelector(".gut-template-list");a&&(a.innerHTML=W(this))}showStatus(e,a="success"){const t=document.querySelector(".gut-status");t&&t.remove();const o=document.createElement("div");o.className="gut-status",o.textContent=e,a==="error"&&o.classList.add("error"),document.body.appendChild(o),setTimeout(()=>{o.parentNode&&o.parentNode.removeChild(o)},3e3)}escapeHtml(e){const a=document.createElement("div");return a.textContent=e,a.innerHTML}}if($("Script loaded (readyState:",document.readyState,")"),document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{$("Initializing after DOMContentLoaded");try{new G}catch(n){console.error("Failed to initialize:",n)}});else{$("Initializing immediately (DOM already ready)");try{new G}catch(n){console.error("Failed to initialize:",n)}}})();