前回創作用サーバー向けに作った実質匿名でメッセージ送信できるdiscord botに、画像送信機能をつけてほしいとのお達しがあった。実装したのでメモがてらの書き散らし
前回作ったやつはこんなの
・GASを使ってGoogle Formの回答をjsonにし、Glitchのサイトにpostリクエストで送る
・Glitchに送られてきたものをNode.jsを使ってDiscordに送る
うむ。それはそうなんだけど画像をそのまま送るはできない。送り方としてぱっと思いついたものは
・Google Formで画像を上げるとgoogle Driveに残るので、ファイルのIDかurlかをGlitchに送り、Google Driveからフェッチしてくる
・一回Base64にエンコードしたものを送って、Glitch側でデコードする
一つ目のやつは認証周りがめんどくさそう?だったのでとりあえず二つ目のやつでやってみた。
まずはGASでの実装
if(response.length==4){
const fileIDs = response[1].getResponse().toString().split(',');
var data = {0 : IDtoBase64(fileIDs[0])};
json.image = data;
for(let i=1; i<fileIDs.length; i++){
json.image[i] = IDtoBase64(fileIDs[i]);
}
}
前回のjsonにimageのキーを追加して、それぞれの画像をIDtoBase64でbase64に変換したものを送るようにしてます。
回答はGoogle Driveにある画像のIDになります。複数画像だと,で区切ったIDが送られてくるので分割してどうこうすることで対応してます
Imageのキーを追加するかどうかは、画像があるかないかで回答の数が変わるのでそれで判別してます…うーん…
IDtoBase64の実装がこれ
function IDtoBase64(file){
//IDからファイル取得
var fileImage = DriveApp.getFileById(file);
//ユーザー名を切り取り
var fileName = fileImage.getName().toString();
console.log(fileName);
var name_splited = fileName.split('-');
var dot_splited = fileName.split('.');
var newName = name_splited[0].toString().split('.')[0].trim()+"." + dot_splited[dot_splited.length-1].toString();
fileImage.setName(newName);
//jpegに変換
const jpg = fileImage.getAs(MimeType.JPEG);
const jpgFile = DriveApp.getFolderById(folderId).createFile(jpg);
//変換前のものは削除
fileImage.setTrashed(true);
//base64に変換
blob = jpgFile.getBlob();
var base64Data = Utilities.base64Encode(blob.getBytes());
console.log("base64 convert completed")
return base64Data;
}
Google DriveからファイルのIDで画像を拾ってきます。
Google Formからあげた画像はアンケートと同じ階層に生成されるフォルダ下に保存されていくのですが、ファイル名が「元のファイル名 – 投稿者名 . jpeg 」とかになっちゃうんですよね。
まあフォルダは私しか見れないのですが、せっかく匿名にしてる意味がなくなっちゃうので一応投稿者名を消してリネームするようにしてます。
あとはエンコード/デコードする際に元の拡張子に合わせて逐一処理を分けるのは面倒なのでjpegファイルに変換して、それをエンコードして返してます。
これをGlitch側で受け取って、前回のserver.jsでimageのキーが含まれているときはそれぞれの画像のbase64を取り出してデコードの処理をしてぢすこ送ります。
if(dataObject.image!==undefined){
//送られてきたものを分割してとりだしてるだけ
var imageStr = dataObject.image.substr(1,dataObject.image.length-2);
var imageStrSplited =imageStr.split(',');
for(var i =0;i<imageStrSplited.length;i++){
var file =imageStrSplited[i].split("=")[1].trim();
sendImage(msgChannelId,file);
}
}
デコードしたやつは一時ファイルに書き込むことにしました
一時ファイルのモジュールにはtmpを使いました。Glitchのターミナルでコマンドを実行してモジュールをダウンロードしときます
$ npm install tmp
取り出したbase64をデコードして送るsendImageはこれ。tmpモジュールを使って一時ファイルを作成し、base64からデコードしたやつを書き出します。一時ファイルのパスがpathに入るので、それを指定してぢすこに送ります。
function sendImage(channelID, imageFile, option = {}) {
tmp.file(function _tempFileCreated(err, path, fd, cleanupCallback) {
if (err) throw err
try {
fs.writeFile(path, imageFile, { encoding: "base64" }, err => {
if (err) throw err
});
console.log(path);
client.channels.get(channelID).send({
//embedしないと画像がプレビューされない
embed : {
image : {
url : "attachment://image.jpg"
}
},
files : [{ attachment : path, name : "image.jpg"},]
})
.then(console.log("画像送信"))
.catch(console.error);;
} finally {
// ファイル削除
cleanupCallback()
}
})
}
これでgoogle formから画像も送れるようになりました やったね

ところで、Glitchは金を払わないと環境変数以外公開されるのであんまり良い方法じゃない気がします。一時ファイルだからすぐ消えるだろうけどめっちゃ良いタイミングでクローンすると画像が見える可能性がある…?一応一時ファイル作ってawaitしてその間にターミナルで確認した分には見えなかったけれど(クローンも同様)
ぢすこbotのサーバー、ラズパイに移行したい…