Metadata-Version: 2.4
Name: meemo
Version: 0.0.4
Summary: Meemo Python Client - Python client library for Meemo External API
Author-email: GDP Labs <jobs@gdplabs.id>
Requires-Python: <3.13,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.0.0
Dynamic: license-file

# Meemo

A Python library for interacting with the Meemo External API, providing access to meeting data including details, transcripts, summaries, participants, and recordings.

## Prerequisites

- **Python** >=3.11, <3.13

## Installation

```bash
pip install meemo
```

Or with uv:

```bash
uv add meemo
```

## Quick Start

```python
from meemo import MeemoClient

# Initialize the client
client = MeemoClient(
    client_id="your-client-id",
    client_secret="your-client-secret",
    base_url="https://api-meemo.glair.ai",
)

# List meetings
meetings = client.meetings.list_meetings()
for meeting in meetings.results:
    print(f"{meeting.id}: {meeting.title} ({meeting.status})")

# Get meeting details
detail = client.meetings.get_meeting(123)
print(f"Host: {detail.host.name}")
print(f"Duration: {detail.duration_seconds}s")

# Get transcript
transcript = client.meetings.get_transcript(123)
for segment in transcript.transcripts:
    print(f"[{segment.speaker}] {segment.text}")

# Get summary
summary = client.meetings.get_summary(123)
print(summary.summary)
```

## Async Usage

For use in async frameworks (FastAPI, Django async views, async workers), use `AsyncMeemoClient`:

```python
import asyncio
from meemo import AsyncMeemoClient

async def main():
    client = AsyncMeemoClient(
        client_id="your-client-id",
        client_secret="your-client-secret",
        base_url="https://api-meemo.glair.ai",
    )

    # All methods are async — same API surface as MeemoClient
    meetings = await client.meetings.list_meetings()
    for meeting in meetings.results:
        print(f"{meeting.id}: {meeting.title}")

    detail = await client.meetings.get_meeting(123)
    transcript = await client.meetings.get_transcript(123)
    summary = await client.meetings.get_summary(123)

    # Revoke token when done
    await client.revoke_token()

asyncio.run(main())
```

## Configuration

### Environment Variables

The library supports the following environment variables:

- `MEEMO_CLIENT_ID`: OAuth2 client ID for authentication
- `MEEMO_CLIENT_SECRET`: OAuth2 client secret for authentication
- `MEEMO_BASE_URL`: Base URL of the Meemo instance

### Client Initialization

```python
from meemo import MeemoClient

# Using explicit parameters
client = MeemoClient(
    client_id="your-client-id",
    client_secret="your-client-secret",
    base_url="https://api-meemo.glair.ai",
    timeout=60.0,
    default_headers={"X-Custom-Header": "value"},
)

# Using environment variables
import os
os.environ["MEEMO_CLIENT_ID"] = "your-client-id"
os.environ["MEEMO_CLIENT_SECRET"] = "your-client-secret"
os.environ["MEEMO_BASE_URL"] = "https://api-meemo.glair.ai"
client = MeemoClient()
```

## Authentication

The library uses **OAuth2 Client Credentials** flow. Tokens are automatically obtained and refreshed when they expire (default: 3 hours).

```python
# Token management is automatic, but you can also manage it manually:

# Revoke the current token
client.revoke_token()
```

## Meetings API

### List Meetings

```python
# Get all meetings
meetings = client.meetings.list_meetings()
print(f"Total: {meetings.count}")

# Filter by organization
meetings = client.meetings.list_meetings(organization_id=5)

# Filter by title and date
meetings = client.meetings.list_meetings(
    title="standup",
    created_after="2024-01-01",
    created_before="2024-12-31",
    summary_complete=True,
)

# Pagination
meetings = client.meetings.list_meetings(page=2, size=20)
```

All filters are keyword-only (see `MeetingListKwargs` for the accepted keys);
`extra_headers` remains the only positional argument.

#### Search by Summary

`search` runs a ranked lexical search over the meeting title, calendar invite
summary, the AI-generated summary markdown (the `summary` field from
`get_summary` / `summary_markdown` on `GET .../summary/` — **not** `notes`), and
tags — so you can find a meeting by what was discussed in it, not just by its
title. Results come back ordered by relevance instead of start time.

```python
# Find meetings whose summary (or title/tags) mentions the query
meetings = client.meetings.list_meetings(search="pricing model")
for m in meetings.results:
    print(f"{m.id}: {m.title}")

# Combine with any other filter (all active filters use AND semantics)
meetings = client.meetings.list_meetings(
    search="pricing model",
    organization_id=5,
    start_after="2024-01-01",
)
```

