Handle authentication for Reddit, and provide some helper functions for cookies and tokens.
Ce script ne devrait pas être installé directement. C'est une librairie créée pour d'autres scripts. Elle doit être inclus avec la commande // @require https://update.greasyfork.org/scripts/575868/1812157/Reddit%20Auth%20%28%2Aredditcom%20only%29.js
// ==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;
});
};