// ==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 // @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 }; 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. // 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{ 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.
`; 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); }); })();