@doridian/furaffinity-api
Version:
FurAffinity wrapper for Node.js
415 lines (414 loc) • 19.3 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParseMyWatchingList = exports.ParseWatchingList = exports.ParseAuthor = exports.ParseSubmission = exports.ParseSubmissionsPaging = exports.ParseBrowsePaging = exports.ParseSearchPaging = exports.ParseScrapsPaging = exports.ParseGalleryPaging = exports.ParseFigures = exports.ParseFigure = exports.FASystemError = void 0;
const cheerio_1 = __importDefault(require("cheerio"));
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
const Enums_1 = require("./Enums");
const _1 = require(".");
const Request_1 = require("./Request");
class FASystemError extends Error {
constructor(message) {
super(message);
}
}
exports.FASystemError = FASystemError;
function convertUserLinkToID(link) {
var _a, _b;
return (_b = (_a = link.match(/\/user\/([^\/]+)(\/?|$)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
}
/**
* Get class names from element
* @param element CheerioElement
*/
function classNames(element) {
return element.attribs.class.split(" ");
}
function checkSystemMessage($) {
var _a;
// Check system message
const noticeMessage = $("section.notice-message");
if (noticeMessage.length !== 0) {
const systemMessage = noticeMessage[0].childNodes[1].childNodes[3].childNodes[0].nodeValue;
throw new FASystemError(systemMessage);
}
// Check system error
const title = $("head title");
if (title[0].firstChild.data === "System Error") {
const sectionBody = $("section .section-body");
throw new FASystemError(((_a = sectionBody[0].firstChild.data) === null || _a === void 0 ? void 0 : _a.trim()) || "Unknown error.");
}
}
/**
* Parse result from figure element
* @param figure CheerioElement
*/
function ParseFigure(figure, selector) {
var _a, _b, _c, _d;
const id = (_a = figure.attribs.id.split("-").pop()) !== null && _a !== void 0 ? _a : "";
const thumb = "http:" + figure.childNodes[0].childNodes[0].childNodes[0].childNodes[0].attribs.src;
const authorElement = selector.find("figcaption p:last-child a").first().attr();
const authorName = authorElement.title;
const authorId = convertUserLinkToID(authorElement.href);
return {
type: Enums_1.SubmissionType[classNames(figure)[1].split("-").pop()],
id,
title: (_c = (_b = figure.childNodes[1].childNodes[0].childNodes[0].childNodes[0]) === null || _b === void 0 ? void 0 : _b.nodeValue) !== null && _c !== void 0 ? _c : "",
url: `${Request_1.ENDPOINT}/view/${id}`,
rating: Enums_1.Rating[(_d = classNames(figure)[0]
.split("-")
.pop()) === null || _d === void 0 ? void 0 : _d.replace(/^[a-z]/, ($1) => $1.toUpperCase())],
thumb: {
icon: thumb.replace(/@\d+?-/g, "@75-"),
tiny: thumb.replace(/@\d+?-/g, "@150-"),
small: thumb.replace(/@\d+?-/g, "@300-"),
medium: thumb.replace(/@\d+?-/g, "@800-"),
large: thumb.replace(/@\d+?-/g, "@1600-")
},
author: {
id: authorId,
url: `${Request_1.ENDPOINT}/user/${authorId}`,
name: authorName
},
getSubmission: () => __awaiter(this, void 0, void 0, function* () {
return yield (0, _1.submission)(id);
})
};
}
exports.ParseFigure = ParseFigure;
/**
* Parse all figure's info from HTML
* @param body HTML document
*/
function ParseFigures(body) {
const $ = cheerio_1.default.load(body);
const results = [];
$("figure").each((index, figure) => {
results.push(ParseFigure(figure, $(figure)));
});
return results;
}
exports.ParseFigures = ParseFigures;
function ParseGalleryPaging(body, results, perpage) {
const $ = cheerio_1.default.load(body);
const links = $(".submission-list .aligncenter:nth-child(1) div");
if ($(links[0]).find("form").length > 0) {
results.prevLink = Request_1.ENDPOINT + $(links[0]).find("form").attr()["action"];
const matchs = results.prevLink.match(/\/gallery\/(.+?)\/(\d+?)\/$/);
if (matchs) {
const id = matchs[1];
const page = Number.parseInt(matchs[2]);
results.prev = () => (0, _1.gallery)(id, page, perpage);
}
}
if ($(links[2]).find("form").length > 0) {
results.nextLink = Request_1.ENDPOINT + $(links[2]).find("form").attr()["action"];
const matchs = results.nextLink.match(/\/gallery\/(.+?)\/(\d+?)\/$/);
if (matchs) {
const id = matchs[1];
const page = Number.parseInt(matchs[2]);
results.next = () => (0, _1.gallery)(id, page, perpage);
}
}
return results;
}
exports.ParseGalleryPaging = ParseGalleryPaging;
function ParseScrapsPaging(body, results, perpage) {
const $ = cheerio_1.default.load(body);
const links = $(".submission-list .aligncenter:nth-child(1) div");
if ($(links[0]).find("form").length > 0) {
results.prevLink = Request_1.ENDPOINT + $(links[0]).find("form").attr()["action"];
const matchs = results.prevLink.match(/\/scraps\/(.+?)\/(\d+?)\/$/);
if (matchs) {
const id = matchs[1];
const page = Number.parseInt(matchs[2]);
results.prev = () => (0, _1.scraps)(id, page, perpage);
}
}
if ($(links[2]).find("form").length > 0) {
results.nextLink = Request_1.ENDPOINT + $(links[2]).find("form").attr()["action"];
const matchs = results.nextLink.match(/\/scraps\/(.+?)\/(\d+?)\/$/);
if (matchs) {
const id = matchs[1];
const page = Number.parseInt(matchs[2]);
results.next = () => (0, _1.scraps)(id, page, perpage);
}
}
return results;
}
exports.ParseScrapsPaging = ParseScrapsPaging;
function ParseSearchPaging(body, results, query, options) {
const $ = cheerio_1.default.load(body);
const links = $("#search-results .pagination button");
if (!classNames(links[0]).includes("disabled")) {
const newOptions = (0, cloneDeep_1.default)(options || {});
newOptions.page = newOptions.page ? newOptions.page - 1 : 1;
results.prev = () => (0, _1.search)(query, newOptions);
}
if (!classNames(links[1]).includes("disabled")) {
const newOptions = (0, cloneDeep_1.default)(options || {});
newOptions.page = newOptions.page ? newOptions.page + 1 : 2;
results.next = () => (0, _1.search)(query, newOptions);
}
return results;
}
exports.ParseSearchPaging = ParseSearchPaging;
function ParseBrowsePaging(body, results, options) {
const $ = cheerio_1.default.load(body);
const links = $(".section-body .navigation:nth-child(1) div");
if ($(links[0]).find("form").length > 0) {
results.prevLink = Request_1.ENDPOINT + $(links[0]).find("form").attr()["action"];
const matchs = results.prevLink.match(/\/browse\/(\d+?)$/);
if (matchs) {
const page = Number.parseInt(matchs[1]);
options = options !== null && options !== void 0 ? options : {};
options.page = page;
results.prev = () => (0, _1.browse)(options);
}
}
if ($(links[2]).find("form").length > 0) {
results.nextLink = Request_1.ENDPOINT + $(links[2]).find("form").attr()["action"];
const matchs = results.nextLink.match(/\/browse\/(\d+?)$/);
if (matchs) {
const page = Number.parseInt(matchs[1]);
options = options !== null && options !== void 0 ? options : {};
options.page = page;
results.next = () => (0, _1.browse)(options);
}
}
return results;
}
exports.ParseBrowsePaging = ParseBrowsePaging;
function ParseSubmissionsPaging(body, results) {
const $ = cheerio_1.default.load(body);
const links = $(".section-body .aligncenter:nth-child(1)");
if ($(links).find("a.more-half").length === 2) {
results.prevLink = Request_1.ENDPOINT + $(links).find("a.more-half").first().attr()["href"];
results.nextLink = Request_1.ENDPOINT + $(links).find("a.more-half").last().attr()["href"];
}
else if ($(links).find("a.more.prev").length === 1) {
results.prevLink = Request_1.ENDPOINT + $(links).find("a.more.prev").first().attr()["href"];
}
else if ($(links).find("a.more").length === 1) {
results.nextLink = Request_1.ENDPOINT + $(links).find("a.more").first().attr()["href"];
}
if (results.prevLink) {
const matchs = results.prevLink.match(/\/submissions\/(.+?)~(.+?)@(\d+)\/$/);
if (matchs) {
const sort = matchs[1];
const startAt = matchs[2];
const perpage = matchs[3];
const options = {
sort: sort,
startAt: startAt,
perpage: perpage
};
results.prev = () => (0, _1.submissions)(options);
}
}
if (results.nextLink) {
const matchs = results.nextLink.match(/\/submissions\/(.+?)~(.+?)@(\d+)\/$/);
if (matchs) {
const sort = matchs[1];
const startAt = matchs[2];
const perpage = matchs[3];
const options = {
sort: sort,
startAt: startAt,
perpage: perpage
};
results.next = () => (0, _1.submissions)(options);
}
}
return results;
}
exports.ParseSubmissionsPaging = ParseSubmissionsPaging;
/**
* Get submission's info
* @param body HTML document
* @param id Subumission id
*/
function ParseSubmission(body, id) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
const $ = cheerio_1.default.load(body);
checkSystemMessage($);
// Get main nodes
const main = $("#columnpage");
const sidebar = main.find(".submission-sidebar");
const content = main.find(".submission-content");
const stats = sidebar.find(".stats-container");
const info = sidebar.find(".info");
const tags = sidebar.find(".tags-row .tags a");
// buttons
let downloadUrl = `http:${sidebar.find(".buttons .download a")[0].attribs.href}`;
const favLinkNode = sidebar.find(".buttons .fav a")[0];
const favLink = favLinkNode ? `http://furaffinity.net${favLinkNode.attribs.href}` : undefined;
// header
const title = (_b = (_a = content.find(".submission-id-sub-container .submission-title p")[0].childNodes[0].data) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
const authorElement = content.find(".submission-id-sub-container a");
const authorName = (_d = (_c = authorElement.find("strong")[0].childNodes[0].data) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : "";
const authorId = convertUserLinkToID(authorElement.attr().href);
const posted = content.find(".submission-id-sub-container strong span")[0].attribs.title;
const authorAvatar = `http:${content.find(".submission-id-avatar img")[0].attribs.src}`;
const authorShinies = !!$(".shinies-promo");
const description = (_f = (_e = content.find(".submission-description").html()) === null || _e === void 0 ? void 0 : _e.trim()) !== null && _f !== void 0 ? _f : "";
// stats
const rating = Enums_1.Rating[(_g = stats.find(".rating span")[0].childNodes[0].data) === null || _g === void 0 ? void 0 : _g.trim()];
const favorites = Number.parseInt((_j = (_h = stats.find(".favorites span")[0].childNodes[0].data) === null || _h === void 0 ? void 0 : _h.trim()) !== null && _j !== void 0 ? _j : "");
const comments = Number.parseInt((_l = (_k = stats.find(".comments span")[0].childNodes[0].data) === null || _k === void 0 ? void 0 : _k.trim()) !== null && _l !== void 0 ? _l : "");
const views = Number.parseInt((_o = (_m = stats.find(".views span")[0].childNodes[0].data) === null || _m === void 0 ? void 0 : _m.trim()) !== null && _o !== void 0 ? _o : "");
// info
const category = Enums_1.Category[(_p = info.find(".category-name")[0].childNodes[0].data) === null || _p === void 0 ? void 0 : _p.trim()];
const species = Enums_1.Species[(_q = info[0].childNodes[3].childNodes[2].childNodes[0].data) === null || _q === void 0 ? void 0 : _q.trim()];
const gender = Enums_1.Gender[(_r = info[0].childNodes[5].childNodes[2].childNodes[0].data) === null || _r === void 0 ? void 0 : _r.trim()];
// fix url when category is story or poetry
if (category === Enums_1.Category.Story || category === Enums_1.Category.Poetry) {
downloadUrl = downloadUrl.replace("d.facdn.net/download/", "d.facdn.net/");
}
const previewUrl = content.find(".submission-area img").length > 0 ? `http:${content.find(".submission-area img")[0].attribs["data-preview-src"]}` : undefined;
return {
id,
url: `http://www.furaffinity.net/view/${id}`,
title: title,
posted: Date.parse(posted),
favLink,
rating: rating,
author: {
id: authorId,
name: authorName,
url: `http://www.furaffinity.net/user/${authorId}`,
avatar: authorAvatar,
shinies: authorShinies
},
description,
content: {
category,
species,
gender
},
stats: {
favorites,
comments,
views
},
downloadUrl,
previewUrl,
keywords: tags
.map((index, tag) => {
var _a, _b;
return (_b = (_a = tag.childNodes[0].data) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
})
.get(),
fave: favLink
? () => __awaiter(this, void 0, void 0, function* () {
yield (0, Request_1.FaveSubmission)(favLink);
})
: undefined
};
}
exports.ParseSubmission = ParseSubmission;
/**
* Get author's info
* @param body HTML document
*/
function ParseAuthor(body) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
const $ = cheerio_1.default.load(body);
checkSystemMessage($);
const name = (_b = (_a = $("userpage-nav-user-details username")[0].childNodes[0].data) === null || _a === void 0 ? void 0 : _a.trim().slice(1)) !== null && _b !== void 0 ? _b : "";
const id = convertUserLinkToID((_c = $("userpage-nav-avatar a").attr().href) !== null && _c !== void 0 ? _c : "");
const url = `https://www.furaffinity.net/user/${id}`;
const shinies = !!$(".userpage-layout-left-col-content > a:nth-child(4)");
const avatar = `https:${$("userpage-nav-avatar img")[0].attribs.src}`;
const statsCells = $(".userpage-section-right .cell");
const views = (_e = (_d = statsCells[0].childNodes[2].data) === null || _d === void 0 ? void 0 : _d.trim()) !== null && _e !== void 0 ? _e : "0";
const submissions = (_g = (_f = statsCells[0].childNodes[6].data) === null || _f === void 0 ? void 0 : _f.trim()) !== null && _g !== void 0 ? _g : "0";
const favs = (_j = (_h = statsCells[0].childNodes[10].data) === null || _h === void 0 ? void 0 : _h.trim()) !== null && _j !== void 0 ? _j : "0";
const commentsEarned = (_l = (_k = statsCells[1].childNodes[2].data) === null || _k === void 0 ? void 0 : _k.trim()) !== null && _l !== void 0 ? _l : "0";
const commentsMade = (_o = (_m = statsCells[1].childNodes[6].data) === null || _m === void 0 ? void 0 : _m.trim()) !== null && _o !== void 0 ? _o : "0";
const journals = (_q = (_p = statsCells[1].childNodes[10].data) === null || _p === void 0 ? void 0 : _p.trim()) !== null && _q !== void 0 ? _q : "0";
// TODO: add exception if author is user, if not already done
const watchButton = $("userpage-nav-interface-buttons a")[0];
const watchLink = watchButton ? `${Request_1.ENDPOINT}${watchButton.attribs.href}` : undefined;
const watching = watchButton ? watchButton.attribs.class.includes('stop') : false;
return {
id,
name,
url,
avatar,
shinies,
watchLink,
stats: {
views: Number.parseInt(views),
submissions: Number.parseInt(submissions),
favs: Number.parseInt(favs),
commentsEarned: Number.parseInt(commentsEarned),
commentsMade: Number.parseInt(commentsMade),
journals: Number.parseInt(journals),
watching
},
watchAuthor: !watching && watchLink
? () => __awaiter(this, void 0, void 0, function* () { return yield (0, Request_1.RequestToggleWatch)(watchLink); }) : undefined,
unwatchAuthor: watching && watchLink
? () => __awaiter(this, void 0, void 0, function* () { return yield (0, Request_1.RequestToggleWatch)(watchLink); }) : undefined
};
}
exports.ParseAuthor = ParseAuthor;
/**
* Get all Author's info from peer page of watching list
* @param body HTML document
*/
function ParseWatchingList(body) {
const $ = cheerio_1.default.load(body);
checkSystemMessage($);
return $(".watch-list-items a")
.map((index, a) => {
var _a, _b;
const name = (_b = (_a = a.childNodes[0].data) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
const id = convertUserLinkToID(a.attribs.href);
const url = `${Request_1.ENDPOINT}/user/${id}`;
return {
id,
name,
url
};
})
.get();
}
exports.ParseWatchingList = ParseWatchingList;
/**
* Get all Author's info from peer page of watching list
* @param body HTML document
*/
function ParseMyWatchingList(body) {
const $ = cheerio_1.default.load(body);
checkSystemMessage($);
return $(".flex-item-watchlist")
.map((index, div) => {
var _a, _b;
const avatar = `https:${$(div).find("img.avatar")[0].attribs.src}`;
const authorElement = $(div).find(".flex-item-watchlist-controls a");
const name = (_b = (_a = authorElement.find("strong")[0].childNodes[0].data) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
const id = convertUserLinkToID(authorElement.attr().href);
const url = `${Request_1.ENDPOINT}/user/${id}`;
return {
id,
name,
url,
avatar
};
})
.get();
}
exports.ParseMyWatchingList = ParseMyWatchingList;