code icon Code

Get Fireflies Transcript

Fetch full transcript with sentences, summary, and analytics

Source Code

import fs from "fs";
import path from "path";

const [outputPath, transcriptId] = process.argv.slice(2);

if (!outputPath || !transcriptId) {
  console.error("Error: outputPath and transcriptId are required");
  console.error("Usage: node script.js session/transcript.json <transcriptId>");
  process.exit(1);
}

console.log(`Fetching transcript ${transcriptId}...`);

try {
  // Build GraphQL query for full transcript
  const query = `
    query Transcript($id: String!) {
      transcript(id: $id) {
        id
        title
        date
        duration
        participants
        organizer_email
        transcript_url
        speakers {
          id
          name
        }
        sentences {
          index
          speaker_name
          speaker_id
          text
          raw_text
          start_time
          end_time
          ai_filters {
            task
            question
            sentiment
          }
        }
        summary {
          overview
          action_items
          keywords
          gist
          short_summary
          bullet_gist
          topics_discussed
          outline
        }
        analytics {
          sentiments {
            positive_pct
            neutral_pct
            negative_pct
          }
          categories {
            questions
            tasks
            metrics
            date_times
          }
          speakers {
            speaker_id
            name
            duration
            duration_pct
            word_count
            words_per_minute
            questions
            filler_words
            longest_monologue
            monologues_count
          }
        }
        meeting_attendees {
          displayName
          email
          name
        }
      }
    }
  `;

  const res = await fetch("https://api.fireflies.ai/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer PLACEHOLDER_TOKEN",
    },
    body: JSON.stringify({ query, variables: { id: transcriptId } }),
  });

  const data = await res.json();

  if (data.errors) {
    const errMsg = data.errors[0]?.message || "GraphQL error";
    throw new Error(`Fireflies API: ${errMsg}`);
  }

  const transcript = data.data?.transcript;
  if (!transcript) {
    throw new Error(`Transcript not found: ${transcriptId}`);
  }

  console.log(`  Title: ${transcript.title}`);

  // Process transcript for readable output
  const dateISO = transcript.date ? new Date(transcript.date).toISOString() : null;
  const dateFormatted = transcript.date
    ? new Date(transcript.date).toLocaleDateString("en-US", {
        weekday: "long",
        year: "numeric",
        month: "long",
        day: "numeric",
      })
    : null;

  // Format sentences with timestamps
  const sentences = (transcript.sentences || []).map((s) => ({
    index: s.index,
    speaker: s.speaker_name || `Speaker ${s.speaker_id}`,
    text: s.text,
    startTime: s.start_time,
    endTime: s.end_time,
    startFormatted: formatTime(s.start_time),
    isTask: s.ai_filters?.task || false,
    isQuestion: s.ai_filters?.question || false,
    sentiment: s.ai_filters?.sentiment || null,
  }));

  // Process speaker analytics
  const speakerStats = (transcript.analytics?.speakers || []).map((s) => ({
    name: s.name || `Speaker ${s.speaker_id}`,
    durationSeconds: s.duration || 0,
    durationPercent: s.duration_pct || 0,
    wordCount: s.word_count || 0,
    wordsPerMinute: s.words_per_minute || 0,
    questions: s.questions || 0,
    fillerWords: s.filler_words || 0,
    longestMonologue: s.longest_monologue || 0,
    monologueCount: s.monologues_count || 0,
  }));

  // Sort speakers by talk time (most to least)
  speakerStats.sort((a, b) => b.durationPercent - a.durationPercent);

  // Build output
  const output = {
    id: transcript.id,
    title: transcript.title || "(Untitled meeting)",
    date: dateISO,
    dateFormatted,
    durationMinutes: transcript.duration || null,
    transcriptUrl: transcript.transcript_url || null,
    participants: transcript.participants || [],
    organizer: transcript.organizer_email || null,
    attendees: (transcript.meeting_attendees || []).map((a) => ({
      name: a.displayName || a.name || a.email?.split("@")[0],
      email: a.email,
    })),
    speakers: (transcript.speakers || []).map((s) => s.name).filter(Boolean),

    // Summary data
    summary: {
      overview: transcript.summary?.overview || null,
      gist: transcript.summary?.gist || null,
      shortSummary: transcript.summary?.short_summary || null,
      bulletGist: transcript.summary?.bullet_gist || null,
      actionItems: transcript.summary?.action_items || null,
      keywords: transcript.summary?.keywords || null,
      topicsDiscussed: transcript.summary?.topics_discussed || [],
      outline: transcript.summary?.outline || null,
    },

    // Analytics (may be null on free tier)
    analytics: transcript.analytics
      ? {
          sentiment: {
            positive: transcript.analytics.sentiments?.positive_pct || 0,
            neutral: transcript.analytics.sentiments?.neutral_pct || 0,
            negative: transcript.analytics.sentiments?.negative_pct || 0,
          },
          categories: {
            questions: transcript.analytics.categories?.questions || 0,
            tasks: transcript.analytics.categories?.tasks || 0,
            metrics: transcript.analytics.categories?.metrics || 0,
            dateTimes: transcript.analytics.categories?.date_times || 0,
          },
          speakers: speakerStats,
        }
      : null,

    // Full transcript
    sentenceCount: sentences.length,
    sentences,
  };

  // Write output
  const dir = path.dirname(outputPath);
  if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  console.log(`\nāœ“ Retrieved transcript`);
  console.log(`  Date: ${dateFormatted}`);
  console.log(`  Duration: ${transcript.duration || "?"} minutes`);
  console.log(`  Sentences: ${sentences.length}`);
  console.log(`  Speakers: ${output.speakers.length}`);
  console.log(`  Output: ${outputPath}`);

  // Show summary preview
  if (output.summary.gist) {
    console.log(`\n  Gist: ${output.summary.gist}`);
  }

  // Show speaker stats if available
  if (speakerStats.length > 0) {
    console.log(`\n  Speaker breakdown:`);
    speakerStats.slice(0, 4).forEach((s) => {
      console.log(`    ${s.name}: ${s.durationPercent.toFixed(0)}% (${s.wordCount} words)`);
    });
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      title: output.title,
      sentenceCount: sentences.length,
      hasAnalytics: !!output.analytics,
    })
  );
} catch (error) {
  console.error("Failed:", error.message);
  throw error;
}

// Helper to format seconds as MM:SS
function formatTime(seconds) {
  if (!seconds && seconds !== 0) return null;
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins}:${secs.toString().padStart(2, "0")}`;
}