ニコニコ動画 ユーザーニコ割以外ブロック

ユーザーニコ割が含まれない動画で、GINZA版の動画プレイヤーを原宿版に戻します。

Install this script?
Author's suggested script

You may also like HarajukuPlayer.

Install this script
// ==UserScript==
// @name        ニコニコ動画 ユーザーニコ割以外ブロック
// @description ユーザーニコ割が含まれない動画で、GINZA版の動画プレイヤーを原宿版に戻します。
// @namespace   https://userscripts.org/users/347021
// @version     6.0.2
// @match       *://www.nicovideo.jp/watch/*
// @exclude     *://www.nicovideo.jp/watch/*?*easy=1*
// @require     https://cdn.rawgit.com/greasemonkey/gm4-polyfill/d58c4f6fbe5702dbf849a04d12bca2f5d635862d/gm4-polyfill.js
// @require     https://greasyfork.org/scripts/17932/code/suppress-prototypejs.js?version=140950
// @require     https://greasyfork.org/scripts/17895/code/polyfill.js?version=189394
// @require     https://greasyfork.org/scripts/19616/code/utilities.js?version=142849
// @require     https://greasyfork.org/scripts/17896/code/start-script.js?version=112958
// @license     Mozilla Public License Version 2.0 (MPL 2.0); https://www.mozilla.org/MPL/2.0/
// @compatible  Firefox
// @compatible  Opera PPAPI版のAdobe Flash Playerではユーザーニコ割を表示できません
// @compatible  Chrome PPAPI版のAdobe Flash Playerではユーザーニコ割を表示できません
// @grant       GM.getValue
// @grant       GM_getValue
// @grant       GM.setValue
// @grant       GM_setValue
// @grant       GM.xmlHttpRequest
// @grant       GM_xmlhttpRequest
// @connect     flapi.nicovideo.jp
// @connect     msg.nicovideo.jp
// @connect     nmsg.nicovideo.jp
// @run-at      document-start
// @icon        data:image/vnd.microsoft.icon;base64,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
// @author      100の人
// @homepage    https://greasyfork.org/scripts/261
// ==/UserScript==

