import 'dart:convert'; import 'package:flutter_qjs/flutter_qjs.dart'; import 'package:html/dom.dart'; import 'package:html/parser.dart'; import 'package:mangayomi/utils/extensions/dom_extensions.dart'; class JsDomSelector { late JavascriptRuntime runtime; JsDomSelector(this.runtime); final Map _elements = {}; int _elementKey = 0; void init() { runtime.onMessage('get_doc_element', (dynamic args) { final input = args[0]; final type = args[1]; final doc = parse(input); final element = switch (type) { 'body' => doc.body, 'documentElement' => doc.documentElement, 'head' => doc.head, _ => doc.parent }; _elementKey++; _elements[_elementKey] = element; return _elementKey; }); runtime.onMessage('get_doc_string', (dynamic args) { final input = args[0]; final type = args[1]; final doc = parse(input); final res = switch (type) { 'text' => doc.text, _ => doc.outerHtml }; return res ?? ""; }); runtime.onMessage('get_element_string', (dynamic args) { final type = args[0]; final key = args[1]; final element = _elements[key]; final res = switch (type) { 'text' => element?.text, 'innerHtml' => element?.innerHtml, 'outerHtml' => element?.outerHtml, 'className' => element?.className, 'localName' => element?.localName, 'namespaceUri' => element?.namespaceUri, 'getSrc' => element?.getSrc, 'getImg' => element?.getImg, 'getHref' => element?.getHref, _ => element?.getDataSrc }; return res ?? ""; }); runtime.onMessage('doc_select_first', (dynamic args) { final input = args[0]; final selector = args[1]; _elementKey++; _elements[_elementKey] = parse(input).selectFirst(selector); return _elementKey; }); runtime.onMessage('ele_selectFirst', (dynamic args) { final selector = args[0]; final key = args[1]; _elementKey++; _elements[_elementKey] = _elements[key]?.selectFirst(selector); return _elementKey; }); runtime.onMessage('ele_element_sibling', (dynamic args) { final type = args[0]; final key = args[1]; final ele = _elements[key]; final element = switch (type) { 'nextElementSibling' => ele?.nextElementSibling, _ => ele?.previousElementSibling }; _elementKey++; _elements[_elementKey] = element; return _elementKey; }); runtime.onMessage('ele_attr', (dynamic args) { final attr = args[0]; final key = args[1]; return _elements[key]?.attr(attr) ?? ""; }); runtime.onMessage('doc_attr', (dynamic args) { final input = args[0]; final attr = args[1]; return parse(input).attr(attr) ?? ""; }); runtime.onMessage('ele_has_attr', (dynamic args) { final attr = args[0]; final key = args[1]; return _elements[key]?.hasAtr(attr) ?? false; }); runtime.onMessage('doc_has_attr', (dynamic args) { final input = args[0]; final attr = args[1]; return parse(input).hasAtr(attr); }); runtime.onMessage('doc_xpath_first', (dynamic args) { final input = args[0]; final xpath = args[1]; return parse(input).xpathFirst(xpath) ?? ""; }); runtime.onMessage('ele_xpathFirst', (dynamic args) { final xpath = args[0]; final key = args[1]; return _elements[key]?.xpathFirst(xpath) ?? ""; }); runtime.onMessage('doc_xpath', (dynamic args) { final input = args[0]; final xpath = args[1]; return jsonEncode(parse(input).xpath(xpath)); }); runtime.onMessage('ele_xpath', (dynamic args) { final xpath = args[0]; final key = args[1]; return jsonEncode(_elements[key]?.xpath(xpath)); }); runtime.onMessage('doc_get_elements_by', (dynamic args) { final input = args[0]; final type = args[1]; final name = args[2]; final doc = parse(input); final elements = switch (type) { 'children' => doc.children, 'getElementsByTagName' => doc.getElementsByTagName(name), _ => doc.getElementsByClassName(name) }; List elementKeys = []; for (var element in elements) { _elementKey++; _elements[_elementKey] = element; elementKeys.add(_elementKey); } return jsonEncode(elementKeys); }); runtime.onMessage('ele_get_elements_by', (dynamic args) { final type = args[0]; final name = args[1]; final key = args[2]; final element = _elements[key]; final elements = switch (type) { 'children' => element?.children, 'getElementsByTagName' => element?.getElementsByTagName(name), _ => element?.getElementsByClassName(name) }; List elementKeys = []; for (var element in elements ?? []) { _elementKey++; _elements[_elementKey] = element; elementKeys.add(_elementKey); } return jsonEncode(elementKeys); }); runtime.onMessage('doc_get_element_by_id', (dynamic args) { final input = args[0]; final id = args[1]; _elementKey++; _elements[_elementKey] = parse(input).getElementById(id); return _elementKey; }); runtime.onMessage('doc_select', (dynamic args) { final input = args[0]; final selector = args[1]; final elements = parse(input).select(selector); List elementKeys = []; for (var element in elements ?? []) { _elementKey++; _elements[_elementKey] = element; elementKeys.add(_elementKey); } return jsonEncode(elementKeys); }); runtime.onMessage('ele_select', (dynamic args) { final selector = args[0]; final key = args[1]; final elements = _elements[key]?.select(selector); List elementKeys = []; for (var element in elements ?? []) { _elementKey++; _elements[_elementKey] = element; elementKeys.add(_elementKey); } return jsonEncode(elementKeys); }); runtime.evaluate(''' class Document { constructor(html) { this.html = html; } getElement(type) { const key = sendMessage( "get_doc_element", JSON.stringify([this.html, type]) ); return new Element(key); } get body() { return this.getElement('body'); } get documentElement() { return this.getElement('documentElement'); } get head() { return this.getElement('head'); } get parent() { return this.getElement('parent'); } getString(type) { return sendMessage( "get_doc_string", JSON.stringify([this.html, type])); } get text() { return this.getString('text'); } get outerHtml() { return this.getString('outerHtml'); } selectFirst(selector) { const key = sendMessage( "doc_select_first", JSON.stringify([this.html, selector]) ); return new Element(key); } select(selector) { let elements = []; JSON.parse( sendMessage("doc_select", JSON.stringify([this.html, selector])) ).forEach((key) => { elements.push(new Element(key)); }); return elements; } xpathFirst(xpath) { return sendMessage( "doc_xpath_first", JSON.stringify([this.html, xpath]) ); } xpath(xpath) { return JSON.parse(sendMessage( "doc_xpath", JSON.stringify([this.html, xpath])) ); } getElementsListBy(type, name) { name = name || ''; let elements = []; JSON.parse(sendMessage( "doc_get_elements_by", JSON.stringify([this.html, type, name])) ).forEach((key) => { elements.push(new Element(key)); }); return elements; } get children() { return this.getElementsListBy('children'); } getElementsByTagName(name) { return this.getElementsListBy('getElementsByTagName', name); } getElementsByClassName(name) { return this.getElementsListBy('getElementsByClassName', name); } getElementById(id) { const key = sendMessage( "doc_get_element_by_id", JSON.stringify([this.html, id]) ); return new Element(key); } attr(attr) { return sendMessage( "doc_attr", JSON.stringify([this.html, attr]) ); } hasAttr(attr) { return sendMessage( "doc_has_attr", JSON.stringify([this.html, attr]) ); } } class Element { constructor(key) { this.key = key; } getString(type) { return sendMessage( "get_element_string", JSON.stringify([type, this.key]) ); } get text() { return this.getString("text"); } get outerHtml() { return this.getString("outerHtml"); } get innerHtml() { return this.getString("innerHtml"); } get className() { return this.getString("className"); } get localName() { return this.getString("localName"); } get namespaceUri() { return this.getString("namespaceUri"); } get getSrc() { return this.getString("getSrc"); } get getImg() { return this.getString("getImg"); } get getHref() { return this.getString("getHref"); } get getDataSrc() { return this.getString("getDataSrc"); } getElementSibling(type) { const key = sendMessage( "ele_element_sibling", JSON.stringify([type, this.key]) ); return new Element(key); } get previousElementSibling() { return this.getElementSibling("previousElementSibling"); } get nextElementSibling() { return this.getElementSibling("nextElementSibling"); } getElementsListBy(type, name) { name = name || ''; let elements = []; JSON.parse(sendMessage( "ele_get_elements_by", JSON.stringify([type, name, this.key])) ).forEach((key) => { elements.push(new Element(key)); }); return elements; } get children() { return this.getElementsListBy('children'); } getElementsByTagName(name) { return this.getElementsListBy('getElementsByTagName', name); } getElementsByClassName(name) { return this.getElementsListBy('getElementsByClassName', name); } xpath(xpath) { return JSON.parse(sendMessage( "xpath", JSON.stringify([xpath, this.key])) ); } attr(attr) { return sendMessage( "ele_attr", JSON.stringify([attr, this.key]) ); } xpathFirst(xpath) { return sendMessage( "xpathFirst", JSON.stringify([xpath, this.key]) ); } selectFirst(selector) { const key = sendMessage( "ele_selectFirst", JSON.stringify([selector, this.key]) ); return new Element(key); } select(selector) { let elements = []; JSON.parse( sendMessage("ele_select", JSON.stringify([selector, this.key])) ).forEach((key) => { elements.push(new Element(key)); }); return elements; } hasAttr(attr) { return sendMessage( "ele_has_attr", JSON.stringify([this.html, attr]) ); } } '''); } }