@silentを検出したい時ってありますよね。
あ~あるある!と言ってくれる人が多いことを願ってこの記事を書いています。
実際そんな多い気はしませんが、個人的なメモもかねてやり方を書きます。
そもそも@silentって何?
@silentというのは、Discordの標準機能として用意されているもので、
メッセージの先頭にこれを入れると、パソコンのデスクトップ通知やスマホの通知欄に現れなくなります。
例えば、夜間にどうでもいいメッセージを送って睡眠を阻害したくないときとかに使ってみるとイイ感じです。
@silentどうでもいいけど、お前のプリン食っといたから。
こんな感じです。プリンが静かに取られちゃいましたね。
今すぐにでも飛び起きてケンカしたいところですが、@silentで通知が来ていないので音とか振動で起きることができません。トホホ。
使い方は異なりますが、LINEにも同じような機能が備わっています(気になる人は自分で調べてくださいネ)。
結論
まず結論から申し上げますと、これでいけます。
if message.flags.silent:
# メッセージが@silentの時の処理
マジでこれだけです。
ここからは余談です
ここからはマジで余談なので、気になる人だけ読んでください。
なんでこれ書いたの
まずなんでこれを書いたのかというと、わかりにくかったからです。
私は個人的に読み上げbotを作っているのですが、その中で「@silentの時だけ読み上げをスキップする」という部分の実装に困っていました。
@silentがサイレントになる問題
送信者は「@silent サイレントメッセージ」と書いてメッセージを送信するわけですよね。
じゃあそれなら「最初に@silentって入ってるメッセージかどうかを判定すればいいじゃん。」となりますよね。
私もそうなりました。具体的なコードとしては以下です。
# NG例
if message.content.startswith("@silent"):
# メッセージが@silentの時の処理
.startswith()は、特定の文字列で始まっている文字列かどうかを検証するメソッドです。
なので、さっきの理論が正しければこれで通るはずです。
しかし現実は違いました。
こういう解釈の違いがあるんです。
送信者「
@silentサイレントメッセージ」
bot「サイレントメッセージ」
あれ?
@silentが神隠しにあってしまいました。
discord.message.contentの中には@silentは含まれないお約束なのです。
わからないからAIに聞いてみよう!
最近、検索で使うブラウザーをCometに乗り換えたので、さっそくパープレに聞いてみることにしました。
パープレ「これでできるッピよ!」
どれどれ…
# AIから出力されたもの if message.content.startswith("@silent"): # メッセージが@silentの時の処理
おい!それできなかったんだって俺。
他に方法はないかと聞いてみました。すると…
パープレ「あるッピよ!例えば…
- 完全一致:
if message.content == "@silent":- メンションとして:
if any(m.name.lower() == "silent" for m in message.mentions):- 正規表現:(割愛します)
- コマンド形式:
@bot.command(name="silent")を!silentコマンドとして登録する- splitで単語確認:
if message.content.split()[0] == "@silent":ってところッピ!」
うーん、全部ダメや!
なんでダメなん?
一個ずつダメな理由を解説していきます。
①完全一致
完全一致しか判定しないというのは意味が全くないです。例えば冒頭の「@silent どうでもいいけど、お前のプリン食っといたから。」も取り逃します。
②メンションとして
これ、割とそれっぽくね?と思ってリンク先(stackoverflow)を参照してみたんですが、そのページのどこからこのコードを引っ張ってきたのか意味が分かりませんでした。なんならリンク先は「どうやってbotに@silentメッセージを送信させるん?」という真逆の内容でした。(念のため最後に後述します)
③正規表現として
割愛していますが、.startswith("@silent"):と同義なのでできるわけがありませんでした。
④コマンド形式として
載せられているコマンドですが、多分これそもそも間違ってます。意味が分かりません。
プレフィックスを設定するとか、コマンドを新しく追加するとか、そんな書き方な気がしますが、どうも違うような気もします。
つまり謎です。無視しましょう。
⑤splitで単語確認
これは、text.split()としたとき、textをスペースごとに分割してリストにしますよ~というのを利用し、
「一番最初に@silentを取り出せたらこれはサイレントメッセージですね!」
っていう話です。もちろんこれも不可です。.startswith("@silent"):とほぼ一緒です。
もう一回質問したら公式のAPIリファレンスからやっと正解引っ張ってきました。
ええ、まあつまりこの問いに対する有益な情報が検索エンジンやAIにさえ簡単に見つけられないということです。
じゃあ私が見つけよう!
ということで、手探りで頑張ってメカニズムを調査したので結果を下に書きたいと思います。
@silentを検出するメカニズム
ここで私は、messageクラスの中にこの@silentに関する何かが潜んでいるのでは…?!と思い、printを使ってみることにしました。結果はこのようになりました。
print(message)
# 通常のメッセージのとき
# <Message id=(メッセージID)
# ...(中略)...
# flags=<MessageFlags value=0>>
# サイレントメッセージのとき
# <Message id=(メッセージID)
# ...(中略)...
# flags=<MessageFlags value=4096>>
おぉっ!?
MessageFlagsが怪しいですね。でも、4096っていうのが何か引っかかったんです。何か別の意味も表しているのではないかと。
メッセージ中に@everyoneやら@ほかのユーザーやら@ロールやらを試しましたが、やっぱり@silentの時だけ<MessageFlags value=4096>となるようです。
つまり、こういうことを書けば@silentの時だけ判定できると思うのです。
if message.flags.value == 4096:
# メッセージが@silentの時の処理
やはりこの読みは当たっていたようで、これをすることで@silentの判定が完璧にできるようになりました。
なんで4096?
疑問に思って調べてみました。すると、Discord公式のDeveloper向けドキュメントにたどり着きました。
中にはこのような表が含まれていました。

引用元:https://discord.com/developers/docs/resources/message#message-object-message-flags
この中のSUPPRESS_NOTIFICATIONSというのが、いわゆる@silentが含まれたメッセージのことです。これの値は1 << 12。
1 << 12ってなんだ。
これはどうやら、2進数で1を何回左にズラすかを表しているようです(左シフト演算子と言うそうです)。つまり、
| 左シフト演算子 | 2進数表記 | 10進数表記 |
|---|---|---|
1 << 12 |
1000000000000 | 4096 |
ということみたいです。
つまり、さっきのコードを使って判別することで一件落着ッ…!
もっとシンプルな方法、ありました。
Discord.py公式のAPIリファレンスを改めて読み込んでみました。すると…
あるやないかい!
ということで。冒頭に出てきたこのコードが完成したわけです。
if message.flags.silent:
# メッセージが@silentの時の処理
ちなみに、本来は下記の書き方らしいです。
if message.flags.suppress_notifications:
# メッセージが@silentの時の処理
長いし、日本語圏の私にとっては覚えられないので(ただ英弱なだけ)、今後も上のコードでいかしてもらいます。
botが@silentメッセージを飛ばす方法
記事のタイトルの逆のことが知りたい方へ向けて。
await message.channel.send("これはサイレントメッセージです。", silent=True)
これがbotが@silentメッセージを送信する方法です。
返信や引用などの場合はがんばって読み替えてください。
おわり
Qiitaで先駆者がいらっしゃらないかな…と思って検索しても、それっぽいのが出てこなかったので、なんとなーくですが書かせていただきました。
皆様がより良いDiscord.pyライフになることを願って、締めたいと思います。ありがとうございました。