Pipedreamを使ってリストに入れたツイッターアカウントのツイートを監s...記録する

# この記事の対象者
* サイバーストーカー
* 初心者のjavascriptのコードを見ても吐き気がしない人

# Pipedreamとは
https://pipedream.com/
無料でも利用できる、トリガーとアクションを組み合わせてワークフローを作成し、色々できるサービスです。
Twitter Youtube Discord Slackなど様々なアプリケーションと連携することが出来ます。
類似のサービスにIFTTTなどがありますが、IFTTTよりも柔軟性が高いです。

# Pipedreamを使ってリストに入れたツイッターアカウントのツイートを記録する
## Twitterアカウントと連携する
最初からリストに新しいツイートがあるのかを調べるトリガーがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984781/c8629b28-96f2-219e-7c68-f5e42a6d1dd2.png)
簡単ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984781/63f7178a-a8aa-facc-9e19-5fb12eddfecd.png)

## ツイートを記録する
Discordのwebhookを使ったりGoogleSheetに記録するアクションがあるのでそれを使えばいいのですが、メディアファイルを記録できなかったりと少し使いにくいので一からコードを書きます。
好きなようにしてください。
途轍もなく汚いコードなので各自で修正してください。

```javascript
import axios from "axios";
import FormData from "form-data";

export default defineComponent({

    async run({ steps, $ }) {

        const avatarURL = steps.trigger.event.user.profile_image_url_https
        const username = steps.trigger.event.user.name + "@" + steps.trigger.event.user.screen_name
        const message = username + "\n" + steps.trigger.event.full_text
        const webhook_url = "https://discord.com/api/webhooks/みたいな感じのdiscordのwebhookのurl"

        async function getFileSize(url) {
            const file = await axios({
                method: "head",
                url: url,
                responseType: "stream",
            });
            if (file.status < 400) {
                return file.headers["content-length"]
            }
            return -1
        }

        async function maxIndex1(variants) {
            let value = -Infinity;
            let index = -1;
            for (const [i, variant] of variants.entries()) {
                if (variant.bitrate > value && (await getFileSize(variant.url)) < 8 * 1024 * 1024) {
                    value = variant.bitrate;
                    index = i;
                }
            }
            return index;
        }

        async function sendMessage({
            webhook_url, content, username, avatarURL, threadID,
        }) {
            const serializedContent = (typeof content !== "string")
                ? JSON.stringify(content)
                : content;
            threadID = threadID || undefined;

            const resp = await axios({
                method: "POST",
                url: webhook_url,
                headers: {
                    "Content-Type": "application/json",
                },
                validateStatus: () => true,
                params: {
                    thread_id: threadID,
                },
                data: {
                    content: serializedContent,
                    username,
                    avatar_url: avatarURL,
                },
            });
            if (resp.status >= 400) {
                throw new Error(JSON.stringify(resp.data));
            }
            return resp.data;
        }

        async function sendMessageWithFile({
            webhook_url, content, username, avatarURL, fileUrl,
        }) {
            const file = await axios({
                method: "get",
                url: fileUrl,
                responseType: "stream",
            })
            if (file.status >= 400) {
                throw new Error(file.data);
            }

            const data = new FormData();
            const serializedContent = (typeof content !== "string")
                ? JSON.stringify(content)
                : content;
            data.append("payload_json", JSON.stringify({
                content: serializedContent,
                username,
                avatar_url: avatarURL,
            }));
            if (file) data.append("file", file.data);

            const resp = await axios({
                method: "POST",
                url: webhook_url,
                headers: {
                    "Content-Type": "multipart/form-data; boundary=" + data._boundary,
                },
                validateStatus: () => true,
                data,
                file,
            });
            if (resp.status >= 400) {
                throw new Error(JSON.stringify(resp.data));
            }
            return resp.data;
        }

        if ("extended_entities" in steps.trigger.event) {
            let extended_entities = steps.trigger.event.extended_entities
            let media_sum = extended_entities.media.length

            for (let media_num = 0; media_num < media_sum; media_num++) {
                let media_url = extended_entities.media[media_num].media_url_https

                if ("video_info" in extended_entities.media[media_num]) {
                    let video_info = extended_entities.media[media_num].video_info
                    let max_bitrate_variant = await maxIndex1(video_info.variants)
                    console.log(max_bitrate_variant)
                    media_url = video_info.variants[max_bitrate_variant].url.split('?')[0]
                }

                try {
                    await sendMessageWithFile({
                        webhook_url: webhook_url,
                        avatarURL: avatarURL,
                        username: username,
                        fileUrl: media_url,
                        content: (media_num == 0) ? message : "",
                    });
                } catch (err) {
                    throw err;
                }
            }
            return "with " + media_sum.toString() + " media(s)."
        } else {
            try {
                await sendMessage({
                    webhook_url: webhook_url,
                    avatarURL: avatarURL,
                    username: username,
                    content: message,
                });
            } catch (err) {
                throw err;
            }
            return "no media"
        }
    },
})
```

## 注意事項
無料版だとワークフローの実行回数に制限があるのでPollingIntervalを調整してください。
pollとワークフローの実行はそれぞれ別のものとしてカウントされます。
ツイートが多ければそれだけ早く無料枠を消費します。
それとpollで見つかった全てのツイートに対して同時実行されるのでDiscordのレート制限にひっかかります。
必ず設定のExecution ControlsからThrottle workflow executionを有効にして間隔をあけるようにしてください。
また、複数の動画があるメディアは最初のメディアしか認識できません。
多分Piepdreamの仕様です。
こういう諸々の問題はアクションも含めてコードを書けば解決できると思うので、これを読んでいるあなたに任せます。