Bagaimana saya mengotomatiskan buletin Substack saya dengan konten dari blog saya

Bagaimana saya mengotomatiskan buletin Substack saya dengan konten dari blog saya


Bagaimana saya mengotomatiskan buletin Substack saya dengan konten dari blog saya

19 November 2025

Saya mengirimkan buletin Substack mingguan saya pagi ini dan mengambil kesempatan untuk merekam video YouTube yang menunjukkan proses saya dan menjelaskan berbagai komponen yang membuatnya berfungsi. Ada sebuah banyak dari lakban digital yang terlibat, mengambil konten dari Django+Heroku+PostgreSQL ke GitHub Actions ke SQLite+Datasette+Fly.io ke JavaScript+Observable dan akhirnya ke Substack.

Proses intinya sama seperti yang saya jelaskan pada tahun 2023. Saya memiliki buku catatan Observable bernama blog-to-newsletter yang mengambil konten dari database blog saya, memfilter apa pun yang telah ada di buletin sebelumnya, memformat apa yang tersisa sebagai HTML dan menawarkan tombol besar “Salin buletin teks kaya ke papan klip”.

Tangkapan layar antarmuka. Sebuah item dalam daftar mengatakan 9080: Mencoba Gemini 3 Pro dengan transkripsi audio dan benchmark pelican baru. Tombol besar bertuliskan Salin buletin teks kaya ke papan klip - di bawah adalah tombol kecil yang bertuliskan Salin tautan/kutipan/TIL saja. Penggeser X hari terakhir disetel ke 2. Terdapat kotak centang untuk konten SKip yang dikirim dalam buletin sebelumnya dan hanya menyertakan konten postingan sebelum batas komentar.

Saya mengklik tombol itu, menempelkan hasilnya ke editor Substack, mengubah beberapa hal dan menekan kirim. Keseluruhan proses biasanya hanya memakan waktu beberapa menit.

Saya melakukan sedikit pengeditan:

  • Saya menetapkan judul dan subjudul untuk buletin. Ini sering kali merupakan salinan langsung dari judul postingan blog unggulan.
  • Substack mengubah URL YouTube menjadi sematan, yang sering kali tidak saya inginkan—terutama jika saya memiliki URL YouTube di dalam contoh kode.
  • Blok teks yang telah diformat sebelumnya sering kali memiliki baris kosong tambahan di bagian akhir, yang saya hapus.
  • Kadang-kadang saya akan melakukan pengeditan konten—menghapus sebagian konten yang tidak sesuai dengan buletin, atau memperbaiki referensi waktu seperti “kemarin” yang tidak masuk akal lagi.
  • Saya memilih gambar unggulan untuk buletin dan menambahkan beberapa tag.

Itulah keseluruhan prosesnya!

Buku catatan yang Dapat Diamati

Sel terpenting dalam buku catatan Observable adalah yang ini:

raw_content = {
  return await (
    await fetch(
      `https://datasette.simonwillison.net/simonwillisonblog.json?sql=${encodeURIComponent(
        sql
      )}&_shape=array&numdays=${numDays}`
    )
  ).json();
}

Ini menggunakan JavaScript fetch() berfungsi untuk mengambil data dari contoh Datasette blog saya, menggunakan kueri SQL yang sangat kompleks yang dibuat di tempat lain di buku catatan.

Berikut tautan untuk melihat dan menjalankan kueri tersebut langsung di Kumpulan Data. Ini adalah 143 baris SQL berbelit-belit yang merakit sebagian besar HTML untuk buletin menggunakan rangkaian string SQLite! Cuplikan ilustrasi:

