LoginSignup
2
0

More than 1 year has passed since last update.

勉強しようと YouTube を見ると面白い動画を再生してしまう件を解決する

Posted at

はじめに

 私は、よく勉強しようと YouTube を見ても、おすすめに流れてくる誘惑をクリックしてしまいます。他のウェブサイトは Norton Family さん(カテゴリーによるウェブサイトの閲覧制限)と Microsoft Family Safety さん(パソコン全体の起動動時間制限)のお力を借りて見れなくしているのですが、YouTube はチャンネルごとに設定することができませんでした...。
 もちろん、自制心をもてという話ですが、少しでも「あ、みたらだめだ💦」と気づけるように警告を表示する Chrome 拡張機能を作りました。学生の皆様は、ぜひご利用ください!
💡 Chrome で勉強用のユーザーを作成してもいいですが、Windows のユーザーアカウントごと分けておくことをおすすめします。

作品:Chrome 拡張機能「YouTube Study Focus」

作る過程をご紹介する前に、まずは出来上がったものを紹介します!
YouTube-Study-Focus_qiita_screenshot.jpeg
こちらからお試しください↓↓

作る過程

2年ほど前に作ろうと思って失敗しただけで、はじめての拡張機能開発だったので、「Chrome 拡張機能 作り方」でググってみました。
すると、Chrome拡張の作り方 (超概要)というめっちゃ分かりやすい記事に出会えました!本当にありがとうございました(泣

自分の場合は、ポップアップというよりもページを変えたかったので、少し変えて作りました。(一度作ってから知りました)

適当なフォルダを作って「manifest.json」と諸ファイルを作成

拡張機能用にフォルダを作成して、その中に src フォルダと以下のようなファイルを作成します。

ディレクトリ構成図
yt-study-focus/
 └ src/
  ├ manifest.json
  ├ popup-dialog.html
  ├ whiteList.js
  ├ youtube.js
  ├ jquery-3.6.1.min.js
  └ images/
   └(後でアイコンを入れる)

manifest.json

「manifest.json」には、次のように記載します。

manifest.json
{
    "name": "YouTube Study Focus",
    "version": "0.0.1",
    "manifest_version": 3,
    "description": "勉強系以外の YouTube チャンネルの動画を再生する前に警告します",
    "action": {
        "default_popup": "popup-dialog.html"
    },
    "icons": {
        "16": "icon/icon16.png",
        "48": "icon/icon48.png",
        "128": "icon/icon128.png"
    },
    "content_scripts": [
        {
            "js": [
                "jquery-3.6.1.min.js",
                "whiteList.js",
                "youtube.js"
            ],
            "matches": [
                "https://youtube.com/*",
                "https://www.youtube.com/*"
            ]
        }
    ],
    "author": "nao_hanpen"
}

jquery-3.6.1.min.js と whiteList.js

これは jQuery と勉強系チャンネルの配列を書いただけのファイルで、本質ではないので今回は省略します

youtube.js

ここで、①動画を再生されたら、②チャンネル ID を取得して、③ホワイトリストになければ警告を表示するということをします。
深夜テンションで書いたので汚いです、、書き方勉強します。

youtube.js
$(function() {
    // 読み込んだら、loadCheck() を実行
    loadCheck();
});

// YouTube は Single Page App 的なこと(ページ遷移しないでコンテンツを変更)をするので、タイトルの部分が変更されないかを監視します。
// 変更があったときも、loadCheck() を呼ぶので、このときに2重に監視しないように、監視済みかを設定しておきます。もっとスマートな方法もあると思います。
var is_observed = false;


/**
 * チャンネル ID を取得して、ホワイトリストになければ警告を表示する
 */
function loadCheck() {
    // 現在の URL を取得 (動画ページかどうかの判定に使用)
    var now_url = location.href;
    console.log( now_url );

    // VideoID を取得
    var videoId = getVideoId(now_url);
    if ( videoId === "" ) {
        // VideoID が取得できなければ 動画ページでないと判断して処理を停止
        console.log("このページには動画が含まれません");
        return;
    }
    console.log("VideoID: " + videoId);

    // 処理中は再生を止める
    var is_prevent_play = true; // 停止中かどうか(video が playing されたら停止スクリプトを実行するかどうか)
    // ロード、再生、再生中に
    $('video').on('load play playing', function(e) {
        // 動画を停止
        if( is_prevent_play ) $('video')[0].pause();
    });

    // 処理中にロード画面を追加
    showLoad();

    // チャンネル ID を取得
    // 画面中の要素から取得しているので、YouTube は順にロードするようになっていて時間がかかる
    // 1回目では取得できない。だいたい2~3回必要。
    var channelURL = null; // チャンネル URL
    var channelId = null; // チャンネル ID
    var channelGetCount = 0; // 取得試行回数
    getChannelId();

    /**
     * チャンネル ID を取得
     */
    function getChannelId() {
        channelGetCount++; // 試行回数を加算
        if( $('#channel-name a.yt-simple-endpoint.style-scope.yt-formatted-string')[0] !== undefined ){
            // 要素が存在したら

            // チャンネル URL を取得
            channelURL = $('#channel-name a.yt-simple-endpoint.style-scope.yt-formatted-string')[0].href;
            console.log( "Channel URL:" + channelURL );
            // チャンネル ID を抽出
            channelId = channelURL.replace("https://www.youtube.com/channel/","");
            channelId = channelId.replace("https://www.youtube.com/c/","");
            console.log("ChannelId is #" + channelId);
            // チャンネル ID を検証
            checkChannelId();
            
            // 要素の監視がまだされていなければ
            if( is_observed === false ) {
                // 監視する
                const observer = new MutationObserver(function () {
                    var is_prevent_play = true;
                    loadCheck();
                });
                observer.observe($("ytd-watch-metadata #title")[0], { childList: true, subtree: true });
                // フラグをオンに
                is_observed = true;
            }
        }else{
            // 要素が存在しなかったら

            if( channelGetCount > 30 ) {
                // 30 回以上試行してもできない場合は強制終了
                console.log( "チャンネル ID を取得できませんでした。終了します" );
            }else{
                // 2秒後に再試行
                console.log( "チャンネル ID を取得できませんでした。2秒後に再試行します..." );
                setTimeout(getChannelId,2000);
            }
        }
    }

    /**
     * チャンネル ID を検証 (ホワイトリストになければ警告を表示)
     */
    function checkChannelId() {
        console.log("チャンネル ID を検証します: " + channelId);
        if( whiteChannel.indexOf(channelId) < 0 ) {
            // ホワイトリストになければ、警告を表示
            showBlock();
        }else{
            // ロード画面を閉じて動画を再生
            hideLoad(); // ロード画面を閉じる
            is_prevent_play = false; // 停止中フラグをオフに
            $('video')[0].play(); // 動画を再生
        }
    }

    /**
     * ロード画面を表示
     */
    function showLoad() {
        $("body").append("<div id='loadingView'></div>");
        $("#loadingView")[0].style.cssText = 'background: rgb(72 72 72 / 95%); content: ""; position: fixed; top: 0; left: 0; width: 100%; height: 100%; color: #fff; z-index: 2000; display: flex; justify-content: center; align-items: center; flex-direction: column;';
        $("div#loadingView").append("<h1>Loading...</h1>");
    }

    /**
     * ロード画面を非表示
     */
    function hideLoad() {
        if( $('#loadingView')[0] !== undefined ) {
            $("#loadingView").remove();
        }
    }

    /**
     * 警告を表示
     */
    function showBlock() {
        hideLoad();
        $("body").append("<div id='sendButton'></div>");
        $("#sendButton")[0].style.cssText = 'background: rgb(204 0 0 / 95%); content: ""; position: fixed; top: 0; left: 0; width: 100%; height: 100%; color: #fff; z-index: 2000; display: flex; justify-content: center; align-items: center; flex-direction: column;';
        $("div#sendButton").append("<h1>このコンテンツは時間を浪費する可能性があります</h1>");
        $("div#sendButton").append("<p>時間を取り戻すことはできません</p>");
        $("div#sendButton").append("<div id='agree-btn'>継続して時間を無駄にします</div>");
        $("#agree-btn")[0].style.cssText = 'display: inline-block; margin-top: 20px; padding: 10px; background-color: #fff; color: rgb(88 88 88); border-radius: 6px; font-size: 14px; line-height: 1; font-weight: 600; cursor: pointer;';
        $("#agree-btn").click(function() {
            console.log('無視して動画を再生');
            $("#sendButton").remove();
            is_prevent_play = false;
            $('video')[0].play();
        });
    }
}

/**
 * URL から Video ID を抽出
 * @param {str} url    URL 文字列
 * @return {str} ID または 存在しないときに空
 */
function getVideoId( url ) {
    const { search } = new URL(url);
    const params = new URLSearchParams(search);
    if (params.has('v')) {
        return params.get('v');
    }
    return '';
}

今後

この拡張機能は、今後、
・ユーザー専用ホワイトリスト
・ユーザー専用ブラックリスト
・保護者向け機能
・ホーム画面(おすすめ)から削除する機能
・コメント非表示機能
を実装する予定です。
「こんな機能あったらいいな」というものがありましたらお気軽にコメントください!

参考文献

2
0
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
2
0