您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ふたクロの「Live」と「新着レスに自動スクロール」を自動クリックし、スレが落ちるか1000に行ったら次スレを探して移動する
当前为
- // ==UserScript==
- // @name futakuro-auto-thread
- // @namespace https://2chan.net/
- // @version 1.0.4
- // @description ふたクロの「Live」と「新着レスに自動スクロール」を自動クリックし、スレが落ちるか1000に行ったら次スレを探して移動する
- // @author ame-chan
- // @match https://*.2chan.net/b/res/*
- // @icon https://www.2chan.net/favicon.ico
- // @grant none
- // @license MIT
- // ==/UserScript==
- (() => {
- 'use strict';
- // ユーザー設定
- const REGEXP_TARGET_TEXT = /twitch\.tv\/rtainjapan/;
- const inlineStyle = `<style id="userscript-style">
- .userscript-dialog {
- position: fixed;
- right: 16px;
- bottom: 16px;
- padding: 8px 24px;
- max-width: 200px;
- line-height: 1.5;
- color: #fff;
- font-size: 1rem;
- background-color: #3e8ed0;
- border-radius: 6px;
- opacity: 1;
- transition: all 0.3s ease;
- transform: translateY(0px);
- z-index: 9999;
- }
- .userscript-dialog.is-hidden {
- opacity: 0;
- transform: translateY(100px);
- }
- .userscript-dialog.is-info {
- background-color: #3e8ed0;
- color: #fff;
- }
- .userscript-dialog.is-danger {
- background-color: #f14668;
- color: #fff;
- }
- </style>`;
- document.head.insertAdjacentHTML('beforeend', inlineStyle);
- const delay = (time = 500) => new Promise((resolve) => setTimeout(() => resolve(true), time));
- const setDialog = async (dialogText, status) => {
- const html = `<div class="userscript-dialog is-hidden is-${status}">${dialogText}</div>`;
- const dialogElm = document.querySelector('.userscript-dialog');
- if (dialogElm) {
- dialogElm.remove();
- }
- document.body.insertAdjacentHTML('afterbegin', html);
- await delay(100);
- document.querySelector('.userscript-dialog')?.classList.remove('is-hidden');
- };
- const getFutabaJson = async (path) => {
- const options = {
- method: 'GET',
- cache: 'no-cache',
- credentials: 'include',
- };
- const result = await fetch(path, options)
- .then((res) => {
- if (!res.ok) {
- throw new Error(res.statusText);
- }
- return res.arrayBuffer();
- })
- .catch((err) => {
- throw new Error(err);
- });
- try {
- const textDecoder = new TextDecoder('utf-8');
- const futabaJson = JSON.parse(textDecoder.decode(result));
- return futabaJson;
- } catch (e) {
- const textDecoder1 = new TextDecoder('Shift_JIS');
- const html = textDecoder1.decode(result);
- const parser = new DOMParser();
- const dom = parser.parseFromString(html, 'text/html');
- const bodyText = dom?.body?.textContent;
- if (bodyText) {
- console.log('json-error:', bodyText);
- setDialog(bodyText, 'danger');
- if (bodyText.includes('満員')) {
- await delay(20000);
- return {
- res: {},
- maxres: '',
- old: 0,
- };
- }
- }
- throw new Error(e);
- }
- };
- const autoMoveThreads = async (matchText, threadNo) => {
- const catalog = await getFutabaJson('/b/futaba.php?mode=json&sort=6');
- const threadKeys = Object.keys(catalog?.res || {});
- const targetKeyArr = [];
- for (const threadKey of threadKeys) {
- // 見ていたスレッドは飛ばす
- if (threadNo === threadKey) continue;
- try {
- const threadText = catalog.res[threadKey].com;
- if (threadText && threadText.includes(matchText)) {
- targetKeyArr.push(Number(threadKey));
- }
- } catch (e) {
- throw new Error(e);
- }
- }
- if (targetKeyArr.length) {
- try {
- const recentThreadKey = targetKeyArr.reduce((a, b) => Math.max(a, b));
- // 見ていたスレッドより古いスレッドしかないならfalse
- if (Number(threadNo) > recentThreadKey) {
- return Promise.resolve(false);
- }
- const threadStatus = await getFutabaJson(`/b/futaba.php?mode=json&res=${String(recentThreadKey)}`);
- const resCount = Object.keys(threadStatus?.res || {}).length;
- const isMin950 = resCount > 0 && resCount < 950;
- const isNotMaxRes = threadStatus.maxres === '';
- const isNotOld = threadStatus.old === 0;
- // レス数が950未満、maxresが空、oldが0なら新規スレッドとみなす
- if (isMin950 && isNotMaxRes && isNotOld) {
- return Promise.resolve(`/b/res/${recentThreadKey}.htm`);
- }
- } catch (e1) {
- return Promise.resolve(false);
- }
- }
- return Promise.resolve(false);
- };
- const observeThreadEnd = (matchText, threadNo) => {
- const sec = 1000;
- let count = 0;
- let fetchTimer = 0;
- let isRequestOK = false;
- let scrollEventHandler = () => {};
- let nextThreadCheckInterval = 10000;
- const checkThreadEnd = async () => {
- const resElms = document.querySelectorAll('.thre > div[style]');
- const lastAddElm = resElms[resElms.length - 1];
- const lastElm = lastAddElm.querySelector('table:last-child');
- const resNo = lastElm?.querySelector('[data-sno]')?.getAttribute('data-sno');
- if (!resNo) return false;
- const path = `/b/futaba.php?mode=json&res=${threadNo}&start=${resNo}&end=${resNo}`;
- const threadStatus = await getFutabaJson(path);
- const resCount = Object.keys(threadStatus?.res || {}).length;
- if (threadStatus.maxres !== '' || (threadStatus.old === 1 && resCount >= 950)) {
- return Promise.resolve(true);
- }
- return Promise.resolve(false);
- };
- const getTime = () => {
- const zeroPadding = (num) => String(num).padStart(2, '0');
- const time = new Date();
- const hour = zeroPadding(time.getHours());
- const minutes = zeroPadding(time.getMinutes());
- const seconds = zeroPadding(time.getSeconds());
- return `${hour}:${minutes}:${seconds}`;
- };
- const updateCheckInterval = (interval) => {
- if (count > interval / sec) {
- interval = interval * 2;
- }
- return interval;
- };
- const tryMoveThreads = async () => {
- if (isRequestOK) return;
- isRequestOK = true;
- window.removeEventListener('scroll', scrollEventHandler);
- if (count >= 30) {
- setDialog('次スレッドは見つかりませんでした', 'danger');
- return;
- }
- count += 1;
- nextThreadCheckInterval = updateCheckInterval(nextThreadCheckInterval);
- const dialogText = `[${getTime()}] 次のスレッドを探しています...<br>${count}巡目(${
- nextThreadCheckInterval / sec
- }秒間隔)`;
- setDialog(dialogText, 'info');
- const result = await autoMoveThreads(matchText, threadNo);
- if (typeof result === 'string') {
- return (location.href = result);
- }
- await delay(nextThreadCheckInterval);
- isRequestOK = false;
- void tryMoveThreads();
- };
- scrollEventHandler = () => {
- if (fetchTimer) clearTimeout(fetchTimer);
- fetchTimer = setTimeout(async () => {
- const isThreadEnd = await checkThreadEnd();
- if (isThreadEnd) {
- void tryMoveThreads();
- }
- }, 6000);
- };
- const threadDown = document.querySelector('#thread_down');
- const observeCallback = (_, observer) => {
- // スレが落ちたらfutakuroによって出現するID
- const threadDown = document.querySelector('#thread_down');
- if (threadDown !== null) {
- if (fetchTimer) clearTimeout(fetchTimer);
- observer.disconnect();
- void tryMoveThreads();
- }
- };
- const borderAreaElm = document.querySelector('#border_area');
- if (threadDown !== null) {
- if (fetchTimer) clearTimeout(fetchTimer);
- void tryMoveThreads();
- } else if (borderAreaElm !== null) {
- const observer = new MutationObserver(observeCallback);
- observer.observe(borderAreaElm, {
- childList: true,
- });
- }
- window.addEventListener('scroll', scrollEventHandler, {
- passive: false,
- });
- };
- const checkAutoLiveScroll = async () => {
- /** スレ本文の要素 */ const threadTopText = document.querySelector('#master')?.innerText;
- if (typeof threadTopText === 'undefined') return;
- /** 新着レスに自動スクロールチェックボックス(futakuro) */ let liveScrollCheckbox =
- document.querySelector('#autolive_scroll');
- while (liveScrollCheckbox === null) {
- await delay(1000);
- liveScrollCheckbox = document.querySelector('#autolive_scroll');
- }
- const hasBody = typeof threadTopText === 'string';
- const matchTargetText = threadTopText.match(REGEXP_TARGET_TEXT);
- const threadNo = document.querySelector('[data-res]')?.getAttribute('data-res');
- if (liveScrollCheckbox !== null && !liveScrollCheckbox.checked && hasBody && matchTargetText) {
- const matchText = matchTargetText[0];
- liveScrollCheckbox.click();
- if (matchText && threadNo) {
- observeThreadEnd(matchText, threadNo);
- }
- }
- };
- const callback = async (_, observer) => {
- const liveWindowElm = document.querySelector('#livewindow');
- if (liveWindowElm !== null) {
- await delay(1000);
- void checkAutoLiveScroll();
- observer.disconnect();
- }
- };
- const observer = new MutationObserver(callback);
- const liveScrollCheckbox = document.querySelector('#autolive_scroll');
- if (liveScrollCheckbox === null) {
- observer.observe(document.body, {
- childList: true,
- });
- } else {
- void checkAutoLiveScroll();
- }
- })();