NodeJSで遠隔操作してみた

暇だったので、nodeで簡単な遠隔操作をしてみることにしました
環境:
OS: `MacOS`
editor: `vscode`

:::message alert
同じWiFiにつながってないと他のパソコンや端末からアクセスできません！
:::

---
### デレクトリ：
```sh
.
├── test.mp3 <= 3
├── index.html <= 2
└── server.js <= 1

1 directory, 5 files
```
---
### ソースコード：
`index.html`と`server.js`を作成してください

:::details 1のコード
```js:main.js
const http = require("http");
const fs = require("fs");
const PORT = 8000;
const html = fs.readFileSync("./index.html", "utf8");
const player = require("play-sound")((opts = {}));
const filePath = "test.mp3";
const { styleText } = require('node:util')

// ユーザー名とパスワードを設定
const USERNAME = "admin";
const PASSWORD = "password";

// isSelectedの状態を保持する
let isRunning = false; // 処理が起動しているかどうかを示すフラグ
let intervalId = null; // 処理を継続するためのID

// HTTP Basic Authenticationをチェック
const authenticate = (req) => {
  const auth = req.headers["authorization"];
  if (!auth) {
    return false; // 認証がない
  }
  const [username, password] = Buffer.from(auth.split(" ")[1], "base64")
    .toString()
    .split(":");
  return username === USERNAME && password === PASSWORD;
};

// サーバーを作成
const server = http.createServer((req, res) => {
  if (!authenticate(req)) {
    res.writeHead(401, { "WWW-Authenticate": "Basic" });
    res.end("Authorization required");
    return;
  }

  // 認証成功時の処理
  if (req.method === "GET") {
    const errorMessage = styleText('green', 'GET :');
    console.log(errorMessage);
    res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
    res.end(html);
  } else if (req.method === "POST" && req.url === "/toggle") {
    const errorMessage = styleText('green', 'POST :');
    console.log(errorMessage);
    let body = "";
    req.on("data", (chunk) => {
      body += chunk;
    });
    req.on("end", () => {
      try {
        const { isSelected } = JSON.parse(body);
        handleToggle(isSelected, res);
      } catch (error) {
        console.error("JSON parsing error:", error);
        res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
        res.end(JSON.stringify({ error: "Invalid JSON" }));
      }
    });
  } else {
    res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
    res.end("Not Found");
  }
});

// 処理の開始または停止
const handleToggle = (isSelected, res) => {
  if (isSelected && !isRunning) {
    isRunning = true;
    const errorMessage = styleText('blue', '開始します');
    console.log(errorMessage);
    intervalId = setInterval(() => {
      const errorMessage = styleText('red', 'StartMp3');
      console.log(errorMessage);
      player.play(filePath, (err) => {
        if (err) {
          console.log(`エラーが発生しました: ${err}`);
        }
      });
    }, 1000); // 1秒ごとにログを出力
  } else if (!isSelected && isRunning) {
    isRunning = false;
    const errorMessage = styleText('blue', '停止します');
    console.log(errorMessage);
    clearInterval(intervalId);
    intervalId = null;
  }

  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
  res.end(JSON.stringify({ message: "State received" }));
};

// サーバーを起動
server.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

```
:::


