毎月Qiitaの投稿数とContributionsの増加量を自動でSlackに通知してモチベーションを高める

# はじめに

エンジニアは成長するために技術記事を書くと良いと言われています。その点でQiitaに記事を書くことは、成長するためにとても良い事と言えます。

そこで本稿は、**Microsoft Azureを用いて、毎月1日の9時にQiitaの投稿数とContributionsが1ヶ月間でどれだけ増えたのかをにSlackに自動で通知する手順**を紹介します。言語はC#です。

これにより、**記事の投稿数やLGTMが着実に積み上がっている様が分かり、記事を投稿するモチベーションを高める**事が狙いです(一般的に成果は見える化した方が良いと言われていますし)。

↓こんな感じに毎月Slackに通知されます。チーム皆の投稿数やContributionsの積み上げが分かります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403606/8558c36d-2e20-958f-2892-061f3f1d478d.png" width=50%>

# 手順の概要

以下の方法で実現します。

1. Microsoft Azure で 定期的に実行される Azure Functions のプロジェクトを作成します。
2. 定期実行処理の中に、Qiita の投稿数と Contributions を取得する処理を書きます。
3. Firebase Realtime Database にユーザーごとの前回実行時点のデータを格納しておき、今回との差分を算出します(例：投稿数が＋2、Contributionsが＋20)。
4. その結果を、Slack の Incoming Webhook を用いて Slack に投稿する処理を書きます。
5. 上記で作成したプログラムを Azure に発行します。

# Azure Functions のプロジェクトを作成する

ポイントだけ紹介します。
まず Visual Studio で新しいプロジェクトを作成する下図の画面で、Azure Functions を選択してプロジェクトを作成します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403606/ccbe6d83-5d69-1370-b02c-23f541769a0d.png" width=75%>

その時点で、下図のように Function1クラスにWeb APIの雛形が作成されています。
基本的には、このメソッドの中身を書き換えれば、Azure Functions の Web API が作れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403606/47895870-c9bf-c4b1-d399-423241b92acb.png)

Visual Studio を用いて Azure Functions のプロジェクトを作成する事についての詳細は以下を参照ください。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio

次に、このメソッドを毎月1日の日本時間9時に自動で実行されるように変更します。
以下のように TimerTrigger を引数にもらうように変更します(合わせてメソッド名も変更しています)。

```c#
/// <summary>
/// OutputReportを月に1回、Slackに通知するAPI
/// </summary>
/// <param name="myTimer">第3引数が時刻で日本時間9時は標準時間0時、第4引数が日にちで1日を指定。</param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName("OutputReport")]
public static async Task OutputReport([TimerTrigger("0 0 0 1 * *")] TimerInfo myTimer, ILogger log)
{
    ...
}

```

TimerTrigger についての詳細は以下を参照ください。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp

# Qiita の投稿数と Contributions を取得する

