// ==UserScript==
// @name 5ch Plus
// @name:ja 5ch プラス
// @description 5ch を拡張します
// @author null-chan <[email protected]>
// @version 0.1.0
// @run-at document-body
// @include http*://*5ch.net/*
// @include http*://imgur.com/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/twemoji/12.0.4/twemoji.min.js
// @grant none
// @copyright Copyright (c) 2019 null-chan
// @license MIT
// @namespace http://greasyfork.icu/users/379206
// ==/UserScript==
(() => {
'use strict'
// ################ 設定 ################ //
//
// true = 有効 | false = 無効
//
const config = {
background_url: 'https://picsum.photos/1920/1080', // base64 使用可
background_blur: true, // 背景画像のぼかし
}
// ################ 設定 ################ //
$(window).on('load', () => console.info('window.on(\'load\')'))
const parse_json = object => {
let x
let ret = ''
for (x in object) {
let y
let dec = ''
for (y in object[x]) dec += `${y}:${object[x][y]}!important;`
ret += `${x}{${dec}}`
}
return ret
}
// https://stackoverflow.com/questions/1484506/random-color-generator
const generateColor = () => {
const letters = '0123456789abcdef'
let color = ''
for (let i = 0; i < 6; i++)
color += letters[Math.floor(Math.random() * 16)]
return color
}
const preset = {
'imgur.com/[a-zA-Z0-9]{7}': {
script: () => (location.href = $('meta[name="twitter:image"]').attr('content')),
},
global: {
style: {
html: {
'font-family': 'メイリオ,Arial',
cursor: 'progress',
},
hidden: {
display: 'none',
opacity: 0,
},
'img.emoji': {
height: '1em',
width: '1em',
'margin-bottom': '0.1em',
'user-select': 'all',
},
'img.emoji::selection': {
background: 'white',
},
'.gray_om': {
color: '#808080',
},
},
script: () => {
twemoji.parse(document.body)
$(window).on('load', () => {
$('html').attr('style', 'cursor:default!important')
$('a[href^="https://"],a[href^="http://"],a[href^="/"]').each(function () {
let href = $(this).attr('href')
console.info(`Detected link: %c${$(this).html()}%c - %c${href}`, 'text-decoration:underline', '', 'text-decoration:underline')
if (/^(https?:)?\/\/jump[.]5ch[.]net\/[?]/.test($(this).attr('href'))) href = $(this).text()
$(this).addClass('rinku')
$(this).attr({
href: href,
rel: 'noreferrer noopener',
target: '_blank',
title: 'ページは新しいタブで開かれます (rel="noreferrer noopener")',
})
})
$(document).on('mouseenter', '.thread .rinku:not([data-tooltip])', function () {
console.info(`Hovered: ${$(this).attr('href')}`)
if ($(this).hasClass('loadeding')) return
$(this).addClass('loadeding')
const uralu = (new RegExp('^(https?:)?//.*5ch[.]net/').test($(this).attr('href')) ? '' : 'https://cors-anywhere.herokuapp.com/') + $(this).attr('href')
$(this).attr('title', 'Loading...')
console.log(`Loading: ${uralu}`)
$.get(uralu, (res, _, xhr) => {
const type = xhr.getResponseHeader('content-type')
console.info(`Loaded: ${uralu} - ${type}`)
let title
if (type.includes('text/html')) title = $(res).filter('title').text() || 'No title found'
else if (type.includes('image/')) {
$(this).attr('title', 'DuckDuckGo Image Proxy')
return $(this).after(`<br><span><img class='preview_img' title='クリックで非表示' src='https://proxy.duckduckgo.com/iu/?u=${$(this).attr('href')}'/><span class='preview_img hidden' style='color:#808080'>画像プレビューが非表示になっています。クリックで表示...</span></span>`)
} else title = `Preview is not available - ${type}`
$(this).attr('title', title)
$(this).after(`<br><span class='gray_om'>${title}</span>`)
})
})
$(document).on('click', '.preview_img', function () {
$(this).toggleClass('hidden')
$(this).siblings('.preview_img').toggleClass('hidden')
})
})
},
},
'http://.*5ch[.]net.*': {
protocol: true,
script: () => location.href = location.href.replace(new RegExp('^http://'), 'https://'),
},
'.*[^jump][.]5ch[.]net/[a-z]{1,}/(#[0-9]{1,2})?': {
style: {
body: {
background: `url(${config.background_url}) center no-repeat fixed`,
'background-size': 'cover',
},
'div[style="margin: 0; padding: 0 0 0 0.5em; border-top: 0.5em solid #BEB; border-bottom: 0.5em solid #BEB; border-radius: 0.50em / 0.50em; padding: 0 0 1.0em 0.5em; height: 25em; overflow-y: scroll; background: #BEB;"]': {
height: 'auto',
'max-height': '25em',
},
'.ADVERTISE_AREA,div[style="margin-top:10px;margin-bottom:10px;width:100%;text-align:center;"]': {
display: 'none',
},
'p[style="margin: 0.75em 30% 0 30%; padding: 0.5em; border-radius: 0.50em / 0.50em; background: #FFF; color: #666;"]': {
display: 'none',
},
'body > div': {
background: 'rgba(0, 0, 0, 0.7)',
},
'.board_header,div[style="margin: 0; padding: 0 0 0 0.5em; border-top: 0.5em solid #BEB; border-bottom: 0.5em solid #BEB; border-radius: 0.50em / 0.50em; padding: 0 0 1.0em 0.5em; height: 25em; overflow-y: scroll; background: #BEB;"]': {
background: 'rgba(0, 0, 0, 0.7)',
color: 'white',
'border-radius': '15px',
border: 'none',
},
'p[style="margin:0; padding: 0; font-size: 0.75em; background: #BEB;"]': {
background: 'none',
},
a: {
color: '#42a5f5',
},
dd: {
color: 'white',
},
},
},
'.*[.]5ch[.]net/test/read.cgi/.*': {
style: {
'.navbar-fixed-top': {
background: 'rgba(0, 0, 0, 0.7)',
},
'.container_body': {
background: 'none',
margin: 0,
padding: 0,
},
'#background': {
height: '100%',
width: '100%',
background: `url(${config.background_url}) center no-repeat fixed`,
'background-size': 'cover',
'z-index': -1,
position: 'fixed',
top: 0,
filter: `blur(${config.background_blur ? '5' : '0'}px)`,
transition: 'all 0.3s linear',
},
'#background.cleek': {
filter: 'blur(0)',
},
'#search-text,#search-button': {
'border-radius': '30px 0 0 30px',
background: 'none',
border: '1px solid rgba(255,255,255,0.5)',
filter: 'invert(0.4)',
},
'#search-button': {
'border-radius': '0 30px 30px 0',
'border-left': 'none',
transition: 'all 0.2s ease-in-out',
},
'#search-text:focus,#search-button:hover': {
filter: 'invert(0)',
},
'.title': {
'margin-top': '70px',
'font-size': '30px',
color: '#f5f5f5',
'text-shadow': '0 1px 10px #212121',
},
'.stoplight': {
background: 'rgba(255, 23, 68, 0.6)',
'margin-bottom': '10px',
'text-align': 'center',
},
'.thread': {
'text-align': 'center',
},
'.post,.post_hover,.menuitem': {
'border-radius': '15px',
background: 'rgba(0, 0, 0, 0.7)',
'box-shadow': '5px 5px 30px black',
transition: 'all 0.2s ease-in-out',
},
'.post,.post_hover': {
'margin-bottom': '15px',
'max-width': '40%',
padding: 0,
},
'.meta': {
background: 'rgba(0, 0, 0, 0.4)',
'border-radius': '15px 15px 0 0',
padding: '5px 10px 5px 10px',
'word-wrap': 'break-word',
'text-align': 'left',
},
'.message': {
margin: '0 10px',
padding: '5px 0 10px',
'text-align': 'left',
},
'.message > .escaped': {
color: 'white',
},
'.message > .escaped > hr': {
'border-top': '1px solid #616161',
},
'.meta > .number,.date,.margin_right': {
color: '#bdbdbd',
},
'.be': {
'margin-left': '5px',
},
'.escaped > a': {
color: '#42a5f5',
'text-shadow': '1px 1px 10px white',
transition: 'all 0.2s ease-in-out',
},
'.escaped > a:hover': {
'text-shadow': 'none',
},
'.meta a': {
color: 'green',
},
'br,nav': {
'user-select': 'none',
},
'.menuitem': {
'box-shadow': '3px 3px 20px black',
},
'.menuitem:hover': {
'box-shadow': '1px 1px 20px black',
},
'.footer': {
padding: '50px 0 20px 0',
},
'.search-logo': {
filter: 'invert(0.5)',
transition: 'all 0.2s ease-in-out',
},
'.search-logo:hover': {
filter: 'invert(1)',
},
'#post-form-inner': {
background: 'rgba(0, 0, 0, 0.7)',
border: 'none',
},
'.navbar-fixed-top,.pagestats,.menuitem,.topmenu,.bottommenu,.post,.post_hover': {
border: 'none',
color: 'white',
},
'.socialmedia,#banner,.ad--right,.ad--bottom': {
display: 'none',
},
'.post[data-id="1002"]': {
display: 'none',
},
'.margin_right': {
'margin-right': '5px',
},
// https://qiita.com/semind/items/e18d5f3131de4904b2c9
'.rainbow': {
background: 'linear-gradient(to right,rgb(255,0,0),rgb(255,69,0),rgb(255,255,0),rgb(0,128,0),rgb(0,0,255),rgb(75,0,130),rgb(238,130,238))',
'background-clip': 'text',
},
'.meta.rainbow *,.message.rainbow *': {
color: 'transparent',
},
'.post img': {
'max-width': '100%',
},
'#scroll': {
position: 'fixed',
background: 'rgba(0, 0, 0, 0.9)',
width: '60px',
height: '60px',
'border-radius': '50px',
right: '30px',
bottom: '30px',
'z-index': '9999',
'font-size': '50px',
'text-align': 'center',
},
},
lang: {
'.menuitem:contains(全部)': 'All',
'.menuitem:contains(最新50)': 'Latest 50',
'.menuitem:contains(スマホ版)': 'Mobile (Not Supported)',
'.menuitem:contains(掲示板に戻る)': 'Back to Board Top',
'.menuitem:contains(ULA版)': 'ULA (Not Supported)',
'.metastats:contains(コメント)': ['コメント', ' Comments'],
},
script: () => {
let long_timer
let long_cleek = false
$('body').append('<div id=\'scroll\'><b>↓</b></div>')
$(window).scroll(function () {
if ($(this).scrollTop() > $(window).height()) $('#scroll').text('↑').addClass('top')
else $('#scroll').text('↓').removeClass('top')
})
$(document).on('click', '#scroll', function () { $('html').stop().animate({ scrollTop: $(this).hasClass('top') ? 0 : $(document).height() }, 500) })
$('.container_body').append('<div id="background"></div>')
$('#background')
.on('dblclick', () => $('#background').toggleClass('cleek'))
.on('mouseup', () => {
if (long_cleek) $('#background').attr('style', `background:url(${config.background_url}?${new Date().getTime()}) center no-repeat fixed!important`)
long_cleek = false
clearTimeout(long_timer)
return true
})
.on('mousedown', () => {
long_timer = setTimeout(() => long_cleek = true, 500)
return true
})
$('.post').hover(function () {
const id = $(this).attr('data-userid')
$(`.post[data-userid="${id}"]`).attr('style', 'transform:scale(1.05, 1.05)!important;box-shadow:none!important')
}, function () {
const id = $(this).attr('data-userid')
$(`.post[data-userid="${id}"]`).removeAttr('style')
})
$(window).on('load', () => {
const ids = []
$('.post[data-id="1001"] .name b').text('オーバースレッドゥ')
if ($('#1 hr').length) {
const nodevalu = $('#1 hr')[0].nextSibling.nodeValue
if (nodevalu.includes(':vvvvv:'))
$('#1 .escaped').append('<br><span class=\'gray_om\'>強制コテハン</span>')
else if (nodevalu.includes(':vvvvvv:'))
$('#1 .escaped').append('<br><span class=\'gray_om\'>強制コテハン + IP表示</span>')
else
$('#1 .escaped').append('<br><span class=\'gray_om\'>めんどいからここらへんみとけ<br>https://info.5ch.net/index.php/新生VIPQ2#.21extend:<br>https://info.5ch.net/index.php/BBS_SLIP</span>')
}
$(`.post[data-userid="${$('#\\31').attr('data-userid')}"] > .meta .name`).each(function () { $(this).after('<span class=\'margin_right\'>(スレ主)</span>') })
$('.uid').each(function () {
if (!$(this).text()) return $(this).css('color', '#bdbdbd').attr('title', 'IDないですこの人').text('ID消すなや')
const id = $(this).text().slice(3)
$(this).text(id !== 'ead' ? id : 'Thread')
const ids_ = ids.filter(item => item[0] === id)
if (!ids_.length) {
if (id === 'ead') return $(this).parents('.post').children('.meta,.message').addClass('rainbow')
const color = generateColor()
ids.push([id, color])
console.log(`ID: ${[id, color]}`)
$(this).attr('title', `#${color}`)
return $(this).css('color', `#${color}`)
}
$(this).attr('title', `#${ids_[0][1]}`)
return $(this).css('color', `#${ids_[0][1]}`)
})
$('.name b a[href^="mailto:"]').each(function () {
let supeisharu = ['フメイ', $(this).attr('href').slice(7)]
if (supeisharu[1] === 'sage') supeisharu[0] = 'サケ'
else if (supeisharu[1] === 'age') supeisharu[0] = 'アブラアゲ'
else if (supeisharu[1] === 'agete') supeisharu[0] = 'アゲロー'
else if (supeisharu[1] === 'sagete') supeisharu[0] = 'サゲロー'
else if (supeisharu[1].test(/@(yahoo.co.jp)$/)) supeisharu[0] === 'ヤホー'
else if (supeisharu[1].test(/@(protonmail.com|protonmail.ch|pm.me)$/)) supeisharu[0] === 'プロト'
$(this).parents('.meta').children('.date').before(`<a class='margin_right' href='${$(this).attr('href')}' title='${supeisharu[1]}' style='color:#81c784!important'>(${supeisharu[0]})</a>`)
})
$('.name a[href^="mailto:"]').each(function () { $(this).removeAttr('href') })
})
$('.date').each(function () {
$(this).html($(this).text().replace(' ', '<span style="margin-right:5px!important"></span>'))
})
},
},
}
$('head').append(`<style type="text/css">${parse_json(preset.global.style)}</style>`)
preset.global.script()
Object.keys(preset).map(item => {
if (item === 'global') return
let path = preset[item]
let url = location.href
if (!preset[item].protocol) url = location.href.slice(location.protocol.length + 2)
const match = new RegExp(`^${item}$`).test(url)
console.info(`${match ? '\x1b[32m' : '\x1b[31m'}Match: ^${item}$ - ${url} - ${match}`)
if (match) {
if (typeof path.style === 'object') $('head').append(`<style type="text/css">${parse_json(path.style)}</style>`)
if (typeof path.lang === 'object') Object.keys(path.lang).map(item => {
if (typeof path.lang[item] === 'string')
$(item).text(path.lang[item])
else if (typeof path.lang[item] === 'object')
$(item).html($(item).html().replace(path.lang[item][0], path.lang[item][1]))
})
if (typeof path.script === 'function') path.script()
}
})
})()