AdventarのRSSがPowerAutomateでヴァリデートに引っかかるので対応したお話


AdventarのRSSがPowerAutomateでヴァリデートに引っかかるので対応したお話

この記事は東京高専プロコンゼミ② Advent Calendar 19日目の記事です。
東京高専プロコンゼミ① Advent Calendarの枠が埋まったのでまだ書きたい人が、②に書いています。
Qiitaで記事を書くのは、はじめてです。Qiitaの皆さんはじめまして。minfaox3です。
いまデプロイされているウェブサイトが突貫工事デザインなのでQiitaで出すことにしました。
いまいちQiitaの色んな機能がよくわかりませんが、がんばります。
LGTM(Looks Good To Me)っていいですね。ですがLGTMって最初なんのことだかさっぱりでした。

はじめに

今日はOffice365PowerAutomate(MicrosoftFlow)のお話をします。
PowerAutomateが初めてでも真似して作れるような形にしています。(わからない語句はリンクを踏むか、ドキュメントで調べてね!)
東京高専の人は学校から渡されているOffice365アカウントで「無料」で使用できます!

PowerAutomateとは

MSのドキュメントにある簡易的な説明では

Power Automate は、よく使うアプリやサービスとの間に自動化されたワークフローを作成し、ファイルの同期、通知の受信、データの収集などを行うことができるようにするためのサービスです。
https://docs.microsoft.com/ja-jp/power-automate/より引用

だそうです。
私が思うこれの強みはいろいろなサービスと連携できることです!(400個以上のサービスと接続できる。)†1
MS以外でもSlack,Google,Twitterなどとも連携できるので仕事や趣味などで十分活用できると思います。
しかもコードを全然書かなくても多くのことができます。(今回は文字列処理を細かく操作したいので少しだけコードを書きます。)
また多くの人が使っている、求められているものはテンプレートとして用意されているので、すぐに導入することも可能です。
例えば
・Office 365 のメールの添付ファイルを OneDrive for Business に保存する
・Planner で新しいタスクが作成されたら Microsoft Teams にメッセージを投稿する
・選択したファイルについて上司の承認を要求する
・毎日の天気予報をメールと電話に配信する
・現在の場所の今日の天気予報を取得する
などです。

PowerAutomateを使える環境

有名なブラウザやモバイル端末で使用可能です。

何をしたいのか

では本題に入ります。私が何をしたかったのかというと、
「予め指定したAdventarのカレンダーでその日の登録された記事をTeamsの特定チャネルとTwitterに投稿したい」
と思いました。

何が起きたのか

まずRSSフィードの更新をトリガーにして、「Twitterに投稿」と「Teamsに投稿」のアクションを追加してこんなフローを作成しました。

テストとしてダミーのRSSを作成して動かしたところうまく動きました。
しかし、AdventarのRSSを入れると動かない!!!
コネクタの部分で400が返ってきて初っ端でコケてました。

原因をググったら以下の記事を見つけました。
Microsoft FlowでRSS読み込みがBad Requestになる件」(@TakamiChie著)
AdventarのRSSが仕様に反しているようです。
このAdventarのRSSを、記事で紹介されていたW3Cのヴァリデートサービスにいれると

guid must be a full URL, unless isPermaLink attribute is false
XML parsing error: unbound prefix

といわれていました。

どう解決したのか

AdventarのRSSは外部が出したものなので吐き出される文法を修正することはできません。
なので賢いかはわかりませんが、以下の方法を取ることにしました。

以下を30分おきに繰り返す。
1. 「https://adventar.org/calendars/取得したいカレンダーの番号.rss」をGETする。
2. 文字列抽出で最新の記事を取得する。
3. OneDriveに保存しておいた、「title.txt」の中身と比較する。
3-a. 違うならば投稿+中身を最新のものに更新。
3-b. 同じならば、何もしない。

最初に完成した全体像を出すとこんな感じです。

定期実行するようにトリガーを設定する

赤枠部を選択して、

次にフローを作成します。
後ですべて入力、変更可能なのでとくになにもいれずそのまま作成することもできます。

すると以下の画面が出て、
「+新しいステップ」ボタンから次のアクションを追加できます。

「Recurrence」をクリックすると、以下のように開いて、繰り返し頻度を変更できます。私はここで30分にしました。作成時に頻度を指定した場合は、そこがもうその数字になっているはずです。(他のアクションでも折りたたまれている場合、題名部をクリックすれば展開され編集できます。)

スマートフォンでも作成はできるのですが(ブラウザもアプリもありますが)、フローの作成はPCのほうがやりやすい(画面の大きさの関係)でPCをおすすめします。

1. 取得したいカレンダーの番号.rssをGETする

