見出し画像

[Disocrd.py]ボイスチャンネルに誰かが参加したらMacに通知を送るBOT

皆さん、Discordは使っていますか?

ゲーマー向けと謳われていますが、サーバやチャンネルの機能が充実しており会議通話アプリとしても非常に利便性が高いので、私も重宝しています。

さて、Discordで複数人との通話を行う場合、グループDMかサーバを利用することになります。

グループDMについては、誰かが通話を開始した際に参加者全員に着信通知が送られてしまったり、テキストチャンネルが一つしかなかったりとあまり使い勝手がよくありません。それに、招待リンクを用いて他のユーザに参加してもらうこともできませんからね。

そういった理由から、”身内でしか使わない”などの事情がなければ、サーバを利用することが多いです。しかし、こちらは先程のグループDMと違って、誰かがボイスチャンネルに参加しても通知が行われません。

ということで、今回はサーバのボイスチャンネルに誰かが参加したらMacに通知を送るBOTを作成したいと思います。

はじめに

※Pythonの導入やDiscordのBOT作成については紹介しません。

【環境】
・macOS Monterey 12.0.1
・Python 3.9.4
・Discord.py 2.0.0a

【参考】https://discordpy.readthedocs.io/ja/latest/api.html

ボイスチャンネルのイベントを取得する

まずは完成形のコードです。

# notification.py

from discord import message
from subprocess import PIPE


