噗浪隱形黑名單

隱形封鎖使用者(只是會在回應和在河道上看不到被封鎖者的發文、轉噗,其他正常)

  1. // ==UserScript==
  2. // @name Plurk shadow block
  3. // @name:zh-TW 噗浪隱形黑名單
  4. // @description Shadow blocks user (only blocks on responses and timeline of yourself)
  5. // @description:zh-TW 隱形封鎖使用者(只是會在回應和在河道上看不到被封鎖者的發文、轉噗,其他正常)
  6. // @version 0.4.0
  7. // @license MIT
  8. // @namespace https://github.com/stdai1016
  9. // @match https://www.plurk.com/*
  10. // @exclude https://www.plurk.com/_*
  11. // @require https://code.jquery.com/jquery-3.5.1.min.js
  12. // @require https://gf.qytechs.cn/scripts/432792-plurk-lib/code/plurk_lib.js?version=972862
  13. // @grant GM_addStyle
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // ==/UserScript==
  17.  
  18. /* jshint esversion: 6 */
  19. /* global $, plurklib */
  20.  
  21. (function () {
  22. 'use strict';
  23. const LANG = {
  24. DEFAULT: {
  25. resp_btn_hide: 'Hide blocked responses',
  26. resp_btn_show: 'Show blocked responses',
  27. set_alert: 'Incorrect format of nick name!',
  28. set_append: 'Append',
  29. set_empty: 'There is no one in your blocklist.',
  30. set_note:
  31. 'A blocked user will not be shown on responses and your timeline,' +
  32. ' but is still able to see your profile, follow you,' +
  33. ' respond to your plurks or befriend you.',
  34. set_remove: 'Remove',
  35. set_replurk: 'Block replurks',
  36. set_response: 'Block responses',
  37. set_tab: 'Shadow Block'
  38. },
  39. 'zh-hant': {
  40. resp_btn_hide: '隱藏被封鎖的回應',
  41. resp_btn_show: '顯示被封鎖的回應',
  42. set_alert: '帳號格式不正確',
  43. set_append: '新增',
  44. set_empty: '沒有任何人在黑名單中',
  45. set_note: '在回應區和自己的河道上看不到被封鎖者的發文、轉噗;' +
  46. '但對方仍可瀏覽您的個人檔案,關注、回應您的訊息,或加您為朋友。',
  47. set_remove: '移除',
  48. set_replurk: '封鎖轉噗',
  49. set_response: '封鎖回應',
  50. set_tab: '隱形黑名單'
  51. }
  52. };
  53. let lang = LANG.DEFAULT;
  54. const curLang = document.documentElement.getAttribute('lang') || '';
  55. if (curLang.toLowerCase() in LANG) lang = LANG[curLang.toLowerCase()];
  56.  
  57. if (typeof plurklib === 'undefined') {
  58. console.error('plurklib load failed!');
  59. return;
  60. }
  61. const pageUserId = plurklib.getPageUserData()?.id;
  62. const currUserId = plurklib.getUserData()?.id;
  63.  
  64. /* ======= storage ======= */
  65. /** Struct in GM storage:
  66. * {
  67. * `u${currUserId}`: {
  68. * `b${blockedUserId}`: {
  69. * id: blockedUserId,
  70. * nick_name: <string>, // for readability
  71. * replurk: <bool>, // block his replurks
  72. * response: <bool>, // block his responses
  73. * date: <UTC_datetime_string>
  74. * }
  75. * }
  76. * }
  77. */
  78. function valueGetSet (val = null) {
  79. if (val != null) GM_setValue(`u${currUserId}`, val);
  80. return GM_getValue(`u${currUserId}`);
  81. }
  82.  
  83. let _blockedUsers = valueGetSet();
  84. if (typeof _blockedUsers !== 'object') _blockedUsers = valueGetSet({});
  85. const blockedList = {
  86. get: id => _blockedUsers[`b${id}`],
  87. add: user => {
  88. _blockedUsers[`b${user.id}`] = {
  89. id: user.id,
  90. nick_name: user.nick_name,
  91. replurk: user.replurk ?? true,
  92. response: user.response ?? true,
  93. date: user.date ?? (new Date()).toUTCString()
  94. };
  95. valueGetSet(_blockedUsers);
  96. },
  97. remove: id => {
  98. delete _blockedUsers[`b${id}`];
  99. valueGetSet(_blockedUsers);
  100. },
  101. contains: user => {
  102. switch (typeof user) {
  103. case 'string':
  104. for (const u in _blockedUsers) {
  105. if (_blockedUsers[u].nick_name === user) return true;
  106. }
  107. break;
  108. case 'number':
  109. return !!_blockedUsers[`b${user}`];
  110. case 'object':
  111. return !!_blockedUsers[`b${user?.id}`];
  112. }
  113. return false;
  114. },
  115. forEach: callbackfn => {
  116. for (const i in _blockedUsers) {
  117. callbackfn(_blockedUsers[i], i, _blockedUsers);
  118. }
  119. },
  120. get length () { return Object.keys(_blockedUsers).length; }
  121. };
  122.  
  123. /* ============== */
  124. GM_addStyle(
  125. '.hide {display:none}' +
  126. '.item_holder .user_item.user_shadow_blocked_users_item .user_info {' +
  127. ' width: calc(100% - 190px);}' +
  128. '.friend_man.not_block {background-color:#999;}' +
  129. '.friend_man.not_block:hover {background-color:#207298}' +
  130. '.resp-hidden-show {background:#f5f5f9;color:#afb8cc;' +
  131. ' font-weight:normal;vertical-align:top;transform:scale(0.9);opacity:0;}' +
  132. '.resp-hidden-show.show {opacity:1}' +
  133. '.resp-hidden-show:not(.show) .onshow {display:none}' +
  134. '.resp-hidden-show.show .onhide {display:none}' +
  135. '.response-status:hover .resp-hidden-show {opacity:1}' +
  136. '.resp-hidden-show:hover {background:#afb8cc;color:#fff}'
  137. );
  138.  
  139. if (window.location.pathname === '/Friends/') {
  140. $('<li><a void="">' + lang.set_tab + '</a></li>').on('click', function () {
  141. window.history.pushState('', document.title, '/Friends/');
  142. $('#pop-window-tabs>ul>li').removeClass('current');
  143. this.classList.add('current');
  144. const $content = $('#pop-window-inner-content .content_inner').empty();
  145. $content.append(
  146. '<div class="note">' + lang.set_note + '</div>',
  147. '<div class="dashboard">' +
  148. ' <div class="search_box"><input>' +
  149. ' <button>' + lang.set_append + '</button></div>' +
  150. ' <div class="empty">' + lang.set_empty + '</div>' +
  151. '</div>');
  152. const $holder = $('<div class="item_holder"></div>').appendTo($content);
  153. if (blockedList.length) {
  154. $content.find('.dashboard .empty').addClass('hide');
  155. }
  156. blockedList.forEach(u => {
  157. plurklib.fetchUserInfo(u.id).then(info => {
  158. makeBlockedUserItem(info, $holder);
  159. }).catch(e => {
  160. console.info(`Cannot get info of "${u.nick_name}" (${e.message})`);
  161. makeBlockedUserItem(u, $holder);
  162. });
  163. });
  164. $content.find('.search_box>button').on('click', function () {
  165. const m = this.parentElement.children[0].value.match(/^[A-Za-z]\w+$/);
  166. if (m) {
  167. this.parentElement.children[0].value = '';
  168. $content.find('.dashboard .empty').addClass('hide');
  169. plurklib.fetchUserInfo(m[0]).then(info => {
  170. blockedList.add(info);
  171. makeBlockedUserItem(info, $holder);
  172. }).catch(e => {
  173. window.alert(`Unknown user "${m[0]}"`);
  174. });
  175. } else { window.alert(lang.set_alert); }
  176. });
  177. }).appendTo('#pop-window-tabs>ul');
  178. } else if (pageUserId === currUserId ||
  179. window.location.pathname.match(/^\/p\/[0-9a-z]+$/)) {
  180. makeButton($('#plurk_responses>.response_box'));
  181. makeButton($('#form_holder>.response_box'));
  182. makeButton($('#cbox_response>.response_box'));
  183. const po = new plurklib.PlurkObserver(prs => prs.forEach(pr => {
  184. pr.plurks.forEach(plurk => {
  185. if (blockedList.contains(plurk.owner_id)) {
  186. if (plurk.isResponse) {
  187. plurk.target.classList.add('shadow-block');
  188. const btn =
  189. pr.target.parentElement.querySelector('.resp-hidden-show');
  190. btn?.classList.remove('hide');
  191. if (blockedList.get(plurk.owner_id).response) {
  192. plurk.target.classList.add('hide');
  193. console.debug(`block #m${plurk.id}`);
  194. } else { btn?.classList.add('show'); }
  195. } else {
  196. plurk.target.classList.add('shadow-block', 'hide');
  197. console.debug(`block #p${plurk.id}`);
  198. }
  199. } else if (blockedList.get(plurk.replurker_id)?.replurk) {
  200. plurk.target.classList.add('shadow-block', 'hide');
  201. console.debug(`block #p${plurk.id}`);
  202. }
  203. });
  204. }));
  205. po.observe({ plurk: true });
  206. }
  207.  
  208. function makeButton ($responseBox) {
  209. if (!$responseBox.length) return;
  210. const $formBtn = $(
  211. '<div><span class="onshow">' + lang.resp_btn_hide + '</span>' +
  212. '<span class="onhide">' + lang.resp_btn_show + '</span></div>'
  213. );
  214. $formBtn.on('click', function () {
  215. $formBtn.toggleClass('show');
  216. const $blocks = $responseBox.children('.list').children('.shadow-block');
  217. if ($formBtn.hasClass('show')) $blocks.removeClass('hide');
  218. else $blocks.addClass('hide');
  219. }).addClass(['resp-hidden-show', 'button', 'small-button', 'hide'])
  220. .insertAfter($responseBox.find('.response-only-owner'));
  221. (new MutationObserver(mrs => mrs.forEach(mr => {
  222. if (!mr.target.querySelector('.handle-remove')) {
  223. $('<div class="handle-remove hide"></div>').prependTo(mr.target);
  224. }
  225. mr.removedNodes.forEach(node => {
  226. if (node.classList.contains('handle-remove')) {
  227. $formBtn.removeClass('show').addClass('hide').text(lang.resp_btn_show);
  228. }
  229. });
  230. }))).observe($responseBox.find('.list').first()[0], { childList: true });
  231. }
  232.  
  233. function makeBlockedUserItem (info, holder) {
  234. const user = blockedList.get(info.id);
  235. if (info.nick_name && info.nick_name !== user.nick_name) {
  236. user.nick_name = info.nick_name;
  237. blockedList.add(user);
  238. }
  239. blockedList.add(user);
  240. const $u = $('<div class="user_item user_shadow_blocked_users_item"></div>');
  241. const img = info.has_profile_image
  242. ? `https://avatars.plurk.com/${info.id}-medium${info.avatar ?? ''}.gif`
  243. : 'https://www.plurk.com/static/default_medium.jpg';
  244. $u.append([
  245. '<a class="user_avatar" target="_blank">',
  246. ` <img class="profile_pic" src="${img}"></img>`,
  247. '</a>',
  248. '<div class="user_info">',
  249. ' <a class="user_link" target="_blank"',
  250. ` style="color:#000">${info.display_name}</a>`,
  251. ` <span class="nick_name">@${info.nick_name}</span>`,
  252. ' <div class="more_info"><br></div>',
  253. '</div>',
  254. '<div class="user_action">',
  255. ` <a void="" data-switch="replurk" title="${lang.set_replurk}"`,
  256. ' class="friend_man icon_only pif-replurk',
  257. ` ${user.replurk ? 'has_block' : 'not_block'}"></a>`,
  258. ` <a void="" data-switch="response" title="${lang.set_response}"`,
  259. ' class="friend_man icon_only pif-message',
  260. ` ${user.response ? 'has_block' : 'not_block'}"></a>`,
  261. ` <a void="" data-remove="1" title="${lang.set_remove}"`,
  262. ' class="friend_man icon_only pif-user-blocked has_block"></a>',
  263. '</div>'
  264. ].join(''));
  265. $u.find('a:not(.icon_only)').attr('href', '/' + info.nick_name);
  266. $u.find('a.icon_only').on('click', function () {
  267. if (this.dataset.switch) {
  268. user[this.dataset.switch] = !user[this.dataset.switch];
  269. if (user[this.dataset.switch]) {
  270. this.classList.add('has_block');
  271. this.classList.remove('not_block');
  272. } else {
  273. this.classList.remove('has_block');
  274. this.classList.add('not_block');
  275. }
  276. blockedList.add(user);
  277. }
  278. if (this.dataset.remove) {
  279. blockedList.remove(user.id);
  280. $u.remove();
  281. }
  282. });
  283. $u.appendTo(holder);
  284. }
  285. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址