fix(podcast): Improve podcast episode validation, make Feed.itemIds protected, expand README and add tests
This commit is contained in:
389
readme.md
389
readme.md
@@ -1,98 +1,343 @@
|
||||
# @push.rocks/smartfeed
|
||||
|
||||
create and parse feeds
|
||||
**The modern TypeScript library for creating and parsing RSS, Atom, and Podcast feeds** 🚀
|
||||
|
||||
## Install
|
||||
`@push.rocks/smartfeed` is a powerful, type-safe feed management library that makes creating and parsing RSS 2.0, Atom 1.0, JSON Feed, and Podcast feeds ridiculously easy. Built with TypeScript from the ground up, it offers comprehensive validation, security features, and supports modern podcast standards including iTunes tags and the Podcast namespace.
|
||||
|
||||
To install `@push.rocks/smartfeed`, you need to have Node.js installed on your machine. After setting up Node.js, run the following command in your terminal:
|
||||
## Features ✨
|
||||
|
||||
- 🎯 **Full TypeScript Support** - Complete type definitions for all feed formats
|
||||
- 📡 **Multiple Feed Formats** - RSS 2.0, Atom 1.0, JSON Feed 1.0, and Podcast RSS
|
||||
- 🎙️ **Modern Podcast Support** - iTunes tags, Podcast namespace (chapters, transcripts, funding, persons)
|
||||
- 🔒 **Built-in Validation** - Comprehensive validation for URLs, emails, domains, and timestamps
|
||||
- 🛡️ **Security First** - XSS prevention, content sanitization, and secure defaults
|
||||
- 📦 **Zero Config** - Works out of the box with sensible defaults
|
||||
- 🔄 **Feed Parsing** - Parse existing RSS and Atom feeds from strings or URLs
|
||||
- 🎨 **Flexible API** - Create feeds from scratch or from standardized article arrays
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @push.rocks/smartfeed --save
|
||||
pnpm install @push.rocks/smartfeed
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Quick Start
|
||||
|
||||
`@push.rocks/smartfeed` is a powerful library for creating and parsing RSS and Atom feeds with ease. It leverages TypeScript for type safety and improved developer experience. Let's explore how you can utilize this library in your projects.
|
||||
|
||||
### Creating Feeds
|
||||
|
||||
You can create feeds by instantiating a `Smartfeed` object and configuring feed options and items. Here’s an example of how to create an RSS feed:
|
||||
|
||||
```typescript
|
||||
import { Smartfeed, IFeedOptions, IFeedItem } from '@push.rocks/smartfeed';
|
||||
|
||||
// Create a new Smartfeed instance
|
||||
const mySmartfeed = new Smartfeed();
|
||||
|
||||
// Define feed options
|
||||
const feedOptions: IFeedOptions = {
|
||||
domain: 'example.com',
|
||||
title: 'Example News',
|
||||
description: 'Latest news from Example',
|
||||
category: 'News',
|
||||
company: 'Example Company',
|
||||
companyEmail: 'contact@example.com',
|
||||
companyDomain: 'https://example.com',
|
||||
};
|
||||
|
||||
// Create a new feed with options
|
||||
const myFeed = mySmartfeed.createFeed(feedOptions);
|
||||
|
||||
// Add items to the feed
|
||||
const feedItem: IFeedItem = {
|
||||
title: 'Breaking News: TypeScript Adoption Soars!',
|
||||
timestamp: Date.now(), // Use current timestamp
|
||||
url: 'https://example.com/news/typescript-adoption',
|
||||
authorName: 'Jane Doe',
|
||||
imageUrl: 'https://example.com/images/typescript-news.jpg',
|
||||
content:
|
||||
'In a recent survey, TypeScript has seen a significant increase in adoption among developers...',
|
||||
};
|
||||
|
||||
// Add the item to the feed
|
||||
myFeed.addItem(feedItem);
|
||||
|
||||
// Export the feed as an RSS string
|
||||
const rssFeedString = myFeed.exportRssFeedString();
|
||||
console.log(rssFeedString);
|
||||
```
|
||||
|
||||
This code snippet creates an RSS feed for a news article. You can customize the `IFeedOptions` and `IFeedItem` objects to match your content.
|
||||
|
||||
### Parsing Feeds
|
||||
|
||||
`@push.rocks/smartfeed` also allows parsing of RSS and Atom feeds from a string or URL. Here’s how you can parse a feed:
|
||||
### Creating a Basic Feed
|
||||
|
||||
```typescript
|
||||
import { Smartfeed } from '@push.rocks/smartfeed';
|
||||
|
||||
// Create a new Smartfeed instance
|
||||
const mySmartfeed = new Smartfeed();
|
||||
const smartfeed = new Smartfeed();
|
||||
|
||||
// Parsing a feed from a string
|
||||
const rssString = `your RSS feed string here`;
|
||||
mySmartfeed.parseFeedFromString(rssString).then((feed) => {
|
||||
console.log(feed);
|
||||
// Create a feed
|
||||
const feed = smartfeed.createFeed({
|
||||
domain: 'example.com',
|
||||
title: 'Tech Insights',
|
||||
description: 'Latest insights in technology and innovation',
|
||||
category: 'Technology',
|
||||
company: 'Example Inc',
|
||||
companyEmail: 'hello@example.com',
|
||||
companyDomain: 'https://example.com'
|
||||
});
|
||||
|
||||
// Parsing a feed from a URL
|
||||
const feedUrl = 'https://example.com/rss';
|
||||
mySmartfeed.parseFeedFromUrl(feedUrl).then((feed) => {
|
||||
console.log(feed);
|
||||
// Add an item
|
||||
feed.addItem({
|
||||
title: 'TypeScript 5.0 Released',
|
||||
timestamp: Date.now(),
|
||||
url: 'https://example.com/posts/typescript-5',
|
||||
authorName: 'Jane Developer',
|
||||
imageUrl: 'https://example.com/images/typescript.jpg',
|
||||
content: 'TypeScript 5.0 brings exciting new features...'
|
||||
});
|
||||
|
||||
// Export as RSS, Atom, or JSON
|
||||
const rss = feed.exportRssFeedString();
|
||||
const atom = feed.exportAtomFeed();
|
||||
const json = feed.exportJsonFeed();
|
||||
```
|
||||
|
||||
### Creating a Podcast Feed
|
||||
|
||||
```typescript
|
||||
import { Smartfeed } from '@push.rocks/smartfeed';
|
||||
|
||||
const smartfeed = new Smartfeed();
|
||||
|
||||
const podcast = smartfeed.createPodcastFeed({
|
||||
domain: 'podcast.example.com',
|
||||
title: 'The Tech Show',
|
||||
description: 'Weekly discussions about technology',
|
||||
category: 'Technology',
|
||||
company: 'Tech Media Inc',
|
||||
companyEmail: 'podcast@example.com',
|
||||
companyDomain: 'https://example.com',
|
||||
itunesCategory: 'Technology',
|
||||
itunesAuthor: 'John Host',
|
||||
itunesOwner: {
|
||||
name: 'John Host',
|
||||
email: 'john@example.com'
|
||||
},
|
||||
itunesImage: 'https://example.com/artwork.jpg',
|
||||
itunesExplicit: false,
|
||||
itunesType: 'episodic'
|
||||
});
|
||||
|
||||
// Add an episode
|
||||
podcast.addEpisode({
|
||||
title: 'Episode 42: The Future of AI',
|
||||
authorName: 'John Host',
|
||||
imageUrl: 'https://example.com/episode42.jpg',
|
||||
timestamp: Date.now(),
|
||||
url: 'https://example.com/episodes/42',
|
||||
content: 'In this episode, we explore the future of artificial intelligence...',
|
||||
audioUrl: 'https://example.com/audio/episode42.mp3',
|
||||
audioType: 'audio/mpeg',
|
||||
audioLength: 45678900, // bytes
|
||||
itunesDuration: 3600, // seconds
|
||||
itunesEpisode: 42,
|
||||
itunesSeason: 2,
|
||||
itunesEpisodeType: 'full',
|
||||
itunesExplicit: false,
|
||||
// Modern podcast features
|
||||
persons: [
|
||||
{ name: 'John Host', role: 'host' },
|
||||
{ name: 'Jane Guest', role: 'guest', href: 'https://example.com/jane' }
|
||||
],
|
||||
transcripts: [
|
||||
{ url: 'https://example.com/transcripts/ep42.txt', type: 'text/plain' }
|
||||
],
|
||||
funding: [
|
||||
{ url: 'https://example.com/support', message: 'Support the show!' }
|
||||
]
|
||||
});
|
||||
|
||||
// Export podcast RSS with iTunes and Podcast namespace
|
||||
const podcastRss = podcast.exportPodcastRss();
|
||||
```
|
||||
|
||||
### Parsing Existing Feeds
|
||||
|
||||
```typescript
|
||||
import { Smartfeed } from '@push.rocks/smartfeed';
|
||||
|
||||
const smartfeed = new Smartfeed();
|
||||
|
||||
// Parse from URL
|
||||
const feed = await smartfeed.parseFeedFromUrl('https://example.com/feed.xml');
|
||||
console.log(feed.title);
|
||||
console.log(feed.items.map(item => item.title));
|
||||
|
||||
// Parse from string
|
||||
const xmlString = '<rss>...</rss>';
|
||||
const parsedFeed = await smartfeed.parseFeedFromString(xmlString);
|
||||
```
|
||||
|
||||
### Creating Feeds from Article Arrays
|
||||
|
||||
```typescript
|
||||
import { Smartfeed } from '@push.rocks/smartfeed';
|
||||
import type { IArticle } from '@tsclass/tsclass';
|
||||
|
||||
const smartfeed = new Smartfeed();
|
||||
|
||||
const articles: IArticle[] = [
|
||||
// Your article objects conforming to @tsclass/tsclass IArticle interface
|
||||
];
|
||||
|
||||
const feedOptions = {
|
||||
domain: 'blog.example.com',
|
||||
title: 'My Blog',
|
||||
description: 'Thoughts on code and design',
|
||||
category: 'Programming',
|
||||
company: 'Example Inc',
|
||||
companyEmail: 'blog@example.com',
|
||||
companyDomain: 'https://example.com'
|
||||
};
|
||||
|
||||
// Creates an Atom feed from articles
|
||||
const atomFeed = await smartfeed.createFeedFromArticleArray(feedOptions, articles);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Smartfeed Class
|
||||
|
||||
The main class for creating and parsing feeds.
|
||||
|
||||
#### `createFeed(options: IFeedOptions): Feed`
|
||||
|
||||
Creates a standard feed (RSS/Atom/JSON).
|
||||
|
||||
**Options:**
|
||||
- `domain` (string) - Feed domain (e.g., 'example.com')
|
||||
- `title` (string) - Feed title
|
||||
- `description` (string) - Feed description
|
||||
- `category` (string) - Feed category
|
||||
- `company` (string) - Company/organization name
|
||||
- `companyEmail` (string) - Contact email
|
||||
- `companyDomain` (string) - Company website URL (absolute)
|
||||
|
||||
#### `createPodcastFeed(options: IPodcastFeedOptions): PodcastFeed`
|
||||
|
||||
Creates a podcast feed with iTunes and Podcast namespace support.
|
||||
|
||||
**Additional Options:**
|
||||
- `itunesCategory` (string) - iTunes category
|
||||
- `itunesSubcategory` (string, optional) - iTunes subcategory
|
||||
- `itunesAuthor` (string) - Podcast author
|
||||
- `itunesOwner` (object) - Owner info with `name` and `email`
|
||||
- `itunesImage` (string) - Artwork URL (1400x1400 to 3000x3000, JPG/PNG)
|
||||
- `itunesExplicit` (boolean) - Explicit content flag
|
||||
- `itunesType` ('episodic' | 'serial', optional) - Podcast type
|
||||
- `itunesSummary` (string, optional) - Detailed summary
|
||||
- `copyright` (string, optional) - Custom copyright
|
||||
- `language` (string, optional) - Language code (default: 'en')
|
||||
|
||||
#### `parseFeedFromUrl(url: string): Promise<ParsedFeed>`
|
||||
|
||||
Parses an RSS or Atom feed from a URL.
|
||||
|
||||
#### `parseFeedFromString(xmlString: string): Promise<ParsedFeed>`
|
||||
|
||||
Parses an RSS or Atom feed from an XML string.
|
||||
|
||||
#### `createFeedFromArticleArray(options: IFeedOptions, articles: IArticle[]): Promise<string>`
|
||||
|
||||
Creates an Atom feed from an array of `@tsclass/tsclass` article objects.
|
||||
|
||||
### Feed Class
|
||||
|
||||
Represents a feed that can be exported in multiple formats.
|
||||
|
||||
#### `addItem(item: IFeedItem): void`
|
||||
|
||||
Adds an item to the feed.
|
||||
|
||||
**Item Properties:**
|
||||
- `title` (string) - Item title
|
||||
- `timestamp` (number) - Unix timestamp in milliseconds
|
||||
- `url` (string) - Absolute URL to the item
|
||||
- `authorName` (string) - Author name
|
||||
- `imageUrl` (string) - Absolute URL to featured image
|
||||
- `content` (string) - Item content/description
|
||||
- `id` (string, optional) - Unique identifier (uses URL if not provided)
|
||||
|
||||
#### `exportRssFeedString(): string`
|
||||
|
||||
Exports the feed as RSS 2.0 XML.
|
||||
|
||||
#### `exportAtomFeed(): string`
|
||||
|
||||
Exports the feed as Atom 1.0 XML.
|
||||
|
||||
#### `exportJsonFeed(): string`
|
||||
|
||||
Exports the feed as JSON Feed 1.0.
|
||||
|
||||
### PodcastFeed Class
|
||||
|
||||
Extends `Feed` with podcast-specific functionality.
|
||||
|
||||
#### `addEpisode(episode: IPodcastItem): void`
|
||||
|
||||
Adds a podcast episode to the feed.
|
||||
|
||||
**Episode Properties (in addition to IFeedItem):**
|
||||
- `audioUrl` (string) - Absolute URL to audio file
|
||||
- `audioType` (string) - MIME type (e.g., 'audio/mpeg')
|
||||
- `audioLength` (number) - File size in bytes
|
||||
- `itunesDuration` (number) - Duration in seconds
|
||||
- `itunesEpisode` (number, optional) - Episode number
|
||||
- `itunesSeason` (number, optional) - Season number
|
||||
- `itunesEpisodeType` ('full' | 'trailer' | 'bonus', optional)
|
||||
- `itunesExplicit` (boolean, optional) - Explicit content flag
|
||||
- `itunesSubtitle` (string, optional) - Short description
|
||||
- `itunesSummary` (string, optional) - Detailed summary
|
||||
- `persons` (array, optional) - People involved (hosts, guests)
|
||||
- `chapters` (array, optional) - Chapter markers
|
||||
- `transcripts` (array, optional) - Transcript links
|
||||
- `funding` (array, optional) - Donation/support links
|
||||
|
||||
#### `exportPodcastRss(): string`
|
||||
|
||||
Exports the podcast feed as RSS 2.0 with iTunes and Podcast namespace extensions.
|
||||
|
||||
## Validation & Security
|
||||
|
||||
`@push.rocks/smartfeed` includes comprehensive validation to ensure feed integrity and security:
|
||||
|
||||
- **URL Validation** - All URLs must be absolute and use http/https protocols
|
||||
- **Email Validation** - Email addresses are validated against RFC standards
|
||||
- **Domain Validation** - Proper domain format checking
|
||||
- **Timestamp Validation** - Ensures timestamps are valid and reasonable
|
||||
- **Content Sanitization** - Prevents XSS attacks through proper XML escaping
|
||||
- **Duplicate Detection** - Prevents duplicate item IDs in feeds
|
||||
- **Required Field Checking** - Validates all required fields are present
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Feed Item IDs
|
||||
|
||||
Feed item IDs should be permanent and never change once published. This allows feed readers to properly track which items have been read:
|
||||
|
||||
```typescript
|
||||
feed.addItem({
|
||||
id: 'post-2024-01-15-typescript-tips', // Permanent ID
|
||||
title: 'TypeScript Tips',
|
||||
url: 'https://example.com/posts/typescript-tips',
|
||||
// ... other fields
|
||||
});
|
||||
```
|
||||
|
||||
This example demonstrates how to parse an RSS feed from a given string or URL. The `parseFeedFromString` and `parseFeedFromUrl` methods return a Promise that resolves to the parsed feed object.
|
||||
If you don't provide an `id`, the `url` will be used. Make sure URLs don't change for published items.
|
||||
|
||||
### Comprehensive Feed Management
|
||||
### HTTPS URLs
|
||||
|
||||
With `@push.rocks/smartfeed`, you have full control over creating and managing feeds. Beyond basic scenarios shown above, you can create feeds from arrays of articles, customize feed and item properties extensively, and export feeds in different formats (RSS, Atom, JSON).
|
||||
Always use HTTPS URLs for security and privacy. The library will warn you if HTTP URLs are used:
|
||||
|
||||
For instance, to create a feed from an array of article objects conforming to `@tsclass/tsclass`'s `IArticle` interface, you can use the `createFeedFromArticleArray` method. Additionally, explore different export options available on the `Feed` class to suit your needs, whether it's RSS 2.0, Atom 1.0, or JSON Feed 1.0.
|
||||
```typescript
|
||||
// ✅ Good
|
||||
imageUrl: 'https://example.com/image.jpg'
|
||||
|
||||
Remember, `@push.rocks/smartfeed` is designed to streamline feed creation and parsing with a focus on type safety and developer experience. Explore its comprehensive API to leverage the full potential of feed management in your applications.
|
||||
// ⚠️ Will trigger a warning
|
||||
imageUrl: 'http://example.com/image.jpg'
|
||||
```
|
||||
|
||||
For complete usage and all available methods, refer to the TypeScript declarations and source code available in the package. Happy coding!
|
||||
### Podcast Artwork
|
||||
|
||||
For podcast feeds, artwork should be:
|
||||
- Square (1:1 aspect ratio)
|
||||
- Between 1400x1400 and 3000x3000 pixels
|
||||
- JPG or PNG format
|
||||
- Maximum 512 KB file size (Apple Podcasts requirement)
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript definitions are included. Import types as needed:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
IFeedOptions,
|
||||
IFeedItem,
|
||||
IPodcastFeedOptions,
|
||||
IPodcastItem,
|
||||
IPodcastOwner,
|
||||
IPodcastPerson,
|
||||
IPodcastChapter,
|
||||
IPodcastTranscript,
|
||||
IPodcastFunding
|
||||
} from '@push.rocks/smartfeed';
|
||||
```
|
||||
|
||||
## Why @push.rocks/smartfeed?
|
||||
|
||||
- **Type-Safe** - Catch errors at compile time, not runtime
|
||||
- **Modern Standards** - Full support for latest podcast specifications
|
||||
- **Secure by Default** - Built-in validation and sanitization
|
||||
- **Developer Friendly** - Intuitive API with great error messages
|
||||
- **Well Tested** - Comprehensive test suite ensuring reliability
|
||||
- **Actively Maintained** - Regular updates and improvements
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
@@ -106,7 +351,7 @@ This project is owned and maintained by Task Venture Capital GmbH. The names and
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
Reference in New Issue
Block a user