1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

discord.py とgoogle formを利用してdiscordに参加した人とgoogle formで参加登録をした人を紐づける

1
Posted at

はじめに

この記事は、discord.pyとgoogle apps scriptがあらかたわかっている人向けに書かれている。もしわかっていない人がいるならPythonで実用Discord Bot(discordpy解説)などを参考にしてほしい。

やったこと

とある研究会を運営するにあたって、discordを連絡手段に利用した。
google formを利用し研究会への参加者を募った。この際に参加者の名前とメールアドレスを収集した。
収集したメールアドレス宛にgoogle apps script を利用することでdiscordへの参加リンクを送信してdiscordサーバーに参加してもらった。
参加者にはdiscord内でのニックネームを名前 (所属)に変更してもらった。
このニックネームをdiscord bot で読むことで、名前とdiscrod_user_idを.csvファイルとして出力し、それをgoogle driveにアップロードし、 apps scriptを用いて処理することで、google formで集めた参加希望者とdiscrodに参加しニックネームを変更した参加者を紐づけた。
最後に、紐づけることができなかった参加者にgoogle app scriptを利用することで参加リマインドメールを送信した。

プログラムの解説

処理の流れ

  1. google formを用いて参加者を収集 google spread sheetに情報を保存 discord招待メールを送信
  2. discord.pyを用いてニックネームとdiscord_user_idを取得。一覧を.csvファイルで保存
  3. .csvファイルをgoogle driveにアップロード
  4. google Apps Scriptを用いてgoogle spread sheet上で参加者とidを紐づけ 紐づいていない参加者にリマインドメールを送信

1, 参加者収集~情報保存

参加者から収集した情報は

  1. 名前
  2. メールアドレス
  3. そのほか1
    である。

この結果をgoogle spread sheetに収集し、以下のようなGoogle Apps Scriptを実行してdiscord招待メールを送信した。

discord招待メール送信スクリプト.gs
function sendEmails() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シートの名前");
  const data = sheet.getDataRange().getValues();
  name_index = ; //名前が入力されているインデックス。A列が0の整数値
  mail_index = ; //メールアドレスが入力されているインデックス
  for (let i = 1; i < data.length; i++) { //多くの場合は for (let (i = 1~ だと思う。一人目の情報が格納されたインデックスを指定すること 0 スタート)
    const name = data[i][name_index]; // 名前
    const email = data[i][mail_index]; // B列:メールアドレス
     const subject = "discord 参加のお願い"; // 件名
    const body = `${name} 

この度は参加申込みをいただき、ありがとうございます。 
~~~メール本文~~
何卒よろしくお願いいたします。
`;

  MailApp.sendEmail(email, subject, body);
   Logger.log(`${name}様に送信完了`);
  }
}

2,disrod.pyを用いた.csvファイル作成

以下のプログラムを実行し、.csvという名前のファイルを作成した。

csvファイル作成.py
import discord
from discord.ext import commands
import re
import csv
import os

TOKEN = "YOUR_BOT_TOKEN" # ここにボットのトークンを貼り付け

# 監査対象のサーバーIDを設定
TARGET_GUILD_ID = YOUR_GUILD_ID # ここに監査したいサーバーのIDを貼り付け

# ニックネームのパターン
# フォーマット: 名前 + [半角/全角スペース] + ( + 任意の文字列 + )
# または、名前 + [半角/全角スペース] + ( + 任意の文字列 + )
# 半角スペースと全角スペースを許容し、半角括弧と全角括弧を許容
# 括弧内は空文字でも任意の文字の並びでもOK
# この正規表現は、(または(で始まり、)または)で終わるパターンを検出します。
NICKNAME_FORMAT_PATTERN = re.compile(
    r".+\s[\((].*[\))]"
)

intents = discord.Intents.default()
intents.members = True # メンバーインテントを有効にする
intents.guilds = True  # ギルドインテントを有効にする

bot = commands.Bot(command_prefix='!', intents=intents)

