- // ==UserScript==
- // @name Plurk shadow block
- // @name:zh-TW 噗浪隱形黑名單
- // @description Shadow blocks user (only blocks on responses and timeline of yourself)
- // @description:zh-TW 隱形封鎖使用者(只是會在回應和在河道上看不到被封鎖者的發文、轉噗,其他正常)
- // @version 0.4.0
- // @license MIT
- // @namespace https://github.com/stdai1016
- // @match https://www.plurk.com/*
- // @exclude https://www.plurk.com/_*
- // @require https://code.jquery.com/jquery-3.5.1.min.js
- // @require https://gf.qytechs.cn/scripts/432792-plurk-lib/code/plurk_lib.js?version=972862
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // ==/UserScript==
-
- /* jshint esversion: 6 */
- /* global $, plurklib */
-
- (function () {
- 'use strict';
- const LANG = {
- DEFAULT: {
- resp_btn_hide: 'Hide blocked responses',
- resp_btn_show: 'Show blocked responses',
- set_alert: 'Incorrect format of nick name!',
- set_append: 'Append',
- set_empty: 'There is no one in your blocklist.',
- set_note:
- 'A blocked user will not be shown on responses and your timeline,' +
- ' but is still able to see your profile, follow you,' +
- ' respond to your plurks or befriend you.',
- set_remove: 'Remove',
- set_replurk: 'Block replurks',
- set_response: 'Block responses',
- set_tab: 'Shadow Block'
- },
- 'zh-hant': {
- resp_btn_hide: '隱藏被封鎖的回應',
- resp_btn_show: '顯示被封鎖的回應',
- set_alert: '帳號格式不正確',
- set_append: '新增',
- set_empty: '沒有任何人在黑名單中',
- set_note: '在回應區和自己的河道上看不到被封鎖者的發文、轉噗;' +
- '但對方仍可瀏覽您的個人檔案,關注、回應您的訊息,或加您為朋友。',
- set_remove: '移除',
- set_replurk: '封鎖轉噗',
- set_response: '封鎖回應',
- set_tab: '隱形黑名單'
- }
- };
- let lang = LANG.DEFAULT;
- const curLang = document.documentElement.getAttribute('lang') || '';
- if (curLang.toLowerCase() in LANG) lang = LANG[curLang.toLowerCase()];
-
- if (typeof plurklib === 'undefined') {
- console.error('plurklib load failed!');
- return;
- }
- const pageUserId = plurklib.getPageUserData()?.id;
- const currUserId = plurklib.getUserData()?.id;
-
- /* ======= storage ======= */
- /** Struct in GM storage:
- * {
- * `u${currUserId}`: {
- * `b${blockedUserId}`: {
- * id: blockedUserId,
- * nick_name: <string>, // for readability
- * replurk: <bool>, // block his replurks
- * response: <bool>, // block his responses
- * date: <UTC_datetime_string>
- * }
- * }
- * }
- */
- function valueGetSet (val = null) {
- if (val != null) GM_setValue(`u${currUserId}`, val);
- return GM_getValue(`u${currUserId}`);
- }
-
- let _blockedUsers = valueGetSet();
- if (typeof _blockedUsers !== 'object') _blockedUsers = valueGetSet({});
- const blockedList = {
- get: id => _blockedUsers[`b${id}`],
- add: user => {
- _blockedUsers[`b${user.id}`] = {
- id: user.id,
- nick_name: user.nick_name,
- replurk: user.replurk ?? true,
- response: user.response ?? true,
- date: user.date ?? (new Date()).toUTCString()
- };
- valueGetSet(_blockedUsers);
- },
- remove: id => {
- delete _blockedUsers[`b${id}`];
- valueGetSet(_blockedUsers);
- },
- contains: user => {
- switch (typeof user) {
- case 'string':
- for (const u in _blockedUsers) {
- if (_blockedUsers[u].nick_name === user) return true;
- }
- break;
- case 'number':
- return !!_blockedUsers[`b${user}`];
- case 'object':
- return !!_blockedUsers[`b${user?.id}`];
- }
- return false;
- },
- forEach: callbackfn => {
- for (const i in _blockedUsers) {
- callbackfn(_blockedUsers[i], i, _blockedUsers);
- }
- },
- get length () { return Object.keys(_blockedUsers).length; }
- };
-
- /* ============== */
- GM_addStyle(
- '.hide {display:none}' +
- '.item_holder .user_item.user_shadow_blocked_users_item .user_info {' +
- ' width: calc(100% - 190px);}' +
- '.friend_man.not_block {background-color:#999;}' +
- '.friend_man.not_block:hover {background-color:#207298}' +
- '.resp-hidden-show {background:#f5f5f9;color:#afb8cc;' +
- ' font-weight:normal;vertical-align:top;transform:scale(0.9);opacity:0;}' +
- '.resp-hidden-show.show {opacity:1}' +
- '.resp-hidden-show:not(.show) .onshow {display:none}' +
- '.resp-hidden-show.show .onhide {display:none}' +
- '.response-status:hover .resp-hidden-show {opacity:1}' +
- '.resp-hidden-show:hover {background:#afb8cc;color:#fff}'
- );
-
- if (window.location.pathname === '/Friends/') {
- $('<li><a void="">' + lang.set_tab + '</a></li>').on('click', function () {
- window.history.pushState('', document.title, '/Friends/');
- $('#pop-window-tabs>ul>li').removeClass('current');
- this.classList.add('current');
- const $content = $('#pop-window-inner-content .content_inner').empty();
- $content.append(
- '<div class="note">' + lang.set_note + '</div>',
- '<div class="dashboard">' +
- ' <div class="search_box"><input>' +
- ' <button>' + lang.set_append + '</button></div>' +
- ' <div class="empty">' + lang.set_empty + '</div>' +
- '</div>');
- const $holder = $('<div class="item_holder"></div>').appendTo($content);
- if (blockedList.length) {
- $content.find('.dashboard .empty').addClass('hide');
- }
- blockedList.forEach(u => {
- plurklib.fetchUserInfo(u.id).then(info => {
- makeBlockedUserItem(info, $holder);
- }).catch(e => {
- console.info(`Cannot get info of "${u.nick_name}" (${e.message})`);
- makeBlockedUserItem(u, $holder);
- });
- });
- $content.find('.search_box>button').on('click', function () {
- const m = this.parentElement.children[0].value.match(/^[A-Za-z]\w+$/);
- if (m) {
- this.parentElement.children[0].value = '';
- $content.find('.dashboard .empty').addClass('hide');
- plurklib.fetchUserInfo(m[0]).then(info => {
- blockedList.add(info);
- makeBlockedUserItem(info, $holder);
- }).catch(e => {
- window.alert(`Unknown user "${m[0]}"`);
- });
- } else { window.alert(lang.set_alert); }
- });
- }).appendTo('#pop-window-tabs>ul');
- } else if (pageUserId === currUserId ||
- window.location.pathname.match(/^\/p\/[0-9a-z]+$/)) {
- makeButton($('#plurk_responses>.response_box'));
- makeButton($('#form_holder>.response_box'));
- makeButton($('#cbox_response>.response_box'));
- const po = new plurklib.PlurkObserver(prs => prs.forEach(pr => {
- pr.plurks.forEach(plurk => {
- if (blockedList.contains(plurk.owner_id)) {
- if (plurk.isResponse) {
- plurk.target.classList.add('shadow-block');
- const btn =
- pr.target.parentElement.querySelector('.resp-hidden-show');
- btn?.classList.remove('hide');
- if (blockedList.get(plurk.owner_id).response) {
- plurk.target.classList.add('hide');
- console.debug(`block #m${plurk.id}`);
- } else { btn?.classList.add('show'); }
- } else {
- plurk.target.classList.add('shadow-block', 'hide');
- console.debug(`block #p${plurk.id}`);
- }
- } else if (blockedList.get(plurk.replurker_id)?.replurk) {
- plurk.target.classList.add('shadow-block', 'hide');
- console.debug(`block #p${plurk.id}`);
- }
- });
- }));
- po.observe({ plurk: true });
- }
-
- function makeButton ($responseBox) {
- if (!$responseBox.length) return;
- const $formBtn = $(
- '<div><span class="onshow">' + lang.resp_btn_hide + '</span>' +
- '<span class="onhide">' + lang.resp_btn_show + '</span></div>'
- );
- $formBtn.on('click', function () {
- $formBtn.toggleClass('show');
- const $blocks = $responseBox.children('.list').children('.shadow-block');
- if ($formBtn.hasClass('show')) $blocks.removeClass('hide');
- else $blocks.addClass('hide');
- }).addClass(['resp-hidden-show', 'button', 'small-button', 'hide'])
- .insertAfter($responseBox.find('.response-only-owner'));
- (new MutationObserver(mrs => mrs.forEach(mr => {
- if (!mr.target.querySelector('.handle-remove')) {
- $('<div class="handle-remove hide"></div>').prependTo(mr.target);
- }
- mr.removedNodes.forEach(node => {
- if (node.classList.contains('handle-remove')) {
- $formBtn.removeClass('show').addClass('hide').text(lang.resp_btn_show);
- }
- });
- }))).observe($responseBox.find('.list').first()[0], { childList: true });
- }
-
- function makeBlockedUserItem (info, holder) {
- const user = blockedList.get(info.id);
- if (info.nick_name && info.nick_name !== user.nick_name) {
- user.nick_name = info.nick_name;
- blockedList.add(user);
- }
- blockedList.add(user);
- const $u = $('<div class="user_item user_shadow_blocked_users_item"></div>');
- const img = info.has_profile_image
- ? `https://avatars.plurk.com/${info.id}-medium${info.avatar ?? ''}.gif`
- : 'https://www.plurk.com/static/default_medium.jpg';
- $u.append([
- '<a class="user_avatar" target="_blank">',
- ` <img class="profile_pic" src="${img}"></img>`,
- '</a>',
- '<div class="user_info">',
- ' <a class="user_link" target="_blank"',
- ` style="color:#000">${info.display_name}</a>`,
- ` <span class="nick_name">@${info.nick_name}</span>`,
- ' <div class="more_info"><br></div>',
- '</div>',
- '<div class="user_action">',
- ` <a void="" data-switch="replurk" title="${lang.set_replurk}"`,
- ' class="friend_man icon_only pif-replurk',
- ` ${user.replurk ? 'has_block' : 'not_block'}"></a>`,
- ` <a void="" data-switch="response" title="${lang.set_response}"`,
- ' class="friend_man icon_only pif-message',
- ` ${user.response ? 'has_block' : 'not_block'}"></a>`,
- ` <a void="" data-remove="1" title="${lang.set_remove}"`,
- ' class="friend_man icon_only pif-user-blocked has_block"></a>',
- '</div>'
- ].join(''));
- $u.find('a:not(.icon_only)').attr('href', '/' + info.nick_name);
- $u.find('a.icon_only').on('click', function () {
- if (this.dataset.switch) {
- user[this.dataset.switch] = !user[this.dataset.switch];
- if (user[this.dataset.switch]) {
- this.classList.add('has_block');
- this.classList.remove('not_block');
- } else {
- this.classList.remove('has_block');
- this.classList.add('not_block');
- }
- blockedList.add(user);
- }
- if (this.dataset.remove) {
- blockedList.remove(user.id);
- $u.remove();
- }
- });
- $u.appendTo(holder);
- }
- })();