output/youtube_draft_edit の履歴(No.1)


概要#

capture1.png capture2.png

本家との違い#

  • (本当は限定公開にできるようにしたかったのですが、変更後に表示されるUIが異なるため頓挫しています。一度非公開にさえできればYoutubeStudio側で一括編集が可能です。)
  • 進捗やエラー処理をコンソールに表示します
  • start() で処理を開始します
  • stop() でいつでも処理を中断できます

利用手順#

  • まず、YoutubeStudioのコンテンツ画面で変更してもいい動画だけを表示します。(つまり、最大50件が一度に処理できる数です)
  • Google Chromeの開発ツールを表示し、コンソール(Console)画面を出します。
  • > のあとにスクリプトをペーストしてEnterを押します
  • さらに > のあとに start() と入力してEnterを押すと処理が始まります
  • stop() でいつでも処理を中断できます

スクリプト#

let stopExecution = false;

const stop = () => {
  stopExecution = true;
  console.log("⏹️ 処理を停止しました");
};

editDraft = async () => {
  return new Promise((resolve, reject) => {
    if (stopExecution) return reject("処理が停止されました");
    try {
      console.log("▶ 編集モードに入ります");
      document.querySelector("#row-container > div:nth-child(8) > ytcp-video-list-cell-actions > div > ytcp-button.edit-draft-button.style-scope.ytcp-video-list-cell-actions").click();
      setTimeout(() => {
        console.log("✅ 編集モードに入りました");
        resolve();
      }, 3000);
    } catch (error) {
      console.error("❌ editDraft エラー:", error);
      reject(error);
    }
  });
};

activateNotChild = async () => {
  return new Promise((resolve, reject) => {
    if (stopExecution) return reject("処理が停止されました");
    try {
      console.log("▶ 子供向けではない設定を選択");
      document.querySelector("#audience > ytkc-made-for-kids-select > div.made-for-kids-rating-container.style-scope.ytkc-made-for-kids-select > tp-yt-paper-radio-group > tp-yt-paper-radio-button:nth-child(2)").click();
      setTimeout(() => {
        console.log("✅ 子供向けではない設定にしました");
        resolve();
      }, 3000);
    } catch (error) {
      console.error("❌ activateNotChild エラー:", error);
      reject(error);
    }
  });
};

activateLastStep = async () => {
  return new Promise((resolve, reject) => {
    if (stopExecution) return reject("処理が停止されました");
    try {
      console.log("▶ 公開設定ステップに進みます");
      document.querySelector("#step-title-3").click();
      setTimeout(() => {
        console.log("✅ 公開設定ステップに移動しました");
        resolve();
      }, 3000);
    } catch (error) {
      console.error("❌ activateLastStep エラー:", error);
      reject(error);
    }
  });
};

activatePrivate = async () => {
  return new Promise((resolve, reject) => {
    if (stopExecution) return reject("処理が停止されました");
    try {
      console.log("▶ 動画を「非公開」に設定します");
      document.querySelector("#private-radio-button").click();
      setTimeout(() => {
        console.log("✅ 動画を「非公開」に設定しました");
        resolve();
      }, 3000);
    } catch (error) {
      console.error("❌ activatePrivate エラー:", error);
      reject(error);
    }
  });
};

doneEdit = async () => {
  return new Promise((resolve, reject) => {
    if (stopExecution) return reject("処理が停止されました");
    try {
      console.log("▶ 編集内容を保存します");
      document.getElementById("done-button").click();
      setTimeout(() => {
        console.log("✅ 編集内容を保存しました");
        resolve();
      }, 10000);
    } catch (error) {
      console.error("❌ doneEdit エラー:", error);
      reject(error);
    }
  });
};

publish = async () => {
  try {
    await editDraft();
    await activateNotChild();
    await activateLastStep();
    await activatePrivate();
    await doneEdit();
    console.log("✅ 1件の動画を処理しました\n");
  } catch (error) {
    console.error("❌ publish 処理中にエラーが発生しました:", error);
  }
};

start = async () => {
  stopExecution = false;
  console.log("🚀 全動画の処理を開始します");
  for (let i = 0; i < 50; i++) {
    if (stopExecution) {
      console.log("⏹️ 処理がユーザーによって停止されました");
      break;
    }
    try {
      const button = document.querySelector("#row-container > div:nth-child(8) > ytcp-video-list-cell-actions > div > ytcp-button.edit-draft-button.style-scope.ytcp-video-list-cell-actions");
      if (!button) {
        console.log("✅ 処理する動画がなくなりました");
        break;
      }
      console.log(`▶ ${i + 1}件目の動画を処理します`);
      await publish();
    } catch (error) {
      console.error(`❌ ${i + 1}件目の処理中にエラーが発生しました:`, error);
    }
  }
  console.log("🎉 全ての処理が完了しました");
};