公開日

GitHub Actions を使用してブログ投稿の公開通知を作成する方法

著者

ブログ投稿のリマインダーをGitHub Actionsで作成する方法

ライターとして、新しいコンテンツを公開する際には一貫性を保つことが重要であることは理解しています。しかし、生活に支障をきたすことがあり、新しいブログ投稿を書くことを忘れることがあります。私の共有スケジュールに沿って進めるために、GitHub Actionsを使って簡単なリマインダーを作成しました。この記事では、このワークフローを作成した方法を紹介します。

GitHub Actionsとは?

GitHub Actionsは、ワークフローを自動化するための強力なツールです。コードの構築、テスト、デプロイに使用できます。また、通知の送信やリマインダーのスケジュールなど、さまざまなタスクを実行するためにも使用できます。

ブログ投稿を書くためのリマインダーを作成する方法

ブログ投稿を書くためのリマインダーを作成するために、ImBIOSのGitHubリポジトリREADME.mdを使用し、.github/workflows/blog-posts.ymlという名前のファイルを追加しました。このファイルで、GitHub Actionsが実行するワークフローを定義しました。ファイルの初期コンテンツは次のとおりです。

name: Blog Posts

on:
  schedule:
    - cron: '0 0 * * 0' # 毎週日曜日の00:00に実行
  workflow_dispatch:

jobs:
  update-posts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Update post list
        run: |
          sleep 1m
          curl -LO https://blog.imam.dev/feed.xml
          node src/list-posts.js
          rm feed.xml
      - name: Commit changes
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add -A
          git diff-index --quiet HEAD || git commit -m "Update blog posts"
      - name: Pull changes
        run: git pull -r
      - name: Push changes
        uses: ad-m/github-push-action@0fafdd62b84042d49ec0cb92d9cac7f7ce4ec79e
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

このワークフローは、毎週日曜日の00:00にトリガーされます。その後、ブログ投稿のリストを更新するスクリプトを実行します。スクリプトはJavaScriptで記述されており、私のブログのRSSフィードを解析します。次に、ブログ投稿のリストを生成し、README.mdファイルを更新します。最後に、変更をコミットしてGitHubにプッシュします。このワークフローは、ouuanのリポジトリを参考にしています。

リマインダーはどこから来たのでしょうか?実際には、list-posts.jsファイルにあります。ブログ投稿のリストにリマインダーを追加しました。ファイルの内容は次のとおりです。

const { readFileSync, writeFileSync } = require('fs')

/**
 * XML文字列をJSONに変換する
 * @param {string} xmlString
 * @returns {object} json
 */
const xmlToJson = (xmlString) => {
  const regex = /<(\w+)([^>]*)>([\s\S]*?)<\/\1>/gm
  const matches = xmlString.matchAll(regex)
  const json = {}

  for (const match of matches) {
    const [, key, attributes, value] = match
    const subMatches = value.matchAll(regex)
    const subJson = {}

    for (const subMatch of subMatches) {
      const [, subKey, subAttributes, subValue] = subMatch

      if (subValue.match(regex)) {
        if (Array.isArray(subJson[subKey])) {
          subJson[subKey].push(
            xmlToJson(`<${subKey}${subAttributes}>${subValue}</${subKey}>`)[subKey]
          )
        } else if (subJson[subKey]) {
          subJson[subKey] = [
            subJson[subKey],
            xmlToJson(`<${subKey}${subAttributes}>${subValue}</${subKey}>`)[subKey],
          ]
        } else {
          subJson[subKey] = xmlToJson(`<${subKey}${subAttributes}>${subValue}</${subKey}>`)[subKey]
        }
      } else if (Array.isArray(subJson[subKey])) {
        subJson[subKey].push(subValue)
      } else if (subJson[subKey]) {
        subJson[subKey] = [subJson[subKey], subValue]
      } else {
        subJson[subKey] = subValue
      }
    }

    if (json[key]) {
      if (Array.isArray(json[key])) {
        json[key].push(subJson)
      } else {
        json[key] = [json[key], subJson]
      }
    } else {
      json[key] = subJson
    }
  }

  return json
}

/**
 * JSONをpubDateでソートする
 * @param {object} json
 * @returns {object} sortedJson
 */
const sortJson = (json) => {
  json.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate))
  return json
}

