Tải jasper/xml tự động
// ==UserScript==
// @name Down Jasper XML Tool
// @namespace http://vnpt-his/
// @version 1.0
// @description Tải jasper/xml tự động
// @author khoanm.vtu
// @match *://*/vnpthis/main/manager.jsp*
// @icon https://www.google.com/s2/favicons?sz=64&domain=vncare.vn
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const TOOL_ID = 'jasper-upl-tool';
const PANEL_STATE_KEY = 'jasper-upl-tool-collapsed';
const TARGET_PATH = '/vnpthis/main/manager.jsp?func=../danhmuc/UTILS_UPL_TLEE';
const PENDING_FILE_KEY = 'jasper-upl-pending-file';
function isUtilsUplPage() {
return /func=\.\.\/danhmuc\/UTILS_UPL_TLEE/i.test(window.location.href);
}
function normalize(text) {
return String(text || '').trim();
}
function debounce(fn, ms) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(null, args), ms);
};
}
function getTargetUrl(rawInput) {
const base = `${window.location.origin}${TARGET_PATH}`;
const val = normalize(rawInput);
return `${base}#${encodeURIComponent(val)}`;
}
function getHashText() {
const raw = String(window.location.hash || '').replace(/^#/, '').trim();
if (!raw) return '';
try {
return decodeURIComponent(raw).trim();
} catch (e) {
return raw;
}
}
function savePendingFileText(text) {
const val = normalize(text);
if (!val) return;
localStorage.setItem(PENDING_FILE_KEY, val);
}
function getPendingFileText() {
const fromHash = getHashText();
if (fromHash) return fromHash;
return normalize(localStorage.getItem(PENDING_FILE_KEY) || '');
}
function clearDoneCache() {
localStorage.removeItem(PENDING_FILE_KEY);
localStorage.removeItem(PANEL_STATE_KEY);
}
function pickXmlValue(selectEl) {
if (!selectEl) return '';
const options = Array.from(selectEl.options || []);
if (options.some((opt) => opt.value === 'EMRXML')) return 'EMRXML';
const xmlOpt = options.find((opt) => /xml/i.test(String(opt.value || '')) || /xml/i.test(String(opt.text || '')));
return xmlOpt ? String(xmlOpt.value || '') : '';
}
function findElementByIdDeep(id, rootDoc = document) {
if (!id || !rootDoc) return null;
const direct = rootDoc.getElementById(id);
if (direct) return direct;
const frames = Array.from(rootDoc.querySelectorAll('iframe, frame'));
for (const frame of frames) {
try {
const childDoc = frame.contentDocument || frame.contentWindow?.document;
if (!childDoc) continue;
const nested = findElementByIdDeep(id, childDoc);
if (nested) return nested;
} catch (e) {
// ignore cross-origin frame
}
}
return null;
}
function dispatchValueEvents(el) {
if (!el) return;
const view = el.ownerDocument?.defaultView || window;
el.dispatchEvent(new view.Event('input', { bubbles: true }));
el.dispatchEvent(new view.KeyboardEvent('keyup', { key: 'a', bubbles: true }));
el.dispatchEvent(new view.KeyboardEvent('keydown', { key: 'a', bubbles: true }));
el.dispatchEvent(new view.Event('change', { bubbles: true }));
}
function findFileInputDeep(preferredId) {
if (preferredId) {
const preferred = findElementByIdDeep(preferredId);
if (preferred) return preferred;
}
return (
findElementByIdDeep('fileDownload') ||
findElementByIdDeep('fileXMLDownload')
);
}
function fillFileNameInput(fileText, preferredId, attempt = 0) {
const inputEl = findFileInputDeep(preferredId);
if (!inputEl) {
if (attempt < 30) {
setTimeout(() => fillFileNameInput(fileText, preferredId, attempt + 1), 300);
}
return;
}
let val = String(fileText || '').trim();
const isXmlInput = String(inputEl.id || '').toLowerCase() === 'filexmldownload';
if (isXmlInput && val && !/\.xml$/i.test(val)) {
val = `${val}.xml`;
}
const view = inputEl.ownerDocument?.defaultView || window;
const valueSetter = Object.getOwnPropertyDescriptor(view.HTMLInputElement.prototype, 'value')?.set;
const setValue = (nextValue) => {
if (valueSetter) {
valueSetter.call(inputEl, nextValue);
} else {
inputEl.value = nextValue;
}
};
inputEl.focus();
setValue(val);
dispatchValueEvents(inputEl);
inputEl.blur();
}
function clearUrlHash() {
if (window.location.hash) {
history.replaceState(null, null, window.location.pathname + window.location.search);
}
}
function clickDownloadBtn(preferredIds, attempt = 0) {
const ids = Array.isArray(preferredIds) ? preferredIds : [preferredIds];
let btn = null;
for (const id of ids) {
btn = findElementByIdDeep(id);
if (btn) break;
}
if (!btn) {
if (attempt < 30) {
setTimeout(() => clickDownloadBtn(ids, attempt + 1), 300);
}
return;
}
if (btn.disabled) {
if (attempt < 30) {
setTimeout(() => clickDownloadBtn(ids, attempt + 1), 250);
}
return;
}
btn.click();
// Xóa hash và localStorage NGAY LẬP TỨC sau khi click, tránh lặp lại khi reload
setTimeout(() => {
clearUrlHash();
clearDoneCache();
}, 200);
}
function getAndClearPendingFileText() {
const fromHash = getHashText();
let fileText = '';
if (fromHash) fileText = fromHash;
else fileText = normalize(localStorage.getItem(PENDING_FILE_KEY) || '');
// Kiểm tra file vừa xử lý trong sessionStorage để tránh lặp khi reload
const lastFile = sessionStorage.getItem('jasper-upl-last-file') || '';
if (fileText && fileText === lastFile) {
// Đã xử lý file này trong phiên, không làm lại nữa
clearUrlHash();
localStorage.removeItem(PENDING_FILE_KEY);
return '';
}
// Lưu lại file vừa xử lý vào sessionStorage
if (fileText) {
sessionStorage.setItem('jasper-upl-last-file', fileText);
}
// Xóa hash và localStorage NGAY LẬP TỨC
clearUrlHash();
localStorage.removeItem(PENDING_FILE_KEY);
return fileText;
}
function selectPathChooseByHash(attempt = 0) {
if (!isUtilsUplPage()) return;
// Lấy fileText từ hash/localStorage, xóa ngay để tránh lặp khi reload
const fileText = getAndClearPendingFileText();
if (!fileText) return;
const selectEl = findElementByIdDeep('cboPATH_CHOOSE');
if (!selectEl) {
if (attempt < 30) {
setTimeout(() => selectPathChooseByHash(attempt + 1), 300);
}
return;
}
const isJasper = /\.jasper$/i.test(fileText);
const desiredValue = isJasper ? 'JASPER' : pickXmlValue(selectEl);
if (!desiredValue) return;
selectEl.value = desiredValue;
dispatchValueEvents(selectEl);
const preferredInputId = isJasper ? 'fileDownload' : 'fileXMLDownload';
setTimeout(() => fillFileNameInput(fileText, preferredInputId), 120);
if (isJasper) {
setTimeout(() => clickDownloadBtn(['downloadBtn']), 450);
} else {
setTimeout(() => clickDownloadBtn(['downloadXMLBtn', 'downloadBtn']), 450);
}
}
function placePanel(panel) {
if (!panel) return;
const PREDEFINED_ORDER = [
'nv-import-panel',
'jasper-upl-tool',
'rpt-id-finder-tool',
'jasper-upl-telegram-tool',
'tool-phieuin-network',
'nv-get-sql-tool',
];
let currentTop = 12;
for (const id of PREDEFINED_ORDER) {
const el = document.getElementById(id);
if (!el) continue;
if (el === panel) break;
const style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') continue;
const rect = el.getBoundingClientRect();
if (rect.height > 0) {
currentTop += Math.round(rect.height + 8);
}
}
const vh = window.innerHeight || document.documentElement.clientHeight || 0;
const panelRect = panel.getBoundingClientRect();
const panelHeight = Math.max(40, Math.round(panelRect.height || 100));
const maxTop = Math.max(12, vh - panelHeight - 12);
panel.style.top = `${Math.min(currentTop, maxTop)}px`;
panel.style.right = '12px';
panel.style.left = 'auto';
}
function createTool() {
if (document.getElementById(TOOL_ID)) return;
const style = document.createElement('style');
style.textContent = `
#${TOOL_ID} {
position: fixed;
top: 12px;
right: 12px;
width: 320px;
z-index: 999999;
background: #fff;
border: 1px solid #c9c9c9;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,.15);
font-family: Arial, sans-serif;
font-size: 13px;
padding: 10px;
transition: width 0.18s ease, padding 0.18s ease;
overflow: hidden;
}
#${TOOL_ID}.collapsed {
width: 42px;
padding: 10px 6px;
max-height: 48px;
}
#${TOOL_ID} .jasper-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
font-weight: 700;
margin-bottom: 8px;
gap: 8px;
}
#${TOOL_ID} .jasper-toggle {
width: 28px;
min-width: 28px;
height: 28px;
padding: 0;
line-height: 1;
border: 1px solid #c9c9c9;
border-radius: 6px;
background: #f7f7f7;
cursor: pointer;
font-weight: 700;
}
#${TOOL_ID} .jasper-body {
display: block;
}
#${TOOL_ID}.collapsed .jasper-body {
display: none;
}
#${TOOL_ID}.collapsed .jasper-header {
justify-content: center;
margin-bottom: 0;
}
#${TOOL_ID}.collapsed .jasper-title {
display: none;
}
#${TOOL_ID}.collapsed .jasper-toggle {
width: 44px;
min-width: 44px;
font-size: 11px;
}
#${TOOL_ID} .jasper-input,
#${TOOL_ID} .jasper-find {
width: 100%;
box-sizing: border-box;
padding: 7px;
font-size: 12px;
}
#${TOOL_ID} .jasper-input {
border: 1px solid #c9c9c9;
border-radius: 6px;
}
#${TOOL_ID} .jasper-find {
margin-top: 8px;
border: none;
border-radius: 6px;
background: #4b5563;
color: #fff;
cursor: pointer;
font-weight: 700;
}
#${TOOL_ID} .jasper-status {
margin-top: 8px;
color: #374151;
font-size: 12px;
word-break: break-all;
}
`;
const panel = document.createElement('div');
panel.id = TOOL_ID;
panel.classList.toggle('collapsed', localStorage.getItem(PANEL_STATE_KEY) === '1');
panel.innerHTML = `
<div class="jasper-header">
<span class="jasper-title">Tải Jasper/XML</span>
<button class="jasper-toggle" type="button" aria-label="Thu gọn/mở rộng panel">◀</button>
</div>
<div class="jasper-body">
<input class="jasper-input" type="text" placeholder="Nhập tên file jasper/xml.." />
<button class="jasper-find" type="button">Tìm</button>
<div class="jasper-status">Sẵn sàng.</div>
</div>
`;
document.head.appendChild(style);
document.body.appendChild(panel);
placePanel(panel);
const input = panel.querySelector('.jasper-input');
const btnFind = panel.querySelector('.jasper-find');
const btnToggle = panel.querySelector('.jasper-toggle');
const status = panel.querySelector('.jasper-status');
function setCollapsed(collapsed) {
panel.classList.toggle('collapsed', collapsed);
btnToggle.textContent = collapsed ? '▶JP' : '◀';
btnToggle.title = collapsed ? 'Mở Down Jasper XML Tool' : 'Thu gọn Down Jasper XML Tool';
localStorage.setItem(PANEL_STATE_KEY, collapsed ? '1' : '0');
}
function goSearch() {
const val = normalize(input.value);
const targetUrl = getTargetUrl(val);
savePendingFileText(val);
status.textContent = `Đang mở: ${targetUrl}`;
window.open(targetUrl, '_blank');
}
btnFind.addEventListener('click', goSearch);
btnToggle.addEventListener('click', () => {
setCollapsed(!panel.classList.contains('collapsed'));
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
goSearch();
}
});
const debouncedPlace = debounce(() => placePanel(panel), 100);
window.addEventListener('resize', debouncedPlace);
['nv-import-panel', 'jasper-upl-tool', 'rpt-id-finder-tool', 'tool-phieuin-network', 'jasper-upl-telegram-tool'].forEach((id) => {
const target = document.getElementById(id);
if (!target) return;
const observer = new MutationObserver(debouncedPlace);
observer.observe(target, { attributes: true, attributeFilter: ['class', 'style'] });
});
const bodyObserver = new MutationObserver(debouncedPlace);
bodyObserver.observe(document.body, { childList: true, subtree: true });
setCollapsed(panel.classList.contains('collapsed'));
}
function initWhenReady() {
if (document.body && document.head) {
createTool();
selectPathChooseByHash();
} else {
setTimeout(initWhenReady, 200);
}
}
initWhenReady();
})();