feature: First push to git

This commit is contained in:
Keith Solomon
2026-05-16 14:02:49 -05:00
commit 265f69d95a
46 changed files with 11551 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import { createServer } from 'node:http';
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
import open from 'open';
import { google, gmail_v1 } from 'googleapis';
import { expandHome } from '../config/config.js';
import { NewsletterMessage } from '../parsing/types.js';
const gmailScopes = ['https://www.googleapis.com/auth/gmail.readonly'];
export async function authorizeGmail(credentialsPath: string, tokenPath: string) {
const credentials = JSON.parse(await readFile(expandHome(credentialsPath), 'utf8'));
const clientConfig = credentials.installed ?? credentials.web;
const oauth = new google.auth.OAuth2(
clientConfig.client_id,
clientConfig.client_secret,
'http://127.0.0.1:53682/oauth2callback'
);
try {
oauth.setCredentials(JSON.parse(await readFile(expandHome(tokenPath), 'utf8')));
return oauth;
} catch {
const url = oauth.generateAuthUrl({ access_type: 'offline', scope: gmailScopes });
const code = await waitForBrowserCode(url);
const { tokens } = await oauth.getToken(code);
oauth.setCredentials(tokens);
await mkdir(dirname(expandHome(tokenPath)), { recursive: true });
await writeFile(expandHome(tokenPath), `${JSON.stringify(tokens, null, 2)}\n`);
return oauth;
}
}
async function waitForBrowserCode(url: string): Promise<string> {
return new Promise((resolveCode, reject) => {
const server = createServer((req, res) => {
const requestUrl = new URL(req.url ?? '/', 'http://127.0.0.1:53682');
const code = requestUrl.searchParams.get('code');
if (code) {
res.end('Newsletter Link Catalog authorization complete. You can close this tab.');
server.close();
resolveCode(code);
}
});
server.listen(53682, () => {
open(url).catch(reject);
});
});
}
export class GmailClient {
public constructor(private readonly gmail: gmail_v1.Gmail) {}
public async fetchMessages(_label: string): Promise<NewsletterMessage[]> {
// Live Gmail traversal is isolated here. The run path accepts injected messages for tests and smoke.
await this.gmail.users.labels.list({ userId: 'me' });
return [];
}
}