Ranking is tiered: title and calendar-summary hits rank above summary-only
hits, which rank above tag-only hits. Summary matching is a contiguous
substring match, so `"budget Q3"` will not match a summary reading
`"Q3 budget"` — title and calendar matching does handle non-adjacent words.

### Get Meeting Details

```python
detail = client.meetings.get_meeting(123)
print(f"Title: {detail.title}")
print(f"Host: {detail.host.name} ({detail.host.email})")
print(f"Location: {detail.location}")
print(f"Language: {detail.language}")
print(f"Keywords: {', '.join(detail.keywords)}")
print(f"Participants: {detail.participant_count}")
print(f"Duration: {detail.duration_seconds}s")

# Calendar event info (if from calendar integration)
if detail.calendar_event:
    print(f"Calendar: {detail.calendar_event.summary}")
    print(f"Meeting Link: {detail.calendar_event.meeting_link}")
```

### Get Meeting Transcript

```python
transcript = client.meetings.get_transcript(123)
print(f"Total segments: {transcript.total_segments}")

for segment in transcript.transcripts:
    minutes = int(segment.start_time // 60)
    seconds = int(segment.start_time % 60)
    print(f"[{minutes:02d}:{seconds:02d}] {segment.speaker}: {segment.text}")
```

### Get Meeting Summary

```python
summary = client.meetings.get_summary(123)
print(summary.summary)  # Markdown or legacy JSON
print(f"Notes: {summary.notes}")
print(f"Keywords: {', '.join(summary.keywords)}")

# Handle both new Markdown and legacy JSON formats
if isinstance(summary.summary, str):
    # New Markdown format
    print(summary.summary)
elif isinstance(summary.summary, dict):
    # Legacy JSON format
    print(summary.summary.get("ringkasan", ""))
```

### Get Meeting Participants

```python
participants = client.meetings.get_participants(123)
print(f"Total: {participants.total_participants}")

for p in participants.participants:
    if p.id:
        print(f"  {p.name} ({p.email}) - {p.position}, {p.department}")
    else:
        print(f"  {p.name} (external guest)")
```

### Get Meeting Recording

```python
recording = client.meetings.get_recording(123)

# Preferred: iterate over assets (audio and/or video).
if recording.assets:
    for asset in recording.assets:
        print(f"{asset.media_kind}: {asset.url} ({asset.format})")
    print(f"Duration: {recording.duration}s")
else:
    print("No recording available")

# Deprecated back-compat aliases (audio only), populated from the first
# audio asset when not provided directly:
if recording.recording_url:
    print(f"Audio URL: {recording.recording_url} ({recording.format})")

# Convenience aliases for the first video asset when present:
if recording.video_url:
    print(f"Video URL: {recording.video_url} ({recording.video_format})")
```

## Multi-Organization Support

External applications can access meetings from multiple organizations with a single set of credentials.

```python
# Get meetings from all accessible organizations
all_meetings = client.meetings.list_meetings()

# Filter to a specific organization
org_meetings = client.meetings.list_meetings(organization_id=5)
```

## Error Handling

```python
import httpx
from meemo import MeemoClient

client = MeemoClient(
    client_id="your-client-id",
    client_secret="your-client-secret",
    base_url="https://api-meemo.glair.ai",
)

try:
    detail = client.meetings.get_meeting(999)
except httpx.HTTPStatusError as e:
    if e.response.status_code == 401:
        print("Authentication failed - check your credentials")
    elif e.response.status_code == 404:
        print("Meeting not found")
    else:
        print(f"HTTP Error: {e.response.status_code}")
        print(f"Response: {e.response.text}")
except ValueError as e:
    print(f"Validation Error: {e}")
```

## Testing

### Unit tests

```bash
cd libs/meemo
uv sync --group dev
uv run pytest tests/unit_tests/ -v
```

### Integration tests (live)

The integration tests exercise a real Meemo instance. Copy `.env.example` to `.env`, fill in your Meemo OAuth credentials, then:

```bash
uv run pytest tests/integration_tests/ -v
```

Optional flags in `.env` or the shell:

- `MEEMO_TEST_CREATE_MEETING=1` — also run the `create_meeting` live test (leaves a real meeting behind; off by default)

## API Reference

- [Meemo External API Documentation](https://gdplabs.gitbook.io/meemo/resources/external-api-documentation)

## License

MIT License.
