工学研究部のドベカレ( UEC koken Advent Calendar 2020 )の記事です。
昨年、私が浪人していた頃、はこだて未来大学に在籍している私の友人にとあるLT会に誘われました。夏休みの自由研究発表といった形で行われた1年生のLT会で、そこで私は初めてdiscordのbotとpythonに出会いました。以来、自作の謎botを高校のオタク友人らのサーバで動かしていました。そしてつい先日、金曜の夕方vcをしていたところ…
友人「お前のbotでlatex書けねえの?」
血迷った私「できるんじゃね?日曜までに作るわ!」
他人事ですよね。作るのは私なんですけど!!そんなこんなでtex文書をコンパイルしてpngで返すbotを作りました。 動作環境はlubuntu18.04、python3.7.6です。ちなみに金曜日なので部室で部会に参加していたのですが、部屋にいた部員の皆さんの反応は「要らなくね?笑」一色でした。まあ私も要らないと思います。それでは作り始めます。とりあえず構想はこんな形です。
メッセージうけとる
↓
何かに渡す
↓
生成されたpngを送り返す
できました。
if message.content.startswith("latex"):
text = message.content
text = text[8:-3]
imgpath = "./latexondiscord/image.png"
#生成される画像のパス
formerupdatetime = os.path.getctime(imgpath) #画像の更新時刻を確認
latexmessage(text) #画像作成(「何か」の部分)
nowupdatetime = os.path.getctime(imgpath) #画像の更新時刻を確認
if formerupdatetime == nowupdatetime: #画像の更新がされていないとき
await message.channel.send("Syntax error!!")
else: #画像がきちんと更新されているとき
await message.channel.send(file=discord.File(imgpath))
では「何か」を作っていきます。計画は次のような感じです。
テキストを受け取る
↓
用意してあるテンプレの\begin{document}〜\end{document}の中に入れる
↓
なんか
↓
できた画像のパスを返す
「なんか」の部分の候補はいくつかありましたが、今回は
platex→dvipdfmx→pdfcrop→mutool
という形にしました。pdfcropで余白を切り取って、mutoolでpngにしています。dviからいきなりpngにしてしまうやり方など、他にも色々あるようですがこれで動いたのでヨシとしました。
「なんか」を書きました。
#!/bin/sh
cd ./latexondiscord &&
yes x | platex ./latex-on-discord.tex >/dev/null 2>&1 &&
dvipdfmx ./latex-on-discord.dvi >/dev/null 2>&1 &&
pdfcrop --margins "5 5 5 5" ./latex-on-discord.pdf ./croped.pdf >/dev/null 2>&1 &&
mutool draw -r 600 -o ./image.png ./croped.pdf >/dev/null 2>&1
yes xの部分はエラーが出てもplatexの処理が止まらないようにgottiくんがつけてくれました。ありがとうございます。
さて、問題はここからです。みなさんご存知のようにlatexではバックスラッシュを用いますが、これをpythonで文字列として扱おうとすると\bや\nなどがうまく書けません。そこで次のようにしました。
def latexmessage(message):
text = repr(message) #バックスラッシュを2倍にしてくれるらしい
text = text[1:-1] #reprでつけられた''の囲いを外す
#テンプレを開く
with open("./latexondiscord/latex-on-discord-template.tex") as file:
nakami = file.read()
#文章を挿入
onew = re.sub("ここに文章を挿入",text, nakami,flags=re.DOTALL)
#コンパイルする方のファイルを開く
with open("./latexondiscord/latex-on-discord.tex", mode="w") as file:
file.write(onew) #書き込む
#シェルスクリプトにコンパイルしてもらう
subprocess.run('./latexondiscord/textopng.sh')
あらかじめ用意したテンプレートには「ここに文章を挿入」と記した部分が\begin{document}〜\end{document}で囲われているのでそこを置換して別のファイルに保存します。それでは全体像です。
import discord
import re
import subprocess
import os
# 自分のBotのアクセストークン
with open("bot.token") as tokenfile:
TOKEN = tokenfile.read()
with open("./latexondiscord/image.png", mode="w") as file:
file.write("") #仮の画像ファイル
# 接続に必要なオブジェクトを生成
client = discord.Client()
# 起動時に動作する処理
@client.event
async def on_ready():
# 起動したらターミナルにログイン通知が表示される
print('LaTeX botがログインしました')
# メッセージ受信時に動作する処理
@client.event
async def on_message(message):
# メッセージ送信者がBotだった場合は無視する
if message.author.bot:
return
#latex文法
if message.content.startswith("```latex"):
text = message.content
text = text[8:-3]
#```latex〜```を取り外す
imgpath = "./latexondiscord/image.png"
#生成される画像のパス
formerupdatetime = os.path.getctime(imgpath) #画像の更新時刻を確認
latexmessage(text) #画像作成
nowupdatetime = os.path.getctime(imgpath) #画像の更新時刻を確認
if formerupdatetime == nowupdatetime: #画像の更新がされていないとき
await message.channel.send("Syntax error!!")
else: #画像がきちんと更新されているとき
await message.channel.send(file=discord.File(imgpath))
def latexmessage(message):
text = repr(message) #バックスラッシュを2倍にしてくれるらしい
text = text[1:-1] #reprでつけられた''の囲いを外す
#テンプレを開く
with open("./latexondiscord/latex-on-discord-template.tex") as file:
nakami = file.read()
#文章を挿入
onew = re.sub("ここに文章を挿入",text, nakami,flags=re.DOTALL)
#コンパイルする方のファイルを開く
with open("./latexondiscord/latex-on-discord.tex", mode="w") as file:
file.write(onew) #書き込む
#シェルスクリプトにコンパイルしてもらう
subprocess.run('./latexondiscord/textopng.sh')
# Botの起動とDiscordサーバーへの接続
client.run(TOKEN)
githubにあげておきました。
https://github.com/ybasviel/latex-on-discord
使ってみた。(このころは文字サイズを変更していたので少し大きいです。)
このセリフ、気持ち良すぎて絶頂しました。