🦕

DenoとGithub ActionsでZennの記事を自動定期宣伝ツイートする

2021/06/28に公開

最近、Denoにハマっています。

Denoで何かを自動化したいと思い、Zennで書いた記事をツイートすることを考えました。

DenoでZennの情報を取得する

先日、DenoでZennの情報を取得するモジュールを作りました。

https://zenn.dev/kawarimidoll/articles/3d51924dc595b6

これをつかってZennの自分の最新記事の情報を取得します。

promote_zenn_article.ts
import {
  zennApi,
} from "https://github.com/kawarimidoll/deno-zenn-api/raw/main/mod.ts";

const { articles } = await zennApi("kawarimidoll");
const article = articles[0];
if (!article) {
  throw new Error("No articles found");
}

console.log({ article });

毎回deno run --allow-netのようにパーミッションを書くのが億劫なので、Velociraptorを使います。
設定ファイルを作ります。

velociraptor.yml
allow:
  - net

scripts:
  promote_zenn_article:
    cmd: promote_zenn_article.ts

これで実行してみます。

❯ vr promote_zenn_article
{
  article: {
    id: 41325,
    title: "DenoでZennのAPIモジュールを作る",
    slug: "3d51924dc595b6",
    published: true,
    commentsCount: 0,
    likedCount: 3,
    bodyLettersCount: 5113,
    readingTime: 5,
    articleType: "tech",
    emoji: "🦕",
    isSuspendingPrivate: false,
    publishedAt: "2021-06-26T12:19:10.637+09:00",
    bodyUpdatedAt: null,
    sourceRepoUpdatedAt: null,
    createdAt: "2021-06-25T20:43:24.621+09:00",
    updatedAt: "2021-06-27T06:45:15.675+09:00",
    user: {
      id: 39895,
      username: "kawarimidoll",
      name: "kawarimidoll",
      avatarSmallUrl: "https://storage.googleapis.com/zenn-user-upload/avatar/icon_2379ac8d86.jpeg"
    },
    topics: [
      {
        id: 57,
        name: "html",
        displayName: "HTML",
        taggingsCount: 290,
        imageUrl: "https://storage.googleapis.com/zenn-user-upload/topics/171432f5a6.png"
      },
      {
        id: 218,
        name: "api",
        displayName: "API",
        taggingsCount: 164,
        imageUrl: "https://storage.googleapis.com/zenn-user-upload/topics/204f07b4d5.png"
      },
      { id: 1843, name: "dom", displayName: "dom", taggingsCount: 17, imageUrl: null },
      {
        id: 181,
        name: "zenn",
        displayName: "Zenn",
        taggingsCount: 276,
        imageUrl: "https://storage.googleapis.com/zenn-user-upload/topics/0b0064a451.jpeg"
      },
      {
        id: 180,
        name: "deno",
        displayName: "Deno",
        taggingsCount: 72,
        imageUrl: "https://storage.googleapis.com/zenn-topics/deno.png"
      }
    ]
  }
}

こんな感じのデータが取得できます。
ここからtitletopicsの内容を使ってツイート文面を作れそうです。

Zenn記事へのリンクを生成する

データには記事へのリンクがないことに気づきます。
いろいろなページを見てみると、リンクはhttps://zenn.dev/[username]/[articles|books|scraps]/[slug]のような形式になっているようです。
これを生成する機能をdeno_zenn_apiリポジトリへ追加しました。

zenn_link.ts
import { ZENN_ROOT } from "./zenn_api.ts";
import {
  implementsZennArticle,
  implementsZennBook,
  ZennArticle,
  ZennBook,
  ZennScrap,
} from "./types.ts";

const zennLink = (
  object: ZennArticle | ZennBook | ZennScrap,
): string => {
  if (!object) {
    return ZENN_ROOT;
  }

  const username = object.user.username;

  const resourceName = implementsZennArticle(object)
    ? "/articles/"
    : implementsZennBook(object)
    ? "/book/"
    : "/scrap/";

  return ZENN_ROOT + username + resourceName + object.slug;
};

export { zennLink };

渡されたリソースがarticlebookscrapかを調べ、リンクになるように文字列を連結します。
これを読み込んで使います。

promote_zenn_article.ts
  import {
    zennApi,
+   zennLink,
  } from "https://github.com/kawarimidoll/deno-zenn-api/raw/main/mod.ts";

  const { articles } = await zennApi("kawarimidoll");
  const article = articles[0];
  if (!article) {
    throw new Error("No articles found");
  }

+ const message = `${article.title}』という記事を書きました
+ ${zennLink(article)}`;
+ console.log(message);

実行してみます。

❯ vr promote_zenn_article
『DenoでZennのAPIモジュールを作る』という記事を書きました
https://zenn.dev/kawarimidoll/articles/3d51924dc595b6

リンク文字列の生成ができました。
ほかにもreadingTimetopicsの情報も入れていくことができます。
このへんはあまり技術に関係しないので割愛します。

ローカルからツイートする

続いて実際にツイートします。
以前作ったIFTTTのAPIを使います。
https://zenn.dev/kawarimidoll/articles/234096fed2ee3c

もちろん、Twitter公式のほうで開発者登録をしていればそのAPIを使えると思います。

ここでは、ツイート文面とAPIキーを受け取るasync sendTweet(params: { message: string; key: string })が定義されているものとし、これを使っていきます。
といっても、importして呼び出せばよいだけですね。

