9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Discord.pyで1からスーパーグローバルチャット導入

Last updated at Posted at 2021-08-20

グローバルチャットとは

グローバルチャットとは、Discordのチャンネルを跨いで会話出来るチャットの事です。
特定のチャンネルのメッセージをBOTが受信し、他のチャンネルへメッセージを転送することで、別のサーバーにいる相手とも、あたかも同じチャンネルで会話しているような感覚でチャットを行えます。
スクリーンショット 2021-08-20 16.02.34.png

スーパーグローバルチャットとは

従来のグローバルチャットでは、BOTがメッセージを仲介して他のチャンネルへと送信する仕組みのため、同じ会話に参加したければ、同じBOTを導入する必要があります。
また、グローバルチャットを導入しているBOTが増えすぎたために、結局会話が分散してしまい、グローバルチャットをサーバーに導入しようと思っても、どのBOTを入れれば良いのか混乱してしまいます。
そこで、BOT間でも連携を行い、更に大きなグローバルチャットを作ろうと考えたのが、スーパーグローバルチャットです。
スクリーンショット 2021-08-20 16.21.35.png

スーパーグローバルチャットでは、おおよそこのような形式でJSONメッセージを作成し、特定のチャンネルに投稿することで、BOT間でメッセージを交換しています。

{
  "type": "message", 
  "userId": "607645717623996426",
  "userName": "つきこう",
  "userDiscriminator": "2710",
  "userAvatar": "a118cc1ef21fcbac75764483108888fb",
  "isBot": false,
  "guildId": "706543524958699570",
  "guildName": "Newグローバルチャットサーバー",
  "guildIcon": "db2be882afde5818366405376458a774",
  "channelId": "707158194572623903",
  "channelName": "kensaku-sgc",
  "messageId": "773865745187733514",
  "content": "Hello World",
  "reference": "877075663352389672",
  "attachmentsUrl": [ 
    "https://cdn.discordapp.com/attachments/771644708048470046/771652380143517706/video0.mov", 
    "https://cdn.discordapp.com/attachments/771644708048470046/771652381800398868/video1.mov"
  ]
}

スーパーグローバルチャットはこのサーバーで導入しております。
動作感の確認や各種ご連絡はこちらへどうぞ
The Global Chat's Guild

この記事について

おおよそのスーパーグローバルチャットの概要を説明したところで、いよいよグローバルチャットの作成に進みましょう。
この記事では、いくつかのステップに分けて、Discord.pyでの他のチャンネルへのメッセージ転送、グローバルチャットの作成、スーパーグローバルチャットの導入と進んでいきます。
この記事の前提条件として、下記を想定しています。

  • Pythonがなんとなく扱える
  • Discord.pyを導入し、BOTを動作させることができる
  • 埋め込み(Embed)でグローバルチャットの作成 (今回はWebhookは扱わない)

メッセージを他のチャンネルに転送する機能の作成

まずは、発言したことをそのまま同じ名前のチャンネルに送信する機能の作成です。

import discord

TOKEN = "<Token>" #トークンを入力
global_channel_name = "super-global-test" #設定したいチャンネル名を入力

client = discord.Client() #接続に必要なオブジェクトを生成

@client.event
async def on_message(message):
    if message.channel.name == global_channel_name: #グローバルチャットにメッセージが来たとき
        #メッセージ受信部
        if message.author.bot: #BOTの場合は何もせず終了
            return
        #メッセージ送信部
        for channel in client.get_all_channels(): #BOTが所属する全てのチャンネルをループ
            if channel.name == global_channel_name: #グローバルチャット用のチャンネルが見つかったとき
                if channel == message.channel: #発言したチャンネルには送らない
                    continue

                await channel.send(message.content) #メッセージを送信

client.run(TOKEN)

これで、メッセージを転送するだけの機能は作れました。
スクリーンショット 2021-08-20 16.36.34.png

メッセージを送信した事を知らせるリアクション

先程の await channel.send(message.content) の後のfor文を抜けた位置に以下のものを付けるだけです。

