去年もやったけど「DiscordBOTでも作ってみない？」4日目


# おさらい

```py
import asyncio
import os

import nextcord
from dotenv import load_dotenv
from nextcord.ext import commands

load_dotenv()

bot = commands.Bot()

@bot.slash_command()
async def git(interaction: nextcord.Interaction):
    pass

@git.subcommand()
async def status(interaction: nextcord.Interaction):
    process = await asyncio.create_subprocess_shell("git status", stdout=asyncio.subprocess.PIPE)
    stdout, stderr = await process.communicate()
    await interaction.response.send_message(embed=nextcord.Embed(
        title="Git Status Result",
        description=f"```sh\n{stdout.decode('utf-8')}```"
    ))

if __name__ == "__main__":
    token = os.getenv("BOT_TOKEN")
    if token is None:
        print("TOKENが設定されていません。")
    else:
        bot.run(token)
```

# 仕上げにArduinoCLIをｶﾞｯﾁｬﾝｺ...

## Remember me?

ArduinoCLIのコマンドの構造は覚えていますでしょうか。
今回はコンパイルコマンドを使用して、コンパイル結果を送信するような構造を作っていきましょう。

まずは、Arduinoコマンドグループを作成します。

```py
...

@bot.slash_command()
async def arduino(interaction: nextcord.Interaction):
    pass


...
```

次に、コンパイルコマンドを作成します。

## ArduinoCLIのコンパイルコマンド

```py
...

@arduino.subcommand()
async def compile(interaction: nextcord.Interaction):
    process = await asyncio.create_subprocess_shell("arduino-cli compile", stdout=asyncio.subprocess.PIPE)
    stdout, stderr = await process.communicate()
    await interaction.response.send_message(embed=nextcord.Embed(
        title="Arduino Compile Result",
        description=f"```sh\n{stdout.decode('utf-8')}```",
        color=0x008184
    ))

...
```

さて、ここで問題があります。
FQBNやプロジェクトのパスを指定しないとコンパイルできないのですが、これをどうやって指定するかです。

## FQBN・プロジェクトを指定できるようにする

```diff_python
  ...

  @arduino.subcommand()
- async def compile(interaction: nextcord.Interaction):
-     process = await asyncio.create_subprocess_shell("arduino-cli compile", stdout=asyncio.subprocess.PIPE)
+ async def compile(interaction: nextcord.Interaction, project: str, fqbn: str = "esp32:esp32:esp32"):
+     process = await asyncio.create_subprocess_shell(f"arduino-cli compile -b {fqbn} {project}", stdout=asyncio.subprocess.PIPE)
      stdout, stderr = await process.communicate()
      await interaction.response.send_message(embed=nextcord.Embed(
          title="Arduino Compile Result",
          description=f"```sh\n{stdout.decode('utf-8')}```",
          color=0x008184
      ))

  ...
```
これで、FQBN及びプロジェクトを指定してコマンドを実行（指定しない場合はデフォルトの`esp32:esp32:esp32`でコマンドを実行）することができるようになりました。

※このコマンドには明らかな脆弱性（文字列をそのまんまコマンドラインに流している）があるので、実際に使うときはスラッシュコマンドの実行権限設定をちゃんとするか、エスケープなどの処理を行ってください。

## あとちょっと！

```diff_python
  ...

  @arduino.subcommand()
  async def compile(interaction: nextcord.Interaction, project: str, fqbn: str = "esp32:esp32:esp32"):
      process = await asyncio.create_subprocess_shell(f"arduino-cli compile -b {fqbn} {project}", stdout=asyncio.subprocess.PIPE)
+     await interaction.response.defer()
      stdout, stderr = await process.communicate()
-     await interaction.response.send_message(embed=nextcord.Embed(
+     await interaction.send(embed=nextcord.Embed(
          title="Arduino Compile Result",
          description=f"```sh\n{stdout.decode('utf-8')}```",
          color=0x008184
      ))

  ...
```

1. `interaction.response.defer()`を追加
  これは、本来であれば3秒ぐらい以内にメッセージを返信しなければエラーになってしまうのですが、このコマンドはコンパイルに時間がかかるため、5秒以内にメッセージを返信することができません。
  そこで、この`defer()`を使用すると、猶予時間が3秒から15分ぐらいに伸びます。
  これで、コンパイルが終わるまで待つことができます。

2. `interaction.response.send_message`を`interaction.send`に変更
  これは、`interaction.response.send_message`は、`interaction.response.defer()`を使用している場合に使用するメソッドで、`interaction.send`は、`interaction.response.defer()`を使用していない場合に使用するメソッドです。
  今回は`interaction.response.defer()`を使用しているので、`interaction.response.send_message`ではなく`interaction.send`を使用する必要があります。

## ほんとに最後！

```diff_python
  ...

  @arduino.subcommand()
  async def compile(interaction: nextcord.Interaction, project: str, fqbn: str = "esp32:esp32:esp32"):
      process = await asyncio.create_subprocess_shell(f"arduino-cli compile -b {fqbn} {project}", stdout=asyncio.subprocess.PIPE)
      await interaction.response.defer()
      stdout, stderr = await process.communicate()

+     project_name = project.split("/")[-1]
+     fqbn_path = fqbn.replace(":", ".")
+     file_name = f"{project_name}.ino.{'bin' if fqbn_path.startswith('esp32') else 'zip'}"
+     file_path = f"{project}/build/{fqbn_path}/{file_name}"

      await interaction.send(embed=nextcord.Embed(
          title="Arduino Compile Result",
          description=f"```sh\n{stdout.decode('utf-8')}```",
          color=0x008184
-     ))
+     ), file=nextcord.File(file_path, filename=file_name))

  ...
```

これで、コンパイル結果をファイルとして送信することができるようになりました。
あとは、ダウンロードしたファイルをマイコンに書き込むだけです。これにて終わり！

# まとめ...ない！

残り時間は29時間。

というわけで、「DiscordBOTでも作ってみない？」