Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gatsby-plugin-feed feature request - dynamic feeds #3489

Closed
agarrharr opened this issue Jan 12, 2018 · 5 comments
Closed

gatsby-plugin-feed feature request - dynamic feeds #3489

agarrharr opened this issue Jan 12, 2018 · 5 comments

Comments

@agarrharr
Copy link
Contributor

agarrharr commented Jan 12, 2018

@nicholaswyoung

Description

As a user of gatsby-plugin-feed, I would like to be able to dynamically generate feeds. For example, I have a website that has multiple podcasts. I want to generate an rss feed for each of them automatically based on the data from Contentful (at podcastname/feed.rss). I also want a master feed at master.rss.

Environment

Gatsby version: ^1.9.145
Node.js version: v8.7.0
Operating System: macOS High Sierra 10.13.2

Example

  plugins: [
    {
      resolve: `gatsby-plugin-feed`,
      options: {
        query: `
        {
          ...
        }
      `,
        feeds: ({query}) => {
          ...Here is where I would like to dynamically generate an array of feeds to return...
        },

I have actually done this with a small modification to the plugin, which I can submit as a pull request.

Here is an example of what I did.

// gatsby-config.js
require('dotenv').config();
const { DateTime } = require('luxon');
var humanizeList = require('humanize-list');

const serialize = podcast =>
  podcast.episode
    ? podcast.episode.map(episode => ({
        title: episode.name,
        description:
          episode.shortDescription || `Episode ${episode.episodeNumber}`,
        url: episode.audioUrl,
        guid: episode.id,
        author: episode.hosts.map(h => h.name, {
          oxfordComma: true,
        }),
        enclosure: {
          url: episode.audioUrl,
          length: episode.audioLength,
          type: 'audio/mp3',
        },
        custom_elements: [
          {
            pubDate: DateTime.fromISO(episode.publicationDate).toHTTP(),
          },
          {
            'itunes:author': humanizeList(
              episode.hosts.map(h => h.name, { oxfordComma: true })
            ),
          },
          {
            'itunes:subtitle': episode.shortDescription,
          },
          {
            'itunes:summary': episode.shortDescription,
          },
          {
            'content:encoded': `<p>${episode.shortDescription}</p>${
              episode.fields ? episode.fields.showNotesFormatted : ``
            }`,
          },
          { 'itunes:explicit': 'clean' },
          {
            'itunes:image': {
              _attr: {
                href: episode.image
                  ? episode.image.file.url
                  : podcast.image ? podcast.image.file.url : ``,
              },
            },
          },
          { 'itunes:duration': episode.duration },
        ],
      }))
    : [];

module.exports = {
  siteMetadata: {
    title: `Orbit FM`,
    description: `Orbit FM is a place with podcasts.`,
    siteUrl: `http://www.orbit.fm`,
    owner: `Orbit FM`,
    ownerEmail: `[email protected]`,
    categories: ['Technology', 'Education'],
  },
  plugins: [
    {
      resolve: `gatsby-plugin-podcast-feed`,
      options: {
        query: `
        {
          site {
            siteMetadata {
              title
              description
              siteUrl
              owner
              ownerEmail
              categories
            }
          }
          allContentfulPodcast {
            edges {
              node {
                id
                image {
                  file {
                    url
                  }
                }
                fields {
                  slug
                }
                episode {
                  id
                  episodeNumber
                  audioUrl
                  name
                  shortDescription
                  publicationDate
                  audioLength
                  duration
                  hosts {
                    name
                  }
                  fields {
                    showNotesFormatted
                  }
                }
              }
            }
          }
        }
      `,
        setup: ({
          query: { site: { siteMetadata }, allContentfulPodcast },
        }) => {
          const podcast = allContentfulPodcast.edges[0].node;
          return {
            title: siteMetadata.title,
            description: siteMetadata.description,
            feed_url: `${siteMetadata.siteUrl}/${podcast.fields.slug}/feed.rss`,
            site_url: siteMetadata.siteUrl,
            image_url: podcast.image ? podcast.image.file.url : ``,
            managingEditor: `${siteMetadata.ownerEmail} (${
              siteMetadata.owner
            })`,
            webMaster: `${siteMetadata.ownerEmail} (${siteMetadata.owner})`,
            copyright: `${new Date().getFullYear()} ${siteMetadata.owner}`,
            language: 'en',
            categories: siteMetadata.categories,
            pubDate: DateTime.fromISO(new Date()).toHTTP(),
            ttl: '60',
            custom_namespaces: {
              itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd',
            },
            custom_elements: [
              { 'itunes:subtitle': siteMetadata.description },
              { 'itunes:author': siteMetadata.owner },
              { 'itunes:explicit': 'clean' },
              {
                'itunes:summary': siteMetadata.description,
              },
              {
                'itunes:owner': [
                  { 'itunes:name': siteMetadata.owner },
                  { 'itunes:email': siteMetadata.ownerEmail },
                ],
              },
              {
                'itunes:image': {
                  _attr: {
                    href: podcast.image ? podcast.image.file.url : ``,
                  },
                },
              },
              ...siteMetadata.categories.map(c => ({
                'itunes:category': {
                  _attr: {
                    text: c,
                  },
                },
              })),
            ],
          };
        },
        feeds: ({
          query: { site: { siteMetadata }, allContentfulPodcast },
        }) => [
          ...allContentfulPodcast.edges.map(({ node }, i) => ({
            serialize: ({ query: { site, allContentfulPodcast } }) =>
              serialize(allContentfulPodcast.edges[i].node),
            output: `${node.fields.slug}/feed.rss`,
          })),
          {
            serialize: ({ query: { site, allContentfulPodcast } }) =>
              serialize({
                image: {
                  file: {
                    url: 'testImage',
                  },
                },
                fields: {
                  slug: 'master',
                },
                episode: allContentfulPodcast.edges.reduce(
                  (a, { node }) => [...a, ...(node.episode || [])],
                  []
                ),
              }),
            output: `master.rss`,
          },
        ],
      },
    },

    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-emotion`,
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography.js`,
      },
    },
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
      },
    },
  ],
};
@agarrharr
Copy link
Contributor Author

agarrharr commented Jan 12, 2018

Here's what I changed in the plugin:

src/gatsby-node.js

-  for (let f of options.feeds) {
+  // options.feeds could be an array or a function
+  const newFeeds = typeof options.feeds === 'function' ? options.feeds({query: options.query}) : options.feeds;
+
+  for (let f of newFeeds) {

@secretfader
Copy link

@agarrharr The API isn't something I'm currently open to adding. I haven't read over all of the code above in detail, but it strikes me as needlessly complex. I think we can get similar functionality out of a simpler config.

Would you be open to other approaches?

@secretfader
Copy link

Now, if you want to participate in the discussion that is happening over in #3413, where we're discussing how to improve configuration, your contributions are welcome there. But as this stands, it's duplicated work and an API I dislike as well.

@agarrharr
Copy link
Contributor Author

@nicholaswyoung I agree. It's very complex (the code I had to write for my site at least). But most of the code is just to add all of the relevant items the are needed for podcast rss feeds.

There may be a better way, but I can't think of it right now. The code for the plugin is pretty simple, but I'm not sure if it would introduce any bugs.

Another idea I had was to make a plugin specific to podcast feeds.

@KyleAMathews
Copy link
Contributor

Due to the high volume of issues, we're closing out older ones without recent activity. Please open a new issue if you need help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants