ผมอยากได้แบบนี้
การตั้งค่า newsletter อยู่ใน todo list นานเกินไป วันนี้จะทำให้เสร็จ ถ้าอยากสมัครรับได้ที่นี่
ผมไม่ชอบ newsletter ยาวๆ ที่ชอบคือ curated digests ของเนื้อหาน่าสนใจ เช่น RubyWeekly หรือ Wistia blog digest
ประกอบ digest เองใช้เวลามาก แต่อัตโนมัติเต็มๆ ไร้ความรู้สึกส่วนตัว ผมอยากได้ semi-automated process ต้องการ script ดึง blog posts ล่าสุดและ output HTML email ที่ปรับแต่งได้
มาสร้างกัน! โปรเจกต์เล็กๆ สนุกๆ มี user เดียวคือผมเอง!
แผนการ
Script ต้องทำ:
Fetch และ parse RSS feed ของ Honeybadger blog
Select articles ตาม category
Render articles ผ่าน ERB template
รันจาก command line พิมพ์ผลลัพธ์ไป STDOUT
Fetch และ Parse Feeds ใน Ruby
รู้ไหมว่า Ruby standard library มี module สำหรับ produce/consume RSS & ATOM feeds? ใช้งานง่ายมาก:
require 'rss' feed = RSS::Parser.parse('https://www.honeybadger.io/blog/feed/') feed.items.each do |item| puts item.title end
Module แม้แต่ fetch feed ให้เลย Service ดี!
จัดการ Categories
ไม่อยากส่ง link ที่ subscriber ไม่สนใจ กรอง articles by category แม้ Ruby RSS library มี categories method แต่คืน array ของ XML node objects ต้องการ category names จึง wrap RSS items ใน decorator class Article
ตอนนี้ select เฉพาะ category "How To" ได้ง่าย:
require 'rss' require 'delegate' class Article < SimpleDelegator def category_names categories.map &:content end end feed = RSS::Parser.parse('https://www.honeybadger.io/blog/feed/') articles = feed.items.map { |o| Article.new(o) }.select { |a| a.category_names.include?("How To") }
Render Template
Email ไม่มี markup มาก ใช้ ERB สำหรับ templating ใส่ template และ rendering code รวมใน class DigestView สำหรับ template เล็กๆ single-purpose แยกไฟล์ overkill
Output สุดท้ายพิมพ์ไป STDOUT ให้ pipe เข้า OSX pbcopy copy ไป clipboard แล้ว paste เข้า mail system
require 'rss' require 'delegate' require 'erb' class Article < SimpleDelegator def category_names categories.map &:content end end class DigestView attr_accessor :articles def initialize(articles) @articles = articles end def render ERB.new(template, 0, '>').result(binding) end def template %{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="https://www.w3.org/1999/xhtml"> <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head> <body> <h1>Headline: Replace me</h1> <p>Intro paragraph: Replace me.</p> <ul> <% for article in @articles %> <li> <a href="<%= article.link %>"> </li> <% end %> </ul> </body> </html>} end end feed = RSS::Parser.parse('https://www.honeybadger.io/blog/feed/') articles = feed.items.map { |o| Article.new(o) }.select { |a| a.category_names.include?("How To") } printf DigestView.new(articles).render
Output 长这样:
Output ของ blog digest generator
Future Work
ยังต้องทำอีกนิดก่อน production แต่มันส่วนใหญ่ customizations สำหรับ Honeybadger ไม่ค่อย useful ที่อื่น:
ทำ template สวย & test กับ email provider
เพิ่ม Google Analytics tracking parameters ให้ links
เพิ่ม post descriptions ใน template