Reddit Auth (*.reddit.com only)

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

لا ينبغي أن لا يتم تثبيت هذا السكريت مباشرة. هو مكتبة لسكبتات لتشمل مع التوجيه الفوقية // @require https://update.greasyfork.org/scripts/575868/1812157/Reddit%20Auth%20%28%2Aredditcom%20only%29.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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