class Notification(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener()
    async def on_voice_state_update(self, member, before, after):

        if before.channel is None and after.channel is not None:
            print(member.display_name + "さんが" + after.channel.name + "に参加しました")

        if before.channel is not None and after.channel is None:
            print(member.display_name + "さんが通話を切断しました")
        
def setup(bot):
    return bot.add_cog(Notification(bot))

ではコードを解説します。

ドキュメントを読むと、ボイスチャンネルのイベントを取得するには on_voice_state_update メソッドを使用すると書かれています。

on_voice_state_update メソッドは member, before, after の3つの引数を受け取り、memberにはボイスチャンネルのイベントを発生させたメンバー、beforeとafterにはVoiceStateクラスが渡されています。

では、VoiceStateクラスについての情報を見てみましょう。

属性の中に、channnelという項目がありますね。

channel
The voice channel that the user is currently connected to. None if the    user is not currently in a voice channel.

Type:
    Optional[Union[VoiceChannel, StageChannel]] 
https://discordpy.readthedocs.io/ja/latest/api.html#voicestate

説明を読むと、どのVCにも参加していない場合はVoiceStateインスタンスのchannel属性がNoneになると書かれています。であれば、beforeのchannnel属性がNoneの場合且つ、afterのchannnel属性がnot Noneの場合は、「VC不参加→VC参加」と状態が遷移したと判定できます。

逆に、beforeのchannel属性がnot Noneであり、afterのchannel属性がNoneの場合、「VC参加→VC不参加」と遷移しているので、VCから切断したことがわかります。

ついでにミュートも取得

# notification.py

from discord import message
from subprocess import PIPE


class Notification(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener()
    async def on_voice_state_update(self, member, before, after):

        if before.channel is None and after.channel is not None:
            print(member.display_name + "さんが" + after.channel.name + "に参加しました")

        if before.channel is not None and after.channel is None:
            print(member.display_name + "さんが通話を切断しました")

        #--------------追加-----------------

        if not before.self_mute and after.self_mute:
            print(member.display_name + "さんがミュートになりました")
        
        if before.self_mute and not after.self_mute:
            print(member.display_name + "さんがミュートを解除しました")

        #--------------追加終わり-----------------
        
def setup(bot):
    return bot.add_cog(Notification(bot))

VoiceStateクラスは他にも、ミュート、スピーカーミュート、配信、ビデオの状態についても取得することが出来ます。

試しに参加者がミュート、またはミュート解除したかの判定を追記してみましょう。

先程のchannnel属性と考え方は同じですが、self_mute属性はbool型ですので、コードを少し変更する必要があります。
ちなみに、ミュート状態がTrue、非ミュート状態がFalseです。

self_mute
Indicates if the user is currently muted by their own accord.

Type:
    bool
https://discordpy.readthedocs.io/ja/latest/api.html#voicestate

macに通知する

macの通知センターへ通知を送る場合、ターミナルからosascriptコマンドを使用して通知を表示するApple Scriptを呼び出す方法があります。

$ osascript -e 'display notification "通知" with title "テスト通知"'

実行すると、このように通知が表示されます。

しかし、Apple Scriptで呼び出す通知は気軽にアイコンを変更したり、クリック時の動作を決めることができません。なので、外部のユーティリティを使用します。

ざっと調べた限り、以下の2つがちょうど良さそうです。

どちらも基本的な機能に差はありませんが、alerterの方がクリック時の動作を変更できたりと自由度が高いです。

今回は、通知を表示してクリック時にDiscordを開くという動作を実装したいだけなので、シンプルなterminal-notifierを利用しました。

terminal-notifierには色々なオプションがありますが、とりあえず以下の構成で通知を作成します。

$ terminal-notifier -title "タイトル" -message "内容" -contentImage https://xxx.c0m -sender 'com.hnc.Discord'

-senderオプションにDiscordを指定することにより、通知を押下した際にDiscord.appが前面に表示されます。

-contentImageについては無くても構いませんが、できるだけDiscordの通知と類似させたかったので追加しています。試しに通知を表示してみたい方は有効な画像のURLに変更してくださいね。

さて、目指すべき通知は以下の形です。

「表示名 (#チャンネル名, カテゴリ名)」のタイトルで、メッセージとユーザのアイコンが表示されています。この通知を表示するために必要な情報を、先程のBOTで取得します。

BOTで情報を取得

まず、ユーザについての情報を取得します。必要なのは、表示名と、アイコンです。

これは、on_voice_state_updateメソッドのmember引数に格納されています。memberはMemberクラスのオブジェクトなので、そちらのドキュメントを覗きましょう。

表示名については、先程のコードにある

member.display_name

で取得できています。アイコンについての情報はdisplay_avatar属性で取得できるのですが、こちらはAsset型となっているので、Asset型のurl属性を指定します。

display_avatar = member.display_avatar.url

これでアイコンのリンクが取得できました。

他に必要な要素は、参加したVCのチャンネル名と、そのチャンネルのカテゴリです。channel属性のnameでチャンネル名、categoryでカテゴリ名が取得できます。

では、これらをコードに追加します。print文を消したくない人は残しておいても構いません。

# notification.py

from discord import message
from subprocess import PIPE


class Notification(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener()
    async def on_voice_state_update(self, member, before, after):

        display_avatar = member.display_avatar.url

        if before.channel is None and after.channel is not None:
            title = f"{member.display_name} (#{after.channel.name}{after.channel.category})"
            message = "通話に参加しました"

        if before.channel is not None and after.channel is None:
            title = f"{member.display_name} (#{before.channel.name}{before.channel.category})"
            message = "通話を切断しました"

        if not before.self_mute and after.self_mute:
            title = f"{member.display_name} (#{before.channel.name}{before.channel.category})"
            message = "ミュートしました"
        
        if before.self_mute and not after.self_mute:
            title = f"{member.display_name} (#{after.channel.name}{before.channel.category})"
            message = "ミュート解除しました"

def setup(bot):
    return bot.add_cog(Notification(bot))

BOTからMacへ通知を送る

通知に必要な情報は全て揃ったので、実際に通知を表示しましょう。

Pythonでシェルのコマンドを実行するには、subprocessを利用します。

# notification.py

from discord import message
from subprocess import PIPE


class Notification(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener()
    async def on_voice_state_update(self, member, before, after):

        display_avatar = member.display_avatar.url

        if before.channel is None and after.channel is not None:
            title = f"{member.display_name} (#{after.channel.name}{after.channel.category})"
            message = "通話に参加しました"

        if before.channel is not None and after.channel is None:
            title = f"{member.display_name} (#{before.channel.name}{before.channel.category})"
            message = "通話を切断しました"

        if not before.self_mute and after.self_mute:
            title = f"{member.display_name} (#{before.channel.name}{before.channel.category})"
            message = "ミュートしました"
        
        if before.self_mute and not after.self_mute:
            title = f"{member.display_name} (#{after.channel.name}{before.channel.category})"
            message = "ミュート解除しました"

        subprocess.run(["terminal-notifier""-title", title, "-message", message, "-contentImage", display_avatar, "-sender""com.hnc.Discord"])

def setup(bot):
    return bot.add_cog(Notification(bot))

では動作を確認します。

問題なく通知が表示されていますね!

おわり

というわけで、DiscordのVCイベントをMacに通知させてみました。

以上

この記事が気に入ったらサポートをしてみませんか?