await message.add_reaction('')

スクリーンショット 2021-08-20 16.40.47.png
このままでは味気ないので、次はグローバルチャットのメッセージの土台作りです。

埋め込み(Embed)でメッセージを送信しよう

Discordには埋め込み(Embed)という機能があり、これを利用すると簡単にユーザーのメッセージを再現出来ます。
まずは、メッセージを埋め込み(Embed)にいれるだけ。
メッセージの送信部を以下のように変えます。

embed=discord.Embed(description=message.content, color=0x9B95C9) #埋め込みの説明に、メッセージを挿入し、埋め込みのカラーを紫`#9B95C9`に設定
await channel.send(embed=embed)

スクリーンショット 2021-08-20 16.43.12.png
でも、これだと誰から来たメッセージなのか分からないので、ユーザーネームとアイコン画像を入れましょう。

ユーザーネームとアイコン画像の追加

先程の embed=discord.Embed(description=message.content, color=0x9B95C9) の下に、以下のものを追加します。

embed.set_author(name=message.author.name,icon_url="https://media.discordapp.net/avatars/{}/{}.png?size=1024".format(message.author.id, message.author.avatar))

※Discord.py v2以降の場合
このままではエラーとなるため、以下のようにします。

if hasattr(message.author.avatar, 'key'): #アイコン画像が設定されているとき
    embed.set_author(name=message.author.name,icon_url="https://media.discordapp.net/avatars/{}/{}.png?size=1024".format(message.author.id, message.author.avatar.key))
else:
    embed.set_author(name=message.author.name)

スクリーンショット 2021-08-20 16.49.32.png

これで送信者は分かるようになりました。

ちなみに、name=の箇所を以下のようにすると Tsukikoh#0721 のような、Discordのユーザータグを表示させる事ができます。

name="{}#{}".format(message.author.name, message.author.discriminator)

スクリーンショット 2021-08-20 16.52.35.png

サーバー名の設定

グローバルチャットは、他のチャンネルとの会話ができるので、時にどのサーバーから発言しているのか知りたくなることがあります。
先程のembed.set_author...の次の行に下記のものを追加します。

embed.set_footer(text="{} / mID:{}".format(message.guild.name, message.id),icon_url="https://media.discordapp.net/icons/{}/{}.png?size=1024".format(message.guild.id, message.guild.icon))

※Discord.py v2以降の場合
このままではエラーとなるため、以下のようにします。

if hasattr(message.guild.icon, 'key'): #アイコン画像が設定されているとき
    embed.set_footer(text="{} / mID:{}".format(message.guild.name, message.id),icon_url="https://media.discordapp.net/icons/{}/{}.png?size=1024".format(message.guild.id, message.guild.icon.key))
else:
    embed.set_footer(text="{} / mID:{}".format(message.guild.name, message.id))

スクリーンショット 2021-08-20 16.57.07.png

ちなみに、mID:は、後でBOTがメッセージを検索しやすいようにするために、埋め込みに元のメッセージのIDを書き込んでいます。この機能は後ほど使います。

#画像を表示
埋め込みには、画像を挿入することもできます。時にはグローバルチャットで画像を共有したくなる事もあるでしょう。
先程の次の行に、以下のものを追加します。

if message.attachments != []: #添付ファイルが存在するとき
    embed.set_image(url=message.attachments[0].url)

このパターンでは、添付ファイルが存在するときに、1枚目の画像だけを埋め込みに添付しています。
スクリーンショット 2021-08-20 17.03.16.png
埋め込みでは、複数枚の画像を入れることができませんし、動画や音楽ファイル等もサポートしていません。そこは残念です。

メッセージの返信を取得

Discordには、最近返信機能が追加されました。
会話中に複数の話題が同時に持ち上がる事ってありますよね。グローバルチャットともなればなおさらだと思います。
そんなときに、どの会話に対して話しているのか示せると便利ですよね。
先程の次の行に以下のものを追加します。

