マテ茶を飲もう

ネットの海を泳いでも見つからなかった情報を残します

読書感想文をAIに書かせる方法

読書感想文をAIに書いてもらう方法の備忘録。

※ この記事により何らかの不利益が生じた場合でも、筆者は一切の責任を負いません。

目次:

電子書籍を購入し、acsmファイルをダウンロード

何はともあれ、スタートは電子書籍を買うことです。 海賊サイトではなく、まともなプラットフォームで買いましょう。 筆者はGoogle Play Booksを使いましたので、このセクションの残りの説明は全てGoogle Play Booksの場合と考えてください。

  1. まずはGoogle Play Booksにアクセスして、必要な本を購入
  2. 続いて、ライブラリに移動
  3. 1.で購入した本の3点ボタン(・・・)をクリックして、エクスポートを選択。この後、大抵の本には以下のように「ASCMファイルが云々」という説明があると思います。この場合、ACSM形式(EPUB用)でエクスポートを選択してください。もしASCMの説明がなく、純粋なEPUBまたはPDFでエクスポートするオプションがある場合はラッキーです。最後の章に飛んでください。
  4. 先ほどダウンロードしたファイルが確かにあることを確認します。Macであれば、ほとんどの場合Downloadsディレクトリーに(書籍名)-epub.acsmというファイルがあると思います。このファイル自体に書籍の本文は含まれておらず、後述するADEで閲覧するために必要なデータだけが入っています。

ADEでepubファイルを入手

前章でacsmファイル(Adobe Content Server Messageの略)をダウンロードしたら、これをepubファイルに変換する作業を行います。 ここが今回の山場です。

  1. Adobe Digital Editionsをダウンロード。添付画像のバージョンは執筆当時のものであり、今後変更される可能性が高いです。
  2. 前章でダウンロードしたacsmファイルをfinderアプリ上でダブルクリック。すると、ADEのポップアップが表示され「コンピューターを認証しろ」的なことを求められます。このとき、eBook VendorはAdobe ID(またはGoogle)、IDには前章で電子書籍を購入した時にログインしていたメールアドレスを指定してログインするのが確実だと思います。Adobe IDを持っていない場合は作成しましょう。I want to Authorize my computer without an IDにはチェックを付けずにAuthorizeボタンを押します。
  3. 以下のような画面が現れたらOKボタンを押し、購入した書籍の中身が実際に表示されるのを待ちます。
  4. 書籍がADE上で読めるようになったら、ADEの役割は終わりです。次に、epubファイルが作成されていることを確認しましょう。筆者の場合、Documents/Digital Editionsディレクトリーに(書籍名).epubというファイルがありました。

上手くいかない場合のトラブルシューティング

上記手順の2.でエラーが表示された場合、以下を順番に試してみてください。 AdobeとかMicrosoftみたいな古い企業はまるで自分たちがデファクトスタンダードかのように使いにくい仕様を放置していることが多くて嫌い

  1. Google Play Booksで書籍を購入する際にログインしていたメールアドレスと、ADEの認証に用いようとしているメールアドレスが同じであることを確かめる
  2. ADEを開いた状態でcommand, shift, Dを同時に押し、上記2.で入力したIDとパスワードを入力してADEの認証情報を削除する。その後、再度Google Play Booksから.acsmファイルをダウンロード(前章の3.)し、ADEでその新ファイルを開く。
  3. Google Playの問い合わせ用チャットで「端末のリセットをしてください」と依頼する。既存のヘルプフォーラムは読んでも無意味なので無視。参考: Adobe Digital Editionsで「E_GOOGLE_DEVICE_LIMIT_REACHED」エラーが出た際の対処法 – K2 WEB DESIGN

Calibreでtxtファイルに変換

以上で.acsmから.epubへの変換が終わりました。 この時点でもAIに読み込ませることは出来そうですが、書籍の本文に関係のない情報を落とし、軽量なファイルにするために.txtへの変換を行います。 .epubから.txtへの変換をオンラインで出来ると謳うサイトもいくつかありますが、筆者の場合は上手く行きませんでしたので、以下で説明するCalibreというアプリを用いました。

アプリのダウンロード

Calibreをダウンロードして起動します。途中でいくつかオプションの設定を求められるかもしれませんが、全てデフォルトのままで大丈夫です。

各種プラグインのインストール

  1. アプリを起動できたら、必要なプラグインをインストールします。画面左上のcalibre-->Preferences...-->Pluginsをクリックします。
  2. Get new pluginsをクリック。
  3. 検索ボックスにkfxと入力し、KFX InputKFX OutputをそれぞれInstall。両方ともStatusがLatest version installedに変わったら、Calibreアプリを再起動します。
  4. 次に、noDRM/DeDRM_toolsというGitHubリポジトリーのリリースページにアクセスし、LatestまたはPre-releaseタグの付いているバージョンのzipファイルをダウンロードします。
  5. FinderでDownloadsディレクトリーを開き、DeDRM_tools_(バージョン).zipをクリックして解凍します。その後、DeDRM_tools_(バージョン)ディレクトリーが作成され、その中にDeDRM_plugin.zipObok_plugin.zipがあることを確認してください。
  6. Calibreアプリに戻り、1. 2.と同じ要領でプラグインの追加ページに移動します。その後、Load plugin from fileを選択し、5.で確認したDeDRM_plugin.zipObok_plugin.zipを選択します。両方とも選択したらApplyボタンをクリックし、再びCalibreアプリを再起動します。

.epubから.txtへの変換

いよいよ、Calibreアプリでepubファイルを読み込みます。

  1. アプリ左上のAdd booksボタンをクリックし、前章で生成したepubファイル(Documents/Digital Editionsディレクトリーにあるはず)を選択します。その後、アプリ右側に本の表紙が表示されるはずですが、その下のFormatsの値がEPUBとなっていることを確認してください。
  2. 次に、.epubから.txtへの変換を行います。読み込んだ本を右クリックし、Convert books-->Convert individuallyを選択してください。
  3. 直前の操作により開かれたポップアップにおいて、右上のOutput formatEPUBからTXTに変更してOKを押します。]
  4. 本のFormatsセクションの値にTXTが追加されていることを確認します。このTXTというリンクをクリックすると、本の内容がtxtファイルとして開かれます。おそらく、なぜかデフォルトでは中国語かベトナム語のようなファイル名になっていると思うので、分かりやすいファイル名と場所に変えておきましょう。この情報は次章で必要なので忘れないように。

AIにデータを渡して依頼

前章までで、必要な前処理は全て完了しました。 あとは好みのAIに処理を依頼するだけです。 いくら市販されている書籍とはいえ、そのtxtファイルのサイズが膨大になることはないはずですが、念の為、使おうとしているAIサービスのインプットサイズ制限やトークンの使用量は調べたほうが良いかもしれません。 私はGoogle有料ユーザーなので、Gemini 2.5 Proに前章のtxtファイルをアップロードし、以下のように依頼しました。

添付したtxtファイルの内容を3,000字程度で要約してください。

概ね満足できる結果が得られました。 良い時代ですね。 学生時代に欲しかった。

第78回中央区民スキー大会の様子

2025年1月26日の中央区民大会に出場したときの様子を記述します。 全体的に緩い雰囲気で、初心者やブランクのある人には特にオススメです。

中央区民大会とは?

概要

中央区民大会とは、東京都中央区スポーツ協会が主催する大会です。 スキーに限らず、サッカーや水泳など、様々な競技が毎年1度ずつ開催されています。 スキー大会の情報は毎年このページに掲載されるはずです。

