|
1 | | -import type { NewActivity } from "@plotday/twister"; |
2 | | -import { ActivityLinkType, ActivityType } from "@plotday/twister"; |
| 1 | +import { ActivityType } from "@plotday/twister"; |
| 2 | +import type { |
| 3 | + ActivityWithNotes, |
| 4 | + Actor, |
| 5 | + ActorId, |
| 6 | + ActorType, |
| 7 | + Note, |
| 8 | +} from "@plotday/twister"; |
3 | 9 |
|
4 | 10 | export type GmailLabel = { |
5 | 11 | id: string; |
@@ -234,6 +240,47 @@ export function parseEmailAddress(headerValue: string): EmailAddress { |
234 | 240 | }; |
235 | 241 | } |
236 | 242 |
|
| 243 | +/** |
| 244 | + * Converts an EmailAddress to an Actor. |
| 245 | + */ |
| 246 | +function emailAddressToActor(emailAddress: EmailAddress): Actor { |
| 247 | + return { |
| 248 | + id: `contact:${emailAddress.email}` as ActorId, |
| 249 | + type: 2 as ActorType, // ActorType.Contact |
| 250 | + email: emailAddress.email, |
| 251 | + name: emailAddress.name, |
| 252 | + }; |
| 253 | +} |
| 254 | + |
| 255 | +/** |
| 256 | + * Parses multiple email addresses from a header value (comma-separated). |
| 257 | + */ |
| 258 | +function parseEmailAddresses(headerValue: string | null): Actor[] { |
| 259 | + if (!headerValue) return []; |
| 260 | + |
| 261 | + return headerValue |
| 262 | + .split(",") |
| 263 | + .map((addr) => addr.trim()) |
| 264 | + .filter((addr) => addr.length > 0) |
| 265 | + .map((addr) => emailAddressToActor(parseEmailAddress(addr))); |
| 266 | +} |
| 267 | + |
| 268 | +/** |
| 269 | + * Parses email addresses and returns just the ActorIds for mentions. |
| 270 | + */ |
| 271 | +function parseEmailAddressIds(headerValue: string | null): ActorId[] { |
| 272 | + if (!headerValue) return []; |
| 273 | + |
| 274 | + return headerValue |
| 275 | + .split(",") |
| 276 | + .map((addr) => addr.trim()) |
| 277 | + .filter((addr) => addr.length > 0) |
| 278 | + .map((addr) => { |
| 279 | + const parsed = parseEmailAddress(addr); |
| 280 | + return `contact:${parsed.email}` as ActorId; |
| 281 | + }); |
| 282 | +} |
| 283 | + |
237 | 284 | /** |
238 | 285 | * Gets a specific header value from a message |
239 | 286 | */ |
@@ -320,114 +367,104 @@ function extractAttachments( |
320 | 367 | } |
321 | 368 |
|
322 | 369 | /** |
323 | | - * Transforms a Gmail thread into an array of Activities |
324 | | - * The first message is the parent, subsequent messages are replies |
| 370 | + * Transforms a Gmail thread into an ActivityWithNotes structure. |
| 371 | + * The subject becomes the Activity title, and each email becomes a Note. |
325 | 372 | */ |
326 | | -export function transformGmailThread(thread: GmailThread): NewActivity[] { |
327 | | - if (!thread.messages || thread.messages.length === 0) return []; |
| 373 | +export function transformGmailThread(thread: GmailThread): ActivityWithNotes { |
| 374 | + if (!thread.messages || thread.messages.length === 0) { |
| 375 | + // Return empty structure for invalid threads |
| 376 | + return { |
| 377 | + id: `gmail:${thread.id}` as any, |
| 378 | + type: ActivityType.Note, |
| 379 | + author: { id: "system" as ActorId, type: 1 as ActorType, name: null }, |
| 380 | + title: null, |
| 381 | + assignee: null, |
| 382 | + doneAt: null, |
| 383 | + start: null, |
| 384 | + end: null, |
| 385 | + recurrenceUntil: null, |
| 386 | + recurrenceCount: null, |
| 387 | + priority: null as any, |
| 388 | + recurrenceRule: null, |
| 389 | + recurrenceExdates: null, |
| 390 | + recurrenceDates: null, |
| 391 | + recurrence: null, |
| 392 | + occurrence: null, |
| 393 | + meta: null, |
| 394 | + mentions: null, |
| 395 | + tags: null, |
| 396 | + draft: false, |
| 397 | + private: false, |
| 398 | + notes: [], |
| 399 | + }; |
| 400 | + } |
328 | 401 |
|
329 | | - const activities: NewActivity[] = []; |
330 | 402 | const parentMessage = thread.messages[0]; |
331 | | - |
332 | | - // Extract key headers |
333 | | - const from = getHeader(parentMessage, "From"); |
334 | 403 | const subject = getHeader(parentMessage, "Subject"); |
335 | | - const to = getHeader(parentMessage, "To"); |
336 | | - const cc = getHeader(parentMessage, "Cc"); |
337 | 404 |
|
338 | | - // Parse sender |
339 | | - const sender = from ? parseEmailAddress(from) : null; |
340 | | - |
341 | | - // Extract body |
342 | | - const body = extractBody(parentMessage.payload); |
343 | | - |
344 | | - // Create parent activity |
345 | | - const parentActivity: NewActivity = { |
346 | | - type: ActivityType.Action, |
347 | | - title: subject || parentMessage.snippet || "Email", |
348 | | - note: body || parentMessage.snippet, |
349 | | - noteType: "text", |
| 405 | + // Create Activity |
| 406 | + const activity: ActivityWithNotes = { |
| 407 | + id: `gmail:${thread.id}` as any, |
| 408 | + type: ActivityType.Note, |
| 409 | + author: { id: "system" as ActorId, type: 1 as ActorType, name: null }, |
| 410 | + title: subject || "Email", |
| 411 | + assignee: null, |
| 412 | + doneAt: null, |
350 | 413 | start: new Date(parseInt(parentMessage.internalDate)), |
| 414 | + end: null, |
| 415 | + recurrenceUntil: null, |
| 416 | + recurrenceCount: null, |
| 417 | + priority: null as any, |
| 418 | + recurrenceRule: null, |
| 419 | + recurrenceExdates: null, |
| 420 | + recurrenceDates: null, |
| 421 | + recurrence: null, |
| 422 | + occurrence: null, |
351 | 423 | meta: { |
352 | | - source: `gmail:${thread.id}:${parentMessage.id}`, |
| 424 | + source: `gmail:${thread.id}`, |
353 | 425 | threadId: thread.id, |
354 | | - messageId: parentMessage.id, |
355 | | - from: sender, |
356 | | - to, |
357 | | - cc, |
358 | | - labels: parentMessage.labelIds, |
| 426 | + historyId: thread.historyId, |
359 | 427 | }, |
| 428 | + mentions: null, |
| 429 | + tags: null, |
| 430 | + draft: false, |
| 431 | + private: false, |
| 432 | + notes: [], |
360 | 433 | }; |
361 | 434 |
|
362 | | - // Initialize links array |
363 | | - parentActivity.links = []; |
364 | | - |
365 | | - // Add Gmail URL as action link |
366 | | - parentActivity.links.push({ |
367 | | - type: ActivityLinkType.external, |
368 | | - title: "Open in Gmail", |
369 | | - url: `https://mail.google.com/mail/u/0/#inbox/${thread.id}`, |
370 | | - }); |
371 | | - |
372 | | - // Add attachments as links |
373 | | - const attachments = extractAttachments(parentMessage); |
374 | | - attachments.forEach((att) => { |
375 | | - parentActivity.links!.push({ |
376 | | - type: ActivityLinkType.external, |
377 | | - title: `Attachment: ${att.filename}`, |
378 | | - url: att.url, |
379 | | - }); |
380 | | - }); |
381 | | - |
382 | | - activities.push(parentActivity); |
383 | | - |
384 | | - // Create activities for replies (messages after the first) |
385 | | - for (let i = 1; i < thread.messages.length; i++) { |
386 | | - const message = thread.messages[i]; |
387 | | - const replyFrom = getHeader(message, "From"); |
388 | | - const replySender = replyFrom ? parseEmailAddress(replyFrom) : null; |
389 | | - const replyBody = extractBody(message.payload); |
390 | | - |
391 | | - const replyActivity: NewActivity = { |
392 | | - type: ActivityType.Action, |
393 | | - title: `Re: ${subject || "Email"}`, |
394 | | - note: replyBody || message.snippet, |
395 | | - noteType: "text", |
396 | | - start: new Date(parseInt(message.internalDate)), |
397 | | - parent: { id: `gmail:${thread.id}:${parentMessage.id}` }, |
398 | | - meta: { |
399 | | - source: `gmail:${thread.id}:${message.id}`, |
400 | | - threadId: thread.id, |
401 | | - messageId: message.id, |
402 | | - from: replySender, |
403 | | - labels: message.labelIds, |
404 | | - }, |
| 435 | + // Create Notes for all messages (including first) |
| 436 | + for (const message of thread.messages) { |
| 437 | + const from = getHeader(message, "From"); |
| 438 | + const to = getHeader(message, "To"); |
| 439 | + const cc = getHeader(message, "Cc"); |
| 440 | + |
| 441 | + const sender = from ? parseEmailAddress(from) : null; |
| 442 | + if (!sender) continue; // Skip messages without sender |
| 443 | + |
| 444 | + const body = extractBody(message.payload); |
| 445 | + |
| 446 | + // Combine to and cc for mentions |
| 447 | + const mentions: ActorId[] = [ |
| 448 | + ...parseEmailAddressIds(to), |
| 449 | + ...parseEmailAddressIds(cc), |
| 450 | + ]; |
| 451 | + |
| 452 | + const note: Note = { |
| 453 | + id: `gmail:${thread.id}:${message.id}` as any, |
| 454 | + activity: activity, |
| 455 | + author: emailAddressToActor(sender), |
| 456 | + note: body || message.snippet, |
| 457 | + links: null, |
| 458 | + mentions: mentions.length > 0 ? mentions : null, |
| 459 | + tags: null, |
| 460 | + draft: false, |
| 461 | + private: false, |
405 | 462 | }; |
406 | 463 |
|
407 | | - // Initialize links array |
408 | | - replyActivity.links = []; |
409 | | - |
410 | | - // Add Gmail URL as action link |
411 | | - replyActivity.links.push({ |
412 | | - type: ActivityLinkType.external, |
413 | | - title: "Open in Gmail", |
414 | | - url: `https://mail.google.com/mail/u/0/#inbox/${thread.id}`, |
415 | | - }); |
416 | | - |
417 | | - // Add attachments as links |
418 | | - const replyAttachments = extractAttachments(message); |
419 | | - replyAttachments.forEach((att) => { |
420 | | - replyActivity.links!.push({ |
421 | | - type: ActivityLinkType.external, |
422 | | - title: `Attachment: ${att.filename}`, |
423 | | - url: att.url, |
424 | | - }); |
425 | | - }); |
426 | | - |
427 | | - activities.push(replyActivity); |
| 464 | + activity.notes.push(note); |
428 | 465 | } |
429 | 466 |
|
430 | | - return activities; |
| 467 | + return activity; |
431 | 468 | } |
432 | 469 |
|
433 | 470 | /** |
|
0 commit comments