diff --git a/packages/content-handler/src/index.ts b/packages/content-handler/src/index.ts index 8e32f1cb3..052642a50 100644 --- a/packages/content-handler/src/index.ts +++ b/packages/content-handler/src/index.ts @@ -26,6 +26,7 @@ import { ConvertkitHandler } from './newsletters/convertkit-handler' import { RevueHandler } from './newsletters/revue-handler' import { GhostHandler } from './newsletters/ghost-handler' import { parseHTML } from 'linkedom' +import { CooperPressHandler } from './newsletters/cooper-press-handler' const validateUrlString = (url: string) => { const u = new URL(url) @@ -73,6 +74,7 @@ const newsletterHandlers: ContentHandler[] = [ new ConvertkitHandler(), new RevueHandler(), new GhostHandler(), + new CooperPressHandler(), ] export const preHandleContent = async ( @@ -122,21 +124,37 @@ export const preParseContent = async ( return undefined } -export const handleNewsletter = async ( - input: NewsletterInput -): Promise => { +export const getNewsletterHandler = async (input: { + postHeader: string + from: string + unSubHeader: string + html: string +}): Promise => { const dom = parseHTML(input.html).document for (const handler of newsletterHandlers) { if (await handler.isNewsletter({ ...input, dom })) { - return handler.handleNewsletter(input) + return handler } } return undefined } +export const handleNewsletter = async ( + input: NewsletterInput +): Promise => { + const handler = await getNewsletterHandler(input) + if (handler) { + console.log('handleNewsletter', handler.name, input.title) + return handler.handleNewsletter(input) + } + + return undefined +} + module.exports = { preHandleContent, handleNewsletter, preParseContent, + getNewsletterHandler, } diff --git a/packages/content-handler/test/data/node-weekly-newsletter.html b/packages/content-handler/test/data/node-weekly-newsletter.html index 01173902f..397faa2e1 100644 --- a/packages/content-handler/test/data/node-weekly-newsletter.html +++ b/packages/content-handler/test/data/node-weekly-newsletter.html @@ -45,15 +45,15 @@ -
Plus Node 16.18.0, an IP info database, turning cron expressions into English, and 2FA with Twilio. |
+
Plus choosing the best Node Docker image to use, and a way to embed Node and V8 into JVM apps. |
- - + +

#​458 — October 13, 2022

Read on the Web

#​459 — October 20, 2022

Read on the Web

@@ -71,135 +71,175 @@
@@ -61,8 +61,8 @@
Together with  - - Userfront + + Memetria
Node.js Weekly
- + +
+ +

Node.js 19 Released

+
+

As an odd-numbered release, Node 19 will never become an 'active LTS' version, but sits as the 'current' release that gets all the tastiest new features until early 2023. It then reaches 'end of life' on June 1, 2023. "If you’re interested in getting access to features early, Node.js 19 is ready,” says Rafael Gonzaga of the core team.

+

New features this time around include:

+
    +
  • +

    Watch mode. An experimental --watch Nodemon-esque mode for 'watching' files and restarting the process when imported files change. (Node 18.11.0 (LTS) also gains this feature.)

    +
  • +
  • +

    HTTP KeepAlive is now enabled by default. It's always been an option but now it's set to true by default. The default duration is 5 seconds.

    +
  • +
  • +

    V8 10.7. Node bumps up to the latest version of the V8 engine. It's not a big jump but does introduce Intl.NumberFormat.

    +
  • +
  • +

    The WebCrypto API is now stable (with the exception of Ed25519, Ed448, X25519, and X448).

    +
  • +
  • +

    Some other dependency upgrades, such as to npm 8.19.2 and llhttp 8.1.0.

    +
  • +
+

As things stand, we're in the odd position of Node 18.x and 19.x both being the 'Current' release, but Node 18 begins its role as an LTS release on October 25. More info in the release policies here and the OpenJS Foundation has extra detail in its release post too.

+
+ +

The Node.js Team

+ +
+ +

Memetria: Secure, Scalable, Full-Featured Redis 7 Hosting — The latest Redis features, instrumented and scaled with the tools teams need as they grow.

+

Memetria sponsor

-

njt: Quick Navigation to npm Package Resources — Provides a rapid way to jump to various destinations related to npm packages (such as a project’s homepage, repo, issues, or even a package cost estimation). You can install it for use in your terminal, as a Chrome or Firefox search, via VS Code’s command palette (via LaunchX) or you can even use it directly on the Web here. – GitHub repo.

-

Alexander Kachkaev

+

Choosing the Best Node.js Docker Image — If you feel tempted to just throw FROM node into your Dockerfile, think again – there are other options to consider.

+

Liran Tal (Snyk)

+
+ +
+ +

▶  Effortless End-to-End Type-Safety with Phero — A demonstration of a library providing a type-safe TypeScript-based way to communicate between frontend and backend. GitHub repo.

+

Jasper Haggenburg

+
+
+

IN BRIEF:

+
-

Knip: Find Unused Files, Dependencies and Exports in TypeScript Projects — Knip’s creator tells us it’s Dutch for “cut” which is quite appropriate as it’s a new tool for trimming away things that aren’t being used in your project. If you just want to compare it to similar existing tools, there’s a handy comparison chart.

-

Lars Kappert

+

PowerShell, NPM Scripts, and Silently Dropped Arguments — If you’re a Powershell user and you’re finding that some arguments aren’t being passed to your Node scripts run through npm run, Lloyd explains what’s going on.

+

Lloyd Atkinson

-
- -

Node Authentication, Simplified — In this article, we lay out a new approach to authentication (plus access control & SSO) in Node.js applications.

-

Userfront sponsor

+
+ +

▶  A Next.js Crash Course — There are a lot of such videos, but this is a well recorded and up to date one so it might help you get the lay of the Next.js land if you’re just starting out with it. (2 hours 30 minutes)

+

Anson Foong

-

Node v16.18.0 (LTS) Released — Largely backported fixes and tweaks – no big headlines here.

-

Juan José (Node Core Team)

+

Web Scraping Google Maps with Puppeteer — We’d be surprised if you’d get away with this for long given there’s an official API, but it’s always interesting to see how it’s done.

+

Darshan Khandelwal

-

How to Write CommonJS Exports That Can Be Name-Imported from ESM — If you’ve ever got tangled up between using CommonJS and ES modules (I sure have!) Dr. Axel clears up a key cross-compatibility concern here.

-

Dr. Axel Rauschmayer

+

Your Step by Step Guide to Containerizing Node.js Web Applications

+

Snyk sponsor

-
- -

Adding Observability to Jest Tests — A look at how to get a bit more out of your Jest-based testing by keeping an eye on things.

-

Eliran Maman (Sprkl)

+
+

Sending UDP Messages without DNS Lookups +
Herman J. Radtke III +

-
- -

🔐  Node.js Authentication with Twilio Verify — If you’re happy using a third party service, bringing two-factor auth into your Express.js app needn’t be too hard. The author demonstrates the creation of a simple app that authenticates users using password-based authentication with an extra layer of OTPs (One-Time Passcodes) powered by Twilio’s Verify service.

-

Alexander Godwin

+
+

How Wix Uses Threading in Node Apps to Cut Kubernetes Pod Costs +
Jessica Wachtel (The New Stack) +

🛠 Code & Tools

-
- +
+ +
+
+ +
+ +

Javet 2.0.0: Embed Node and V8 in Java Apps — Lets you spin up V8 interpreters or full Node.js runtimes within JVM-based apps. There’s a slide presentation to sell you on the idea and demonstrate how the integration works. (The name Javet comes from Java, V, and Eight.)

+

Sam Cao

-

IP Index: A Fast IP Lookup Web Service + Library — Returns blacklist status, detects VPN/hosting and shows geo and ASN info. The repo gets updated every day too.

-

Mykhailo Gorianskyi

+

Editly 0.14.0: Declarative Command Line Video Editing — Brings Node and FFmpeg together to let you more programatically edit and construct videos instead of wrangling with arcane ffmpeg command line options.

+

Mikael Finstad

-

cRonstrue: Library to Convert cron Expressions into Human Readable Form — Love the project name! The idea is given something like */10 * * * *, it will return “Every 10 minutes”. No dependencies.

-

Brady Holt

-
- -
- -

Dynaboard: The Pro-Code Web App Builder Made for Developers — Build high performance public and private web apps in a collaborative — code forward — WYSIWYG environment.

-

Dynaboard sponsor

-
- -
- -

Whoiser: A WHOIS Client for Node.js — Given a domain name, TLD, or IP address, it queries online WHOIS databases for info.

-

Andrei Igna

-
- -
- -

Print Ready: A JS-Powered CLI for Converting HTML Into PDFs — Uses Paged.js to render your HTML file inside Puppeteer, then exports a PDF from Puppeteer.

-

Nicholas C. Zakas

-
- -
- -

Check HTML Links: A Fast Checker for Broken Links/References in HTML — An npm package you can run on static pages to find broken links in href, src, and srcset, and can process 500-1000 documents in seconds.

-

Modern Web

-
- -
- -

human-signals: Human-Friendly Process Signal Info — Basically a JavaScript object that contains info about the various POSIX signals (SIGHUP, SIGINT, et al.)

-

ehmicky

-
- -
- -

Need to Upgrade Your Node.js App? Hire Us to Do It for You

-

UpgradeJS․com - The JS Upgrade Service by OmbuLabs sponsor

+

Send Email, Push and SMS with Smart Routing, with Just 8 Lines of Code — Are you stuck using marketing tools like salesforce to contact your users? Send notifications from right within your application using the Courier API.

+

Courier.com sponsor

-

Flyweight: A Brand New ORM for SQLite — Early days but provides some extra abstraction around SQLite you might appreciate. -
Andrew Jones +

lady-gg: Simple TypeScript gRPC Client +
Mish Ushakov

-
+
    -
  • -

    AdminJS 6.4
    - ↳ Admin panel / UI for Node apps.

    +
  • +

    Awilix 8.0
    + ↳ Inversion of Control (IoC) container for Node.

  • -
  • -

    Faker 7.6
    - ↳ Generate large amounts of fake data.

    +
  • +

    Nx 15.0
    + ↳ Smart, fast and extensible build system.

  • -
  • -

    Middy 3.6
    - ↳ Node middleware engine for AWS Lambda.

    +
  • +

    Nightwatch 2.4
    + ↳ End-to-end testing framework, now with improved component testing support.

  • -
  • -

    quagga2 1.7.5
    - ↳ Advanced barcode scanning for browser and Node.

    +
  • +

    lowdb 4.0
    + ↳ Simple to use local JSON database.

  • -
  • -

    node-jira-client 8.2
    - ↳ Node wrapper for Jira's REST API.

    +
  • +

    Prisma 4.5
    + ↳ Next-generation ORM. There's a lot new here.

  • -
  • -

    RedisSMQ 7.1.1
    - ↳ High-performance Redis message queue.

    +
  • +

    PSD 0.3
    + ↳ Zero-dependency PSD/Photoshop file parser.

    +
  • +
  • +

    fdir 5.3
    + ↳ Performance-oriented directory crawler and globbing library.

    +
  • +
  • +

    google-translate 2.0
    + ↳ Consume Google's Translate API.

    +
  • +
  • +

    Mercurius 11.1
    + ↳ Implement GraphQL servers with Fastify.

    +
  • +
  • +

    Mongoist 2.5.6
    + ↳ MongoDB driver built with async/await in mind.

    +
  • +
  • +

    Mojo.js 1.7
    + ↳ Web framework inspired by Perl's Mojolicious.

@@ -208,13 +248,13 @@

💻 Jobs

-

Full-Stack Engineer (NYC / Remote) — 100M+ devices, 100B+ API calls. Radar is looking for Product Engineers to build geospatial dev tools. -
Radar +

Doppler - A SecretOps Platform Built by Developers for Developers — Doppler’s looking for Sr. Full-Stack Engineers to help shape the future of security devtools. TypeScript, React, Express, and Go, apply here. +
Doppler

-

Find Tech Jobs with Hired — Create a profile on Hired to connect with hiring managers at growing startups and Fortune 500 companies. It's free for job-seekers. +

Find Tech Jobs with Hired — Create a profile on Hired to connect with hiring managers at growing startups and Fortune 500 companies. It's free for job-seekers.
Hired

@@ -233,9 +273,7 @@

Published by Cooper Press Ltd.
Fairfield Enterprise Centre, Louth, LN11 0LS, United Kingdom

-

Cancel your subscription or change your address.

- - +

Cancel your subscription or change your address.

@@ -246,5 +284,5 @@ -n +n diff --git a/packages/content-handler/test/newsletter.test.ts b/packages/content-handler/test/newsletter.test.ts index 11777c420..d8eb84d00 100644 --- a/packages/content-handler/test/newsletter.test.ts +++ b/packages/content-handler/test/newsletter.test.ts @@ -13,9 +13,9 @@ import { generateUniqueUrl } from '../src/content-handler' import fs from 'fs' import { BeehiivHandler } from '../src/newsletters/beehiiv-handler' import { ConvertkitHandler } from '../src/newsletters/convertkit-handler' -import { parseHTML } from 'linkedom' import { GhostHandler } from '../src/newsletters/ghost-handler' import { CooperPressHandler } from '../src/newsletters/cooper-press-handler' +import { getNewsletterHandler } from '../src' chai.use(chaiAsPromised) chai.use(chaiString) @@ -93,104 +93,95 @@ describe('Newsletter email test', () => { }) }) - describe('isProbablyNewsletter', () => { - it('returns true for substack newsletter', async () => { + describe('getNewsletterHandler', () => { + it('returns substack newsletter handler', async () => { const html = load('./test/data/substack-forwarded-newsletter.html') - const dom = parseHTML(html).document - await expect( - new SubstackHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(SubstackHandler) }) - it('returns true for private forwarded substack newsletter', async () => { + + it('returns SubstackHandler for private forwarded substack newsletter', async () => { const html = load( './test/data/substack-private-forwarded-newsletter.html' ) - const dom = parseHTML(html).document - await expect( - new SubstackHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(SubstackHandler) }) - it('returns false for substack welcome email', async () => { + + it('returns undefined for substack welcome email', async () => { const html = load('./test/data/substack-forwarded-welcome-email.html') - const dom = parseHTML(html).document - await expect( - new SubstackHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.false + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.undefined }) - it('returns true for beehiiv.com newsletter', async () => { + + it('returns BeehiivHandler for beehiiv.com newsletter', async () => { const html = load('./test/data/beehiiv-newsletter.html') - const dom = parseHTML(html).document - await expect( - new BeehiivHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(BeehiivHandler) }) - it('returns true for milkroad newsletter', async () => { + + it('returns BeehiivHandler for milkroad newsletter', async () => { const html = load('./test/data/milkroad-newsletter.html') - const dom = parseHTML(html).document - await expect( - new BeehiivHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(BeehiivHandler) }) - it('returns true for ghost newsletter', async () => { + + it('returns GhostHandler for ghost newsletter', async () => { const html = load('./test/data/ghost-newsletter.html') - const dom = parseHTML(html).document - await expect( - new GhostHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(GhostHandler) }) - it('returns true for convertkit newsletter', async () => { + + it('returns ConvertkitHandler for convertkit newsletter', async () => { const html = load('./test/data/convertkit-newsletter.html') - const dom = parseHTML(html).document - await expect( - new ConvertkitHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(ConvertkitHandler) }) - it('returns true for node-weekly newsletter', async () => { + + it('returns CooperPressHandler for node-weekly newsletter', async () => { const html = load('./test/data/node-weekly-newsletter.html') - const dom = parseHTML(html).document - await expect( - new CooperPressHandler().isNewsletter({ - dom, - postHeader: '', - from: '', - unSubHeader: '', - }) - ).to.eventually.be.true + const handler = await getNewsletterHandler({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + expect(handler).to.be.instanceOf(CooperPressHandler) }) }) @@ -293,18 +284,18 @@ describe('Newsletter email test', () => { before(() => { nock('https://u25184427.ct.sendgrid.net') .head( - '/ls/click?upn=MnmHBiCwIPe9TmIJeskmA7mFdqmsIs-2B5Xs-2FNpSIs56o0z9xhskaXR4aYohHPLtwRHfml_vVXscVLXlj5UtQe3aqo5RMTdTq2PepdZjP86UOmA8nxtQVfuqJiLh7Fio3fEtt5ouN4IH56AfszUQpxY-2FQ233kp0bjSZhBBVWAB43dgKumQkDW-2BxDFnQIUpvhmEgzSJq-2FMRG00GM7fkZVuPU-2BX8cdg8AGRHUU9Qhw6W67XEMkJVygTdm70Mo9ypNi8N33hgmhM3F6un9s7p1K1Gq-2FunslA-3D-3D' + '/ls/click?upn=MnmHBiCwIPe9TmIJeskmA7mFdqmsIs-2B5Xs-2FNpSIs56obSPDXnoBEjufvIqRCEJUf5Uqg_vVXscVLXlj5UtQe3aqo5RMTdTq2PepdZjP86UOmA8nyFE700vlj1-2FK27spZiPEiEkDU3SIXWGeoiU60KFhM-2B-2Bxx5yiL8KKbAV6oFceRi8O1gMc3mdwg5D8FaaM3PublaX24iAcVbn99PzxJaPuVrU6xDWbRovw2UgGTIoEI-2BBO-2B0qzi2wv5c6yJTkUGZOcsJ6xGLXO1BO-2BHSbyZMZV4NMw-3D-3D' ) .reply(301, undefined, { - Location: 'https://nodeweekly.com/issues/458', + Location: 'https://nodeweekly.com/issues/459', }) - nock('https://nodeweekly.com').head('/issues/458').reply(200, '') + nock('https://nodeweekly.com').head('/issues/459').reply(200, '') }) it('gets the URL from the header', async () => { const html = load('./test/data/node-weekly-newsletter.html') const url = await new CooperPressHandler().findNewsletterUrl(html) - expect(url).to.startWith('https://nodeweekly.com/issues/458') + expect(url).to.startWith('https://nodeweekly.com/issues/459') }) }) })