if message.reference: #返信メッセージであるとき
    reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
    if reference_msg.embeds and reference_msg.author == client.user: #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断
        reference_message_content = reference_msg.embeds[0].description #メッセージの内容を埋め込みから取得
        reference_message_author = reference_msg.embeds[0].author.name #メッセージのユーザーを埋め込みから取得
    elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
        reference_message_content = reference_msg.content #メッセージの内容を取得
        reference_message_author = reference_msg.author.name+'#'+reference_msg.author.discriminator #メッセージのユーザーを取得
    reference_content = ""
    for string in reference_message_content.splitlines(): #埋め込みのメッセージを行で分割してループ
        reference_content += "> " + string + "\n" #各行の先頭に`> `をつけて結合
    reference_value = "**@{}**\n{}".format(reference_message_author, reference_content) #返信メッセージを生成
    embed.add_field(name='返信しました', value=reference_value, inline=True) #埋め込みに返信メッセージを追加

スクリーンショット 2021-08-21 14.44.55.png

ちなみに、行で分割して、各行の先頭に> を入れて再度結合していますが、これは、Discordの埋め込みでは、Markdown形式の引用が利用出来るため、その形式に変換しています。
スクリーンショット 2021-08-21 14.45.16.png

グローバルチャット完成

ここまでで、グローバルチャットの基本の機能が導入できました。

ここまでのプログラムまとめ

import discord

TOKEN = "<Token>" #トークンを入力
global_channel_name = "super-global-test" #設定したいチャンネル名を入力

client = discord.Client() #接続に必要なオブジェクトを生成

@client.event
async def on_message(message):

    if message.channel.name == global_channel_name: #グローバルチャットにメッセージが来たとき
        #メッセージ受信部
        if message.author.bot: #BOTの場合は何もせず終了
            return
        #メッセージ送信部
        for channel in client.get_all_channels(): #BOTが所属する全てのチャンネルをループ
            if channel.name == global_channel_name: #グローバルチャット用のチャンネルが見つかったとき
                if channel == message.channel: #発言したチャンネルには送らない
                    continue
                    
                embed=discord.Embed(description=message.content, color=0x9B95C9) #埋め込みの説明に、メッセージを挿入し、埋め込みのカラーを紫`#9B95C9`に設定
                embed.set_author(name="{}#{}".format(message.author.name, message.author.discriminator),icon_url="https://media.discordapp.net/avatars/{}/{}.png?size=1024".format(message.author.id, message.author.avatar))
                embed.set_footer(text="{} / mID:{}".format(message.guild.name, message.id),icon_url="https://media.discordapp.net/icons/{}/{}.png?size=1024".format(message.guild.id, message.guild.icon))
                if message.attachments != []: #添付ファイルが存在するとき
                    embed.set_image(url=message.attachments[0].url)

                if message.reference: #返信メッセージであるとき
                    reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
                    if reference_msg.embeds and reference_msg.author == client.user: #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断
                        reference_message_content = reference_msg.embeds[0].description #メッセージの内容を埋め込みから取得
                        reference_message_author = reference_msg.embeds[0].author.name #メッセージのユーザーを埋め込みから取得
                    elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
                        reference_message_content = reference_msg.content #メッセージの内容を取得
                        reference_message_author = reference_msg.author.name+'#'+reference_msg.author.discriminator #メッセージのユーザーを取得
                    reference_content = ""
                    for string in reference_message_content.splitlines(): #埋め込みのメッセージを行で分割してループ
                        reference_content += "> " + string + "\n" #各行の先頭に`> `をつけて結合
                    reference_value = "**@{}**\n{}".format(reference_message_author, reference_content) #返信メッセージを生成
                    embed.add_field(name='返信しました', value=reference_value, inline=True) #埋め込みに返信メッセージを追加
                    
                await channel.send(embed=embed) #メッセージを送信
        await message.add_reaction('') #リアクションを送信

client.run(TOKEN)

いよいよスーパーグローバルチャットの導入へ進みましょう。

スーパーグローバルチャットの導入

