futaba auto reloader

赤福Firefox版で自動更新しちゃう(実況モードもあるよ!)

当前为 2016-11-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name futaba auto reloader
  3. // @namespace https://github.com/himuro-majika
  4. // @description 赤福Firefox版で自動更新しちゃう(実況モードもあるよ!)
  5. // @author himuro_majika
  6. // @include http://*.2chan.net/*/res/*
  7. // @include https://*.2chan.net/*/res/*
  8. // @include http://board.futakuro.com/*/res/*
  9. // @require http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js
  10. // @version 1.7.1
  11. // @grant GM_addStyle
  12. // @license MIT
  13. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAPUExURYv4i2PQYy2aLUe0R////zorx9oAAAAFdFJOU/////8A+7YOUwAAAElJREFUeNqUj1EOwDAIQoHn/c88bX+2fq0kRsAoUXVAfwzCttWsDWzw0kNVWd2tZ5K9gqmMZB8libt4pSg6YlO3RnTzyxePAAMAzqMDgTX8hYYAAAAASUVORK5CYII=
  14. // ==/UserScript==
  15. this.$ = this.jQuery = jQuery.noConflict(true);
  16.  
  17. (function ($) {
  18. /*
  19. * 設定
  20. */
  21. var USE_SOUDANE = true; //そうだねをハイライト表示する
  22. var USE_CLEAR_BUTTON = true; //フォームにクリアボタンを表示する
  23. var USE_TITLE_NAME = true; //新着レス数・スレ消滅状態をタブに表示する
  24. var RELOAD_INTERVAL_NORMAL = 60000; //リロード間隔[ミリ秒](通常時)
  25. var RELOAD_INTERVAL_LIVE = 5000; //リロード間隔[ミリ秒](実況モード時)
  26. var LIVE_SCROLL_INTERVAL = 12; //実況モードスクロール間隔[ミリ秒]
  27. var LIVE_SCROLL_SPEED = 2; //実況モードスクロール幅[px]
  28. var LIVE_TOGGLE_KEY = "76"; //実況モードON・OFF切り替えキーコード(With Alt)
  29. var SHOW_NORMAL_BUTTON = true; //通常モードボタンを表示する
  30. var USE_NOTIFICATION_DEFAULT = false; // 新着レスの通知をデフォルトで有効にする
  31. var USE_SAVE_MHT = false; // スレ消滅時にMHTで保存する
  32.  
  33. var res = 0; //新着レス数
  34. var timerNormal, timerLiveReload, timerLiveScroll;
  35. var url = location.href;
  36. var script_name = "futaba_auto_reloader";
  37. var isWindowActive = true; // タブのアクティブ状態
  38. var isNotificationEnable = USE_NOTIFICATION_DEFAULT; // 通知の有効フラグ
  39.  
  40. if(!isFileNotFound()){
  41. setNormalReload();
  42. }
  43. soudane();
  44. makeFormClearButton();
  45. reset_title();
  46. make_live_button();
  47. addCss();
  48. setWindowFocusEvent();
  49.  
  50. //通常リロード開始
  51. function setNormalReload() {
  52. timerNormal = setInterval(rel, RELOAD_INTERVAL_NORMAL);
  53. console.log(script_name + ": Start auto reloading @" + url);
  54. }
  55. //通常リロード停止
  56. function clearNormalReload() {
  57. clearInterval(timerNormal);
  58. console.log(script_name + ": Stop auto reloading @" + url);
  59. }
  60. /*
  61. * 404チェック
  62. */
  63. function isFileNotFound() {
  64. if(document.title == "404 File Not Found") {
  65. return true;
  66. }
  67. else {
  68. return false;
  69. }
  70. }
  71.  
  72. /*
  73. * ボタン作成
  74. */
  75. function make_live_button() {
  76. var normal_flag = true; //通常モード有効フラグ
  77. //通常モードボタン
  78. var $normalButton = $("<a>", {
  79. id: "GM_FAR_relButton_normal",
  80. class: "GM_FAR_relButton",
  81. text: "[通常]",
  82. title: (RELOAD_INTERVAL_NORMAL / 1000) + "秒毎のリロード",
  83. css: {
  84. cursor: "pointer",
  85. "background-color": "#ea8",
  86. },
  87. click: function() {
  88. normalMode();
  89. }
  90. });
  91.  
  92. var live_flag = false; //実況モード有効フラグ
  93. //実況モードボタン
  94. var $liveButton = $("<a>", {
  95. id: "GM_FAR_relButton_live",
  96. class: "GM_FAR_relButton",
  97. text: "[実況(Alt+" + String.fromCharCode(LIVE_TOGGLE_KEY) + ")]",
  98. title: (RELOAD_INTERVAL_LIVE / 1000) + "秒毎のリロード + スクロール",
  99. css: {
  100. cursor: "pointer",
  101. },
  102. click: function() {
  103. liveMode();
  104. }
  105. });
  106. // 通知ボタン
  107. var $notificationButton = $("<a>", {
  108. id: "GM_FAR_notificationButton",
  109. text: "[通知]",
  110. title: "新着レスのポップアップ通知",
  111. css: {
  112. cursor: "pointer",
  113. },
  114. click: function() {
  115. toggleNotification();
  116. }
  117. });
  118. if (isNotificationEnable) {
  119. $notificationButton.css("background-color", "#a9d8ff");
  120. }
  121.  
  122. var $input = $("input[value$='信する']");
  123. $input.after($notificationButton);
  124. $input.after($liveButton);
  125. if(SHOW_NORMAL_BUTTON){
  126. $input.after($normalButton);
  127. }
  128.  
  129. //実況モードトグルショートカットキー
  130. window.addEventListener("keydown",function(e) {
  131. if ( e.altKey && e.keyCode == LIVE_TOGGLE_KEY ) {
  132. liveMode();
  133. }
  134. }, false);
  135.  
  136. /*
  137. * 通常モード切り替え
  138. */
  139. function normalMode() {
  140. if(normal_flag) {
  141. clearNormalReload();
  142. $normalButton.css("background" , "none");
  143. normal_flag = false;
  144. } else {
  145. setNormalReload();
  146. $normalButton.css("background-color" , "#ea8");
  147. normal_flag = true;
  148. }
  149.  
  150. }
  151.  
  152. /*
  153. * 実況モード
  154. * 呼出ごとにON/OFFトグル
  155. */
  156. function liveMode() {
  157. if (!live_flag) {
  158. //実況モード時リロード
  159. timerLiveReload = setInterval(rel_scroll, RELOAD_INTERVAL_LIVE);
  160. //自動スクロール
  161. timerLiveScroll = setInterval(live_scroll, LIVE_SCROLL_INTERVAL);
  162. $liveButton.css("backgroundColor", "#ffa5f0");
  163. startspin();
  164. console.log(script_name + ": Start live mode @" + url);
  165. live_flag = true;
  166. } else {
  167. clearInterval(timerLiveReload);
  168. clearInterval(timerLiveScroll);
  169. $liveButton.css("background", "none");
  170. stopspin();
  171. console.log(script_name + ": Stop live mode @" + url);
  172. live_flag = false;
  173. }
  174.  
  175. //新着スクロール
  176. function rel_scroll() {
  177. $('html, body').animate(
  178. {scrollTop:window.scrollMaxY},"fast"
  179. );
  180. if(isAkahukuNotFound()){
  181. //404時
  182. liveMode();
  183. }
  184. else {
  185. clickrelbutton();
  186. }
  187. }
  188.  
  189. function live_scroll() {
  190. window.scrollBy( 0, LIVE_SCROLL_SPEED );
  191. }
  192. function startspin() {
  193. $("#akahuku_throp_menu_opener").css(
  194. "animation", "spin 2s infinite steps(8)"
  195. );
  196. }
  197. function stopspin() {
  198. $("#akahuku_throp_menu_opener").css(
  199. "animation", "none"
  200. );
  201. }
  202. }
  203. /*
  204. * 通知切り替え
  205. */
  206. function toggleNotification() {
  207. if(isNotificationEnable) {
  208. $notificationButton.css("background" , "none");
  209. isNotificationEnable = false;
  210. } else {
  211. Notification.requestPermission(function(result) {
  212. if (result == "denied") {
  213. $notificationButton.attr("title",
  214. "通知はFirefoxの設定でブロックされています\n" +
  215. "ロケーションバー(URL)の左のアイコンをクリックして\n" +
  216. "「サイトからの通知の表示」を「許可」に設定してください");
  217. return;
  218. } else if (result == "default") {
  219. console.log("default");
  220. return;
  221. }
  222. $notificationButton.attr("title", "新着レスのポップアップ通知");
  223. $notificationButton.css("background-color" , "#a9d8ff");
  224. isNotificationEnable = true;
  225. });
  226. }
  227.  
  228. }
  229. }
  230.  
  231.  
  232. /*
  233. * 新着レスをリセット
  234. */
  235. function reset_title() {
  236. //ページ末尾でホイールダウンした時
  237. window.addEventListener("DOMMouseScroll",function scroll(event) {
  238. var window_y = window.scrollY;
  239. var window_ymax = window.scrollMaxY;
  240. if (event.detail > 0 && window_y >= window_ymax) {
  241. reset_titlename();
  242. }
  243. return;
  244. } ,false);
  245. //F5キー押された時
  246. window.addEventListener("keydown",function(e) {
  247. if ( e.keyCode == "116" ) {
  248. reset_titlename();
  249. }
  250. }, false);
  251.  
  252. function reset_titlename() {
  253. res = 0;
  254. var title_char = title_name();
  255. document.title = title_char;
  256. }
  257. }
  258.  
  259. function rel() {
  260. soudane();
  261. setTimeout(changetitle, 1000);
  262. if(isAkahukuNotFound()){
  263. //404時
  264. clearNormalReload();
  265. if (USE_SAVE_MHT) {
  266. saveMHT();
  267. }
  268. console.log(script_name + ": Page not found, Stop auto reloading @" + url);
  269. }
  270. else {
  271. clickrelbutton();
  272. }
  273. }
  274. /**
  275. * 赤福の続きを読むボタンをクリック
  276. */
  277. function clickrelbutton() {
  278. var relbutton = $("#akahuku_reload_button").get(0);
  279. if(relbutton){
  280. var e = document.createEvent("MouseEvents");
  281. e.initEvent("click", false, true);
  282. relbutton.dispatchEvent(e);
  283. }
  284. setTimeout(function(){
  285. if (!isWindowActive && isNotificationEnable) {
  286. getNewResContent();
  287. }
  288. }, 1000);
  289. }
  290. /**
  291. * MHTで保存
  292. */
  293. function saveMHT() {
  294. var saveMHTButton = $("#akahuku_throp_savemht_button").get(0);
  295. if (saveMHTButton) {
  296. var e = document.createEvent("MouseEvents");
  297. e.initEvent("click", false, true);
  298. saveMHTButton.dispatchEvent(e);
  299. }
  300. }
  301. /*
  302. * そうだねの数に応じてレスを着色
  303. */
  304. function soudane() {
  305. if ( USE_SOUDANE ) {
  306. $("td > .sod").each(function(){
  307. var sodnum = $(this).text().match(/\d+/);
  308. if (sodnum){
  309. var col = "rgb(180, 240," + (Math.round(10 * sodnum + 180)) + ")";
  310. $(this).parent().css("background-color", col);
  311. }
  312. });
  313. }
  314. }
  315.  
  316. /*
  317. * タブタイトルに新着レス数・スレ消滅状態を表示
  318. */
  319. function changetitle() {
  320. if ( USE_TITLE_NAME ) {
  321. var title_char = title_name();
  322. var newres = $("#akahuku_new_reply_header_number").text();
  323. if (isAkahukuNotFound()) {
  324. document.title = "#" + title_char;
  325. } else {
  326. if(newres) {
  327. res += parseInt(newres);
  328. }
  329. if ( res !== 0) {
  330. document.title = "(" + res + ")" + title_char;
  331. }
  332. }
  333. }
  334. }
  335. // 新着レスの内容を取得
  336. function getNewResContent() {
  337. var $newrestable = $("#akahuku_new_reply_header ~ table:not([id])");
  338. if ($newrestable.length) {
  339. var restexts = [];
  340. $newrestable.each(function() {
  341. var texts = [];
  342. $(this).find("blockquote").contents().each(function() {
  343. if ($(this).text() !== "") {
  344. texts.push($(this).text());
  345. }
  346. });
  347. restexts.push(texts.join("\r\n"));
  348. });
  349. var popupText = restexts.join("\r\n===============\r\n");
  350. showNotification(popupText);
  351. }
  352. }
  353. /*
  354. * 赤福のステータスからスレ消滅状態をチェック
  355. */
  356. function isAkahukuNotFound() {
  357. var statustext = $("#akahuku_reload_status").text();
  358. if (statustext.match(/(No Future)|((M|N)ot Found)/)) {
  359. return true;
  360. }
  361. else {
  362. return false;
  363. }
  364. }
  365.  
  366. function title_name() {
  367. var title = document.title;
  368. var title_num = title.match(/^(#|\(\d+\))/);
  369. var title_num_length;
  370. if(!title_num){
  371. title_num_length = 0;
  372. }
  373. else {
  374. title_num_length = title_num[0].length;
  375. }
  376. var act_title_name = title.substr(title_num_length);
  377. return act_title_name;
  378. }
  379.  
  380. function makeFormClearButton() {
  381. if ( USE_CLEAR_BUTTON ) {
  382. var $formClearButton = $("<div>", {
  383. id: "formClearButton",
  384. text: "[クリア]",
  385. css: {
  386. cursor: "pointer",
  387. margin: "0 5px"
  388. },
  389. click: function() {
  390. clearForm();
  391. }
  392. });
  393. var $comeTd = $(".ftdc b:contains('コメント')");
  394. $comeTd.after($formClearButton);
  395. }
  396.  
  397. function clearForm() {
  398. $("#ftxa").val("");
  399. }
  400. }
  401. function addCss() {
  402. GM_addStyle(
  403. "@keyframes spin {" +
  404. " 0% { transform: rotate(0deg); }" +
  405. " 100% { transform: rotate(359deg); }" +
  406. "}"
  407. );
  408. }
  409. // タブのアクティブ状態を取得
  410. function setWindowFocusEvent() {
  411. $(window).focus();
  412. $(window).bind("focus", function() {
  413. // タブアクティブ時
  414. isWindowActive = true;
  415. }).bind("blur", function() {
  416. // タブ非アクティブ時
  417. isWindowActive = false;
  418. });
  419. }
  420. // 新着レスをポップアップでデスクトップ通知する
  421. function showNotification(body) {
  422. Notification.requestPermission();
  423. var icon = $("#akahuku_thumbnail").attr("src");
  424. var instance = new Notification(
  425. document.title, {
  426. body: body,
  427. icon: icon,
  428. }
  429. );
  430. }
  431. })(jQuery);