Skip to content

Commit 249b705

Browse files
committed
Evening port of atom version
0 parents  commit 249b705

17 files changed

+1622
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
coverage

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# jsonfeed-to-atom Change Log
2+
All notable changes to this project will be documented in this file.
3+
This project adheres to [Semantic Versioning](http://semver.org/).
4+
5+
## Unreleased
6+
* init

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Bret Comnes
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# jsonfeed-to-rss [![stability][0]][1]
2+
[![npm version][2]][3] [![build status][4]][5] [![coverage][12]][13]
3+
[![downloads][8]][9] [![js-standard-style][10]][11]
4+
5+
Convert a JSON feed to an rss feed ([RSS 2.0.11][rss]).
6+
7+
![JSON feed icon](/icon.png)
8+
9+
## Installation
10+
```console
11+
$ npm install jsonfeed-to-rss
12+
```
13+
14+
## Usage
15+
16+
```js
17+
const jsonfeedToRSS = require('jsonfeed-to-rss')
18+
const someJSONFeed = require('./load-some-json-feed-data.json')
19+
20+
const rssFeed = jsonfeedToRSS(someJSONFeed) // Returns an rss 2.0.11 formatted json feed
21+
```
22+
23+
Example input:
24+
25+
```json
26+
{
27+
"version": "https://jsonfeed.org/version/1",
28+
"title": "bret.io log",
29+
"home_page_url": "https://bret.io",
30+
"feed_url": "https://bret.io/feed.json",
31+
"description": "A running log of announcements, projects and accomplishments.",
32+
"next_url": "https://bret.io/2017.json",
33+
"icon": "https://bret.io/icon-512x512.png",
34+
"author": {
35+
"name": "Bret Comnes",
36+
"url": "https://bret.io",
37+
"avatar": "https://gravatar.com/avatar/8d8b82740cb7ca994449cccd1dfdef5f?size=512"
38+
},
39+
"items": [
40+
{
41+
"date_published": "2018-04-07T20:48:02.000Z",
42+
"content_text": "Wee wooo this is some content. \n Maybe a new paragraph too",
43+
"url": "https://bret.io/my-text-post",
44+
"id": "https://bret.io/my-text-post-2018-04-07T20:48:02.000Z"
45+
},
46+
{
47+
"date_published": "2018-04-07T22:06:43.000Z",
48+
"content_html": "<p>Hello, world!</p>",
49+
"title": "This is a blog title",
50+
"url": "https://bret.io/my-blog-post",
51+
"external_url": "https://example.com/some-external-link",
52+
"id": "https://bret.io/my-blog-post-2018-04-07T22:06:43.000Z"
53+
}
54+
]
55+
}
56+
```
57+
58+
Example output:
59+
60+
```xml
61+
<?xml version="1.0" encoding="utf-8"?>
62+
<feed xmlns="http://www.w3.org/2005/Atom">
63+
<title>bret.io log</title>
64+
<id>https://bret.io/feed.xml</id>
65+
<updated>2018-04-07T22:06:43.000Z</updated>
66+
<link rel="self" type="application/atom+xml" href="https://bret.io/feed.xml"/>
67+
<link rel="alternate" type="application/json" href="https://bret.io/feed.json"/>
68+
<link rel="alternate" type="text/html" href="https://bret.io"/>
69+
<link rel="next" href="https://bret.io/2017.xml"/>
70+
<author>
71+
<name>Bret Comnes</name>
72+
<uri>https://bret.io</uri>
73+
</author>
74+
<generator uri="https://github.com/bcomnes/jsonfeed-to-atom#readme" version="1.0.0">jsonfeed-to-atom</generator>
75+
<rights>© 2018 Bret Comnes</rights>
76+
<subtitle>A running log of announcements, projects and accomplishments.</subtitle>
77+
<entry>
78+
<id>https://bret.io/my-text-post-2018-04-07T20:48:02.000Z</id>
79+
<title>Wee wooo this is some content.</title>
80+
<updated>2018-04-07T20:48:02.000Z</updated>
81+
<published>2018-04-07T20:48:02.000Z</published>
82+
<content type="text">Wee wooo this is some content.
83+
Maybe a new paragraph too</content>
84+
<link rel="alternate" href="https://bret.io/my-text-post"/>
85+
</entry>
86+
<entry>
87+
<id>https://bret.io/my-blog-post-2018-04-07T22:06:43.000Z</id>
88+
<title>This is a blog title</title>
89+
<updated>2018-04-07T22:06:43.000Z</updated>
90+
<published>2018-04-07T22:06:43.000Z</published>
91+
<content type="html">
92+
<![CDATA[<p>Hello, world!</p>]]>
93+
</content>
94+
<link rel="alternate" href="https://bret.io/my-blog-post"/>
95+
<link rel="related" href="https://example.com/some-external-link"/>
96+
</entry>
97+
</feed>
98+
```
99+
100+
## API
101+
### `jsonfeedToAtom(parsedJsonfeed, opts)`
102+
Coverts a parsed JSON feed into an atom feed. Returns the string of the atom feed.
103+
104+
Opts include:
105+
106+
```js
107+
{
108+
// a function that returns the atom feed url
109+
feedURLFn: (feedURL, jf) => feedURL.replace(/\.json\b/, '.xml')
110+
}
111+
```
112+
113+
## See also
114+
115+
- [JSON Feed: Mapping RSS and Atom to JSON Feed](https://jsonfeed.org/mappingrssandatom)
116+
- [AtomEnabled: Developers > Syndication](https://web.archive.org/web/20160113103647/http://atomenabled.org/developers/syndication/#link)
117+
- [bcomnes/generate-feed](https://github.com/bcomnes/generate-feed)
118+
119+
## License
120+
[MIT](https://tldrlegal.com/license/mit-license)
121+
122+
[0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square
123+
[1]: https://nodejs.org/api/documentation.html#documentation_stability_index
124+
[2]: https://img.shields.io/npm/v/jsonfeed-to-atom.svg?style=flat-square
125+
[3]: https://npmjs.org/package/jsonfeed-to-atom
126+
[4]: https://img.shields.io/travis/bcomnes/jsonfeed-to-atom/master.svg?style=flat-square
127+
[5]: https://travis-ci.org/bcomnes/jsonfeed-to-atom
128+
[8]: http://img.shields.io/npm/dm/jsonfeed-to-atom.svg?style=flat-square
129+
[9]: https://npmjs.org/package/jsonfeed-to-atom
130+
[10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square
131+
[11]: https://github.com/feross/standard
132+
[12]: https://img.shields.io/coveralls/bcomnes/jsonfeed-to-atom/master.svg?style=flat-square
133+
[13]: https://coveralls.io/github/bcomnes/jsonfeed-to-atom
134+
[rss]: http://www.rssboard.org/rss-specification
135+
136+
137+
RSS LINKS:
138+
139+
http://www.rssboard.org/rss-specification
140+
https://jsonfeed.org/mappingrssandatom
141+
https://jsonfeed.org/version/1
142+
http://www.rssboard.org/rss-profile
143+
https://validator.w3.org/feed/docs/rss2.html
144+
http://web.resource.org/rss/1.0/modules/content/
145+
146+
147+
ITUNES RSS LINKS
148+
149+
https://help.apple.com/itc/podcasts_connect/#/itc1723472cb
150+
https://help.apple.com/itc/podcasts_connect/#/itcbaf351599
151+
https://help.apple.com/itc/podcasts_connect/#/itca5b22233a
152+
https://gist.github.com/bcomnes/defb825462722d96da7b877e22dd7588
153+
https://www.apple.com/itunes/marketing-on-podcasts/identity-guidelines.html#messaging-and-style

generate-snapshot.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var fs = require('fs')
2+
var jsonfeedToRSS = require('./')
3+
var jsonfeedToRSSObj = require('./jsonfeed-to-rss-object')
4+
var testFeed = require('./test-feed.json')
5+
6+
const rssObj = jsonfeedToRSSObj(testFeed)
7+
const rssFeed = jsonfeedToRSS(testFeed)
8+
9+
fs.writeFileSync('snapshot.xml', rssFeed)
10+
fs.writeFileSync('snapshot.json', JSON.stringify(rssObj, null, ' '))
11+
12+
console.log('update snapshot snapshot.xml')

icon.png

3.97 KB
Loading

index.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
/**
3+
* Converts a parsed JSON feed to an rss feed document
4+
* @module jsonfeed-to-rss
5+
*/
6+
7+
const builder = require('xmlbuilder')
8+
const jsonfeedToRSSObject = require('./jsonfeed-to-rss-object')
9+
10+
/**
11+
* Convert a parsed JSON feed object into an rss 2.0.11 xml document
12+
*/
13+
module.exports = function jsonfeedToRSS (jsonfeed) {
14+
const feedObj = jsonfeedToRSSObject(jsonfeed)
15+
const feed = builder.create(feedObj, { encoding: 'utf-8', skipNullAttributes: true, skipNullValues: true })
16+
return feed.end({ pretty: true })
17+
}

jsonfeed-to-rss-object.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const packageInfo = require('./package.json')
2+
const generateTitle = require('./lib/generate-title')
3+
4+
module.exports = function jsonfeedToAtomObject (jf, opts) {
5+
const now = new Date()
6+
opts = Object.assign({
7+
language: 'en-us',
8+
copyright: ${now.getFullYear()} ${jf.author && jf.author.name ? jf.author.name : ''}`,
9+
managingEditor: getManagingEditor(jf),
10+
webMaster: getManagingEditor(jf),
11+
idIsPermalink: false,
12+
category: null,
13+
ttl: null, // TODO default on ttl
14+
skipHours: null,
15+
skipDays: null,
16+
relativeItemLinks: false // enable item level relative links
17+
}, opts)
18+
19+
// 2.0.11 http://www.rssboard.org/rss-specification
20+
// JSON Feed to rss mapping based off http://cyber.harvard.edu/rss/rss.html
21+
// and http://www.rssboard.org/rss-profile and
22+
// https://validator.w3.org/feed/docs/rss2.html
23+
24+
const { title, version, home_page_url: homePageURL, description } = jf
25+
if (version !== 'https://jsonfeed.org/version/1') throw new Error('jsonfeed-to-atom: JSON feed version 1 required')
26+
if (!title) throw new Error('jsonfeed-to-rss: missing title')
27+
if (!homePageURL) throw new Error('jsonfeed-to-rss: JSON feed missing home_page_url property')
28+
if (!description) throw new Error('jsonfeed-to-rss: JSON feed missing description property')
29+
30+
const rss = {
31+
title,
32+
link: homePageURL,
33+
description,
34+
language: opts.language,
35+
copyright: opts.copyright,
36+
managingEditor: opts.managingEditor,
37+
webMaster: opts.webMaster,
38+
pubDate: now.toUTCString(), // override with the newest pubdate thats less than now
39+
lastBuildDate: now.toUTCString(),
40+
category: opts.category, // no mapping, so leave as option
41+
generator: `${packageInfo.name} ${packageInfo.version} (${packageInfo.homepage})`,
42+
docs: 'http://www.rssboard.org/rss-specification',
43+
// TODO: cloud
44+
ttl: opts.ttl,
45+
image: jf.icon ? {
46+
url: jf.icon,
47+
link: homePageURL,
48+
title: title
49+
} : undefined,
50+
skipHours: opts.skipHours,
51+
skipDays: opts.skipDays
52+
}
53+
54+
if (jf.items) {
55+
let mostRecentlyUpdated = '0'
56+
rss.item = jf.items.map(item => {
57+
// capture mostRecentlyUpdated date, if any
58+
if (item.date_published && (item.date_published > mostRecentlyUpdated)) mostRecentlyUpdated = item.date_published
59+
if (item.date_modified && (item.date_modified > mostRecentlyUpdated)) mostRecentlyUpdated = item.date_modified
60+
61+
// Generate item object
62+
const rssItem = {
63+
title: generateTitle(item),
64+
link: item.external_url || item.url,
65+
description: {
66+
'#cdata': item.content_html || item.content_text
67+
},
68+
category: item.tags,
69+
guid: {
70+
'#text': item.id,
71+
'@isPermaLink': opts.idIsPermalink
72+
},
73+
pubDate: item.date_published
74+
}
75+
76+
if (item.attachments) {
77+
rssItem.enclosure = item.attachments.map(attachment => ({
78+
'@type': attachment.mime_type,
79+
'@url': attachment.url,
80+
'@length': attachment.size_in_bytes
81+
}))
82+
}
83+
if (opts.relativeItemLinks) rssItem['@xml:base'] = item.url
84+
return rssItem
85+
})
86+
// Replace pubdate date most recently updated or published
87+
if (mostRecentlyUpdated > '0') rss.pubDate = new Date(mostRecentlyUpdated).toUTCString()
88+
}
89+
90+
return {
91+
rss: {
92+
'@version': '2.0',
93+
'@xml:base': homePageURL,
94+
channel: rss
95+
}
96+
}
97+
}
98+
99+
function getManagingEditor (jf) {
100+
const {author} = jf
101+
if (!author && (!author.url || author.name)) return null
102+
let managingEditor = []
103+
if (author.url) managingEditor.push(author.url)
104+
if (author.name) managingEditor.push(`(${author.name})`)
105+
return managingEditor.join(' ')
106+
}

lib/generate-title.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const striptags = require('striptags')
2+
const trimRight = require('trim-right')
3+
const trimLeft = require('trim-left')
4+
5+
/**
6+
* Generate the required atom title for a JSON feed item
7+
*/
8+
module.exports = function generateTitle (item) {
9+
// A fairly arbitrary attempt at generating titles.
10+
// Ideally your JSON feed includes a title for every post. Most UI's will look strange without it
11+
if (item.title) return item.title
12+
if (item.summary) return truncate(cleanWhitespace(item.summary))
13+
if (item.content_text) return truncate(cleanWhitespace(item.content_text))
14+
if (item.content_html) return truncate(cleanWhitespace(striptags(item.content_html)))
15+
throw new Error('jsonfeed-to-atom: can\'t generate a title for entry ' + item.id)
16+
}
17+
18+
/**
19+
* Removes title unfriendly whitespace
20+
*/
21+
function cleanWhitespace (str) {
22+
return trimLeft(trimRight(str.split('\n')[0]))
23+
}
24+
25+
/**
26+
* Truncate a string to 100 characters with an ellipsis
27+
*/
28+
function truncate (string) {
29+
return string.length > 100 ? string.slice(0, 100) + '…' : string
30+
}

0 commit comments

Comments
 (0)