// XMLファイルを読み込み、JSONに変換する
const xmlString = readFileSync('feed.xml', 'utf8')
const feeds = sortJson(xmlToJson(xmlString).rss.channel.item)

// 投稿のMarkdownリストを作成する
const posts = feeds
  .slice(0, 5)
  .map(
    (item) =>
      `- ${new Date(item.pubDate).toISOString().split('T')[0]} [${item.title}](${
        item.link
      }?utm_source=GitHubProfile)`
  )

// 投稿が変更された場合はREADME.mdを更新する
// それ以外の場合は、ブログ投稿を書くように思い出させるエラーをスローする
const readme = readFileSync('README.md', 'utf8')
if (readme.includes(posts.join('\n'))) {
  throw new Error('No new blog posts')
} else {
  const updatedReadme = readFileSync('README.md', 'utf8').replace(
    /(?<=<!--START_SECTION:blog-posts-->\n)[\s\S]*(?=\n<!--END_SECTION:blog-posts-->)/,
    posts.join('\n')
  )
  writeFileSync('README.md', updatedReadme)

  console.log('Updated README.md')
}

このスクリプトは、私のブログのRSSフィードを読み込み、ブログ投稿のリストを生成します。その後、README.mdファイルをブログ投稿のリストで更新します。新しいブログ投稿がない場合は、ブログ投稿を書くように思い出させるエラーをスローします。

これは、投稿が同じままである間にスクリプトが実行されたときにスローされるエラーであり、私のメールに送信されるリマインダーや、私にとってより目立つものとは違います。そこで、失敗したワークフロー実行の通知を有効にすることにしました。手順は次のとおりです。

  1. ページの右上隅にあるボタンをクリックし、設定を選択します。

  2. 左側のサイドバーから通知を選択します。

  3. Actionsをクリックします。

  4. 失敗したワークフローのみ通知を送信するを選択します。

これで、スクリプトが実行され、新しいブログ投稿がない場合に通知を受け取ります。GitHubのWebサイトでも通知を確認できます。

私が試した別の方法

先ほど説明したワークフローは、私のREADME.mdを常に最新の状態に保つための修正版です。ブログ投稿を書くためのリマインダーを作成する別の方法も試しました。しかし、これはREADME.mdの更新メカニズムのない純粋なリマインダーであり、単なるリマインダーです。

ブログ投稿を書くためのリマインダーを作成するために、新しいGitHubリポジトリを作成し、.github/workflows/remind.ymlという名前のファイルを追加しました。このファイルで、GitHub Actionsが実行するワークフローを定義しました。ファイルの内容は次のとおりです。

name: Reminder to write a blog post

on:
  schedule:
    - cron: '0 10 * * 1-5'

jobs:
  remind:
    runs-on: ubuntu-latest
    steps:
      - name: Send a reminder
        uses: dawidd6/action-send-mail@v3.1.0
        with:
          server_address: smtp.gmail.com
          server_port: 465
          username: ${{ secrets.EMAIL_USERNAME }}
          password: ${{ secrets.EMAIL_PASSWORD }}
          subject: 'Reminder to write a new blog post'
          body: "Don't forget to write a new blog post today!"
          to: my-email@example.com

このワークフローは、毎週平日午前10時にメールのリマインダーを送信し、新しいブログ投稿を書くように思い出させます。サードパーティのアクションであるdawidd6/action-send-mailを使用してメールを送信しました。メールの資格情報はGitHubのシークレットとして提供しているので、ワークフローファイルには表示されません。

まとめ

ブログ投稿を書くためのリマインダーを作成する2つの方法を試しました。1つ目は、私のGitHubプロファイルのREADME.mdファイルを更新する方法です。2つ目は、メールリマインダーを送信する方法です。現在、1つ目の方法を使用しています。これは、2つ目の方法よりも目立つからです。私のGitHubプロファイルにアクセスするたびにリマインダーを確認できます。

GitHub Actionsを使用してブログ投稿を書くためのリマインダーを作成することは、ブログのスケジュールを管理するためのシンプルで効果的な方法です。このワークフローがあれば、新しい投稿を書くことを忘れることはありません。リマインダーのワークフローを作成することに興味がある場合は、GitHub Actionsのドキュメントをチェックして詳細を確認してください。楽しいブログライフを!