// ==UserScript== // @name F**k 三观 // @namespace http://tampermonkey.net/ // @version 0.9 // @description try to take over the world! // @author yetone // @match https://*.douban.com/* // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== (function() { 'use strict'; let $style = document.createElement('style'); $style.innerText = ` .my-hl { -webkit-animation: highlight 1.6s ease-out; animation: highlight 1.6s ease-out } @-webkit-keyframes highlight { 0% { background: #ebebeb } html[data-theme=dark] 0% { background: #444 } to { background: transparent none repeat 0 0/auto auto padding-box border-box scroll; background: initial } } @keyframes highlight { 0% { background: #ebebeb } html[data-theme=dark] 0% { background: #444 } to { background: transparent none repeat 0 0/auto auto padding-box border-box scroll; background: initial } }`; document.head.appendChild($style); const iptTypes = { Array: { type: 'text', processor: arr => arr.join(','), extractor: $node => $node.value.split(',').filter(x => !!x), }, String: { type: 'text', }, Boolean: { type: 'checkbox', extractor: $node => $node.checked, }, Number: { type: 'number', }, }; const settingDef = { kws: { type: 'Array', label: '关键词(懒得做好看了,英文逗号隔开,别怪我没提醒你)', iptStyle: 'width: 300px;', default: ['三观'], getProcessor: kws => kws.filter(x => !!x), setProcessor: kws => kws.filter(x => !!x), }, forward: { type: 'Boolean', label: '包括转发的原内容?', default: true, }, blockThreshold: { type: 'Number', label: '提示拉黑的阈值', default: 3, getProcessor: x => x | 0, setProcessor: x => x | 0, }, }; const setting = makeSetting(); renderSettingArea(); process(); function makeSetting() { let props = Object.keys(settingDef).reduce((p, c) => { let key = `fk-setting-${c}`; let { type = 'String', default: dft, getProcessor = x => x, setProcessor = x => x } = settingDef[c]; return { ...p, [c]: { get() { let v = GM_getValue(key); v = getType(v) === type ? v : dft; return getProcessor(v); }, set(v) { v = setProcessor(v); GM_setValue(key, v); } } }; }, {}); return Object.defineProperties({}, props); } function renderSettingFields() { return Object.keys(settingDef).map(k => { let { type = 'String', label = k, iptStyle = '' } = settingDef[k]; let { type: iptType = 'text', processor = x => x } = iptTypes[type]; let value = setting[k]; return `
`; }).join(''); } function setSetting($form) { Object.keys(settingDef).forEach(k => { let { type = 'String', label = k } = settingDef[k]; let { extractor = $node => $node.value } = iptTypes[type]; let $ipt = $form.querySelector(`[name=fk-${k}]`); let value = extractor($ipt); setting[k] = value; }); } function renderSettingArea() { let $w = document.querySelector('.aside'); if ($w === null) { return; } let $div = document.createElement('div'); $div.style.padding = '8px 0'; let $a = document.createElement('a'); $div.append($a); $a.innerText = 'F**k 设置'; $a.href = 'javascript:;'; $a.style.color = '#ccc'; $a.addEventListener('click', function() { let $area = $div.querySelector('.fk-setting'); if ($area !== null) { $area.remove(); return; } $area = document.createElement('div'); $area.classList.add('fk-setting'); $area.style.padding = '8px 0'; let $form = document.createElement('form'); $form.innerHTML = `${renderSettingFields()}`; $area.appendChild($form); $form.addEventListener('submit', function(e) { e.preventDefault(); setSetting($form); alert('保存成功!'); }, true); $div.append($area); }, true); $w.prepend($div); } function getType(obj) { return Object.prototype.toString.call(obj).slice(8, -1); } function fetchStatus(sid) { let url = `https://www.douban.com/doubanapp/dispatch?uri=/status/${sid}/`; return new Promise((resolve, reject) => { $.ajax_withck({url, success: r => { let $n = $(r).find('.status-wrapper'); $n.find('.actions').remove(); let $pubtime = $n.find('.pubtime') let pubtime = $pubtime.html(); $pubtime.html(`${pubtime}`); resolve($n[0]); }, error: () => resolve(sid)}); }); } function renderStatuses(sids) { if (sids.length === 0) { return '没有广播'; } return Promise.all(sids.map(fetchStatus)).then(res => { let missing = []; let lines = []; for (let r of res) { if (r instanceof String) { missing.push(r); lines.push(`${r} 已经不存在了
`); continue; } lines.push(r.outerHTML); } return [lines.join(''), missing]; }); } function process() { let $statuses = document.querySelectorAll('.status-wrapper, .item-status'); $statuses.forEach($x => { if ($x.dataset.fk) { return; } let kws = search($x); $x.dataset.fk = true; if (kws.size === 0) { return; } let info = getStatusInfo($x); let $div = document.createElement('div'); $div.style.color = '#bfbfbf'; $div.style.fontSize = '12px'; $div.style.textAlign = 'center'; $div.style.borderBottom = '1px solid #e5e5e5'; $div.style.padding = '8px 0'; let $tip = document.createElement('div'); $tip.innerText = `${info.user.name}的广播包含你设置的${Array.from(kws).map(x => `「${x}」`).join('、')},已折叠`; $tip.style.cursor = 'pointer'; $tip.style.display = 'inline-block'; $div.addEventListener('click', function() { $div.style.display = 'none'; $x.style.display = 'block'; $x.classList.add('my-hl'); }, false); $div.appendChild($tip); let key = `imangry-sids-${info.user.id}`; let sidsMap = GM_getValue(key) || {}; let sids = [] kws.forEach(kw => { let _sids = sidsMap[kw] || []; if (_sids.indexOf(info.id) === -1) { _sids.push(info.id); sidsMap[kw] = _sids; } _sids.forEach(sid => { if (sids.indexOf(sid) === -1) { sids.push(sid); } }); }); GM_setValue(key, sidsMap); if (sids.length >= setting.blockThreshold) { let $dd = document.createElement('div'); $dd.style.display = 'inline-block'; $dd.style.marginLeft = '8px'; let $span = document.createElement('span'); $span.innerText = `已发布${sids.length}次`; $span.style.color = '#888'; $span.style.cursor = 'pointer'; $span.addEventListener('click', function(e) { e.stopPropagation(); let old = $span.innerText; $span.innerText = '加载中...'; renderStatuses(Array.from(sids)).then(([ html, missing ]) => { show_dialog(``, 760); search(document.querySelector('#fk-dialog .fk-dialog-bd')); $span.innerText = old; }); }); $dd.appendChild($span); let $btn = document.createElement('a'); $btn.href = 'javasript:;'; $btn.innerText = '拉黑'; $btn.style.marginLeft = '8px'; $btn.addEventListener('click', function(e) { e.stopPropagation(); if (!confirm(`确定拉黑${info.user.name}?`)) { return; } $.postJSON_withck('https://www.douban.com/j/contact/addtoblacklist', { people: info.user.id }, function() { alert(`已拉黑${info.user.name}`); }); }, false); $dd.appendChild($btn); $div.appendChild($dd); } insertAfter($div, $x); $x.style.display = 'none'; }); } function getUserInfo($node) { let $pic = $node.querySelector('.usr-pic img') || $node.querySelector('img.avatar'); let match = $pic === null ? null : $pic.src.match(/(\d+)-\d+\.(jpg|jpeg|png|gif)/); let id = match !== null ? match[1] : $node.querySelector('.status-item').dataset.uid; let $lnk = $node.querySelector('.lnk-people') || $node.querySelector('a.author'); let name = $lnk === null ? 'unknow' : $lnk.innerText; return {id, name}; } function getStatusInfo($node) { let id = $node.dataset.sid; let $text = $node.querySelector('.status-saying') || $node.querySelector('.status-preview'); let text = $text === null ? '' : $text.innerText; let user = getUserInfo($node); return { id, text, user }; } function insertAfter($new, $target) { let $p = $target.parentNode; if($p.lastChild === $target) { $p.appendChild($new); } else { $p.insertBefore($new, $target.nextSibling); } } function search($parent) { let nodes = []; let need = false; let kws = new Set(); if (setting.kws.length === 0) { console.log('您没有设置关键词!!!'); return kws; } let pattern = new RegExp(setting.kws.join('|'), 'g'); for (let $node of $parent.childNodes) { if ($node.nodeType !== 3) { if ($node.nodeType === 1){ if (!setting.forward && $node.classList.contains('status-real-wrapper')) { continue; } if ($node.classList.contains('comment-form')) { continue; } if ($node.classList.contains('reshared_by')) { continue; } if ($node.classList.contains('actions') && $node.parentNode.classList.contains('bd')) { continue; } } for (let kw of search($node)) { kws.add(kw); } continue; } let text = $node.textContent; let lastIdx = 0; text.replace(pattern, (c, idx, t) => { kws.add(c); need = true; nodes.push(new Text(t.substring(lastIdx, idx))); lastIdx = idx + c.length; let $b = document.createElement('b'); $b.style.background = '#ffb56e'; $b.style.fontWeight = 'normal'; $b.innerText = c; nodes.push($b); }); nodes.push(new Text(text.substring(lastIdx))); } if (need) { while ($parent.childNodes.length > 0) { $parent.childNodes.forEach($x => { $parent.removeChild($x); }); } nodes.forEach($x => { $parent.appendChild($x); }); } return kws; } })();