Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions packages/v1-ready/fathom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Fathom API Module

This module provides integration with the [Fathom Video API](https://docs.fathom.ai/api-reference) for the Frigg Framework.

## Features

- API Key authentication
- List meetings with comprehensive filtering options
- List teams and team members
- Pagination support with cursor-based iteration
- Full TypeScript support

## Installation

```bash
npm install @friggframework/api-module-fathom
```

## Quick Start

```javascript
const { Api } = require('@friggframework/api-module-fathom');

// Initialize the API with your API key
const api = new Api({
apiKey: 'your-fathom-api-key'
});

// List all meetings
const meetings = await api.listMeetings();
console.log(meetings.data);

// List meetings with filters
const filteredMeetings = await api.listMeetings({
recorded_by: ['user@example.com'],
meeting_type: 'internal',
include_transcript: true,
created_after: '2024-01-01T00:00:00Z'
});

// Iterate through all meetings (handles pagination automatically)
for await (const meeting of api.iterateMeetings()) {
console.log(meeting.title);
}

// List teams
const teams = await api.listTeams();

// List team members
const teamMembers = await api.listTeamMembers();
```

## API Methods

### `listMeetings(params)`

List meetings with optional filtering parameters.

**Parameters:**
- `recorded_by` (array): Filter by meeting owner emails
- `teams` (array): Filter by team names
- `calendar_invitees` (array): Filter by attendee emails
- `created_after` (string): ISO timestamp to filter meetings created after
- `meeting_type` (string): 'all', 'internal', or 'external' (default: 'all')
- `include_transcript` (boolean): Include transcript data (default: false)
- `cursor` (string): Pagination cursor

**Returns:** Object with `data` array and `next_cursor` for pagination

### `listTeams()`

List all teams associated with the API key.

**Returns:** Object with `data` array of team objects

### `listTeamMembers()`

List all team members.

**Returns:** Object with `data` array of team member objects

### `iterateMeetings(params)`

Async generator that automatically handles pagination for iterating through all meetings.

**Parameters:** Same as `listMeetings()`

**Yields:** Individual meeting objects

## Authentication

Fathom uses API key authentication. You can obtain your API key from the Fathom settings under API Access.

### Setting up authentication:

```javascript
// Using environment variable
const api = new Api({
apiKey: process.env.FATHOM_API_KEY
});

// Direct initialization
const api = new Api({
apiKey: 'your-api-key-here'
});
```

## Environment Variables

- `FATHOM_API_KEY`: Your Fathom API key

## Testing

```bash
# Run tests
npm test

# Run tests with coverage
npm run coverage

# Run tests in watch mode
npm run test:watch
```

## Error Handling

The module throws errors for:
- 400 Bad Request - Invalid parameters
- 401 Unauthorized - Invalid API key
- Network errors

Example error handling:

```javascript
try {
const meetings = await api.listMeetings();
} catch (error) {
if (error.message.includes('401')) {
console.error('Invalid API key');
} else {
console.error('API error:', error.message);
}
}
```

## Module Definition

This module includes a complete Frigg Definition for use in integrations:

```javascript
const { Definition } = require('@friggframework/api-module-fathom');

// Use in your integration
const fathomDefinition = Definition;
```

## Links

- [Fathom Website](https://fathom.video)
- [API Documentation](https://docs.fathom.ai/api-reference)
- [Frigg Framework](https://github.com/friggframework)
87 changes: 87 additions & 0 deletions packages/v1-ready/fathom/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const { ApiKeyRequester, get } = require('@friggframework/core');

class Api extends ApiKeyRequester {
constructor(params) {
super(params);
this.baseUrl = 'https://api.fathom.ai/external/v1';

this.URLs = {
meetings: '/meetings',
teams: '/teams',
teamMembers: '/team-members',
};

this.apiKey = get(params, 'apiKey', null);
this.access_token = this.apiKey;
}

async _request(url, options = {}, i = 0) {
options.headers = options.headers || {};
options.headers['X-Api-Key'] = this.apiKey;
options.headers['Content-Type'] = 'application/json';

return super._request(url, options, i);
}

async listMeetings(params = {}) {
const queryParams = new URLSearchParams();

if (params.recorded_by && Array.isArray(params.recorded_by)) {
params.recorded_by.forEach(email => queryParams.append('recorded_by[]', email));
}

if (params.teams && Array.isArray(params.teams)) {
params.teams.forEach(team => queryParams.append('teams[]', team));
}

if (params.calendar_invitees && Array.isArray(params.calendar_invitees)) {
params.calendar_invitees.forEach(email => queryParams.append('calendar_invitees[]', email));
}

if (params.created_after) {
queryParams.append('created_after', params.created_after);
}

if (params.meeting_type) {
queryParams.append('meeting_type', params.meeting_type);
}

if (params.include_transcript !== undefined) {
queryParams.append('include_transcript', params.include_transcript);
}

if (params.cursor) {
queryParams.append('cursor', params.cursor);
}

const query = queryParams.toString();
const url = query ? `${this.URLs.meetings}?${query}` : this.URLs.meetings;

return this._get(url);
}

async listTeams() {
return this._get(this.URLs.teams);
}

async listTeamMembers() {
return this._get(this.URLs.teamMembers);
}

async *iterateMeetings(params = {}) {
let cursor = null;
do {
const response = await this.listMeetings({ ...params, cursor });

if (response.data && Array.isArray(response.data)) {
for (const meeting of response.data) {
yield meeting;
}
}

cursor = response.next_cursor || null;
} while (cursor);
}
}

module.exports = { Api };
9 changes: 9 additions & 0 deletions packages/v1-ready/fathom/defaultConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "fathom",
"label": "Fathom",
"productUrl": "https://fathom.video",
"apiDocs": "https://docs.fathom.ai/api-reference",
"logoUrl": "https://assets-global.website-files.com/6123a1d125034c5d3ee5fc7f/632a973b1c3c733a6c5b69e8_fathom-logo.svg",
"categories": ["video", "meetings", "productivity", "ai", "transcription"],
"description": "Fathom is an AI meeting assistant that records, transcribes, highlights, and summarizes your meetings."
}
80 changes: 80 additions & 0 deletions packages/v1-ready/fathom/definition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { Api } = require('./api');
const config = require('./defaultConfig.json');

const Definition = {
API: Api,
getName: function () {
return config.name;
},
moduleName: config.name,
requiredAuthMethods: {
getAuthorizationRequirements: async function () {
return {
type: 'api_key',
fields: [
{
key: 'apiKey',
label: 'API Key',
placeholder: 'Enter your Fathom API key',
type: 'password',
required: true,
helpText: 'You can find your API key in Fathom settings under API Access'
}
]
};
},

setAuthParams: async function (api, params) {
api.apiKey = params.apiKey;
api.access_token = params.apiKey;
},

getEntityDetails: async function (api, callbackParams, tokenResponse, userId) {
const teams = await api.listTeams();
const primaryTeam = teams && teams.data && teams.data[0];

return {
identifiers: { externalId: primaryTeam ? primaryTeam.id : 'default' },
details: {
name: primaryTeam ? primaryTeam.name : 'Fathom User',
team: primaryTeam
},
};
},

apiPropertiesToPersist: {
credential: ['apiKey'],
entity: [],
},

getCredentialDetails: async function (api, userId) {
const teams = await api.listTeams();
const primaryTeam = teams && teams.data && teams.data[0];

return {
identifiers: { externalId: primaryTeam ? primaryTeam.id : 'default' },
details: {
authenticated: true,
teamName: primaryTeam ? primaryTeam.name : 'Unknown'
},
};
},

testAuthRequest: async function (api) {
try {
const response = await api.listTeams();
return response && (response.data !== undefined);
} catch (error) {
if (error.message && error.message.includes('401')) {
throw new Error('Invalid API key');
}
throw error;
}
},
},
env: {
apiKey: process.env.FATHOM_API_KEY,
}
};

module.exports = { Definition };
9 changes: 9 additions & 0 deletions packages/v1-ready/fathom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { Api } = require('./api');
const { Definition } = require('./definition');
const config = require('./defaultConfig.json');

module.exports = {
Api,
Definition,
config,
};
1 change: 1 addition & 0 deletions packages/v1-ready/fathom/jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('dotenv').config({ path: '../../../.env' });
3 changes: 3 additions & 0 deletions packages/v1-ready/fathom/jest-teardown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = async () => {
// Add any global teardown logic here if needed
};
17 changes: 17 additions & 0 deletions packages/v1-ready/fathom/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
testEnvironment: 'node',
collectCoverageFrom: [
'**/*.js',
'!jest.config.js',
'!coverage/**',
'!node_modules/**',
'!tests/**',
'!jest-setup.js',
'!jest-teardown.js',
],
coverageReporters: ['text', 'lcov', 'html'],
setupFilesAfterEnv: ['./jest-setup.js'],
globalTeardown: './jest-teardown.js',
testMatch: ['**/tests/**/*.test.js'],
testTimeout: 30000,
};
Loading
Loading