近年の要項を見る限り、実施日は1月下旬の日曜日、種目は2本制のSLです。 板や服装の制限はなく、中央区内在住 ・ 在勤 ・ 在学のいずれかを満たす人であれば、誰でも参加できます。 18歳以上の人は、この大会で良い成績を残すと3月上旬の東京都大会に中央区代表として出場することが出来るようです。 18歳未満の人は順位が付かず、タイムが記載された「認定証」を貰うことになります。

参加費は無料で、しかも競技終了後のコースで練習も出来るのがすごいところ。(後述)

参考:今回の実施要項

申し込み方法

申し込み方法はアナログです。 中央スポーツ協会のホームページから申込書(PDF or Excel)をダウンロードし、協会に直接持って行くか郵送する必要があります。 港区のように、ネットで完結できるようになってほしいですね。

参考:今回の申込書

当日の様子

コース

レース会場は裏太郎のかもしかコース。 すぐ隣では、GSのゲートが3レーン立っていました。 (SURF&SNOWより拝借)

タイムスケジュール

当日の時程はこんな感じ。 受付は8:30から開始されました。 要項にも書いてありましたが、この場所はかもしかコース(普段常設ポールがあるところ)のゴールエリアのため、裏太郎の駐車場から歩いて行くには少し大変です。 レースコース脇は他のチームが練習していましたので、受付場所に辿り着くためにはレースコースを降りなければなりません。 もはやインスペクション代わりになってしまいますが、この緩さが区民大会の良いところ。 他の参加者も、コース脇を荒らさないように降りて受付に集まっていました。

受付を済ませ、ゼッケンと時程表・エントリーリストの紙を貰ったら、8:50~のインスペクションに向けてリフトで上がります。

1本目インスペクションの様子

開会式

9:30になると、開会式が始まります。 ちなみにこの時もコース脇を降りてくるので、これまでに最低3回はコースを見たことになります。

開会式では、中央区スキー連盟の新会長(?)の挨拶の後、大会の注意事項が説明されます。 曰く、今年は参加者が少ないため

  • 1本目・2本目共にゼッケン順に出走
  • 1本目で失格になっても2本目を滑って良い

とのことでした。 また、2本目終了後、15時頃パトロールが撤収に来るまでは自由に練習して良いとのこと。

ちなみに、総エントリー数は45人で、実際に参加していたのは40人弱でしょうか。 カテゴリー別の内訳は、オープン2人、女子6人、男子1部5人、男子2部2人、男子3部10人です。 調べる限り、他の区・他の年でも参加者は少ないようです。 そもそも大会の存在を知られていないことが原因ではないかと思うのですが、もっと多くの人が参加するようになってほしいですね。

1本目

そうこうしているうちに1本目の時間になりました。 他の大会とは違い、前走者がいないため、1番スタートの選手は難しかったかもしれません。

僕は全体最後にスタート。 下地は十分硬いため全く掘れておらず、ゲートの間隔も広い(15mくらいあった気がする)ため、快適に滑ることが出来ました。 細かいタイムは覚えていませんが、28秒台だったはずです。


相変わらず上体が起きていることは置いといて、もっと攻めれたなぁ...

2本目

1本目の後、しばらくフリー練習をして2本目に備えます。 一般的な大会であれば、1本目のタイム・順位が張り出されますが、この大会にはありません。

インスペクションで、1本目と同じく余裕のあるセットであることを確認。 1本目よりも直線的かな。

今回もほぼ全体最後のスタート。


もっと攻めれたなぁ(2回目)

また、今回はinsta360を胸に付けて滑っていました。 もちろんスタッフの許可は頂いています。


閉会式

最後の人が2本目を滑り終わったのが12:30頃。 朝配布された時程表では、閉会式は13:30とされていたため、サンホテル1階のレストランで昼食を取ることに。

ゆっくりラーメンを食べ、13:20頃ゴールエリアに向かうと、なぜか誰もいない。 その直後、コースを滑ってきた人に話を聞くと...

