WykopObserve

API WykopObserve dla dodatków na wykop.pl - daje API, które robi pętlę z callbackiem, po wpisach z filtrowaniem wedłów różnych kryteriów.

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greatest.deepsurf.us/scripts/437595/1002297/WykopObserve.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// observe module start

const {wykopObserve, filterGroups, loginUser, getAdjacentEls} = (function () {

	const headerProfileElement = "header-profile-element";
	const linkPageAuthorElement = "link-page-author-element"; // element osoby która dodała ten link
	
	const mikroblogPageComment = "mikroblog-page-comment";
	const mikroblogPageSubComment = "mikroblog-page-sub-comment";
	const linkPageComment = "link-page-comment";
	const linkPageSubComment = "link-page-sub-comment";
	const wpisPageComment = "wpis-page-comment";
	const wpisPageSubComment = "wpis-page-sub-comment";
	const tagPageComment = "tag-page-comment";
	const tagPageSubComment = "tag-page-sub-comment";
	const mojPageComment = "moj-page-comment";
	const mojPageSubComment = "moj-page-sub-comment";
	
	const mikroblogPageWriteElement = "mikroblog-page-write-element";
	const linkPageWriteElement = "link-page-write-element";
	const wpisPageWriteElement = "wpis-page-write-element";
	const tagPageWriteElement = "tag-page-write-element";
	const ludziePageWriteElement = "ludzie-page-write-element";
	const mojPageWriteElement = "moj-page-write-element";
	
	const ludziePageLinkSubComment = "ludzie-page-link-sub-comment";
	const ludziePageWpisComment = "ludzie-page-wpis-comment";
	const ludziePageWpisSubComment = "ludzie-page-wpis-sub-comment";
	
	const glownaPageComment = "glowna-page-comment";
	
	const mikroblogLinkWpisGlownaTagMojComment = [
		mikroblogPageComment,
		linkPageComment,
		wpisPageComment,
		glownaPageComment,
		tagPageComment,
		mojPageComment,
	];
	
	const mikroblogLinkWpisTagSubMojComment = [
		mikroblogPageSubComment,
		linkPageSubComment,
		wpisPageSubComment,
		tagPageSubComment,
		mojPageSubComment,
	];
	
	const mikroblogLinkWpisGlownaTagMojCommentOrSubComment = [
		...mikroblogLinkWpisGlownaTagMojComment,
		...mikroblogLinkWpisTagSubMojComment,
	];
	
	const ludziePageCommentOrSubComment = [
		ludziePageLinkSubComment,
		ludziePageWpisComment,
		ludziePageWpisSubComment
	]
	
	const writeElement = [
		mikroblogPageWriteElement,
		linkPageWriteElement,
		wpisPageWriteElement,
		tagPageWriteElement,
		ludziePageWriteElement,
		mojPageWriteElement,
	];
	
	const places = [
		headerProfileElement,
		linkPageAuthorElement,
		
		mikroblogPageComment,
		mikroblogPageSubComment,
		linkPageComment,
		linkPageSubComment,
		wpisPageComment,
		wpisPageSubComment,
		tagPageComment,
		tagPageSubComment,
		mojPageComment,
		mojPageSubComment,
		
		mikroblogPageWriteElement,
		linkPageWriteElement,
		wpisPageWriteElement,
		tagPageWriteElement,
		ludziePageWriteElement,
		mojPageWriteElement,
		
		
		ludziePageLinkSubComment,
		ludziePageWpisComment,
		ludziePageWpisSubComment,
		
		glownaPageComment,
		
		
		//"chat-page-comment",
		//"chat-page-write-element",
		
		//"own-page-author-element", // element osoby która utworzyła własną treść w edytorze wykopowym
	];
	
	const otherFilter = "other";
	
	
	const getWykopPageType = ()=>location.pathname.split("/")[1];
	const hasSub = (profileEl)=>profileEl.parentElement.parentElement.parentElement.classList.contains("sub");
	const getSub = (profileEl)=>hasSub(profileEl)?"-sub":"";
	
	
	function getProfiles (scopeEl, placesFilter) {
		const profiles = [];
		
		const loggedUserEl = document.querySelector('.logged-user > a');
		if (loggedUserEl && placesFilter.includes(headerProfileElement)) {
			profiles.push(loggedUserEl);
		}
		
		const userCardEl = document.querySelector('.usercard > a');
		if (getWykopPageType() === "link" && userCardEl && placesFilter.includes(linkPageAuthorElement)) {
			profiles.push(userCardEl);
		}
		
		// UWAGA: to filtruje tylko wstępnie. później filtruje się pojedynczo w observer
		
		//if (getWykopPageType() === "mikroblog" && placesArr.includes(mikroblogPageComment)) {
			//profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entry"] .profile')));
		//}
		//if (getWykopPageType() === "mikroblog" && placesFilter.includes(mikroblogPageSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entrycomment"] .profile')));
		//}
		if (getWykopPageType() === "link" && placesFilter.includes(linkPageComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll(':not(.sub) [data-type="comment"] .profile')));
		} // nie odkomentowywać, bo dam się dynamicznie nie aktualizują ani nie doładowują ukryte, bo one są tylko hidden.
		if (getWykopPageType() === "link" && placesFilter.includes(linkPageSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('.sub [data-type="comment"] .profile')));
		}
		//if (getWykopPageType() === "wpis" && placesArr.includes(wpisPageComment)) {
			//profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entry"] .profile')));
		//}
		//if (getWykopPageType() === "wpis" && placesFilter.includes(wpisPageSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entrycomment"] .profile')));
		//}
		//if (getWykopPageType() === "tag" && placesArr.includes(tagPageComment)) {
			//profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entry"] .profile')));
		//}
		//if (getWykopPageType() === "tag" && placesFilter.includes(tagPageSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entrycomment"] .profile')));
		//}
		//if (getWykopPageType() === "moj" && placesFilter.includes(mojPageSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entrycomment"] .profile')));
		//}
		
		if (getWykopPageType() === "mikroblog" && placesFilter.includes(mikroblogPageWriteElement)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-submitflag="commentSubmit"] .profile')));
		}
		if (getWykopPageType() === "link" && placesFilter.includes(linkPageWriteElement)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-submitflag="commentSubmit"] .profile')));
		}
		if (getWykopPageType() === "wpis" && placesFilter.includes(wpisPageWriteElement)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-submitflag="commentSubmit"] .profile')));
		}
		if (getWykopPageType() === "tag" && placesFilter.includes(tagPageWriteElement)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-submitflag="commentSubmit"] .profile')));
		}
		if (getWykopPageType() === "ludzie" && placesFilter.includes(ludziePageWriteElement)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-submitflag="commentSubmit"] .profile')));
		}
		if (getWykopPageType() === "moj" && placesFilter.includes(mojPageWriteElement)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-submitflag="commentSubmit"] .profile')));
		}
		
		if (getWykopPageType() === "ludzie" && placesFilter.includes(ludziePageLinkSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll(':not(.sub) [data-type="comment"] .profile')));
		}
		//if (getWykopPageType() === "ludzie" && placesArr.includes(ludziePageWpisComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entry"] .profile')));
			// bez if-a, żeby observer się dodał, a filtrowane w observer-ze i w allFn
			// reszta zapytań [data-type="entry"] .profile za-komentowana, żeby nie powielać.
			// glownaPageComment też
		//}
		//if (getWykopPageType() === "ludzie" && placesFilter.includes(ludziePageWpisSubComment)) {
			profiles.push(...Array.from(scopeEl.querySelectorAll('[data-type="entrycomment"] .profile')));
		//}
		
		
		if (profiles.length===0) { console.log("profiles is empty"); }
		return profiles;
	}
	
	function getPlace (profileEl) {
		let place = "other";
		if (profileEl === document.querySelector('.logged-user > a')) {
			place = headerProfileElement;
		} else
		if (getWykopPageType() === "link" && profileEl === document.querySelector('.usercard > a')) {
			place = linkPageAuthorElement;
		} else
		if (getWykopPageType() === "ludzie" && profileEl.parentElement.matches('[data-type="comment"]')) {
			place = ludziePageLinkSubComment;
		} else
		if (getWykopPageType() === "ludzie" && profileEl.parentElement.matches('[data-type="entry"]')) {
			place = ludziePageWpisComment;
		} else
		if (getWykopPageType() === "ludzie" && profileEl.parentElement.matches('[data-type="entrycomment"]')) {
			place = ludziePageWpisSubComment;
		} else
		if (getWykopPageType() === "ludzie" && profileEl.parentElement.matches('[data-submitflag="commentSubmit"]')) {
			place = ludziePageWpisSubComment;
		} else
		if (document.querySelector(".grid .info")?.textContent?.includes("Strona główna")
				&&
				profileEl.parentElement.matches('[data-type="entry"]')) {
			place = glownaPageComment;
		} else
		if (["ludzie","link","tag","mikroblog","wpis","moj"].includes(getWykopPageType())) {
			place = `${getWykopPageType()}-page${getSub(profileEl)}-comment`;
		}
		
		if (place === "") { console.log("place null", profileEl); }
		return place;
	}
	
	function getProfileElNick(profileEl) {
		const nickB = profileEl.getAttribute("href").split("/");
		const nick = nickB[nickB.length-2];
		return nick;
	}
	
	const getSex = avatarEl=>avatarEl.classList.contains("male")?"male":avatarEl.classList.contains("female")?"female":null;
	
	async function wykopObserve (userFilters, callbackFn, {once=false,delay=null}) {
	
	const cleanP = [...(new Set(userFilters.flat(Infinity)))];
	userFilters = cleanP.filter(p=>[...places, otherFilter].includes(p));
	const ff = cleanP.filter(p=>![...places, otherFilter].includes(p));
	if (ff.length>0) { console.warn(`[WykopObserve] faulty filters: ${ff.map(f=>`"${f}"`).join(", ")}.`); }
	const sym = Symbol();
	
	
	function getAttrs (profileEl) {
		const avatarEl = profileEl.querySelector(".avatar");
		
		const place = getPlace(profileEl);
		const isFirstTime = profileEl.parentElement.parentElement[sym]!==true;
		const nick = getProfileElNick(profileEl);
		const authorSex = getSex(avatarEl);
		const attrs = {place, isFirstTime, nick, authorSex};
		return attrs;
	}
	function getElements (profileEl) {
		const liEL = profileEl.parentElement.parentElement;
		return {
			profileEl,
			liEl: liEL.matches("li")?liEL:null,
			contentEl: profileEl.parentElement.querySelector(".text > p")
		};
	}
	
	async function cbFn (elements, attrs) {
		await callbackFn(elements, attrs);
		//if (delay >= 0) { await delayFn(delay); }
	}
	
	function addCommentOrSubCommentChangeObserver (cbFn, parentProfileEl, placesFilter) {
		const profileElObserver = new MutationObserver((mutations)=> {
			//console.log("profilEl mutation");
			const target = mutations[0].target;
			if (!(target instanceof HTMLElement)) { return false; }
			const profileEl = target.querySelector(".profile");
			const attrs = getAttrs(profileEl);
			const elements = getElements(profileEl);
			if (placesFilter.includes(attrs.place)) {
				cbFn(elements, attrs);
			}
		});
		profileElObserver.observe( parentProfileEl.parentElement.parentElement, {childList: true} );
	};
	
	async function cbAndObserveChanges (scopeEl, observe=true) {
		const placesFilter = userFilters;
		const profiles = getProfiles(scopeEl, placesFilter);
		
		for (const profileEl of profiles) {
			
			const attrs = getAttrs(profileEl);
			const elements = getElements(profileEl);
			if (placesFilter.includes(attrs.place) && (observe===false || once === false || attrs.isFirstTime === true)) {
				await cbFn(elements, attrs);
				if (observe) { addCommentOrSubCommentChangeObserver(cbFn, profileEl, placesFilter); }
				profileEl.parentElement.parentElement[sym] = true;
			}
			
			// addCommentOrSubCommentChangeObserver
			// trzeba dodać do każdego Comment i SubComment, bo to obserwuje modyfikację konkretnego elementu pod względem posiadania
			// 	brody. nie sprawdza ilości a modyfikację elementu (np. usunięcie, edycję).
			// obsługuje: usunięcie(stan "usunięty"), stan edycji(jak jest pole do wpisywania to też odświeża avatar), stan po edycji.
			// modyfikacja WpisPageComment też trigger-uje observera.
		}
		
	}
	
	
	
	function addSubObserver (subOrStreamEl, callback=()=>{}) {
		//if (subOrStreamEl.dataset.hasOwnProperty("childCount")) { return false; }
		if (subOrStreamEl[sym]===true) { return false; }
		//subOrStreamEl.dataset.childCount = `${subOrStreamEl.childElementCount}`;
		const subObserver = new MutationObserver(async function(mutations) {
			const target = mutations[0].target;
			if (!(target instanceof HTMLElement)) { return false; }
			//target.dataset.childCount = `${target.childElementCount}`;
			await cbAndObserveChanges(target);
			callback();
		});
		subObserver.observe( subOrStreamEl, {childList: true} );
		subOrStreamEl[sym]=false;
	};
	
	function observeExpandSubComments () {
		const subEls = Array.from( document.querySelectorAll(".sub") );
		subEls.forEach((subEl)=>addSubObserver(subEl));
	}
	
	function cbAndObserveOnNewAndInfiniteScrollInTagPage () {
		const streamEl = document.querySelector(".comments-stream");
		addSubObserver(streamEl, observeExpandSubComments);
	}
	
	
	await cbAndObserveChanges(document);
	// dodaje do wpisPageComment lub wpisPgeSubComment i obserwuje modyfikacje
	// nie reaguje na ładowanie dodatkowych postów w tagu.
	
	observeExpandSubComments();
	// dodaje dla wpisPageSubComment, gdy rozwinąć dodatkowe zwinięte komentarze wpisPageSubComment
	// w link, mikroblog, na profilu też działa doładowywanie. (w link niepotrzebne właściwie)
	
	cbAndObserveOnNewAndInfiniteScrollInTagPage();
	// dodaje dla tagPageComment, doładowane jako nowe albo z infinite scroll
	// nie dodaje observer dla expandSubComments
	
	const refresh = ()=>cbAndObserveChanges(document, false);
	return {refresh};
	
	// const wpisZeroObserver = new MutationObserver(async function(mutations) {
	// 	const liEl = mutations[0].target;
	// 	if (!(liEl instanceof HTMLElement)) { return false; }
	// 	const subEl = liEl.querySelector(".sub");
	// 	if (!(subEl instanceof HTMLElement)) { return false; }
		
	// 	if (!(liEl.childElementCount !== 1 && subEl)) { return false; }
		
	// 	await cbAndObserveChanges(subEl);
	// 	subEl.dataset.childCount = `${subEl.childElementCount}`;
	// 	addSubObserver(subEl);
	// });
	// function subZeroFn () {
	// 	const streamElChildren = Array.from( document.querySelector(".comments-stream").children );
		
	// 	streamElChildren.forEach((liEl)=>{
	// 		if (!(liEl.childElementCount === 1)) { return false; }
	// 		wpisZeroObserver.observe( liEl, {childList: true} );
	// 	});
	// }
	//subZeroFn();
	// raczej nic nie robi
	// dodaje dla wpisPageSubComment ale setNowAndObserveChanges też to robi?
	// nie dodaje gdy zmienia się liczba wpisPageSubComment
	// nie dodaje, gdy rozwinąć dodatkowe zwinięte komentarze wpisPageSubComment
	
	
	}
	
	function getLogin () {
		const avatarEl = document.querySelector(".logged-user img.avatar");
		if (!(avatarEl instanceof HTMLImageElement)) { return false; }
		return {login:avatarEl.alt, sex:getSex(avatarEl)};
	}
	
	function getAdjacentEls(liEl) {
		return liEl?.parentElement?.children||[];
	}
	
	//} // observe lib end
	
	return {wykopObserve, loginUser:getLogin(), getAdjacentEls, filterGroups: {
		all: places,
		mikroblogLinkWpisGlownaTagMojComment,
		mikroblogLinkWpisTagSubMojComment,
		mikroblogLinkWpisGlownaTagMojCommentOrSubComment,
		ludziePageCommentOrSubComment,
		writeElement,
	}};
	
	})();
	// observe module end