PowerAutomateはHTTPリクエストを発出することができるのでそれでrssファイルをGETします。
「+新しいステップ」ボタンを押してアクション追加します。
そしてHTTPと入力して▼を押します。

するとHTTPのコネクタが見えるはずなので赤枠部をクリックしてください。

また赤枠部をクリック。

つぎにこんな感じに値を設定します。

右上の「・・・」から名前の変更を選択して、「HTTP」以外に変えておいてください。(名前がぶつかります。)
私は「HTTP2」にして進めます。

2. 文字列を抽出する

まず、AdventarのRSSを分析してどのように文字列を抽出するか考えます。
RSSの記事部はこんな感じです。(掲載の都合上空白などは一部変更しています。)

<item>
     <title><![CDATA[タイトル]]></title>
     <link>リンク</link>
     <guid>113185</guid>
     <pubDate>Sat, 05 Dec 2020 15:00:00 GMT</pubDate>
     <description><![CDATA[〇〇カレンダー 6日目]]></description>
     <dc:creator>投稿した人</dc:creator>
</item>

タイトル、リンク、○日目をsubstring()で取得します。AdventarのRSSは新しいものが上に増えていくので、タグをfind()したものを位置として最新のものを取得できそうです。
しかし、<item></item>より先に

<channel>
<title>東京高専プロコンゼミ② Advent Calendar 2020</title>
<link>https://adventar.org/calendars/5926</link>
<description>
中略
</channel>

のような感じでタグがあるので「<item><title>」,「]]></title><link>」のような感じで前の部分をつなげて取ることにします。空白などが邪魔なので空白や改行をすべて削除してから処理していきます。

先程のように「+新しいステップ」から変数を検索して変数の初期化を選びます。投稿時に値を使いたいので変数に保存するため変数を定義します。
このように種類を文字列に設定して、変数名はかぶらないようにしてください。

これを4つほど作ります。「Title」(題名),「P-link」(リンク),「Description」(概要),「Temp」(テンポラリーな文字列変数)
作成できたら「+新しいステップ」で「変数」と検索して「文字列変数に追加」を選択します。
まず空白などを除去するので、変数に「Temp」を設定し、値のテキストフィールド部をクリックしてください。すると右にこんなものがポップアップしてきます。

今回は「式」を選択します。
そしてfxのテキストフィールドに、

replace(replace(replace(string(actions('HTTP2').outputs.body),' ',''),decodeUriComponent('%0D'),''),decodeUriComponent('%0A'),'')

と入力してOKを押してください。するとこのような見た目になります。

これでTempにはGETしたRSSの中身の空白などが削除されたものが代入されました。
また「+新しいステップ」で「変数」と検索して「文字列変数に追加」を選択します。次は変数を「Title」に設定して、以下を式に設定してください。

substring(string(actions('HTTP2').outputs.body),add(indexOf(string(actions('HTTP2').outputs.body),'<![CDATA['),9),sub(indexOf(string(actions('HTTP2').outputs.body),']>'),add(indexOf(string(actions('HTTP2').outputs.body),'<![CDATA['),10)))

そして同様に変数を「Description」に設定して、以下を式に設定してください。

substring(string(actions('HTTP2').outputs.body),add(indexOf(string(actions('HTTP2').outputs.body),'<description><![CDATA['),22),sub(indexOf(string(actions('HTTP2').outputs.body),']]></description>'),add(indexOf(string(actions('HTTP2').outputs.body),'<description><![CDATA['),22)))

最後に「P-link」に以下を同様に代入しましょう。

substring(variables('Temp'),add(indexOf(variables('Temp'),']]></title><link>'),17),sub(indexOf(variables('Temp'),'</link><guid>'),add(indexOf(variables('Temp'),']]></title><link>'),17)))

これですべての値の準備は整いました。

3. 保存しておいた、「title.txt」の中身と取得したタイトルを比較する

前準備として自分のOneDriveにtitle.txtを保存しておきました。
空のテキストファイルを保存することはできないので適当な内容を入れておいてください。
「+新しいステップ」で「OneDrive」と検索して「パスによるファイルコンテンツの取得」を選択します。フォルダーマークをクリックするとこのようにポップアップ表示出てきます。

「>」をクリックしていって「title.txt」を選択します。

そうすると

/パス/title.txt

とファイルパスのテキストフィールドに入力されているはずです。
次に「+新しいステップ」で「コントロール」と検索して「条件」を選択します。
左の値の選択に式

string(actions('GetContent').outputs.body)

を設定して、「次の値に等しい」にします。
右の値には、動的なコンテンツから変数「Title」を選択してください。
設定を完了するとこのような見た目になります。

3-a. 違うならば投稿+中身を最新のものに更新

いいえの中のアクションの追加をクリックして、「OneDrive」と検索して「ファイルの更新」を選択します。