本当は13:30から閉会式の予定だったんだけどね、皆が2本目を滑り終えた時点でゴールエリアに人が集まっていたから、その場のノリで急いで集計をして閉会式をやってたよ。僕もそうだけど、もう皆も練習してるんじゃないかな。

いや、放送も無いのに分からんて...😅

こんなに時間に緩い大会は初めてですが、もちろん結果は知りたいので運営の方々を探すことに。 ひとまずサンホテルのレストランに戻って探していると、偶然見つけられました。 事情を話すと、

君は確か優勝だった気がするな。賞状が車にあるから取りに行こう。あと、3月の都大会にも推薦しておくね。

とのこと。 他の人は元より、自分のタイムも分からないですが、優勝とのことで良かったです。 話を聞く限り、中央区はオープン以外の各カテゴリーの上位2番までが都大会に進めるようです。

その後、15時過ぎまで2本目のセットで練習しました。 運営の方々はいらっしゃらず、ポールが折れたら各自で避けるスタイルです。 普段は計測ごとに¥100かかるスーパーレーサーのタイマーも無料で使えました。

まとめ

お読みいただきありがとうございました。 上記の通り、色々と緩い部分がありますが、その分初心者やブランクのある人には最適な大会だと思います。 見通しの良い中緩斜面ですし、ワンピースを着ていなくても全く浮かず、楽しく滑ることができると思います。

おそらくボランティアで運営をしてくださった皆様、ありがとうございました。 参加者・運営者共に年齢が高く、今後の大会の存続が心配ですが、来年度以降は自分に出来ることがあれば積極的に手伝おうと思います。

次は都大会ですが、まだ連絡がありません。 宿とか車とかどうすれば良いんだろう...😅

【完全無料】Oracle Cloudでdiscord.js botを常時ホストする方法

対象者

低スペックで構わないので、個人用botを動かせる完全無料サーバーを探している人。

2024年4月時点での簡単なホスト方法を記します。M2 MacBook Airで実行しました。

目次:

Oracle Cloudに登録

公式サイトから、手順に従って登録してください。

数年前はOracleのキャパ問題(?)によってアカウント申請が通りにくかった覚えがありますが、今回は一度で作成できました。

ただし、相変わらず Tokyo は Home Region として人気のようなので、一応 Japan Central (Osaka) を選びました。

SSH接続の準備

ターミナルを起動し、これから作成するoracleのサーバーにSSH接続するための鍵を生成しましょう。

例:

cd ~/.ssh
ssh-keygen -t ed25519 -f oracle

※異なるディレクトリーで生成しても構いませんが、場所を覚えておいてください。

lsコマンドの結果、oracle.puboracleの2ファイルが表示されることを確認して次に進みます。

コンピュートインスタンスを作成

Oracle Cloudにログインし、Launch resources --> Create a VM instance に移動します。

Image and Shape で、Always Free-eligible というタグがついているものを選択します。 本当は VM.Standard.A1.Flex を選びたかったんですが、キャパ不足のようなエラー(詳しくは覚えてない)が表示されてしまったので、VM.Standard.E2.1.Micro にしました。

次に、先ほど生成したSSH公開鍵oracle.pubを登録します。

ターミナルで

cat ~/.ssh/oracle.pub | pbcopy

を実行し、クリップボードにコピーします。その後Oracleのページに戻り、Add SSH Keys --> Paste public keys に移動してペーストすればokです。

以上を終えたら、最低限の設定は完了です。

作成ボタンを押し、画面が遷移するのを待ちます。 遷移後の画面で、ステータスが RUNNING になれば完了です。 タブを開いたまま、次に進みます。

インスタンスSSH接続・最低限の設定

先ほど生成したSSH鍵と、インスタンスの詳細ページに表示されているPublic IP address(赤塗り部分)、その下のusernameを用います。 生成直後のusernameは全員共通でopcのようです。

ターミナルで以下のコマンドを打ちます。

ssh -i ~/.ssh/oracle opc@(public ip address)

その結果、以下のような表示になれば接続できています。