with content as (
  select
    id,
    'entry' as type,
    title,
    created,
    slug,
    '

'

|| 'https://simonwillison.net/' || strftime('%Y/', created) || substr('JanFebMarAprMayJunJulAugSepOctNovDec', (strftime('%m', created) - 1) * 3 + 1, 3) || '/' || cast(strftime('%d', created) as integer) || '/' || slug || '/' || '">' || title || ' - ' || date(created) || '' || body as html, 'null' as json, '' as external_url from blog_entry union all # ...

URL blog saya terlihat seperti ini /2025/Nov/18/gemini-3/—SQL ini membuat singkatan tiga huruf bulan dari nomor bulan menggunakan operasi substring.

Ini adalah sebuah sangat buruk cara merakit HTML, tapi saya terjebak karena itu menghibur saya.

Buku catatan Observable lainnya mengambil data tersebut, memfilter apa pun yang tertaut ke konten yang disebutkan dalam buletin sebelumnya dan menyusunnya menjadi blok HTML yang dapat disalin menggunakan tombol besar itu.

Berikut resep yang digunakan untuk mengubah HTML menjadi konten teks kaya pada clipboard yang cocok untuk Substack. Saya tidak ingat bagaimana saya menemukan jawabannya tetapi ini sangat efektif:

Object.assign(
  html`<button style="https://simonwillison.net/2025/Nov/19/how-i-automate-my-substack-newsletter/font-size: 1.4em; padding: 0.3em 1em; font-weight: bold;"https://simonwillison.net/2025/Nov/19/how-i-automate-my-substack-newsletter/>Copy rich text newsletter to clipboard`,
  {
    onclick: () => {
      const htmlContent = newsletterHTML;
      // Create a temporary element to hold the HTML content
      const tempElement = document.createElement("div");
      tempElement.innerHTML = htmlContent;
      document.body.appendChild(tempElement);
      // Select the HTML content
      const range = document.createRange();
      range.selectNode(tempElement);
      // Copy the selected HTML content to the clipboard
      const selection = window.getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
      document.execCommand("copy");
      selection.removeAllRanges();
      document.body.removeChild(tempElement);
    }
  }
)

Dari Django+Postgresql ke Kumpulan Data+SQLite

Blog saya sendiri adalah aplikasi Django yang dihosting di Heroku, dengan data disimpan di Heroku PostgreSQL. Berikut kode sumber untuk aplikasi Django itu. Saya menggunakan admin Django sebagai CMS saya.

Datasette menyediakan API JSON melalui database SQLite… yang berarti ada sesuatu yang perlu mengubah database PostgreSQL tersebut menjadi database SQLite yang dapat digunakan oleh Datasette.

Sistem saya untuk melakukan hal itu ada di repositori GitHub simonw/simonwillisonblog-backup. Ia menggunakan Tindakan GitHub pada jadwal yang dijalankan setiap dua jam, mengambil data terbaru dari PostgreSQL dan mengonversinya ke SQLite.

Alat db-to-sqlite saya bertanggung jawab atas konversi itu. Saya menyebutnya seperti ini:

db-to-sqlite \
  $(heroku config:get DATABASE_URL -a simonwillisonblog | sed s/postgres:/postgresql+psycopg2:/) \
  simonwillisonblog.db \
  --table auth_permission \
  --table auth_user \
  --table blog_blogmark \
  --table blog_blogmark_tags \
  --table blog_entry \
  --table blog_entry_tags \
  --table blog_quotation \
  --table blog_quotation_tags \
  --table blog_note \
  --table blog_note_tags \
  --table blog_tag \
  --table blog_previoustagname \
  --table blog_series \
  --table django_content_type \
  --table redirects_redirect

Itu heroku config:get DATABASE_URL perintah menggunakan kredensial Heroku dalam variabel lingkungan untuk mengambil URL koneksi database untuk database PostgreSQL blog saya (dan memperbaiki perbedaan kecil dalam skema URL).

db-to-sqlite kemudian dapat mengekspor data itu dan menulisnya ke file database SQLite bernama simonwillisonblog.db.

Itu --table opsi menentukan tabel yang harus disertakan dalam ekspor.

Repositori melakukan lebih dari sekedar konversi: ia juga mengekspor data yang dihasilkan ke file JSON yang ada di repositori, yang memberi saya riwayat penerapan perubahan yang saya buat pada konten saya. Ini adalah cara murah untuk mendapatkan riwayat revisi konten blog saya tanpa harus dipusingkan dengan pelacakan riwayat terperinci di dalam aplikasi Django itu sendiri.

Di akhir alur kerja Tindakan GitHub saya adalah kode ini yang menerbitkan database yang dihasilkan ke Kumpulan Data yang berjalan di Fly.io menggunakan plugin publikasikan kumpulan data fly:

datasette publish fly simonwillisonblog.db \
  -m metadata.yml \
  --app simonwillisonblog-backup \
  --branch 1.0a2 \
  --extra-options "--setting sql_time_limit_ms 15000 --setting truncate_cells_html 10000 --setting allow_facet off" \
  --install datasette-block-robots \
  # ... more plugins

Seperti yang Anda lihat, ada banyak bagian yang bergerak! Anehnya, sebagian besar semuanya berhasil—saya jarang perlu campur tangan dalam prosesnya, dan biaya untuk berbagai komponen tersebut cukup rendah.



Source link

Postagens Similares

  • اسے ہفتہ وار بھیج دیں – پروڈکشن سسٹم انجینئرز کے لئے ڈی اوپس/ایس آر ای نیوز پوڈ کاسٹ

    پروڈکشن سسٹم چلانے والے سافٹ ویئر انجینئرز کی بڑھتی ہوئی کمیونٹی کے لئے ، ایک آزاد ڈی او اوپس/ایس آر ای نیوز پوڈ کاسٹ اس منظر کو تیز اور سمارٹ نیوز جیسی توانائی اور اس کے سیٹ اپ کے ساتھ اختلافات کے ساتھ مارتا ہے۔ ڈی او اوپس ، ایس آر ای ، اور پلیٹ…

  • Apakah Google Ads Gen Z Anda Buktinya? Strategi Memenangkan Segmen 18-24

    Ketika rata-rata usia pelanggan meningkat untuk suatu merek, hal ini jarang terjadi karena kegagalan platform. Hal ini biasanya merupakan sinyal bahwa audiens yang lebih muda menemukan, mengevaluasi, dan membeli di tempat yang berbeda, dan merek-merek lama yang sudah mapan belum bisa mengimbanginya. Pada tahun 2026, Gen Z berusia 14 hingga 29 tahun. Mereka adalah generasi…

  • Une petite fille a été refoulée dans un parc à thème parce qu’elle était à peine trop petite – les tentatives de contournement de l’employé ont rendu la situation encore plus déchirante

    Une vidéo devenue virale sur X montre comment une petite fille mesurant un peu moins de 4 pieds 4 pouces se voit refuser l’entrée dans un parc à thème. Alors que de nombreux commentateurs ont convenu que la décision était nécessaire pour sa sécurité, d’autres ont reculé – arguant que la marge entre sa taille…

  • ਜ਼ੈਕਸਨ – ‘ਵੀਡੀਓਪਟਿਕਨ’ | ਅਸਪਸ਼ਟ ਧੁਨੀ

    ਨਵੀਂ-ਵੇਵ ਅਤੇ ਪੋਸਟ-ਪੰਕ ਸ਼ੁੱਧਤਾ ਦਾ ਇੱਕ ਸ਼ਾਨਦਾਰ ਪ੍ਰਦਰਸ਼ਨ, ਵੀਡੀਓਪਟਿਕਨ ਵੈਨਕੂਵਰ-ਅਧਾਰਤ ਤਿਕੜੀ ਦੀ ਨਵੀਂ ਐਲਬਮ ਹੈ ਜ਼ੈਕਸਨ. ਆਕਰਸ਼ਕ ਤਾਲਾਂ, ਰਾਤ ​​ਦੇ ਸੰਸਲੇਸ਼ਣ, ਗਤੀਸ਼ੀਲ ਗਿਟਾਰਾਂ ਅਤੇ ਅੰਤਰਮੁਖੀ ਵੋਕਲਾਂ ‘ਤੇ ਬਣਾਇਆ ਗਿਆ, ਇਹ ਰਿਕਾਰਡ ਸਖਤੀ ਨਾਲ ਬਣਾਏ ਗਏ, ਬ੍ਰਿਟਿਸ਼-ਪ੍ਰਭਾਵਿਤ ਚੱਟਾਨ ਦੇ ਪ੍ਰਵੇਸ਼ ਦੇ ਨਾਲ-ਨਾਲ ਚੇਤਨਾ ਦੀਆਂ ਅੰਤਰਮੁਖੀ ਅਤੇ ਬਦਲਦੀਆਂ ਸਥਿਤੀਆਂ ਦੀ ਪੜਚੋਲ ਕਰਦਾ ਹੈ। ਯਾਦਗਾਰੀ ਰੂਪ ਵਿੱਚ ਐਲਬਮ…

  • por qué los desarrolladores que hacen las mismas observaciones sobre los LLM llegan a conclusiones opuestas

    16 de marzo de 2026 – Baldur Bjarnason “¿Por qué estás siendo tan difícil? ¿Esto podría ser algo bueno para nosotros? ¿Cómo puedes estar tan seguro?” Esto fue a finales de 2000. Estábamos tomando unas copas en Watershed en Bristol. Para aquellos de ustedes que no están familiarizados con Watershed, es un cine artístico, un…

  • “Merry Go Round”令人激动、令人眼花缭乱的首秀 – JamSphere

    这条路有一种安静的吸引力 苏珊娜 步入出道时代。没有宏大的宣言,没有强迫的虚张声势。相反,她带着泰然自若、耐心到来,歌曲让她明白站在不确定的立场上深感感受是多么复杂。她的第一张正式单曲, “旋转木马” 将于 2026 年 2 月 10 日发布,这不仅仅是一个介绍。这是一位英国独立艺术家多年来一直在塑造这一时刻的情商和创造性自信的声明。 音乐一直活在苏珊娜的骨子里。从早期训练到驿站马车、歌唱课程和当地剧院,表演从来都不是一个遥远的梦想,而是一种生活在其中的语言。然而,尽管她写歌多年,但她还是在进入发行模式之前等待。这个停顿很重要。这让她的声音,无论是文学上的还是艺术上的,都变得成熟。 “旋转木马” 重新审视早期的歌曲创作,现在通过清晰、自信和生活经验重新构建。呈现出来的首张专辑给人一种既怀旧又鲜明的现实感,就像用更明智的眼睛重温记忆一样。 从声音上来说,这首歌曲倾向于旋律流行、脉搏欢快。稳定的鼓声支撑着这首歌,而闪闪发光的吉他和明亮、令人上瘾的合成线在它们周围旋转,创造出反映歌曲情感主题的动感。制作是精心策划的,但绝不拥挤。它留下了空间 苏珊娜的 轻快、空灵的声音可以轻松地在编曲中滑行。她的讲话轻松而有意,充满温暖、好奇和克制。这是一种乐观的流行音乐,带有一种反思性的暗流,这种暗流让你摇摆不定,同时静静地质疑自己的感受。 的情感核心 “旋转木马” 在于它对浪漫暧昧的探索。这首歌捕捉到了那种令人陶醉的空间,情感真实而持久,但确定性却从未完全到来。 苏珊娜 描绘了亲密、有趣的亲密和不言而喻的联系的时刻,只是为了将它们与犹豫和情感退缩形成对比。其结果是一幅悬浮在运动中的爱情肖像。永远旋转,永远不落地。旋转木马的中心隐喻成为情感循环的有力象征,既令人兴奋又令人疲惫。 这首歌不是把一切都拼出来,而是靠暗示来发展。身体上的亲密与言语上的沉默形成鲜明对比。温柔的手势胜于雄辩,但缺乏明确的定义会造成一种低沉的紧张气氛。 苏珊娜 探索在转瞬即逝的时刻感到被深深地关注,同时仍然质疑自己的立场意味着什么。情感的推拉是持续不断的。心灵感觉一致,但语言却跟不上。这种克制是这首歌最大的优点之一。它相信听众能够在意图和行动之间认出自己。 抒情地说, 苏珊娜 显示出对情感细微差别的敏锐理解。她探讨了情感如何同时感到失重和沉重,快乐如何与焦虑共存,以及希望如何悄悄地将某人束缚在永远不会完全发展的情况中。这首歌并没有将不确定性贬低或浪​​漫化。相反,它与它并存。场景的展开方式几乎有电影般的品质,捕捉到转瞬即逝的目光、向前迈出的半步,以及一个人在全力感受一切的同时试图不过度解读的内心独白。 是什么让 “旋转木马” 作为处女作,特别引人注目的是它的诚实而不自怜。苏珊娜并没有认为自己无能为力。她意识到自己所处的循环,意识到头晕,但仍然选择留下来。这种选择是用温柔而不是评判来描绘的。它反映了一个非常人性化的真理。有时我们仍处于旋转之中,并不是因为我们天真,而是因为希望有其自身的引力。这首歌的结尾情绪倾向于那种安静的乐观主义,坚持着一些未定义的事情可能仍然成为现实的可能性。 除了音乐本身, 苏珊娜的 出现感觉是经过仔细考虑的。网上称为 苏扎贝尔,她已经在 TikTok、Instagram、YouTube 和 X 上建立了稳定的形象。她的视觉效果、短片内容和微妙的预告片揭示了一位艺术家懂得讲故事不仅仅局限于歌曲。一种有凝聚力的美学正在形成,植根于柔软、内省和情感的可及性。对于首次亮相的艺术家来说,这种刻意的基础水平是罕见且令人耳目一新的。 尽管 “旋转木马” 引入了欢快的流行音乐,苏珊娜明确表示这只是一个开始。她的项目由实验驱动,未来的版本将探索不同的流派、情绪和情感章节。这种开放性标志着艺术家重视进化而非可预测性。每个版本都将被允许作为她自己的创造性和情感位置的反映,而不是过早地被迫进入固定的身份。 随着二月的临近,人们的期待不断增强。 “旋转木马” 感觉准备好与那些了解不确定关系的痛苦和近乎爱情的安静兴奋的听众产生深刻的共鸣。这首歌适合那些曾在清晰与幻想之间徘徊、在原地转圈、同时暗自希望旅程能慢下来足以改变方向的人。 凭借这次首秀, 苏珊娜 并没有简单地自我介绍。她以优雅、耐心和流畅的情感邀请听众进入她的内心世界。 “旋转木马” 这是一位艺术家自信的第一步,他明白,脆弱性与技巧和意图相结合,可以成为强大的基础。如果这首单曲有任何迹象的话,苏珊娜不会在这里短暂旋转。她来这里是为了创造一些持久的东西,一次一个令人眼花缭乱的美丽篇章。 您可以通过苏珊娜的社交媒体与她联系并了解她的最新动态: TikTok:@sooozabellofficial Instagram:@soozabellofficial YouTube:@sooozabellofficial X/Twitter:@sooozabell…

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *