From 1fda6946bb40cfb947369b269f0db3172141164a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 18 Oct 2022 09:45:33 +0800 Subject: [PATCH] Save newsletters hosted by cooper-press.com correctly --- .../src/newsletters/cooper-press-handler.ts | 39 +++ .../test/data/node-weekly-newsletter.html | 250 ++++++++++++++++++ .../content-handler/test/newsletter.test.ts | 36 +++ 3 files changed, 325 insertions(+) create mode 100644 packages/content-handler/src/newsletters/cooper-press-handler.ts create mode 100644 packages/content-handler/test/data/node-weekly-newsletter.html diff --git a/packages/content-handler/src/newsletters/cooper-press-handler.ts b/packages/content-handler/src/newsletters/cooper-press-handler.ts new file mode 100644 index 000000000..1740c39f8 --- /dev/null +++ b/packages/content-handler/src/newsletters/cooper-press-handler.ts @@ -0,0 +1,39 @@ +import { ContentHandler } from '../content-handler' +import { parseHTML } from 'linkedom' + +export class CooperPressHandler extends ContentHandler { + constructor() { + super() + this.name = 'cooper-press' + } + + findNewsletterHeaderHref(dom: Document): string | undefined { + const readOnline = dom.querySelectorAll('a') + let res: string | undefined = undefined + readOnline.forEach((e) => { + if (e.textContent === 'Read on the Web') { + res = e.getAttribute('href') || undefined + } + }) + return res + } + + async isNewsletter(input: { + postHeader: string + from: string + unSubHeader: string + html: string + }): Promise { + const dom = parseHTML(input.html).document + return Promise.resolve( + dom.querySelectorAll('a[href*="cooperpress.com"]').length > 0 + ) + } + + async parseNewsletterUrl( + postHeader: string, + html: string + ): Promise { + return this.findNewsletterUrl(html) + } +} diff --git a/packages/content-handler/test/data/node-weekly-newsletter.html b/packages/content-handler/test/data/node-weekly-newsletter.html new file mode 100644 index 000000000..01173902f --- /dev/null +++ b/packages/content-handler/test/data/node-weekly-newsletter.html @@ -0,0 +1,250 @@ + + + + + + + + + + + + +
Plus Node 16.18.0, an IP info database, turning cron expressions into English, and 2FA with Twilio. |
+ + + +
+
+ + + +

#​458 — October 13, 2022

Read on the Web

+ + +
+ + + + + +
Together with  + + Userfront + +
+
+
Node.js Weekly
+
+ +
+ +
+ +

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

+
+ +
+ +

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

+
+ +
+ +

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

+

Userfront sponsor

+
+ +
+ +

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

+

Juan José (Node Core Team)

+
+ +
+ +

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

+
+ +
+ +

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)

+
+ +
+ +

🔐  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

+
+
+ +

🛠 Code & Tools

+
+ +
+ +
+ +

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

+
+ +
+ +

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

+
+ +
+

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

+
+
+
    +
  • +

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

    +
  • +
  • +

    Faker 7.6
    + ↳ Generate large amounts of fake data.

    +
  • +
  • +

    Middy 3.6
    + ↳ Node middleware engine for AWS Lambda.

    +
  • +
  • +

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

    +
  • +
  • +

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

    +
  • +
  • +

    RedisSMQ 7.1.1
    + ↳ High-performance Redis message queue.

    +
  • +
+
+
+ +

💻 Jobs

+ +
+

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

+
+ +
+

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 +

+
+
+
+
+ +
+
+ + +n + diff --git a/packages/content-handler/test/newsletter.test.ts b/packages/content-handler/test/newsletter.test.ts index d4a1f2562..8393bce0e 100644 --- a/packages/content-handler/test/newsletter.test.ts +++ b/packages/content-handler/test/newsletter.test.ts @@ -15,6 +15,7 @@ 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' chai.use(chaiAsPromised) chai.use(chaiString) @@ -179,6 +180,17 @@ describe('Newsletter email test', () => { }) ).to.eventually.be.true }) + it('returns true for node-weekly newsletter', async () => { + const html = load('./test/data/node-weekly-newsletter.html') + await expect( + new CooperPressHandler().isNewsletter({ + html, + postHeader: '', + from: '', + unSubHeader: '', + }) + ).to.eventually.be.true + }) }) describe('findNewsletterUrl', async () => { @@ -289,6 +301,30 @@ describe('Newsletter email test', () => { expect(url).to.startWith('https://www.openml.fyi/2022-10-14/') }).timeout(10000) }) + + context('when email is from cooper press', () => { + before(() => { + nock('https://u25184427.ct.sendgrid.net') + .head( + '/ls/click?upn=MnmHBiCwIPe9TmIJeskmA7mFdqmsIs-2B5Xs-2FNpSIs56o0z9xhskaXR4aYohHPLtwRHfml_vVXscVLXlj5UtQe3aqo5RMTdTq2PepdZjP86UOmA8nxtQVfuqJiLh7Fio3fEtt5ouN4IH56AfszUQpxY-2FQ233kp0bjSZhBBVWAB43dgKumQkDW-2BxDFnQIUpvhmEgzSJq-2FMRG00GM7fkZVuPU-2BX8cdg8AGRHUU9Qhw6W67XEMkJVygTdm70Mo9ypNi8N33hgmhM3F6un9s7p1K1Gq-2FunslA-3D-3D' + ) + .reply(302, undefined, { + Location: 'https://nodeweekly.com/issues/458', + }) + .get('/issues/458') + .reply(200, '') + }) + + after(() => { + nock.restore() + }) + + 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') + }).timeout(10000) + }) }) describe('generateUniqueUrl', () => {