[opc@(インスタンスの名前) ~] $

これは任意ですが、接続の度に秘密鍵の場所やIPアドレスを入力するのは面倒なので、SSHの設定ファイル(大抵~/.ssh/config)に以下を追記すると楽になります。

Host (任意の名前。例:oracle-discord)
    HostName (上記IPアドレス)
    User opc
    IdentifyFile ~/.ssh/oracle

上記のように設定すると、ssh oracle-discordと入力するだけでインスタンスに接続できます。

次に、SSH接続した状態で最低限の設定変更を行います。

タイムゾーンの変更

おそらく、初期状態では日本標準時(JST)ではなくグリニッジ標準時(GMT)に設定されています。

[opc@(インスタンスの名前) ~]$ date

の結果に、JSTではなくGMTが表示されている場合、以下のコマンドでタイムゾーンを変更しましょう。

[opc@(インスタンスの名前) ~]$ sudo timedatectl set-timezone Asia/Tokyo

その後、上記のdateコマンドを実行し、GMTではなくJSTと表示されていることを確認します。

日本語パックのインストール

[opc@(インスタンスの名前) ~]$ localectl status

の結果がLANG=ja_JP.utf8になっていない場合、以下の流れに沿ってロケールを日本語に設定しましょう。

[opc@(インスタンスの名前) ~]$ sudo yum install glibc-langpack-ja

↓途中でy Enter、Complete! と表示されるまで数分待つ

[opc@(インスタンスの名前) ~]$ sudo localectl set-locale LANG=ja_JP.utf8

再度localectl statusを実行し、LANG=ja_JP.utf8が表示されていればOKです。

gitをインストール

[opc@(インスタンスの名前) ~]$ sudo yum install git-all
[opc@(インスタンスの名前)~]$ git -v
git version 2.39.3

のようになるのを確かめる。

ローカルのファイルを移す

ローカルで行うこと

  1. 開発ディレクトリーで用いているnode.jsのバージョンを調べる。私が使っていた v16.20.2 はいつの間にかサポートが終了していたので、これを機にOracle Linuxが対応しているバージョンのうち最も新しいLTSである v18.20.2 に上げました。

  2. GitHubにpush(参考:githubでpushするまでの手順 #GitHub - Qiita)

クラウドサーバーで行うこと

  1. 上記インスタンスSSH接続

    ssh oracle-discord

  2. GitHubリポジトリーをクローン

    [opc@(インスタンスの名前) ~]$git clone https://github.com/~~~.git

  3. node.jsとnvmをインストール

    まず、oracle公式サイトでnode.jsの対応表を見ます。 2024年4月時点では、v18.x までのみ対応しているようです(node.js自体の最新LTSは v20.12.2)。

    公式サイトに沿ってインストールしても良いですが、いずれnvm(Node Version Manager)が必要になるので、

    まずはnvmをインストール→指定バージョンのnode.jsをインストール

    という順番で進めます(参考: How to Install Node.js and NPM on Oracle Linux | Atlantic.Net )。

    nvmのインストール:

    [opc@(インスタンスの名前) ~]$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash

    [opc@(インスタンスの名前) ~]$ source ~/.bashrc

    [opc@(インスタンスの名前) ~]$ nvm -vの結果、0.39.7のように表示されればOK。

    node.jsのインストール:

    [opc@(インスタンスの名前) ~]$ nvm ls-remoteでインストール可能なバージョン一覧を確認。v18.20.2をインストールする場合↓

    [opc@(インスタンスの名前) ~]$ nvm install 18

    [opc@(インスタンスの名前) ~]$ nvm use 18

    [opc@(インスタンスの名前) ~]$ node -vの結果、v.18.20.2と表示されればOK。

  4. ローカルと同じように実行し、必要なモジュールをインストール

    私の場合、環境変数を読み込むための dotenv モジュールが無いというエラーが出たので、npm install dotenvを実行して解決しました。

  5. foreverで常時起動

    サーバーとの接続を切ってもbotを動かし続けるため、foreverというデーモンを導入します。

    [opc@(インスタンスの名前) ~]$ npm install -g forever (グローバルオプション -g を忘れないように注意)

    インストール後、メインファイルが格納されているディレクトリーに移動し、startコマンドを実行します。

    [opc@(インスタンスの名前) ~]$ forever start index.js

    実行中のスクリプトの一覧や停止方法など:Node.jsのスクリプトをデーモン化する!

  6. 動作確認

    [opc@(インスタンスの名前) ~]$ exitでサーバーとの接続を切った後も、botがオンラインになっていれば成功です👏

macOS Sonomaでメモのポップアップを無効にする方法

Appleの余計なお世話を無効にしたい

macOS SonomaがインストールされたMacを買うと、スクリーン右下にカーソルを移動させるとメモアプリが表示されると思います。Appleの純正メモアプリを使いたくない人向けに、これを無効にする方法を記します。Montery以前とは多少違いますが、大体一緒です。

手順

  1. システム設定 アプリを開き、デスクトップとDockに移動。一番下までスクロールし、ホットコーナー...をクリック。
  2. クイックメモをクリックし、-を選択。
  3. メモアプリを起動する以外にも色々設定できるようです。要らないけど。

MacのTerminalにpureをインストールする方法

Macにデフォルトで搭載されているTerminalは、目に優しくない。pureというテーマを導入すると良いらしいので、新しいMacBook Proにインストールしようとしましたが、公式サイトの通りにやっても上手くいかなかったので、僕のケースの解決策を記しておきます。

追記:M2, M3でも同様に出来ました。

環境

1. Preztoをインストール

Preztoは、zshのconfiguration frameworkです。まずはこれをインストールし、その設定ファイル(.zpreztorc)でpureを選択します。 ターミナルで以下のコマンドを実行します。

git clone --recursive https://github.com/sorin-ionescu/prezto.git "${ZDOTDIR:-$HOME}/.zprezto"
setopt EXTENDED_GLOB
for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do
  ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}"
done

その後ターミナルを再起動すると、3色(>>>)のシンプルな表示になっていると思います。なお、既に .zshrc ファイルが存在する状態で行うと上手くいかない場合があるようです。その場合は、ターミナルにrm ~/.zshrcと入力してファイルを削除してから実施してください(rmコマンドは自己責任で)。

2. Preztoの設定を変更

既にデフォルトのターミナルよりはだいぶ見やすいですが、pureを入れると更に分かりやすくなります。vi ~/.zpreztorcでPreztoの設定ファイルを開き、iキーを押して以下の通り変更します。

2.1 テーマを変更

ファイルの中央付近にある

zstyle ':prezto:module:prompt' theme 'sorin'

zstyle ':prezto:module:prompt' theme 'pure'

に変更。

2.2 補完機能とシンタックスハイライトを追加

'syntax-highlighting' \'autosuggestions' \を追加して、

zstyle ':prezto:load' pmodule \
  'environment' \
  'terminal' \
  'editor' \
  'history' \
  'directory' \
  'spectrum' \
  'utility' \
  'completion' \
  'syntax-highlighting' \
  'autosuggestions' \
  'prompt'

とする。

esc:wqと入力して終了。この時点で変更が反映されているはずですが、上手くいかない人はターミナルを再起動してみてください。

これはただの好みですが、テーマはIcebergがオススメです。こちらの記事を参考にしました。

Discord.jsで新メンバーが入った時の処理を自動化した

僕が運営しているスキーチーム用のbotをdiscord.js V14で作ったので、その概要と作成方法を共有。

前提

チームの入会フォーム(Google Form)が提出されると、discordサーバーへの招待リンクがメールで届くようになっている。discordサーバーは大まかにコーチ&現役フォルダーと現役フォルダーに分かれ、セキュリティーの観点から、○期ロールが付与されるまでの間は前者しか閲覧できないようになっている。discordのユーザーネームは本名と無関係に設定している人が多いので、僕がこんな感じでやっていた:

