Library to simplify requests to Furaffinity
Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/483952/1769047/Furaffinity-Request-Helper.js
// ==UserScript==
// @name Furaffinity-Request-Helper
// @namespace Violentmonkey Scripts
// @require https://greasyfork.org/scripts/525666-furaffinity-prototype-extensions/code/525666-furaffinity-prototype-extensions.js
// @grant none
// @version 1.5.2
// @author Midori Dragon
// @description Library to simplify requests to Furaffinity
// @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png
// @license MIT
// @homepageURL https://greasyfork.org/scripts/483952-furaffinity-request-helper
// @supportURL https://greasyfork.org/scripts/483952-furaffinity-request-helper/feedback
// ==/UserScript==
// jshint esversion: 11
(function () {
'use strict';
class Semaphore {
maxConcurrency;
currentConcurrency;
waitingQueue;
constructor(maxConcurrency) {
this.maxConcurrency = maxConcurrency;
this.currentConcurrency = 0;
this.waitingQueue = [];
}
acquire() {
return new Promise((resolve) => {
if (this.currentConcurrency < this.maxConcurrency) {
// There is room, increment the current concurrency and resolve the promise
this.currentConcurrency++;
resolve();
}
else {
// The semaphore is full, add the resolve function to the waiting queue
this.waitingQueue.push(resolve);
}
});
}
release() {
if (this.waitingQueue.length > 0) {
// There are waiting tasks, let the next one run
const nextResolve = this.waitingQueue.shift();
if (nextResolve != null) {
nextResolve();
}
}
else {
// No waiting tasks, decrement the current concurrency level
this.currentConcurrency--;
}
}
}
class PercentHelper {
static _percentAll = {};
constructor() {
throw new Error('The PercentHelper class is static and cannot be instantiated.');
}
static setPercentValue(id, value) {
// Check if the value is provided and if the id exists in percentAll
if (value && PercentHelper._percentAll.hasOwnProperty(id)) {
// Set the value for the given id
PercentHelper._percentAll[id] = value;
return true;
}
// Return false if value is not provided or id doesn't exist
return false;
}
static getPercentValue(id, decimalPlaces = 2) {
// Check if the id is provided, return -1 if not
if (id == null) {
return -1;
}
// Retrieve the percent value from the percentAll map using the given id
const percent = PercentHelper._percentAll[id];
// If the percent value is not found, return -1
if (!percent) {
return -1;
}
// Return the percent value rounded to the specified number of decimal places
return parseFloat(percent.toFixed(decimalPlaces));
}
static createPercentValue(uniqueId) {
// Initialize the percent value at 0 for the given uniqueId
PercentHelper._percentAll[uniqueId] = 0;
}
static deletePercentValue(id) {
if (PercentHelper._percentAll.hasOwnProperty(id)) {
// Delete the percent value from the list
delete PercentHelper._percentAll[id];
}
}
static updatePercentValue(id, value, totalValue) {
if (id != null && id !== '' && id !== -1) {
const progress = (value / totalValue) * 100;
PercentHelper.setPercentValue(id, progress);
}
}
}
const DEFAULT_ACTION_DELAY = 100;
class WaitAndCallAction {
delay = 10;
_action;
_intervalId;
_running = false;
constructor(action, delay) {
this._action = action;
if (delay != null) {
this.delay = delay;
}
}
start() {
if (this._action != null && this._running === false) {
this._running = true;
this._intervalId = setInterval(() => {
this._action(PercentHelper.getPercentValue(this._intervalId?.toString()));
}, this.delay);
PercentHelper.createPercentValue(this._intervalId.toString());
return this._intervalId;
}
}
stop() {
if (this._running) {
this._running = false;
clearInterval(this._intervalId);
if (this._intervalId != null) {
PercentHelper.deletePercentValue(this._intervalId.toString());
}
}
}
static async callFunctionAsync(fn, action, delay) {
if (action == null) {
return await fn();
}
const waitAndCallAction = new WaitAndCallAction(action, delay);
const percentId = waitAndCallAction.start();
try {
return await fn(percentId);
}
finally {
waitAndCallAction.stop();
}
}
static callFunction(fn, action, delay) {
if (action == null) {
return fn();
}
const waitAndCallAction = new WaitAndCallAction(action, delay);
const percentId = waitAndCallAction.start();
const result = fn(percentId);
waitAndCallAction.stop();
return result;
}
}
function convertToNumber(value) {
if (value == null) {
return undefined;
}
const number = parseInt(value.toString());
if (isNaN(number)) {
return undefined;
}
return number;
}
class IdArray {
constructor() { }
static getTillId(collection, toId, attributeName = 'id') {
const result = [];
toId = toId.toString();
// Iterate over the collection and break when the toId is found.
for (const elem of collection) {
// Add the element to the result array.
result.push(elem);
// Break the loop if the element ID matches the toId.
const attribute = elem.getAttribute(attributeName);
if (attribute?.replace('sid-', '') === toId) {
break;
}
}
return result;
}
static getSinceId(collection, fromId, attributeName = 'id') {
// Convert the collection to an array and reverse it for processing from the end
const array = [...collection];
array.reverse();
// Initialize an empty result array to store elements with IDs greater than or equal to fromId
const result = [];
fromId = fromId.toString();
// Iterate over the reversed array
for (const elem of array) {
// Add the current element to the result array
result.push(elem);
// If the current element's ID matches fromId, stop processing further
const attribute = elem.getAttribute(attributeName);
if (attribute?.replace('sid-', '') === fromId) {
break;
}
}
// Reverse the result array to maintain the original order
result.reverse();
return result;
}
static getBetweenIds(collection, fromId, toId, attributeName = 'id') {
const array = collection;
let startIndex = -1; // Index of the first element with ID equal to or greater than fromId
let endIndex = -1; // Index of the last element with ID equal to or less than toId
fromId = fromId.toString();
toId = toId.toString();
// Iterate through the array and find the indices of the first and last elements with IDs within the range
for (let i = 0; i < array.length; i++) {
const attribute = array[i].getAttribute(attributeName);
if (attribute?.replace('sid-', '') === fromId) {
startIndex = i;
}
if (attribute?.replace('sid-', '') === toId) {
endIndex = i;
}
// If both indices are found, break the loop
if (startIndex !== -1 && endIndex !== -1) {
break;
}
}
// If both indices are still -1, return the entire array
if (startIndex === -1 && endIndex === -1) {
return array;
}
// If only one index is -1, set it to the other extreme value
if (startIndex === -1) {
startIndex = 0;
}
if (endIndex === -1) {
endIndex = array.length - 1;
}
// Extract the elements between the start and end indices
const result = [];
for (let i = startIndex; i <= endIndex; i++) {
result.push(array[i]);
}
return result;
}
static containsId(collection, id, attributeName = 'id') {
id = id.toString();
for (const elem of collection) {
// The id attribute is a string, so we need to remove the "sid-" prefix to compare it to the given id
const attribute = elem.getAttribute(attributeName);
if (attribute?.replace('sid-', '') === id) {
return true;
}
}
return false;
}
}
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["Error"] = 1] = "Error";
LogLevel[LogLevel["Warning"] = 2] = "Warning";
LogLevel[LogLevel["Info"] = 3] = "Info";
})(LogLevel || (LogLevel = {}));
class Logger {
static get _logLevel() {
window.__FF_GLOBAL_LOG_LEVEL__ ??= LogLevel.Error;
return window.__FF_GLOBAL_LOG_LEVEL__;
}
static setLogLevel(logLevel) {
window.__FF_GLOBAL_LOG_LEVEL__ = logLevel;
}
static get logError() {
return LogLevel.Error <= Logger._logLevel ? console.error.bind(console) : () => { };
}
static get logWarning() {
return LogLevel.Warning <= Logger._logLevel ? console.warn.bind(console) : () => { };
}
static get logInfo() {
return LogLevel.Info <= Logger._logLevel ? console.log.bind(console) : () => { };
}
}
async function elementsTillId(getElements, toId, fromPage) {
if (toId == null || toId <= 0) {
Logger.logError('No toId given');
return [];
}
const allElements = [];
let lastElementId;
let running = true;
let i = (fromPage != null && fromPage >= 1) ? fromPage : 1;
while (running) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
running = false;
continue;
}
let currElementId = lastElementId;
if (elements.length !== 0) {
currElementId = elements[0].id;
}
if (currElementId === lastElementId) {
running = false;
}
else {
if (IdArray.containsId(elements, toId)) {
allElements.push(IdArray.getTillId(elements, toId));
running = false;
}
else {
allElements.push(elements);
i++;
}
}
}
return allElements;
}
async function elementsSinceId(getElements, fromId, toPage) {
if (fromId == null || fromId <= 0) {
Logger.logError('No fromId given');
return [];
}
const direction = toPage == null || toPage <= 0 ? -1 : 1;
let lastElementId;
let running = true;
let i = toPage == null || toPage <= 0 ? 1 : toPage;
if (toPage == null || toPage <= 0) {
while (running) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
running = false;
continue;
}
let currElementId = lastElementId;
if (elements.length !== 0) {
currElementId = elements[0].id;
}
if (currElementId === lastElementId) {
running = false;
}
else {
if (IdArray.containsId(elements, fromId)) {
running = false;
}
else {
i++;
}
}
}
}
const allElements = [];
lastElementId = undefined;
running = true;
while (running) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
running = false;
continue;
}
let currElementId = lastElementId;
if (elements.length !== 0) {
currElementId = elements[0].id;
}
if (currElementId === lastElementId) {
running = false;
}
else {
if (IdArray.containsId(elements, fromId)) {
const elementsPush = IdArray.getSinceId(elements, fromId);
if (direction < 0) {
elementsPush.reverse();
running = false;
}
allElements.push(elementsPush);
}
else {
if (direction < 0) {
elements.reverse();
}
allElements.push(elements);
}
i += direction;
}
}
if (direction < 0) {
allElements.reverse();
}
return allElements;
}
async function elementsBetweenIds(getElements, fromId, toId, fromPage, toPage, percentId) {
if (fromId == null || fromId <= 0) {
Logger.logError('No fromId given');
return [];
}
if (toId == null || toId <= 0) {
Logger.logError('No toId given');
return [];
}
if (fromPage == null || fromPage <= 0 || toPage == null || toPage <= 1) {
Logger.logWarning('No fromPage or toPage given. Percentages can not be calculated.');
percentId = undefined;
}
let i = (fromPage != null && fromPage >= 1) ? fromPage : 1;
const allElements = [];
let lastElementId;
let running = true;
let completedPages = 0;
while (running) {
if (toPage != null && toPage >= 1 && i >= toPage) {
running = false;
}
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
running = false;
continue;
}
let currElementId = lastElementId;
if (elements.length !== 0) {
currElementId = elements[0].id;
}
if (currElementId === lastElementId) {
running = false;
}
else {
if (IdArray.containsId(elements, fromId)) {
allElements.push(IdArray.getSinceId(elements, fromId));
}
if (IdArray.containsId(elements, toId)) {
allElements.push(IdArray.getBetweenIds(elements, fromId, toId));
running = false;
}
else {
allElements.push(elements);
i++;
}
}
completedPages++;
if (toPage != null && toPage >= 1) {
PercentHelper.updatePercentValue(percentId, completedPages, toPage);
}
}
return allElements;
}
async function elementsTillPage(getElements, toPage, percentId) {
if (toPage == null || toPage === 0) {
Logger.logWarning('toPage must be greater than 0. Using default 1 instead.');
toPage = 1;
}
else if (toPage < 0) {
toPage = Number.MAX_SAFE_INTEGER;
}
const allElements = [];
let completedPages = 0;
for (let i = 1; i <= toPage; i++) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
break;
}
if (elements.length === 0) {
i = toPage;
}
else {
allElements.push(elements);
}
completedPages++;
PercentHelper.updatePercentValue(percentId, completedPages, toPage);
}
return allElements;
}
async function elementsSincePage(getElements, fromPage) {
if (fromPage == null || fromPage <= 0) {
Logger.logWarning('fromPage must be greater than 0. Using default 1 instead.');
fromPage = 1;
}
const allElements = [];
let lastElementId;
let running = true;
let i = fromPage;
while (running) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
running = false;
continue;
}
let currElementId = lastElementId;
if (elements.length !== 0) {
currElementId = elements[0].id;
}
if (currElementId === lastElementId) {
running = false;
}
else {
allElements.push(elements);
i++;
}
}
return allElements;
}
async function elementsBetweenPages(getElements, fromPage, toPage, percentId) {
if (fromPage == null || fromPage <= 0) {
Logger.logWarning('fromPage must be greater than 0. Using default 1 instead.');
fromPage = 1;
}
if (toPage == null || toPage === 0) {
Logger.logWarning('toPage must be greater than 0. Using default 1 instead.');
toPage = 1;
}
else if (toPage < 0) {
toPage = Number.MAX_SAFE_INTEGER;
}
const allElements = [];
const direction = fromPage <= toPage ? 1 : -1;
const totalPages = Math.abs(toPage - fromPage) + 1;
let completedPages = 0;
for (let i = fromPage; i <= toPage; i += direction) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
break;
}
if (elements.length === 0) {
i = toPage;
}
else {
allElements.push(elements);
}
completedPages++;
PercentHelper.updatePercentValue(percentId, completedPages, totalPages);
}
return allElements;
}
async function findElementPageNo(getElements, elementId, idPrefix, fromPage, toPage, percentId) {
if (elementId == null || elementId <= 0) {
Logger.logError('No elementId given');
return -1;
}
if (fromPage == null || fromPage <= 0) {
Logger.logWarning('fromPage must be greater than 0. Using default 1 instead.');
fromPage = 1;
}
if (toPage == null || toPage === 0) {
Logger.logWarning('toPage must be greater than 0. Using default 1 instead.');
toPage = 1;
}
else if (toPage < 0) {
toPage = Number.MAX_SAFE_INTEGER;
}
const direction = fromPage <= toPage ? 1 : -1;
const totalPages = Math.abs(toPage - fromPage) + 1;
let completedPages = 0;
for (let i = fromPage; i <= toPage; i += direction) {
let elements = [];
try {
elements = await getElements(i);
}
catch (error) {
Logger.logError(`Failed to fetch page ${i}:`, error);
continue;
}
if (elements.length === 0) {
break;
}
else {
const resultElement = elements.find(el => el.id.trimStart(idPrefix) === elementId.toString());
if (resultElement != null) {
return i;
}
}
completedPages++;
PercentHelper.updatePercentValue(percentId, completedPages, totalPages);
}
return -1;
}
function checkTags(element) {
const userLoggedIn = document.body.getAttribute('data-user-logged-in') === '1';
if (!userLoggedIn) {
Logger.logWarning('User is not logged in, skipping tag check');
setBlockedState(element, false);
return;
}
const tagsHideMissingTags = document.body.getAttribute('data-tag-blocklist-hide-tagless') === '1';
const tags = element.getAttribute('data-tags')?.trim().split(/\s+/);
let blockReason = '';
if (tags != null && tags.length > 0 && tags[0] !== '') {
// image has tags
const blockedTags = getBannedTags(tags);
if (blockedTags.length <= 0) {
setBlockedState(element, false);
}
else {
setBlockedState(element, true);
Logger.logInfo(`${element.id} blocked tags: ${blockedTags.join(', ')}`);
// provide hint
blockReason = 'Blocked tags:\n';
for (const tag of blockedTags) {
blockReason += '• ' + tag + '\n';
}
}
}
else {
// image has no tags
setBlockedState(element, tagsHideMissingTags);
// provide hint
if (tagsHideMissingTags) {
blockReason = 'Content is missing tags.';
}
}
if (blockReason !== '' && element.id !== 'submissionImg') {
// apply hint to everything but main image on submission view page
//element.setAttribute('data-block-reason', block_reason);
element.setAttribute('title', blockReason);
}
}
function getBannedTags(tags) {
const blockedTags = document.body.getAttribute('data-tag-blocklist') ?? '';
const tagsBlocklist = Array.from(blockedTags.split(' '));
let bTags = [];
if (tags == null || tags.length === 0) {
return [];
}
for (const tag of tags) {
for (const blockedTag of tagsBlocklist) {
if (tag === blockedTag) {
bTags.push(blockedTag);
}
}
}
// Remove dupes and return
return [...new Set(bTags)];
}
function setBlockedState(element, isBlocked) {
element.classList[isBlocked ? 'add' : 'remove']('blocked-content');
}
function checkTagsAll(doc) {
if (doc == null) {
return;
}
const uploads = doc.querySelectorAll('img[data-tags]');
uploads.forEach((element) => checkTags(element));
}
class Gallery {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/gallery/';
}
static async fetchPage(username, folder, pageNumber, semaphore, signal) {
if (username == null) {
Logger.logError('Cannot fetch gallery page: no username given');
throw new Error('Cannot fetch gallery page: no username given');
}
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('No page number given. Using default value of 1.');
pageNumber = 1;
}
if (!username.endsWith('/')) {
username += '/';
}
let url = Gallery.hardLink + username;
if (folder != null) {
url += `folder/${folder.id}/`;
if (folder.name != null) {
url += `${folder.name}/`;
}
}
const page = await FuraffinityRequests.getHTML(url + pageNumber, semaphore, signal);
checkTagsAll(page);
return page;
}
async _fetchFigures(username, folder, pageNumber, signal) {
if (pageNumber == null || pageNumber <= 0) {
pageNumber = 1;
}
Logger.logInfo(`Getting gallery of "${username}" on page "${pageNumber}".`);
const galleryDoc = await Gallery.fetchPage(username, folder, pageNumber, this._semaphore, signal);
if (!galleryDoc || !(galleryDoc instanceof Document) || galleryDoc.getElementById('no-images')) {
Logger.logInfo(`No images found at gallery of "${username}" on page "${pageNumber}".`);
return [];
}
const figures = galleryDoc.getElementsByTagName('figure');
if (figures == null || figures.length === 0) {
Logger.logInfo(`No figures found at gallery of "${username}" on page "${pageNumber}".`);
return [];
}
return Array.from(figures);
}
async getSubmissionPageNo(username, submissionId, folder, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
submissionId = convertToNumber(submissionId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
return await WaitAndCallAction.callFunctionAsync((percentId) => findElementPageNo((page) => this._fetchFigures(username, folder, page, signal), submissionId, 'sid-', fromPageNumber, toPageNumber, percentId), action, delay);
}
async getFiguresBetweenIds(username, fromId, toId, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((page) => this._fetchFigures(username, undefined, page, signal), toId, undefined), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((page) => this._fetchFigures(username, undefined, page, signal), fromId, undefined), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenIds((page) => this._fetchFigures(username, undefined, page, signal), fromId, toId, undefined, undefined, percentId), action, delay);
}
}
async getFiguresInFolderBetweenIds(username, folder, fromId, toId, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((page) => this._fetchFigures(username, folder, page, signal), toId, undefined), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((page) => this._fetchFigures(username, folder, page, signal), fromId, undefined), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenIds((page) => this._fetchFigures(username, folder, page, signal), fromId, toId, undefined, undefined, percentId), action, delay);
}
}
async getFiguresBetweenIdsBetweenPages(username, fromId, toId, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((page) => this._fetchFigures(username, undefined, page, signal), toId, fromPageNumber), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((page) => this._fetchFigures(username, undefined, page, signal), fromId, toPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenIds((page) => this._fetchFigures(username, undefined, page, signal), fromId, toId, fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getFiguresInFolderBetweenIdsBetweenPages(username, folder, fromId, toId, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((page) => this._fetchFigures(username, folder, page, signal), toId, fromPageNumber), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((page) => this._fetchFigures(username, folder, page, signal), fromId, toPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenIds((page) => this._fetchFigures(username, folder, page, signal), fromId, toId, fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getFiguresBetweenPages(username, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromPageNumber == null || fromPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsTillPage((page) => this._fetchFigures(username, undefined, page, signal), toPageNumber, percentId), action, delay);
}
else if (toPageNumber == null || toPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSincePage((page) => this._fetchFigures(username, undefined, page, signal), fromPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenPages((page) => this._fetchFigures(username, undefined, page, signal), fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getFiguresInFolderBetweenPages(username, folder, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromPageNumber == null || fromPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsTillPage((page) => this._fetchFigures(username, folder, page, signal), toPageNumber, percentId), action, delay);
}
else if (toPageNumber == null || toPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSincePage((page) => this._fetchFigures(username, folder, page, signal), fromPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenPages((page) => this._fetchFigures(username, folder, page, signal), fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getFigures(username, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => this._fetchFigures(username, undefined, pageNumber, signal), action, delay);
}
async getFiguresInFolder(username, folder, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => this._fetchFigures(username, folder, pageNumber, signal), action, delay);
}
async getPage(username, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => Gallery.fetchPage(username, undefined, pageNumber, this._semaphore, signal), action, delay);
}
async getPageInFolder(username, folder, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => Gallery.fetchPage(username, folder, pageNumber, this._semaphore, signal), action, delay);
}
}
class Scraps {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/scraps/';
}
static async fetchPage(username, pageNumber, semaphore, signal) {
if (username == null) {
Logger.logError('Cannot fetch scraps page: no username given');
throw new Error('Cannot fetch scraps page: no username given');
}
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('No page number given. Using default value of 1.');
pageNumber = 1;
}
if (!username.endsWith('/')) {
username += '/';
}
const page = await FuraffinityRequests.getHTML(Scraps.hardLink + username + pageNumber, semaphore, signal);
checkTagsAll(page);
return page;
}
async _fetchFigures(username, pageNumber, signal) {
if (pageNumber == null || pageNumber <= 0) {
pageNumber = 1;
}
Logger.logInfo(`Getting scraps of "${username}" on page "${pageNumber}".`);
const galleryDoc = await Scraps.fetchPage(username, pageNumber, this._semaphore, signal);
if (!galleryDoc || !(galleryDoc instanceof Document) || galleryDoc.getElementById('no-images')) {
Logger.logInfo(`No images found at scraps of "${username}" on page "${pageNumber}".`);
return [];
}
const figures = galleryDoc.getElementsByTagName('figure');
if (figures == null || figures.length === 0) {
Logger.logInfo(`No figures found at scraps of "${username}" on page "${pageNumber}".`);
return [];
}
return Array.from(figures);
}
async getSubmissionPageNo(username, submissionId, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
submissionId = convertToNumber(submissionId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
return await WaitAndCallAction.callFunctionAsync((percentId) => findElementPageNo((page) => this._fetchFigures(username, page, signal), submissionId, 'sid-', fromPageNumber, toPageNumber, percentId), action, delay);
}
async getFiguresBetweenIds(username, fromId, toId, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((page) => this._fetchFigures(username, page, signal), toId, undefined), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((page) => this._fetchFigures(username, page, signal), fromId, undefined), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenIds((page) => this._fetchFigures(username, page, signal), fromId, toId, undefined, undefined, percentId), action, delay);
}
}
async getFiguresBetweenIdsBetweenPages(username, fromId, toId, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((page) => this._fetchFigures(username, page, signal), toId, fromPageNumber), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((page) => this._fetchFigures(username, page, signal), fromId, toPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenIds((page) => this._fetchFigures(username, page, signal), fromId, toId, fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getFiguresBetweenPages(username, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromPageNumber == null || fromPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsTillPage((page) => this._fetchFigures(username, page, signal), toPageNumber, percentId), action, delay);
}
else if (toPageNumber == null || toPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSincePage((page) => this._fetchFigures(username, page, signal), fromPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenPages((page) => this._fetchFigures(username, page, signal), fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getFigures(username, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => this._fetchFigures(username, pageNumber, signal), action, delay);
}
async getPage(username, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => Scraps.fetchPage(username, pageNumber, this._semaphore, signal), action, delay);
}
}
class Favorites {
semaphore;
constructor(semaphore) {
this.semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/favorites/';
}
static async fetchPage(username, dataFavId, direction, semaphore, signal) {
if (username == null) {
Logger.logError('Cannot fetch favorites page: no username given');
throw new Error('Cannot fetch favorites page: no username given');
}
if (direction == null) {
Logger.logWarning('No direction given. Using default 1 instead.');
direction = 1;
}
if (!username.endsWith('/')) {
username += '/';
}
let url = Favorites.hardLink;
if (dataFavId != null && dataFavId >= 0) {
url += `${username}${dataFavId}/`;
}
else {
Logger.logWarning('No last data fav id given. Using default 1 instead.');
url += username;
}
if (direction >= 0) {
url += 'next/';
}
else {
url += 'prev/';
}
const page = await FuraffinityRequests.getHTML(url, semaphore, signal);
checkTagsAll(page);
return page;
}
async getSubmissionDataFavId(username, submissionId, fromDataFavId, toDataFavId, maxPageNo, signal, action, delay = DEFAULT_ACTION_DELAY) {
submissionId = convertToNumber(submissionId);
fromDataFavId = convertToNumber(fromDataFavId);
toDataFavId = convertToNumber(toDataFavId);
maxPageNo = convertToNumber(maxPageNo);
return await WaitAndCallAction.callFunctionAsync(() => this._getSubmissionDataFavId(username, submissionId, fromDataFavId, toDataFavId, maxPageNo, signal), action, delay);
}
async getFiguresBetweenIds(username, fromId, toId, maxPageNo, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
maxPageNo = convertToNumber(maxPageNo);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresTillId(username, toId, undefined, maxPageNo, signal), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresSinceId(username, fromId, undefined, maxPageNo, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresBetweenIds(username, fromId, toId, undefined, undefined, maxPageNo, signal), action, delay);
}
}
/** @deprecated Use `getFiguresBetweenIdsBetweenDataIds` instead. */
async getFiguresBetweenIdsBetweenPages(username, fromId, toId, fromDataFavId, toDataFavId, maxPageNo, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await this.getFiguresBetweenIdsBetweenDataIds(username, fromId, toId, fromDataFavId, toDataFavId, maxPageNo, signal, action, delay);
}
async getFiguresBetweenIdsBetweenDataIds(username, fromId, toId, fromDataFavId, toDataFavId, maxPageNo, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromDataFavId = convertToNumber(fromDataFavId);
toDataFavId = convertToNumber(toDataFavId);
maxPageNo = convertToNumber(maxPageNo);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresTillId(username, toId, fromDataFavId, maxPageNo, signal), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresSinceId(username, fromId, toDataFavId, maxPageNo, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresBetweenIds(username, fromId, toId, fromDataFavId, toDataFavId, maxPageNo, signal), action, delay);
}
}
async getFiguresBetweenPages(username, fromDataFavId, toDataFavId, maxPageNo, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromDataFavId = convertToNumber(fromDataFavId);
toDataFavId = convertToNumber(toDataFavId);
maxPageNo = convertToNumber(maxPageNo);
if (fromDataFavId == null || fromDataFavId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresTillPage(username, toDataFavId, maxPageNo, signal), action, delay);
}
else if (toDataFavId == null || toDataFavId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresSincePage(username, fromDataFavId, maxPageNo, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync(() => this._getFiguresBetweenPages(username, fromDataFavId, toDataFavId, maxPageNo, signal), action, delay);
}
}
async getFigures(username, fromDataFavId, direction, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromDataFavId = convertToNumber(fromDataFavId);
direction = convertToNumber(direction);
return await WaitAndCallAction.callFunctionAsync(() => this._getFigures(username, fromDataFavId, direction, signal), action, delay);
}
async getPage(username, fromDataFavId, direction, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromDataFavId = convertToNumber(fromDataFavId);
direction = convertToNumber(direction);
return await WaitAndCallAction.callFunctionAsync(() => Favorites.fetchPage(username, fromDataFavId, direction, this.semaphore, signal), action, delay);
}
async _getSubmissionDataFavId(username, submissionId, fromDataFavId, toDataFavId, maxPageNo, signal) {
if (submissionId == null || submissionId <= 0) {
Logger.logError('No submissionId given');
throw new Error('No submissionId given');
}
if (fromDataFavId == null || fromDataFavId <= 0) {
Logger.logWarning('fromDataFavId must be greater than 0. Using default 1 instead.');
fromDataFavId = -1;
}
if (toDataFavId == null || toDataFavId <= 0) {
Logger.logWarning('toDataFavId must be greater than 0. Using default 1 instead.');
toDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
let dataFavId = fromDataFavId;
let lastFigureId;
let running = true;
let i = 0;
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, 1, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
const resultFigure = figures.find(figure => figure.id.trimStart('sid-') === submissionId.toString());
if (resultFigure != null) {
return parseInt(resultFigure.getAttribute('data-fav-id'));
}
}
if (currFigureId === lastFigureId) {
running = false;
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
return -1;
}
async _getFiguresTillId(username, toId, fromDataFavId, maxPageNo, signal) {
if (toId == null || toId <= 0) {
Logger.logError('No toId given');
throw new Error('No toId given');
}
if (fromDataFavId == null || fromDataFavId <= 0) {
Logger.logWarning('No fromDataFavId given. Using default 1 instead.');
fromDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
let running = true;
let dataFavId = fromDataFavId;
const allFigures = [];
let lastFigureId;
let i = 0;
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, 1, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (IdArray.containsId(figures, toId)) {
allFigures.push(IdArray.getTillId(figures, toId));
running = false;
}
else {
allFigures.push(figures);
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
return allFigures;
}
async _getFiguresSinceId(username, fromId, toDataFavId, maxPageNo, signal) {
if (fromId == null || fromId <= 0) {
Logger.logError('No fromId given');
throw new Error('No fromId given');
}
if (toDataFavId == null || toDataFavId <= 0) {
Logger.logWarning('No toDataFavId given. Using default 1 instead.');
toDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
let dataFavId = toDataFavId >= 0 ? toDataFavId : -1;
const direction = toDataFavId >= 0 ? -1 : 1;
let lastFigureId;
let running = true;
let i = 0;
if (toDataFavId < 0) {
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, direction, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (IdArray.containsId(figures, fromId)) {
running = false;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
running = true;
i = 0;
}
const allFigures = [];
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, direction, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = direction >= 0 ? figures[figures.length - 1].getAttribute('data-fav-id') : figures[0].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (direction < 0) {
if (IdArray.containsId(figures, fromId)) {
allFigures.push(IdArray.getSinceId(figures, fromId).reverse());
running = false;
}
else {
allFigures.push(Array.from(figures).reverse());
}
}
else {
if (IdArray.containsId(figures, toDataFavId, 'data-fav-id')) {
allFigures.push(IdArray.getTillId(figures, toDataFavId, 'data-fav-id'));
running = false;
}
else {
allFigures.push(figures);
}
}
}
i++;
}
if (direction < 0) {
allFigures.reverse();
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
return allFigures;
}
async _getFiguresBetweenIds(username, fromId, toId, fromDataFavId, toDataFavId, maxPageNo, signal) {
if (fromId == null || fromId <= 0) {
Logger.logError('No fromId given');
throw new Error('No fromId given');
}
if (toId == null || toId <= 0) {
Logger.logError('No toId given');
throw new Error('No toId given');
}
if (fromDataFavId == null || fromDataFavId <= 0) {
Logger.logWarning('No fromDataFavId given. Using default 1 instead.');
fromDataFavId = -1;
}
if (toDataFavId == null || toDataFavId <= 0) {
Logger.logWarning('No toDataFavId given. Using default 1 instead.');
toDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
const direction = fromDataFavId >= 0 ? 1 : (toDataFavId >= 0 ? -1 : 1);
let dataFavId = fromDataFavId >= 0 ? fromDataFavId : toDataFavId;
let lastFigureId;
let running = true;
let i = 0;
if (fromDataFavId < 0 && toDataFavId < 0) {
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, direction, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (IdArray.containsId(figures, fromId)) {
running = false;
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
running = true;
i = 0;
}
const allFigures = [];
lastFigureId = undefined;
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, direction, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = direction >= 0 ? figures[figures.length - 1].getAttribute('data-fav-id') : figures[0].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (direction < 0) {
if (IdArray.containsId(figures, fromId)) {
allFigures.push(IdArray.getSinceId(figures, fromId).reverse());
running = false;
}
else if (IdArray.containsId(figures, toId)) {
allFigures.push(IdArray.getTillId(figures, toId).reverse());
}
else {
allFigures.push(Array.from(figures).reverse());
}
}
else {
if (IdArray.containsId(figures, toId)) {
allFigures.push(IdArray.getTillId(figures, toId));
running = false;
}
else if (IdArray.containsId(figures, fromId)) {
allFigures.push(IdArray.getSinceId(figures, fromId));
}
else {
allFigures.push(figures);
}
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
if (direction < 0) {
allFigures.reverse();
}
return allFigures;
}
async _getFiguresTillPage(username, toDataFavId, maxPageNo, signal) {
if (toDataFavId == null || toDataFavId <= 0) {
Logger.logWarning('toDataFavId must be greater than 0. Using default 1 instead.');
toDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
let dataFavId = toDataFavId;
const allFigures = [];
let lastFigureId;
let running = true;
let i = 0;
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, 1, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (IdArray.containsId(figures, toDataFavId, 'data-fav-id')) {
allFigures.push(IdArray.getTillId(figures, toDataFavId, 'data-fav-id'));
running = false;
}
else {
allFigures.push(figures);
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
return allFigures;
}
async _getFiguresSincePage(username, fromDataFavId, maxPageNo, signal) {
if (fromDataFavId == null || fromDataFavId <= 0) {
Logger.logWarning('fromDataFavId must be greater than 0. Using default 1 instead.');
fromDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
let dataFavId = fromDataFavId;
const allFigures = [];
let lastFigureId;
let running = true;
let i = 0;
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, 1, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (IdArray.containsId(figures, fromDataFavId, 'data-fav-id')) {
allFigures.push(IdArray.getSinceId(figures, fromDataFavId, 'data-fav-id'));
}
else {
allFigures.push(figures);
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
return allFigures;
}
async _getFiguresBetweenPages(username, fromDataFavId, toDataFavId, maxPageNo, signal) {
if (fromDataFavId == null || fromDataFavId <= 0) {
Logger.logWarning('fromDataFavId must be greater than 0. Using default 1 instead.');
fromDataFavId = -1;
}
if (toDataFavId == null || toDataFavId <= 0) {
Logger.logWarning('toDataFavId must be greater than 0. Using default 1 instead.');
toDataFavId = -1;
}
if (maxPageNo == null || maxPageNo <= 0) {
Logger.logWarning('maxPageNo must be greater than 0. Using default ' + Number.MAX_SAFE_INTEGER + ' instead.');
maxPageNo = Number.MAX_SAFE_INTEGER;
}
let dataFavId = fromDataFavId;
const allFigures = [];
let lastFigureId;
let running = true;
let i = 0;
while (running && i < maxPageNo) {
const figures = await this._getFigures(username, dataFavId, 1, signal);
let currFigureId = lastFigureId;
if (figures.length !== 0) {
currFigureId = figures[0].id;
const dataFavIdString = figures[figures.length - 1].getAttribute('data-fav-id');
if (dataFavIdString == null) {
running = false;
break;
}
dataFavId = parseInt(dataFavIdString);
}
if (currFigureId === lastFigureId) {
running = false;
}
else {
if (IdArray.containsId(figures, fromDataFavId, 'data-fav-id')) {
allFigures.push(IdArray.getSinceId(figures, fromDataFavId, 'data-fav-id'));
}
else if (IdArray.containsId(figures, toDataFavId, 'data-fav-id')) {
allFigures.push(IdArray.getTillId(figures, toDataFavId, 'data-fav-id'));
running = false;
}
else {
allFigures.push(figures);
}
}
i++;
}
if (i >= maxPageNo) {
Logger.logWarning('Max page number reached. Aborting.');
}
return allFigures;
}
async _getFigures(username, dataFavId, direction, signal) {
Logger.logInfo(`Getting Favorites of "${username}" since id "${dataFavId}" and direction "${direction}".`);
const galleryDoc = await Favorites.fetchPage(username, dataFavId, direction, this.semaphore, signal);
if (!galleryDoc || !(galleryDoc instanceof Document) || galleryDoc.getElementById('no-images')) {
Logger.logInfo(`No images found at favorites of "${username}" on page "${dataFavId}".`);
return [];
}
const figures = galleryDoc.getElementsByTagName('figure');
if (figures == null || figures.length === 0) {
Logger.logInfo(`No figures found at favorites of "${username}" on page "${dataFavId}".`);
return [];
}
return Array.from(figures);
}
}
class Journals {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/journals/';
}
static async fetchPage(username, pageNumber, semaphore, signal) {
if (username == null) {
Logger.logError('Cannot fetch journals page: no username given');
throw new Error('Cannot fetch journals page: no username given');
}
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('Page number must be greater than 0. Using default 1 instead.');
pageNumber = 1;
}
if (!username.endsWith('/')) {
username += '/';
}
const url = Journals.hardLink + username;
return await FuraffinityRequests.getHTML(url + pageNumber, semaphore, signal);
}
async getJournalPageNo(username, journalId, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
journalId = convertToNumber(journalId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
return await WaitAndCallAction.callFunctionAsync((percentId) => findElementPageNo((pg) => this._getSections(username, pg, signal), journalId, 'jid-', fromPageNumber, toPageNumber, percentId), action, delay);
}
async getFiguresBetweenIds(username, fromId, toId, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((pg) => this._getSections(username, pg, signal), toId, undefined), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((pg) => this._getSections(username, pg, signal), fromId, undefined), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync(() => elementsBetweenIds((pg) => this._getSections(username, pg, signal), fromId, toId, undefined, undefined), action, delay);
}
}
async getFiguresBetweenIdsBetweenPages(username, fromId, toId, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsTillId((pg) => this._getSections(username, pg, signal), toId, fromPageNumber), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSinceId((pg) => this._getSections(username, pg, signal), fromId, toPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync(() => elementsBetweenIds((pg) => this._getSections(username, pg, signal), fromId, toId, fromPageNumber, toPageNumber), action, delay);
}
}
async getSectionsBetweenPages(username, fromPageNumber, toPageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromPageNumber == null || fromPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsTillPage((pg) => this._getSections(username, pg, signal), toPageNumber, percentId), action, delay);
}
else if (toPageNumber == null || toPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => elementsSincePage((pg) => this._getSections(username, pg, signal), fromPageNumber), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => elementsBetweenPages((pg) => this._getSections(username, pg, signal), fromPageNumber, toPageNumber, percentId), action, delay);
}
}
async getSections(username, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => this._getSections(username, pageNumber, signal), action, delay);
}
async getPage(username, pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => Journals.fetchPage(username, pageNumber, this._semaphore, signal), action, delay);
}
async _getSections(username, pageNumber, signal) {
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('pageNumber must be greater than 0. Using default 1 instead.');
pageNumber = 1;
}
Logger.logInfo(`Getting Journals of "${username}" on page "${pageNumber}".`);
const galleryDoc = await Journals.fetchPage(username, pageNumber, this._semaphore, signal);
if (!galleryDoc) {
Logger.logWarning(`No journals found at "${username}" on page "${pageNumber}".`);
return [];
}
const columnPage = galleryDoc.getElementById('columnpage');
if (!columnPage) {
Logger.logWarning(`No column page found at "${username}" on page "${pageNumber}".`);
return [];
}
const sections = columnPage.getElementsByTagName('section');
if (sections == null || sections.length === 0) {
Logger.logWarning(`No journals found at "${username}" on page "${pageNumber}".`);
return [];
}
return Array.from(sections);
}
}
class GalleryRequests {
Gallery;
Scraps;
Favorites;
Journals;
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
this.Gallery = new Gallery(this._semaphore);
this.Scraps = new Scraps(this._semaphore);
this.Favorites = new Favorites(this._semaphore);
this.Journals = new Journals(this._semaphore);
}
}
class Browse {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/browse/';
}
static async fetchPage(pageNumber, browseOptions, semaphore, signal) {
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('Page number must be greater than 0. Using default 1 instead.');
pageNumber = 1;
}
browseOptions ??= new BrowseOptions();
const payload = {
'cat': browseOptions.category,
'atype': browseOptions.type,
'species': browseOptions.species,
'gender': browseOptions.gender,
'perpage': browseOptions.perPage,
'page': pageNumber,
'rating_general': browseOptions.ratingGeneral ? 'on' : 'off',
'rating_mature': browseOptions.ratingMature ? 'on' : 'off',
'rating_adult': browseOptions.ratingAdult ? 'on' : 'off',
};
for (const key in payload) {
if (payload[key] == null || payload[key] === 0 || payload[key] === 'off') {
delete payload[key];
}
}
const payloadArray = Object.entries(payload).map(([key, value]) => [key, value?.toString() ?? '']);
const url = Browse.hardLink;
const page = await FuraffinityRequests.postHTML(url, payloadArray, semaphore, signal);
checkTagsAll(page);
return page;
}
get newBrowseOptions() {
return new BrowseOptions();
}
static get newBrowseOptions() {
return new BrowseOptions();
}
get BrowseOptions() {
return BrowseOptions;
}
static get BrowseOptions() {
return BrowseOptions;
}
async getFiguresBetweenIds(fromId, toId, browseOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getBrowseFiguresTillId(toId, undefined, browseOptions, this._semaphore, signal), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getBrowseFiguresSinceId(fromId, undefined, browseOptions, this._semaphore, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getBrowseFiguresBetweenIds(fromId, toId, undefined, undefined, browseOptions, this._semaphore, signal, percentId), action, delay);
}
}
async getFiguresBetweenIdsBetweenPages(fromId, toId, fromPageNumber, toPageNumber, browseOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getBrowseFiguresTillId(toId, fromPageNumber, browseOptions, this._semaphore, signal), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getBrowseFiguresSinceId(fromId, toPageNumber, browseOptions, this._semaphore, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getBrowseFiguresBetweenIds(fromId, toId, fromPageNumber, toPageNumber, browseOptions, this._semaphore, signal, percentId), action, delay);
}
}
async getFiguresBetweenPages(fromPageNumber, toPageNumber, browseOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromPageNumber == null || fromPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getBrowseFiguresTillPage(toPageNumber, browseOptions, this._semaphore, signal, percentId), action, delay);
}
else if (toPageNumber == null || toPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getBrowseFiguresSincePage(fromPageNumber, browseOptions, this._semaphore, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getBrowseFiguresBetweenPages(fromPageNumber, toPageNumber, browseOptions, this._semaphore, signal, percentId), action, delay);
}
}
async getFigures(pageNumber, browseOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getBrowseFigures(pageNumber, browseOptions, this._semaphore, signal), action, delay);
}
async getPage(pageNumber, browseOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => Browse.fetchPage(pageNumber, browseOptions, this._semaphore, signal), action, delay);
}
}
class BrowseOptions {
category;
type;
species;
gender;
perPage;
ratingGeneral = true;
ratingMature = true;
ratingAdult = true;
constructor() {
this.category = BrowseOptions.category['all'];
this.type = BrowseOptions.type['all'];
this.species = BrowseOptions.species['any'];
this.gender = BrowseOptions.gender['any'];
this.perPage = BrowseOptions.results['72'];
}
static category = {
'all': 1,
'3d-models': 34,
'artwork-digital': 2,
'artwork-traditional': 3,
'cel-shading': 4,
'crafting': 5,
'designs': 6,
'food-recipes': 32,
'fursuiting': 8,
'icons': 9,
'mosaics': 10,
'photography': 11,
'pixel-art': 36,
'sculpting': 12,
'virtual-photography': 35,
'2d-animation': 37,
'3d-animation': 38,
'pixel-animation': 39,
'flash': 7,
'interactive-media': 40,
'story': 13,
'poetry': 14,
'prose': 15,
'music': 16,
'podcasts': 17,
'skins': 18,
'handhelds': 19,
'resources': 20,
'adoptables': 21,
'auctions': 22,
'contests': 23,
'current-events': 24,
'desktops': 25,
'stockart': 26,
'screenshots': 27,
'scraps': 28,
'wallpaper': 29,
'ych-sale': 30,
'other': 31
};
static type = {
'all': 1,
'abstract': 2,
'animal-related-non-anthro': 3,
'anime': 4,
'comics': 5,
'doodle': 6,
'fanart': 7,
'fantasy': 8,
'human': 9,
'portraits': 10,
'scenery': 11,
'still-life': 12,
'tutorials': 13,
'miscellaneous': 14,
'general-furry-art': 100,
'abduction': 122,
'baby-fur': 101,
'bondage': 102,
'digimon': 103,
'fat-furs': 104,
'fetish-other': 105,
'fursuit': 106,
'gore': 119,
'hyper': 107,
'hypnosis': 121,
'inflation': 108,
'micro': 109,
'muscle': 110,
'my-little-pony': 111,
'paw': 112,
'pokemon': 113,
'pregnancy': 114,
'sonic': 115,
'transformation': 116,
'tf-tg': 120,
'vore': 117,
'water-sports': 118,
'techno': 201,
'trance': 202,
'house': 203,
'90s': 204,
'80s': 205,
'70s': 206,
'60s': 207,
'pre-60s': 208,
'classical': 209,
'game-music': 210,
'rock': 211,
'pop': 212,
'rap': 213,
'industrial': 214,
'other-music': 200
};
static species = {
'any': 1,
'airborne-vehicle': 10001,
'alien': 5001,
'amphibian': 1000,
'aquatic': 2000,
'avian': 3000,
'bear': 6002,
'bovine': 6007,
'canine': 6017,
'cervine': 6018,
'dog': 6010,
'dragon': 4000,
'equine': 10009,
'exotic': 5000,
'feline': 6030,
'fox': 6075,
'slime': 10007,
'hybrid-species': 10002,
'inanimate': 10006,
'insect': 8003,
'land-vehicle': 10003,
'mammal': 6000,
'marsupial': 6042,
'mustelid': 6051,
'plant': 10008,
'primate': 6058,
'reptilian': 7000,
'robot': 10004,
'rodent': 6067,
'sea-vehicle': 10005,
'taur': 5025,
'vulpine': 6015,
'original-species': 11014,
'character': 11015,
'aeromorph': 11001,
'angel-dragon': 11002,
'avali': 11012,
'chakat': 5003,
'citra': 5005,
'crux': 5006,
'dracat': 5009,
'dutch': 11003,
'felkin': 11011,
'ferrin': 11004,
'jogauni': 11005,
'langurhali': 5014,
'nevrean': 11006,
'protogen': 11007,
'rexouium': 11016,
'sergal': 5021,
'synx': 11010,
'wickerbeast': 11013,
'yinglet': 11009,
'zorgoia': 11008,
'angel': 12001,
'centaur': 12002,
'cerberus': 12003,
'shape-shifter': 12038,
'chimera': 12004,
'chupacabra': 12005,
'cockatrice': 12006,
'daemon': 5007,
'demon': 12007,
'displacer-beast': 12008,
'dragonborn': 12009,
'drow': 12010,
'dwarf': 12011,
'eastern-dragon': 4001,
'elf': 5011,
'gargoyle': 5012,
'goblin': 12012,
'golem': 12013,
'gryphon': 3007,
'harpy': 12014,
'hellhound': 12015,
'hippogriff': 12016,
'hobbit': 12017,
'hydra': 4002,
'imp': 12018,
'incubus': 12019,
'jackalope': 12020,
'kirin': 12021,
'kitsune': 12022,
'kobold': 12023,
'lamia': 12024,
'manticore': 12025,
'minotaur': 12026,
'naga': 5016,
'nephilim': 12027,
'orc': 5018,
'pegasus': 12028,
'peryton': 12029,
'phoenix': 3010,
'sasquatch': 12030,
'satyr': 5020,
'sphinx': 12031,
'succubus': 12032,
'tiefling': 12033,
'troll': 12034,
'unicorn': 5023,
'water-dragon': 12035,
'werewolf': 12036,
'western-dragon': 4004,
'wyvern': 4005,
'yokai': 12037,
'alicorn': 13001,
'argonian': 5002,
'asari': 13002,
'bangaa': 13003,
'bubble-dragon': 13004,
'burmecian': 13005,
'charr': 13006,
'chiss': 13007,
'chocobo': 5004,
'deathclaw': 13008,
'digimon': 5008,
'draenei': 5010,
'drell': 13009,
'elcor': 13010,
'ewok': 13011,
'hanar': 13012,
'hrothgar': 13013,
'iksar': 5013,
'kaiju': 5015,
'kelpie': 13041,
'kemonomimi': 13014,
'khajiit': 13015,
'koopa': 13016,
'krogan': 13017,
'lombax': 13018,
'mimiga': 13019,
'mobian': 13020,
'moogle': 5017,
'neopet': 13021,
'nu-mou': 13022,
'pokemon': 5019,
'pony-mlp': 13023,
'protoss': 13024,
'quarian': 13025,
'ronso': 13026,
'salarian': 13027,
'sangheili': 13028,
'tauntaun': 13029,
'tauren': 13030,
'trandoshan': 13031,
'transformer': 13032,
'turian': 13033,
'twilek': 13034,
'viera': 13035,
'wookiee': 13036,
'xenomorph': 5024,
'yautja': 13037,
'yordle': 13038,
'yoshi': 13039,
'zerg': 13040,
'aardvark': 14001,
'aardwolf': 14002,
'african-wild-dog': 14003,
'akita': 14004,
'albatross': 14005,
'crocodile': 7001,
'alpaca': 14006,
'anaconda': 14007,
'anteater': 14008,
'antelope': 6004,
'arachnid': 8000,
'arctic-fox': 14009,
'armadillo': 14010,
'axolotl': 14011,
'baboon': 14012,
'badger': 6045,
'bat': 6001,
'beaver': 6064,
'bee': 14013,
'binturong': 14014,
'bison': 14015,
'blue-jay': 14016,
'border-collie': 14017,
'brown-bear': 14018,
'buffalo': 14019,
'buffalo-bison': 14020,
'bull-terrier': 14021,
'butterfly': 14022,
'caiman': 14023,
'camel': 6074,
'capybara': 14024,
'caribou': 14025,
'caterpillar': 14026,
'cephalopod': 2001,
'chameleon': 14027,
'cheetah': 6021,
'chicken': 14028,
'chimpanzee': 14029,
'chinchilla': 14030,
'chipmunk': 14031,
'civet': 14032,
'clouded-leopard': 14033,
'coatimundi': 14034,
'cockatiel': 14035,
'corgi': 14036,
'corvid': 3001,
'cougar': 6022,
'cow': 6003,
'coyote': 6008,
'crab': 14037,
'crane': 14038,
'crayfish': 14039,
'crow': 3002,
'crustacean': 14040,
'dalmatian': 14041,
'deer': 14042,
'dhole': 14043,
'dingo': 6011,
'dinosaur': 8001,
'doberman': 6009,
'dolphin': 2002,
'donkey': 6019,
'duck': 3003,
'eagle': 3004,
'eel': 14044,
'elephant': 14045,
'falcon': 3005,
'fennec': 6072,
'ferret': 6046,
'finch': 14046,
'fish': 2005,
'flamingo': 14047,
'fossa': 14048,
'frog': 1001,
'gazelle': 6005,
'gecko': 7003,
'genet': 14049,
'german-shepherd': 6012,
'gibbon': 14050,
'giraffe': 6031,
'goat': 6006,
'goose': 3006,
'gorilla': 6054,
'gray-fox': 14051,
'great-dane': 14052,
'grizzly-bear': 14053,
'guinea-pig': 14054,
'hamster': 14055,
'hawk': 3008,
'hedgehog': 6032,
'heron': 14056,
'hippopotamus': 6033,
'honeybee': 14057,
'horse': 6034,
'housecat': 6020,
'human': 6055,
'humanoid': 14058,
'hummingbird': 14059,
'husky': 6014,
'hyena': 6035,
'iguana': 7004,
'impala': 14060,
'jackal': 6013,
'jaguar': 6023,
'kangaroo': 6038,
'kangaroo-mouse': 14061,
'kangaroo-rat': 14062,
'kinkajou': 14063,
'kit-fox': 14064,
'koala': 6039,
'kodiak-bear': 14065,
'komodo-dragon': 14066,
'labrador': 14067,
'lemur': 6056,
'leopard': 6024,
'liger': 14068,
'linsang': 14069,
'lion': 6025,
'lizard': 7005,
'llama': 6036,
'lobster': 14070,
'longhair-cat': 14071,
'lynx': 6026,
'magpie': 14072,
'maine-coon': 14073,
'malamute': 14074,
'mammal-feline': 14075,
'mammal-herd': 14076,
'mammal-marsupial': 14077,
'mammal-mustelid': 14078,
'mammal-other predator': 14079,
'mammal-prey': 14080,
'mammal-primate': 14081,
'mammal-rodent': 14082,
'manatee': 14083,
'mandrill': 14084,
'maned-wolf': 14085,
'mantid': 8004,
'marmoset': 14086,
'marten': 14087,
'meerkat': 6043,
'mink': 6048,
'mole': 14088,
'mongoose': 6044,
'monitor-lizard': 14089,
'monkey': 6057,
'moose': 14090,
'moth': 14091,
'mouse': 6065,
'musk-deer': 14092,
'musk-ox': 14093,
'newt': 1002,
'ocelot': 6027,
'octopus': 14094,
'okapi': 14095,
'olingo': 14096,
'opossum': 6037,
'orangutan': 14097,
'orca': 14098,
'oryx': 14099,
'ostrich': 14100,
'otter': 6047,
'owl': 3009,
'panda': 6052,
'pangolin': 14101,
'panther': 6028,
'parakeet': 14102,
'parrot': 14103,
'peacock': 14104,
'penguin': 14105,
'persian-cat': 14106,
'pig': 6053,
'pigeon': 14107,
'pika': 14108,
'pine-marten': 14109,
'platypus': 14110,
'polar-bear': 14111,
'pony': 6073,
'poodle': 14112,
'porcupine': 14113,
'porpoise': 2004,
'procyonid': 14114,
'puffin': 14115,
'quoll': 6040,
'rabbit': 6059,
'raccoon': 6060,
'rat': 6061,
'ray': 14116,
'red-fox': 14117,
'red-panda': 6062,
'reindeer': 14118,
'reptillian': 14119,
'rhinoceros': 6063,
'robin': 14120,
'rottweiler': 14121,
'sabercats': 14122,
'sabertooth': 14123,
'salamander': 1003,
'scorpion': 8005,
'seagull': 14124,
'seahorse': 14125,
'seal': 6068,
'secretary-bird': 14126,
'serpent-dragon': 4003,
'serval': 14127,
'shark': 2006,
'sheep': 14128,
'shiba-inu': 14129,
'shorthair-cat': 14130,
'shrew': 14131,
'siamese': 14132,
'sifaka': 14133,
'silver-fox': 14134,
'skunk': 6069,
'sloth': 14135,
'snail': 14136,
'snake-serpent': 7006,
'snow-leopard': 14137,
'sparrow': 14138,
'squid': 14139,
'squirrel': 6070,
'stoat': 14140,
'stork': 14141,
'sugar-glider': 14142,
'sun-bear': 14143,
'swan': 3011,
'swift-fox': 14144,
'tanuki': 5022,
'tapir': 14145,
'tasmanian-devil': 14146,
'thylacine': 14147,
'tiger': 6029,
'toucan': 14148,
'turtle': 7007,
'vulture': 14149,
'wallaby': 6041,
'walrus': 14150,
'wasp': 14151,
'weasel': 6049,
'whale': 2003,
'wolf': 6016,
'wolverine': 6050,
'zebra': 6071
};
static gender = {
'any': '',
'male': 'male',
'female': 'female',
// 'herm': 'herm',
'trans-male': 'trans_male',
'trans-female': 'trans_female',
'intersex': 'intersex',
'non-binary': 'non_binary',
// 'multiple': 'multiple',
// 'other': 'other',
// 'not-specified': 'not-specified'
};
static results = {
'24': 24,
'48': 48,
'72': 72,
'96': 96,
'128': 128
};
}
class Search {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/search/';
}
static async fetchPage(pageNumber, searchOptions, semaphore, signal) {
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('Page number must be greater than 0. Using default 1 instead.');
pageNumber = 1;
}
searchOptions ??= new SearchOptions();
const payload = {
'page': pageNumber,
'q': searchOptions.input,
'perpage': searchOptions.perPage,
'order-by': searchOptions.orderBy,
'order-direction': searchOptions.orderDirection,
'category': searchOptions.category,
'arttype': searchOptions.type,
'species': searchOptions.species,
'range': searchOptions.range,
'range_from': undefined,
'range_to': undefined,
'rating-general': searchOptions.ratingGeneral ? 1 : 0,
'rating-mature': searchOptions.ratingMature ? 1 : 0,
'rating-adult': searchOptions.ratingAdult ? 1 : 0,
'type-art': searchOptions.typeArt ? 1 : 0,
'type-music': searchOptions.typeMusic ? 1 : 0,
'type-flash': searchOptions.typeFlash ? 1 : 0,
'type-story': searchOptions.typeStory ? 1 : 0,
'type-photos': searchOptions.typePhotos ? 1 : 0,
'type-poetry': searchOptions.typePoetry ? 1 : 0,
'mode': searchOptions.matching
};
if (searchOptions.rangeFrom instanceof Date && searchOptions.rangeFrom != null) {
const year = searchOptions.rangeFrom.getFullYear();
const month = (searchOptions.rangeFrom.getMonth() + 1).toString().padStart(2, '0');
const day = searchOptions.rangeFrom.getDate().toString().padStart(2, '0');
const formattedDate = `${year}-${month}-${day}`;
payload['range_from'] = formattedDate;
}
else if (typeof searchOptions.rangeFrom == 'string' && searchOptions.rangeFrom) {
payload['range_from'] = searchOptions.rangeFrom;
}
if (searchOptions.rangeTo instanceof Date && searchOptions.rangeTo != null) {
const year = searchOptions.rangeTo.getFullYear();
const month = (searchOptions.rangeTo.getMonth() + 1).toString().padStart(2, '0');
const day = searchOptions.rangeTo.getDate().toString().padStart(2, '0');
const formattedDate = `${year}-${month}-${day}`;
payload['range_to'] = formattedDate;
}
else if (typeof searchOptions.rangeTo == 'string' && searchOptions.rangeTo) {
payload['range_to'] = searchOptions.rangeTo;
}
for (const key in payload) {
if (payload[key] == null || payload[key] === 0 || payload[key] === 'off') {
delete payload[key];
}
}
const payloadArray = Object.entries(payload).map(([key, value]) => [key, value?.toString() ?? '']);
const url = Search.hardLink;
const page = await FuraffinityRequests.postHTML(url, payloadArray, semaphore, signal);
checkTagsAll(page);
return page;
}
get newSearchOptions() {
return new SearchOptions();
}
static get newSearchOptions() {
return new SearchOptions();
}
get SearchOptions() {
return SearchOptions;
}
static get SearchOptions() {
return SearchOptions;
}
async getFiguresBetweenIds(fromId, toId, searchOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getSearchFiguresTillId(toId, undefined, searchOptions, this._semaphore, signal), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getSearchFiguresSinceId(fromId, undefined, searchOptions, this._semaphore, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getSearchFiguresBetweenIds(fromId, toId, undefined, undefined, searchOptions, this._semaphore, signal, percentId), action, delay);
}
}
async getFiguresBetweenIdsBetweenPages(fromId, toId, fromPageNumber, toPageNumber, searchOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromId = convertToNumber(fromId);
toId = convertToNumber(toId);
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromId == null || fromId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getSearchFiguresTillId(toId, fromPageNumber, searchOptions, this._semaphore, signal), action, delay);
}
else if (toId == null || toId <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getSearchFiguresSinceId(fromId, toPageNumber, searchOptions, this._semaphore, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getSearchFiguresBetweenIds(fromId, toId, fromPageNumber, toPageNumber, searchOptions, this._semaphore, signal, percentId), action, delay);
}
}
async getFiguresBetweenPages(fromPageNumber, toPageNumber, searchOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
fromPageNumber = convertToNumber(fromPageNumber);
toPageNumber = convertToNumber(toPageNumber);
if (fromPageNumber == null || fromPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getSearchFiguresTillPage(toPageNumber, searchOptions, this._semaphore, signal, percentId), action, delay);
}
else if (toPageNumber == null || toPageNumber <= 0) {
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getSearchFiguresSincePage(fromPageNumber, searchOptions, this._semaphore, signal), action, delay);
}
else {
return await WaitAndCallAction.callFunctionAsync((percentId) => SearchRequests.getSearchFiguresBetweenPages(fromPageNumber, toPageNumber, searchOptions, this._semaphore, signal, percentId), action, delay);
}
}
async getFigures(pageNumber, searchOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => SearchRequests.getSearchFigures(pageNumber, searchOptions, this._semaphore, signal), action, delay);
}
async getPage(pageNumber, searchOptions, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => Search.fetchPage(pageNumber, searchOptions, this._semaphore, signal), action, delay);
}
}
class SearchOptions {
input;
perPage;
orderBy;
orderDirection;
category;
type;
species;
range;
rangeFrom;
rangeTo;
ratingGeneral = true;
ratingMature = true;
ratingAdult = true;
typeArt = true;
typeMusic = true;
typeFlash = true;
typeStory = true;
typePhotos = true;
typePoetry = true;
matching;
constructor() {
this.input = '';
this.perPage = 72;
this.orderBy = SearchOptions.orderBy['relevancy'];
this.orderDirection = SearchOptions.orderDirection['descending'];
this.category = BrowseOptions.category['all'];
this.type = BrowseOptions.type['all'];
this.species = BrowseOptions.species['any'];
this.range = SearchOptions.range['alltime'];
this.rangeFrom = undefined;
this.rangeTo = undefined;
this.matching = SearchOptions.matching['all'];
}
static orderBy = {
'relevancy': 'relevancy',
'date': 'date',
'popularity': 'popularity'
};
static orderDirection = {
'ascending': 'asc',
'descending': 'desc'
};
static range = {
'1day': '1day',
'3days': '3days',
'7days': '7days',
'30days': '30days',
'90days': '90days',
'1year': '1year',
'3years': '3years',
'5years': '5years',
'alltime': 'all',
'manual': 'manual'
};
static matching = {
'all': 'all',
'any': 'any',
'extended': 'extended'
};
}
class SearchRequests {
Browse;
Search;
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
this.Browse = new Browse(this._semaphore);
this.Search = new Search(this._semaphore);
}
//#region Browse
static async getBrowseFiguresTillId(toId, fromPage, browseOptions, semaphore, signal) {
return await elementsTillId((page) => SearchRequests.getBrowseFigures(page, browseOptions, semaphore, signal), toId, fromPage);
}
static async getBrowseFiguresSinceId(fromId, toPage, browseOptions, semaphore, signal) {
return await elementsSinceId((page) => SearchRequests.getBrowseFigures(page, browseOptions, semaphore, signal), fromId, toPage);
}
static async getBrowseFiguresBetweenIds(fromId, toId, fromPage, toPage, browseOptions, semaphore, signal, percentId) {
return await elementsBetweenIds((page) => SearchRequests.getBrowseFigures(page, browseOptions, semaphore, signal), fromId, toId, fromPage, toPage, percentId);
}
static async getBrowseFiguresTillPage(toPageNumber, browseOptions, semaphore, signal, percentId) {
return await elementsTillPage((page) => SearchRequests.getBrowseFigures(page, browseOptions, semaphore, signal), toPageNumber, percentId);
}
static async getBrowseFiguresSincePage(fromPageNumber, browseOptions, semaphore, signal) {
return await elementsSincePage((page) => SearchRequests.getBrowseFigures(page, browseOptions, semaphore, signal), fromPageNumber);
}
static async getBrowseFiguresBetweenPages(fromPageNumber, toPageNumber, browseOptions, semaphore, signal, percentId) {
return await elementsBetweenPages((page) => SearchRequests.getBrowseFigures(page, browseOptions, semaphore, signal), fromPageNumber, toPageNumber, percentId);
}
static async getBrowseFigures(pageNumber, browseOptions, semaphore, signal) {
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('No pageNumber given. Using default value of 1.');
pageNumber = 1;
}
const galleryDoc = await Browse.fetchPage(pageNumber, browseOptions, semaphore, signal);
if (galleryDoc == null || !(galleryDoc instanceof Document) || galleryDoc.getElementById('no-images')) {
Logger.logInfo(`No images found at browse on page "${pageNumber}".`);
return [];
}
const figures = galleryDoc.getElementsByTagName('figure');
if (figures == null || figures.length === 0) {
Logger.logInfo(`No figures found at browse on page "${pageNumber}".`);
return [];
}
return Array.from(figures);
}
//#endregion
//#region Search
static async getSearchFiguresTillId(toId, fromPage, searchOptions, semaphore, signal) {
return await elementsTillId((page) => SearchRequests.getSearchFigures(page, searchOptions, semaphore, signal), toId, fromPage);
}
static async getSearchFiguresSinceId(fromId, toPage, searchOptions, semaphore, signal) {
return await elementsSinceId((page) => SearchRequests.getSearchFigures(page, searchOptions, semaphore, signal), fromId, toPage);
}
static async getSearchFiguresBetweenIds(fromId, toId, fromPage, toPage, searchOptions, semaphore, signal, percentId) {
return await elementsBetweenIds((page) => SearchRequests.getSearchFigures(page, searchOptions, semaphore, signal), fromId, toId, fromPage, toPage, percentId);
}
static async getSearchFiguresTillPage(toPageNumber, searchOptions, semaphore, signal, percentId) {
return await elementsTillPage((page) => SearchRequests.getSearchFigures(page, searchOptions, semaphore, signal), toPageNumber, percentId);
}
static async getSearchFiguresSincePage(fromPageNumber, searchOptions, semaphore, signal) {
return await elementsSincePage((page) => SearchRequests.getSearchFigures(page, searchOptions, semaphore, signal), fromPageNumber);
}
static async getSearchFiguresBetweenPages(fromPageNumber, toPageNumber, searchOptions, semaphore, signal, percentId) {
return await elementsBetweenPages((page) => SearchRequests.getSearchFigures(page, searchOptions, semaphore, signal), fromPageNumber, toPageNumber, percentId);
}
static async getSearchFigures(pageNumber, searchOptions, semaphore, signal) {
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('No pageNumber given. Using default value of 1.');
pageNumber = 1;
}
const galleryDoc = await Search.fetchPage(pageNumber, searchOptions, semaphore, signal);
if (galleryDoc == null || !(galleryDoc instanceof Document) || galleryDoc.getElementById('no-images')) {
Logger.logInfo(`No images found at search on page "${pageNumber}".`);
return [];
}
const figures = galleryDoc.getElementsByTagName('figure');
if (figures == null || figures.length === 0) {
Logger.logInfo(`No figures found at search on page "${pageNumber}".`);
return [];
}
return Array.from(figures);
}
}
class UserRequests {
GalleryRequests;
SearchRequests;
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
this.GalleryRequests = new GalleryRequests(this._semaphore);
this.SearchRequests = new SearchRequests(this._semaphore);
}
static get hardLinks() {
return {
user: FuraffinityRequests.fullUrl + '/user/',
watch: FuraffinityRequests.fullUrl + '/watch/',
unwatch: FuraffinityRequests.fullUrl + '/unwatch/',
block: FuraffinityRequests.fullUrl + '/block/',
unblock: FuraffinityRequests.fullUrl + '/unblock/',
};
}
async getUserPage(username, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._getUserPage(username, signal), action, delay);
}
async watchUser(username, watchKey, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._watchUser(username, watchKey, signal), action, delay);
}
async unwatchUser(username, unwatchKey, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._unwatchUser(username, unwatchKey, signal), action, delay);
}
async blockUser(username, blockKey, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._blockUser(username, blockKey, signal), action, delay);
}
async unblockUser(username, unblockKey, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._unblockUser(username, unblockKey, signal), action, delay);
}
async _getUserPage(username, signal) {
if (username == null) {
Logger.logWarning('No username given');
return;
}
const url = UserRequests.hardLinks['user'] + username;
return await FuraffinityRequests.getHTML(url, this._semaphore, signal);
}
async _watchUser(username, watchKey, signal) {
if (username == null || username === '') {
Logger.logError('No username given');
throw new Error('No username given');
}
if (watchKey == null || watchKey === '' || watchKey === -1) {
Logger.logError('No watch key given');
throw new Error('No watch key given');
}
const url = UserRequests.hardLinks['watch'] + username + '?key=' + watchKey;
return await FuraffinityRequests.getHTML(url, this._semaphore, signal) == null;
}
async _unwatchUser(username, unwatchKey, signal) {
if (username == null || username === '') {
Logger.logError('No username given');
throw new Error('No username given');
}
if (unwatchKey == null || unwatchKey === '' || unwatchKey === -1) {
Logger.logError('No unwatch key given');
throw new Error('No unwatch key given');
}
const url = UserRequests.hardLinks['unwatch'] + username + '?key=' + unwatchKey;
return await FuraffinityRequests.getHTML(url, this._semaphore, signal) == null;
}
async _blockUser(username, blockKey, signal) {
if (username == null || username === '') {
Logger.logError('No username given');
throw new Error('No username given');
}
if (blockKey == null || blockKey === '' || blockKey === -1) {
Logger.logError('No block key given');
throw new Error('No block key given');
}
const url = UserRequests.hardLinks['block'] + username + '?key=' + blockKey;
return await FuraffinityRequests.getHTML(url, this._semaphore, signal) == null;
}
async _unblockUser(username, unblockKey, signal) {
if (username == null || username === '') {
Logger.logError('No username given');
throw new Error('No username given');
}
if (unblockKey == null || unblockKey === '' || unblockKey === -1) {
Logger.logError('No unblock key given');
throw new Error('No unblock key given');
}
const url = UserRequests.hardLinks['unblock'] + username + '?key=' + unblockKey;
return await FuraffinityRequests.getHTML(url, this._semaphore, signal) == null;
}
}
class NewSubmissions {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/msg/submissions/';
}
async getSubmissionsPage(firstSubmissionId, signal, action, delay = DEFAULT_ACTION_DELAY) {
firstSubmissionId = convertToNumber(firstSubmissionId);
return await WaitAndCallAction.callFunctionAsync(() => this._getSubmissionsPage(firstSubmissionId, signal), action, delay);
}
async removeSubmissions(submissionIds, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._removeSubmissions(submissionIds, signal), action, delay);
}
async nukeSubmissions(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._nukeSubmissions(signal), action, delay);
}
async _getSubmissionsPage(firstSubmissionId, signal) {
if (firstSubmissionId == null || firstSubmissionId <= 0) {
return await FuraffinityRequests.getHTML(`${NewSubmissions.hardLink}new@72/`, this._semaphore, signal);
}
else {
return await FuraffinityRequests.getHTML(`${NewSubmissions.hardLink}new~${firstSubmissionId}@72/`, this._semaphore, signal);
}
}
async _removeSubmissions(submissionIds, signal) {
if (submissionIds == null || submissionIds.length === 0) {
Logger.logError('No submission ids to remove');
throw new Error('No submission ids to remove');
}
const payload = [
['messagecenter-action', Message.hardActions['remove']],
];
for (const submissionId of submissionIds) {
payload.push(['submissions[]', submissionId.toString()]);
}
return await FuraffinityRequests.postHTML(`${NewSubmissions.hardLink}new@72/`, payload, this._semaphore, signal);
}
async _nukeSubmissions(signal) {
const payload = [
['messagecenter-action', Message.hardActions['nuke']],
];
return await FuraffinityRequests.postHTML(`${NewSubmissions.hardLink}new@72/`, payload, this._semaphore, signal);
}
}
class MessageTypeRequests {
_semaphore;
_removeAction;
_nukeAction;
_fieldName;
get _hardLink() {
return FuraffinityRequests.fullUrl + '/msg/others/';
}
constructor(semaphore, removeAction, nukeAction, fieldName) {
this._semaphore = semaphore;
this._removeAction = removeAction;
this._nukeAction = nukeAction;
this._fieldName = fieldName;
}
async removeMessages(ids, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._removeMessages(ids, signal), action, delay);
}
async nukeMessages(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._nukeMessages(signal), action, delay);
}
async _removeMessages(ids, signal) {
if (ids == null || ids.length === 0) {
Logger.logError('No message ids to remove');
throw new Error('No message ids to remove');
}
const payload = [this._removeAction];
for (const id of ids) {
payload.push([this._fieldName, id.toString()]);
}
return await FuraffinityRequests.postHTML(this._hardLink, payload, this._semaphore, signal);
}
async _nukeMessages(signal) {
const payload = [this._nukeAction];
return await FuraffinityRequests.postHTML(this._hardLink, payload, this._semaphore, signal);
}
}
class NewMessages {
Watches;
JournalComments;
Shouts;
Favorites;
Journals;
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
this.Watches = new MessageTypeRequests(semaphore, ['remove-watches', 'Remove Selected Watches'], ['nuke-watches', 'Nuke Watches'], 'watches[]');
this.JournalComments = new MessageTypeRequests(semaphore, ['remove-journal-comments', 'Remove Selected Comments'], ['nuke-journal-comments', 'Nuke Journal Comments'], 'comments-journals[]');
this.Shouts = new MessageTypeRequests(semaphore, ['remove-shouts', 'Remove Selected Shouts'], ['nuke-shouts', 'Nuke Shouts'], 'shouts[]');
this.Favorites = new MessageTypeRequests(semaphore, ['remove-favorites', 'Remove Selected Favorites'], ['nuke-favorites', 'Nuke Favorites'], 'favorites[]');
this.Journals = new MessageTypeRequests(semaphore, ['remove-journals', 'Remove Selected Journals'], ['nuke-journals', 'Nuke Journals'], 'journals[]');
}
static get hardLink() {
return FuraffinityRequests.fullUrl + '/msg/others/';
}
static hardActions = {
remove: ['remove-all', 'Remove Selected'],
nuke: ['nuke-all', 'Nuke Selected'],
};
async getMessagesPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(NewMessages.hardLink, this._semaphore, signal), action, delay);
}
async removeMessages(userIds, journalCommentIds, shoutIds, favoriteIds, journalIds, signal, action, delay = DEFAULT_ACTION_DELAY) {
userIds ??= [];
journalCommentIds ??= [];
shoutIds ??= [];
favoriteIds ??= [];
journalIds ??= [];
return await WaitAndCallAction.callFunctionAsync(() => this._removeMessages(userIds, journalCommentIds, shoutIds, favoriteIds, journalIds, signal), action, delay);
}
async _removeMessages(userIds, journalCommentIds, shoutIds, favoriteIds, journalIds, signal) {
const payload = [
NewMessages.hardActions['remove'],
];
for (const id of userIds)
payload.push(['watches[]', id.toString()]);
for (const id of journalCommentIds)
payload.push(['journalcomments[]', id.toString()]);
for (const id of shoutIds)
payload.push(['shouts[]', id.toString()]);
for (const id of favoriteIds)
payload.push(['favorites[]', id.toString()]);
for (const id of journalIds)
payload.push(['journals[]', id.toString()]);
if (payload.length === 1) {
Logger.logError('No messages to remove');
throw new Error('No messages to remove');
}
return await FuraffinityRequests.postHTML(NewMessages.hardLink, payload, this._semaphore, signal);
}
}
class Message {
NewSubmissions;
NewMessages;
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
this.NewSubmissions = new NewSubmissions(this._semaphore);
this.NewMessages = new NewMessages(this._semaphore);
}
static get hardActions() {
return {
remove: 'remove_checked',
nuke: 'nuke_notifications',
};
}
;
}
class AccountInformation {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLinks() {
return {
settings: FuraffinityRequests.fullUrl + '/controls/settings/',
siteSettings: FuraffinityRequests.fullUrl + '/controls/site-settings/',
userSettings: FuraffinityRequests.fullUrl + '/controls/user-settings/',
};
}
async getSettingsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(AccountInformation.hardLinks['settings'], this._semaphore, signal), action, delay);
}
async getSiteSettingsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(AccountInformation.hardLinks['siteSettings'], this._semaphore, signal), action, delay);
}
async getUserSettingsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(AccountInformation.hardLinks['userSettings'], this._semaphore, signal), action, delay);
}
}
class UserProfile {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLinks() {
return {
profile: FuraffinityRequests.fullUrl + '/controls/profile/',
profilebanner: FuraffinityRequests.fullUrl + '/controls/profilebanner/',
contacts: FuraffinityRequests.fullUrl + '/controls/contacts/',
avatar: FuraffinityRequests.fullUrl + '/controls/avatar/',
};
}
async getProfilePage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(UserProfile.hardLinks['profile'], this._semaphore, signal), action, delay);
}
async getProfilebannerPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(UserProfile.hardLinks['profilebanner'], this._semaphore, signal), action, delay);
}
async getContactsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(UserProfile.hardLinks['contacts'], this._semaphore, signal), action, delay);
}
async getAvatarPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(UserProfile.hardLinks['avatar'], this._semaphore, signal), action, delay);
}
}
class ManageContent {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLinks() {
return {
submissions: FuraffinityRequests.fullUrl + '/controls/submissions/',
folders: FuraffinityRequests.fullUrl + '/controls/folders/submissions/',
journals: FuraffinityRequests.fullUrl + '/controls/journal/',
favorites: FuraffinityRequests.fullUrl + '/controls/favorites/',
buddylist: FuraffinityRequests.fullUrl + '/controls/buddylist/',
shouts: FuraffinityRequests.fullUrl + '/controls/shouts/',
badges: FuraffinityRequests.fullUrl + '/controls/badges/',
};
}
async getFoldersPages(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(ManageContent.hardLinks['folders'], this._semaphore, signal), action, delay);
}
async getAllWatchesPages(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._getAllWatchesPages(signal), action, delay);
}
async getWatchesPage(pageNumber, signal, action, delay = DEFAULT_ACTION_DELAY) {
pageNumber = convertToNumber(pageNumber);
return await WaitAndCallAction.callFunctionAsync(() => this._getWatchesPage(pageNumber, signal), action, delay);
}
async _getAllWatchesPages(signal) {
let usersDoc = await FuraffinityRequests.getHTML(ManageContent.hardLinks['buddylist'] + 'x', this._semaphore, signal);
const columnPage = usersDoc?.getElementById('columnpage');
const sectionBody = columnPage?.querySelector('div[class="section-body"');
const paginationLinks = sectionBody?.querySelector('div[class*="pagination-links"]');
const pages = paginationLinks?.querySelectorAll(':scope > a');
const userPageDocs = [];
if (pages != null) {
for (let i = 1; i <= pages.length; i++) {
usersDoc = await this._getWatchesPage(i, signal);
if (usersDoc)
userPageDocs.push(usersDoc);
}
}
return userPageDocs;
}
async _getWatchesPage(pageNumber, signal) {
if (pageNumber == null || pageNumber <= 0) {
Logger.logWarning('No page number given. Using default 1 instead.');
pageNumber = 1;
}
return await FuraffinityRequests.getHTML(ManageContent.hardLinks['buddylist'] + pageNumber, this._semaphore, signal);
}
}
class Security {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLinks() {
return {
sessions: FuraffinityRequests.fullUrl + '/controls/sessions/logins/',
logs: FuraffinityRequests.fullUrl + '/controls/logs/',
labels: FuraffinityRequests.fullUrl + '/controls/labels/',
};
}
async getSessionsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(Security.hardLinks['sessions'], this._semaphore, signal), action, delay);
}
async getLogsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(Security.hardLinks['logs'], this._semaphore, signal), action, delay);
}
async getLabelsPage(signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => FuraffinityRequests.getHTML(Security.hardLinks['labels'], this._semaphore, signal), action, delay);
}
}
class PersonalUserRequests {
MessageRequests;
AccountInformation;
UserProfile;
ManageContent;
Security;
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
this.MessageRequests = new Message(this._semaphore);
this.AccountInformation = new AccountInformation(this._semaphore);
this.UserProfile = new UserProfile(this._semaphore);
this.ManageContent = new ManageContent(this._semaphore);
this.Security = new Security(this._semaphore);
}
}
class SubmissionRequests {
_semaphore;
constructor(semaphore) {
this._semaphore = semaphore;
}
static get hardLinks() {
return {
view: FuraffinityRequests.fullUrl + '/view/',
fav: FuraffinityRequests.fullUrl + '/fav/',
unfav: FuraffinityRequests.fullUrl + '/unfav/',
journal: FuraffinityRequests.fullUrl + '/journal/',
};
}
async getSubmissionPage(submissionId, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._getSubmissionPage(submissionId, signal), action, delay);
}
async favSubmission(submissionId, favKey, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._favSubmission(submissionId, favKey, signal), action, delay);
}
async unfavSubmission(submissionId, unfavKey, signal, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._unfavSubmission(submissionId, unfavKey, signal), action, delay);
}
async getJournalPage(journalId, action, delay = DEFAULT_ACTION_DELAY) {
return await WaitAndCallAction.callFunctionAsync(() => this._getJournalPage(journalId), action, delay);
}
async _getSubmissionPage(submissionId, signal) {
if (submissionId == null || submissionId === '' || submissionId === -1) {
Logger.logError('No submissionId given');
throw new Error('No submissionId given');
}
const url = SubmissionRequests.hardLinks['view'] + submissionId;
return await FuraffinityRequests.getHTML(url, this._semaphore, signal);
}
async _favSubmission(submissionId, favKey, signal) {
if (submissionId == null || submissionId === '' || submissionId === -1) {
Logger.logError('No submissionId given');
throw new Error('No submissionId given');
}
if (favKey == null || favKey === '' || favKey === -1) {
Logger.logError('No favKey given');
throw new Error('No favKey given');
}
const url = SubmissionRequests.hardLinks['fav'] + submissionId + '?key=' + favKey;
const resultDoc = await FuraffinityRequests.getHTML(url, this._semaphore, signal);
if (resultDoc == null) {
Logger.logError('Failed to fetch fav page');
throw new Error('Failed to fetch fav page');
}
const standardpage = resultDoc.getElementById('standardpage');
if (standardpage) {
const blocked = standardpage.querySelector('div[class="redirect-message"]');
if (blocked != null && (blocked.textContent?.includes('blocked') ?? false)) {
const errorMessage = blocked.textContent?.trim() ?? 'Cannot fav: you are blocked by this user';
Logger.logError(errorMessage);
throw new Error(errorMessage);
}
}
return this._getFavKey(resultDoc);
}
async _unfavSubmission(submissionId, unfavKey, signal) {
if (submissionId == null || submissionId === '' || submissionId === -1) {
Logger.logError('No submissionId given');
throw new Error('No submissionId given');
}
if (unfavKey == null || unfavKey === '' || unfavKey === -1) {
Logger.logError('No unfavKey given');
throw new Error('No unfavKey given');
}
const url = SubmissionRequests.hardLinks['unfav'] + submissionId + '?key=' + unfavKey;
const resultDoc = await FuraffinityRequests.getHTML(url, this._semaphore, signal);
if (resultDoc == null) {
Logger.logError('Failed to fetch unfav page');
throw new Error('Failed to fetch unfav page');
}
return this._getFavKey(resultDoc);
}
async _getJournalPage(journalId) {
if (journalId == null || journalId === '' || journalId === -1) {
Logger.logError('No journalId given');
throw new Error('No journalId given');
}
const url = SubmissionRequests.hardLinks['journal'] + journalId;
return await FuraffinityRequests.getHTML(url, this._semaphore);
}
_getFavKey(doc) {
const columnPage = doc.getElementById('columnpage');
const navbar = columnPage?.querySelector('div[class*="favorite-nav"');
const buttons = navbar?.querySelectorAll('a[class*="button"][href]');
if (!buttons || buttons.length === 0) {
return;
}
let favButton;
for (const button of Array.from(buttons)) {
if (button?.textContent?.toLowerCase().includes('fav') ?? false) {
favButton = button;
}
}
if (favButton != null) {
return favButton.getAttribute('href')?.split('?key=')[1];
}
}
}
class FuraffinityRequests {
UserRequests;
PersonalUserRequests;
SubmissionRequests;
static logLevel = 1;
static Types = {
BrowseOptions: BrowseOptions,
SearchOptions: SearchOptions
};
_semaphore;
static _useHttps = window.location.protocol.includes('https');
static _httpsString = window.location.protocol.trimEnd(':') + '://';
static _domain = window.location.hostname;
constructor(maxAmountRequests = 2) {
this._semaphore = new Semaphore(maxAmountRequests);
this.UserRequests = new UserRequests(this._semaphore);
this.PersonalUserRequests = new PersonalUserRequests(this._semaphore);
this.SubmissionRequests = new SubmissionRequests(this._semaphore);
}
set maxAmountRequests(value) {
if (this._semaphore.maxConcurrency === value) {
return;
}
this._semaphore.maxConcurrency = value;
}
get maxAmountRequests() {
return this._semaphore.maxConcurrency;
}
static set useHttps(value) {
if (FuraffinityRequests._useHttps === value) {
return;
}
FuraffinityRequests._useHttps = value;
if (value) {
FuraffinityRequests._httpsString = 'https://';
}
else {
FuraffinityRequests._httpsString = 'http://';
}
}
static get useHttps() {
return FuraffinityRequests._useHttps;
}
static get fullUrl() {
return FuraffinityRequests._httpsString + FuraffinityRequests._domain;
}
static async getHTML(url, semaphore, signal, action, delay = DEFAULT_ACTION_DELAY) {
if (url == null || url === '') {
Logger.logError('No url given for GET request');
throw new Error('No url given for GET request');
}
return await WaitAndCallAction.callFunctionAsync(() => getHTMLLocal(url, semaphore, signal), action, delay);
}
static async postHTML(url, payload, semaphore, signal, action, delay = DEFAULT_ACTION_DELAY) {
if (url == null || url === '') {
Logger.logError('No url given for POST request');
throw new Error('No url given for POST request');
}
return await WaitAndCallAction.callFunctionAsync(() => postHTMLLocal(url, payload, semaphore, signal), action, delay);
}
}
async function getHTMLLocal(url, semaphore, signal) {
Logger.logInfo(`Requesting '${url}'`);
const semaphoreActive = semaphore != null && semaphore.maxConcurrency > 0;
if (semaphoreActive) {
// Acquire a slot in the semaphore to ensure that the maximum concurrency is not exceeded.
await semaphore.acquire();
}
try {
// Send the GET request and retrieve the HTML document.
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error ${response.status} for: ${url}`);
}
const html = await response.text();
// Parse the HTML document using a DOMParser.
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc;
}
catch (error) {
// Enrich network-level errors (e.g. "Failed to fetch") with the URL for better diagnostics.
const message = error instanceof Error ? error.message : String(error);
const enriched = new Error(`${message} (URL: ${url})`);
if (!(signal?.aborted ?? false)) {
Logger.logError(enriched);
}
throw enriched;
}
finally {
// Release the slot in the semaphore.
if (semaphoreActive) {
semaphore.release();
}
}
}
async function postHTMLLocal(url, payload, semaphore, signal) {
// Check if the semaphore is active and acquire it if necessary
const semaphoreActive = semaphore != null && semaphore.maxConcurrency > 0;
if (semaphoreActive) {
await semaphore.acquire();
}
try {
// Send a POST request with the given payload
const response = await fetch(url, {
method: 'POST',
body: new URLSearchParams(payload).toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
signal,
});
// Check if the response is not ok and throw an error
if (!response.ok) {
throw new Error(`HTTP error ${response.status} for: ${url}`);
}
const responseData = await response.text();
// Parse the response data as an HTML document
const parser = new DOMParser();
const doc = parser.parseFromString(responseData, 'text/html');
return doc;
}
catch (error) {
// Enrich network-level errors (e.g. "Failed to fetch") with the URL for better diagnostics.
const message = error instanceof Error ? error.message : String(error);
const enriched = new Error(`${message} (URL: ${url})`);
if (!(signal?.aborted ?? false)) {
Logger.logError(enriched);
}
throw enriched;
}
finally {
// Release the semaphore if it was acquired
if (semaphoreActive) {
semaphore.release();
}
}
}
Object.defineProperties(window, {
FARequestHelper: { get: () => FuraffinityRequests }
});
})();