diff --git a/package-lock.json b/package-lock.json index 33d2bef..a105671 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "eslint-config-next": "13.1.4", "focus-visible": "^5.2.0", "i": "^0.3.7", + "mp3-duration": "^1.1.0", "next": "^13.1.7-canary.12", "nodemailer": "^6.9.1", "podcast": "^2.0.1", @@ -2852,6 +2853,19 @@ "node": ">=8" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/bluebird-co": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bluebird-co/-/bluebird-co-2.2.0.tgz", + "integrity": "sha512-p8EZTlGgWO6lUZkWbtmK90zhKNGy4CY9rLUUlI6aApZH0U7lAaJTb/KxkH2nHjKNpktN+iWtikXzAM+Z2dQJ5g==", + "peerDependencies": { + "bluebird": "^3.x" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6491,6 +6505,18 @@ "node": ">=10" } }, + "node_modules/mp3-duration": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mp3-duration/-/mp3-duration-1.1.0.tgz", + "integrity": "sha512-5zO6ZLEHEPb1Da5p+et7zsMQ0pt0x2YCZLyUPayBqLMhLPQt3fFAXxvCyznZZQnhXCeIX99tXhvQocm8+VmpvA==", + "dependencies": { + "bluebird": "^3.5.1", + "bluebird-co": "^2.2.0" + }, + "engines": { + "node": ">=0.11.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -11060,6 +11086,17 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bluebird-co": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bluebird-co/-/bluebird-co-2.2.0.tgz", + "integrity": "sha512-p8EZTlGgWO6lUZkWbtmK90zhKNGy4CY9rLUUlI6aApZH0U7lAaJTb/KxkH2nHjKNpktN+iWtikXzAM+Z2dQJ5g==", + "requires": {} + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -13602,6 +13639,15 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, + "mp3-duration": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mp3-duration/-/mp3-duration-1.1.0.tgz", + "integrity": "sha512-5zO6ZLEHEPb1Da5p+et7zsMQ0pt0x2YCZLyUPayBqLMhLPQt3fFAXxvCyznZZQnhXCeIX99tXhvQocm8+VmpvA==", + "requires": { + "bluebird": "^3.5.1", + "bluebird-co": "^2.2.0" + } + }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", diff --git a/package.json b/package.json index b02e529..b4913b3 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "eslint-config-next": "13.1.4", "focus-visible": "^5.2.0", "i": "^0.3.7", + "mp3-duration": "^1.1.0", "next": "^13.1.7-canary.12", "nodemailer": "^6.9.1", "podcast": "^2.0.1", diff --git a/src/data/episodes.js b/src/data/episodes.js index 4a091ab..1d3d55d 100644 --- a/src/data/episodes.js +++ b/src/data/episodes.js @@ -2,6 +2,8 @@ import * as srtparsejs from "srtparsejs"; import fs from 'fs'; import path from 'path'; +import db from '@/db'; + import { extractFromXml } from '@extractus/feed-extractor' export const PAGE_SIZE = 15; @@ -210,7 +212,7 @@ Object.entries(episodeExtra).forEach(([id, { slug }]) => { slugToEpisode[slug] = id }) -export async function getEpisodes() { +export async function getEpisodesReal() { const feedRes = await fetch('https://feeds.buzzsprout.com/1764837.rss', { next: { revalidate: 60 * 10 } }); const feedString = await feedRes.text() /* const feedString = fs.readFileSync('./feed.rss').toString() */ @@ -270,14 +272,38 @@ export async function getEpisodes() { slug: episodeExtra[id].slug, transcript: episodeExtra[id]?.transcript, audio: { - src: `https://pdcn.co/e/www.buzzsprout.com/1764837/${id.split('Buzzsprout-')[1]}.mp3`, - type: 'audio/mpeg', - }, + src: `https://pdcn.co/e/www.buzzsprout.com/1764837/${id.split('Buzzsprout-')[1]}.mp3`, + type: 'audio/mpeg', + }, })), ...feedEntries] : feedEntries; } +export async function getEpisodes() { + const dbEpisodes = await db.all('select * from episodes order by number desc;'); + return dbEpisodes.map(({ title, pub_date, summary: description, content, slug, duration, filename, number, episode_type, buzzsprout_id, youtube_url, transcript_filename }) => { + const filepath = path.join(process.cwd(), 'public', 'files', 'episodes', filename); + return { + num: number, + id: buzzsprout_id, + title, + description, + content, + published: pub_date, + chapters: [], + youtube: youtube_url, + slug, + transcript: transcript_filename, + audio: { + src: '', + type: '', + length: '', + }, + }; + }); +} + export async function getEpisode({ episodeSlug }) { const episodes = await getEpisodes() const episodeId = slugToEpisode[episodeSlug] || episodeSlug diff --git a/src/db.js b/src/db.js index 1a17013..8d24d13 100644 --- a/src/db.js +++ b/src/db.js @@ -44,7 +44,15 @@ pub_date text, youtube_url text, transcript_filename text )`] - } + }, + { + key: 5, + name: 'create table feed', + sql: [`create table feed ( +id integer primary key autoincrement, +last_build_date text + )`] + }, ]; const checkForMigrationsSql = `select key from migrations where run='True' order by key`; @@ -65,21 +73,21 @@ async function runMigrations(db) { console.log('Migrations to run:', toRun.map(({ name }) => name)); await db.exec(toRun.reduce((prev, { sql, key }) => `${prev} ${sql.join(';')} ; insert into migrations (key, run) values (${key}, 'True') ;`, '')); console.log('migrations run'); - /* db.all(checkForMigrationsSql, (err, rows) => { - * console.log('xx') - * const runMigrations = rows.map(({ key }) => key); - * console.log(runMigrations); - * let toRun = []; - * migrations.forEach(({ key, name, sql }) => { - * if (!runMigrations.includes(key)) { - * toRun.push({ key, name, sql }); - * } - * }); - * console.log('Migrations to run:', toRun.map(({ name }) => name)); - * db.exec(toRun.reduce((prev, { sql, key }) => `${prev} ${sql.join(';')} ; insert into migrations (key, run) values (${key}, 'True') ;`, ''), () => { - * console.log('migrations run'); - * }); - * }); */ + /* db.all(checkForMigrationsSql, (err, rows) => { + * console.log('xx') + * const runMigrations = rows.map(({ key }) => key); + * console.log(runMigrations); + * let toRun = []; + * migrations.forEach(({ key, name, sql }) => { + * if (!runMigrations.includes(key)) { + * toRun.push({ key, name, sql }); + * } + * }); + * console.log('Migrations to run:', toRun.map(({ name }) => name)); + * db.exec(toRun.reduce((prev, { sql, key }) => `${prev} ${sql.join(';')} ; insert into migrations (key, run) values (${key}, 'True') ;`, ''), () => { + * console.log('migrations run'); + * }); + * }); */ }; const createMigrationTable = `create table migrations ( diff --git a/src/pages/api/feed.rss.js b/src/pages/api/feed.rss.js index 11b0fcc..e9b303d 100644 --- a/src/pages/api/feed.rss.js +++ b/src/pages/api/feed.rss.js @@ -4,26 +4,66 @@ import fs from 'fs'; import db from '@/db'; import { Podcast } from 'podcast'; -/* import mp3Duration from 'mp3-duration'; */ +import mp3Duration from 'mp3-duration'; import { getEpisodes } from '@/data/episodes'; +async function syncEpisodes() { + const episodes = await getEpisodes(); + let newEpisodes = false; + + const dbUpdates = episodes.map(async ({ title, published, description, content, slug, audio: { src, length }, num, id, youtube }) => { + const filename = `0${num}-mixed.mp3`; // TODO auto-add the 0 prefix + const filepath = path.join(process.cwd(), 'public', 'files', 'episodes', filename); + if (fs.existsSync(filepath)) { + const existsInDb = await db.get('select id from episodes where number=?', num); + if (!existsInDb) { + newEpisodes = true; + console.log('adding to db'); + const duration = Math.round(await mp3Duration(filepath)); + await db.run('insert into episodes (number, content, summary, slug, season, episode, filename, duration, title, episode_type, buzzsprout_id, buzzsprout_url, pub_date, youtube_url) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);', + num, + content, + description, + slug, + 1, + num, + filename, + duration, + title, + 'full', + id, + src, + published, + youtube); + console.log('added to db', num); + } + } + }) + // if (newEpisodes) { + // TODO upsert: "insert into feed (last_build_date) VALUES(datetime('now'),datetime('now', 'localtime'));" + // } + await Promise.all(dbUpdates); + return newEpisodes; +}; + export default async function handler(req, res) { if (req.method === 'GET') { + await syncEpisodes(); const feed = new Podcast({ title: 'The React Show (Reactors)', - description: "Discussions about React, JavaScript, and web development by React experts with a focus on diving deep into learning React and discussing what it's like to work within the React industry.", + description: "Premium subscription to The React Show. Discussions about React, JavaScript, and web development by React experts with a focus on diving deep into learning React and discussing what it's like to work within the React industry.", feedUrl: 'https://www.thereactshow.com/api/feed.rss', siteUrl: 'https://www.thereactshow.com', imageUrl: 'https://storage.buzzsprout.com/variants/d1tds1rufs5340fyq9mpyzo491qp/5cfec01b44f3e29fae1fb88ade93fc4aecd05b192fbfbc2c2f1daa412b7c1921.jpg', - author: 'Owl Creek Studio', - copyright: '© 2023 Owl Creek Studio', + author: 'The React Show', + copyright: '© 2023 Owl Creek', language: 'en', categories: ['Technology','Education','Business'], pubDate: 'May 20, 2012 04:00:00 GMT', ttl: 60, - itunesAuthor: 'Owl Creek Studio', - itunesOwner: { name: 'Owl Creek Studio' }, + itunesAuthor: 'The React Show', + itunesOwner: { name: 'The React Show' }, itunesExplicit: false, itunesCategory: [{ text: 'Technology' @@ -37,9 +77,8 @@ export default async function handler(req, res) { itunesImage: 'https://storage.buzzsprout.com/variants/d1tds1rufs5340fyq9mpyzo491qp/5cfec01b44f3e29fae1fb88ade93fc4aecd05b192fbfbc2c2f1daa412b7c1921.jpg' }); - const episodes = await getEpisodes(); - episodes.forEach(({ title, published, description, content, slug, audio: { src, length }, num, id, youtube }) => { - const filename = `0${num}-mixed.mp3`; // TODO auto-add the 0 prefix + const dbEpisodes = await db.all('select * from episodes order by number desc;'); + dbEpisodes.forEach(({ title, pub_date, summary: description, content, slug, duration, filename, number, episode_type }) => { const filepath = path.join(process.cwd(), 'public', 'files', 'episodes', filename); if (fs.existsSync(filepath)) { feed.addItem({ @@ -47,11 +86,15 @@ export default async function handler(req, res) { description: content, content, url: `https://www.thereactshow.com/podcast/${slug}`, - date: published, + date: pub_date, + itunesTitle: title, itunesExplicit: false, itunesSummary: description, - /* itunesDuration: await mp3Duration(filepath), TODO */ - itunesDuration: 1234, + itunesDuration: duration, + itunesAuthor: 'The React Show', + itunesSeason: 1, + itunesEpisode: number, + itunesEpisodeType: episode_type, enclosure : { url: `https://www.thereactshow.com/files/episodes/${filename}`, file: filepath @@ -60,33 +103,6 @@ export default async function handler(req, res) { } }) - const dbUpdates = episodes.map(async ({ title, published, description, content, slug, audio: { src, length }, num, id, youtube }) => { - const filename = `0${num}-mixed.mp3`; // TODO auto-add the 0 prefix - const filepath = path.join(process.cwd(), 'public', 'files', 'episodes', filename); - if (fs.existsSync(filepath)) { - const existsInDb = await db.get('select id from episodes where number=?', num); - if (!existsInDb) { - console.log('adding to db'); - await db.run('insert into episodes (number, content, summary, slug, season, episode, filename, title, episode_type, buzzsprout_id, buzzsprout_url, pub_date, youtube_url) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);', - num, - content, - description, - slug, - 1, - num, - filename, - title, - 'episodic', - id, - src, - published, - youtube); - console.log('added to db', num); - } - } - }) - await Promise.all(dbUpdates); - const xml = feed.buildXml(); res.setHeader('Content-Type', 'text/xml; charset=utf-8');