futaba auto reloader k

ふたば☆ちゃんねるで自動更新しちゃう(実況モードもあるよ!)

ของเมื่อวันที่ 02-07-2019 ดู เวอร์ชันล่าสุด

// ==UserScript==
// @name           futaba auto reloader k
// @namespace      https://github.com/akoya-tomo
// @description    ふたば☆ちゃんねるで自動更新しちゃう(実況モードもあるよ!)
// @author         akoya_tomo
// @include        http://*.2chan.net/*/res/*
// @include        https://*.2chan.net/*/res/*
// @require        http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js
// @version        1.7.1rev8
// @grant          GM_addStyle
// @license        MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAPUExURYv4i2PQYy2aLUe0R////zorx9oAAAAFdFJOU/////8A+7YOUwAAAElJREFUeNqUj1EOwDAIQoHn/c88bX+2fq0kRsAoUXVAfwzCttWsDWzw0kNVWd2tZ5K9gqmMZB8libt4pSg6YlO3RnTzyxePAAMAzqMDgTX8hYYAAAAASUVORK5CYII=
// @noframes
// ==/UserScript==
/* globals jQuery */

this.$ = this.jQuery = jQuery.noConflict(true);

(function ($) {
	/*
	 *	設定
	 */
	var USE_SOUDANE = true;								//そうだねをハイライト表示する
	var USE_CLEAR_BUTTON = true;					//フォームにクリアボタンを表示する
	var USE_TITLE_NAME = true;						//新着レス数・スレ消滅状態をタブに表示する
	var RELOAD_INTERVAL_NORMAL = 60000;		//リロード間隔[ミリ秒](通常時)
	var RELOAD_INTERVAL_LIVE = 5000;			//リロード間隔[ミリ秒](実況モード時)
	var LIVE_SCROLL_INTERVAL = 12;				//実況モードスクロール間隔[ミリ秒]
	var LIVE_SCROLL_SPEED = 2;						//実況モードスクロール幅[px]
	var LIVE_TOGGLE_KEY = "76";						//実況モードON・OFF切り替えキーコード(With Alt)
	var SHOW_NORMAL_BUTTON = true;				//通常モードボタンを表示する
	var USE_NOTIFICATION_DEFAULT = false;	// 新着レスの通知をデフォルトで有効にする
	var USE_SAVE_MHT = false;							// スレ消滅時にMHTで保存する(赤福のみ)
	var USE_BOARD_NAME = true;				//板名をタブに表示する
	var NOTIFY_THREAD_NOT_FOUND = false;	//スレの消滅を通知する(通知ボタンとは独立して動作)
	var KEEP_THREAD_NOT_FOUND_MARK = false;		//タイトルのスレ消滅表示をリセット操作で消さない(true = 消さない : false = 消す)
	var USE_SYNC_BUTTON = false;			//[同期]ボタンを使用する(赤福のみ)

	var res = 0;	//新着レス数
	var timerNormal, timerLiveReload, timerLiveScroll;
	var url = location.href;
	var script_name = "futaba_auto_reloader_k";
	var isWindowActive = true;	// タブのアクティブ状態
	var isNotificationEnable = USE_NOTIFICATION_DEFAULT;	// 通知の有効フラグ
	var serverName = document.domain.match(/^[^.]+/);
	var boardName = $("#tit").text().match(/^[^@]+/);
	var resnum = $(".rtd").length;	// 総レス数
	var newres_index = resnum;	// リロード前の総レス数
	var normal_flag, live_flag;

	if(!isFileNotFound()){
		set_title();
		setNormalReload();
	}
	soudane();
	makeFormClearButton();
	reset_title();
	make_live_button();
	addCss();
	setWindowFocusEvent();
	check_akahuku_reload();
	check_futaba_reload();

	//通常リロード開始
	function setNormalReload() {
		timerNormal = setInterval(rel, RELOAD_INTERVAL_NORMAL);
		console.log(script_name + ": Start auto reloading @" + url);
	
		$(document).on("KOSHIAN_reload", () => {
			checkNewRes();
		});

		$(document).on("KOSHIAN_reload_notfound", () => {
			stopAutoReloading();
		});

	}
	//通常リロード停止
	function clearNormalReload() {
		clearInterval(timerNormal);
		console.log(script_name + ": Stop auto reloading @" + url);
	}
	/*
	 * 新着レス確認
	 */
	function checkNewRes() {
		newres_index = resnum;
		resnum = $(".rtd").length;
		soudane();
		changetitle();
		if (!isWindowActive && isNotificationEnable) {
			getNewResContent();
		}
	}
	/*
	 * 自動リロード停止
	 */
	function stopAutoReloading() {
		clearNormalReload();
		changetitle();
		if (!isWindowActive && NOTIFY_THREAD_NOT_FOUND) {
			var popupText = "スレは落ちています\r\n自動更新を停止しました";
			showNotification(popupText);
		}
		console.log(script_name + ": Page not found, Stop auto reloading @" + url);
	}
	/*
	 * 404チェック
	 */
	function isFileNotFound() {
		if(document.title == "404 File Not Found") {
			return true;
		}
		else {
			return false;
		}
	}

	/*
	 * ボタン作成
	 */
	function make_live_button() {
		normal_flag = true;	//通常モード有効フラグ
		//通常モードボタン
		var $normalButton = $("<a>", {
			id: "GM_FAR_relButton_normal",
			class: "GM_FAR_relButton",
			text: "[通常]",
			title: (RELOAD_INTERVAL_NORMAL / 1000) + "秒毎のリロード",
			css: {
				cursor: "pointer",
				"background-color": "#ea8",
				display: "inline-block"
			},
			click: function() {
				normalMode();
			}
		});

		live_flag = false;	//実況モード有効フラグ
		//実況モードボタン
		var $liveButton = $("<a>", {
			id: "GM_FAR_relButton_live",
			class: "GM_FAR_relButton",
			text: "[実況(Alt+" + String.fromCharCode(LIVE_TOGGLE_KEY) + ")]",
			title: (RELOAD_INTERVAL_LIVE / 1000) + "秒毎のリロード + スクロール",
			css: {
				cursor: "pointer",
				display: "inline-block"
			},
			click: function() {
				liveMode();
			}
		});
		// 通知ボタン
		var $notificationButton = $("<a>", {
			id: "GM_FAR_notificationButton",
			text: "[通知]",
			title: "新着レスのポップアップ通知",
			css: {
				cursor: "pointer",
				display: "inline-block"
			},
			click: function() {
				toggleNotification();
			}
		});
		if (isNotificationEnable) {
			$notificationButton.css("background-color", "#a9d8ff");
		}

		var $input = $("#KOSHIAN_form_sage_button").length ? $("#KOSHIAN_form_sage_button") : $("input[name$='email']");	// 「KOSHIAN 返信フォームを固定」に合わせて位置変更
		$input.after($notificationButton);
		$input.after($liveButton);
		if(SHOW_NORMAL_BUTTON){
			$input.after($normalButton);
		}

		//実況モードトグルショートカットキー
		window.addEventListener("keydown",function(e) {
			if ( e.altKey && e.keyCode == LIVE_TOGGLE_KEY ) {
				liveMode();
			}
		}, false);

		/*
		 * 通常モード切り替え
		 */
		function normalMode() {
			if(normal_flag) {
				clearNormalReload();
				$normalButton.css("background" , "none");
				normal_flag = false;
			} else {
				setNormalReload();
				$normalButton.css("background-color" , "#ea8");
				normal_flag = true;
			}

		}

		/*
		 * 実況モード
		 * 呼出ごとにON/OFFトグル
		 */
		function liveMode() {
			if (!live_flag) {
				//実況モード時リロード
				timerLiveReload = setInterval(rel_scroll, RELOAD_INTERVAL_LIVE);
				//自動スクロール
				timerLiveScroll = setInterval(live_scroll, LIVE_SCROLL_INTERVAL);
				$liveButton.css("backgroundColor", "#ffa5f0");
				startspin();
				console.log(script_name + ": Start live mode @" + url);
				live_flag = true;
			} else {
				clearInterval(timerLiveReload);
				clearInterval(timerLiveScroll);
				$liveButton.css("background", "none");
				stopspin();
				console.log(script_name + ": Stop live mode @" + url);
				live_flag = false;
			}

			//新着スクロール
			function rel_scroll() {
				$('html, body').animate(
					{scrollTop:window.scrollMaxY},"fast"
				);
				if(isAkahukuNotFound()){
					//404時
					liveMode();
				}
				else {
					clickrelbutton();
				}
			}

			function live_scroll() {
				window.scrollBy( 0, LIVE_SCROLL_SPEED );
			}
			function startspin() {
				$("#akahuku_throp_menu_opener").css(
					"animation", "spin 2s infinite steps(8)"
				);
			}
			function stopspin() {
				$("#akahuku_throp_menu_opener").css(
					"animation", "none"
				);
			}
		}
		/*
		 * 通知切り替え
		 */
		function toggleNotification() {
			if(isNotificationEnable) {
				$notificationButton.css("background" , "none");
				isNotificationEnable = false;
			} else {
				Notification.requestPermission(function(result) {
					if (result == "denied") {
						$notificationButton.attr("title",
							"通知はFirefoxの設定でブロックされています\n" +
							"ロケーションバー(URL)の左のアイコンをクリックして\n" +
							"「サイトからの通知の表示」を「許可」に設定してください");
						return;
					} else if (result == "default") {
						console.log("default");
						return;
					}
					$notificationButton.attr("title", "新着レスのポップアップ通知");
					$notificationButton.css("background-color" , "#a9d8ff");
					isNotificationEnable = true;
				});
			}

		}
	}
	/*
	 * ボタンを返信ボタンの横に移動(赤福用)
	 */
	function moveLiveButton() {
		var $input = $("input[value$='信する']");
		$("#GM_FAR_notificationButton").insertAfter($input);
		$("#GM_FAR_relButton_live").insertAfter($input);
		$("#GM_FAR_relButton_normal").insertAfter($input);
	}

	/*
	 * 新着レスをリセット
	 */
	function reset_title() {
		//ページ末尾でホイールダウンした時
		window.addEventListener("wheel", function(event){
			//Windowsで拡大率使用時にwindow_yが小数点以下でずれる対応
			var window_y = Math.ceil($(window).height() + $(window).scrollTop());
			var window_ymax = $(document).height();
			//console.log(script_name + ": window_y,yamx,deltaY: " + window_y +',' + window_ymax + ',' + event.deltaY);
			if (event.deltaY > 0 && window_y >= window_ymax && (!KEEP_THREAD_NOT_FOUND_MARK || !isAkahukuNotFound())) {
				reset_titlename();
			}
			return;
		}, false);
		//F5キー押された時
		window.addEventListener("keydown",function(e) {
			if ( e.keyCode == "116" && (!KEEP_THREAD_NOT_FOUND_MARK || !isAkahukuNotFound()) ) {
				reset_titlename();
			}
		}, false);

		function reset_titlename() {
			res = 0;
			var title_char = title_name();
			document.title = title_char;
		}
	}

	function rel() {
		//soudane();	//新着レス確認にて実行
		setTimeout(changetitle2, 1000);	//KOSHIAN リロード拡張対策
		if(isAkahukuNotFound()){
			//404時
			stopAutoReloading();
			if (USE_SAVE_MHT) {
				saveMHT();
			}
		}
		else {
			clickrelbutton();
		}
	}
	
	/**
	 * [リロード]ボタンをクリック
	 */
	function clickrelbutton() {
		var syncbutton = $("#akahuku_reload_syncbutton").get(0);
		var relbutton = $("#akahuku_reload_button").get(0);
		var e = document.createEvent("MouseEvents");
		e.initEvent("click", false, true);
		if(USE_SYNC_BUTTON && syncbutton && !live_flag) {
			// 赤福の[同期]ボタン(除く実況モード)
			syncbutton.dispatchEvent(e);
		} else if(relbutton){
			// 赤福の[続きを読む]ボタン
			relbutton.dispatchEvent(e);
		} else {
			// ふたば or KOSHIANの[リロード]ボタン
			relbutton = $("#contres > a").get(0);
			if(relbutton){
				relbutton.dispatchEvent(e);
			}
		}
		/* リロード完了を検出してから新着レスを確認するように変更
		setTimeout(function(){
			if (!isWindowActive && isNotificationEnable) {
				getNewResContent();
			}
		}, 1000);
		*/
	}
	/**
	 * 赤福の動的リロードの状態を取得
	 */
	function check_akahuku_reload() {
		var target = $("#akahuku_reload_status").get(0);
		if (target) {
			setAkahukuReloadObserver(target);
			moveLiveButton();
		} else {
			$(document).on("AkahukuContentApplied", () => {
				target = $("#akahuku_reload_status").get(0);
				if (target) {
					setAkahukuReloadObserver(target);
					moveLiveButton();
				} else {
					console.error (script_name + " - #akahuku_reload_status not found");
				}
			});
		}
		function setAkahukuReloadObserver(target) {
			var status = "";
			var config = { childList: true };
			var observer = new MutationObserver(function() {
				if (target.textContent == status) {
					return;
				}
				status = target.textContent;
				if (status.indexOf("新着") === 0) {
					checkNewRes();
				} else if (status.indexOf("No") === 0 || status.indexOf("Mot") === 0) {
					stopAutoReloading();
					if (USE_SAVE_MHT) {
						saveMHT();
					}
				}
			});
			observer.observe(target, config);
		}
	}
	/**
	 * ふたばの動的リロードの状態を取得
	 */
	function check_futaba_reload() {
		var contdisp = $("#contdisp").get(0);
		if (contdisp) {
			setFutabaReloadObserver(contdisp);
		}
		function setFutabaReloadObserver(target) {
			var status = "";
			var reloading = false;
			var config = { childList: true };
			var observer = new MutationObserver(function() {
				if (target.textContent == status) {
					return;
				}
				status = target.textContent;
				if (status == "・・・") {
					reloading = true;
				} else if (reloading && status.endsWith("頃消えます")) {
					checkNewRes();
					reloading = false;
				} else if (status == "スレッドがありません") {
					stopAutoReloading();
					reloading = false;
				} else {
					reloading = false;
				}
			});
			observer.observe(target, config);
		}
	}
	/**
	 * MHTで保存
	 */
	function saveMHT() {
		var saveMHTButton = $("#akahuku_throp_savemht_button").get(0);
		if (saveMHTButton) {
			var e = document.createEvent("MouseEvents");
			e.initEvent("click", false, true);
			saveMHTButton.dispatchEvent(e);
		}
	}
	/*
	 * そうだねの数に応じてレスを着色
	 */
	function soudane() {
		if ( USE_SOUDANE ) {
			$("td > .sod").each(function(){
				var sodnum = $(this).text().match(/\d+/);
				if (sodnum){
					var col = "rgb(180, 240," + (Math.round(10 * sodnum + 180)) + ")";
					$(this).parent().css("background-color", col);
				}
			});
		}
	}

	/*
	 * タブタイトルに新着レス数・スレ消滅状態を表示
	 */
	function changetitle() {
		if ( USE_TITLE_NAME ) {
			var title_char = title_name();
			var newres = Math.max(resnum - newres_index, 0);
			if (isAkahukuNotFound()) {
				document.title = "#" + title_char;
			} else {
				if(newres) {
					res += newres;
				}
				if ( res !== 0) {
					document.title = "(" + res + ")" + title_char;
				}
			}
		}
	}
	/*
	 * タブタイトルにスレ消滅状態を表示
	 */
	function changetitle2() {
		if (USE_TITLE_NAME) {
			var title_char = title_name();
			if (isAkahukuNotFound()) {
				document.title = "#" + title_char;
			}
		}
	}
	// 新着レスの内容を取得
	function getNewResContent() {
		if (resnum > newres_index) {
			var $newrestable = $(".rtd").slice(newres_index);
			if ($newrestable.length) {
				var restexts = [];
				$newrestable.each(function() {
					var texts = [];
					$(this).find("blockquote").contents().each(function() {
						if ($(this).text() !== "") {
							texts.push($(this).text());
						}
					});
					restexts.push(texts.join("\r\n"));
				});
				var popupText = restexts.join("\r\n===============\r\n");
				showNotification(popupText);
			}
		}
	}
	/*
	 * ステータスからスレ消滅状態をチェック
	 */
	function isAkahukuNotFound() {
		var statustext = null;
		// KOSHIAN
		var $status = $("#KOSHIAN_NOTIFY");
		if ($status.length) {
			statustext = $status.text();
			if (statustext.match(/CODE:404/)) {
				return true;
			} else {
				//return false	// KOSHIAN 返信フォーム拡張(改)が#KOSHIAN_NOTIFYを書き込むので、ここではreturnしない
			}
		}
		// 赤福
		$status = $("#akahuku_reload_status");
		if ($status.length) {
			statustext = $status.text();
			if (statustext.match(/(No Future)|((M|N)ot Found)/)) {
				return true;
			}
			else {
				return false;
			}
		} else {
			// ふたば
			$status = $("#contdisp");
			if ($status.length) {
				statustext = $status.text();
				if (statustext == "スレッドがありません") {
					return true;
				}
				else {
					return false;
				}
			}
		}
		return false;
	}

	function title_name() {
		var title = document.title;
		var title_num = title.match(/^(#|\(\d+\))/);
		var title_num_length;
		if(!title_num){
			title_num_length = 0;
		}
		else {
			title_num_length = title_num[0].length;
		}
		var act_title_name = title.substr(title_num_length);
		return act_title_name;
	}

	function makeFormClearButton() {
		if ( USE_CLEAR_BUTTON ) {
			var $formClearButton = $("<div>", {
				id: "formClearButton",
				text: "[クリア]",
				css: {
					cursor: "pointer",
					margin: "0 5px"
				},
				click: function() {
					clearForm();
				}
			});
			var $comeTd = $(".ftdc b:contains('コメント')");
			$comeTd.after($formClearButton);
		}

		function clearForm() {
			$("#ftxa").val("");
		}
	}
	function addCss() {
		GM_addStyle(
			"@keyframes spin {" +
			"  0% { transform: rotate(0deg); }" +
			"  100% { transform: rotate(359deg); }" +
			"}"
		);
	}
	//タブのアクティブ状態を取得
	function setWindowFocusEvent() {
		$(window).focus();
		$(window).bind("focus", function() {
			//タブアクティブ時
			isWindowActive = true;
		}).bind("blur", function() {
			//タブ非アクティブ時
			isWindowActive = false;
		});
	}
	//新着レスをポップアップでデスクトップ通知する
	function showNotification(body) {
		Notification.requestPermission();
		//ファビコンからアイコン取得
		var	icon = $("head > link[rel*='icon']").attr("href");
		if (!icon) {
			icon = "/favicon.ico";
		}
		var instance = new Notification(
			document.title, {
				body: body,
				icon: icon,
			}
		);
		instance.onclick = (e) => {
			window.focus();
			e.target.close();
		};
	}
	//タイトルに板名を追加する
	function set_title() {
		if ( USE_BOARD_NAME ) {
			if (boardName == "二次元裏") {
				boardName = serverName;
			}
			document.title = boardName + " " + document.title;
		}
	}

})(jQuery);