feat: replace browser login with imported Reddit sessions

This commit is contained in:
jjbrunton committed 2026-08-08 16:48:59 +01:00
1 parent 2b0545afcf
commit c483c7b8be
76 files changed
+2048 -2828

No files matched your search

@@ -0,0 +1,15 @@
# RedditBot Session Exporter
This local Chrome extension exports `reddit.com` cookies and the current Reddit Chat Matrix credential into one JSON file for the self-hosted dashboard's **Import Reddit session** control. The file is equivalent to a login credential: do not share it, commit it, or send it to anyone.
## Install and use
1. In Chrome, open `chrome://extensions`, enable **Developer mode**, then choose **Load unpacked** and select this folder.
If it was already loaded, click its **Reload** button after updating these files so Chrome starts the new service worker.
2. Sign in to Reddit in the same Chrome profile.
3. Open [Reddit Chat](https://www.reddit.com/chat/) and wait for it to finish loading. The extension badge changes to `OK` when it captures the Matrix credential.
4. Click the **RedditBot Session Exporter** toolbar icon and save `redditbot-session.json`.
5. In RedditBot Dashboard > Settings > Access, choose **Import Reddit session** and select the exported file.
6. Delete the downloaded JSON file once the dashboard confirms that the session is authenticated.
The extension observes only requests to Reddit's Matrix homeservers, verifies the captured token with Matrix `whoami`, reads Reddit cookies, and saves the combined export locally. It does not send the dump to any server; you upload it directly to your RedditBot dashboard.
@@ -0,0 +1,15 @@
{
"manifest_version": 3,
"name": "RedditBot Session Exporter",
"version": "1.0.0",
"description": "Exports Reddit cookies and Reddit Chat credentials for a self-hosted RedditBot dashboard.",
"permissions": ["cookies", "downloads", "storage", "webRequest"],
"host_permissions": [
"https://reddit.com/*",
"https://*.reddit.com/*",
"https://matrix.redditspace.com/*",
"https://matrix.reddit.com/*"
],
"action": { "default_title": "Export RedditBot session" },
"background": { "service_worker": "service-worker.js", "type": "module" }
}
@@ -0,0 +1,58 @@
import { createSessionExportDownload } from './session-export.js';
const MATRIX_URLS = [
'https://matrix.redditspace.com/*',
'https://matrix.reddit.com/*',
];
chrome.webRequest.onBeforeSendHeaders.addListener(
async (details) => {
const authorization = details.requestHeaders?.find(
(header) => header.name.toLowerCase() === 'authorization',
)?.value;
if (!authorization?.toLowerCase().startsWith('bearer ')) return;
const url = new URL(details.url);
await chrome.storage.local.set({
matrixCapture: {
homeserver: `${url.protocol}//${url.host}`,
accessToken: authorization.slice('bearer '.length).trim(),
capturedAt: Date.now(),
},
});
await chrome.action.setBadgeText({ text: 'OK' });
await chrome.action.setBadgeBackgroundColor({ color: '#198754' });
},
{ urls: MATRIX_URLS },
['requestHeaders', 'extraHeaders'],
);
async function showError(message) {
await chrome.action.setBadgeText({ text: 'ERR' });
await chrome.action.setBadgeBackgroundColor({ color: '#dc3545' });
await chrome.action.setTitle({ title: `RedditBot export not ready: ${message}` });
}
chrome.action.onClicked.addListener(async () => {
try {
const cookies = await chrome.cookies.getAll({ domain: 'reddit.com' });
const { matrixCapture } = await chrome.storage.local.get('matrixCapture');
if (!matrixCapture) {
throw new Error('Open Reddit Chat, wait for it to load, then click the extension again.');
}
const whoami = await fetch(`${matrixCapture.homeserver}/_matrix/client/v3/account/whoami`, {
headers: { Authorization: `Bearer ${matrixCapture.accessToken}` },
});
if (!whoami.ok) throw new Error('The captured Reddit Chat token has expired. Reload Reddit Chat and try again.');
const identity = await whoami.json();
if (!identity?.user_id) throw new Error('Reddit Chat did not return an account identity.');
await chrome.downloads.download({
...createSessionExportDownload(cookies, { ...matrixCapture, userId: identity.user_id }),
saveAs: true,
});
} catch (error) {
await showError(error instanceof Error ? error.message : 'Could not create the session dump.');
}
});
@@ -0,0 +1,34 @@
const REDDITBOT_SESSION_FILENAME = 'redditbot-session.json';
function isRedditDomain(domain) {
const normalised = domain.toLowerCase().replace(/^\./, '');
return normalised === 'reddit.com' || normalised.endsWith('.reddit.com');
}
function toExportCookie(cookie) {
return {
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path,
expires: cookie.expirationDate,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: cookie.sameSite,
};
}
export function createSessionExportDownload(cookies, matrix) {
if (!matrix?.homeserver || !matrix?.accessToken || !matrix?.userId) {
throw new Error('Open Reddit Chat before exporting so Matrix credentials can be captured.');
}
const exportFile = {
cookies: cookies.filter((cookie) => isRedditDomain(cookie.domain)).map(toExportCookie),
matrix,
};
return {
filename: REDDITBOT_SESSION_FILENAME,
url: `data:application/json;charset=utf-8,${encodeURIComponent(JSON.stringify(exportFile))}`,
};
}