ファイルは先ほどと同様に「title.txt」を選択して、ファイルコンテンツフィールドには動的なコンテンツから変数「Title」を選択してください。
そのまま下のアクションの追加をクリックして、「Twitter」と検索して「ツイートの投稿」を選択します。いままでTwitterのコネクタを使用したことがなければ、ツイートしたいアカウントでログインしてください。(TwitterのAPIは不要です。)ツイートの投稿アクションの「・・・」を押して、マイコネクションの新しい接続の追加から他のアカウントに切り替えたりもできます。
ツイートのテキストに動的なコンテンツの追加からの変数の追加などで、言いたい本文を書きましょう。

同様に、下のアクションの追加をクリックして、「Teams」と検索して「メッセージをフローボットとしてチャンネルに投稿する」を選択します。
投稿したいチーム、チャネルを設定し、先程と同じようにメッセージを設定しましょう。

3-b. 同じならば、何もしない

「はい」配下のアクションを空っぽのままにしておけばいいだけです。

以上で完成です!

改善点

記事が滞納されていた場合で30分以内に2つの記事が新しく追加されると最新の日付しか取得できません。今回は時間がなかったので実装しませんでしたがForEachですべてのタイトルを取得、保存してそれと比べれば対応することが可能です。

他の手法

OneDriveにテキストファイルを保存して行っていますが、Excelファイルで保存したり、データベースを活用すると改善点で触れた複数タイトルの保存などもやりやすいと思います。

コード部分の説明

わかりやすく改行してコメントをつけて説明します。

スペースなどの除去

replace( // replace(もとの文字列,置換する文字,置換される文字)はreplaceし終わった結果の文字列を返します。
    replace(
        replace(
            string(actions('HTTP2').outputs.body), //もとの文字列です。actions(アクション名).outputs.プロパティで指定のアクションの任意の値を取得できます。
            ' ', //取り除く文字(空白です。)
            '' //変わりの文字(削除したいので何もないを指定しています。)
        ),
        decodeUriComponent('%0D'),// 取り除く文字(CRです。)
        '' 
    ),
    decodeUriComponent('%0A'),// 取り除く文字(LFです。)
    ''
)

タイトル抽出

substring( //substring(もとの文,はじめの位置,切り取る文字数)はsubstringした結果の文字列を返します
    string(actions('HTTP2').outputs.body),//もとの文字列です。actions(アクション名).outputs.プロパティで指定のアクションの任意の値を取得できます。
    add( //add(たされる数,たす数)はaddした結果の値を返します。
        indexOf( //indexOf(調べられる文字列,調べる文字列)は発見した位置を返します
            string(actions('HTTP2').outputs.body), 
            '<![CDATA['
        )
        ,9
    ),
    sub( //sub(引かれる数,引く数)はsubした結果の値を返します。
        indexOf(
            string(actions('HTTP2').outputs.body)
            ,']>'
        ),
        add(
            indexOf(
                string(actions('HTTP2').outputs.body),
                '<![CDATA['
            ),
            10
        )
    )
)

Descriptionは文字列が違うだけで同じです。

P-link

substring(
    variables('Temp'),
    add(
        indexOf(
            variables('Temp'), //variables(変数名)で指定した変数の値を取得できます。
            ']]></title><link>'
        ),
        17
    ),
    sub(
        indexOf(
            variables('Temp'),
            '</link><guid>'
        ),
        add(
            indexOf(
                variables('Temp'),
                ']]></title><link>'
            ),
            17
        )
    )
)

おわりに

どうでしたでしょうか?
Qiitaで記事を書くのが初めてで、Qiitaに合った書き方をできているかわかりませんが、PowerAutomateに興味を持っていただけたり、なにかの問題の解決に役立てていたら幸いです。
技術的な記事(自分の他のとこで書いた記事)はQiitaに転載してもいいかもしれませんね。
誤字脱字や違うよってことがあれば教えてください!Twitterやってないのでコメント欄?とかで教えていただければ修正します。

謝辞

@TakamiChieさんの記事がなければ私はW3CのRSSヴァリデートサービスにいれて確認する必要があるなんて全然気がつかなかったと思います。とても感謝しています。
TwitterのAPIキー取得も、PythonをHerokuとかにデプロイしたりもしないでできたのはMicrosoftのPowerAutomateのおかげです!
そして、ここまで読んでくださりありがとうございました!
メリークリスマス!

カレンダー次回予告

明日(12/20)は
①のカレンダーでshibh308さんの担当記事「非公式高専プロコンの話にすると思います」(12/18現在)
②のカレンダーでmakiartさんの担当記事「爆速でwebサイトを公開する」(12/18現在)
らしいです!お楽しみに!

補足

†1:特定のアクションはそのサービスのアカウントを持っている、サブスクリプションなどライセンスを所持している必要があります。