您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
2ちゃんねるの各板のトップページに整形したスレッド一覧を表示
- // ==UserScript==
- // @name 2ch Thread List
- // @namespace https://gf.qytechs.cn/users/1009-kengo321
- // @version 10
- // @description 2ちゃんねるの各板のトップページに整形したスレッド一覧を表示
- // @grant none
- // @match *://*.2ch.net/*
- // @match *://*.5ch.net/*
- // @license MIT
- // @noframes
- // @run-at document-start
- // ==/UserScript==
- ;(function() {
- 'use strict'
- var byId = function(id) {
- return document.getElementById(id)
- }
- var getBoardId = function() {
- var p = window.location.pathname
- return p.slice(1, p.indexOf('/', 1))
- }
- var parseSubjectText = (function() {
- var lineRegExp = /^(\d+)\.dat<>(.*)\s*\((\d+)\)$/gm
- var matchedResults = function(str) {
- var result = [], matched = null
- while ((matched = lineRegExp.exec(str))) result.push(matched)
- return result
- }
- var removeCopyright = function(str) {
- return str.replace('[転載禁止]', '')
- .replace('[無断転載禁止]', '')
- .replace('©2ch.net', '')
- .replace('©2ch.net', '')
- .replace('©bbspink.com', '')
- .replace('©bbspink.com', '')
- }
- var newThreadInfo = function(matchedResult, i) {
- return {
- line: i + 1,
- id: parseInt(matchedResult[1], 10),
- title: removeCopyright(matchedResult[2].trim()),
- resNum: parseInt(matchedResult[3], 10),
- }
- }
- return function(subjectText) {
- return matchedResults(subjectText).map(newThreadInfo)
- }
- })()
- const millis = {
- toSeconds(millis) {
- return Math.trunc(millis / 1000)
- },
- }
- const addForceProperty = (threadInfos, nowAsSeconds) => {
- const secondsInMinute = 60
- const secondsInDay = 86400
- const lowerLimit = elapsed => Math.max(elapsed, secondsInMinute * 3)
- return threadInfos.map(i => {
- const elapsed = nowAsSeconds - i.id
- const force = elapsed < 0
- ? 0
- : Math.trunc(i.resNum / (lowerLimit(elapsed) / secondsInDay))
- return Object.assign({}, i, {force})
- })
- }
- const threadInfos = () => {
- return threadInfos.data.slice()
- }
- threadInfos.data = []
- threadInfos.set = infos => {
- threadInfos.data = infos.slice()
- }
- var sortThreadInfos = (function() {
- var cmp = function(prop) {
- return function(a, b) {
- if (a[prop] < b[prop]) return -1
- if (a[prop] > b[prop]) return 1
- return 0
- }
- }
- var negate = function(func) {
- return function() { return -func.apply(null, arguments) }
- }
- var cmpSeq = function(comparators) {
- return function(a, b) {
- for (var i = 0; i < comparators.length; i++) {
- var r = comparators[i](a, b)
- if (r !== 0) return r
- }
- return 0
- }
- }
- var reversableCmpObj = function(prop) {
- return {
- asc: cmpSeq([cmp(prop), line.asc]),
- desc: cmpSeq([negate(cmp(prop)), line.asc]),
- }
- }
- var line = {asc: cmp('line'), desc: negate(cmp('line'))}
- var title = reversableCmpObj('title')
- var resNum = reversableCmpObj('resNum')
- var id = reversableCmpObj('id')
- var force = reversableCmpObj('force')
- var current = line.asc
- var setOrReverseCmp = function(comp) {
- return function() {
- current = (current === comp.asc ? comp.desc : comp.asc)
- }
- }
- var result = function(threadInfos) {
- return threadInfos.slice().sort(current)
- }
- result.byLineInAsc = function() { current = line.asc }
- result.byLine = setOrReverseCmp(line)
- result.byTitle = setOrReverseCmp(title)
- result.byResNum = setOrReverseCmp(resNum)
- result.byId = setOrReverseCmp(id)
- result.byForce = setOrReverseCmp(force)
- return result
- })()
- var newThreadList = (function() {
- var addCells = function(row) {
- ;[].slice.call(arguments, 1).forEach(function(content) {
- var cell = row.insertCell()
- if (['string', 'number'].indexOf(typeof(content)) >= 0) {
- cell.textContent = content
- } else {
- cell.appendChild(content)
- }
- })
- }
- var sorter = function(setSortType) {
- return function() {
- setSortType()
- updateThreadList(threadInfos())
- }
- }
- var setTHead = function(tHead) {
- var r = tHead.insertRow()
- var addTh = function(e) {
- var th = r.ownerDocument.createElement('th')
- th.textContent = e[0]
- th.addEventListener('click', e[1])
- r.appendChild(th)
- }
- ;[['番号', sorter(sortThreadInfos.byLine)],
- ['タイトル', sorter(sortThreadInfos.byTitle)],
- ['レス', sorter(sortThreadInfos.byResNum)],
- ['勢い', sorter(sortThreadInfos.byForce)],
- ['作成日時', sorter(sortThreadInfos.byId)],
- ].forEach(addTh)
- }
- var threadUrl = function(threadId) {
- return '/test/read.cgi/'
- + getBoardId()
- + '/'
- + threadId
- + '/'
- }
- var decodeEntityRefs = (function() {
- var e = document.createElement('span')
- return function(html) {
- e.innerHTML = html
- return e.textContent
- }
- })()
- var newLink = function(threadInfo) {
- var result = document.createElement('a')
- result.target = '_blank'
- result.href = threadUrl(threadInfo.id)
- result.textContent = decodeEntityRefs(threadInfo.title)
- return result
- }
- var padZero = function(dateUnit) {
- return dateUnit <= 9 ? '0' + dateUnit : '' + dateUnit
- }
- var toZeroPaddingString = function(date) {
- var monthDay = [date.getMonth() + 1, date.getDate()]
- var times = [date.getHours(), date.getMinutes(), date.getSeconds()]
- return date.getFullYear()
- + '/'
- + monthDay.map(padZero).join('/')
- + ' '
- + times.map(padZero).join(':')
- }
- var setTBody = function(tBody, threadInfos) {
- ;(threadInfos || []).forEach(function(info) {
- addCells(tBody.insertRow()
- , info.line
- , newLink(info)
- , info.resNum
- , info.force
- , toZeroPaddingString(new Date(info.id * 1000)))
- })
- }
- return function(threadInfos) {
- var result = document.createElement('table')
- result.id = 'thread-list'
- setTHead(result.createTHead())
- setTBody(result.createTBody(), threadInfos)
- return result
- }
- })()
- var addThreadListBoxIfAbsent = function() {
- if (!byId('thread-list-box')) {
- var b = document.body
- b.insertBefore(newThreadListBox(), b.firstChild)
- }
- }
- var replaceThreadListBy = function(threadList) {
- var old = threadList.ownerDocument.getElementById('thread-list')
- old.parentNode.replaceChild(threadList, old)
- }
- var newTopBar = function() {
- var message = document.createElement('span')
- message.id = 'thread-list-error-message'
- var button = document.createElement('input')
- button.id = 'thread-list-reload-button'
- button.type = 'button'
- button.value = '更新'
- button.addEventListener('click', function() {
- button.disabled = true
- message.textContent = ''
- requestSubjectText(getBoardId())
- })
- var result = document.createElement('div')
- result.id = 'thread-list-top-bar'
- result.appendChild(button)
- result.appendChild(message)
- return result
- }
- var newThreadListBox = function() {
- var result = document.createElement('div')
- result.id = 'thread-list-box'
- result.appendChild(newTopBar())
- result.appendChild(newThreadList())
- return result
- }
- const updateThreadList = threadInfos => {
- const sorted = sortThreadInfos(threadInfos)
- const list = newThreadList(sorted)
- replaceThreadListBy(list)
- }
- var subjectTextLoaded = function(event) {
- var xhr = event.target
- if (xhr.status === 200) {
- const parsed = parseSubjectText(xhr.responseText)
- const added = addForceProperty(parsed, millis.toSeconds(Date.now()))
- threadInfos.set(added)
- updateThreadList(added)
- } else {
- byId('thread-list-error-message').textContent = xhr.statusText
- }
- }
- var requestSubjectText = (function() {
- var handler = function(errorMessage, fn) {
- return function f(event) {
- if (document.body) {
- addStyleIfAbsent()
- addThreadListBoxIfAbsent()
- byId('thread-list-reload-button').disabled = false
- byId('thread-list-error-message').textContent = errorMessage
- if (fn) fn(event)
- } else {
- document.addEventListener('DOMContentLoaded', f.bind(this, event))
- }
- }
- }
- function getSubjectTxtURL(boardId) {
- const l = window.location
- return `${l.protocol}//${l.host}/${boardId}/subject.txt`
- }
- return function(boardId) {
- var xhr = new XMLHttpRequest()
- xhr.timeout = 30000
- xhr.open('GET', getSubjectTxtURL(boardId))
- xhr.overrideMimeType('text/plain; charset=shift_jis')
- xhr.addEventListener('load', handler('', subjectTextLoaded))
- xhr.addEventListener('timeout', handler('タイムアウト'))
- xhr.addEventListener('error', handler('エラー'))
- xhr.send()
- }
- })()
- var addStyleIfAbsent = function() {
- if (byId('thread-list-style')) return
- var style = document.createElement('style')
- style.id = 'thread-list-style'
- style.innerHTML = [
- '#thread-list {',
- ' margin: 0 auto;',
- ' border-collapse: collapse;',
- '}',
- '#thread-list th {',
- ' color: white;',
- ' background-color: steelblue;',
- ' cursor: default;',
- '}',
- '#thread-list th:hover {',
- ' background-color: cornflowerblue;',
- '}',
- '#thread-list th:active {',
- ' background-color: mediumblue;',
- '}',
- '#thread-list td {',
- ' color: black;',
- '}',
- '#thread-list th, #thread-list td {',
- ' border: solid thin lightsteelblue;',
- ' padding: 0 0.5em;',
- ' line-height: 1.5em;',
- '}',
- '#thread-list tbody tr:nth-child(odd) {',
- ' background-color: azure;',
- '}',
- '#thread-list tbody tr:nth-child(even) {',
- ' background-color: aliceblue;',
- '}',
- '#thread-list tbody tr:nth-child(5n) {',
- ' border-bottom: solid medium lightsteelblue;',
- '}',
- '#thread-list td:nth-child(4n+1),',
- '#thread-list td:nth-child(4n+3),',
- '#thread-list td:nth-child(4n+4) {',
- ' text-align: right;',
- '}',
- '#thread-list a:link {',
- ' color: black;',
- ' text-decoration: none;',
- '}',
- '#thread-list a:visited {',
- ' color: purple;',
- '}',
- '#thread-list a:hover {',
- ' color: maroon;',
- ' text-decoration: underline;',
- '}',
- '#thread-list-box {',
- ' background-color: silver;',
- '}',
- '#thread-list-top-bar {',
- ' text-align: center;',
- '}',
- '#thread-list-error-message {',
- ' color: red;',
- '}',
- ].join('\n')
- document.head.appendChild(style)
- }
- var isBoardTopPage = function() {
- return /^\/[^/]+\/(?:index\.html)?$/.test(window.location.pathname)
- }
- var main = function() {
- if (!isBoardTopPage()) return
- requestSubjectText(getBoardId())
- }
- main()
- })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址