Qiita は Web API を公開しています。ただし、ユーザーごとのContributionsは、APIで取得できないようです。
[Qiita の API の詳細はこちら](https://qiita.com/api/v2/docs)

そのため今回は `https://qiita.com/ユーザー名` のURLから取得するコンテンツを用いて、投稿数と Contributions を取得します。

今回は雑な取得方法でやりました。
Contributions はコンテンツの中に `"newContribution":14411,` という形式で入っていたので、その形式で書かれている部分文字列を探して取得しました(超雑な取得方法でごめんなさい)。
具体的なコードは以下です。

```c#
// ユーザーページからデータを取得
var client = new HttpClient();
var response = await client.GetAsync("https://qiita.com/" + userName);
var contents = await response.Content.ReadAsStringAsync();

// 以下の形式で埋め込まれている Contributions を探して取得する
// "newContribution":14411,
const string contributionStartString = "\"newContribution\":";
var contributionStartIndex = contents.IndexOf(contributionStartString);
var contributionSubstring = contents.Substring(contributionStartIndex + contributionStartString.Length);
var contributionStringLength = contributionSubstring.IndexOf(",");
var contributionsString = contributionSubstring.Substring(0, contributionStringLength);
int contributions = int.Parse(contributionsString);
```

同じように、投稿数は `"articles":{"totalCount":59},` という形式で入っている文字列を探して取得できます。

# Firebase Realtime Database を用いて前回と今回との差分を算出する

Firebase Realtime Database に、ユーザーごとの前回実行時点の投稿数とContributionsを格納しておき、今回との差分を算出します(例：投稿数が＋2、Contributionsが＋20)。

そのために、今回の結果と前回の結果を両方格納できるOutputUserクラスを以下のように作ります。
(以下は投稿数とContributionsとフォロワー数の前回と今回の結果を格納しています)

```c#
/// <summary>
/// OutputReportに利用するユーザー情報
/// </summary>
class OutputUser
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="qiitaUserName">Qiitaのユーザー名</param>
    public OutputUser(string qiitaUserName)
    {
        QiitaUserName = qiitaUserName;
    }

    /// <summary>
    /// Qiitaのユーザー名
    /// </summary>
    public string QiitaUserName { get; set; }

    /// <summary>
    /// Qiitaの投稿数
    /// </summary>
    public int QiitaArticleCount { get; set; }

    /// <summary>
    /// 前回のQiitaの投稿数
    /// </summary>
    public int PreviousQiitaArticleCount { get; set; }

    /// <summary>
    /// QiitaのContributions
    /// </summary>
    public int QiitaContributions { get; set; }

    /// <summary>
    /// 前回のQiitaのContributions
    /// </summary>
    public int PreviousQiitaContributions { get; set; }

    /// <summary>
    /// Qiitaのフォロワー数
    /// </summary>
    public int QiitaFollowers { get; set; }

    /// <summary>
    /// 前回のQiitaのフォロワー数
    /// </summary>
    public int PreviousQiitaFollowers { get; set; }

    /// <summary>
    /// 新しいデータに更新する
    /// </summary>
    /// <param name="qiitaArticleCount">Qiitaの投稿数</param>
    /// <param name="qiitaContributions">QiitaのContributions</param>
    /// <param name="qiitaFollowers">Qiitaのフォロワー</param>
    public void Update(int qiitaArticleCount, int qiitaContributions, int qiitaFollowers)
    {
        PreviousQiitaArticleCount = QiitaArticleCount;
        PreviousQiitaContributions = QiitaContributions;
        PreviousQiitaFollowers = QiitaFollowers;
        QiitaArticleCount = qiitaArticleCount;
        QiitaContributions = qiitaContributions;
        QiitaFollowers = qiitaFollowers;
    }

    /// <summary>
    /// 対象ユーザーのアウトプット情報を出力する
    /// </summary>
    /// <returns></returns>
    public string Report()
    {
        return $"{QiitaUserName}{Environment.NewLine}" +
            $"Qiita 投稿数：{QiitaArticleCount} (＋{QiitaArticleCount - PreviousQiitaArticleCount}){Environment.NewLine}" +
            $"Qiita Contributions：{QiitaContributions} (＋{QiitaContributions - PreviousQiitaContributions}){Environment.NewLine}" +
            $"Qiita フォロワー：{QiitaFollowers} (＋{QiitaFollowers - PreviousQiitaFollowers}){Environment.NewLine}" +
            $"------------------------------------------";
    }
}
```

あとは、このクラスを用いて、実行時に Firebase から前回のデータを取得して、差分を算出した上で、次回のために今回のデータを Firebase に登録し直す処理を作ります。
Firebase Realtime Database を用いてデータを登録したり取得したりする方法は、以下の記事を参照ください。

https://qiita.com/kojimadev/items/f2cca226fcd5e83821c9

上記で紹介した FirebaseServiceクラスを用いると、以下のように、Firebase から前回のデータを取得して、差分を算出した上で、次回のために今回のデータを Firebase に登録し直す処理が書けます。

```c#
// Firebase から対象ユーザーのデータを取得する
var firebaseService = GetFirebaseService();
var outputUser = await firebaseService.GetRecordAsync<OutputUser>("outputUsers", userName);
if (outputUser == null)
{
    // Firebaseにデータが存在しなければ新規作成
    outputUser = new OutputUser(userName);
}
// 今回の投稿数とContributionsとフォロワー数で対象ユーザーを更新して
// 対象ユーザーに前回と今回の差分を保持させる
outputUser.Update(articleCount, contributions, followerCount);

// 更新後のデータでFirebaseに登録しなおす
await firebaseService.UpdateRecordAsync("outputUsers", userName, outputUser);
```

# Slack にメッセージを投稿する

Incoming Webhook を用いて Slack にメッセージを送信する方法は、以下の記事に詳細を記載していますので、そちらを参照ください。

https://qiita.com/kojimadev/items/480c99aa691b82757b72#slack%E3%81%AB%E6%8A%95%E7%A8%BF%E3%81%99%E3%82%8B%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B

上記の記事で紹介している SlackNotificationService クラスの Notify メソッドを利用すれば、以下のように、対象ユーザーごとの出力用文字列を結合して、その文字列を Slack に投稿することができます。

```c#
// 対象ユーザーが複数名の場合に、ユーザーごとの結果を1つの文字列に結合する
var builder = new StringBuilder();
foreach (var outputUser in outputUsers)
{
    builder.AppendLine(outputUser.Report());
}

// 結合した文字列をSlackに投稿する
var service = new SlackNotificationService();
await service.Notify(builder.ToString(), _WebhookUrl, "OutputReport", "", false);
```
# Azure Functions の発行

Microsoft Azure のアカウントを取得した上で、Visual Studio のプロジェクトを右クリックして[発行]というメニューを実行すれば、Azure Functions にプログラムを発行できます。
詳細は以下を参照ください。
[Azure にプロジェクトを発行する - Microsoft](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio#publish-the-project-to-azure)

# ソースコード一式

ここまでの手順で作成したソースコードを以下のリポジトリで公開しています。

https://github.com/kojimadev/OutputReportFunctionApp

Functionsクラスの以下の4つのフィールドに初期値を設定すれば、動作すると思います。
[Functionsクラスはこちら](https://github.com/kojimadev/OutputReportFunctionApp/blob/master/src/OutputReportFunctionApp/Functions.cs)

```c#
public class Functions
{
    /// <summary>
    /// Firebase RealtimeDatabase の Secret
    /// </summary>
    private const string _DatabaseSecret = "";

    /// <summary>
    /// Firebase RealtimeDatabase の Url
    /// </summary>
    private const string _DatabaseUrl = "";

    /// <summary>
    /// Slackに通知する際に利用するIncoming WebhookのWebhook URL。Incoming Webhookの詳細は以下参照。
    /// https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%7%94%A8
    /// </summary>
    private const string _WebhookUrl = "Incoming WebhookのURL";

    /// <summary>
    /// 対象のユーザー名の一覧
    /// </summary>
    private static readonly List<string> _TargetUsers = new()
        {
            "Qiitaのユーザー名(例:kojimadev)",
        };
    ...
}
```

上記の初期値を設定をした上で Azure Functions に発行すれば、毎月1日の9時にこんな感じでSlackに通知されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403606/8558c36d-2e20-958f-2892-061f3f1d478d.png" width=50%>

なお、本稿では Azure Functions と Firebase を利用していますが、私の場合はどちらも無料枠の範囲に収まっています。

# まとめ

月初に記事の投稿数やLGTMが着実に積み上がっている事が分かると、投稿するモチベーションは高まりやすいと思いますので、活用してもらえると嬉しいです。

ちなみに私は、エンジニアリングマネージャーとして「ハピネスチームビルディング」と名付けたチームの皆で主体的に楽しく成長する活動を実施しています。
ITエンジニア向け情報誌「Software Design」で連載している内容を以下で公開していますので、よろしければ、そちらも参照ください。

https://note.com/kojimadev/n/nbe692f9b3cec

Twitterでも開発に役立つ情報を発信しています → [@kojimadev](https://twitter.com/kojimadev)