:::details 2のコード
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>遠隔操作ボタン</title>
    <style>
        body {
            background-color: #1a1a1a;
            color: #fff;
            font-family: 'Arial', sans-serif;
            overflow: hidden;
        }

        .center {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            position: relative;
        }

        .toggle-btn {
            background-color: #007bff;
            color: #fff;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 1.5em;
            position: relative;
            outline: none;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
            transition: transform 0.3s;
            z-index: 1;
        }

        .selected {
            background-color: #26ff00;
            animation: glow 1s infinite alternate;
        }

        @keyframes glow {
            from {
                box-shadow: 0 0 20px rgba(47, 255, 0, 0.5);
            }
            to {
                box-shadow: 0 0 40px rgb(94, 255, 0);
            }
        }

        .error-message {
            color: rgb(115, 255, 0);
            font-weight: bold;
            position: absolute;
            top: 0; /* ボタンの上に表示 */
            left: 50%;
            transform: translateX(-50%);
            display: none; /* 初期は非表示 */
            z-index: 2;
        }
        .server-message {
            color: blue;
            font-weight: bold;
            position: absolute;
            top: 0; /* ボタンの上に表示 */
            left: 50%;
            transform: translateX(-50%);
            display: none; /* 初期は非表示 */
            z-index: 2;
        }

        .server_err {
            background-color: rgb(251, 59, 59);
            color: #fff;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            /*cursor: pointer;*/
            font-size: 1.5em;
            position: relative;
            outline: none;
            /*box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);*/
            transition: transform 0.3s;
            z-index: 1;
        }
        .server_sf {
            background-color: rgb(78, 99, 255);
            color: #fff;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            /*cursor: pointer;*/
            font-size: 1.5em;
            position: relative;
            outline: none;
            /*box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);*/
            transition: transform 0.3s;
            z-index: 1;
        }
    </style>
</head>
<body>
    <div class="center">
        <div class="server_sf" id="serverMessage">安全</div>
        <div class="server_err" id="errorMessage">エラー</div>
        <button class="toggle-btn" id="toggleButton">実行</button>
    </div>

    <script>
        const button = document.getElementById('toggleButton');
        const errorMessage = document.getElementById('errorMessage');
        const serverMassage = document.getElementById('serverMessage');
        let isSelected = false;

        errorMessage.style.display = 'none'; // 正常な応答時にエラーメッセージを非表示
        serverMassage.style.display = 'block'; // 正常な応答時にエラーメッセージを非表示

        button.addEventListener('click', () => {
            isSelected = !isSelected;
            button.classList.toggle('selected', isSelected);
            button.textContent = isSelected ? "運転中..." : "実行";

            fetch('/toggle', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ isSelected })
            })
            .then(response => {
                console.log('サーバーの応答:', response); // サーバー応答のデバッグ
                if (!response.ok) {
                    throw new Error("サーバーの応答が不正です");
                }
                return response.json();
            })
            .then(data => {
                console.log('サーバーからの応答:', data);
                errorMessage.style.display = 'none'; // 正常な応答時にエラーメッセージを非表示
                serverMassage.style.display = 'block'; // 正常な応答時にエラーメッセージを非表示
            })
            .catch(error => {
                console.error('エラー:', error);
                console.error('サーバーからの応答')
                errorMessage.style.display = 'block'; // エラー時にエラーメッセージを表示
                serverMassage.style.display = 'none'; // 正常な応答時にエラーメッセージを非表示
            });
        });
    </script>
</body>
</html>
```
:::
:::details 3のmp3
mp3のフリー音源を探してきて、作業デレクトリにもっていってtest.mp3と名前を変えればOK
:::

:::details 実行
```sh
npm install play-sound
node main.js
```
そしたら[http://localhost:8000](http://localhost:8000)に移動して
あと一応パスワードをつけといたんで、`admin` `password`ログインできますが
```js:server.jsの一部分
// ユーザー名とパスワードを設定
const USERNAME = "admin";
const PASSWORD = "password";
```
ここの部分で設定できます
:::
### 最後に
:::details 重要
他のパソコンや端末でやてみたいと思うかもしれませんが、[http://localhost:8000](http://localhost:8000)このURLを打ち込んでもうまくいきません！
ターミナルで
```sh
ifconfig
```
でわかります
:::
:::details 僕の場合
```sh
% ifconfig   
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
	inet 127.0.0.1 netmask 0xff000000 <= この行！
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
	nd6 options=201<PERFORMNUD,DAD> ...
```
つまり他のパソコンや端末で動かすためには、
調べたやつ：`127.0.0.1` `:8000`ということなので[http://127.0.0.1:8000](http://127.0.0.1:8000)に行けばうまくいきます
:::
よくわからない時は`Gemini`や`ChatGPT`に聞くとわかったりします