スーパーグローバルチャットとはで説明したように、スーパーグローバルチャットは、JSONメッセージをBOT間で送受信することにより、BOTを超えたグローバルチャットを実現しています。

スーパーグローバルチャット導入の為の下準備

まずは、スーパーグローバルチャットに必要となるパッケージを標準ライブラリからインポートします。

import json #JSONを扱うために使用
import urllib.parse #パーセントエンコーディングを扱うために使用

次に、JSONを試しに送受信するための仮のチャンネルを作成し、IDをコピーします。
スクリーンショット 2021-08-21 4.05.03.png

ここでは、このJSONを扱うためのチャンネルのIDを878353900275642478とします。

JSONのチャンネルを読み込むように条件変更

まずは、グローバル変数のglobal_channel_name = "super-global-test"次の行に以下のものを追加します。

json_channel_id = 878353900275642478 #JSONチャンネルID

次に、プログラムを一部変更します。

変更前
if message.channel.name == global_channel_name: #グローバルチャットにメッセージが来たとき
    if message.author.bot: #BOTの場合は何もせず終了
        return

    for channel in client.get_all_channels(): #BOTが所属する全てのチャンネルをループ
変更後
if message.channel.name == global_channel_name or message.channel.id == json_channel_id:#グローバルチャットかJSONチャンネルにメッセージが来たとき
    if message.channel.name == global_channel_name: #グローバルチャットにメッセージが来たとき
        if message.author.bot: #BOTの場合は何もせず終了
            return
        """ここにJSONの送信部分を記述します"""

    if message.channel.id == json_channel_id: #JSONチャンネルにメッセージが来たとき
        if message.author == client.user: #メッセージ送信者がこのBOTの場合は何もせず終了
            return
        """ここにJSONの受信部分を記述します"""

    for channel in client.get_all_channels(): #BOTが所属する全てのチャンネルをループ

辞書型リストを作成し、JSONを出力

先程指定した場所に、下記のものを追加します。

"""ここにJSONの送信部分を記述します"""

dic = {} #辞書型リストを初期化
dic.update({"type": "message"}) #JSONで送信するデータの種類: メッセージ
dic.update({"userId": str(message.author.id)}) #ユーザーID
dic.update({"userName": message.author.name}) #ユーザーネーム
dic.update({"userDiscriminator": message.author.discriminator}) #ユーザータグ
dic.update({"userAvatar": message.author.avatar}) #ユーザーのアバター画像を示すキー
dic.update({"isBot": message.author.bot}) #ユーザーがBOTかどうか
dic.update({"guildId": str(message.guild.id)}) #サーバーID
dic.update({"guildName": message.guild.name}) #サーバー名
dic.update({"guildIcon": message.guild.icon}) #サーバーアイコン画像を示すキー
dic.update({"channelId": str(message.channel.id)}) #チャンネルID
dic.update({"channelName": message.channel.name}) #チャンネル名
dic.update({"messageId": str(message.id)}) #メッセージID
dic.update({"content": message.content}) #メッセージ内容

jsondata = json.dumps(dic, ensure_ascii=False) #辞書型リストをJSONに変換
await client.get_channel(json_channel_id).send(jsondata) #JSONチャンネルにJSONを送信

2021/09/02追記
※2021/09/01からスーパーグローバルチャットでは、ID類(messageId、userId、channelId、guildId、reference)が文字型(str型)になります。
スーパーグローバルチャットの規格策定時は、Discord.py(Python3)の仕様に合わせ、ID類はint型(JSONのNumber型)としていましたが、Discord.js(Node.js)では、Int型で扱うと下の桁が抜け落ちてしまい、それを防止するためにBigInt型で扱う事としていました。しかし、これでは標準ライブラリのJSONパッケージが使えないなどの不都合が合ったために、Str型に統一することとなりました。
当記事のサンプルプログラムでは、以前より問題無く作動するよう設計しておりました。

※Discord.py v2以降の場合
このままではエラーとなるため、以下のようにします。

#ユーザーのアバター画像を示すキー
if hasattr(message.author.avatar, 'key'): #アバター画像が設定されているとき
    dic.update({"userAvatar": message.author.avatar.key})