新入生がコーチ&現役フォルダー内にある自己紹介チャンネルに投稿 → 管理者が個人情報シート(入会フォームの回答をまとめたspreadsheet)を見て何期生かを確認 → 新入生のニックネームを本名(○期)に変更 & ○期ロールを付与 → 個人情報シートに新入生のdiscord IDを入力

5分もあれば終わる処理だけど、まぁまぁ面倒くさいので、勉強を兼ねてbotを作ってみることにした。

目標

  • 上記フローを自動化する。
  • 新メンバーと管理者のみが閲覧できる新入生ウェルカムチャンネルを作り、いわゆる ようこそ画面 の代わりとする。新たにサーバーに入った新入生は、まずこのチャンネルと自己紹介チャンネルのみ閲覧できるようになっており、新入生ウェルカムチャンネルに自分の名前を入力すると他のチャンネルも閲覧できるようになる。その後、ウェルカムチャンネルはアクセス出来なくなる。

実行環境

  • マシン:Ventura 13.4 のMac Mini
  • エディター:VS Code
  • インストールが必要なパッケージ
    • Node.js:今回はv18.15.0を使った
    • discord.js:v14を推奨
    • dotenv
    • axios
    • nodemon

コード

全体構成

今回の機能に関係するものを抜き出した。(こういうツリー構造をスムーズに書くにはどうすれば良いんでしょう。ターミナルでtreeコマンドを打ち、その出力を<pre>タグ内に貼り付けているけど、そのままだと表示がズレる。)

discord_bot
├── .env
├── .gitignore
├── events
│     ├── guildMemberAdd.js
│     ├── messageCreate.js
│     └── ready.js
├── index.js
├── newMember
│     ├── indexForNewMember.js
│     └── modifyDiscord.js
└── node_modules

全体の流れは次の通り:

サーバーに新メンバーが入る
↪︎(1) guildMemberAdd.js が実行され、NewMemberロールを付与する
↪︎新メンバーが新入生ウェルカムチャンネルに名前を送信する
↪︎(2) messageCreate.js が実行され、データのチェック, discordの各種設定(ニックネームの変更, ロールの変更など), 個人情報シートの更新を行う。
↪︎新メンバーが他のチャンネルを閲覧できるようになり、新入生ウェルカムチャンネルへのアクセスは失う。

各機能の解説

(0)根幹ファイル

nodemonで常時動かしているのはindex.js。なお、セキュリティー上公開できない情報(トークン, サーバーIDなど)は全て.envファイルに保存してある。それを別ファイルで用いたいときは、process.env.TOKENのように書く。

require('dotenv').config()
const { Client, Collection, Events, IntentsBitField } = require("discord.js")
const fs = require('node:fs')
const path = require('node:path')

//今回のbotに必要な権限を与える
const client = new Client({
    intents: [
        IntentsBitField.Flags.Guilds,
        IntentsBitField.Flags.GuildMembers,
        IntentsBitField.Flags.GuildMessages,
        IntentsBitField.Flags.MessageContent,
    ]
})

//各種イベントはeventsフォルダーに格納しているので、それを全て読み込む
const eventsPath = path.join(__dirname, 'events')
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'))

//readyイベントはbotの起動時のみ、その他のイベントには常時反応できるようにする
for (const file of eventFiles){
    const filePath = path.join(eventsPath, file)
    const event = require(filePath)
    if(event.once){
        client.once(event.name, (...args) => event.excute(...args))
    }else{
        client.on(event.name, (...args) => event.excute(...args))
    }
}

//botを起動
client.login(process.env.TOKEN)

(1)guildMemberAddイベント

サーバーに新メンバーが入ると、以下のコードが実行される。NewMemberロールのIDは書き換えてください。

