Greasy Fork

AlienwareArena Auto Daily-Quests

Stop wasting time seeing the forum every time you forgot what to do with every daily quest

目前为 2022-01-16 提交的版本。查看 最新版本

//* eslint-env browser, es6, greasemonkey, jquery */
// ==UserScript==
// @name         AlienwareArena Auto Daily-Quests
// @namespace    U5levhE76dPtqAkMLISdx2sj7BES
// @version      0.1.2
// @description  Stop wasting time seeing the forum every time you forgot what to do with every daily quest
// @author       _SUDO
// @source       https://codeberg.org/Sudo/AlienwareArena-Auto-Daily-Quests
// @supportURL   https://codeberg.org/Sudo/AlienwareArena-Auto-Daily-Quests/issues
// @compatible   chrome Chrome + Tampermonkey or Violentmonkey
// @compatible   edge Edge + Tampermonkey or Violentmonkey
// @compatible   firefox Firefox + Greasemonkey or Tampermonkey or Violentmonkey
// @match        *://*.alienwarearena.com/
// @exclude      *://*.alienwarearena.com/account
// @icon         https://www.alienwarearena.com/favicon.ico
// @license      MIT
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @noframes
// ==/UserScript==

'use strict'

const config = {
	news_to_read: 4, // How many news will be automatically read. By default are 4 since sometimes news don't like to be read :3
	use_browser_notif: true, // If the normal inside-page notifications can't be show, create a browser notification instead
	use_sound_notif: true, // Will be only triggered if an error occurs that requires manual interaction
	verbose_logging: true, // If messages should be printed in console
	dev_unsafe_mode: false, // Expose all variables and functions to the console (AD_*)
}

function logger(...args) {
	if (!config.verbose_logging) return
	console.log('[AD]', ...args)
}
logger.error = function (...args) {
	if (!config.verbose_logging) return
	console.error('[AD]', ...args)
}

// Variable to keep track of elements (will be shared)
const ref = {
	pages: {
		mainURL: window.location.host,
		// APIs
		notifications: '/ajax/user/notifications/user-notifications-data',
		status: '/api/v1/users/arp/status',
		// Pages
		ladder: 'rewards/leaderboard',
		news: '/esi/featured-tile-data/News',
		profile: 'account/personalization',
		account: 'account',
		search: '/search/forum_topic/',
	},
	elements: {
		account: {
			// This is necessary.
			token: '#user_account_model__token',
			phone: '#user_account_model_phone',
			birthdayNotifications: '#user_account_model_birthdayNotifications',
		},
		login: {
			btn: 'a.nav-link.nav-link-login',
			user: '#_username',
			pass: '#_password',
			remember: '#remember_me',
			login: '#_login',
		},
		quest: {
			title: '.quest-title',
			progress: '.quest-item-progress',
		},
		quests: {
			avatars: 'div[id*=avatar-]',
			badges:
				'.account-badges__list-badge:not(.disabled):not(.account-badges__list-badge--highlight)',
			borders: '.account-borders__list-border:not(.disabled)',
		},
	},
}

/**
 * @param {string} type name of type, listed in `/api/v1/users/arp/status`
 * @param {Array<String>|String} name Accept multiple strings, will be used if type fails
 * @param {{qName: string}} [action] On quest match, this will be executed. Receive quest name as a prop.
 */

