Reddit Auth (*.reddit.com only)

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

Ten skrypt nie powinien być instalowany bezpośrednio. Jest to biblioteka dla innych skyptów do włączenia dyrektywą meta // @require https://update.greasyfork.org/scripts/575868/1812157/Reddit%20Auth%20%28%2Aredditcom%20only%29.js

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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;
	});
};