Scratchに画像を送信する

# はじめに
こんちゃ。Scratchでサーバーからプロジェクトに画像を送信できるようになったのでここに詳細を書きます
[具体的な活用例です](https://scratch.mit.edu/projects/1030739324/)
本編行きます

# 使ったもの
[Node.js](https://nodejs.org/en/)

## ライブラリ
[Scratch-api](https://www.npmjs.com/package/scratch-api)
[Jimp](https://www.npmjs.com/package/jimp)

## サービス
[YouTube Data API (v3)](https://developers.google.com/youtube/v3?hl=ja)
[Google Apps Script](https://workspace.google.com/intl/ja/products/apps-script/) (以下GAS)

# 仕組み
1. Youtubeからサムネイル画像のURLを取ってくる
2. それをいい感じのサイズに加工 (ここでは120x90)
3. それを15x15の画像に分割
4. 画像を送れるように変換
5. 送信する

# 詳しく
## 1. Youtubeからサムネイル画像のURLを取ってくる
ここでGAS登場。
GASのYoutube Data APIを使ってサムネイルのURLを取得します。
簡単なコード

```javascript:getYoutube.gs
function doGet(e){
  try{
    const result = YouTube.Videos.list("snippet", {id: e.parameter.id});
    const url = result.items[0].snippet.thumbnails.high.url;
    Logger.log(url)
    return ContentService.createTextOutput(JSON.stringify({
      "url": url
    })).setMimeType(ContentService.MimeType.JSON);
  }catch(e){
    return ContentService.createTextOutput(JSON.stringify({
    "url": "",
    "msg": e.message
  })).setMimeType(ContentService.MimeType.JSON);
  }
}
```

これを[デプロイ](https://wa3.i-3-i.info/word16767.html)して使用します

## 2. それをいい感じのサイズに加工
Jimpを使います
```javascript:script.js
const Jimp = require('jimp');

const process_img = async (img_buf) => {
let img = (await Jimp.read(img_buf))
    .resize(120, 90);
    
    return img.getBufferAsync(Jimp.MIME_PNG);
};
```
ローカルに展開したくないので[バッファ](https://qiita.com/Yuki_Oshima/items/6346231d93ca342899f0)を使ってます

## 3. それを15x15の画像に分割
[ChatGPT](https://chatgpt.com/)に書いてもらいました。優秀。
```javascript:write_by_chatGPT.js
// write by ChatGPT
async function splitImageIntoSubImages(
    imageBuffer,
    width,
    height,
    subWidth,
    subHeight
) {
    try {
        // Jimpを使用して画像を読み込む
        const image = await Jimp.read(imageBuffer);

        // サブ画像を格納する配列
        let subImages = [];

        // 10x10のサブ画像を生成して配列に格納する
        for (let y = 0; y < height / subHeight; y++) {
            for (let x = 0; x < width / subWidth; x++) {
                const subImage = image
                    .clone()
                    .crop(
                        x * subWidth,
                        y * subHeight,
                        subWidth,
                        subHeight
                    );
                subImages.push(
                    await subImage.getBufferAsync(Jimp.MIME_PNG)
                );
            }
        }

        return subImages;
    } catch (error) {
        throw error;
    }
}
```

## 4. 画像を送れるように変換
それぞれのピクセルのデータを以下のように変換します
```
[
    [R, G, B],
    [R, G, B],
    [R, G, B],
    ...
]
```
ついでにそれぞれの数値を3桁になるようにゼロ埋めして全部つなげます

これもChatGPT。賢い。(少し手は加えた)
```javascript:iikanji_ni_suru.js
// by ChatGPT
async function pngBufferToArray(pngBuffer) {
    try {
        const image = await Jimp.read(pngBuffer);
        const width = image.bitmap.width;
        const height = image.bitmap.height;
        let pixelArray = [];

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const { r, g, b } = Jimp.intToRGBA(
                    image.getPixelColor(x, y)
                );
                pixelArray.push([r, g, b]);
            }
        }

        pixelArray = pixelArray.map((row) => {
            return row.map((col) => {
                return col.toString().padStart(3, '0');
            });
        });

        pixelArray = pixelArray.map((row) => row.join(''));

        return pixelArray.join('');
    } catch (error) {
        console.error('Error processing image:', error);
        return null;
    }
}
```
これでとりあえず送るデータは完成。ということで送りましょう

## 5. 送信する
クラウド変数を使って送ります。

:::note warn
クラウド変数の反映には0.1秒間隔があることに注意してください
:::

15x15の画像なら8つクラウド変数を用意すれば一度に送れます。
こうやって
```javascript
function setCloud(str, base_name, cloud) {
    let cloudData = split_n(255, str);

    let i = 0;

    cloudData.forEach((data) => {
        let name = base_name + i.toString();

        cloud.set(name, data);

        i++;
    });
}
```
一気にセットできるやつを作成してそれを使います
(split_nは[ここ](https://qiita.com/turmericN/items/0819317b5c075d971bfa)から)

こんな感じで送信します
```javascript
for (let i = 0; i < imgDat.length; i++) {
    setCloud(imgDat[i], base_name, cloud);
    console.log(`packet${i} send complete`);

    await sleep(500); // 間隔開ける!!

    cloud.set(`${base_name}8`, i + 1);

    await sleep(500);
}
```

以上。これをつなぎ合わせただけでは動きません。
各自補完してください

# あとがき
めっちゃ短い。まぁ1時間クオリティだしなぁ
15x15に分けて送信するのは[パケット](https://wa3.i-3-i.info/word176.html)とかの発想から持ってきた。
15x15なのは120 90どちらも15で割り切れるから&クラウド変数の使用数が10個を超えないから。
ほんじゃまた。

なんかBuffer非推奨とか言われてる...もう作っちゃったしいいや
またいつか改善するわ

[追記]
Glitchにデプロイしました。Glitch便利ですね

[追追記]
~~なんかGlitchで動作しませんでした。なんでやろなぁ~~
名無しのNさんからnodejsのバージョンを調整すればいいと教えてもらいました。
ありがとうございます