else:
    dic.update({"userAvatar": None})
#サーバーアイコン画像を示すキー
if hasattr(message.guild.icon, 'key'): #アイコン画像が設定されているとき
    dic.update({"guildIcon": message.guild.icon.key})
else:
    dic.update({"guildIcon": None})

これで、添付ファイルや返信を除くメッセージのJSONデータが送信できるようになりました。
スクリーンショット 2021-08-21 4.53.17.png

添付ファイルをJSONに追加

dic.updateの行の終わりとjsondata = の行の間に、以下のものを追加します。

if message.attachments != []: #添付ファイルが存在するとき
    arr = [] #リストを初期化
    for attachment in message.attachments: #添付ファイルをループ
        arr.append(attachment.proxy_url) #添付ファイルのURLを追加
    dic.update({"attachmentsUrl": arr})

これで、JSONデータに添付ファイルが追加されました。
スクリーンショット 2021-08-21 4.52.37.png

2021/09/02 追記
※2021/09/01から、スーパーグローバルチャットでは添付ファイルのパーセントエンコーディングが撤廃されます。
スーパーグローバルチャットの規格を作るときに、DiscordのメッセージでJSONの中にURLが入ることで、途中から全てリンクになってしまったため、それを解消するためにパーセントエンコーディングを導入しましたが、後からパーセントエンコーディングなどを行わなくても問題無く送受信できることを確認しました。

スクリーンショット 2021-08-21 4.54.46.png

返信元メッセージIDをJSONに追加

スーパーグローバルチャットでは、返信元メッセージを、メッセージIDで指定します。
同じチャンネル内で返信している場合は、そのままメッセージIDを取得すれば良いのですが、他のチャンネルや他のBOTから受信したメッセージの返信の場合は少し複雑です。
そのために、あらかじめ埋め込みのフッターに差し込んであった「mID:」(大元のメッセージのID)を使用します。

if message.reference: #返信のとき
    reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
    reference_mid = 0 #メンバーID用変数を初期化
    if reference_msg.embeds and reference_msg.author == client.user:  #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断

        arr = reference_msg.embeds[0].footer.text.split(" / ") #埋め込みのフッターを「 / 」区切りで取得

        for ref_msg in arr: #区切ったフッターをループ
            if "mID:" in ref_msg: #「mID:」が含まれるとき
                reference_mid = ref_msg.replace("mID:","",1) #「mID:」を取り除いたものをメッセージIDとして取得
                break

    elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
        reference_mid = str(reference_msg.id) #返信元メッセージIDを取得

    dic.update({"reference": reference_mid})

これで、JSONで返信元メッセージを伝達できるようになりました。

メッセージ送信部を辞書型リストに適応させる

JSONを実際に受信する前に、このままでは、JSONを受信したときに表示ができないので、修正していきます。

埋め込み本体

変更前
embed=discord.Embed(description=message.content, color=0x9B95C9) #埋め込みの説明に、メッセージを挿入し、埋め込みのカラーを紫`#9B95C9`に設定
変更後
embed=discord.Embed(description=dic["content"], color=0x9B95C9) #埋め込みの説明に、メッセージを挿入し、埋め込みのカラーを紫`#9B95C9`に設定

ユーザー情報

変更前
embed.set_author(name="{}#{}".format(message.author.name, message.author.discriminator),icon_url="https://media.discordapp.net/avatars/{}/{}.png?size=1024".format(message.author.id, message.author.avatar))
変更後
embed.set_author(name="{}#{}".format(dic["userName"], dic["userDiscriminator"]),icon_url="https://media.discordapp.net/avatars/{}/{}.png?size=1024".format(dic["userId"], dic["userAvatar"]))

フッター

変更前
embed.set_footer(text="{} / mID:{}".format(message.guild.name, message.id),icon_url="https://media.discordapp.net/icons/{}/{}.png?size=1024".format(message.guild.id, message.guild.icon))
変更後
if message.channel.name == global_channel_name:
    bot_name = "このBOT"
