はじめに
DiscordとGoogleSpreadSheet(以下GSS)を連携して書き込んだり読み込んだりしたい!という考えから作成したもの
結果できたものは
- Discordで送信したメッセージや、ファイルのURLをGSSに書き込み
- GoogleAppsScript(以下GAS)で送信したメッセージやファイルをDiscordに書き込み
- 送信したファイルのURLをGSSに書き込み
使用言語は以下
- python3.6.4
- HTML5
- JavaScript
- Vue.js
- GAS
ちなみにVue.jsを使ってるのは筆者の趣味。使わなくても問題ないはず(試してないけど)
Discordから送信
全体ソースコード
import discord
import gspread
import json
client = discord.Client()
#ServiceAccountCredentials:Googleの各サービスへアクセスできるservice変数を生成します。
from oauth2client.service_account import ServiceAccountCredentials
@client.event
async def on_ready():
print('Logged in as '+ client.user.name)
@client.event
async def on_message(message):
if message.author != client.user: # 自分以外からのメッセージのときに反応
# 特定のチャンネルでない場合は何もしない
if message.channel.name != 'チャンネル名':
return
#2つのAPIを記述しないとリフレッシュトークンを3600秒毎に発行し続けなければならない
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
#認証情報設定
#ダウンロードしたjsonファイル名をクレデンシャル変数に設定(秘密鍵、Pythonファイルから読み込みしやすい位置に置く)
credentials = ServiceAccountCredentials.from_json_keyfile_name('jsonファイルパス', scope)
#OAuth2の資格情報を使用してGoogle APIにログインします。
gc = gspread.authorize(credentials)
#共有設定したスプレッドシートキー
SPREADSHEET_KEY = 'スプレッドシートキー'
#共有設定したスプレッドシートのシートを開く
worksheet = gc.open_by_key(SPREADSHEET_KEY).worksheet("シート名")
if message.author != client.user: # 誰かから送信された場合
try:
#メッセージ内容
export_message = message.content
#ファイルURL
export_value = message.attachments[0].url
#ファイル名
export_name = message.attachments[0].filename
for num in range(1,996):
# A列の値
import_value = worksheet.cell(num , 1).value
if len(import_value) == 0:
# セルに書き込み
worksheet.update_cell(num,2, export_message)
worksheet.update_cell(num,3, export_value)
return
except IndexError:
print('IndexError:'+ message.author.name)
pass
else:
return
# Discordのデベロッパサイトで取得したトークン
client.run("トークン")
解説
Discordから送信する場合はGSS側に書き込み許可を出さないといけなかったりするので事前準備がいくつか必要です。
ここで説明すると長くなるので以下のサイトを参考にしてください
【もう迷わない】Pythonでスプレッドシートに読み書きする初期設定まとめ
やることは単純で、
- 書き込まれたメッセージやファイルを解読する
- 指定したGSSのセルに書き込む
の2つだけです
DiscordBot.pyexport_message = message.content export_value = message.attachments[0].url export_name = message.attachments[0].filename
メッセージやファイルを解読するための処理です。
message.content
で書き込まれたメッセージを取得できます
message.attachments[0].filename
でファイル名を取得できます
message.attachments[0].url
でファイルURLを取得できます
DiscordBot.pyworksheet = gc.open_by_key(SPREADSHEET_KEY).worksheet("シート名") for num in range(1,996): # A列の値 import_value = worksheet.cell(num , 1).value if len(import_value) == 0: # セルに書き込み worksheet.update_cell(num,2, export_message) worksheet.update_cell(num,3, export_value) return
セルに書き込むための処理です。
SPREADSHEET_KEYとシート名が一致するシートに書き込みます
worksheet.update_cell(1,1, 'AAA')
でセルA1にAAAを書き込みます書き込むための記述はGASと大して変わらないので比較的わかりやすいと思います。
GASから送信
全体ソースコード
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta charset="utf-8" content="width=device-width,initial-scale=1.0"/>
</head>
<body>
<main id="main" class="main">
<form enctype="multipart/form-data">
<label>テキスト</label><br>
<input type="text" v-model="Text" size="45"><br>
<label>テキストエリア</label><br>
<textarea type="text" v-model="TextArea" rows="10" cols="40">
</textarea><br>
<label>ファイル</label><br>
<input type="file" name="myFile" id="file" accept="application/zip" v-on:change="changeFile"/>
<br>
<button type="button" type="submit" id="upload" v-on:click="Submit(this)">
送信
</button>
</form>
</main>
<?!= HtmlService.createHtmlOutputFromFile('Index.js').getContent(); ?>
</body>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
var tmpText = "";
var tmpTextArea = "";
var tmpFile = "";
var main = new Vue({
el: '#main',
data: function() {
return{
Text:"",
TextArea:"",
file:"",
}
},
methods: {
// シート書き込み処理
pushSheetData:function(e){
var reader = new FileReader();
reader.readAsDataURL(this.file);
tmpText = this.Text;
tmpTextArea = this.TextArea;
tmpFile = this.file;
reader.onload = function(evt) {
var data = reader.result.split(",");
wkFileData = {fileName: tmpFile.name, mimeType: data[0].match(/:(\w.+);/)[1] ,data: data[1]};
google.script.run.withSuccessHandler(function(arg){
console.log("Arg:"+arg);
alert("ファイルのアップロードに成功しました。");
}).withFailureHandler(function(arg){
console.log("Arg:"+arg);
alert("ファイルのアップロードに失敗しました。");
}).processForm(tmpText,tmpTextArea,wkFileData);
}
},
// 送信ボタン押下時処理
Submit:function(e){
this.pushSheetData(e);
//初期化
this.Text="";
this.TextArea="";
},
// アップロードされたファイル
changeFile: function (e) {
const target = e.target.files;
if(target.length != 0){
this.file = target[0];
}
},
}
})
</script>
function doGet(e){
var htmlOutput = HtmlService.createTemplateFromFile("Index").evaluate();
htmlOutput.setTitle('Sample').addMetaTag('viewport', 'width=device-width, initial-scale=1');
return htmlOutput;
}
//Htmlから取得したファイルを変換する
function processForm(Text,TextArea,formObject) {
// 指定したユーザにメンションを送れる
const mention = "<@18桁の数字> "
// ファイルを送信できる形式に変換
var blobs = Utilities.newBlob(Utilities.base64Decode(formObject.data), formObject.mimeType, formObject.fileName);
// テキストエリアの入力値とファイルを送信
discordSend(mention+TextArea,blobs);
// シートに書き込み
setSheetData(Text, TextArea);
return blobs;
}
//Discordに送信
function discordSend(message,file) {
// 各所必要な項目をセットします
const url = 'discordのwebhooksのurl';
const token = 'Discordのデベロッパサイトで取得したトークン';
const channel = '#送信したいチャンネル名';
const text = message;//送信するメッセージ
const username = '送信させるユーザ名';
const parse = 'full';
const method = 'post';
const attachment = file;//送信するファイル
const payload = {
'token' : token,
'channel' : channel,
"content" : text,
'username' : username,
'parse' : parse,
'attachment1': attachment,
};
const params = {
'method' : method,
'payload' : payload,
'muteHttpExceptions': true
};
response = UrlFetchApp.fetch(url, params);
}
//対象のシートに取得したデータを書き込み
function setSheetData(Text, TextArea){
var sheetName = SpreadsheetApp.openById("スプレッドシートID").getSheetByName("シート名");
//A列の最終行を判断
var last_row = sheetName.getRange('A:A').getValues().filter(String).length;
last_row++;
//スプレッドシートに挿入
sheetName.getRange("A"+last_row).setValue(Text);
sheetName.getRange("B"+last_row).setValue(TextArea);
}
import discord
import gspread
import json
client = discord.Client()
#ServiceAccountCredentials:Googleの各サービスへアクセスできるservice変数を生成します。
from oauth2client.service_account import ServiceAccountCredentials
@client.event
async def on_ready():
print('Logged in as '+ client.user.name)
@client.event
async def on_message(message):
if message.author != client.user: # 自分以外からのメッセージのときに反応
# 特定のチャンネルでない場合は何もしない
if message.channel.name != 'チャンネル名':
return
#2つのAPIを記述しないとリフレッシュトークンを3600秒毎に発行し続けなければならない
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
#認証情報設定
#ダウンロードしたjsonファイル名をクレデンシャル変数に設定(秘密鍵、Pythonファイルから読み込みしやすい位置に置く)
credentials = ServiceAccountCredentials.from_json_keyfile_name('jsonファイルパス', scope)
#OAuth2の資格情報を使用してGoogle APIにログインします。
gc = gspread.authorize(credentials)
#共有設定したスプレッドシートキー
SPREADSHEET_KEY = 'スプレッドシートキー'
#共有設定したスプレッドシートのシートを開く
worksheet = gc.open_by_key(SPREADSHEET_KEY).worksheet("シート名")
if message.author.name == client.user.name: # 名前が一致する場合
# スプレッドシートから送信された場合
try:
for num in range(1,996):
# A列の値
import_value = worksheet.cell(num , 1).value
if len(import_value) == 0:
# ファイルURL
export_value = message.attachments[0].url
# セルに書き込み
worksheet.update_cell(num,1, export_value)
return
except IndexError:
print('IndexError:'+ message.author.name)
pass
# Discordのデベロッパサイトで取得したトークン
client.run("トークン")
解説
GAS単体ではGASからメッセージやファイルを送信することはできるのですが、送信したファイルのURLを取得することはできません。(というかあるんですかね)
なので
- GASから受け取ったメッセージやファイルをDiscordへ送信する。(GSSへの書き込みも同時に行う)
- discord.pyを利用して送信されたファイルのURLをGSSに書き込む
ということをしています。
Index.jspushSheetData:function(e){ var reader = new FileReader(); reader.readAsDataURL(this.file); tmpText = this.Text; tmpTextArea = this.TextArea; tmpFile = this.file; reader.onload = function(evt) { var data = reader.result.split(","); wkFileData = {fileName: tmpFile.name, mimeType: data[0].match(/:(\w.+);/)[1] ,data: data[1]}; google.script.run.withSuccessHandler(function(arg){ console.log("Arg:"+arg); alert("ファイルのアップロードに成功しました。"); }).withFailureHandler(function(arg){ console.log("Arg:"+arg); alert("ファイルのアップロードに失敗しました。"); }).processForm(tmpText,tmpTextArea,wkFileData); } }
Servlet.gsfunction processForm(Text,TextArea,formObject) { // 指定したユーザにメンションを送れる const mention = "<@18桁の数字> " // ファイルを送信できる形式に変換 var blobs = Utilities.newBlob(Utilities.base64Decode(formObject.data), formObject.mimeType, formObject.fileName); // テキストエリアの入力値とファイルを送信 discordSend(mention+TextArea,blobs); return blobs; }
ローカルからアップロードするファイルを扱うための処理です。
Googleドライブに書き込むときも同様にできます。
メンションを飛ばしたい場合はDiscordで開発者モードを使い取得したユーザのIDを利用し"<@18桁の数字> "
のように記述します。1
Servlet.gsfunction discordSend(message,file) { // 各所必要な項目をセットします const url = 'discordのwebhooksのurl'; const token = 'Discordのデベロッパサイトで取得したトークン'; const channel = '#送信したいチャンネル名'; const text = message;//送信するメッセージ const username = '送信させるユーザ名'; const parse = 'full'; const method = 'post'; const attachment = file;//送信するファイル const payload = { 'token' : token, 'channel' : channel, "content" : text, 'username' : username, 'parse' : parse, 'attachment1': attachment, }; const params = { 'method' : method, 'payload' : payload, 'muteHttpExceptions': true }; response = UrlFetchApp.fetch(url, params); }
Discordへ送信するための処理です。
const username
の部分に送信させたいユーザを、
const text
の部分に送信したいメッセージを、
const attachment
の部分に送信したいファイルを格納します。
DiscordBot.pyif message.author.name == client.user.name: # 名前が一致する場合 for num in range(1,996): # A列の値 import_value = worksheet.cell(num , 1).value if len(import_value) == 0: # ファイルURL export_value = message.attachments[0].url # セルに書き込み worksheet.update_cell(num,1, export_value) return
送信したファイルをセルに書き込むための処理です。
これはDiscordから送信する時とさほど変わりません。
ただ、GASから送信したユーザは同じ名前でも全く別のユーザ(IDが#0000)となるので名前が一致する場合で判別してます(どうやったらうまく判別できるのかは知りたいところ)
あとがき
Discordで管理するとログが流れるので、履歴をたどるのがめんどくさかったから割と使いやすいんじゃね?って思ってる。
それにGoogleフォームとかも自作できるし、Googleドライブの容量を気にする必要もないし、と応用は利きそう。
ただ不安なのが同期まわり。GASから送信する際、ファイルURLは後から書き込んでるからズレたりしないだろうか(Discord.py側でまとめてセルに書き込めばいい話なんだろうけど)。