Skip to content

Commit 7582916

Browse files
committed
gmail <-> synopsys in collectathon
- uses desktop oauth flow for prototyping - imports with entity cid from gmail messageId
1 parent 1d0685f commit 7582916

File tree

3 files changed

+146
-2
lines changed

3 files changed

+146
-2
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
script for gmail to synopsys
2+
3+
# Setup Credentials
4+
5+
You'll need to get an [oauth crednetials.json](https://developers.google.com/gmail/api/quickstart/nodejs#authorize_credentials_for_a_desktop_application) file from google cloud console and put it in this directory.
6+
7+
1. visit [google cloud console](https://console.cloud.google.com/apis/credentials)
8+
2. create a new project (or select an existing one)
9+
3. enable the gmail api (you may need to search for it)
10+
4. create credentials and select desktop app
11+
- Create Credentials > OAuth client ID
12+
- Application type > Desktop app
13+
- After the credentials are created, click `Download JSON` and put it in this directory as `credentials.json`
14+
15+
# Running
16+
17+
The first time you run the script it will open a browser window prompting you to login and authorize access. Afterwards credentials will be stored in a `token.json` file and you won't have to do this again (hopefully)
18+
19+
```
20+
deno run --allow-net --allow-read --allow-write --allow-run gmail.ts [number of emails to import]
21+
```
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import fs from 'node:fs/promises';
2+
import path from 'node:path';
3+
import process from 'node:process';
4+
import { authenticate } from 'npm:@google-cloud/local-auth@3.0.1';
5+
import { OAuth2Client } from "npm:google-auth-library@9.14.2";
6+
import { google } from "npm:googleapis@144.0.0";
7+
import { clipEmail, cid } from "./synopsys.ts";
8+
9+
type GmailEntity = {
10+
messageId: string;
11+
subject: string;
12+
from: string;
13+
date: string;
14+
snippet: string;
15+
}
16+
17+
// If modifying these scopes, delete token.json.
18+
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
19+
20+
// The file token.json stores the user's access and refresh tokens, and is
21+
// created automatically when the authorization flow completes the first time
22+
const TOKEN_PATH = path.join(process.cwd(), 'token.json');
23+
const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json');
24+
25+
async function loadSavedCredentialsIfExist(): Promise<OAuth2Client | null> {
26+
try {
27+
const content = await fs.readFile(TOKEN_PATH);
28+
const credentials = JSON.parse(content.toString());
29+
return google.auth.fromJSON(credentials);
30+
} catch (err) {
31+
return null;
32+
}
33+
}
34+
35+
async function saveCredentials(client: OAuth2Client) {
36+
const content = await fs.readFile(CREDENTIALS_PATH);
37+
const keys = JSON.parse(content.toString());
38+
const key = keys.installed || keys.web;
39+
const payload = JSON.stringify({
40+
type: 'authorized_user',
41+
client_id: key.client_id,
42+
client_secret: key.client_secret,
43+
refresh_token: client.credentials.refresh_token,
44+
});
45+
await fs.writeFile(TOKEN_PATH, payload);
46+
}
47+
48+
async function authorize(): Promise<OAuth2Client> {
49+
let client: any = await loadSavedCredentialsIfExist();
50+
if (client) {
51+
return client;
52+
}
53+
client = await authenticate({
54+
scopes: SCOPES,
55+
keyfilePath: CREDENTIALS_PATH,
56+
});
57+
if (client.credentials) {
58+
await saveCredentials(client);
59+
}
60+
return client;
61+
}
62+
63+
async function listMessages(auth: OAuth2Client, maxResults: number = 10) {
64+
const gmail = google.gmail({ version: 'v1', auth });
65+
const res = await gmail.users.messages.list({
66+
userId: 'me',
67+
maxResults: maxResults
68+
});
69+
70+
return res.data.messages || [];
71+
}
72+
73+
async function getMessage(auth: OAuth2Client, messageId: string) {
74+
const gmail = google.gmail({ version: 'v1', auth });
75+
const res = await gmail.users.messages.get({
76+
userId: 'me',
77+
id: messageId
78+
});
79+
80+
return res.data;
81+
}
82+
83+
async function getMessageDetails(auth: OAuth2Client, messageId: string): Promise<GmailEntity> {
84+
const gmail = google.gmail({ version: 'v1', auth });
85+
const res = await gmail.users.messages.get({
86+
userId: 'me',
87+
id: messageId,
88+
format: 'metadata',
89+
metadataHeaders: ['Subject', 'From', 'Date'],
90+
});
91+
92+
const headers = res.data.payload?.headers;
93+
const subject = headers?.find(h => h.name === 'Subject')?.value || 'No subject';
94+
const from = headers?.find(h => h.name === 'From')?.value || 'Unknown sender';
95+
const date = headers?.find(h => h.name === 'Date')?.value || 'Unknown date';
96+
97+
return {
98+
messageId,
99+
subject,
100+
from,
101+
date,
102+
snippet: res.data.snippet || 'No snippet available',
103+
};
104+
}
105+
106+
async function importGmail(message: GmailEntity) {
107+
const entityCid = await cid({ messageId: message.messageId, source: "gmail" });
108+
clipEmail(message.from, ["gmail"], message, entityCid);
109+
}
110+
111+
if (import.meta.main) {
112+
const maxResults = parseInt(process.argv[2]) || 10;
113+
const auth = await authorize();
114+
const messages = await listMessages(auth, maxResults);
115+
for (const message of messages) {
116+
const messageDetails = await getMessageDetails(auth, message.id);
117+
importGmail(messageDetails);
118+
}
119+
}

typescript/packages/collectathon/synopsys.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ export async function clipEmail(
1515
sender: string,
1616
collections: string[],
1717
entity: any,
18+
entityCid?: string,
1819
) {
1920
entity["import/sender"] = sender;
2021
entity["import/source"] = "Email";
2122
entity["import/tool"] = "ingest";
2223
entity["import/time"] = new Date().toISOString();
2324

24-
const entityCid = await cid(entity);
25-
const entityFacts = await jsonToFacts(entity);
25+
if (!entityCid) {
26+
entityCid = await cid(entity);
27+
}
28+
29+
const entityFacts = await jsonToFacts(entity, { "/": entityCid } as Entity);
2630

2731
const collectionsFacts = await Promise.all(
2832
collections.map(async (collectionName) => {

0 commit comments

Comments
 (0)