Skip to content
Merged
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
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ The application supports various types of Letterboxd URLs for the `LETTERBOXD_UR
### Currently Supported
- **Watchlists**: `https://letterboxd.com/username/watchlist/`
- **Regular Lists**: `https://letterboxd.com/username/list/list-name/`

### Planned Support (Coming Soon)
- **Watched Movies**: `https://letterboxd.com/username/films/`
- **Actor Filmography**: `https://letterboxd.com/actor/actor-name/`
- **Director Filmography**: `https://letterboxd.com/director/director-name/`
- **Writer Filmography**: `https://letterboxd.com/writer/writer-name/`

### Planned Support (Coming Soon)
- **Watched Movies**: `https://letterboxd.com/username/films/`
- **Collections**: `https://letterboxd.com/films/in/collection-name/`
- **Popular Movies**: `https://letterboxd.com/films/popular/`

Expand All @@ -32,6 +32,15 @@ LETTERBOXD_URL=https://letterboxd.com/dave/list/official-top-250-narrative-featu

# Another user's list
LETTERBOXD_URL=https://letterboxd.com/criterion/list/the-criterion-collection/

# Actor filmography (e.g., Tom Hanks)
LETTERBOXD_URL=https://letterboxd.com/actor/tom-hanks/

# Director filmography (e.g., Christopher Nolan)
LETTERBOXD_URL=https://letterboxd.com/director/christopher-nolan/

