feat: add support for other languages

Co-authored-by: k0xp00f <d.abdelkarim1404+gmail.com>
This commit is contained in:
kareem 2023-01-07 18:22:20 +01:00 committed by GitHub
parent 356ad04da6
commit d16ae48dcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 240 additions and 99 deletions

View file

@ -3,29 +3,31 @@
// IMPORTS
////////////////////////////////////////////////////////
import * as cheerio from 'cheerio';
import axiosInstance from '../utils/axiosInstance.js';
import getAxiosInstance from '../utils/getAxiosInstance.js';
import AppError from '../utils/AppError.js';
////////////////////////////////////////////////////////
// FUNCTION
////////////////////////////////////////////////////////
/**
*
* makes a call to quora.com(with the resourceStr appended) and returns parsed JSON containing the data about the resource requested.
* @param {string} resourceStr a string after the baseURL
* @param {string} keyword
* @param {boolean}} toEncode
* @param {{keyword: string, lang?: string, toEncode?: boolean}} options additional options
* @returns JSON containing the result
* @description makes a call to quora.com(with the resourceStr appended) and returns parsed JSON containing the data about the resource requested.
* @example await fetcher('What-is-free-and-open-software'); // will return object containing answers
* await fetcher('topic/Space-Physics'); // will return 'space physics' topic object
* await fetcher('profile/Charlie-Cheever'); // will return object containing information about charlie cheever
*/
const fetcher = async (resourceStr, keyword = '', toEncode = true) => {
const fetcher = async (
resourceStr,
{ keyword, lang, toEncode = true }
) => {
try {
// as url might contain unescaped chars. so, encoding it right away
const str = toEncode ? encodeURIComponent(resourceStr) : resourceStr;
const axiosInstance = getAxiosInstance(lang);
const res = await axiosInstance.get(str);
const $ = cheerio.load(res.data);
const regex = new RegExp(`"{\\\\"data\\\\":\\{\\\\"${keyword}.*\\}"`); // equivalent to /"\{\\"data\\":\{\\"searchConnection.*\}"/

View file

@ -3,17 +3,20 @@
////////////////////////////////////////////////////////
// import log from '../utils/log.js';
import AppError from '../utils/AppError.js';
import { quetrefy } from '../utils/urlModifiers.js';
import fetcher from './fetcher.js';
////////////////////////////////////////////////////////
// FUNCTION
////////////////////////////////////////////////////////
const getAnswers = async slug => {
const KEYWORD = 'question';
const getAnswers = async (slug, lang) => {
// getting data and destructuring it in case it exists
const res = await fetcher(slug, 'question');
const res = await fetcher(slug, { keyword: KEYWORD, lang });
const {
data: { question: rawData },
data: { [KEYWORD]: rawData },
} = JSON.parse(res);
if (!rawData)
@ -42,14 +45,14 @@ const getAnswers = async slug => {
isAnon: ansObj.node.answer.author.isAnon,
image: ansObj.node.answer.author.profileImageUrl,
isVerified: ansObj.node.answer.author.isVerified,
url: ansObj.node.answer.author.profileUrl,
url: quetrefy(ansObj.node.answer.author.profileUrl),
name: `${ansObj.node.answer.author.names[0].givenName} ${ansObj.node.answer.author.names[0].familyName}`,
credential: ansObj.node.answer.authorCredential?.translatedString,
// additionalCredentials: ansObj.node.answer?.credibilityFacts.map(),
},
originalQuestion: {
text: JSON.parse(ansObj.node.answer.question.title).sections,
url: ansObj.node.answer.question.url,
url: quetrefy(ansObj.node.answer.question.url),
qid: ansObj.node.answer.question.qid,
isDeleted: ansObj.node.answer.question.isDeleted,
},
@ -59,7 +62,7 @@ const getAnswers = async slug => {
const data = {
question: {
text: JSON.parse(rawData.title).sections,
url: rawData.url,
url: quetrefy(rawData.url),
qid: rawData.qid,
idDeleted: rawData.isDeleted,
isViewable: rawData.isVisibleToViewer,
@ -70,12 +73,12 @@ const getAnswers = async slug => {
topics: rawData.topics.map(topicObj => ({
tid: topicObj.tid,
name: topicObj.name,
url: topicObj.url,
url: quetrefy(topicObj.url),
})),
relatedQuestions: rawData.bottomRelatedQuestionsInfo.relatedQuestions.map(
questionObj => ({
qid: questionObj.qid,
url: questionObj.url,
url: quetrefy(questionObj.url),
text: JSON.parse(questionObj.title).sections,
})
),

View file

@ -2,6 +2,7 @@
// IMPORTS
////////////////////////////////////////////////////////
import AppError from '../utils/AppError.js';
import { quetrefy } from '../utils/urlModifiers.js';
import fetcher from './fetcher.js';
////////////////////////////////////////////////////////
@ -28,14 +29,14 @@ const feedAnswerCleaner = answer => ({
isAnon: answer.author.isAnon,
image: answer.author.profileImageUrl,
isVerified: answer.author.isVerified,
url: answer.author.profileUrl,
url: quetrefy(answer.author.profileUrl),
name: `${answer.author.names[0].givenName} ${answer.author.names[0].familyName}`,
credential: answer.authorCredential?.translatedString,
// additionalCredentials: answer?.credibilityFacts.map(),
},
question: {
text: JSON.parse(answer.question.title).sections,
url: answer.question.url,
url: quetrefy(answer.question.url),
qid: answer.question.qid,
isDeleted: answer.question.isDeleted,
},
@ -45,7 +46,7 @@ const feedPostCleaner = post => ({
isPinned: post.isPinned,
pid: post.pid,
isViewable: post.viewerHasAccess,
url: post.url,
url: quetrefy(post.url),
title: JSON.parse(post.title).sections,
isDeleted: post.isDeleted,
text: JSON.parse(post.content).sections,
@ -61,14 +62,14 @@ const feedPostCleaner = post => ({
image: post.author.profileImageUrl,
isVerified: post.author.isVerified,
isPlusUser: post.author.consumerBundleActive,
url: post.author.profileUrl,
url: quetrefy(post.author.profileUrl),
name: `${post.author.names[0].givenName} ${post.author.names[0].familyName}`,
credential: post.authorCredential?.translatedString,
},
...(post.tribeItem && {
space: {
name: post.tribeItem.tribe.nameString,
url: post.tribeItem.tribe.url,
url: quetrefy(post.tribeItem.tribe.url),
image: post.tribeItem.tribe.iconRetinaUrl,
description: post.tribeItem.descriptionString,
numFollowers: post.tribeItem.tribe.numFollowers,
@ -79,7 +80,7 @@ const feedQuestionCleaner = question => ({
type: 'question',
text: JSON.parse(question.title).sections,
qid: question.qid,
url: question.url,
url: quetrefy(question.url),
isDeleted: question.isDeleted,
numFollowers: question.followerCount,
creationTime: question.creationTime,
@ -105,12 +106,14 @@ const feedCleaner = feed => {
////////////////////////////////////////////////////////
// FUNCTION
////////////////////////////////////////////////////////
const getProfile = async slug => {
const KEYWORD = 'user';
const getProfile = async (slug, lang) => {
// getting data and destructuring it in case it exists
const res = await fetcher(`profile/${slug}`, 'user');
const res = await fetcher(`profile/${slug}`, { keyword: KEYWORD, lang });
const {
data: { user: rawData },
data: { [KEYWORD]: rawData },
} = JSON.parse(res);
if (!rawData)
@ -125,7 +128,7 @@ const getProfile = async slug => {
uid: rawData.uid,
image: rawData.profileImageUrl,
name: `${rawData.names[0].givenName} ${rawData.names[0].familyName}`,
profile: rawData.profileUrl,
profile: quetrefy(rawData.profileUrl),
isDeceased: rawData.isDeceased,
isBusiness: rawData.businessStatus,
isBot: rawData.isUserBot,
@ -184,7 +187,7 @@ const getProfile = async slug => {
numFollowingSpaces: rawData.numFollowedTribes,
spaces: rawData.followingTribesConnection.edges.map(space => ({
numItems: space.node.numItemsOfUser,
url: space.node.url,
url: quetrefy(space.node.url),
name: space.node.nameString,
image: space.node.iconRetinaUrl,
isSensitive: space.node.isSensitive,
@ -194,7 +197,7 @@ const getProfile = async slug => {
numFollowingTopics: rawData.numFollowedTopics,
topics: rawData.expertiseTopicsConnection.edges.map(topic => ({
name: topic.node.name,
url: topic.node.url,
url: quetrefy(topic.node.url),
isSensitive: topic.node.isSensitive,
numFollowers: topic.node.numFollowers,
image: topic.node.photoUrl,

View file

@ -3,13 +3,14 @@
////////////////////////////////////////////////////////
import AppError from '../utils/AppError.js';
import fetcher from './fetcher.js';
import { quetrefy } from '../utils/urlModifiers.js';
////////////////////////////////////////////////////////
// HELPER FUNCTIONS
////////////////////////////////////////////////////////
const topicCleaner = topic => ({
type: 'topic',
url: topic.url,
url: quetrefy(topic.url),
name: topic.name,
numFollowers: topic.numFollowers,
image: topic.photoUrl,
@ -18,7 +19,7 @@ const topicCleaner = topic => ({
const spaceCleaner = space => ({
type: 'space',
numUsers: space.tribeUserCount,
url: space.url,
url: quetrefy(space.url),
name: space.nameString,
description: space.descriptionString,
image: space.iconRetinaUrl,
@ -29,7 +30,7 @@ const profileCleaner = profile => ({
credential: profile.bestCredential?.translatedString,
isAnon: profile.isAnon,
name: `${profile.names[0]?.givenName} ${profile.names[0]?.familyName}`,
url: profile.profileUrl,
url: quetrefy(profile.profileUrl),
image: profile.profileImageUrl,
numFollowers: profile.followerCount,
isVerified: profile.isVerified,
@ -39,7 +40,7 @@ const profileCleaner = profile => ({
const questionCleaner = question => ({
type: 'question',
text: JSON.parse(question.title).sections,
url: question.url,
url: quetrefy(question.url),
isDeleted: question.isDeleted,
numFollowers: question.followerCount,
creationTime: question.creationTime,
@ -55,7 +56,7 @@ const answerCleaner = ({ question, previewAnswer: answer }) => ({
originalQuestion: {
text: JSON.parse(answer.originalQuestionIfDifferent.question.title)
.sections,
url: answer.originalQuestionIfDifferent.question.url,
url: quetrefy(answer.originalQuestionIfDifferent.question.url),
qid: answer.originalQuestionIfDifferent.question.qid,
},
}),
@ -69,7 +70,7 @@ const answerCleaner = ({ question, previewAnswer: answer }) => ({
numShares: answer.numSharers,
numAnswerRequests: answer.numRequesters,
isBusinessAnswer: answer.businessAnswer,
url: answer.url,
url: quetrefy(answer.url),
isSensitive: answer.isSensitive,
author: {
uid: answer.author.uid,
@ -77,7 +78,7 @@ const answerCleaner = ({ question, previewAnswer: answer }) => ({
image: answer.author.profileImageUrl,
isVerified: answer.author.isVerified,
isPlusUser: answer.author.consumerBundleActive,
url: answer.author.profileUrl,
url: quetrefy(answer.author.profileUrl),
name: `${answer.author.names[0].givenName} ${answer.author.names[0].familyName}`,
credential: answer.authorCredential?.translatedString,
},
@ -86,7 +87,7 @@ const postCleaner = post => ({
type: 'post',
pid: post.pid,
isViewable: post.viewerHasAccess,
url: post.url,
url: quetrefy(post.url),
title: JSON.parse(post.title).sections,
isDeleted: post.isDeleted,
isSensitive: post.isSensitive,
@ -103,7 +104,7 @@ const postCleaner = post => ({
image: post.author.profileImageUrl,
isVerified: post.author.isVerified,
isPlusUser: post.author.consumerBundleActive,
url: post.author.profileUrl,
url: quetrefy(post.author.profileUrl),
name: `${post.author.names[0].givenName} ${post.author.names[0].familyName}`,
credential: post.authorCredential?.translatedString,
},
@ -111,7 +112,7 @@ const postCleaner = post => ({
space: {
isSensitive: post.tribeItem.tribe.isSensitive,
name: post.tribeItem.tribe.nameString,
url: post.tribeItem.tribe.url,
url: quetrefy(post.tribeItem.tribe.url),
image: post.tribeItem.tribe.iconRetinaUrl,
description: post.tribeItem.descriptionString,
numFollowers: post.tribeItem.tribe.numFollowers,
@ -139,11 +140,14 @@ const resultsCleaner = results => {
////////////////////////////////////////////////////////
// FUNCTION
////////////////////////////////////////////////////////
const getSearch = async querySlug => {
const res = await fetcher(`search/${querySlug}`, 'searchConnection', false);
const KEYWORD = 'searchConnection';
const getSearch = async (querySlug, lang) => {
const options = { keyword: KEYWORD, lang, toEncode: false };
const res = await fetcher(`search/${querySlug}`, options);
const {
data: { searchConnection: rawData },
data: { [KEYWORD]: rawData },
} = JSON.parse(res);
if (!rawData)

View file

@ -2,17 +2,21 @@
// IMPORTS
////////////////////////////////////////////////////////
import AppError from '../utils/AppError.js';
import { quetrefy } from '../utils/urlModifiers.js';
import fetcher from './fetcher.js';
////////////////////////////////////////////////////////
// FUNCTION
////////////////////////////////////////////////////////
const getTopic = async slug => {
const KEYWORD = 'topic';
const getTopic = async (slug, lang) => {
// getting data and destructuring it in case it exists, else throwing an error
const res = await fetcher(`topic/${slug}`, 'topic');
const res = await fetcher(`topic/${slug}`, { keyword: KEYWORD, lang });
const {
data: { topic: rawData },
data: { [KEYWORD]: rawData },
} = JSON.parse(res);
if (!rawData)
@ -24,7 +28,7 @@ const getTopic = async slug => {
const data = {
tid: rawData.tid,
name: rawData.name,
url: rawData.url,
url: quetrefy(rawData.url),
image: rawData.photoUrl,
aliases: rawData.aliases,
numFollowers: rawData.numFollowers,
@ -34,7 +38,7 @@ const getTopic = async slug => {
mostViewedAuthors: rawData.mostViewedAuthors.map(author => ({
uid: author.user.uid,
name: `${author.user.names[0].givenName} ${author.user.names[0].familyName}`,
profile: author.user.profileUrl,
profile: quetrefy(author.user.profileUrl),
image: author.user.profileImageUrl,
isAnon: author.user.isAnon,
isVerified: author.user.isVerified,
@ -46,7 +50,7 @@ const getTopic = async slug => {
relatedTopics: rawData.relatedTopics.map(topic => ({
tid: topic.tid,
name: topic.name,
url: topic.url,
url: quetrefy(topic.url),
image: topic.photoUrl,
numFollowers: topic.numFollowers,
})),