Added authentication to platform, game searcher tested
parent
4c273def44
commit
ecfd1784b1
|
@ -7,7 +7,7 @@ class Credentials {
|
||||||
constructor(username, password) {
|
constructor(username, password) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.token = "";
|
this.token = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchToken() {
|
async fetchToken() {
|
||||||
|
|
|
@ -2,6 +2,6 @@ module.exports = Object.freeze({
|
||||||
F95_BASE_URL: "https://f95zone.to",
|
F95_BASE_URL: "https://f95zone.to",
|
||||||
F95_SEARCH_URL: "https://f95zone.to/search/?type=post",
|
F95_SEARCH_URL: "https://f95zone.to/search/?type=post",
|
||||||
F95_LATEST_UPDATES: "https://f95zone.to/latest",
|
F95_LATEST_UPDATES: "https://f95zone.to/latest",
|
||||||
F95_LOGIN_URL: "https://f95zone.to/login",
|
F95_LOGIN_URL: "https://f95zone.to/login/login",
|
||||||
F95_WATCHED_THREADS: "https://f95zone.to/watched/threads",
|
F95_WATCHED_THREADS: "https://f95zone.to/watched/threads",
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,8 @@ const ky = require("ky-universal").create({
|
||||||
throwHttpErrors: false,
|
throwHttpErrors: false,
|
||||||
});
|
});
|
||||||
const cheerio = require("cheerio");
|
const cheerio = require("cheerio");
|
||||||
const qs = require("querystring");
|
const axiosCookieJarSupport = require("axios-cookiejar-support").default;
|
||||||
|
const tough = require("tough-cookie");
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
const shared = require("./shared.js");
|
const shared = require("./shared.js");
|
||||||
|
@ -17,6 +18,8 @@ const f95url = require("./constants/url.js");
|
||||||
const userAgent =
|
const userAgent =
|
||||||
"Mozilla/5.0 (X11; Linux x86_64)" +
|
"Mozilla/5.0 (X11; Linux x86_64)" +
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36";
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36";
|
||||||
|
axiosCookieJarSupport(axios);
|
||||||
|
const cookieJar = new tough.CookieJar();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
|
@ -30,6 +33,8 @@ module.exports.fetchHTML = async function (url) {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": userAgent
|
"User-Agent": userAgent
|
||||||
},
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
jar: cookieJar
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -40,50 +45,44 @@ module.exports.fetchHTML = async function (url) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* Gets the HTML code of a login-protected page.
|
* It authenticates to the platform using the credentials
|
||||||
* @param {String} url URL to fetch
|
* and token obtained previously. Save cookies on your
|
||||||
|
* device after authentication.
|
||||||
* @param {Credentials} credentials Platform access credentials
|
* @param {Credentials} credentials Platform access credentials
|
||||||
* @returns {Promise<String>} HTML code or `null` if an error arise
|
* @returns {Promise<Boolean>} Result of the operation
|
||||||
*/
|
*/
|
||||||
module.exports.fetchHTMLWithAuth = async function (url, credentials) {
|
module.exports.autenticate = async function (credentials) {
|
||||||
shared.logger.trace(`Fetching ${url} with user ${credentials.username}`);
|
shared.logger.info(`Authenticating with user ${credentials.username}`);
|
||||||
|
if (!credentials.token) throw new Error(`Invalid token for auth: ${credentials.token}`);
|
||||||
|
|
||||||
const data = {
|
// Prepare the parameters to send to the platform to authenticate
|
||||||
"login": credentials.username,
|
const params = new URLSearchParams();
|
||||||
"url": "",
|
params.append("login", credentials.username);
|
||||||
"password": credentials.password,
|
params.append("url", "");
|
||||||
"password_confirm": "",
|
params.append("password", credentials.password);
|
||||||
"additional_security": "",
|
params.append("password_confirm", "");
|
||||||
"remember": "1",
|
params.append("additional_security", "");
|
||||||
"_xfRedirect": "https://f95zone.to/",
|
params.append("remember", "1");
|
||||||
"website_code": "",
|
params.append("_xfRedirect", "https://f95zone.to/");
|
||||||
"_xfToken": credentials.token,
|
params.append("website_code", "");
|
||||||
};
|
params.append("_xfToken", credentials.token);
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": userAgent,
|
"User-Agent": userAgent,
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
}
|
"Connection": "keep-alive"
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
jar: cookieJar // Retrieve the stored cookies! What a pain to understand that this is a MUST!
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(qs.stringify(data));
|
await axios.post(f95url.F95_LOGIN_URL, params, config);
|
||||||
const response = await axios({
|
return true;
|
||||||
method: "post",
|
|
||||||
url: url,
|
|
||||||
data: qs.stringify(data),
|
|
||||||
headers: {
|
|
||||||
"user-agent": userAgent,
|
|
||||||
"content-type": "application/x-www-form-urlencoded;charset=utf-8"
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
});
|
|
||||||
//const response = await axios.post(url, qs.stringify(data), config);
|
|
||||||
return response.data;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
shared.logger.error(`Error ${e.message} occurred while trying to fetch ${url}`);
|
shared.logger.error(`Error ${e.message} occurred while authenticating to ${f95url.F95_LOGIN_URL}`);
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,12 +92,17 @@ module.exports.fetchHTMLWithAuth = async function (url, credentials) {
|
||||||
*/
|
*/
|
||||||
module.exports.getF95Token = async function() {
|
module.exports.getF95Token = async function() {
|
||||||
try {
|
try {
|
||||||
// Fetch the response of the platform
|
const config = {
|
||||||
const response = await axios.get(f95url.F95_LOGIN_URL, {
|
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": userAgent
|
"User-Agent": userAgent,
|
||||||
|
"Connection": "keep-alive"
|
||||||
},
|
},
|
||||||
});
|
withCredentials: true,
|
||||||
|
jar: cookieJar // Used to store the token in the PC
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch the response of the platform
|
||||||
|
const response = await axios.get(f95url.F95_LOGIN_URL, config);
|
||||||
|
|
||||||
// The response is a HTML page, we need to find the <input> with name "_xfToken"
|
// The response is a HTML page, we need to find the <input> with name "_xfToken"
|
||||||
const $ = cheerio.load(response.data);
|
const $ = cheerio.load(response.data);
|
||||||
|
|
|
@ -75,7 +75,7 @@ function extractInfoFromTitle(body) {
|
||||||
|
|
||||||
// From the title we can extract: Name, author and version
|
// From the title we can extract: Name, author and version
|
||||||
// TITLE [VERSION] [AUTHOR]
|
// TITLE [VERSION] [AUTHOR]
|
||||||
const matches = title.match(/\[(.*?)\]/);
|
const matches = title.match(/\[(.*?)\]/g);
|
||||||
const endIndex = title.indexOf("["); // The open bracket of the version
|
const endIndex = title.indexOf("["); // The open bracket of the version
|
||||||
const name = title.substring(0, endIndex).trim();
|
const name = title.substring(0, endIndex).trim();
|
||||||
const version = matches[0].trim();
|
const version = matches[0].trim();
|
||||||
|
|
|
@ -4,19 +4,19 @@
|
||||||
const cheerio = require("cheerio");
|
const cheerio = require("cheerio");
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
const { fetchHTMLWithAuth } = require("./network-helper.js");
|
const { fetchHTML } = require("./network-helper.js");
|
||||||
const shared = require("./shared.js");
|
const shared = require("./shared.js");
|
||||||
const f95Selector = require("./constants/css-selector.js");
|
const f95Selector = require("./constants/css-selector.js");
|
||||||
|
const { F95_BASE_URL } = require("./constants/url.js");
|
||||||
|
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* Search for a game on F95Zone and return a list of URLs, one for each search result.
|
* Search for a game on F95Zone and return a list of URLs, one for each search result.
|
||||||
* @param {String} name Game name
|
* @param {String} name Game name
|
||||||
* @param {Credentials} credentials Platform access credentials
|
|
||||||
* @returns {Promise<String[]>} URLs of results
|
* @returns {Promise<String[]>} URLs of results
|
||||||
*/
|
*/
|
||||||
module.exports.searchGame = async function (name, credentials) {
|
module.exports.searchGame = async function (name) {
|
||||||
shared.logger.info(`Searching games with name ${name}`);
|
shared.logger.info(`Searching games with name ${name}`);
|
||||||
|
|
||||||
// Replace the whitespaces with +
|
// Replace the whitespaces with +
|
||||||
|
@ -26,17 +26,16 @@ module.exports.searchGame = async function (name, credentials) {
|
||||||
const url = `https://f95zone.to/search/83456043/?q=${searchName}&t=post&c[child_nodes]=1&c[nodes][0]=2&c[title_only]=1&o=relevance`;
|
const url = `https://f95zone.to/search/83456043/?q=${searchName}&t=post&c[child_nodes]=1&c[nodes][0]=2&c[title_only]=1&o=relevance`;
|
||||||
|
|
||||||
// Fetch and parse the result URLs
|
// Fetch and parse the result URLs
|
||||||
return await fetchResultURLs(url, credentials);
|
return await fetchResultURLs(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* Search for a mod on F95Zone and return a list of URLs, one for each search result.
|
* Search for a mod on F95Zone and return a list of URLs, one for each search result.
|
||||||
* @param {String} name Mod name
|
* @param {String} name Mod name
|
||||||
* @param {Credentials} credentials Platform access credentials
|
|
||||||
* @returns {Promise<String[]>} URLs of results
|
* @returns {Promise<String[]>} URLs of results
|
||||||
*/
|
*/
|
||||||
module.exports.searchMod = async function (name, credentials) {
|
module.exports.searchMod = async function (name) {
|
||||||
shared.logger.info(`Searching mods with name ${name}`);
|
shared.logger.info(`Searching mods with name ${name}`);
|
||||||
|
|
||||||
// Replace the whitespaces with +
|
// Replace the whitespaces with +
|
||||||
|
@ -46,7 +45,7 @@ module.exports.searchMod = async function (name, credentials) {
|
||||||
const url = `https://f95zone.to/search/83459796/?q=${searchName}&t=post&c[child_nodes]=1&c[nodes][0]=41&c[title_only]=1&o=relevance`;
|
const url = `https://f95zone.to/search/83459796/?q=${searchName}&t=post&c[child_nodes]=1&c[nodes][0]=41&c[title_only]=1&o=relevance`;
|
||||||
|
|
||||||
// Fetch and parse the result URLs
|
// Fetch and parse the result URLs
|
||||||
return await fetchResultURLs(url, credentials);
|
return await fetchResultURLs(url);
|
||||||
};
|
};
|
||||||
//#endregion Public methods
|
//#endregion Public methods
|
||||||
|
|
||||||
|
@ -55,14 +54,13 @@ module.exports.searchMod = async function (name, credentials) {
|
||||||
* @private
|
* @private
|
||||||
* Gets the URLs of the threads resulting from the F95Zone search.
|
* Gets the URLs of the threads resulting from the F95Zone search.
|
||||||
* @param {String} url Search URL
|
* @param {String} url Search URL
|
||||||
* @param {Credentials} credentials Platform access credentials
|
|
||||||
* @return {Promise<String[]>} List of URLs
|
* @return {Promise<String[]>} List of URLs
|
||||||
*/
|
*/
|
||||||
async function fetchResultURLs(url, credentials) {
|
async function fetchResultURLs(url) {
|
||||||
shared.logger.info(`Fetching ${url}...`);
|
shared.logger.info(`Fetching ${url}...`);
|
||||||
|
|
||||||
// Fetch HTML and prepare Cheerio
|
// Fetch HTML and prepare Cheerio
|
||||||
const html = await fetchHTMLWithAuth(url, credentials);
|
const html = await fetchHTML(url);
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
// Here we get all the DIV that are the body of the various query results
|
// Here we get all the DIV that are the body of the various query results
|
||||||
|
@ -84,11 +82,12 @@ async function fetchResultURLs(url, credentials) {
|
||||||
* @returns {String} URL to thread
|
* @returns {String} URL to thread
|
||||||
*/
|
*/
|
||||||
function extractLinkFromResult(selector) {
|
function extractLinkFromResult(selector) {
|
||||||
const link = selector
|
const partialLink = selector
|
||||||
.find(f95Selector.GS_RESULT_THREAD_TITLE)
|
.find(f95Selector.GS_RESULT_THREAD_TITLE)
|
||||||
.attr("href")
|
.attr("href")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
return link;
|
// Compose and return the URL
|
||||||
|
return new URL(partialLink, F95_BASE_URL).toString();
|
||||||
}
|
}
|
||||||
//#endregion Private methods
|
//#endregion Private methods
|
||||||
|
|
|
@ -416,6 +416,15 @@
|
||||||
"follow-redirects": "^1.10.0"
|
"follow-redirects": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"axios-cookiejar-support": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==",
|
||||||
|
"requires": {
|
||||||
|
"is-redirect": "^1.0.0",
|
||||||
|
"pify": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-eslint": {
|
"babel-eslint": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
||||||
|
@ -1459,6 +1468,11 @@
|
||||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-redirect": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
|
||||||
|
},
|
||||||
"is-regex": {
|
"is-regex": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
|
||||||
|
@ -2299,6 +2313,11 @@
|
||||||
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"pify": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="
|
||||||
|
},
|
||||||
"pkg-dir": {
|
"pkg-dir": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||||
|
@ -2342,11 +2361,15 @@
|
||||||
"iterate-value": "^1.0.0"
|
"iterate-value": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"psl": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
|
||||||
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"randombytes": {
|
"randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
@ -2711,6 +2734,16 @@
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tough-cookie": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
|
||||||
|
"requires": {
|
||||||
|
"psl": "^1.1.33",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
|
"universalify": "^0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"type-check": {
|
"type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|
|
@ -32,10 +32,12 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.0",
|
"axios": "^0.21.0",
|
||||||
|
"axios-cookiejar-support": "^1.0.1",
|
||||||
"cheerio": "^1.0.0-rc.3",
|
"cheerio": "^1.0.0-rc.3",
|
||||||
"ky": "^0.24.0",
|
"ky": "^0.24.0",
|
||||||
"ky-universal": "^0.8.2",
|
"ky-universal": "^0.8.2",
|
||||||
"log4js": "^6.3.0"
|
"log4js": "^6.3.0",
|
||||||
|
"tough-cookie": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
|
|
|
@ -21,8 +21,9 @@ async function searchKOD() {
|
||||||
await creds.fetchToken();
|
await creds.fetchToken();
|
||||||
console.log(`Token obtained: ${creds.token}`);
|
console.log(`Token obtained: ${creds.token}`);
|
||||||
|
|
||||||
const html = await networkHelper.fetchHTMLWithAuth("https://f95zone.to/login/login", creds);
|
console.log("Authenticating...");
|
||||||
console.log(html);
|
const authenticated = await networkHelper.autenticate(creds);
|
||||||
|
console.log(`Authentication result: ${authenticated}`);
|
||||||
|
|
||||||
console.log("Searching KOD...");
|
console.log("Searching KOD...");
|
||||||
const urls = await searcher.searchGame("kingdom of deception", creds);
|
const urls = await searcher.searchGame("kingdom of deception", creds);
|
||||||
|
|
Loading…
Reference in New Issue