const axios = require('axios')
const setCookie = require('set-cookie-parser')
const qs = require('querystring')
const objectAssignDeep = require('object-assign-deep')
const EksiGuest = require('./EksiGuest')
const EksiMember = require('./EksiMember')
const { request } = require('./utils')
const { URLS } = require('./constants')
const { AuthError } = require('./exceptions')
/**
* Manage all Eksi Sozluk abilities.
*
* @augments EksiGuest
*/
class EksiSozluk extends EksiGuest {
/**
* Create an Eksi Sozluk instance.
*
* @param {object} options Eksi Sozluk instance settings
* @param {object} options.httpClient Axios settings as HTTP client, all Axios settings are useable
* @param {number} [options.httpClient.timeout=3000] Timeout of requests in miliseconds
* @param {string} [options.httpClient.baseURL=https://eksisozluk.com] Base URL of Eksi Sozluk, you can use proxy here
*/
constructor (options = {}) {
// handle default options
const _options = objectAssignDeep(
{
httpClient: {
timeout: 3000,
baseURL: URLS.BASE
}
},
options
)
// make http client ready
const httpClient = axios.create(_options.httpClient)
super(httpClient)
this._request = request(httpClient)
}
/**
* Check is user authenticated or not.
*
* @param {string} [cookies=this.cookies] Cookies string.
* @returns {Promise.<boolean>} If user authenticated returns true, otherwise false.
*/
isAuthenticated (cookies = this.cookies) {
return new Promise((resolve, reject) => {
if (cookies) {
axios({
url: URLS.BASE,
method: 'GET',
headers: {
cookie: cookies
}
}).then(res => {
const isLoggedIn = res.data.includes('data-logged-in="true"')
resolve(isLoggedIn)
})
} else {
// cookies not exist, return false
resolve(false)
}
})
}
/**
* Is verify ReCaptcha required to login?
*
* @returns {Promise.<boolean>} If ReCaptcha required returns true, otherwise false.
*/
isRecaptchaRequired () {
return new Promise((resolve, reject) => {
axios({
url: URLS.LOGIN,
method: 'GET'
}).then(res => {
// check recaptcha
const isReCaptchaRequired = res.data.includes('g-recaptcha')
resolve(isReCaptchaRequired)
})
})
}
/**
* @typedef SessionToken
* @property {string} value Token string.
* @property {(Date|null)} expiresAt When will token expires?
*/
/**
* Create Eksi Sozluk session token with your credentials.
*
* @param {string} email Your email address.
* @param {string} password Your password.
* @param {object} options Parameters that user can specify.
* @param {boolean} [options.extendTime=false] If true, token will expire at 2 weeks later.
* @returns {SessionToken} Eksi Sozluk session token.
* @throws {AuthError} User not authorized, password or email is wrong.
*/
createToken (email, password, options = {}) {
return new Promise((resolve, reject) => {
// handle default options
const _options = objectAssignDeep(
{
extendTime: false
},
options
)
axios({
url: URLS.LOGIN,
method: 'GET'
})
.then(res => {
// check recaptcha
const isReCaptchaRequired = res.data.includes('g-recaptcha')
if (isReCaptchaRequired) {
return reject(new Error('ReCaptcha Required'))
}
return res
})
.then(res => {
// parse csrf token and cookies
const csrfRegex = new RegExp(
'(?<=input name="__RequestVerificationToken" type="hidden" value=")(.*)(?=" />)',
'u'
)
const csrfToken = csrfRegex.exec(res.data)[0]
const cookies = setCookie.parse(res.headers['set-cookie'], {
map: true
})
const csrfTokenInCookies = cookies.__RequestVerificationToken.value
return { csrfToken, csrfTokenInCookies }
})
.then(async ({ csrfToken, csrfTokenInCookies }) => {
const requestBody = {
UserName: email,
Password: password,
RememberMe: _options.extendTime,
__RequestVerificationToken: csrfToken
}
const config = {
maxRedirects: 0,
validateStatus: status => {
return status === 302 // accept just redirects
},
headers: {
Cookie: `__RequestVerificationToken=${csrfTokenInCookies};`
}
}
return await axios.post(URLS.LOGIN, qs.stringify(requestBody), config)
})
.then(res => {
const isUnknownError = res.data.includes(
'<title>büyük başarısızlıklar sözkonusu - ekşi sözlük</title>'
)
if (isUnknownError) {
return reject(new Error('Unknown Error'))
}
const cookies = setCookie.parse(res.headers['set-cookie'], {
map: true
})
resolve({
value: cookies.a.value, // token
expiresAt: cookies.a.expires || null
})
})
.catch(err => {
// password or username is wrong
if (err.response && err.response.status === 404) {
return reject(new AuthError())
}
// handle other errors
reject(err)
})
})
}
/**
* Login Eksi Sozluk with your credentials.
*
* @param {string} email Your email address.
* @param {string} password Your password.
* @returns {EksiMember} Eksi Sozluk session.
* @throws {AuthError} User not authorized, password or email is wrong.
*/
async login (email, password) {
const token = await this.createToken(email, password, { extendTime: true })
const cookie = `a=${token.value}`
// no need for validate the session token
const member = new EksiMember(this.httpClient, cookie)
await member.retrieve()
return member
}
/**
* Login Eksi Sozluk with session cookie.
*
* @param {string} token Session token of member.
* @returns {EksiMember} Eksi Sozluk session.
* @throws {AuthError} User not authorized.
*/
async loginWithToken (token) {
const cookie = `a=${token}`
// check given session token
const isAuthenticated = await this.isAuthenticated(cookie)
if (!isAuthenticated) {
throw new AuthError()
}
const member = new EksiMember(this.httpClient, cookie)
await member.retrieve()
return member
}
}
module.exports = EksiSozluk