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.

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/437595/1002297/WykopObserve.js

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// 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