// ==UserScript==
// @name PTT 文章&推文隱藏腳本
// @namespace http://tampermonkey.net/
// @version 1.3-default-JQ-with-GM.config
// @description 隱藏含有特定關鍵字的文章與推文
// @author LianhuanJ
// @match https://www.ptt.cc/bbs/*
// @grant GM_getValue
// @grant GM.getValue
// @grant GM_setValue
// @grant GM.setValue
// @grant GM_deleteValue
// @grant GM.deleteValue
// @grant GM_addValueChangeListener
// @grant GM_registerMenuCommand
// @grant GM.registerMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @require https://greasyfork.org/scripts/371339-gm-webextpref/code/GM_webextPref.js
// @license MIT
// ==/UserScript==
/* jshint esversion: 8 */
const block_counts = {article: 0, push: 0};
const func_state = {article_polling: false};
const pref = GM_webextPref({
default: {
// For explicit search
_block_author: [],
_block_push_id: [],
// For implicit search
_block_author_reg: [/[a-zA-Z]+5566$/],
_block_push_id_reg: [/[a-zA-Z]+5566$/],
_block_title_keyword: ["(本文已被刪除)"],
// Color
_block_title_keyword_color: "#484F4F",
_block_author_color: "#625750",
_block_author_reg_color: "#667292",
_block_push_id_reg_color: "#D96459",
_block_push_id_color: "#36486B",
// Switch
_block_article_with_title: true,
_block_article_with_author: true,
_block_article_with_author_reg: true,
_block_push_with_id: true,
_block_push_with_id_reg: true,
},
body: [
{
key: "_block_article_with_title",
label: "Block Article with Title keyword",
type: "checkbox",
children: [
{ key: "_block_title_keyword_color", label: "", type: "color" },
{ key: "_block_title_keyword", label: "", type: "textarea" },
]
},
{
key: "_block_article_with_author",
label: "Block Article with Author",
type: "checkbox",
children: [
{ key: "_block_author_color", label: "", type: "color" },
{ key: "_block_author", label: "", type: "textarea" },
]
},
{
key: "_block_article_with_author_reg",
label: "Block Article with Author Regex",
type: "checkbox",
children: [
{ key: "_block_author_reg_color", label: "", type: "color" },
{ key: "_block_author_reg", label: "", type: "textarea" },
]
},
{
key: "_block_push_with_id",
label: "Block Push With ID",
type: "checkbox",
children: [
{ key: "_block_push_id_color", label: "", type: "color" },
{ key: "_block_push_id", label: "", type: "textarea" },
]
},
{
key: "_block_push_with_id_reg",
label: "Block Push With ID Regex",
type: "checkbox",
children: [
{ key: "_block_push_id_reg_color", label: "", type: "color" },
{ key: "_block_push_id_reg", label: "", type: "textarea" },
]
}
],
navbar: false,
css: " body{width: 100%} label::after{content: ' '} textarea{font-family: 'Consolas'} .webext-pref-toolbar{display: block}",
});
function _blockArticle(autohide, obj, bg_color){
$(obj).css("background-color", bg_color);
$(obj).addClass("blocklisted");
$(obj).toggle(!autohide);
}
function _blockPush(autohide, obj, bg_color, explicity){
$(obj).css("background-color", bg_color);
$(obj).addClass("blocklisted");
$(obj).toggleClass("explicitly", explicity);
$(obj).toggle(!autohide);
}
function escapeStrArrForRegex(strArr = []){
let reg_char = ['{', '}', '[', ']', '(', ')', '+', '-', '*', '/', '^', '.', '\\'];
let reg_char_exp = RegExp(`(${reg_char.map( x => '\\' + x ).join('|')})`, 'g');
return strArr.map( x => x.replaceAll(reg_char_exp, `\\$1`));
}
function blockArticle(articles = $(), autohide = true){
let count = 0;
let block_article_with_author = pref.get('_block_article_with_author');
let block_article_with_author_reg = pref.get('_block_article_with_author_reg');
let block_article_with_title = pref.get('_block_article_with_title');
let block_title_keyword_color = pref.get('_block_title_keyword_color');
let block_author_color = pref.get('_block_author_color');
let block_author_reg_color = pref.get('_block_author_reg_color');
let block_author = pref.get('_block_author');
let block_author_reg = pref.get('_block_author_reg');
let block_title_keyword = pref.get('_block_title_keyword');
block_author_reg = RegExp(block_author_reg, 'g');
// Another pass `cause GM_webextPref save the values as string
[block_title_keyword] = [block_title_keyword].map( x => { return (typeof(x) == "string") ? x.split(',') : x ;});
// Convert Array to Regex pattern
let block_title_keyword_reg = RegExp(`(${escapeStrArrForRegex(block_title_keyword).join('|')})`, 'g');
articles.each(function(){
let this_article = this;
let blocklisted = false;
let explicitly_blocklisted = false;
// Check Authour, explicitly
let this_article_author = $(this_article).children(".meta").children(".author").text();
if( !blocklisted && block_article_with_author ){
if( block_author.includes(this_article_author) ){
_blockArticle(autohide, this_article, block_author_color);
explicitly_blocklisted = blocklisted = true;
}
}
// Check Authour, implicitly
if( !blocklisted && block_article_with_author_reg ){
if( this_article_author.match(block_author_reg) ){
_blockArticle(autohide, this_article, block_author_reg_color);
blocklisted = true;
}
}
// Check Title, implicitly
let this_article_title = $(this_article).children(".title").text();
if( !blocklisted && block_article_with_title ){
if( this_article_title.match(block_title_keyword_reg) ){
_blockArticle(autohide, this_article, block_title_keyword_color);
blocklisted = true;
}
}
// Append block-this-author button
let dropdown_menu = $(this_article).find(".dropdown");
let block_button = $(dropdown_menu).children(".item").has("a.blockThisAuthor");
let unblock_button = $(dropdown_menu).children(".item").has("a.unblockThisAuthor");
if( block_button.length == 0 )
block_button = $(`<div class="item"><a class="blockThisAuthor" data-author="${this_article_author}">黑單 ${this_article_author}</a></div>`).appendTo(dropdown_menu);
if( unblock_button.length == 0 )
unblock_button = $(`<div class="item"><a class="unblockThisAuthor" data-author="${this_article_author}">解除黑單 ${this_article_author}</a></div>`).appendTo(dropdown_menu);
$(block_button).toggle(!explicitly_blocklisted);
$(unblock_button).toggle(explicitly_blocklisted);
if(blocklisted)
count++ ;
});
return count;
}
function blockPush(pushes = $(), autohide = true){
let count = 0;
let block_push_with_id = pref.get('_block_push_with_id');
let block_push_with_id_reg = pref.get('_block_push_with_id_reg');
let block_push_id_color = pref.get('_block_push_id_color');
let block_push_id_reg_color = pref.get('_block_push_id_reg_color');
let block_push_id = pref.get('_block_push_id');
let block_push_id_reg = pref.get('_block_push_id_reg');
// Another pass `cause GM_webextPref save the values as string
[block_push_id] = [block_push_id].map( x => { return (typeof(x) == "string") ? x.split(',') : x ;});
block_push_id_reg = RegExp(block_push_id_reg, 'g');
pushes.each(function(){
let this_push = this;
let this_push_ID_place = $(this_push).children(".push-userid");
let this_push_ID = $(this_push_ID_place).text();
let blocklisted = false;
// Check Push ID, explicitly
if( !blocklisted && block_push_with_id ){
if( block_push_id.includes(this_push_ID) ){
_blockPush(autohide, this_push, block_push_id_color, true);
blocklisted = true;
}
}
// Check Push ID, implicitly
if( !blocklisted && block_push_with_id_reg ){
if( this_push_ID.match(block_push_id_reg) ){
_blockPush(autohide, this_push, block_push_id_reg_color, false);
blocklisted = true;
}
}
// Block-this-push-ID event
if( !$(this_push_ID_place).attr("title") ){
$(this_push_ID_place).attr("title", "加入/解除 推文黑名單");
$(this_push_ID_place).on("click", () => {
if( $(this_push).hasClass("explicitly") ){
if( confirm(`解除推文黑單 ${this_push_ID}?`) )
pref.set("_block_push_id", Array.from(new Set(pref.get("_block_push_id").toString().split(',').filter( (x)=> x != this_push_ID && x != '' ))).toString());
}
else{
if( confirm(`推文黑單 ${this_push_ID}?`) )
pref.set("_block_push_id", `${pref.get("_block_push_id").toString()},${this_push_ID}` );
}
});
}
if(blocklisted)
count++ ;
});
return count;
}
function creatConfigButton(){
let ButtonPlace = $("#topbar > a.board");
ButtonPlace.after(`<button class="blocklist_config" style="color: green">設定</button>`);
$("button.blocklist_config").on("click", pref.openDialog );
}
function resetBlocklist(autohide = true){
$('.blocklisted').css("background-color", '');
$('.blocklisted').show();
$('.blocklisted').removeClass("blocklisted explicitly");
$(".blocklist_counter").toggleClass("btn selected", !autohide);
}
function updateBlocklistCounter(block_counts){
// Put blocklisted number somewhere
if( $("button.blocklist_counter").length == 0 ){
let counter_place = $("#topbar > a.board");
counter_place.after(`<button class="blocklist_counter" style="color: red">隱藏: ${block_counts.article + block_counts.push}</button>`);
}
else {
$("button.blocklist_counter").text(`隱藏: ${block_counts.article + block_counts.push}`);
}
}
async function trackPush(num){
let now_num = $(".new-push").length ;
if( num != now_num ){
block_counts.push = blockPush($(".new-push:eq(-1) > .push"), !$("button.blocklist_counter").hasClass("btn selected"));
updateBlocklistCounter(block_counts);
}
if( $("#article-polling").text() == "推文會自動更新,並會自動捲動" )
setTimeout( () => {trackPush(now_num);}, 1000 );
else
func_state.article_polling = false;
}
function main(autohide = true){
let start = window.performance.now();
if( $('.blocklisted').length > 0 )
resetBlocklist(autohide);
// Blocking Articles
console.time("Blocking Articles");
block_counts.article = blockArticle($(".r-ent"), autohide);
console.timeEnd("Blocking Articles");
// Blocking Pushes
console.time("Blocking Pushes");
block_counts.push = blockPush($(".push"), autohide);
console.timeEnd("Blocking Pushes");
updateBlocklistCounter(block_counts);
if( $(".blocklist_script_log").length == 0 )
$('body').append(`<h5 class="blocklist_script_log" style="display: block; text-align: center; color: white">Script time: ${(window.performance.now() - start).toFixed(2)}ms</h5>`);
else
$(".blocklist_script_log").text(`Script time: ${(window.performance.now() - start).toFixed(2)}ms`);
}
// init
Promise.all([pref.ready()]).then( () => {
main(true);
creatConfigButton();
// Resize webext-pref-iframe
$("head").append(`<style>iframe.webext-pref-iframe{width: 90vw !important; margin: 10vh 5vw !important}</style>`);
// Binding events below
pref.on("change", () => {main( !$(".blocklist_counter").hasClass("btn selected") );});
// Blocklist_counter event
$(".blocklist_counter").on("click", () => {
$(".blocklisted").toggle(500);
$(".blocklist_counter").toggleClass("btn selected");
if( $(".blocklist_counter").hasClass("btn selected") && $(".blocklisted").length > 0 ){
$('html, body').animate({
scrollTop: $(".blocklisted:eq(0)").offset().top - Math.max( $("#action-bar-container:eq(0)").outerHeight(), $("#topbar-container").outerHeight() )
}, 500);}
});
// Block-this-author event
$("a.blockThisAuthor").on("click", function(){
pref.set("_block_author", `${pref.get("_block_author").toString()},${$(this).data("author")}`);
});
$("a.unblockThisAuthor").on("click", function(){
pref.set("_block_author", Array.from(new Set(pref.get("_block_author").toString().split(',').filter( (x)=> x != $(this).data("author") && x != '' ))).toString());
});
// Update pushes event
$("#article-polling").on("click", function(){
if( !$(this).hasClass("fatal-error") && !func_state.article_polling )
func_state.article_polling = true;
setTimeout( () => {trackPush($(".new-push").length);}, 500 );
});
});