(function () {
'use strict';

/**
 * ユーザーニコ割が含まれない動画で、Qwatch(GINZA版の動画プレイヤー) を ニコプレーヤー4 に戻します。
 */
class NicoPlayer4Restore
{
	/**
	 * ニコプレーヤー4への置換が行われた場合にbody要素へ追加するクラス。
	 * @constant {string}
	 */
	static get NICO_PLAYER_4_CLASS() {return 'nico-player-4';}

	/**
	 * 自動再生チェックボックスのid属性値。半角英数とハイフンのみからなる文字列。
	 * @constant {string}
	 */
	static get AUTO_PLAY_CHECKBOX_ID() {return 'nicovideo-user-cm-only-347021-autoplay';}

	/**
	 * ニコスクリプト簡易設定モードで設定される「クリックでマイリストにジャンプ!」の動画ID。
	 * @constant {string}
	 */
	static get USER_CM_MYLIST() {return 'nm5575978';}

	constructor()
	{
		if ('niconicoVideoHarajukuAlreadyWorked' in document.documentElement.dataset) {
			console.error('『ニコニコ動画 ユーザーニコ割以外ブロック』が二重に動作しています。'
				+ '『ニコニコ動画 ユーザーニコ割以外ブロック』と『ニコニコ動画(原宿) リピート再生』を同時にインストールしていた場合、現在の状態になることがあります。'
				+ 'お手数をおかけしますが、片方のアンインストールをお願いいたします。');
			return;
		}
		document.documentElement.dataset.niconicoVideoHarajukuAlreadyWorked = '';

		/**
		 * 投稿者コメント編集モードなら真。
		 * @member {boolean}
		 */
		this.editMode = new URLSearchParams(window.location.search).has('edit');

		if (document.doctype.systemId) {
			// 非公開動画なら
			return;
		}

		this.waitLibJSExcuted().then(() => this.fixVideoPlayer());
		this.waitSWFInserted().then(flvplayer => {
			let flashVars = new URLSearchParams(flvplayer.querySelector('[name="flashvars"]').value);
			if (flashVars.has('isNicoPlayer4')) {
				this.fixForNicoPlayer4(flashVars);
			}
		});
		this.fixLinks();
	}

	/**
	 * lib.jsの実行を待機します。
	 * @access protected
	 * @returns {Promise}
	 */
	waitLibJSExcuted()
	{
		return new Promise(resolve => {
			document.addEventListener('load', function handleEvent(event) {
				if (event.target.src && event.target.src.includes('/lib.js')) {
					event.currentTarget.removeEventListener(event.type, handleEvent, true);
					resolve();
				}
			}, true);
		});
	}

	/**
	 * ニコプレーヤー4に置換した場合の修正を行います。
	 * @access protected
	 * @param {URLSearchParams} flashVars
	 */
	fixForNicoPlayer4(flashVars)
	{
		document.body.classList.add(NicoPlayer4Restore.NICO_PLAYER_4_CLASS);
		this.addStyleSheets();
		this.enableAutoPlay();
		this.showCounts(flashVars);
		startScript(
			async () => {
				if (window.chrome) {
					// Opera、Google Chrome
					// PPAPI版では設定が保存されないため、スクリプト側でリピート設定を保存する
					let playerBottomTextlink = document.getElementsByClassName('hiddenInfoTabHeader')[0];
					playerBottomTextlink.insertAdjacentHTML('beforeend', `<label>
						<input type="checkbox" name="repeat"
							${await GM.getValue('repeat', false) ? ' checked=""' : ''} />
						リピート再生
					</label>`);

					document.getElementsByClassName('hiddenInfoTabHeader')[0].lastChild.firstElementChild
						.addEventListener('change', event => GM.setValue('repeat', event.target.checked));
				}

				GreasemonkeyUtils.executeOnUnsafeContext(this.fixRepeat, [], true);
			},
			parent => parent.classList.contains('hiddenInfo'),
			target => target.classList.contains('hiddenInfoTabHeader'),
			() => document.getElementsByClassName('hiddenInfoTabHeader')[0]
		);
		if (flashVars.has('existedMylistOnly')) {
			// マイリストへのリンクのみが存在すれば
			this.showMylistLink(flashVars);
		}
	}

	/**
	 * 一般会員でも自動再生を有効にします。
	 * @access protected
	 */
	async enableAutoPlay()
	{
		if (!this.editMode) {
			GreasemonkeyUtils.executeOnUnsafeContext(function (autoPlay) {
				require(['prepareapp/PlayerStartupObserver'], function (PlayerStartupObserver) {
					PlayerStartupObserver.ready(function () {
						let player = document.getElementById('external_nicoplayer');
						if (autoPlay || player.getPlayerConfig().autoPlay) {
							window.setTimeout(function () {
								player.ext_play(true);
							}, 1);
						}
					});
				});
			}, [await GM.getValue('autoPlay')]);

			this.showAutoPlaySetting();
		}
	}

	/**
	 * 自動再生の設定項目を作成します。
	 * @access protected
	 */
	showAutoPlaySetting()
	{
		this.isPremium().then(premium => {
			if (premium) {
				GM.setValue('autoPlay', false);
			} else {
				// プレーヤー側で自動再生の設定ができなければ (一般会員なら)
				this.waitSWFInserted().then(async function (flvplayer) {
					flvplayer.insertAdjacentHTML('afterend', `
						<input type="checkbox" id="${NicoPlayer4Restore.AUTO_PLAY_CHECKBOX_ID}"
							${await GM.getValue('autoPlay') ? ' checked=""' : ''} />
						<label for="${NicoPlayer4Restore.AUTO_PLAY_CHECKBOX_ID}">自動再生</label>
					`);
					flvplayer.nextElementSibling.addEventListener('change', function (event) {
						GM.setValue('autoPlay', event.target.checked);
					});
				});
			}
		});
	}

	/**
	 * プレミアム会員であれば真を返します。
	 * @access protected
	 * @returns {Promise.<boolean>}
	 */
	isPremium()
	{
		let script = document.querySelector('[src^="http://res.nimg.jp/js/watch.js"] ~ script ~ script');
		return (script ? Promise.resolve(script) : new Promise(resolve => {
			document.head.addEventListener('beforescriptexecute', function handleEvent(event) {
				if (event.target.text.includes('isPremium')) {
					event.currentTarget.removeEventListener(event.type, handleEvent);
					resolve(event.target);
				}
			});
		})).then(function (script) {
			return script.text.includes('isPremium: true');
		});
	}

	/**
	 * 当クラスの動作に必要なCSSをまとめてページに挿入します。
	 * @access protected
	 */
	addStyleSheets()
	{
		document.head.insertAdjacentHTML('beforeend', `<style>
			/*====================================
				ニコプレーヤー4
			*/
			#playerContainer.controll_panel.text_marquee,
			#playerAlignmentArea.size_medium #playerNicoplayer,
			#playerAlignmentArea.size_medium #playerContainer.controll_panel #nicoplayerContainer,
			#playerAlignmentArea.size_medium #playerContainer.controll_panel #external_nicoplayer {
				/* ニコプレーヤー4 */
				width: 976px;
				height: 504px;
			}

			body.ja-jp #textMarquee,    /* Qwatchのマーキーエリア */
			#playerTabWrapper {         /* Qwatchのコメント欄 */
				display: none;
			}

			/*====================================
				再生数・コメント数・マイリスト数
			*/
			#nicoplayerContainerInner span {
				display: block;
				width: 70px;
				padding-right: 3px;
				height: 13px;
				position: absolute;
				right: 296px;
				font-size: 12px;
				font-family: "MS Pゴシック",sans-serif;
				color: white;
				background: linear-gradient(#2F2F2F,#2A2A2A,#232323,#1d1d1d,#171717,#131313,#111111,#141414,#1b1b1b,#242424,#2d2d2d,#343434);
				font-weight: bold;
				text-align: right;
				text-decoration: none;
			}
			/*------------------------------------
				再生数
			*/
			#view-counts {
				top: 6px;
			}
			/*------------------------------------
				コメント数
			*/
			#comment-counts {
				top: 21px;
			}
			/*------------------------------------
				マイリスト数
			*/
			#mylist-counts {
				top: 36px;
			}
			/*------------------------------------
				16:9
			*/
			#nicoplayerContainerInner.wide span {
				right: 168px;
			}



			/* TODO: 以下未修正 */



			/*------------------------------------
				フルスクリーン時
			*/
			#nicoplayerContainerInner [style*="100%"] ~ span {
				display: none;
			}

			/*====================================
				マイリストへのリンク
			*/
			#flvplayer_container a {
				display: block;
				width: 544px;
				height: 56px;
				position: absolute;
				top: 10px;
				left: 10px;
				background: linear-gradient(dimgray, whitesmoke) white;
				font-weight: bold;
				font-size: 28px;
				text-align: center;
				line-height: 56px;
				color: black;
				text-decoration: none;
			}
			/*------------------------------------
				フルスクリーン時
			*/
			#flvplayer_container [style*="100%"] ~ a {
				display: none;
			}

			/*====================================
				Qwatch
			*/
			#flvplayer-comments,
			#flvplayer-comments ~ #flvplayer {
				height: 518px;
			}
			#flvplayer-comments ~ #flvplayer {
				width: 672px;
				border-top: solid 1px black;
				position: absolute;
				top: 0;
				left: 10px;
			}

			/*====================================
				動画の自動再生
			*/
			/*------------------------------------
				チェックボックス
			*/
			#flvplayer_container input#${NicoPlayer4Restore.AUTO_PLAY_CHECKBOX_ID} {
				display: none;
			}
			#flvplayer_container input ~ label::before {
				content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAMAAACecocUAAAAB3RJTUUH3goTCgUTGKIHYgAAABd0RVh0U29mdHdhcmUAR0xEUE5HIHZlciAzLjRxhaThAAAACHRwTkdHTEQzAAAAAEqAKR8AAAAEZ0FNQQAAsY8L/GEFAAAADFBMVEXd3d2ZmZl3d3dVVVWo8LMzAAAAFUlEQVR42mNgRgAGJgYYYKI5mxEBABpMAFHkjrK3AAAAAElFTkSuQmCC");
				display: block;
				margin-bottom: 0.2em;
			}
			#flvplayer_container input:checked ~ label::before {
				content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAMAAACecocUAAAAB3RJTUUH3goTCgUTGKIHYgAAABd0RVh0U29mdHdhcmUAR0xEUE5HIHZlciAzLjRxhaThAAAACHRwTkdHTEQzAAAAAEqAKR8AAAAEZ0FNQQAAsY8L/GEFAAAAFVBMVEX////d3d27u7uZmZl3d3dVVVUzMzO4z3+iAAAAMUlEQVR42kXIyREAMAjDQOew+y85g4Gg145wJ3B1BDec9rfSMssKohxM+7YRtE9HaHpbsAGH466nbwAAAABJRU5ErkJggg==");
			}
			/*------------------------------------
				ラベル
			*/
			#flvplayer_container label[for="${NicoPlayer4Restore.AUTO_PLAY_CHECKBOX_ID}"] {
				padding-left: 19px;
				position: absolute;
				bottom: 1em;
				right: -1em;
				font-size: 12px;
				line-height: 1em;
				width: 1em;
			}
		</style>`);
	}

	/**
	 * 動画プレーヤーのパラメーターを修正します。
	 * @access protected
	 */
	fixVideoPlayer()
	{
		GreasemonkeyUtils.executeOnUnsafeContext(/* global jQuery */ function () {
			jQuery.prototype.flash = new Proxy(jQuery.prototype.flash, {
				apply(flash, thisArg, argumentsList)
				{
					if (thisArg.attr('id') === 'nicoplayerContainerInner') {
						const flashVars = argumentsList[0].flashvars;
						flashVars.isNicoPlayer4 = '1';
						if (flashVars.bgms) {
							// ユーザーニコ割、またはBGMが設定されていれば
							const bgms = flashVars.bgms.split(',');
							for (let i = 0, l = bgms.length; i < l; i = i + 2) {
								if (bgms[i] === 'nm5575978') {
									// マイリストへのリンクなら
									flashVars.existedMylistOnly = '1';
								} else if (bgms[i + 1] === 'swf') {
									// ユーザーニコ割である可能性があれば
									delete flashVars.isNicoPlayer4;
									delete flashVars.existedMylistOnly;
									break;
								}
							}
						}
					}
					return Reflect.apply(flash, thisArg, argumentsList);
				},
			});
		});
	}

	/**
	 * マーキーエリアにマイリストへのリンクを表示します。
	 * @access protected
	 * @param {URLSearchParams} flashVars
	 */
	showMylistLink(flashVars)
	{
		this.getMylistId(flashVars).then(mylistId => {
			if (mylistId) {
				this.waitSWFInserted().then(function (flvplayer) {
					flvplayer.insertAdjacentHTML('afterend', `
						<a href="${mylistId.startsWith('mylist') ? '/' : '/watch/'}${mylistId}" target="_blank">
							クリックでマイリストにジャンプ!
						</a>
					`);
					flvplayer.nextElementSibling.addEventListener('click', function () {
						GreasemonkeyUtils.executeOnUnsafeContext(function () {
							if (flvplayer.ext_getStatus() === 'playing') {
								flvplayer.ext_play(false);
							}
						});
					});
				});
			}
		});
	}

	/**
	 * マイリスト等のIDを取得します。
	 * @access protected
	 * @param {URLSearchParams} flashVars
	 * @returns {Promise.<?string>}
	 */
	getMylistId(flashVars)
	{
		return this.getAuthorComments(flashVars).then(function (authorComments) {
			for (let chat of Array.from(authorComments.getElementsByTagName('chat'))) {
				// 全角形のASCII文字を半角に変換する
				let comment = chat.textContent.normalize('NFKC');
				// IDを取得する
				let matches = new RegExp(
					`@CM\\s+${NicoPlayer4Restore.USER_CM_MYLIST}\\s+.*?(mylist/[0-9]+|[a-z]{2}[0-9]+|[0-9]{5,})`,
					'i'
				).exec(comment);
				if (matches) {
					// IDが存在すれば
					return matches[1];
				}
			}
			return null;
		});
	}

	/**
	 * 投稿者コメントを取得します。
	 * @access protected
	 * @param {URLSearchParams} flashVars
	 * @returns {Promise.<?Document>}
	 */
	getAuthorComments(flashVars)
	{
		const flvInfo = this.getFLVInfo(flashVars);
		const messageServer = flvInfo.get('ms');
		if (messageServer) {
			return new Promise(function (resolve) {
				GM.xmlHttpRequest({
					method: 'POST',
					url: messageServer,
					data: `<packet><thread thread="${flvInfo.get('thread_id')}" version="20061206" res_from="-1000" fork="1" /></packet>`,
					onload: function (response) {
						resolve(new DOMParser().parseFromString(response.responseText, 'application/xml'));
					},
				});
			});
		} else {
			return Promise.resolve(null);
		}
	}

	/**
	 * 動画プレーヤーのパラメーターから、動画の情報を取得します。
	 * @access protected
	 * @param {URLSearchParams} flashVars
	 * @returns {URLSearchParams}
	 */
	getFLVInfo(flashVars)
	{
		return new URLSearchParams(decodeURIComponent(flashVars.get('flvInfo')));
	}

	/**
	 * embed要素が埋め込まれるまで待機します。
	 * @access protected
	 * @returns {Promise.<HTMLObjectElement>}
	 */
	waitSWFInserted()
	{
		let flvplayer = document.getElementById('external_nicoplayer');
		if (flvplayer) {
			// すでに埋め込まれていれば
			return Promise.resolve(flvplayer);
		} else {
			return new Promise(function (resolve) {
				new MutationObserver(function (mutations, observer) {
					for (let mutation of mutations) {
						for (let addedNode of mutation.addedNodes) {
							if (addedNode.id === 'external_nicoplayer') {
								observer.disconnect();
								resolve(addedNode);
								return;
							}
						}
					}
				}).observe(document, { childList: true, subtree: true });
			});
		}
	}

	/**
	 * 再生数、コメント数、マイリスト数が動画プレイヤー内に表示されるようにします。
	 * @access protected
	 * @param {URLSearchParams} flashVars
	 */
	showCounts(flashVars)
	{
		if (/^https?:\/\/nmsg\.nicovideo\.jp\/api\/$/u.test(this.getFLVInfo(flashVars).get('ms'))) {
			GreasemonkeyUtils.executeOnUnsafeContext(/* global require */function () {
				require(['prepareapp/PlayerStartupObserver'], function (PlayerStartupObserver) {
					PlayerStartupObserver.ready(function () {
						if (document.getElementById('external_nicoplayer').ext_isWide()) {
							document.getElementById('nicoplayerContainerInner').classList.add('wide');
						}
						document.getElementById('external_nicoplayer').insertAdjacentHTML('afterend', `
							<span id="view-counts">${document.getElementsByClassName('viewCount')[0].textContent}</span>
							<span id="comment-counts">${document.getElementsByClassName('commentCount')[0].textContent}</span>
							<span id="mylist-counts">${document.getElementsByClassName('mylistCount')[0].textContent}</span>
						`);
					});
				});
			}, [], true);
		}
	}

	/**
	 * リピート再生が行われない問題に対処するため、ページに埋め込む関数です。
	 */
	fixRepeat()
	{
		/**
		 * 再生終了時にリピートを試行する回数。
		 * @constant {number}
		 */
		const MAX_RETRIES = 50;

		/**
		 * Opera、Google Chrome でリピート再生の設定をするチェックボックス。
		 * @type {HTMLInputElement}
		 */
		let repeatCheckbox = document.getElementsByName('repeat')[0];

		/**
		 * ニコプレーヤー4。
		 * @type {HTMLEmbedElement}
		 */
		let flvplayer;
		require(['prepareapp/PlayerStartupObserver'], function (PlayerStartupObserver) {
			PlayerStartupObserver.ready(function () {
				flvplayer = document.getElementById('external_nicoplayer');
				if (repeatCheckbox && repeatCheckbox.checked) {
					flvplayer.ext_setRepeat(true);
				}
			});
		});

		if (repeatCheckbox) {
			// Opera、Google Chrome
			repeatCheckbox.addEventListener('change', function (event) {
				require(['prepareapp/PlayerStartupObserver'], function (PlayerStartupObserver) {
					PlayerStartupObserver.ready(function () {
						flvplayer.ext_setRepeat(event.target.checked);
					});
				});
			});
		}

		/**
		 * 他のスクリプトで設定された onNicoPlayerStatus 関数。
		 * @type {?Function}
		 */
		let _onNicoPlayerStatus = window.onNicoPlayerStatus;
		window.onNicoPlayerStatus = _onNicoPlayerStatus ? function (id, status) {
			_onNicoPlayerStatus(id, status);
			onNicoPlayerStatus(id, status);
		} : onNicoPlayerStatus;

		/**
		 * すでにリピート処理を実行中であれば真。
		 * @type {boolean}
		 */
		let already = false;

		/**
		 * 一つ前のステータス。
		 * @type {string}
		 */
		let previousStatus = 'paused';

		/**
		 * プレーヤーのステータスの変化を監視します。
		 * @param {string} id - 要素のID。
		 * @param {string} status - paused、playing、seeking、end、stopped。
		 */
		function onNicoPlayerStatus(id, status) {
			let _previousStatus = previousStatus;
			previousStatus = status;

			switch (status) {
				case 'seeking':
					if (!already) {
						if (_previousStatus === 'paused') {
							// ポーズ状態でシークした後、「始めから」ボタンを押したときにシーク失敗とみなされるのを防ぐ
							previousStatus = 'paused';
						} else {
							// 直前のステータスが paused でなければ、シークに失敗しているとみなす
							repeat();
						}
					}
					break;

				case 'stopped':
					if (flvplayer.ext_getPlayheadTime() !== 0) {
						// 一時停止・再生終了以外で再生が停止したら、シークに失敗しているとみなす
						repeat();
					}
					break;
			}
		}

		/**
		 * リピート処理を開始します。
		 */
		function repeat() {
			if (already) {
				return;
			}
			already = true;
			tryPlaying(0);
		}

		/**
		 * ステータスが playing になるまで、再生開始を繰り返します。
		 * @param {number} retryCount
		 */
		function tryPlaying(retryCount) {
			flvplayer.ext_play(true);
			if (retryCount++ < MAX_RETRIES) {
				if (flvplayer.ext_getStatus() !== 'playing') {
					window.setTimeout(tryPlaying, 10, retryCount);
				} else {
					already = false;
					// コメントが表示されるようにする
					let ngScoringFilteringLevel = flvplayer.getPlayerConfig().ngScoringFilteringLevel;
					flvplayer.updatePlayerConfig({ ngScoringFilteringLevel: ngScoringFilteringLevel === 'HIGH' ? 'MIDDLE' : 'HIGH'});
					flvplayer.updatePlayerConfig({ ngScoringFilteringLevel: ngScoringFilteringLevel });
				}
			}
		}
	}

	/**
	 * ニコプレーヤー4の場合に正常に動作しなくなるリンクテキストについて、抑制の解除などを行います。
	 * @access protected
	 */
	fixLinks()
	{
		document.addEventListener('DOMContentLoaded', function handleEvent(event) {
			event.currentTarget.removeEventListener(event.type, handleEvent);

			document.addEventListener('click', function (event) {
				const a = event.target.closest('a');
				if (a && (a.classList.contains('videoHeaderTagLink')
					|| /^https?:\/\/www\.nicovideo\.jp\/watch\/[^/]+$/u.test(a.href))) {
					event.stopPropagation();
				}
			}, true);
		});
	}
}

startScript(
	() => new NicoPlayer4Restore(),
	() => true,
	() => true,
	() => document.documentElement
);

})();