Skip to content

Commit 7643f8a

Browse files
committed
grain init
1 parent 9cbf1c2 commit 7643f8a

File tree

12 files changed

+856
-6
lines changed

12 files changed

+856
-6
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import grain from "../../grain.app.mjs";
2+
import { axios } from "@pipedream/platform";
3+
4+
export default {
5+
key: "grain-get-recording",
6+
name: "Get Recording",
7+
description: "Fetches a specific recording by its ID from Grain, optionally including the transcript and intelligence notes. [See the documentation](https://grainhq.notion.site/grain-public-api-877184aa82b54c77a875083c1b560de9)",
8+
version: "0.0.1",
9+
type: "action",
10+
props: {
11+
grain,
12+
recordId: {
13+
propDefinition: [
14+
grain,
15+
"recordId",
16+
],
17+
async options({ page }) {
18+
const recordings = await this.grain.listRecordings({
19+
page,
20+
});
21+
return recordings.map((recording) => ({
22+
label: recording.title,
23+
value: recording.id,
24+
}));
25+
},
26+
},
27+
transcriptFormat: {
28+
propDefinition: [
29+
grain,
30+
"transcriptFormat",
31+
],
32+
optional: true,
33+
},
34+
intelligenceNotesFormat: {
35+
propDefinition: [
36+
grain,
37+
"intelligenceNotesFormat",
38+
],
39+
optional: true,
40+
},
41+
allowedIntelligenceNotes: {
42+
propDefinition: [
43+
grain,
44+
"allowedIntelligenceNotes",
45+
],
46+
optional: true,
47+
},
48+
},
49+
async run({ $ }) {
50+
const response = await this.grain.fetchRecording({
51+
recordId: this.recordId,
52+
transcriptFormat: this.transcriptFormat,
53+
intelligenceNotesFormat: this.intelligenceNotesFormat,
54+
allowedIntelligenceNotes: this.allowedIntelligenceNotes,
55+
});
56+
57+
$.export("$summary", `Successfully fetched recording with ID ${this.recordId}`);
58+
return response;
59+
},
60+
};

components/grain/grain.app.mjs

Lines changed: 127 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,133 @@
1+
import { axios } from "@pipedream/platform";
2+
13
export default {
24
type: "app",
35
app: "grain",
4-
propDefinitions: {},
6+
propDefinitions: {
7+
recordId: {
8+
type: "string",
9+
label: "Record ID",
10+
description: "The ID of the recording to fetch",
11+
async options() {
12+
const recordings = await this.listRecordings();
13+
return recordings.map((recording) => ({
14+
value: recording.id,
15+
label: recording.title,
16+
}));
17+
},
18+
},
19+
transcriptFormat: {
20+
type: "string",
21+
label: "Transcript Format",
22+
description: "Format for the transcript",
23+
options: [
24+
{
25+
label: "JSON",
26+
value: "json",
27+
},
28+
{
29+
label: "VTT",
30+
value: "vtt",
31+
},
32+
],
33+
optional: true,
34+
},
35+
intelligenceNotesFormat: {
36+
type: "string",
37+
label: "Intelligence Notes Format",
38+
description: "Format for the intelligence notes",
39+
options: [
40+
{
41+
label: "JSON",
42+
value: "json",
43+
},
44+
{
45+
label: "Markdown",
46+
value: "md",
47+
},
48+
{
49+
label: "Text",
50+
value: "text",
51+
},
52+
],
53+
optional: true,
54+
},
55+
allowedIntelligenceNotes: {
56+
type: "string[]",
57+
label: "Allowed Intelligence Notes",
58+
description: "Whitelist of intelligence notes section titles",
59+
optional: true,
60+
},
61+
},
562
methods: {
6-
// this.$auth contains connected account data
7-
authKeys() {
8-
console.log(Object.keys(this.$auth));
63+
_baseUrl() {
64+
return "https://grain.com";
65+
},
66+
async _makeRequest(opts = {}) {
67+
const {
68+
$ = this, method = "GET", path = "/", headers, ...otherOpts
69+
} = opts;
70+
return axios($, {
71+
...otherOpts,
72+
method,
73+
url: this._baseUrl() + path,
74+
headers: {
75+
...headers,
76+
Authorization: `Bearer ${this.$auth.oauth_access_token}`,
77+
},
78+
});
79+
},
80+
async listRecordings(opts = {}) {
81+
return this._makeRequest({
82+
path: "/_/public-api/recordings",
83+
...opts,
84+
});
85+
},
86+
async fetchRecording({
87+
recordId, transcriptFormat, intelligenceNotesFormat, allowedIntelligenceNotes, ...opts
88+
}) {
89+
return this._makeRequest({
90+
path: `/_/public-api/recordings/${recordId}`,
91+
params: {
92+
transcript_format: transcriptFormat,
93+
intelligence_notes_format: intelligenceNotesFormat,
94+
allowed_intelligence_notes: allowedIntelligenceNotes,
95+
},
96+
...opts,
97+
});
98+
},
99+
async emitNewEvent(eventType, entityType) {
100+
// Logic to emit event - placeholder implementation
101+
console.log(`Emit ${eventType} event for ${entityType}`);
102+
},
103+
},
104+
hooks: {
105+
async addedHighlight() {
106+
await this.emitNewEvent("added", "highlight");
107+
},
108+
async addedStory() {
109+
await this.emitNewEvent("added", "story");
110+
},
111+
async addedRecording() {
112+
await this.emitNewEvent("added", "recording");
113+
},
114+
async updatedHighlight() {
115+
await this.emitNewEvent("updated", "highlight");
116+
},
117+
async updatedStory() {
118+
await this.emitNewEvent("updated", "story");
119+
},
120+
async updatedRecording() {
121+
await this.emitNewEvent("updated", "recording");
122+
},
123+
async removedHighlight() {
124+
await this.emitNewEvent("removed", "highlight");
125+
},
126+
async removedStory() {
127+
await this.emitNewEvent("removed", "story");
128+
},
129+
async removedRecording() {
130+
await this.emitNewEvent("removed", "recording");
9131
},
10132
},
11-
};
133+
};

