Skip to content

Commit

Permalink
Live and Shorts URL Support (#1)
Browse files Browse the repository at this point in the history
* Add support for live and shorts URL. Add more test coverage.

* Bump version to 1.0.1
  • Loading branch information
ericmmartin authored Jan 19, 2025
1 parent af33413 commit 486b6cf
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 10 deletions.
4 changes: 0 additions & 4 deletions .eslintignore

This file was deleted.

6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.DS_Store
node_modules
/dist
repomix*.txt
coverage/
repomix-output.txt

# local env files
.env.local
Expand All @@ -23,5 +24,4 @@ yarn-error.log*
*.sw?
.editorconfig
/storybook-static
.npmrc
repomix-output.txt
.npmrc
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules/
dist/
coverage/
repomix*.txt
repomix-output.txt
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ export default {
transform: {
'^.+\\.ts$': 'ts-jest',
},
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "youtube-transcript-plus",
"version": "1.0.0",
"version": "1.0.1",
"description": "Fetch transcript from a youtube video",
"type": "module",
"main": "dist/youtube-transcript-plus.js",
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/cache/fs-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,24 @@ describe('FsCache', () => {
expect(await cache.get('invalid-key')).toBeNull();
});
});

describe('FsCache Expiry', () => {
const cacheDir = './test-cache';
let cache: FsCache;

beforeEach(async () => {
await fs.mkdir(cacheDir, { recursive: true });
cache = new FsCache(cacheDir, 1000); // 1 second TTL
});

afterEach(async () => {
await fs.rm(cacheDir, { recursive: true, force: true });
});

it('should return null after the TTL has expired', async () => {
await cache.set('key1', 'value1');
expect(await cache.get('key1')).toBe('value1');
await new Promise((resolve) => setTimeout(resolve, 1100)); // Wait for TTL to expire
expect(await cache.get('key1')).toBeNull();
});
});
15 changes: 15 additions & 0 deletions src/__tests__/cache/in-memory-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,18 @@ describe('InMemoryCache', () => {
expect(await cache.get('key1')).toBeNull();
});
});

describe('InMemoryCache Expiry', () => {
let cache: InMemoryCache;

beforeEach(() => {
cache = new InMemoryCache(1000); // 1 second TTL
});

it('should return null after the TTL has expired', async () => {
await cache.set('key1', 'value1');
expect(await cache.get('key1')).toBe('value1');
await new Promise((resolve) => setTimeout(resolve, 1100)); // Wait for TTL to expire
expect(await cache.get('key1')).toBeNull();
});
});
74 changes: 74 additions & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import {
YoutubeTranscriptInvalidVideoIdError,
YoutubeTranscriptDisabledError,
YoutubeTranscriptNotAvailableLanguageError,
YoutubeTranscriptTooManyRequestError,
YoutubeTranscriptNotAvailableError,
} from '../errors';
import { retrieveVideoId } from '../utils';

describe('YoutubeTranscript', () => {
it('should fetch transcript successfully', async () => {
Expand Down Expand Up @@ -38,3 +41,74 @@ describe('YoutubeTranscript', () => {
);
});
});

describe('retrieveVideoId', () => {
it('should return the video ID from a valid YouTube URL', () => {
const url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
expect(retrieveVideoId(url)).toBe('dQw4w9WgXcQ');
});

it('should return the video ID from a short YouTube URL', () => {
const url = 'https://youtu.be/dQw4w9WgXcQ';
expect(retrieveVideoId(url)).toBe('dQw4w9WgXcQ');
});

it('should return the video ID from an embedded YouTube URL', () => {
const url = 'https://www.youtube.com/embed/dQw4w9WgXcQ';
expect(retrieveVideoId(url)).toBe('dQw4w9WgXcQ');
});

it('should return the video ID from a live YouTube URL', () => {
const url = 'https://www.youtube.com/live/dQw4w9WgXcQ';
expect(retrieveVideoId(url)).toBe('dQw4w9WgXcQ');
});

it('should return the video ID from a YouTube Shorts URL', () => {
const url = 'https://youtube.com/shorts/dQw4w9WgXcQ';
expect(retrieveVideoId(url)).toBe('dQw4w9WgXcQ');
});

it('should throw an error for an invalid YouTube URL', () => {
const url = 'https://www.youtube.com/watch?v=invalid';
expect(() => retrieveVideoId(url)).toThrow(YoutubeTranscriptInvalidVideoIdError);
});

it('should throw an error for a non-YouTube URL', () => {
const url = 'https://www.google.com';
expect(() => retrieveVideoId(url)).toThrow(YoutubeTranscriptInvalidVideoIdError);
});
});

describe('YoutubeTranscript Error Handling', () => {
it('should throw YoutubeTranscriptTooManyRequestError when too many requests are made', async () => {
const transcriptFetcher = new YoutubeTranscript();
const videoId = 'dQw4w9WgXcQ';
// Mock a response that indicates too many requests
jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: true,
text: () => Promise.resolve('<div class="g-recaptcha"></div>'),
} as Response);

await expect(transcriptFetcher.fetchTranscript(videoId)).rejects.toThrow(
YoutubeTranscriptTooManyRequestError,
);
});

it('should throw YoutubeTranscriptNotAvailableError when no transcript is available', async () => {
const transcriptFetcher = new YoutubeTranscript();
const videoId = 'dQw4w9WgXcQ';
// Mock a response that indicates no transcript is available
jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: true,
status: 404,
text: () =>
Promise.resolve(
' <div id="player">{"captions":{"playerCaptionsTracklistRenderer":{"visibility":"UNKNOWN"}},"videoDetails"</div>',
),
} as Response);

await expect(transcriptFetcher.fetchTranscript(videoId)).rejects.toThrow(
YoutubeTranscriptNotAvailableError,
);
});
});
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ export const DEFAULT_USER_AGENT =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';

export const RE_YOUTUBE =
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i;
/(?:v=|\/|v\/|embed\/|watch\?.*v=|youtu\.be\/|\/v\/|e\/|watch\?.*vi?=|\/embed\/|\/v\/|vi?\/|watch\?.*vi?=|youtu\.be\/|\/vi?\/|\/e\/)([a-zA-Z0-9_-]{11})/i;

export const RE_XML_TRANSCRIPT = /<text start="([^"]*)" dur="([^"]*)">([^<]*)<\/text>/g;

0 comments on commit 486b6cf

Please sign in to comment.