89 lines
5.6 KiB
JavaScript
89 lines
5.6 KiB
JavaScript
// In-memory read-only DOM primitives for executing the actual observer strings.
|
|
// No browser, network, module imports in the observer context or platform state.
|
|
const vm = require('node:vm');
|
|
let input = '';
|
|
process.stdin.setEncoding('utf8');
|
|
process.stdin.on('data', d => input += d);
|
|
process.stdin.on('end', () => {
|
|
const { code, arg, scenario = 'note', observer = 'source' } = JSON.parse(input);
|
|
class El {
|
|
constructor(tag, attrs = {}, text = '', children = [], top = 10) {
|
|
this.tagName = tag.toUpperCase(); this.attrs = attrs; this.id = attrs.id || '';
|
|
this.ownText = text; this.children = children; this.top = top;
|
|
this.scrollTop = 0; this.scrollHeight = this.clientHeight = 600;
|
|
for (const child of children) child.parentElement = this;
|
|
}
|
|
get href() { return this.attrs.href ? new URL(this.attrs.href, location.href).href : ''; }
|
|
get innerText() { return [this.ownText, ...this.children.map(c => c.innerText)].filter(Boolean).join('\n'); }
|
|
getBoundingClientRect() { return { left: 0, right: 300, top: this.top, bottom: this.top + 35, width: 300, height: 35 }; }
|
|
getAttribute(k) { return this.attrs[k] ?? null; }
|
|
matches(query) { return query.split(',').some(q => match(this, q.trim())); }
|
|
closest(query) { let node = this; while (node) { if (node.matches(query)) return node; node = node.parentElement; } return null; }
|
|
contains(node) { return node === this || this.children.some(c => c.contains(node)); }
|
|
querySelectorAll(query) { return flat(this).slice(1).filter(n => n.matches(query)); }
|
|
querySelector(query) { return this.querySelectorAll(query)[0] || null; }
|
|
}
|
|
function flat(n) { return [n, ...n.children.flatMap(flat)]; }
|
|
function atom(n, raw) {
|
|
let q = raw;
|
|
const attrs = [...q.matchAll(/\[([\w-]+)(\*=|=)?["']?([^\]"']*)["']?\]/g)];
|
|
for (const [, k, op, value] of attrs) {
|
|
const actual = n.attrs[k];
|
|
if (actual === undefined || op === '=' && actual !== value || op === '*=' && !actual.includes(value)) return false;
|
|
}
|
|
q = q.replace(/\[[^\]]+\]/g, '');
|
|
const id = q.match(/#([\w-]+)/); if (id && n.id !== id[1]) return false;
|
|
const classes = [...q.matchAll(/\.([\w-]+)/g)].map(x => x[1]);
|
|
if (classes.some(c => !(n.attrs.class || '').split(/\s+/).includes(c))) return false;
|
|
const tag = q.match(/^[A-Za-z]+/); return !tag || tag[0].toUpperCase() === n.tagName;
|
|
}
|
|
function match(n, raw) {
|
|
const parts = raw.trim().split(/\s+/); let cur = n;
|
|
if (!atom(cur, parts.pop())) return false;
|
|
while (parts.length) {
|
|
const part = parts.pop(); cur = cur.parentElement;
|
|
while (cur && !atom(cur, part)) cur = cur.parentElement;
|
|
if (!cur) return false;
|
|
}
|
|
return true;
|
|
}
|
|
const location = { href: 'https://www.xiaohongshu.com/explore/' + 'a'.repeat(24) };
|
|
const pa = 'https://www.xiaohongshu.com/user/profile/' + 'b'.repeat(24);
|
|
const pb = 'https://www.xiaohongshu.com/user/profile/' + 'c'.repeat(24);
|
|
const author = new El('a', {id:'author-avatar',href:pa,class:'avatar'}, '', [new El('img')]);
|
|
const authorName = new El('a', {id:'author-name',href:pa,class:'name'}, '正文作者');
|
|
const header = new El('div', {class:'note-header'}, '', [author, authorName]);
|
|
const desc = new El('div', {id:'detail-desc'}, '本人正在找装修公司');
|
|
const replyAuthor = new El('a', {id:'reply-author',href:pa,class:'name'}, '回复者');
|
|
const replyContent = new El('div', {id:'reply-content',class:'content'}, '回复本人需求');
|
|
const reply = new El('div', {id:'reply',class:'comment-item-sub'}, '', [new El('div',{class:'author-wrapper'},'', [replyAuthor]),replyContent]);
|
|
const commentAuthor = new El('a', {id:'comment-author',href:pb,class:'name'}, '评论作者');
|
|
const commentContent = new El('div', {id:'comment-content',class:'content'}, '评论本人的需求');
|
|
const comment = new El('div', {id:'comment',class:'comment-item','data-comment-id':'actual-comment'}, '', [new El('div',{class:'author-wrapper'},'', [commentAuthor]),commentContent, reply]);
|
|
const commentArea = new El('div', {id:'discussion',class:'comments-container'}, '', [comment]);
|
|
const counter = new El('span',{id:'comment-entry',class:'chat-wrapper'},'101');
|
|
const root = new El('div',{id:'noteContainer'},'',[header,desc,commentArea,counter]);
|
|
root.getBoundingClientRect = () => ({left:0,right:350,top:0,bottom:900,width:350,height:900});
|
|
if (scenario === 'ambiguous') {
|
|
const other = new El('a',{id:'other-avatar',href:pb,class:'avatar'},'', [new El('img')]); other.parentElement=header;header.children.push(other);
|
|
}
|
|
if (scenario === 'below_viewport') for (const el of flat(commentArea)) el.top=800;
|
|
const body = new El('body',{},'',[root]);
|
|
const html = new El('html',{},'',[body]); html.scrollHeight=900;
|
|
const document = {
|
|
body, documentElement:html, scrollingElement:html,
|
|
querySelectorAll(q) { return flat(html).filter(n => n.matches(q)); },
|
|
querySelector(q) { return this.querySelectorAll(q)[0] || null; },
|
|
createTreeWalker(el) {
|
|
const nodes=flat(el).filter(n=>n.ownText).map(n=>({nodeValue:n.ownText,parentElement:n}));let i=0;
|
|
return {nextNode(){return nodes[i++] || null;}};
|
|
},
|
|
createRange() { let node; return {selectNodeContents(n){node=n;},getClientRects(){return [node.parentElement.getBoundingClientRect()];}}; }
|
|
};
|
|
const context={document,location,URL,Set,Array,Math,JSON,NodeFilter:{SHOW_TEXT:4},innerHeight:600,innerWidth:400,
|
|
getComputedStyle(){return {display:'block',visibility:'visible',opacity:'1',overflowY:'visible'};}};
|
|
const fn=vm.runInNewContext('(' + code + ')',context,{timeout:1000});
|
|
const result=fn(arg);
|
|
process.stdout.write(JSON.stringify(result));
|
|
});
|