増えすぎたXのミュート、いちいち解除できないので自動化した件

## はじめに

X（旧Twitter）で、インプレゾンビ対策として**通報・ブロック・ミュートを行うChromeアドオン**を使っていたところ、**ミュートリストがかなり肥大化**してしまっていました。

以前は「ちょっと見たくないな」と感じたアカウントをミュートしていたのですが、**最近になってそろそろ解除しても問題なさそうだと思い**、ミュートを解除しようとしました。

しかし、いざやろうとすると、**リストから解除対象を探して解除するのは非常に手間でした**。

「どうせインプレゾンビはすでにブロック済みだし、ミュートは全部解除しちゃってもいいか」と判断し、**ChatGPT君を使ってJavaScriptで順番に自動解除するスクリプトを作ってみました**。

自分用のメモ的な内容ではありますが、**同じようにミュート解除の手間で悩んでいる方の参考になれば嬉しいです**。

> ⚠️ **注意**  
> XのDOM構造や仕様は頻繁に変更されるため、**今後このスクリプトが動作しなくなる可能性があります**。  
> また、**短時間に連続して大量のミュート解除を行うと、X側の制限（レート制限や一時的な操作ブロック）に引っかかる可能性**があります。  
> 実行は自己責任で、適切なペースで行ってください。

---

## 実行環境

- ブラウザ：**Google Chrome**
- ※JavaScriptで動作しているため、**FirefoxやEdgeなどでも動作する可能性あり**（未検証）

---

## 実行手順

1. **X（旧Twitter）にログイン**  
　👉 https://x.com/

2. **ミュートリストのページへ移動**  
　「設定とプライバシー」→「プライバシーと安全」→「ミュートとブロック」→「ミュートしたアカウント」  
　※URL直リンク：https://x.com/settings/muted/all

3. **F12キーを押して開発者ツールを開き、「Console」タブを選択(下図赤枠)**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145241/e7041e68-14da-4cc8-a269-d4ae66d9b26d.png)

4. **以下のスクリプトを貼り付けて、Enterキーで実行**
## スクリプト（シンプル版）

以下は、すぐに使える**シンプルな一括ミュート解除スクリプト**です。  
とりあえず動かしたい方はこちらをどうぞ。

> 🔧 より丁寧かつ安定した処理を行いたい方は、**巻末の「高機能版スクリプト（補足）」も併せてご覧ください。**


```javascript
// 1秒ごとに「ミュートを解除」ボタンを探してクリックする処理
let interval = setInterval(() => {
  const buttons = Array.from(document.querySelectorAll('button[aria-label*="ミュートを解除"]'));
  if (buttons.length > 0) {
    buttons[0].click();
    console.log(`解除: ${buttons[0].getAttribute("aria-label")}`);
  } else {
    // ボタンが見つからなければスクロールして次のアカウントを読み込む
    window.scrollBy(0, 300);
  }
}, 1000);
```
---

## スクリプトの停止方法

このスクリプトには停止処理がありません。**途中で中断したい場合は、F5キーなどでページをリロード**してください。

---

## おわりに

ミュート機能はタイムラインを快適に保つのに便利ですが、気付かないうちにリストが膨れ上がって**管理しづらくなることもあります**。

手動での解除が難しい状況では、こうした**簡易スクリプトでの自動処理が役立つ場合があります**。

繰り返しになりますが、**短時間に連続して操作を行うと、X側で一時的な制限や操作ブロックが発生する可能性**もあるため、**念のため適度なペースでの実行をおすすめします**。
**X側の仕様変更には常に注意が必要**です。動作しなくなった場合は、DOMの構造やラベル名を見直してみてください。


---

## 高機能版：安定・安全に一括解除したい方向け

以下は、より丁寧かつ安全にミュートを解除するためのスクリプトです。
使用方法はシンプル版と同じです。

- **すでに解除済みのアカウントはスキップ（重複処理なし）**
- **スクロールしても新しい対象が出てこない場合、自動で終了**
- **進捗ログが出力されるので確認しやすい**
- **各処理に適切な待機時間を入れているので、安定性が高い**

```javascript
// ===============================
// 自動でミュート解除するスクリプト
// ===============================

// ミュート解除済みユーザーのラベルを記録するセット（重複防止）
let unmuted = new Set();

// ミュート解除した人数のカウンター
let totalClicked = 0;

// 新しいユーザーが見つからずスクロールした回数（終了判定用）
let scrollAttempts = 0;

// 最大何回スクロールするか（新しい解除対象が出なかったら終了）
const MAX_SCROLL_ATTEMPTS = 30;

// -------------------------------
// 現在表示中の「ミュート解除」ボタンをすべて取得する関数
// -------------------------------
function findUnmutedButtons() {
  return Array.from(
    document.querySelectorAll('button[aria-label*="さんのミュートを解除"]')
  )
    // すでに処理済み（unmutedにある）ボタンは除外
    .filter(btn => !unmuted.has(btn.getAttribute('aria-label')));
}

// -------------------------------
// 1件ずつミュート解除 → スクロール → 再呼び出し を繰り返す処理
// -------------------------------
function clickAndScrollLoop() {
  const buttons = findUnmutedButtons(); // 新しいボタンを取得

  // ボタンが見つからない場合の処理（→スクロール試行 or 終了）
  if (buttons.length === 0) {
    if (scrollAttempts >= MAX_SCROLL_ATTEMPTS) {
      console.log("✅ 全ての処理が完了しました");
      return; // 終了
    }

    // ページをスクロールして新しいユーザーを読み込む
    window.scrollBy(0, 1500);
    scrollAttempts++; // スクロール回数をカウント
    console.log(`🔄 スクロール試行 ${scrollAttempts}/${MAX_SCROLL_ATTEMPTS}`);

    // 少し待ってから再試行（読み込みに時間がかかるため）
    setTimeout(clickAndScrollLoop, 1500);
    return;
  }

  // ミュート解除対象ボタンが見つかった場合の処理
  const btn = buttons[0]; // 最初のボタンを選ぶ（1件ずつ処理）
  const label = btn.getAttribute('aria-label'); // ユーザー名のラベルを取得

  unmuted.add(label); // 処理済みに記録（Setに追加）

  // ボタンを画面内にスクロールして見えるようにしてから…
  btn.scrollIntoView({ behavior: 'smooth', block: 'center' });

  // 実際にクリック（ミュート解除）
  btn.click();
  totalClicked++;
  console.log(`🟢 ${totalClicked}人目: ${label} をミュート解除`);

  scrollAttempts = 0; // 成功したのでスクロールカウントをリセット

  // 次の処理に進むために、少し時間を空けて再帰的に呼び出し
  setTimeout(clickAndScrollLoop, 1200);
}

// -------------------------------
// 実行開始！
// -------------------------------
console.log("🚀 ミュート解除スクリプト開始します…");
clickAndScrollLoop();
```