私でもできる！discord上にあるファイルをプレビュー表示するbot作成

:::note warn
ここで言う「私でもできる！」は筆者でもできるという意味であり、これを読んだ全ての人ができるという意味ではないので注意
:::

## 結論
[ここにソースコードがあります](https://github.com/yanbaru1331/PDFConvPic-DiscordBot)
## 背景
discordでファイルを共有するとプレビュー(スマホ版だとリンク先で確認できる)ができず、送信されたファイルをクリックすると強制的にダウンロードされてしまうのでbotにプレビューっぽいものを実装してPCにダウンロードせずに中身を確認をする。

## 要件
- 利用者目線
    - 色々な発火(コマンドの起動)方法が欲しい
    - pdfを画像に変換できる
    - チャットを汚したくないのでスレッドを作ってそこに送りたい
    - ついでに文字起こしもしたい

- 開発者目線
    - 仮想環境で運用
    - poetryを使いたい

## 実装
### コマンドの発火まで
まず実行されたのがスレッドなのかそれ以外なのかを判断する。
`try excpet`はあまりよろしくないが、とりあえず気合いの実装
スレッドがあるかどうかを判断してる
```py
async def pdf(ctx, *args):
    try:
        checkId = ctx.message.channel.parent.id
        threadId = ctx.message.channel.id
        thFlug = 1
    except:
        thFlug = 0
```

### スレッドを作って変換関数の実行
スレッドがあるかどうかを判断して、無いなら`日付`+`conversion image`でスレッドを作る。
あるならスレッドの中にスレッドは作れないのでそのままそこに送る。

完全に別スレッドに画像ファイルを隔離したいぜ！みたいな人は上のスレッドの有無は要らなくてただ単にスレッドをチャット内に生成すればOK
```py
    if thFlug == 0:
        thread = await ctx.message.create_thread(
            name=(f"{datetime.datetime.now().strftime('%Y%m%d%H%M')}conversion image"),
            auto_archive_duration=10080,
            slowmode_delay=0
        )
    elif thFlug == 1:
        thread = client.get_channel(threadId)

    # 以下の処理でmessageはmessageオブジェクトを指しているので
    message = ctx.message
    for attachment in message.attachments:
        # pdf以外を除外
        if attachment.content_type != "application/pdf":
            continue
        #   処理関数の実行
        await conv_pdf(attachment, message, thread)
```

### pdfを画像に変換する関数
[pdfを画像に変換して送信する部分](https://qiita.com/mov_huyu/items/3eb97a35e316cc567d01)[1]は既に実装されたものがあったのでそれを関数化

```py
async def conv_pdf(attachment, message, thread):
    # ファイルの保存
    await attachment.save(f"{message.id}.pdf")
    images = pdf2image.convert_from_path(f"./{message.id}.pdf")
    for index, image in enumerate(images):
        # 画像変換
        image.save(f"{message.id}-{str(index+1)}.jpg")
        # 画像送信
        await thread.send(file=discord.File(f"{message.id}-{str(index+1)}.jpg"))
        # 画像削除
        os.remove(f"{message.id}-{str(index+1)}.jpg")
    # pdfの削除
    os.remove(f"{message.id}.pdf")
```

### 文字起こしの関数
関数以外は大体同じなので関数だけ
Nitro以外は2000文字まで送信できるので1900文字で切って繰り返し送信するように実装

読み取り処理の部分はこの[サイト](https://dev.classmethod.jp/articles/export-text-data-from-image-using-python/)[2]を参考に作成

```py
async def conv_text(attachment, message, thread):
    tools = pyocr.get_available_tools()
    if len(tools) == 0:
        await ctx.reply("OCRが起動してません。管理者に問い合わせてください")    
    
    tool = tools[0]
    await attachment.save(f"{message.id}.pdf")
    images = pdf2image.convert_from_path(f"./{message.id}.pdf")
    #lang = 'eng'
    lang = 'jpn'
    text = ""
    # 画像オブジェクトからテキストに
    for image in images:
        tmp = ""
        tmp = tool.image_to_string(
            image,
            lang=lang,
            builder=pyocr.builders.TextBuilder()
        )
        text = text + tmp
    
    for i in range(-(len(text)//-1900)):
        await thread.send(text[:1900])
        text = text[1900:]
```

### 残りのところ
ファイルが添付されたメッセージのリクエスト形式が
- 返信
    ex)添付されたメッセージに返信して`!pdf`
- メンション
    ex)ファイルを添付して`!pdf`
- discordのURL添付
    ex)discordのURLを添付して`!pdf` 

に分かれるのでそれぞれの場合に応じて
- 返信
    返信先のメッセージ取得
    添付ファイルの取得
- discordのURL添付
    URLリンク先のメッセージの取得
    添付ファイルの取得
- メンション
    添付ファイルの取得 

みたいな処理が必要になる。
これを気合いで実装する

### Dockerfileを作る

pythonが動く環境とpoetryとpopplerやocrのインストール

```dockerfile
FROM python:3.11

# 定義するもの
ENV PATH="/root/.local/bin:$PATH" 
ENV POETRY_HOME=/opt/poetry
WORKDIR /RePdf

#実行するもの
RUN wget -O - https://install.python-poetry.org | python3 - \
    && cd /usr/local/bin \
    && ln -s /opt/poetry/bin/poetry \
    && poetry config virtualenvs.create false \
    && apt update \
    && apt install poppler-utils poppler-data -y \
    && apt install tesseract-ocr libtesseract-dev poppler-utils tesseract-ocr-jpn -y \
```
### compose.yamlを作る
ファイルは保存して変換後に消すので永続的に保存する必要がない。なので永続化する必要なし
```yaml
services:
  python:
    build: .
    volumes:
      - .:/RePdf
    tty: true
```


## 実行
```
sudo docker compose build 
docker compose up -d
```
コンテナ内に入ったら`.env`を作成して
```env
DISCORD_TOKEN=TOKEN
```
を作成する。

`.env`ファイルがあるディレクトリで
```
poetry shell
python __init__.py 
```
で動く

## 困ったこと
- `.env`ファイル作っただけだとうまくenvファイルの参照ができなくて困った
```py
dotenv_path = join(dirname(__name__), '.env')
load_dotenv(verbose=True, dotenv_path=dotenv_path)
```
を追加してenvファイルを読み込む必要があった

- 急にpdfから画像への変換ができなくなった

同じディレクトリでもパスを書かなきゃいけないらしく
```
images = pdf2image.convert_from_path(f"{message.id}.pdf")
```
ここの
`"{message.id}.pdf"`を`"./{message.id}.pdf"`にする必要がある

## 参考文献
[1][Discordに送信されたPDFファイルを画像ファイルに変換して送信](
https://qiita.com/mov_huyu/items/3eb97a35e316cc567d01)
[2][Pythonでpdfを画像として認識しテキストを抽出を試してみる(pyocr)](https://dev.classmethod.jp/articles/export-text-data-from-image-using-python/)