Mastodon 連合 or ローカルタイムライン用正規表現フィルタ Fork

    @@ -8,7 +8,15 @@ /* ご多分に漏れず危険性を熟知なり覚悟なりして自己責任で使うタイプのものです。 - ブックマークレットを実行すると入力欄とボタンが出ます。 + + ■Hatena::Let からブックマークレットとして登録 + この文章の上の方にある「Mastodon 連合 or ローカルタイムライン用正規表現...」というリンクをブックマークバーにドラッグして投げ込んでください。 + + ■Gist からユーザスクリプトとしてインストール + この文章の右上の方にある「Raw」というボタンからインストールできます。 + + ブックマークレットまたはユーザスクリプトをインストールすると、マストドンの画面右下に入力欄とボタンが出ます。 + 入力欄に正規表現を入れてエンターを押してください。ボタンはフィルタのオンオフです。 本文の他、インスタンスのホスト名、アカウント名、表示名、CW のテキストおよびボタンのラベル、sensitive (NSFW) フラグの有無、ファイルの拡張子もマッチ対象です。 入力した正規表現は Cookie に保存されます。他のブックマークレット等から覗かれる可能性もゼロではないので、気になる場合は使用を控えるか、Fork して覗けないような仕組みを追加してください。
    @@ -36,6 +44,9 @@ 更新履歴: + 6/1 + 1.4.1 に対応しました。 + 5/15 Edge でも動くようにしました。 マストドン以外の URL に誤って適用された際になるべく作動しないようにしました。
    @@ -68,110 +79,107 @@ -名前も検索される文字列に含まれます。 */ - ((d = document, l = { childList: 1 }, p = d.querySelector('.columns-area>div:nth-child(4)'), k = new Map(), r, x, a, f, o, c, b, i, t, m) => { + (( + d = document, + M = MutationObserver, + A = (s, n, f) => Array.from(n.querySelectorAll(s), f), + Q = (s, n) => n.querySelector(s), + E = (e, o) => Object.assign(d.createElement(e), o), + L = (n, t, f) => n.addEventListener(t, f), + l = { childList: 1 }, + p = Q('#mastodon,div[data-react-class="Mastodon"]', d), + k = new Map(), + r = new RegExp( + decodeURIComponent((/let_filter_regexp=([^;]*)/.exec(d.cookie) || [, ''])[1]) + || 'ほげほげ|ふがふが|ぴよぴよ', 'i' + ), + x = e => { - if (!p) return; + A('.status:not(.viewed)', e.target, (n, m) => { - r = new RegExp( - decodeURIComponent((d.cookie.split('let_filter_regexp=')[1] || '').split(';')[0]) - || 'ほげほげ|ふがふが|ぴよぴよ', 'i' - ); + A('img[alt]', n, i => { - x = e => { + i.parentNode.insertBefore(E('span', { textContent: i.alt }), i) + .style.display = 'none'; - Array.from(e.target.querySelectorAll('.status:not(.viewed)'), n => { + }); - Array.from(n.querySelectorAll('img[alt]'), i => { + m = Q('.media-spoiler', n) ? '\tsensitive\tmedia' : ''; + A('a.media-gallery__item-thumbnail, video', n, a => { - i.parentNode.insertBefore(Object.assign( - d.createElement('span'), { - textContent: i.alt - } - ), i).style.display = 'none'; + m += '\tmedia' + ((a.href || a.src).match(/(\.\w+)(?:\?|$)/) || [''])[0]; - }); + }); - m = n.querySelector('.media-spoiler') ? '\tsensitive\tmedia' : ''; - Array.from(n.querySelectorAll('a.media-gallery__item-thumbnail, video'), a => { + if (r.test( + Q('.display-name', n).textContent + + '\t' + Q('.status__relative-time', n).href.replace(/^https?:\/\/|\/\d+$/g, '') + + '\t' + Q('.status__content', n).textContent + m + )) n.style.display = 'none'; - m += '\tmedia' + ((a.href || a.src).match(/(\.\w+)(?:\?|$)/) || [''])[0]; + n.classList.add('viewed'); - }); + }); - if (r.test( - n.querySelector('.display-name').textContent - + '\t' + n.querySelector('.status__relative-time').href.replace(/^https?:\/\/|\/\d+$/g, '') - + '\t' + n.querySelector('.status__content').textContent + m - )) n.style.display = 'none'; + }, + a = s => { - n.className += ' viewed'; + if (s == '🔈') { - }); + b.textContent = '🔇'; + x({ target: t }); + o.observe(t, l); - }; + } else { - a = s => { + b.textContent = '🔈'; + o.disconnect(); + if (t) A('.status', t, n => { - if (s == '🔈') { + n.classList.remove('viewed'); + n.style.display = ''; - b.textContent = '🔇'; - x({ target: t }); - o.observe(t, l); + }); + } - } else { + }, + f = n => { - b.textContent = '🔈'; - o.disconnect(); - if (t) Array.from(t.querySelectorAll('.status'), n => { + n = Q('.mastodon-column-container[aria-hidden="false"],.column', p); - n.className = 'status'; - n.style.display = ''; + if (n) { - }); - } + if (!k.get(n)) k.set(n, new M(f).observe(n, { attributes: 1 })); + n = Q('.status-list', n); - }; + } - f = n => { + if ((!t || t != n) && n) { - n = p.querySelector('.mastodon-column-container[aria-hidden="false"]'); + t = n; + a('🔇'); + a('🔈'); - if (n) { + } - if (!k.get(n)) k.set(n, new MutationObserver(s => f()).observe(n, { attributes: 1 })); - n = n.querySelector('.status-list'); + }, + w = s => { - } + p = Q('.columns-area>div:nth-child(4)', d); + if (!p) return; - if ((!t || t != n) && n) { + if (m) m.disconnect(); - t = n; - a('🔇'); - a('🔈'); - - } - - }; - - o = new MutationObserver(s => s.forEach(x)); - - c = d.getElementById('mute-buttons'); - if (!c) c = p.appendChild(Object.assign(d.createElement('div'), { - id: 'mute-buttons' - })); + if (!c) c = p.appendChild(E('div', { id: 'mute-buttons' })); // Edge は style に直接テキストを指定できない (2017/5/15 時点) c.style.cssText = 'position: fixed; height: 1em; bottom: 2em; right: 0; z-index: 999'; - b = c.appendChild(Object.assign(d.createElement('button'), { - textContent: '🔈' - })); - b.addEventListener('click', e => a(b.textContent)); + b = c.appendChild(E('button', { textContent: '🔈' })); + L(b, 'click', e => a(b.textContent)); - i = c.appendChild(Object.assign(d.createElement('input'), { - value: r.source - })); + i = c.appendChild(E('input', { value: r.source })); i.style.width = '12em'; - i.addEventListener('change', (e, s) => { + L(i, 'change', (e, s) => { s = e.target.value; a('🔇');
    @@ -180,7 +188,7 @@ try { s = new RegExp(s); } catch(_) {} - if (typeof s == 'object') { + if (s.source) { r = s; d.cookie = `let_filter_regexp=${encodeURIComponent(r.source)}; path=\x2F; expires=Tue, 19 Jan 2038 03:14:07 GMT`;
    @@ -191,18 +199,25 @@ } }); - i.addEventListener('focus', e => { + L(i, 'focus', e => (e.target.style.cssText = 'padding-left: 0; width: 12em')); + L(i, 'blur', e => (e.target.style.cssText = 'padding-left: 2em; width: 0')); - e.target.style.cssText = 'padding-left: 0; width: 12em'; + f(); + m = new M(f); + m.observe(p, l); - }); - i.addEventListener('blur', e => { + }, + o = new M(s => s.forEach(x)), + c = Q('#mute-buttons', d), + b, // 🔈 button + i, // RegExp input + t, // div column + m // MutationObserver root + ) => { - e.target.style.cssText = 'padding-left: 2em; width: 0'; - - }); - - f(); - new MutationObserver(s => f()).observe(p, l); + if (!p) return; + if (Q('.columns-area', p)) return w(); + m = new M(w); + m.observe(p, { childList: 1, subtree: 1 }); })();
  • /*
     * @title Mastodon 連合 or ローカルタイムライン用正規表現フィルタ
     * @description 名前にも適用されます。画像とか動画とか不適切系も判別できます。
     * @include *
     * @license MIT License
     * @javascript_url
     */
    
    /*
    ご多分に漏れず危険性を熟知なり覚悟なりして自己責任で使うタイプのものです。
    
    ■Hatena::Let からブックマークレットとして登録
    この文章の上の方にある「Mastodon 連合 or ローカルタイムライン用正規表現...」というリンクをブックマークバーにドラッグして投げ込んでください。
    
    ■Gist からユーザスクリプトとしてインストール
    この文章の右上の方にある「Raw」というボタンからインストールできます。
    
    ブックマークレットまたはユーザスクリプトをインストールすると、マストドンの画面右下に入力欄とボタンが出ます。
    
    入力欄に正規表現を入れてエンターを押してください。ボタンはフィルタのオンオフです。
    本文の他、インスタンスのホスト名、アカウント名、表示名、CW のテキストおよびボタンのラベル、sensitive (NSFW) フラグの有無、ファイルの拡張子もマッチ対象です。
    入力した正規表現は Cookie に保存されます。他のブックマークレット等から覗かれる可能性もゼロではないので、気になる場合は使用を控えるか、Fork して覗けないような仕組みを追加してください。
    
    入力例:
    
    ほげほげに関するトゥートを NG
    ほげほげ|ホゲホゲ|hogehoge
    ほげほげに関するトゥートのみ表示
    ^(?![\s\S]*(ほげほげ|ホゲホゲ|hogehoge))
    不適切なコンテンツを NG
    sensitive
    不適切なコンテンツのみ表示
    ^(?![\s\S]*sensitive)
    画像、動画のみ表示
    ^(?![\s\S]*media)
    画像、動画のみ表示 (誤爆ほぼなし)
    ^(?![\s\S]*	media)
    PNG のみ表示 (イラスト多めになる)
    ^(?![\s\S]*media\.png)
    動画 (GIFアニメ含む) のみ表示
    ^(?![\s\S]*media\.mp4)
    mstdn.jp からのトゥートのみ表示
    ^(?![\s\S]*mstdn\.jp)
    
    更新履歴:
    
    6/1
    1.4.1 に対応しました。
    
    5/15
    Edge でも動くようにしました。
    マストドン以外の URL に誤って適用された際になるべく作動しないようにしました。
    入力例と初期値の単語を変更しました。
    
    5/10
    ユーザスクリプトのメタデータが全部使えるところにコピーしました。
    コピー元は http://let.hatelabo.jp/pacochi/let/hLHW8svRzY8o、
    コピー先は https://gist.github.com/pacochi/0249d7c40e723a56c87e39873504268b です。
    
    4/30
    -HTML の構造が変わっていたので追従しました。
    
    4/27
    -インスタンス名とアカウント名も検索対象になりました。「\tpawoo.net/@pacochi」というような文字列が追加されます。
    
    4/26
    -https://addons.mozilla.org/ja/firefox/addon/bookmarklets-context-menu/
     を使うと Firefox でも mstdn.jp 等での利用ができるみたいなので、その辺の調整をしました。
     jQuery を使うとエラーが出てくるみたいなのでプレーンにしました。
    
    4/24
    -正規表現入力欄が入力後に隠れるようになりました。クリックするとまた出てきます。
    -フィルタが効かなくなる現象の出現頻度が低くなりました。
    -画像や動画がある場合、検索される文字列に「\tmedia」という文字列が追加されます。拡張子が判別できたら「\tmedia.png」のような文字列になります。
    -不適切なコンテンツがある場合、検索される文字列に「\tsensitive」という文字列が追加されます。「\t」はタブ文字です。
    
    4/22
    -https://mstdn.jp/users/soraki_yoshiko2/updates/1277707 こういう経緯で作りました。
    -名前も検索される文字列に含まれます。
    */
    
    ((
     d = document,
     M = MutationObserver,
     A = (s, n, f) => Array.from(n.querySelectorAll(s), f),
     Q = (s, n) => n.querySelector(s),
     E = (e, o) => Object.assign(d.createElement(e), o),
     L = (n, t, f) => n.addEventListener(t, f),
     l = { childList: 1 },
     p = Q('#mastodon,div[data-react-class="Mastodon"]', d),
     k = new Map(),
     r = new RegExp(
      decodeURIComponent((/let_filter_regexp=([^;]*)/.exec(d.cookie) || [, ''])[1])
      || 'ほげほげ|ふがふが|ぴよぴよ', 'i'
     ),
     x = e => {
    
    	A('.status:not(.viewed)', e.target, (n, m) => {
    
    		A('img[alt]', n, i => {
    
    			i.parentNode.insertBefore(E('span', { textContent: i.alt }), i)
    			 .style.display = 'none';
    
    		});
    
    		m = Q('.media-spoiler', n) ? '\tsensitive\tmedia' : '';
    		A('a.media-gallery__item-thumbnail, video', n, a => {
    
    		m += '\tmedia' + ((a.href || a.src).match(/(\.\w+)(?:\?|$)/) || [''])[0];
    
    		});
    
    		if (r.test(
    		 Q('.display-name', n).textContent
    		 + '\t' + Q('.status__relative-time', n).href.replace(/^https?:\/\/|\/\d+$/g, '')
    		 + '\t' + Q('.status__content', n).textContent + m
    		)) n.style.display = 'none';
    
    		n.classList.add('viewed');
    
    	});
    
     },
     a = s => {
    
    	if (s == '🔈') {
    
    		b.textContent = '🔇';
    		x({ target: t });
    		o.observe(t, l);
    
    	} else {
    
    		b.textContent = '🔈';
    		o.disconnect();
    		if (t) A('.status', t, n => {
    
    			n.classList.remove('viewed');
    			n.style.display = '';
    
    		});
    	}
    
     },
     f = n => {
    
    	n = Q('.mastodon-column-container[aria-hidden="false"],.column', p);
    
    	if (n) {
    
    		if (!k.get(n)) k.set(n, new M(f).observe(n, { attributes: 1 }));
    		n = Q('.status-list', n);
    
    	}
    
    	if ((!t || t != n) && n) {
    
    		t = n;
    		a('🔇');
    		a('🔈');
    
    	}
    
     },
     w = s => {
    
    	p = Q('.columns-area>div:nth-child(4)', d);
    	if (!p) return;
    
    	if (m) m.disconnect();
    
    	if (!c) c = p.appendChild(E('div', { id: 'mute-buttons' }));
    	// Edge は style に直接テキストを指定できない (2017/5/15 時点)
    	c.style.cssText = 'position: fixed; height: 1em; bottom: 2em; right: 0; z-index: 999';
    
    	b = c.appendChild(E('button', { textContent: '🔈' }));
    	L(b, 'click', e => a(b.textContent));
    
    	i = c.appendChild(E('input', { value: r.source }));
    	i.style.width = '12em';
    	L(i, 'change', (e, s) => {
    
    		s = e.target.value;
    		a('🔇');
    
    		if (s.length) {
    
    			try { s = new RegExp(s); } catch(_) {}
    
    			if (s.source) {
    
    				r = s;
    				d.cookie = `let_filter_regexp=${encodeURIComponent(r.source)}; path=\x2F; expires=Tue, 19 Jan 2038 03:14:07 GMT`;
    				a('🔈');
    
    			}
    
    		}
    
    	});
    	L(i, 'focus', e => (e.target.style.cssText = 'padding-left: 0; width: 12em'));
    	L(i, 'blur', e => (e.target.style.cssText = 'padding-left: 2em; width: 0'));
    
    	f();
    	m = new M(f);
    	m.observe(p, l);
    
     },
     o = new M(s => s.forEach(x)),
     c = Q('#mute-buttons', d),
     b, // 🔈 button
     i, // RegExp input
     t, // div column
     m  // MutationObserver root
    ) => {
    
    	if (!p) return;
    	if (Q('.columns-area', p)) return w();
    	m = new M(w);
    	m.observe(p, { childList: 1, subtree: 1 });
    
    })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。