else:
    bot_name = message.author.name
embed.set_footer(text="{} / {} / mID:{}".format(dic["guildName"], bot_name, dic["messageId"]),icon_url="https://media.discordapp.net/icons/{}/{}.png?size=1024".format(dic["guildId"], dic["guildIcon"]))

このBOTで受信したメッセージなのか、他のBOTから受け取ったメッセージなのかを表示出来るように、「bot_name」を追加しています。

添付ファイル

変更前
if message.attachments != []: #添付ファイルが存在するとき
    embed.set_image(url=message.attachments[0].url)
変更後
if "attachmentsUrl" in dic: #添付ファイルが存在するとき
    embed.set_image(url=urllib.parse.unquote(dic["attachmentsUrl"][0]))

メッセージ返信

変更前
if message.channel.name == global_channel_name:
reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
if reference_msg.embeds and reference_msg.author == client.user: #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断
    reference_message_content = reference_msg.embeds[0].description #メッセージの内容を埋め込みから取得
    reference_message_author = reference_msg.embeds[0].author.name #メッセージのユーザーを埋め込みから取得
elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
    reference_message_content = reference_msg.content #メッセージの内容を取得
    reference_message_author = reference_msg.author.name+'#'+reference_msg.author.discriminator #メッセージのユーザーを取得
変更後
if message.reference: #返信メッセージであるとき
    if message.channel.name == global_channel_name:
    reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
    if reference_msg.embeds and reference_msg.author == client.user: #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断
        reference_message_content = reference_msg.embeds[0].description #メッセージの内容を埋め込みから取得
        reference_message_author = reference_msg.embeds[0].author.name #メッセージのユーザーを埋め込みから取得
    elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
        reference_message_content = reference_msg.content #メッセージの内容を取得
        reference_message_author = reference_msg.author.name+'#'+reference_msg.author.discriminator #メッセージのユーザーを取得
else: #JSONチャンネルから受信したとき
    reference_mid = dic["reference"] #返信元メッセージID

    reference_message_content = "" #返信元メッセージ用変数を初期化
    reference_message_author = "" #返信元ユーザータグ用変数を初期化
    past_dic = None #返信元メッセージの辞書型リスト用変数を初期化

    async for past_message in message.channel.history(limit=1000): #JSONチャンネルの過去ログ1000件をループ
        try: #JSONのエラーを監視
            past_dic = json.loads(past_message.content) #過去ログのJSONを辞書型リストに変換
        except json.decoder.JSONDecodeError as e: #JSON読み込みエラー→そもそもJSONでは無い可能性があるのでスルー
            continue
        if "type" in past_dic and past_dic["type"] != "message": #メッセージでは無い時はスルー
            continue

        if not "messageId" in past_dic: #キーにメッセージIDが存在しない時はスルー
            continue

        if str(past_dic["messageId"]) == str(reference_mid): #過去ログのメッセージIDが返信元メッセージIDと一致したとき
            reference_message_author = "{}#{}".format(past_dic["userName"],past_dic["userDiscriminator"]) #ユーザータグを取得
            reference_message_content = past_dic["content"] #メッセージ内容を取得
            break

枠組みが一旦完成

これで、JSONメッセージを受け入れるための枠組みができました。
JSONを実際に受信する前に、この状態でBOT内でメッセージの送受信が問題無く行えるか確認しましょう。

ここまでのプログラムまとめ

import discord
import json
import urllib.parse

TOKEN = "<Token>" #トークンを入力
global_channel_name = "super-global-test" #設定したいチャンネル名を入力
json_channel_id = 878353900275642478 #JSONチャンネルID

client = discord.Client() #接続に必要なオブジェクトを生成

