class ThiefService { base64ToFile(base64: string, filename: string, mimeType: string): File { // Nếu có tiền tố "data:image/xxx;base64," thì cắt bỏ const pureBase64 = base64.includes(",") ? base64.split(",")[1] : base64; const byteString = atob(pureBase64); const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } const blob = new Blob([ab], { type: mimeType }); return new File([blob], filename, { type: mimeType }); } clickByPoint(el: Element) { const rect: DOMRect = el.getBoundingClientRect(); const x: number = rect.left + rect.width / 2; const y: number = rect.top + rect.height / 2; const target: Element | null = document.elementFromPoint(x, y); if (target) { target.dispatchEvent( new MouseEvent("click", { bubbles: true, cancelable: true, view: window, }) ); } } async getElementByXPath( xpath: string, retryCount: number = 3, delay: number = 400 ): Promise { return new Promise((resolve) => { let attempts = 0; const tryFind = () => { const el: Node | null = document.evaluate( xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; if (el instanceof HTMLElement) { resolve(el); return; } attempts++; if (attempts < retryCount) { setTimeout(tryFind, delay); } else { resolve(null); } }; tryFind(); }); } async imageUrlToBase64(url: string): Promise { const response = await fetch(url); const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === "string") { // Kết quả là data URL (base64) resolve(reader.result.split(",")[1]); } else { reject("Không thể đọc dữ liệu ảnh"); } }; reader.onerror = reject; reader.readAsDataURL(blob); }); } getImageExtension(url: string): string | null { try { const pathname = new URL(url).pathname; // Lấy phần path từ URL const match = pathname.match(/\.([a-zA-Z0-9]+)$/); // Tìm đuôi file return match ? match[1].toLowerCase() : null; } catch { // Nếu url không hợp lệ thì fallback sang split const parts = url.split("?"); const path = parts[0]; const match = path.match(/\.([a-zA-Z0-9]+)$/); return match ? match[1].toLowerCase() : null; } } imageLocalToBase64(relativePath: string): Promise { return new Promise((resolve, reject) => { try { // Lấy URL đầy đủ trong extension const url = chrome.runtime.getURL(`${relativePath}`); // Fetch file fetch(url) .then((res) => res.blob()) .then((blob) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); // base64 reader.onerror = reject; reader.readAsDataURL(blob); }) .catch(reject); } catch (err) { reject(err); } }); } scrollToElement(el: HTMLElement, behavior: ScrollBehavior = "smooth") { if (!el) return; el.scrollIntoView({ behavior, // "smooth" hoặc "auto" block: "center", // Đưa phần tử ra giữa màn hình inline: "nearest", }); } getElementPointCoores(el: HTMLElement): { x: number; y: number } | null { if (!el) return null; const rect = el.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; return { x, y }; } setInputValue(el: HTMLInputElement | HTMLTextAreaElement, value: string) { if (!el) return; // Gán giá trị trực tiếp el.value = value; // Tạo và dispatch sự kiện input el.dispatchEvent(new Event("input", { bubbles: true })); // Tạo và dispatch sự kiện change (nếu cần) el.dispatchEvent(new Event("change", { bubbles: true })); } writeToInput = async (value: string, xpath: string) => { const el = (await this.getElementByXPath(xpath)) as HTMLInputElement; if (!el) throw new Error("Xpath is not found"); // Scroll to EL this.scrollToElement(el); this.clickByPoint(el); this.setInputValue(el, value); }; pressEnter(el: HTMLElement) { if (!el) { throw new Error("Textarea not found:", el); } el.focus(); // Chuỗi sự kiện bàn phím ["keydown", "keypress", "keyup"].forEach((type) => { el.dispatchEvent( new KeyboardEvent(type, { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true, cancelable: true, }) ); }); } } export const thiefService = new ThiefService();