components/grain/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
"publishConfig": {
1313
"access": "public"
1414
}
15-
}
15+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import grain from "../../grain.app.mjs";
2+
import { axios } from "@pipedream/platform";
3+
import crypto from "crypto";
4+
5+
export default {
6+
key: "grain-new-highlight-instant",
7+
name: "New Highlight Added",
8+
description: "Emit new event when a highlight that matches the filter is added. [See the documentation](https://grainhq.notion.site/Grain-Public-API-877184aa82b54c77a875083c1b560de9)",
9+
version: "0.0.{{ts}}",
10+
type: "source",
11+
dedupe: "unique",
12+
props: {
13+
grain,
14+
http: {
15+
type: "$.interface.http",
16+
customResponse: true,
17+
},
18+
db: "$.service.db",
19+
},
20+
hooks: {
21+
async deploy() {
22+
const options = {
23+
path: "/_/public-api/recordings",
24+
params: {
25+
include_highlights: true,
26+
},
27+
};
28+
const recordings = await this.grain._makeRequest(options);
29+
const highlights = recordings.flatMap((recording) => recording.highlights || []);
30+
highlights.slice(0, 50).forEach((highlight) => {
31+
this.$emit(highlight, {
32+
id: highlight.id,
33+
summary: `New highlight: ${highlight.text}`,
34+
ts: Date.parse(highlight.created_datetime),
35+
});
36+
});
37+
},
38+
async activate() {
39+
const response = await this.grain._makeRequest({
40+
method: "POST",
41+
path: "/_/public-api/hooks",
42+
data: {
43+
hook_url: this.http.endpoint,
44+
events: [
45+
"highlight_added",
46+
],
47+
},
48+
headers: {
49+
Authorization: `Bearer ${this.grain.$auth.oauth_access_token}`,
50+
},
51+
});
52+
this.db.set("webhookId", response.id);
53+
},
54+
async deactivate() {
55+
const webhookId = this.db.get("webhookId");
56+
if (webhookId) {
57+
await this.grain._makeRequest({
58+
method: "DELETE",
59+
path: `/_/public-api/hooks/${webhookId}`,
60+
headers: {
61+
Authorization: `Bearer ${this.grain.$auth.oauth_access_token}`,
62+
},
63+
});
64+
}
65+
},
66+
},
67+
async run(event) {
68+
const webhookSignature = this.http.headers["x-grain-signature"];
69+
const rawBody = JSON.stringify(event.body);
70+
const secretKey = this.grain.$auth.oauth_access_token; // Assuming token is used as secret
71+
const computedSignature = crypto.createHmac("sha256", secretKey).update(rawBody)
72+
.digest("base64");
73+
74+
if (computedSignature !== webhookSignature) {
75+
this.http.respond({
76+
status: 401,
77+
body: "Unauthorized",
78+
});
79+
return;
80+
}
81+
82+
const {
83+
type, data,
84+
} = event.body;
85+
if (type === "highlight_added") {
86+
this.$emit(data, {
87+
id: data.id,
88+
summary: `New highlight added: ${data.text}`,
89+
ts: Date.parse(data.created_datetime),
90+
});
91+
}
92+
},
93+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import grain from "../../grain.app.mjs";
2+
import { axios } from "@pipedream/platform";
3+
4+
export default {
5+
key: "grain-new-recording-instant",
6+
name: "New Recording Instant",
7+
description: "Emit a new event when a recording that matches the filter is added. [See the documentation](https://grainhq.notion.site/grain-public-api-877184aa82b54c77a875083c1b560de9)",
8+
version: "0.0.{{ts}}",
9+
type: "source",
10+
dedupe: "unique",
11+
props: {
12+
grain,
13+
http: {
14+
type: "$.interface.http",
15+
customResponse: false,
16+
},
17+
db: "$.service.db",
18+
},
19+
methods: {
20+
_getWebhookId() {
21+
return this.db.get("webhookId");
22+
},
23+
_setWebhookId(id) {
24+
this.db.set("webhookId", id);
25+
},
26+
},
27+
hooks: {
28+
async deploy() {
29+
const recordings = await this.grain.listRecordings({
30+
paginate: false,
31+
});
32+
const recentRecordings = recordings.slice(-50);
33+
for (const recording of recentRecordings) {
34+
this.$emit(recording, {
35+
id: recording.id,
36+
summary: `New recording: ${recording.title}`,
37+
ts: Date.parse(recording.start_datetime),
38+
});
39+
}
40+
},
41+
async activate() {
42+
const hookId = await this.grain.createWebhook({
43+
event: "recording_added",
44+
url: this.http.endpoint,
45+
});
46+
this._setWebhookId(hookId);
47+
},
48+
async deactivate() {
49+
const id = this._getWebhookId();
50+
await this.grain.deleteWebhook(id);
51+
},
52+
},
53+
async run(event) {
54+
if (event.type === "recording_added") {
55+
const recording = event.data;
56+
this.$emit(recording, {
57+
id: recording.id,
58+
summary: `New recording: ${recording.title}`,
59+
ts: Date.parse(recording.start_datetime),
60+
});
61+
}
62+
},
63+
};

0 commit comments

Comments
 (0)