guildMemberAdd.js

const {Events} = require('discord.js')

module.exports = {
   name: Events.GuildMemberAdd,
   async excute(member){
      //臨時のロールNewMemberを付与する。
      await member.roles.add('1116738049548755044')
   }
}

(2)messageCreateイベント

サーバー内のいずれかのチャンネルにメッセージが送信されると、以下のコードが実行される。そのチャンネルが新入生ウェルカムチャンネルであり、送信者がbot以外のときのみ、indexForNewMember.js ファイルに処理を引き渡す。

messageCreate.js

const {Events} = require('discord.js')

module.exports = {
    name: Events.MessageCreate,
    async excute(message){
        //新入生ウェルカムチャンネルにのみ反応する
        if(message.channelId==process.env.WELCOME_CHANNEL_ID && !message.author.bot){
            const indexForNewMember = require('../newMember/indexForNewMember.js')
            indexForNewMember(message)
        }
    }
}

indexForNewMember.js ファイルは以下の動作をまとめたもので、①の動作は同ディレクトリーの modifyDiscord.js ファイルで行っている。

  • 入力された名前が個人情報シートに記録されているかをチェック
  • ①新入生のロールとニックネームを変更する
  • 個人情報シートに新入生のdiscord IDを追記する
  • 新入生ウェルカムチャンネルの不要な投稿を削除する

indexForNewMember.js

const {Events} = require('discord.js')
const axios = require('axios')

module.exports = async (message)=>{
    const name = message.content
    const requestToPersonalInfoSheet = await axios.get(`${process.env.PERSONAL_INFO_SHEET}/search?sheet=名簿&氏名=${name}`)
    const record = requestToPersonalInfoSheet.data

    if(record.length===0){
        message.reply(`❌ ${name}さんのデータは個人情報シートに記録されていません。表記を確認して、もう一度お試しください。`)
    }else{
        const memberID = message.author.id
        const gen = record[0]['代'] //integer

        //新入生のDiscord設定を変更(ロールの変更, ニックネームの変更)。
        const modifyDiscord = require('./modifyDiscord.js')
        await modifyDiscord(message, name, gen)

        //個人情報シートを更新
        const discordInfoSheet = axios.post(`${process.env.PERSONAL_INFO_SHEET}?sheet=discord`, {名前: name, 代: gen, ID: memberID})

        //ウェルカムchの不要なメッセージを削除する
        const messagesToDelete = await message.channel.messages.fetch({after: '1116774827383066684'})
        message.channel.bulkDelete(messagesToDelete)
    }
}

modifyDiscord.js

const {Events} = require('discord.js')

module.exports = async (message, name, gen) => {
    //gen期のロールを付与する。ToDo管理botロールのランクを○期より上に設定しておく必要がある。
    const role = message.guild.roles.cache.find(role => role.name===`${gen}期`)
    if (!role) {
        console.log(`${gen}期ロールが存在しません。`); 
        return;
    }

    if(!message.member.roles.cache.has(role.id)){
        try {
            await message.member.roles.add(role)
        } catch (error) {
            console.log(`Error adding role to new member: ${error}`)
        }
    }

    //ニックネームを変更。これもToDo管理botより高いロールが既に付与されている人に対してはエラーを吐く。
    try {
        await message.member.setNickname(`${name}(${gen}期)`) 
    } catch (error) {
        console.log(`Unable to change the nickname. Error: ${error}`)
    }

    await message.reply(`${name}さんのDiscord設定が完了しました。他のチャンネルも覗いてみてください!`)

    //NewMemberロールを削除
    await message.member.roles.remove('1116738049548755044')
}

ソースコード

GitHubにアップロード済み。元々はdiscordサーバーで完結するtodo管理機能を作ろうと思っていたので、discord_todo_botというフォルダー名になっている。この記事に書かれていないGitHub上のコードは、大体todo機能関連。

クラウドサーバーで常時起動

別の記事を参照。