@client.event
async def on_message(message):

    if message.channel.name == global_channel_name or message.channel.id == json_channel_id:#グローバルチャットかJSONチャンネルにメッセージが来たとき
        #メッセージ受信部
        if message.channel.name == global_channel_name: #グローバルチャットにメッセージが来たとき
            if message.author.bot: #BOTの場合は何もせず終了
                return

            """ここにJSONの送信部分を記述します"""
            dic = {} #辞書型リストを初期化
            dic.update({"type": "message"}) #JSONで送信するデータの種類: メッセージ
            dic.update({"userId": str(message.author.id)}) #ユーザーID
            dic.update({"userName": message.author.name}) #ユーザーネーム
            dic.update({"userDiscriminator": message.author.discriminator}) #ユーザータグ
            dic.update({"userAvatar": message.author.avatar}) #ユーザーのアバター画像を示すキー
            dic.update({"isBot": message.author.bot}) #ユーザーがBOTかどうか
            dic.update({"guildId": str(message.guild.id)}) #サーバーID
            dic.update({"guildName": message.guild.name}) #サーバー名
            dic.update({"guildIcon": message.guild.icon}) #サーバーアイコン画像を示すキー
            dic.update({"channelId": str(message.channel.id)}) #チャンネルID
            dic.update({"channelName": message.channel.name}) #チャンネル名
            dic.update({"messageId": str(message.id)}) #メッセージID
            dic.update({"content": message.content}) #メッセージ内容

            if message.attachments != []: #添付ファイルが存在するとき
                arr = [] #リストを初期化
                for attachment in message.attachments: #添付ファイルをループ
                    arr.append(attachment.proxy_url) #添付ファイルのURLを追加
                dic.update({"attachmentsUrl": arr})

            if message.reference: #返信のとき
                reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
                reference_mid = 0 #メンバーID用変数を初期化
                if reference_msg.embeds and reference_msg.author == client.user:  #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断

                    arr = reference_msg.embeds[0].footer.text.split(" / ") #埋め込みのフッターを「 / 」区切りで取得

                    for ref_msg in arr: #区切ったフッターをループ
                        if "mID:" in ref_msg: #「mID:」が含まれるとき
                            reference_mid = ref_msg.replace("mID:","",1) #「mID:」を取り除いたものをメッセージIDとして取得
                            break

                elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
                    reference_mid = str(reference_msg.id) #返信元メッセージIDを取得

                dic.update({"reference": reference_mid})

            jsondata = json.dumps(dic, ensure_ascii=False) #辞書型リストをJSONに変換
            await client.get_channel(json_channel_id).send(jsondata) #JSONチャンネルにJSONを送信

        if message.channel.id == json_channel_id: #JSONチャンネルにメッセージが来たとき
            if message.author == client.user: #メッセージ送信者がこのBOTの場合は何もせず終了
                return
            """ここにJSONの受信部分を記述します"""

        #メッセージ送信部
        for channel in client.get_all_channels(): #BOTが所属する全てのチャンネルをループ
            if channel.name == global_channel_name: #グローバルチャット用のチャンネルが見つかったとき
                if channel == message.channel: #発言したチャンネルには送らない
                    continue
                    
                embed=discord.Embed(description=dic["content"], color=0x9B95C9) #埋め込みの説明に、メッセージを挿入し、埋め込みのカラーを紫`#9B95C9`に設定
                embed.set_author(name="{}#{}".format(dic["userName"], dic["userDiscriminator"]),icon_url="https://media.discordapp.net/avatars/{}/{}.png?size=1024".format(dic["userId"], dic["userAvatar"]))
                if message.channel.name == global_channel_name:
                    bot_name = "このBOT"
                else:
                    bot_name = message.author.name
                embed.set_footer(text="{} / {} / mID:{}".format(dic["guildName"], bot_name, dic["messageId"]),icon_url="https://media.discordapp.net/icons/{}/{}.png?size=1024".format(dic["guildId"], dic["guildIcon"]))
                if "attachmentsUrl" in dic: #添付ファイルが存在するとき
                    embed.set_image(url=urllib.parse.unquote(dic["attachmentsUrl"][0]))
                if message.reference: #返信メッセージであるとき
                    if message.channel.name == global_channel_name:
                        reference_msg = await message.channel.fetch_message(message.reference.message_id) #メッセージIDから、元のメッセージを取得
                        if reference_msg.embeds and reference_msg.author == client.user: #返信の元のメッセージが、埋め込みメッセージかつ、このBOTが送信したメッセージのとき→グローバルチャットの他のサーバーからのメッセージと判断
                            reference_message_content = reference_msg.embeds[0].description #メッセージの内容を埋め込みから取得
                            reference_message_author = reference_msg.embeds[0].author.name #メッセージのユーザーを埋め込みから取得
                        elif reference_msg.author != client.user: #返信の元のメッセージが、このBOTが送信したメッセージでは無い時→同じチャンネルのメッセージと判断
                            reference_message_content = reference_msg.content #メッセージの内容を取得
                            reference_message_author = reference_msg.author.name+'#'+reference_msg.author.discriminator #メッセージのユーザーを取得
                    else:
                        reference_mid = dic["reference"] #返信元メッセージID

                        reference_message_content = "" #返信元メッセージ用変数を初期化
                        reference_message_author = "" #返信元ユーザータグ用変数を初期化
                        past_dic = None #返信元メッセージの辞書型リスト用変数を初期化

                        async for past_message in message.channel.history(limit=1000): #JSONチャンネルの過去ログ1000件をループ
                            try: #JSONのエラーを監視
                                past_dic = json.loads(past_message.content) #過去ログのJSONを辞書型リストに変換
                            except json.decoder.JSONDecodeError as e: #JSON読み込みエラー→そもそもJSONでは無い可能性があるのでスルー
                                continue
                            if "type" in past_dic and past_dic["type"] != "message": #メッセージでは無い時はスルー
                                continue

                            if not "messageId" in past_dic: #キーにメッセージIDが存在しない時はスルー
                                continue

                            if str(past_dic["messageId"]) == str(reference_mid): #過去ログのメッセージIDが返信元メッセージIDと一致したとき
                                reference_message_author = "{}#{}".format(past_dic["userName"],past_dic["userDiscriminator"]) #ユーザータグを取得
                                reference_message_content = past_dic["content"] #メッセージ内容を取得
                                break
                    reference_content = ""
                    for string in reference_message_content.splitlines(): #埋め込みのメッセージを行で分割してループ
                        reference_content += "> " + string + "\n" #各行の先頭に`> `をつけて結合
                    reference_value = "**@{}**\n{}".format(reference_message_author, reference_content) #返信メッセージを生成
                    embed.add_field(name='返信しました', value=reference_value, inline=True) #埋め込みに返信メッセージを追加
                    
                await channel.send(embed=embed) #メッセージを送信
        await message.add_reaction('') #リアクションを送信

