LoginSignup
1
1

私でもできる!discordのpdf変換bot作成

Last updated at Posted at 2023-10-09

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

結論

ここにソースコードがあります

背景

discordでファイルを共有するとプレビュー(スマホ版だとリンク先で確認できる)ができず、送信されたファイルをクリックすると強制的にダウンロードされてしまうのでbotにプレビューっぽいものを実装してPCにダウンロードせずに中身を確認をする。

要件

  • 利用者目線

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

    • 仮想環境で運用
    • poetryを使いたい

実装

コマンドの発火まで

まず実行されたのがスレッドなのかそれ以外なのかを判断する。
try excpetはあまりよろしくないが、とりあえず気合いの実装
スレッドがあるかどうかを判断してる

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

    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を画像に変換して送信する部分[1]は既に実装されたものがあったのでそれを関数化

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文字で切って繰り返し送信するように実装

読み取り処理の部分はこのサイト[2]を参考に作成

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のインストール

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を作る

ファイルは保存して変換後に消すので永続的に保存する必要がない。なので永続化する必要なし

services:
  python:
    build: .
    volumes:
      - .:/RePdf
    tty: true

実行

sudo docker compose build 
docker compose up -d

コンテナ内に入ったら.envを作成して

DISCORD_TOKEN=TOKEN

を作成する。

.envファイルがあるディレクトリで

poetry shell
python __init__.py 

で動く

困ったこと

  • .envファイル作っただけだとうまくenvファイルの参照ができなくて困った
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ファイルを画像ファイルに変換して送信
[2]Pythonでpdfを画像として認識しテキストを抽出を試してみる(pyocr)

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1