Reddit Auth (*.reddit.com only)

Handle authentication for Reddit, and provide some helper functions for cookies and tokens.

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greasyfork.org/scripts/575868/1812157/Reddit%20Auth%20%28%2Aredditcom%20only%29.js

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name        Reddit Auth (*.reddit.com only)
// @namespace   https://greasyfork.org/en/users/1574555-littux
// @version     1.0.1
// @description Handle authentication for Reddit, and provide some helper functions for cookies and tokens.
// @author      littux
// @match       https://*.reddit.com/*
// @grant       GM_xmlhttpRequest
// @connect     www.reddit.com
// @icon        https://b.thumbs.redditmedia.com/7GVLmrH9CdZeqXceSEWkmL8_DSUKRGUfwMxnUNh8D8A.png
// @license     GPL-3.0-only
// @run-at      document-start
// ==/UserScript==

unsafeWindow.__littuxUserscripts__ ??= {};
const userScripts = unsafeWindow.__littuxUserscripts__;

const gmFetch = (options) => {
	return new Promise((resolve, reject) => {
		GM_xmlhttpRequest({
			...options,
			onload: (res) => resolve(res),
			onerror: (err) => reject(err)
		});
	});
}

/** "ping" a URL to get its cookies by pretending to fetch an image */
const pingURL = (url) => {
	return new Promise((resolve) => {
		const img = new Image();
		img.onload = () => resolve();
		img.onerror = () => resolve();
		img.src = url + (url.includes("?") ? "&" : "?") + "v=" + Date.now();
	});
}

const getCookie = async (name) => {
	if (typeof window.cookieStore?.get === "function") {
		return (await cookieStore.get(name))?.value ?? null;
	}
	const cookie = "; " + document.cookie + ";";
	const searchPattern = "; " + name + "=";
	const patternStartIndex = cookie.indexOf(searchPattern);
	if (patternStartIndex === -1) return null;

	const valueStartIndex = patternStartIndex + searchPattern.length;

	return decodeURIComponent(
		cookie.substring(
			valueStartIndex, cookie.indexOf(";", valueStartIndex)
		)
	);
};

const getCookiePing = async (name, pingSrc) => {
	let cookieData = await getCookie(name);
	if (!cookieData) {
		await pingURL(pingSrc);
		cookieData = await getCookie(name);
		if (!cookieData) throw new Error("Failed getting cookie '" + name + "' by fetching " + pingSrc);
	}
	return cookieData;
}


const _getToken = async () => {
	userScripts.loid ??= await getCookie("loid");
	userScripts.tokenKey ??= "rAPI:accessToken~"+userScripts.loid, userScripts.expiryKey ??= "rAPI:accessTokenExpiry~"+userScripts.loid; // Seperate tokens for different users

	let token = localStorage.getItem(userScripts.tokenKey);
	let expiry = Number(localStorage.getItem(userScripts.expiryKey));
	if (token && expiry > Date.now()) return token;

	try {
		const response = await gmFetch({
			anonymous: false,
			method: "POST",
			url: "https://www.reddit.com/svc/shreddit/token",
			headers: {
				"Content-Type": "application/json",
				"origin": "https://www.reddit.com" // 403 if this isn't included
			},
			data: JSON.stringify({ csrf_token: await getCookiePing("csrf_token", "https://sh.reddit.com/404") })
		});

		if (response.status === 200) {
			const data = JSON.parse(response.responseText);
			token = data.token;
			expiry = data.expires;
		} else {
			alert(`Error "${response.status}" while attempting to fetch authentication headers (redditNativeTranslations)`);
		}
	} catch (e) {
		console.debug("Authentication token fetch error:", e);
	}

	// Save to cache
	localStorage.setItem(userScripts.tokenKey, token);
	localStorage.setItem(userScripts.expiryKey, expiry.toString());
	console.log("Saved new auth token to cache.");
	return token;
};

const getToken = async () => {
	if (userScripts.pendingTokenPromise) return userScripts.pendingTokenPromise;
	return userScripts.pendingTokenPromise = _getToken().finally(() => {
		userScripts.pendingTokenPromise = null;
	});
};