ケイマン君のブログ

ケイマン君のオフィシャルサイトです。主にPHPのコーディングやゲームなどをプレイしています。

discord-interactionの書き方

discord-inteactionとは?

discord公式が出してるhttp経由のdiscord-bot製法

メリット

flaskとかで動くから常時shellを動かす必要がない、しかもawsとかでもできる そのれいがcyclic.shでここの記事にあるようにdiscord-interactionのおかげで24時間動かせたりする。

デメリット

特にないが埋め込みでvideoオプションが使えなかったり、discord.py やdiscord.jsと少し書き方が異なる点 ※もちろんdiscord.py や discord.jsでないのでdiscord.pyやjsの機能は使えないです

早速サンプル

from flask import Flask, request, send_file
import os

app = Flask(__name__)

@app.route('/', methods=['GET'])

if __name__ == '__main__':
    app.run()

これは普通のflaskappですがここに

import yt_dlp
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import requests
import os
from discord_interactions import InteractionType, InteractionResponseType, verify_key_decorator

app = Flask(__name__)
CORS(app)

import subprocess
import json

PUBLIC_KEY = os.getenv('PUBLIC_KEY')
CLIENT_ID = os.getenv('CLIENT_ID')
DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')

@app.route('/interactions', methods=['POST'])
@verify_key_decorator(PUBLIC_KEY)
def interactions():
    # JSONデータを取得
    data = request.json

    print(data)

    # インタラクションの種類を取得
    interaction_type = data["type"]

    # PINGの場合はPONGを返す
    if interaction_type == 1:
        response_ping = {"type": 1}
        requests.post(f"https://discord.com/api/v9/interactions/{data['id']}/{data['token']}/callback", json=response_ping, headers={"Authorization": f"Bot {DISCORD_TOKEN}", "Content-Type": "application/json"})

    # APPLICATION_COMMANDの場合は遅延レスポンスを返す
    elif interaction_type == 2:
        # 遅延レスポンスを返す
        response_data = {"type": 5}
        response = requests.post(f"https://discord.com/api/v9/interactions/{data['id']}/{data['token']}/callback", json=response_data, headers={"Authorization": f"Bot {DISCORD_TOKEN}", "Content-Type": "application/json"})
        
        command = data["data"]["name"]
        
        if command == "yt-ogp":
            ipturl = data["data"]["options"][0]["value"]
            media_type = data["data"]["options"][1]["value"]

            try:
                # yt-dlpを使用してURLを取得
                ydl_opts = {'format': 'best', 'no_cache': True}
                if media_type == 'audio':
                    ydl_opts['format'] = 'bestaudio'
                    ydl_opts['extract_audio'] = True

                with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                    result = ydl.extract_info(ipturl, download=False)
                    if 'url' in result:
                        video_url = result['url']
                        thumbnail = result.get('thumbnail')
                        title = result.get('title')
                        description = result.get('description', '')[:100]  # descriptionを取得し、最大100文字までに制限する
                        uploader = result.get('uploader')
                        uploader_url = result.get('uploader_url')

                        # Embedを作成
                        embed = {
                            "type": "link",
                            "title": title,
                            "description": description,
                            "url": video_url,
                            "color": 15548997,
                            "image": {"url": thumbnail},
                            "author": {"name": uploader, "url": uploader_url}
                        }

                        # メッセージを送信
                        message_data = {"embeds": [embed]}
                        headers = {
                            "Authorization": f"Bot {DISCORD_TOKEN}",
                            "Content-Type": "application/json"
                        }
                        requests.patch(f"https://discord.com/api/v9/webhooks/{CLIENT_ID}/{data['token']}/messages/@original", json=message_data, headers=headers)
                        return '', 200
            except Exception as e:
                print('Error processing interaction:', e)
                return 'Error processing interaction', 500
    return '', 200

@app.route('/register-commands', methods=['GET'])
def register_commands():
    print('Received command registration request')

    commands = [
        {
            "name": "yt-ogp",
            "description": "Fetch information from YouTube URL",
            "options": [
                {
                    "name": "url",
                    "description": "YouTube URL",
                    "type": 3,
                    "required": True
                },
                {
                    "name": "type",
                    "description": "Type of content (video or audio)",
                    "type": 3,
                    "required": True,
                    "choices": [
                        {"name": "動画", "value": "video"},
                        {"name": "音楽", "value": "audio"}
                    ]
                }
            ]
        }
    ]

    try:
        response = requests.put(f"https://discord.com/api/v9/applications/{CLIENT_ID}/commands", json=commands, headers={"Authorization": f"Bot {DISCORD_TOKEN}", "Content-Type": "application/json"})
        if response.status_code == 200:
            print('Commands registered:', response.json())
            return 'Commands have been registered'
    except Exception as e:
        print('Error registering commands:', e)
        return 'Error registering commands', 500
    return '', 200

if __name__ == '__main__':
    app.run(debug=False)

これがあるflaskアプリですが、解説します。

※ここにflask-corsがありますが、これはなくても動きますがつけることをお勧めします ※ちゃんとenvも設定してくださいね

まず/inteactionsエンドポイントですが、

まず、リクエストされたjsonを取得して、

PINGリクエストならPONGと返してます。(これはないと動きません) なぜなら、discordのセキュリティーで、正しくレスポンスができるかどうかを確かめるためです。 さらにdiscord-interactionsには、@verify_key_decorator(PUBLIC_KEY)これを指定しないと、discordと通信できません。(認証関係ですね)

※verify_key_decoratorをimportから持ってきてくださいね

from discord_interactions import InteractionType, InteractionResponseType, verify_key_decorator

これで認証はできそうですね!!

次に、 そしてアプリケーションインタラクションだったらという処理がありますが、 まずDEFFERレスポンスを出しています。 これは遅延レスポンスをしますよーというもので

    if interaction_type == 1:
        response_ping = {"type": 1}
        requests.post(f"https://discord.com/api/v9/interactions/{data['id']}/{data['token']}/callback", json=response_ping, headers={"Authorization": f"Bot {DISCORD_TOKEN}", "Content-Type": "application/json"})

    # APPLICATION_COMMANDの場合は遅延レスポンスを返す
    elif interaction_type == 2:
        # 遅延レスポンスを返す
        response_data = {"type": 5}
        response = requests.post(f"https://discord.com/api/v9/interactions/{data['id']}/{data['token']}/callback", json=response_data, headers={"Authorization": f"Bot {DISCORD_TOKEN}", "Content-Type": "application/json"})

これはPINGならPONGのあとにありますが、 type2とはアプリケーションコマンドのレスポンスのことです。

このときにまずpostで、ちょっと待った!! て感じで送る感じです。

つまりあとで(厳密には15分以内に)PATCHで後ほどのapiにpatchすればメッセージが送ることができます

コードの例

requests.patch(f"https://discord.com/api/v9/webhooks/{CLIENT_ID}/{data['token']}/messages/@original", json=message_data, headers=headers)

これで後で送るとメッセージが確定されます

これで大体よさそうですね!!