// ==UserScript== // @name dA_showAIOnThumb // @namespace http://phi.pf-control.de // @version 2024-01-18 // @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 // @downloadURL none // ==/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 autoIgnore:false }; 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="

dA_showAiOnThumb

"+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{ //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. autoignoreNam(el); // 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. autoignoreNam(el); // 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{ document.querySelector("a.active").classList.remove("active"); ev.target.classList.add("active"); document.querySelector('div.settings_form').innerHTML=`

dA_showAIOnThumb Settings


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.

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.

Instead of marking AI thumbnails, this will remove them and leave an empty space in place.

This requires the userscript dA_ignore! It will add users that have posted AI art automatically to the ignore-list of dA_ignore.
`; 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.autoIgnore = document.getElementById("da_saiot_autoIgnore").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(`[data-testid="thumb"]:not([da_showaionthumb])`)]; thumbs.forEach(el=>{ let thmb=el.querySelector("a:not([data-username])"); if(!thmb)thmb=el.parentNode.querySelector("a"); if(!thmb)thmb=el.parentNode.parentNode.querySelector("a"); if(!thmb)thmb=el.parentNode.parentNode.parentNode.querySelector("a"); if(!thmb)return; el.setAttribute("da_showaionthumb",""); //mark thumb as checked if(!settings.onlyOnHover){ //check all immediatelly checkAIGenerated(thmb); //function will cancel if already checked }else{ //check on mouseover thmb.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; let savedSettings=JSON.parse(res); Object.entries(savedSettings).forEach(([key,val])=>{settings[key]=val}); 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); }); })();