# Writer filmography (e.g., Aaron Sorkin)
LETTERBOXD_URL=https://letterboxd.com/writer/aaron-sorkin/
```

**Note**: All Letterboxd lists must be public for the application to access them.
Expand Down
48 changes: 40 additions & 8 deletions src/scraper/index.itest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,48 @@ describe('scraper index integration tests', () => {
).rejects.toThrow('Watched movies scraping not implemented');
});

it('should throw error for actor filmography (not implemented)', async () => {
await expect(
fetchMoviesFromUrl('https://letterboxd.com/actor/tom-hanks')
).rejects.toThrow('Actor filmography scraping not implemented');
it('should fetch movies from actor filmography', async () => {
// Using Tom Hanks' filmography
const movies = await fetchMoviesFromUrl('https://letterboxd.com/actor/tom-hanks');

expect(movies).toBeDefined();
expect(Array.isArray(movies)).toBe(true);
expect(movies.length).toBeGreaterThan(0);

// Verify structure of returned movies
const firstMovie = movies[0];
expect(firstMovie).toHaveProperty('id');
expect(firstMovie).toHaveProperty('name');
expect(firstMovie).toHaveProperty('slug');
expect(firstMovie).toHaveProperty('tmdbId');
});

it('should throw error for director filmography (not implemented)', async () => {
await expect(
fetchMoviesFromUrl('https://letterboxd.com/director/christopher-nolan')
).rejects.toThrow('Director filmography scraping not implemented');
it('should fetch movies from director filmography', async () => {
// Using Christopher Nolan's filmography
const movies = await fetchMoviesFromUrl('https://letterboxd.com/director/christopher-nolan');

expect(movies).toBeDefined();
expect(Array.isArray(movies)).toBe(true);
expect(movies.length).toBeGreaterThan(0);

const firstMovie = movies[0];
expect(firstMovie).toHaveProperty('id');
expect(firstMovie).toHaveProperty('name');
expect(firstMovie).toHaveProperty('slug');
});

it('should fetch movies from writer filmography', async () => {
// Using Charlie Kaufman's filmography
const movies = await fetchMoviesFromUrl('https://letterboxd.com/writer/charlie-kaufman');

expect(movies).toBeDefined();
expect(Array.isArray(movies)).toBe(true);
expect(movies.length).toBeGreaterThan(0);

const firstMovie = movies[0];
expect(firstMovie).toHaveProperty('id');
expect(firstMovie).toHaveProperty('name');
expect(firstMovie).toHaveProperty('slug');
});

it('should throw error for collections (not implemented)', async () => {
Expand Down
66 changes: 54 additions & 12 deletions src/scraper/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,64 @@ describe('scraper index', () => {
);
});

it('should throw error for actor filmography (not implemented)', async () => {
await expect(
fetchMoviesFromUrl('https://letterboxd.com/actor/tom-hanks')
).rejects.toThrow('Actor filmography scraping not implemented');
it('should fetch movies from actor filmography URL', async () => {
const mockMovies = [
{ id: 1, name: 'Movie 1', slug: '/film/movie1/', tmdbId: '123', imdbId: null, publishedYear: null },
];

const mockGetMovies = jest.fn().mockResolvedValue(mockMovies);
(ListScraper as jest.Mock).mockImplementation(() => ({
getMovies: mockGetMovies,
}));

const result = await fetchMoviesFromUrl('https://letterboxd.com/actor/tom-hanks');

expect(result).toEqual(mockMovies);
expect(ListScraper).toHaveBeenCalledWith(
'https://letterboxd.com/actor/tom-hanks',
undefined,
undefined
);
});

it('should throw error for director filmography (not implemented)', async () => {
await expect(
fetchMoviesFromUrl('https://letterboxd.com/director/steven-spielberg')
).rejects.toThrow('Director filmography scraping not implemented');
it('should fetch movies from director filmography URL', async () => {
const mockMovies = [
{ id: 1, name: 'Movie 1', slug: '/film/movie1/', tmdbId: '123', imdbId: null, publishedYear: null },
];

const mockGetMovies = jest.fn().mockResolvedValue(mockMovies);
(ListScraper as jest.Mock).mockImplementation(() => ({
getMovies: mockGetMovies,
}));

const result = await fetchMoviesFromUrl('https://letterboxd.com/director/steven-spielberg');

expect(result).toEqual(mockMovies);
expect(ListScraper).toHaveBeenCalledWith(
'https://letterboxd.com/director/steven-spielberg',
undefined,
undefined
);
});

it('should throw error for writer filmography (not implemented)', async () => {
await expect(
fetchMoviesFromUrl('https://letterboxd.com/writer/aaron-sorkin')
).rejects.toThrow('Writer filmography scraping not implemented');
it('should fetch movies from writer filmography URL', async () => {
const mockMovies = [
{ id: 1, name: 'Movie 1', slug: '/film/movie1/', tmdbId: '123', imdbId: null, publishedYear: null },
];

const mockGetMovies = jest.fn().mockResolvedValue(mockMovies);
(ListScraper as jest.Mock).mockImplementation(() => ({
getMovies: mockGetMovies,
}));

const result = await fetchMoviesFromUrl('https://letterboxd.com/writer/aaron-sorkin');

expect(result).toEqual(mockMovies);
expect(ListScraper).toHaveBeenCalledWith(
'https://letterboxd.com/writer/aaron-sorkin',
undefined,
undefined
);
});

it('should throw error for collections (not implemented)', async () => {
Expand Down
22 changes: 7 additions & 15 deletions src/scraper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,28 @@ export const fetchMoviesFromUrl = async (url: string): Promise<LetterboxdMovie[]
}

switch (listType) {
case ListType.ACTOR_FILMOGRAPHY:
case ListType.DIRECTOR_FILMOGRAPHY:
case ListType.WRITER_FILMOGRAPHY:
case ListType.WATCHLIST:
case ListType.REGULAR_LIST:
// Filmography pages and lists use the same HTML structure
// Determine take parameters from environment variables
let take: number | undefined = undefined;
let strategy: 'oldest' | 'newest' | undefined = undefined;

if (env.LETTERBOXD_TAKE_AMOUNT && env.LETTERBOXD_TAKE_STRATEGY) {
take = env.LETTERBOXD_TAKE_AMOUNT;
strategy = env.LETTERBOXD_TAKE_STRATEGY;
}

const listScraper = new ListScraper(url, take, strategy);
return listScraper.getMovies();

case ListType.WATCHED_MOVIES:
// TODO: Implement watched movies scraping
throw new Error('Watched movies scraping not implemented');

case ListType.ACTOR_FILMOGRAPHY:
// TODO: Implement actor filmography scraping
throw new Error('Actor filmography scraping not implemented');

case ListType.DIRECTOR_FILMOGRAPHY:
// TODO: Implement director filmography scraping
throw new Error('Director filmography scraping not implemented');

case ListType.WRITER_FILMOGRAPHY:
// TODO: Implement writer filmography scraping
throw new Error('Writer filmography scraping not implemented');

case ListType.COLLECTIONS:
// TODO: Implement collections scraping
throw new Error('Collections scraping not implemented');
Expand Down
Loading