0xFFFE.Genfluence.Archivist (prod)

Add Download All and Delete All features to Genfluence.

2024-05-30 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name             0xFFFE.Genfluence.Archivist (prod)
// @match            https://www.genfluence.ai/*
// @description      Add Download All and Delete All features to Genfluence.
// @license MIT
// @version          1.7
// @namespace https://greasyfork.org/users/1309423
// ==/UserScript==

(function() {
  const BASE_URL = "https://www.genfluence.ai";
  const NAVBAR_ELEM_SELECTOR = ".drawer-content > .navbar";
  const CASH_ELEM_SELECTOR = ".drawer-content .text-xl";
  const MAIN_ELEM_SELECTOR = "main#skip";
  const TOGGLE_ELEM_ID = "xga-settings-toggle";
  const PANEL_ELEM_ID = "xga-settings-panel";
  const LOGS_ELEM_ID = "xga-settings-logs";
  const TOGGLE_ELEM_SELECTOR = "#" + TOGGLE_ELEM_ID;
  const SLEEP_TIME_SETUP = 1000;
  const SLEEP_TIME_IMAGE = 100;
  const SLEEP_TIME_PAGINATION = 500;

  let setupTimeout = 60;
  let navBarElem = null;
	let toggleButtonElem = null;
  let downloadButtonElem = null;
  let cashElem = null;
  let logsElem = null;
  let deleteButtonElem = null;
  let mainElem = null;
  let panelElem = null;
  let requestStop = false;

  async function sleep(duration) {
    await new Promise(r => setTimeout(r, duration));
  }

  async function deleteImage(imageId, retry) {
    if (!retry) {
      retry = 0;
    }
    if (retry > 2) {
      return null;
    }

    try {
      let result = await fetch(BASE_URL + "/api/images/delete_images", {
        "credentials": "include",
        "headers": {
          "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0",
          "Accept": "application/json, text/plain, */*",
          "Accept-Language": "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3",
          "Content-Type": "application/json",
          "Sec-Fetch-Dest": "empty",
          "Sec-Fetch-Mode": "cors",
          "Sec-Fetch-Site": "same-origin",
          "Pragma": "no-cache",
          "Cache-Control": "no-cache"
        },
        "referrer": BASE_URL + "/create",
        "body": "{\"imageIds\":[\"" + imageId + "\"]}",
        "method": "POST",
        "mode": "cors"
      });
      return result;
    } catch (error) {
      await sleep(2000);
      return deleteImage(imageId, retry + 1);
    }
  }

  async function downloadImage(url, name) {
    let link = document.createElement("a");
    let blob = await fetch(url).then(r => r.blob());
    let file = new Blob([blob], {
      type: 'application/octet-stream'
    });
    link.href = URL.createObjectURL(file);
    link.download = name;
    link.click();
    URL.revokeObjectURL(link.href);
  }

  async function fetchImages(position, retry) {
    if (!retry) {
      retry = 0;
    }
    if (retry > 2) {
      return null;
    }

    let positionStart = position;
    let positionEnd = position + 19;
    try {
      let result = await fetch(BASE_URL + "/api/images/get_images", {
        "credentials": "include",
        "headers": {
          "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0",
          "Accept": "application/json, text/plain, */*",
          "Accept-Language": "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3",
          "Content-Type": "application/json",
          "Sec-Fetch-Dest": "empty",
          "Sec-Fetch-Mode": "cors",
          "Sec-Fetch-Site": "same-origin",
          "Pragma": "no-cache",
          "Cache-Control": "no-cache"
        },
        "referrer": BASE_URL + "/create",
        "body": "{\"by\":\"history\",\"projectId\":\"\",\"from\":" + positionStart + ",\"to\":" + positionEnd + "}",
        "method": "POST",
        "mode": "cors"
      });
      if (result.ok) {
      	// If the response is successful, process the JSON
        return result.json();
      } else {
        console.error(`Received error: ${result.status}. Retrying...`);
        await sleep(3000); // Wait before retrying
        return fetchImages(position, retry + 1);
      }
    } catch (error) {
      await sleep(3000);
      return fetchImages(position, retry + 1);
    }
  }

  function toggleSettings() {
    // if settings panel is visible => hide
    // if settings panel is invisible => show
    if (panelElem.dataset.enabled === "true") {
      panelElem.dataset.enabled = "false";
      panelElem.style.top = "-50vh";
    } else {
      panelElem.dataset.enabled = "true";
      panelElem.style.top = "50vh";
    }
  }

  async function cancelAction() {
    console.log("cancelAction-begin");
    requestStop = true;
    console.log("cancelAction-end");
  }

  async function runDelete() {
    console.log("runDelete-begin");

    let initRes = await fetchImages(0);
    let imagesTotal = initRes.totalRecords;
    let deleteButtonValueBackup = deleteButtonElem.value;

    requestStop = false;
    downloadButtonElem.disabled = true;
    deleteButtonElem.value = "❌ Cancel delete";
    deleteButtonElem.removeEventListener('click', runDelete);
    deleteButtonElem.addEventListener('click', cancelAction);

    mainElem.style.pointerEvents = "none";
    mainElem.style.opacity = "25%";

    for (let offsetCur = 0; offsetCur < imagesTotal; offsetCur += 20) {
      let offsetRes = await fetchImages(0); //
      if (!offsetRes) { break; }
      if (!offsetRes.images) { break; }
      if (requestStop) { break; }

      await sleep(SLEEP_TIME_PAGINATION);
      for (let imageCur = 0; imageCur < 20; imageCur += 1) {
        let image = offsetRes.images[imageCur];
        if (!image) { continue; }
        if (requestStop) { break; }

        deleteImage(image.id);
        let pcent = Math.floor(10000 * (imageCur + offsetCur) / imagesTotal) / 100;
        logsElem.textContent = `🗑️ Deleting... (${pcent.toFixed(2)}%)`;
        await sleep(SLEEP_TIME_IMAGE);
      }
    }

    deleteButtonElem.value = deleteButtonValueBackup;
    logsElem.textContent = "";
    deleteButtonElem.removeEventListener('click', cancelAction);
    deleteButtonElem.addEventListener('click', runDelete);

    mainElem.style.pointerEvents = "auto";
    mainElem.style.opacity = "100%";
    downloadButtonElem.disabled = false;
    console.log("runDelete-end");
  }

  
  async function runDownload() {
    console.log("runDownload-begin");
    let initRes = await fetchImages(0);
    let imagesTotal = initRes.totalRecords;
    let downloadButtonValueBackup = downloadButtonElem.value;
    
    requestStop = false;
    deleteButtonElem.disabled = true;
    downloadButtonElem.value = "❌ Cancel download";
    downloadButtonElem.removeEventListener('click', runDownload);
    downloadButtonElem.addEventListener('click', cancelAction);

    mainElem.style.pointerEvents = "none";
    mainElem.style.opacity = "25%";

    for (let offsetCur = 0; offsetCur < imagesTotal; offsetCur += 20) {
      let offsetRes = await fetchImages(offsetCur); //
      if (!offsetRes) { break; }
      if (!offsetRes.images) { break; }
      if (requestStop) { break; }

      for (let imageCur = 0; imageCur < 20; imageCur += 1) {
        if (imageCur >= offsetRes.images.length) { break; }
        if (requestStop) { break; }
        
        let pcent = Math.floor(10000 * (imageCur + offsetCur) / imagesTotal) / 100;
        // let globalIndex = (imageCur + offsetCur);
        // console.log(`offset = ${globalIndex} / ${imagesTotal} (${pcent.toFixed(2)}%)`);

        let image = offsetRes.images[imageCur];
        if (!image) { continue; }
        let imageUrl = image.imgUrl;
        let imageTimestamp = image.createdAt.substring(0, 19).replace(/[:-]/g,"");
        let imageName = `anydream--${imageTimestamp}--${image.id}.${image.extention}`;

        downloadImage(imageUrl, imageName);
        logsElem.textContent = `💾 Downloading... (${pcent.toFixed(2)}%)`;
        await sleep(SLEEP_TIME_IMAGE);
      }

      await sleep(SLEEP_TIME_PAGINATION);
    }

    downloadButtonElem.value = downloadButtonValueBackup;
    logsElem.textContent = "";
    downloadButtonElem.removeEventListener('click', cancelAction);
    downloadButtonElem.addEventListener('click', runDownload);

    mainElem.style.pointerEvents = "auto";
    mainElem.style.opacity = "100%";
    deleteButtonElem.disabled = false;
    console.log("runDownload-end");
  }

  function setup() {
    console.log("setup-begin");

    let bodyElem = document.querySelector("body");
    navBarElem = document.querySelector(NAVBAR_ELEM_SELECTOR);
    mainElem = document.querySelector(MAIN_ELEM_SELECTOR);
    cashElem = document.querySelector(CASH_ELEM_SELECTOR);
    console.log(navBarElem);
    console.log(mainElem);
    console.log(cashElem);

    // Add transition on elements
    mainElem.style.transition = "all 0.25s ease-in-out";

    toggleButtonElem = document.createElement('input');
    toggleButtonElem.id = TOGGLE_ELEM_ID;
    toggleButtonElem.type = "button";
    toggleButtonElem.value = "🛠️";
    toggleButtonElem.style.marginRight = "0.5em";
    toggleButtonElem.addEventListener('click', toggleSettings);

    panelElem = document.createElement('div');
    panelElem.id = PANEL_ELEM_ID;

    panelElem.style.boxShadow = "0px 0px 50px rgb(20,184,166,0.25)";
    panelElem.style.width = "min(80vw, 400px)";
    panelElem.dataset.enabled = "false";
    panelElem.style.height = "min(30vh, 400px)";
    panelElem.style.position = "absolute";
    panelElem.style.top = "-50vh";
    panelElem.style.left = "50vw";
    panelElem.style.backgroundColor = "#0f172a";
    panelElem.style.transform = "translate(-50%,-50%)";
    panelElem.style.display = "flex";
    panelElem.style.justifyContent= "center";
    panelElem.style.flexDirection= "column";
    panelElem.style.alignItems= "stretch";
    panelElem.style.gap = "1em";
    panelElem.style.padding = "60px 20px 20px 20px";
    panelElem.style.transition = "all 0.25s ease-in-out";

    let panelCloseElem = document.createElement('input');
    panelCloseElem.type = "button";
    panelCloseElem.value = "[Close]";
    panelCloseElem.style.position = "absolute";
    panelCloseElem.style.top = "10px";
    panelCloseElem.style.right = "10px";
    panelCloseElem.addEventListener('click', toggleSettings);
	  panelElem.appendChild(panelCloseElem);

    
    logsElem = document.createElement('div');
    logsElem.id = LOGS_ELEM_ID;
    logsElem.dataset.enabled = "false";
    logsElem.style.display = "block";
    //logsElem.style.border = "1px solid white";
    logsElem.style.backgroundColor = "rgb(55 65 81/var(--tw-bg-opacity))";
    logsElem.style.flexBasis = "200px";
    logsElem.style.padding = "5px";
    panelElem.appendChild(logsElem);
    
    downloadButtonElem = document.createElement('input');
    downloadButtonElem.type = "button";
    downloadButtonElem.id = "xga-download-all";
    downloadButtonElem.value = "💾 Download all";
    downloadButtonElem.style.border = "2px solid #14B8A6";
    downloadButtonElem.style.backgroundColor = "#333";
    downloadButtonElem.style.borderRadius = "5px";
    downloadButtonElem.style.padding = "0.25em 1em";
    downloadButtonElem.style.marginRight = "0.5em";
    downloadButtonElem.style.width = "100%";
    downloadButtonElem.addEventListener('click', runDownload);
    panelElem.appendChild(downloadButtonElem);

    deleteButtonElem = document.createElement('input');
    deleteButtonElem.type = "button";
    deleteButtonElem.id = "xga-delete-all";
    deleteButtonElem.value = "🗑️ Delete all";
    deleteButtonElem.style.border = "2px solid darkred";
    deleteButtonElem.style.backgroundColor = "#333";
    deleteButtonElem.style.borderRadius = "5px";
    deleteButtonElem.style.padding = "0.25em 1em";
    deleteButtonElem.style.width = "100%";
    deleteButtonElem.addEventListener('click', runDelete);
    panelElem.appendChild(deleteButtonElem);

    bodyElem.appendChild(panelElem);
    bodyElem.style.position = "relative";

    cashElem.parentNode.parentNode.insertBefore( toggleButtonElem, cashElem.parentNode);
    console.log("setup-end");
  }

  function trySetup() {
    console.log("trysetup-begin " + setupTimeout);
    navBarElem = document.querySelector(NAVBAR_ELEM_SELECTOR);
    toggleButtonElem = document.querySelector(TOGGLE_ELEM_SELECTOR);
    // console.log(navBarElem);
    // console.log(downloadButtonElem);
    if (navBarElem && !toggleButtonElem) {
      // ready for setup
      setup();
    } else if (setupTimeout > 0) {
      	setupTimeout = setupTimeout - 1;
        setTimeout(trySetup, SLEEP_TIME_SETUP);
    }
    console.log("trysetup-end");
  }

  setTimeout(trySetup, SLEEP_TIME_SETUP);
}());