client.run(TOKEN)

次はいよいよ、実際にJSONを受信します。

JSONを受信

"""ここにJSONの受信部分を記述します"""
try: #JSONのエラーを監視
    dic = json.loads(message.content) #JSONを辞書型リストに変換
except json.decoder.JSONDecodeError as e: #JSON読み込みエラー→そもそもJSONでは無い可能性があるのでスルー
    return

if "type" in dic and dic["type"] != "message": #メッセージ以外のJSON場合はスルー
    return

if not "messageId" in dic: #キーに「"messageId"」がない場合
    dic["messageId"] = str(message.id) #JSONメッセージ本体のメッセージIDを仮に挿入

初期のスーパーグローバルチャットでは、キーに「"messageId"」はありませんでした。
旧式のスーパーグローバルチャットの形式を使用するBOTも存在しますし、エラーにならないようにするために、ここでは仮にJSONメッセージのメッセージIDを、大元のメッセージIDの代わりにしています。

完成!!!

これで、スーパーグローバルチャットの導入に必要な条件が全て整いました。
問題無く動作するかを確認し、是非スーパーグローバルチャットにご参加ください。
参加のご連絡は@Tskikoh#0721まで

スーパーグローバルチャットはこのサーバーで導入しております。
動作感の確認や参加以外のご連絡・質問等はこちらへどうぞ
The Global Chat's Guild

9
4
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
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?