0xFFFE.Genfluence.Archivist (prod)

Add "Download All" and "Delete All" features to Genfluence.AI.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name             0xFFFE.Genfluence.Archivist (prod)
// @match            https://www.genfluence.ai/*
// @description      Add "Download All" and "Delete All" features to Genfluence.AI.
// @license          LGPL-3.0-or-later
// @version          1.9
// @namespace https://greasyfork.org/users/1309423
// ==/UserScript==

(function() {
  const PREFIX = "xga-";
  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 = `${PREFIX}-settings-toggle`;
  const PANEL_ELEM_ID = `${PREFIX}-settings-panel`;
  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 (panelElem.dataset.enabled === "true") {
      panelElem.dataset.enabled = "false";
      panelElem.classList.add("hidden");
      mainElem.classList.remove(`${PREFIX}-hidden`);
    } else {
      panelElem.dataset.enabled = "true";
      panelElem.classList.remove("hidden");
      mainElem.classList.add(`${PREFIX}-hidden`);
    }
  }

  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.classList.add("disabled");
    deleteButtonElem.value = "❌ Cancel delete";
    deleteButtonElem.removeEventListener('click', runDelete);
    deleteButtonElem.addEventListener('click', cancelAction);

    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);
    downloadButtonElem.classList.remove("disabled");

    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.classList.add("disabled");
    downloadButtonElem.value = "❌ Cancel download";
    downloadButtonElem.removeEventListener('click', runDownload);
    downloadButtonElem.addEventListener('click', cancelAction);

    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.classList.remove("hidden");
    deleteButtonElem.classList.remove("disabled");

    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);

    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = `
      body {
        position: relative;
      }
      ${MAIN_ELEM_SELECTOR} {
      	transition: all 0.25s ease-in-out;
        pointer-events: auto;
        opacity: 100%;
      }
      ${MAIN_ELEM_SELECTOR}.${PREFIX}-hidden {
        pointer-events: none;
        opacity: 25%;
      }
      #${PANEL_ELEM_ID} {
        box-shadow: 0px 0px 50px rgb(20,184,166,0.5);
        position: fixed;
        top: 50vh;
        left: 50vw;
        background-color: #0f172a;
        transform: translate(-50%,-50%);
        display: flex;
        justify-content: center;
        flex-direction: column;
        align-items: stretch;
        gap: 1em;
        padding: 60px 30px 30px 30px;
        transition: all 0.25s ease-in-out;
        border: 2px solid #fdd888;
        opacity: 1;
        z-index: 100;
        width: min(80vw, 400px);
      }
      #${PANEL_ELEM_ID}.hidden {
        opacity: 0;
        top: -50vh;
      }
      #${PANEL_ELEM_ID} > .button-close {
        position: absolute;
        top: 10px;
        right: 10px;
      }
      #${PANEL_ELEM_ID} > .button.disabled {
        pointer-events: none;
        opacity: 25%;
      }
      #${PANEL_ELEM_ID} > .logs {
        display: block;
        background-color: rgb(55 65 81/var(--tw-bg-opacity));
        flex-basis: 50px;
        padding: 5px;
      }
      #${PANEL_ELEM_ID} > .button-download {
        border: 2px solid #14B8A6;
        background-color: #333;
        border-radius: 5px;
        padding: 0.25em 1em;
        margin-right: 0.5em;
        width: 100%;
      }
      #${PANEL_ELEM_ID} > .button-delete {
        border: 2px solid darkred;
        background-color: #333;
        border-radius: 5px;
        padding: 0.25em 1em;
        width: 100%;
      }
      #${PANEL_ELEM_ID} h2 {
        font-size: 120%;
        color: #fdd888;
      }
      #${TOGGLE_ELEM_ID} {
        margin-right: 0.5em;
        outline: 0px;
        box-shadow: none;
      }
    `;
    document.getElementsByTagName('head')[0].appendChild(style);

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

    panelElem = document.createElement('div');
    panelElem.id = PANEL_ELEM_ID;
    panelElem.dataset.enabled = "false";
    panelElem.classList.add("hidden");

    let panelTitle = document.createElement("h2");
    panelTitle.textContent = "Genfluence Archivist";
    panelElem.appendChild(panelTitle);

    let panelCloseElem = document.createElement('input');
    panelCloseElem.classList.add("button-close");
    panelCloseElem.type = "button";
    panelCloseElem.value = "[Close]";
    panelCloseElem.addEventListener('click', toggleSettings);
	  panelElem.appendChild(panelCloseElem);

    logsElem = document.createElement('div');
    logsElem.classList.add("logs");
    logsElem.dataset.enabled = "false";
    panelElem.appendChild(logsElem);

    downloadButtonElem = document.createElement('input');
    downloadButtonElem.classList.add("button", "button-download");
    downloadButtonElem.type = "button";
    downloadButtonElem.value = "💾 Download all";
    downloadButtonElem.addEventListener('click', runDownload);
    panelElem.appendChild(downloadButtonElem);

    deleteButtonElem = document.createElement('input');
    deleteButtonElem.classList.add("button", "button-delete");
    deleteButtonElem.type = "button";
    deleteButtonElem.value = "🗑️ Delete all";
    deleteButtonElem.addEventListener('click', runDelete);
    panelElem.appendChild(deleteButtonElem);

    bodyElem.appendChild(panelElem);

    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);
    if (navBarElem && !toggleButtonElem) {
      setup();
    } else if (setupTimeout > 0) {
      setupTimeout = setupTimeout - 1;
      setTimeout(trySetup, SLEEP_TIME_SETUP);
    }
    console.log("trysetup-end");
  }
  setTimeout(trySetup, SLEEP_TIME_SETUP);
}());