promote_zenn_article.ts
+ import { IFTTT_WEBHOOK_KEY } from "./env.ts";
+ import { sendTweet } from "./tweet_with_ifttt.ts";
(略)
  console.log(message);
+ console.log(await sendTweet({ message, key: IFTTT_WEBHOOK_KEY }));

なお、環境変数を読み込むenv.tsに関しては以前の記事で説明しています。
https://zenn.dev/kawarimidoll/articles/1c48c097020cbc

velociraptor.ymlで環境変数を読めるようにしておきます。

velociraptor.yml
  allow:
+   - read=.env,.env.example,.env.defaults
+   - env
    - net

  scripts:
    promote_zenn_article:
      cmd: promote_zenn_article.ts

実行します。
※実施したタイミングの関係で前述のものとは抽出している記事が異なりますが、ここでは実行結果の例示なので気にしないでください。

❯ vr promote_zenn_article
『DenoでKyを使って極楽要求(しなさい)』という #Zenn 記事を書きました
kyとかfetchとかDenoとかについていろいろ書いています
5分くらいで読めるのでスキマ時間のお供にどうぞ
https://zenn.dev/kawarimidoll/articles/13c3f75f6f22d6
(本ツイートはDeno🦕で自動生成しています)
Congratulations! You've fired the send_tweet event

https://twitter.com/KawarimiDoll/status/1407497324608294912
良い感じです。
なお、IFTTTを経由したことでリンクがift.ttの短縮リンクになっていますが、別に問題はないと思います。

GitHub Actionsから実行する

これを自動で定期実行したいと思います。
方法はいろいろあると思いますが、今回はGithub Actionsを使いました。

環境変数の設定

ツイートAPIを使用するため、環境変数(今回はIFTTT_WEBHOOK_KEY)を設定します。

GitHubのリポジトリのSettingsからSecretsへ入り、New repository secretします。

値を入れてAdd Secretします。

一覧に追加されればOKです。
Updateボタンから値の変更をすることはできますが、設定された値を表示する方法はありません。

環境変数の使用

設定した環境変数は、Github Actionsの設定ファイル内で、${{ secrets.ENV_NAME }}の形式で使用できます。

ということで、GitHub Actions内で.envファイルを生成し、必要な環境変数を適用すればOKです。

- name: Create env file
  run: |
    touch .env
    echo IFTTT_WEBHOOK_KEY=${{ secrets.IFTTT_WEBHOOK_KEY }} >> .env

しかし、dotenvのsafe modeを使用しており、同一リポジトリの別の箇所で他の環境変数を使っている場合には問題が起こります。

.env.exampleIFTTT_WEBHOOK_KEY以外の環境変数が定義されているにも関わらず、.envIFTTT_WEBHOOK_KEYしか無いため、エラーが発生します。

.env.example
IFTTT_WEBHOOK_KEY=ifttt_webhook_key
OTHER_KEY=other_key
.env
IFTTT_WEBHOOK_KEY=xxxxxxxxxxx
error: Uncaught MissingEnvVarsError: The following variables were defined in the example file but are not present in the environment:
  OTHER_KEY

Make sure to add them to your env file.

したがって.env.exampleをコピーして枠組みを作り、必要な環境変数だけ適用する方法を取ります。

  - name: Create env file
    run: |
-     touch .env
+     cp .env.example .env
      echo IFTTT_WEBHOOK_KEY=${{ secrets.IFTTT_WEBHOOK_KEY }} >> .env

これで生成される.envは以下の形式になります。
.env.exampleで定義されているすべての値が含まれているため、エラーは生じません。
また、キーが重複している場合は後のもので上書きされるため、正しいsecretが使われます。

.env
IFTTT_WEBHOOK_KEY=ifttt_webhook_key
OTHER_KEY=other_key
IFTTT_WEBHOOK_KEY=xxxxxxxxxxx

yml全体像

velociraptorのアクションが提供されているので、それを使って実行します。

https://velociraptor.run/docs/github-actions/

scheduleの設定、前述の環境変数の設定とあわせ、全体としてはこのようになります。
scheduleの設定の際は、UTCで解釈されることに注意してください。また、GitHubのサーバーの稼働状態により、実行に遅延が生じることがあるようです。

.github/workflows/promote_zenn_article.yml
name: tweet_promote_zenn_article

on:
  # 03:20UTC -> 12:20JST
  schedule:
    - cron: "20 3 * * *"

jobs:
  promote:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
      - uses: denoland/setup-deno@v1
      - uses: jurassiscripts/setup-velociraptor@v1
      - name: Create env file
        run: |
          cp .env.example .env
          echo IFTTT_WEBHOOK_KEY=${{ secrets.IFTTT_WEBHOOK_KEY }} >> .env
      - run: vr promote_zenn_article

これで毎日お昼ごろに最新記事がツイートされるようになりました。

おわりに

GitHub ActionsからDenoを自動実行することができました。

今回は「毎日」「最新記事を」「ツイート」してみましたが、

  • 間隔を変える
  • インプットを変える
    • ランダムで記事を紹介する
    • LIKE数の多い記事を紹介する
    • Zennの人気記事を(勝手に)紹介する
  • アウトプットを変える

…などにも応用できると思います。夢が広がる。

こんな感じでDenoで遊んでいるリポジトリはこちらです。
https://github.com/kawarimidoll/deno-dev-playground

参考

https://stackoverflow.com/questions/60176044/how-do-i-use-an-env-file-with-github-actions

Discussion