const quests = [
	{
		type: 'read_articles',
		name: [
			'Extra extra read all about it!',
			'Extra Extra! Read all about it.',
			'Keep up with the times',
		],
		action: () => {
			AD.getNews((news) => {
				if (!news) return

				// First, we need to fetch the current featured news IDs. Then get
				// the page to simulate like if was opened normally in a tab.

				// Populate array
				let newsToRead = []
				for (let i = 0; i < config.news_to_read; i++)
					newsToRead.push(news.data[i])

				const promises = newsToRead.map(
					(newsObj) =>
						new Promise((resolve, reject) =>
							$.ajax({
								url: newsObj.url,
								type: 'get',
							})
								.done(() => {
									logger(`Post request send to news: ${newsObj.id}`)
									return resolve()
								})
								.fail(({ status, statusText }) => {
									logger.error(`Error in request to news ${newsObj.id}:`, {
										status,
										statusText,
									})
									return reject()
								})
						)
				)

				//* Wait until all promises are fulfilled
				//* and check for any rejections to notify the user. Errors included
				Promise.allSettled(promises).then((values) => {
					AD.notifyUser('Successfully sended news read requests', 'success')

					if (values.find((val) => val.status === 'rejected')) {
						AD.notifyUser(
							'Some requests were rejected, check the console for more information',
							'error'
						)
					}
				})
			})
		},
	},
	{
		type: 'visit_leaderboard',
		name: 'Climbing the ladder',
		action: () => {
			$.ajax({
				url: ref.pages.ladder,
				type: 'get',
			})
				.done(() => {
					logger('Got leaderboard page')
					AD.notifyUser(
						'Leaderboard quest completed\nRefresh the page to see the updated quest'
					)
				})
				.fail(({ status, statusText }) => {
					logger.error('Request Error: getting Leaderboard page!', {
						status,
						statusText,
					})
					AD.notifyUser('Error getting Leaderboard page', 'error')
				})
		},
	},
	{
		type: 'post_replies',
		name: [
			'Converse among yourselves',
			'Hey there, neighbor!',
			"Let's talk about it",
			"We're all friends here",
		],
		action: (qName) => {
			// Comment or reply in forum...
			// https://www.alienwarearena.com/comments/${post_id}/new/ucf

			// Make quest name URL friendly
			qName = qName
				.replace(/[^a-z0-9]/gi, '-') // Replace special characters with dash to make it URL friendly
				.replace(/-{2,}/gi, '-') // Remove extra dashes
				.replace(/(^-{1,}|-{1,}$)/gi, '') // Remove dashes at the start and end of string
			const encoded_qName = encodeURI(`[DailyQuest] ${qName}`)

			logger('Replaced special characters from quest name: ', qName)

			AD.domParsePage(`${ref.pages.search}${encoded_qName}`, (doc) => {
				logger(
					`Searching in: "${
						ref.pages.mainURL + ref.pages.search
					}${encoded_qName}"`
				)
				logger(
					'Forum posts found:',
					doc.querySelectorAll('.search-results__wrapper-forum_topic')
				)

				const commentID = RegExp(/\/([\d]+)\//)
				let commentURL = doc.querySelector(
					'.search-results__wrapper-forum_topic'
				).pathname
				commentURL = commentURL.split(commentID)[1]

				// Create comment, see http://mdn.io/FormData
				// If you want to customize this, I suggest you creating your own message with the forum editor and just copy paste the network post request
				// TODO: add to config
				const comments = [
					'ty',
					'Thanks',
					'Hey there, neighbor!',
					'Hello my baby, hello my honey, hello my neighbor!',
					'Hi! Thanks for the info.',
					'Hello World!',
				]
				const formData = new FormData()
				formData.append(
					'topic_post[content]',
					`<p>${comments[Math.floor(Math.random() * comments.length)]}</p>`
				)

				logger(`Send to: ${ref.pages.mainURL}/ucf/show/${commentURL}`)
				$.ajax({
					url: `comments/${commentURL}/new/ucf`,
					type: 'POST',
					data: formData,
					processData: false,
					contentType: false,
				})
					.done((post_response) => {
						logger('Successfully reply in forum!')
						AD.notifyUser(
							`Successfully reply in forum\nTrying to delete reply...`
						)

						const { postId } = post_response

						console.assert(
							post_response || postId,
							{ id: postId, response: post_response },
							'[AD] Error in forum response object'
						)

						// Now try to delete the reply
						// FIXME: some post found are deleted/hidden/blocked (returns 404) but allows to post on BUT NOT REMOVE THE COMMENT
						// *^ Filter them or just ignore the warning..? No user should be able to access them!!! (really is a issue from the site not mine)
						$.ajax({
							url: `/forums/post/delete/${postId}`,
							type: 'POST',
						})
							.done(() => {
								logger('Successfully deleted previous reply in forum!')
								AD.notifyUser('Removed previous reply in forum')
								AD.notifyUser(
									`${qName} quest completed!\nRefresh the page to see the updated quest`,
									'success'
								)
							})
							.fail(({ status, statusText }) => {
								logger.error(
									'Error trying to delete the previous reply in forum!',
									{ status, statusText }
								)
								AD.notifyUser(
									`Error trying to delete the previous reply in forum.\nPlease check manually at "${ref.pages.mainURL}/ucf/show/${commentURL}".`,
									'error'
								)
							})
					})
					.fail(({ status, statusText }) => {
						logger.error('Error trying to reply in forum', {
							status,
							statusText,
						})
					})
			})
		},
	},
	{
		type: 'change_border',
		name: ['Frame it', 'Out with the old, in with the new'],
		action: () => {
			/*change border*/
			AD.domParsePage(ref.pages.profile, (doc) => {
				const borders = AD.pickRandomElement(ref.elements.quests.borders, doc)

				if (borders) {
					const selectBorderID = borders.getAttribute('data-border-id')
					logger('BORDER:', selectBorderID)
					$.ajax({
						url: '/border/select',
						type: 'POST',
						data: JSON.stringify({ id: selectBorderID }),
					})
						.done(() => {
							logger('Successfully changed border *wink wink*')
							AD.notifyUser(
								'Switcharoo quest completed!\nRefresh the page to see the updated quest',
								'success'
							)
						})
						.fail(({ status, statusText }) => {
							logger.error('Error trying to change border', {
								status,
								statusText,
							})
						})
				} else {
					AD.notifyUser(
						'You need at least one border unlocked to complete this task.\nPlease, upload one and try again later.',
						'error'
					)
				}
			})
		},
	},
	{
		type: 'share_page',
		name: [
			'Share the love',
			'The more, the merrier',
			'Did you hear the news?',
			'Spread the love',
		],
		action: () => {
			/* read news */
			AD.getNews((news) => {
				if (!news) return

				// Get the last posted news
				// and fake share event to backend
				$.ajax({
					url: `/arp/quests/share/${news.data[0].id}`,
					type: 'post',
				})
					.done(() => {
						logger('Successfully shared *wink wink*')
						AD.notifyUser(
							'Shared the love quest completed!\nRefresh the page to see the updated quest',
							'success'
						)
					})
					.fail(({ status, statusText }) => {
						logger.error('Error trying to fake share', { status, statusText })
					})
			})
		},
	},
	{
		type: 'change_avatar',
		name: ['Switcharoo', 'Just picture it...', 'Smile!', 'Say cheese'],
		action: () => {
			/*change your avatar (aka pfp)*/
			// TODO: test and handle when the user has no avatars

			AD.domParsePage(ref.pages.profile, (doc) => {
				const avatar = AD.pickRandomElement(ref.elements.quests.avatars, doc)

				if (avatar) {
					const avatar_id = avatar
						.getElementsByTagName('a')[0]
						.getAttribute('data-avatar-id')
					$.ajax({
						url: `/account/profile/avatars/switch/${avatar_id}`,
						type: 'get',
					})
						.done(() => {
							logger('Successfully changed avatar *wink wink*')
							AD.notifyUser(
								'Switcharoo quest completed!\nRefresh the page to see the updated quest',
								'success'
							)
						})
						.fail(() => logger('Error trying to change avatar'))
				} else {
					logger.error(
						'No profile picture found. User probably have never uploaded any.'
					)
					AD.notifyUser(
						'You need at least one different profile picture to change automatically.\nPlease, upload one and try again later.'
					)
				}
			})
		},
	},
	{
		type: 'change_badge',
		name: [
			"There's a new sheriff in town...",
			'Show off your colors!',
			'Show off in style.',
			'Show and tell',
		],
		action: () => {
			/*change badges*/
			if (AD.getUser().level < 3) {
				AD.notifyUser(
					"Your current level is too low and doesn't allow badges",
					'error'
				)
				return
			}

			AD.domParsePage(ref.pages.profile, (doc) => {
				const badges = () =>
					AD.pickRandomElement(ref.elements.quests.badges, doc)
				logger('Badges:', badges())

				if (!badges()) {
					AD.notifyUser(
						'You need at least one badge to change it automatically.',
						'error'
					)
					return
				}

				// Populate the badges array, at least we know that al least one badge will be used
				let badges_ids = [
					badges().getAttribute('data-badge-id'),
					badges().getAttribute('data-badge-id'),
					badges().getAttribute('data-badge-id'),
				]

				// If any duplicated badge was selected, remove it from the list
				// If we don't do this, then the request will be rejected if any duplicates
				badges_ids = [...new Set(badges_ids)]

				// Finally, check the amount of badges allowed for the user level
				if (AD.getUser().level <= 7) badges_ids.length = 1
				else if (AD.getUser().level <= 11) badges_ids.length = 2

				logger(`${badges_ids.length} Badges:`, badges_ids)
				logger(JSON.stringify(badges_ids))

				$.ajax({
					url: `/badges/update/${AD.getUser().id}`, // url update uses user_id
					type: 'post',
					//data: badges_ids
					data: JSON.stringify(badges_ids),
				})
					.done((e) => {
						logger('Successfully changed badges *wink wink*', e)
						AD.notifyUser(
							"There's a new sheriff in town quest completed!\nRefresh the page to see the updated quest",
							'success'
						)
					})
					.fail(({ status, statusText }) =>
						logger.error('Error trying to change badges', {
							status,
							statusText,
						})
					)
			})
		},
	},
	{
		type: 'update_about_me',
		name: "What's new with you?",
		action: () => {
			AD.domParsePage(ref.pages.account, (doc) => {
				// TODO: move to config
				const aboutMessages = [
					'Those who know do not speak.\nThose who speak do not know.\n♥',
					"Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.",
					'Find out who you are and do it on purpose.',
					"One's real life is often the life that one does not lead.",
				]

				const formData = {
					//* That's what I get it the request. I'm unsure what contains with 2A, phone or newsletter nor other site connections...
					// TODO: check request content with other connections and options
					user_account_model: {
						username: AD.getUser().username, // string
						phone: '', // string, can be empty
						birthdayNotifications: '1', // number but its string
						_token: () => doc.querySelector(ref.elements.account.token).value, // string, hidden element form data
						about:
							aboutMessages[Math.floor(Math.random() * aboutMessages.length)],
					},
				}

				// We could use query params and all stuff here, but
				// Jquery has a build in function to make our like easier $.param()

				$.ajax({
					url: ref.pages.account,
					type: 'post',
					data: $.param(formData),
					processData: true,
					contentType: 'application/x-www-form-urlencoded',
				})
					.done(() => {
						logger('Successfully updated about message *wink wink*')
						AD.notifyUser(
							"What's new with you quest completed!\nRefresh the page to see the updated quest",
							'success'
						)
					})
					.fail(({ status, statusText }) =>
						logger.error('Error trying to fake about message', {
							status,
							statusText,
						})
					)
			})
		},
	},
]

/**
 * @see {@link https://marcgg.com/blog/2016/11/01/javascript-audio/}
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/AudioContext}
 */
function notifSound() {
	try {
		const AudioContext = window.AudioContext || window.webkitAudioContext
		const context = new AudioContext()
		const audio = context.createOscillator()
		audio.type = 'sine'
		audio.frequency.value = 261.6
		audio.connect(context.destination)
		audio.start()
		setTimeout(() => context.close(), 1500) // 1.5 sec of sound notification
	} catch (err) {
		logger('Audio Notification Sound Error!', err)
	}
}

// Will serve as cache for the quest and
// the daily RP votes
let statusCache = null

class AutoDaily {
	logAccountIn() {
		// Notify user
		AD.notifyUser('Login to allow me to automate your daily points :)')

		/*
          //* Auto loggin if you want...
          const mail = '[email protected]'
          const pass = 'yoursupersecretpasswordhere'

          const loginBTN = AD.getElement(ref.elements.login.btn)
          loginBTN.click()

          //* Await 1sec
          setTimeout(1000)

          //* Try to login
          const mailBTN = AD.getElement(ref.elements.login.user)
          const passBTN = AD.getElement(ref.elements.login.pass)

          mailBTN.value = mail
          passBTN.value = pass

          //* Should remember user?
          // const rememberMe = AD.getElement(ref.elements.login.remember)
          // remember_me.checked = true

          // Click login BTN
          AD.getElement(ref.elements.login.login).click()
        */
	}

	/**
	 * Try to get user notifications and check their success response
	 * @returns {Promise<boolean>} If user is logged in
	 */
	async isUserLogged() {
		//* Can be used a global variable embedded:
		//* window.user_is_logged_in
		return new Promise((resolve) =>
			$.ajax({
				url: ref.pages.notifications,
				type: 'get',
			})
				.done((resp) => {
					if (resp.success) {
						logger('User is logged in :)')
						return resolve(true)
					}

					logger('User is not logged in')
					return resolve(false)
				})
				.fail(({ status, statusText }) => {
					logger.error('isLoggedIn() unexpected error!', {
						status,
						statusText,
					})
					return resolve(false)
				})
		)
	}

	async initAutoQuest() {
		logger('initAutoQuest()')

		const isCompleted = await this.getQuestProgress()

		if (isCompleted) return
		else {
			AD.getUserStatus().then(({ quest }) => {
				logger('QUEST:', { title: quest.title, type: quest.type })
				this.execQuest({
					name: quest.title.replace(/_/g, ' '), // use spaced name instead of hypens
					type: quest.type,
				})
			})
		}
	}

	/**
	 *
	 * @param {string} name Name of the quest, only used if the quest type doesn't match
	 * @param {string} type Quest type
	 */
	execQuest({ name, type }) {
		const qType = quests.find((e) => e.type.includes(type))

		if (qType) {
			logger(`Execution quest "${name}" action()`)
			qType.action(name)
		} else {
			this.questNotFound()
		}
	}

	/**
	 * @param {boolean} forceUpdate If true, will skip saved cache (if exists)
	 * @returns {Promise<object>|Promise<null>|Error}
	 */
	static async getUserStatus(forceUpdate = false) {
		return new Promise((resolve, reject) => {
			if (!forceUpdate && statusCache) {
				logger('Returning status from cache')
				return resolve(statusCache)
			}

			$.ajax({
				url: ref.pages.status,
				type: 'get',
			})
				.done((resp) => {
					logger('Got user status from api:', resp)

					//* Quests are in an array, being the first element the current quest
					//* ARP is also in an array, being the second element the content vote
					const val = { quest: resp.quests[0], arp: resp.daily_arp[1] }

					// Save to cache
					statusCache = val

					return resolve(val)
				})
				.fail(({ status, statusText }) => {
					logger.error('Error getting user API status!', {
						status,
						statusText,
					})
					return reject(null)
				})
		})
	}

	/**
	 *
	 * @returns {Promise<Number>}
	 */
	async getDailyRP() {
		return await AD.getUserStatus().arp
	}

	questNotFound() {
		logger('Quest not found!')
		const questTitle = AD.getElement(ref.elements.quest.title)

		if (questTitle.nodeName === 'A' && questTitle.href !== '#') {
			logger('Quest has a link, clicked quest')
			AD.notifyUser(`Quest ${questTitle.textContent} has a link`)
			$.ajax({
				url: new URL(questTitle.href).pathname,
				type: 'get',
			})
				.done(() => {
					logger(`Got ${questTitle} page`)
					AD.notifyUser(`Quest completed, restart the page`)
				})
				.fail(({ status, statusText }) => {
					logger.error(`Request Error: getting ${questTitle} page!\n`, {
						status,
						statusText,
					})
					AD.notifyUser(`Error getting ${questTitle} page`, 'error')
				})
		} else {
			logger('Quest has no link :(')
			AD.notifyUser(
				'Quest has no link, requires manual interaction :(',
				'error'
			)
		}
	}

	/**
	 * @returns {Promise<Boolean>}
	 */
	async getQuestProgress() {
		// TODO: check progress when is not a boolean

		return AD.getUserStatus().then(({ quest }) => {
			// const progress = quest.progress // AD.getElement(ref.elements.quest.progress)

			/*
       +progress.textContent Only works if the string only contains numeric characters, else it returns NaN. ('/' is not a number. Empty string are converted to 0 zero)
       @see https://stackoverflow.com/a/175787
      */
			if (quest.completed) {
				logger('Quest is already completed')
				AD.notifyUser('Quest is already completed :D')
				return true
			}

			//* Check if x/x or x of x is completed (eg. 0/2, 2 of 4)
			/*
			const _ = RegExp(/(\d+)(\D+)(\d+)/gi)
			if (_.test(progress)) {
				// Match regex
				// Filter boolean remove empty strings
				const outOf = progress.split(_).filter(Boolean)
				if (outOf[0] >= outOf[1]) {
					logger('Progress regex === complete')
					return true
				}

				logger('Progress regex:', { 0: outOf[0], 1: outOf[1] })
			}
      */
			logger('Quest not completed')

			return false
		})
	}

	/**
	 * Create visual in-site notification
	 * @param {String} msg
	 * @param {"info"|"success"|"warning"|"error"} type The color of the message will use the type
	 * @see {@link https://github.com/mouse0270/bootstrap-notify}
	 */
	static notifyUser(msg, type = 'info') {
		const browserNotif = () => {
			if (typeof GM_notification === 'function') {
				GM_notification({
					title: GM_info.script.name,
					text: `[${type.toUpperCase()}] ${msg}`,
					silent: false,
					// image:
				})
			}
		}

		try {
			$.notify(
				{
					element: 'body',
					// options
					// title: GM_info.script.name,
					message: msg,
				},
				{
					// settings
					z_index: 9999,
					// newest_on_top: true,
					placement: {
						from: 'top',
						align: 'left',
					},
					type: type,
					// FIXME: onshow() get executed when the notification can't be displayed
					onShow: () => {
						logger('onShow()...')
						// Make a sound if is important
						if (type === 'warning' || type === 'error') {
							console.log('sound here.', msg)
							notifSound()
						}
					},
					timer: null,
					allow_dismiss: true,
				}
			)
		} catch (err) {
			logger.error(
				'notifyUser(): ERROR creating notification.\nMaybe JQuery is not loaded'
			)
			if (config.use_browser_notif) browserNotif()
		}
	}

	/* ----------
	 *    UTILS
	 * ----------
	 */

	/**
	 * @typedef {Object} getUserObject
	 * @property {number|string} userId User ID
	 * @property {string} userName User name
	 */
	/**
	 * Allows to get the user specify data from exposed variables
	 * @returns {getUserObject} Returns null on error
	 */
	static getUser() {
		//* user_id is present in every page as one of the embedded variables
		//* If not, can be obtained with: `ref.pages.notifications`
		// Please, if anyone has problems with this way of getting your user ID.
		// Let me know and I'll use the notifications instead

		const id = unsafeWindow.user_id
		const username = unsafeWindow.user_username
		const level = unsafeWindow.current_level

		return {
			id: typeof id === 'number' || typeof id === 'string' ? id : null,
			username: typeof username === 'string' ? username : null,
			level:
				typeof level === 'number' || typeof level === 'string' ? level : null,
		}
	}

	/**
	 * Allows to get a HTML element with error handling
	 * @param {string} elem Query selector string to match element
	 * @param {HTMLElement} [parent=document] The parent node of the element
	 * @returns {HTMLElement} Random element
	 */
	static getElement(elem, parent = document) {
		// TODO: add error notification

		if (!elem) throw new Error('No element provided')

		let result = null
		try {
			const _elem = parent.querySelector(elem)
			if (_elem) result = _elem
		} catch (err) {
			logger.error(`Error trying to get "${elem}"`)
		}

		return result
	}

	/**
	 * Allows to pick a random HTML element with error handling
	 * @param {string} elements_array Query selectorAll string to match elements
	 * @param {HTMLElement} [parent=document] The parent node of the element
	 * @returns {HTMLElement|null} Random element
	 */
	static pickRandomElement(elements_array, parent = document) {
		// TODO: add error notification

		if (!elements_array) throw new Error('No elements provided')

		let result = null
		try {
			const _elem = parent.querySelectorAll(elements_array)
			if (_elem) result = _elem[Math.floor(Math.random() * _elem.length)]
		} catch (err) {
			logger.error(`Error trying to get "${elements_array}"\n`, err)
		}

		return result
	}

	/**
	 * @callback handleDataCallback
	 * @param {HTMLBodyElement} responseDocument Parsed body element to use
	 * @returns {void}
	 */
	/**
	 * Get all news of the website
	 * @param {handleDataCallback} handleData Callback function to use the news object
	 * @returns {void} Returns on error
	 */
	static getNews(handleData) {
		if (typeof handleData !== 'function')
			throw new Error('getNews expected a function as parameter')

		$.ajax({
			url: ref.pages.news,
			type: 'get',
		})
			.done((news) => {
				// logger('News:\n', news)
				handleData(news)
			})
			.fail(({ status, statusText }) => {
				logger.error('Error getNews()', { status, statusText })
				return null
			})
	}

	/**
	 * Allows to fetch the content of a page as a HTML document
	 * @param {String} page URL of the page to parse
	 * @param {Function} handleData Callback function to use the parsed document
	 * @return {HTMLBodyElement|null} Shadow element of the page parsed
	 */
	static domParsePage(page, handleData) {
		if (!page) throw new Error('domParsePage expected a page as parameter')
		if (typeof handleData !== 'function')
			throw new Error('domParsePage expected a function as parameter')

		$.ajax({
			url: page,
			type: 'get',
		})
			.done((html) => {
				const parser = new DOMParser().parseFromString(html, 'text/html')

				/*
					* Create and open a page with the HTML document from the parser
					{
						let w = window.open()
						w.document.write(html)
						w.document.close()
					}
					*/

				handleData(parser.body)
			})
			.fail(({ status, statusText }) => {
				logger.error('Error domParsePage()', { status, statusText })
				return null
			})
	}

	async beforeInit() {
		logger('BeforeInit()...')

		//* Check if jquery is loaded. If not, I dought something will work...
		try {
			// eslint-disable-next-line
			jQuery()
		} catch (err) {
			logger.error('No JQuery found.\n', err)
			AD.notifyUser(
				'Looks like JQuery is not loaded in the page.\nPlease verify if any extension may be interfiering.',
				'warning'
			)
			return
		}

		// Used to restore notifications backgrounds/colors
		if (typeof GM_addStyle === 'function') {
			GM_addStyle(`
           .alert-success {
            color:#004a31
           }
           .alert-info {
            color:#007d7d;
            background-color:#ccfcfc;
            border-color:#b8fbfb
           }
           .alert-error {
            color:#7d0000;
            background-color:#fccccc;
            border-color:#fbb8b8
           }`)
		}

		this.isUserLogged().then((logged) =>
			logged ? this.initAutoQuest() : this.logAccountIn()
		)
	}

	init() {
		logger('init()...')

		// Awaits until site loads
		if (document.readyState === 'complete') {
			logger('Document ready!')
			this.beforeInit()
		} else {
			logger('Document not ready, adding Event Listener...')
			document.addEventListener('readystatechange', () => {
				logger('Document ready!')
				if (document.readyState === 'complete') this.beforeInit()
			})
		}
	}
}

// Make life easier
const AD = AutoDaily

// Make ref actions console accessible
if (config.dev_unsafe_mode) {
	unsafeWindow.AD_config = config
	unsafeWindow.AD_ref = ref
	unsafeWindow.AD_quests = quests
	unsafeWindow.AD_AutoDaily = AutoDaily
	unsafeWindow.AD_notifSound = notifSound
	if (typeof GM_registerMenuCommand === 'function') {
		//! Will trigger popup warnings
		GM_registerMenuCommand(
			'Open Status API',
			() => window.open(ref.pages.status),
			null
		)
		GM_registerMenuCommand(
			'Open Notifications API',
			() => window.open(ref.pages.notifications),
			null
		)
	}
}

// Start
new AutoDaily().init()