Greasy Fork

Greasy Fork is available in English.

dA_showAIOnThumb

Display on thumbnail that art was generated using AI!

当前为 2023-12-30 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         dA_showAIOnThumb
// @namespace    http://phi.pf-control.de
// @version      2023-12-30
// @description  Display on thumbnail that art was generated using AI!
// @author       Dediggefedde
// @match        *://*.deviantart.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deviantart.com
// @grant        GM.xmlHttpRequest
// @grant        GM_addStyle
// @grant   	 GM.setValue
// @grant   	 GM.getValue
// @noframes
// ==/UserScript==

(function() {
	'use strict';

	let settings = {
			onlyOnHover:  false,                    //bool, true: check all thumbs for AI automatically; false: check only on hover
			AITags:       ["ai","aiart","dreamup"], //tags that will mark an art as AI in addition to dA's own "created with AI tools" marker
			hideAIThumbs: false,                    //remove AI thumbs instead of marking them
		 // moveOtherThumbs:false, //not implemented
	};

	let bounceInterval =500; //int [ms], avoid multiple activation at once, minimum time before activating script again

	//helper variables
	let antiBounce=new Date();
	let fetchedIDs={};

	//style for isAIgenerated!
	//checked items have the attribute. The value is "1" if they are AI generated, otherwise "0"
	//here: AI text with white background and blue circle over the thumbnail with 70% transparency
	GM_addStyle(`
[isAIGenerated="1"]{
		position:relative;
}
[isAIGenerated="1"]::after {
		 content: "AI";
		 position: relative;
		 left: 50%;
		 top: -95%;
		 padding: 5px;
					background: radial-gradient( ellipse at center, rgb(var(--g-bg-primary-rgb)) 0%, rgb(var(--g-bg-primary-rgb)) 60%,  rgb(var(--green4-rgb)) 65%, rgb(var(--green4-rgb)) 70%, rgba(0,0,0,0) 75% );
					color: var(--g-typography-primary);
		 width: 15px;
		 height: 15px;
		 line-height: 15px;
		 display: block;
		 filter: opacity(70%);
					transform: translateX(-50%);
					}
	#dA_saiot_notify p {
					font-weight: bold;
					text-align: center;
					margin: 0;
					color: var(--g-typography-secondary);
	}
	#dA_saiot_notify{
					position: fixed;
					width: 400px;
					display: block;
					top: 0%;
					background-color: var(--g-bg-tertiary);
					padding: 10px;
					border-radius: 0 10px 10px 0;
					border: 1px solid var(--g-divider1);
					box-shadow: 1px 1px 2px var(--g-bg-primary);
					transition:left;
					transition-duration:0.5s;
					transform: translateY(100%) translateY(10px);
					color: var(--g-typography-primary);
 }
 div.settings_form label{cursor:pointer;}
`);

	let msgbox,viewtimer;
	let thumbs;

	function notify(text){
			msgbox.innerHTML="<p>dA_showAiOnThumb</p>"+text;
			msgbox.style.left="0px";
			if(viewtimer!=null)clearTimeout(viewtimer);
			viewtimer=setTimeout(()=>{msgbox.style.left="-450px";},2000);
	}

	//request deviation data. deviation id, username and type ("art") is in the url.
	//include_session=false necessary
	function requestDevData(devID, username,type){
			let token=document.querySelector("input[name=validate_token]").value;
			return new Promise((resolve, reject) =>{
					GM.xmlHttpRequest({
							method: "GET",
							url: `https://www.deviantart.com/_puppy/dadeviation/init?deviationid=${devID}&username=${username}&type=${type}&include_session=false&csrf_token=${token}`,
							headers: {
									"accept": 'application/json, text/plain, */*',
									"content-type": 'application/json;charset=UTF-8'
							},
							onerror: function(response) {
									reject(response);
							},
							onload: async function(response) {
									try{
											let result=JSON.parse(response.response);
											resolve(result);
									}catch(ex){
											reject(response);
									}
							}
					});
			});
	}

/*not implemented. Not really working, very messy
function moveAIImgs(el){
			let ind=thumbs.indexOf(el);
			console.log("check move",el,ind,thumbs.length);
			for(let j=ind+1;j<thumbs.length;++j){
					console.log("checked:",thumbs[j]);
					if(!thumbs[j].hasAttribute("isAIGenerated")){ //only if at end of container/row (only rows?)
						 // el.parentNode.parentNode.insertBefore(thumbs[j].parentNode, el.parentNode.nextSibling);
							console.log("at",el,"moved",thumbs[j]);
							thumbs[j].parentNode.setAttribute("moved","1");
							return;
					}
			}
			console.log("not moved");
	}
	*/

	//takes a thumbnail link element, extracts information, triggers request and adds isAIGenerated attribute
	function checkAIGenerated(el){
			if(el.hasAttribute("isAIGenerated"))return; //skip for items already checked

			let url=el.href;
			let dats=/deviantart.com\/(.*?)\/(.*?)\/.*?-(\d+)$/gi.exec(url); //[match, artis, type, id] extracted from URL

			if(fetchedIDs[dats[3]]!=null){ //cached results for dev ID
					el.setAttribute("isAIGenerated",fetchedIDs[dats[3]]);
					return;
			}

			requestDevData(dats[3],dats[1],dats[2]).then((res)=>{ //request of extented data from PUPPY-API
					try{ //responce might be successfull but have other object members

							if(res.deviation.isAiGenerated){ //extract and add information
									el.setAttribute("isAIGenerated","1"); //set element information
									fetchedIDs[dats[3]]="1"; //cache result for deviation id.
								 // moveAIImgs(el);
							}else{
									el.setAttribute("isAIGenerated","0");
									fetchedIDs[dats[3]]="0";
							}
							if(res.deviation.extended.tags!=null){
									res.deviation.extended.tags.forEach(tg=>{
											if(settings.AITags.includes(tg.name)){
													el.setAttribute("isAIGenerated","1"); //set element information
													fetchedIDs[dats[3]]="1"; //cache result for deviation id.
												//  moveAIImgs(el);
											}});
							}

					}catch(ex){
							console.log("dA_showAIOnThumb Error 2",ex,res); //error code 2, exception and return from server
					}
			})
					.catch(err=>{
					console.log("dA_showAIOnThumb Error 3",err); //error code 3, error code from promise call
			});
	}

	function init(mutationList, observer){ //called on DOM change

			//debounce to avoid calling it multiple times at once
			let dNow=new Date();
			if(dNow-antiBounce<bounceInterval)return;
			antiBounce=dNow;


			if (location.href.indexOf('https://www.deviantart.com/settings') == 0 && document.getElementById("dA_showAiOnThumb_Options")==null) {

					if(!document.querySelector("#dA_saiot_notify")){
							msgbox=document.createElement("div");
							msgbox.id="dA_saiot_notify";
							msgbox.style.left="-450px";
							document.body.append(msgbox);
					}

					let menuPoint = document.createElement("li");
					menuPoint.innerHTML='<a href="#">AI Thumbnail</a>';
					menuPoint.id="dA_showAiOnThumb_Options";

					document.getElementById("settings_public").parentNode.after(menuPoint);

					menuPoint.firstChild.addEventListener("click",(ev)=>{
							document.querySelector("a.active").classList.remove("active");
							ev.target.classList.add("active");
							document.querySelector('div.settings_form').innerHTML=`
					<div class="fooview ch">
					<div class="fooview-inner">
						<h3>dA_showAIOnThumb Settings</h3>
						<div class="altaltview altaltview-wider">

							<div class="row">
							<input ${ settings.onlyOnHover ? 'checked="checked"' : '' } type="checkbox" id="da_saiot_checkhover" class="icheckbox">
							<label for="da_saiot_checkhover" class="l">Mark AI thumbs only on hover</label>
							<br><small>Check and mark AI images only when moving the cursor over the Thumbnail. Can improve performance on slower computers/connections. Otherwise all thumbnail are checked and marked when the appear.</small>
							</div>

							<div class="row">
							<input value='${ settings.AITags?settings.AITags.join(","):'' }' type="text" id="da_saiot_AITags" class="itext_uplifted" />
							<label for="da_saiot_AITags" class="l">Tags that mark deviations as AI</label>
							<br><small>Comma-separated. Deviations with tags in this list will be marked as AI-generated. If Deviantart marks a submission as AI-generated/assisted on its own, it will be marked in any case.</small>
							</div>

							<div class="row">
							<input ${ settings.hideAIThumbs ? 'checked="checked"' : '' } type="checkbox" id="da_saiot_removeAI" class="icheckbox">
							<label for="da_saiot_removeAI" class="l">Remove AI thumbs instead of marking them</label>
							<br><small>Instead of marking AI thumbnails, this will remove them and leave an empty space in place.</small>
							</div>

						</div>

						<div class=" buttons ch hh " id="submit">
							<div style="text-align:right" class="rr">
								<a class="smbutton smbutton-green" href="javascript:void(0)"><span id="da_saiot_saveSettings">Save</span></a>
							</div>
						</div>

					</div>
					</div>
					`;
							document.getElementById('da_saiot_saveSettings').addEventListener("click",(ev)=> {
									settings.onlyOnHover = document.getElementById("da_saiot_checkhover").checked;
									settings.hideAIThumbs = document.getElementById("da_saiot_removeAI").checked;
									settings.AITags = document.getElementById("da_saiot_AITags").value.split(',').map((el)=>{return el.trim();});
									setTimeout(() => {
											GM.setValue('settings',JSON.stringify(settings));
											notify("List saved!");
									}, 0);
							},false);

					},false);
			}


			//check all thumbs which were not already checked
			thumbs=[...document.querySelectorAll(`div[data-hook='deviation_std_thumb']>a[data-hook='deviation_link']:not([da_showaionthumb]),
																			 div[data-testid='grid-row'] a[data-hook='deviation_link']:not([da_showaionthumb])`)];
			thumbs.forEach(el=>{
					el.setAttribute("da_showaionthumb",""); //mark thumb as checked
					if(!settings.onlyOnHover){ //check all immediatelly
							checkAIGenerated(el); //function will cancel if already checked
					}else{ //check on mouseover
							el.addEventListener("mouseenter",(ev=>{
									checkAIGenerated(ev.target);
							}),false); //no bubbling
					}
			});

	}

	//new technique! checks DOM for mutation.
	//might be better than setTimeout for idling and more responsive
	//is triggered multiple times at once, maybe requires "debouncing"
	//here actually not, since I mark all thumbnails and no expensive operation is done beside that
	//technically, "debouncing" should cancel/delay first triggers and only use last one or have a delay to avoid missing things
	//again, since I check all thumbnails and a lot of hovers/scrolls triggers mutation, it is probably fine. ^^'

	GM.getValue("settings").then((res)=>{
			if(res==null)return;
			settings=JSON.parse(res);
			if(settings.hideAIThumbs){
					GM_addStyle("div:has(>[isAIGenerated='1']){display:none!important;}");
			}
	}).finally(()=>{
			const observer = new MutationObserver(init);
			observer.observe(document.body,{ childList: true, subtree: true });
			init(null,null);
	});

})();