@bot.event
async def on_ready():
    print(f'{bot.user} としてログインしました!')
    print('ニックネームの設定状況とフォーマットを確認します...')

    guild = bot.get_guild(TARGET_GUILD_ID)
    if guild is None:
        print(f"エラー: 指定されたサーバーID {TARGET_GUILD_ID} が見つかりません。")
        await bot.close()
        return

    print(f"サーバー '{guild.name}' ({guild.id}) のメンバーニックネームを確認中...")

    # 結果を格納するリスト
    nickname_not_set_members = []
    nickname_set_members = []
    formatted_nickname_members = []
    not_formatted_nickname_members = []

    for member in guild.members:
        # Bot自身はスキップ
        if member.bot:
            continue

        nickname = member.nick # ニックネームを取得

        if nickname is None:
            # ニックネームが設定されていない場合
            nickname_not_set_members.append({'user_id': member.id, 'user_name': member.name, 'current_nickname': 'なし'})
        else:
            # ニックネームが設定されている場合
            nickname_set_members.append({'user_id': member.id, 'user_name': member.name, 'nickname': nickname})
            
            # 設定されているニックネームが指定のフォーマットに合致するかチェック
            if NICKNAME_FORMAT_PATTERN.search(nickname):
                formatted_nickname_members.append({'user_id': member.id, 'user_name': member.name, 'nickname': nickname})
            else:
                not_formatted_nickname_members.append({'user_id': member.id, 'user_name': member.name, 'nickname': nickname, 'reason': f'指定フォーマットに合致しません: "{nickname}"'})

    # CSV ファイルに保存するための出力ディレクトリ
    output_dir = "MENBERS"
    os.makedirs(output_dir, exist_ok=True)

    # 1. ニックネームが設定されていない(変更されていない)人のリスト
    not_set_csv_path = os.path.join(output_dir, "nickname_not_set_members.csv")
    with open(not_set_csv_path, 'w', newline='', encoding='utf-8') as f:
        fieldnames = ['user_id', 'user_name', 'current_nickname']
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(nickname_not_set_members)
    print(f"ニックネームが設定されていない(変更されていない)メンバーのリストを '{not_set_csv_path}' に保存しました。")

    # 2. ニックネームが指定フォーマットに合致する人のリスト
    formatted_csv_path = os.path.join(output_dir, "formatted_nickname_members.csv")
    with open(formatted_csv_path, 'w', newline='', encoding='utf-8') as f:
        fieldnames = ['user_id', 'user_name', 'nickname']
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(formatted_nickname_members)
    print(f"指定フォーマットに合致するメンバーのリストを '{formatted_csv_path}' に保存しました。")

    # 3. ニックネームが指定フォーマットに合致しない人のリスト
    not_formatted_csv_path = os.path.join(output_dir, "not_formatted_nickname_members.csv")
    with open(not_formatted_csv_path, 'w', newline='', encoding='utf-8') as f:
        fieldnames = ['user_id', 'user_name', 'nickname', 'reason']
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(not_formatted_nickname_members)
    print(f"指定フォーマットに合致しないメンバーのリストを '{not_formatted_csv_path}' に保存しました。")


    print('ニックネームの確認が完了しました。Botをシャットダウンします。')
    await bot.close()

# Botを実行
bot.run(TOKEN)

3,GASを用いたgoogle spread sheetとの紐づけ

先ほど作ったcsvファイルformatted_csv_path.csvとnickname_not_set_members.csvをgoogle driveにアップロードし、csvファイルのidを得る。その後、以下のgsファイルを実行する

紐づけ.gs
vfunction myFunction() {
  const csvFileId = 'csvファイルid';
  const spreadsheetId = 'google spreadshet id';
  const sheetName = 'シート名';
  const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

  // --- ここからメール送信機能の追加 ---

  // メールアドレスの列インデックス (例: G列がメールアドレスの場合、インデックスは6)
  const emailColIndex = ;
  // discordに参加した場合にチェックする列のインデックス 
  const ajColIndex = ;

  // discord idを格納する列のインデックス
  const apColIndex = ;
 
  // メール本文

  const text =`参加者の皆様
お世話になっております。
先日ご案内いたしましたdiscord公式サーバーですが、現時点での参加が確認できておりません。
~~~メール本文~~~
`; // 必要に応じてメール本文を編集してください

  // スプレッドシートの最新のデータ範囲を取得し直す 
  const updatedSheetValues = sheet.getDataRange().getValues();

  // メール送信対象者のリスト
  const recipients = [];

  for (let k = 0; k < updatedSheetValues.length; k++) {
    const row = updatedSheetValues[k];
    const apValue = row[apColIndex]; 
    const ajValue = row[ajColIndex];// 
    const recipientEmail = row[emailColIndex]; 

    // ヘッダー行をスキップ (必要であれば、k > 0 でスキップ)
    // 最初の行がヘッダーであれば、k > 0 とします。
    // 今回のケースではk > 0 を追加してヘッダー行をスキップします。
    if (k === 0) {
      continue;
    }

    // 条件チェック: AP列が 'y' でない かつ AJ列がチェック(TRUE)ではない
    // AP列が空欄の場合も'y'ではないと判断します
    // AJ列がチェックボックスの場合、チェックされているとTRUE、空欄だとFALSEになります
    if (apValue !== 'y' && ajValue !== true && recipientEmail) {
      // recipientEmailが存在し、有効な形式であることを確認することも推奨されます
      recipients.push({
        email: recipientEmail,
        name: row[3] // D列の名前(メールの宛名などに使えるように)
      });
      Logger.log(`メール送信対象者: ${row[3]} (${recipientEmail}) - AP: "${apValue}", AJ: "${ajValue}"`);
    }
  }

  // 対象者へのメール送信
  if (recipients.length > 0) {
    Logger.log(`${recipients.length} 人にメールを送信します。`);
    recipients.forEach(recipient => {
      try {
       GmailApp.sendEmail(
         recipient.email, // 宛先
        '【重要】discoed参加のお願い', // 件名
        text // 本文
        );
        Logger.log(`メールを ${recipient.name} (${recipient.email}) に送信しました。`);
        Logger.log(text)
      } catch (e) {
        Logger.log(`エラー: ${recipient.name} (${recipient.email}) へのメール送信に失敗しました: ${e.message}`);
      }
    });
    Logger.log('すべてのメール送信処理が完了しました。');
  } else {
    Logger.log('メール送信対象者はいませんでした。');
  }
}

これで、設定した列に紐づけされたdiscord.user.idが格納されるはずです。

終わりに

これを用いれば、アンケート結果に応じたdiscrod.roleの付与などに応用が利くと思っています。
皆様の参考になれば幸いです

  1. 発表希望やアレルギーの有無などを収集した。詳しくは述べないが、ここで収集した情報によってdiscrodのroleを付与した。かなり使いやすかった。

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?