B++

Remove the random recommendations, bottom bar, sidebar, microphone, and search optimization from the Bing search page. Remove the website logo and switch to a dual-column search result layout. Automatically redirect to the correct Baidu Tieba page. 移除必应搜索页面莫名其妙的推荐、底部栏、侧边栏、麦克风、优化搜索等,去除网页logo,改成双列搜索结果,百度贴吧自动正确跳转

  1. // ==UserScript==
  2. // @name B++
  3. // @name:zh-CN B艹:必应搜索页面大修
  4. // @name:en B++: Bing Search Page Overhaul
  5. // @namespace Bing Plus Plus
  6. // @version 3.0
  7. // @description:zh-CN 移除必应搜索页面大量元素,去除网页 logo,改成双列瀑布流结果,百度贴吧自动正确跳转,自动连续到下一页
  8. // @description:en Remove a large number of elements on the Bing search page, remove the webpage logo, change to a two-column waterfall layout for the results, ensure Baidu Tieba automatically redirects correctly, and automatically continue to the next page. (Performance optimized)
  9. // @author Yog-Sothoth
  10. // @match https://*.bing.com/search*
  11. // @grant GM_addStyle
  12. // @license MIT
  13. // @description Remove the random recommendations, bottom bar, sidebar, microphone, and search optimization from the Bing search page. Remove the website logo and switch to a dual-column search result layout. Automatically redirect to the correct Baidu Tieba page. 移除必应搜索页面莫名其妙的推荐、底部栏、侧边栏、麦克风、优化搜索等,去除网页logo,改成双列搜索结果,百度贴吧自动正确跳转
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. function throttle(func, limit) {
  20. let lastRan;
  21. let lastTimer;
  22. return function() {
  23. const context = this;
  24. const args = arguments;
  25. if (!lastRan) {
  26. func.apply(context, args);
  27. lastRan = Date.now();
  28. } else {
  29. clearTimeout(lastTimer);
  30. lastTimer = setTimeout(function() {
  31. if ((Date.now() - lastRan) >= limit) {
  32. func.apply(context, args);
  33. lastRan = Date.now();
  34. }
  35. }, limit - (Date.now() - lastRan));
  36. }
  37. };
  38. }
  39.  
  40. function redirectTowww4IfNeeded() {
  41. const urlObj = new URL(window.location.href);
  42. if (/^(cn\.)/.test(urlObj.hostname)){
  43. window.location.replace(`https://www4.${urlObj.hostname.replace(/^(cn\.)/, '')}${urlObj.pathname}${urlObj.search}`);
  44. }
  45. }
  46.  
  47. redirectTowww4IfNeeded();
  48.  
  49. function removeElement(selector, context = document) {
  50. const elements = context.querySelectorAll(selector);
  51. elements.forEach(element => element.remove());
  52. }
  53.  
  54. function replace(context = document) {
  55. const bContent = context.getElementById('b_content') || context;
  56. let as = bContent.querySelectorAll('.b_algo h2 a');
  57. let as2 = bContent.querySelectorAll('.b_algo .b_tpcn .tilk');
  58.  
  59. if (as.length === 0 || as2.length === 0 || as.length !== as2.length) {
  60. return;
  61. }
  62.  
  63. for (let i = 0; i < as.length; i++) {
  64. let url = as[i]?.getAttribute('href');
  65. if (url) {
  66. let new_url = url.replace(/jump2\.bdimg|jump\.bdimg/, 'tieba.baidu');
  67. as[i].setAttribute('href', new_url);
  68. as2[i].setAttribute('href', new_url);
  69. }
  70. }
  71. }
  72.  
  73. const css = `
  74. #b_context {
  75. display: none !important;
  76. width: 0 !important;
  77. min-width: 0 !important;
  78. max-width: 0 !important;
  79. }
  80.  
  81. #b_content {
  82. padding: 0 !important;
  83. margin-top: 40px !important;
  84. width: 100% !important;
  85. max-width: none !important;
  86. }
  87.  
  88. #b_results {
  89. display: flex !important;
  90. flex-wrap: wrap !important;
  91. width: 100% !important;
  92. margin: 0 !important;
  93. padding: 5px 30px 30px 30px !important;
  94. box-sizing: border-box !important;
  95. }
  96.  
  97. #b_content #b_results > li.b_algo {
  98. box-sizing: border-box !important;
  99. width: 47.5% !important;
  100. margin-right: 3% !important;
  101. margin-bottom: 30px !important;
  102. float: none !important;
  103. list-style: none !important;
  104. margin-left: 0 !important;
  105. padding-left: 0 !important;
  106. min-height: 1px !important;
  107. }
  108.  
  109. #b_content #b_results > li.b_algo:nth-child(2n) {
  110. margin-right: 0 !important;
  111. }
  112.  
  113. .b_pag,
  114. .b_ans {
  115. width: 100% !important;
  116. margin-right: 0 !important;
  117. order: 9999;
  118. }
  119.  
  120. #b_results .ContentItem {
  121. display: inline-flex !important;
  122. flex-wrap: wrap !important;
  123. width: 100% !important;
  124. }
  125. #b_results .MainContent_Sub_Left_MainContent {
  126. max-width: 100% !important;
  127. }
  128. `;
  129. GM_addStyle(css);
  130.  
  131. const elementsToRemove = [
  132. '.b_ans', '.b_pag', '.b_ans .b_mop', '.b_vidAns', '.b_rc_gb_sub.b_rc_gb_sub_section',
  133. '.b_rc_gb_scroll', '.b_msg', '.b_footer', '.b_phead_sh_link',
  134. '.b_sh_btn-io', '#id_mobile', '[aria-label="更多结果"]', '.b_algoRCAggreFC',
  135. '.b_factrow b_twofr', '[id^="mic_"]', '[class="tpic"]',
  136. '[class="b_vlist2col b_deep"]', '[class="b_deep b_moreLink "]',
  137. '.b_algo b_vtl_deeplinks', '[class="tab-head HeroTab"]',
  138. '[class="tab-menu tab-flex"]', '[class="b_deepdesk"]',
  139. '[class^="b_algo b_algoBorder b_rc_gb_template b_rc_gb_template_bg_"]',
  140. '[class="sc_rf"]', '[class="b_algospacing"]', '#b_pole',
  141. '.b_caption.b_rich', '.b_ad.b_adMiddle', '.b_ad.b_adBottom',
  142. '.b_imagePair.wide_wideAlgo .inner', '.b_imagePair.wide_wideAlgo[rel="dns-prefetch"]','[class="b_results_eml"]','[class="b_slidebar"]','[class="b_inline_ajax_rs"]','[class="b_ad b_adTop"]',
  143. '#b_context', '.b_logo'
  144. ];
  145.  
  146. function updateClassForBAlgoElements() {
  147. const bContent = document.getElementById('b_results');
  148. if (bContent) {
  149. for (let i = 0; i < bContent.children.length; i++) {
  150. const element = bContent.children[i];
  151. if (element.classList.contains('b_algo') && element.classList.contains('b_rc_gb_template')) {
  152. element.classList.remove(...[...element.classList].filter(cls => cls.startsWith('b_rc_gb_template_bg_')));
  153. if (!element.classList.contains('b_algo')) {
  154. element.classList.add('b_algo');
  155. }
  156. }
  157. }
  158. }
  159. }
  160.  
  161. (function cleanCurrentUrl() {
  162. const urlObj = new URL(window.location.href);
  163. const params = urlObj.searchParams;
  164. const keepParams = new Set(['q', 'first']);
  165.  
  166. for (const key of [...params.keys()]) {
  167. if (!keepParams.has(key)) {
  168. params.delete(key);
  169. }
  170. }
  171.  
  172. const newUrl = urlObj.origin + urlObj.pathname + urlObj.search;
  173. window.history.replaceState(null, '', newUrl);
  174. })();
  175.  
  176. function processBingSearchPage() {
  177. const urlParams = new URLSearchParams(window.location.search);
  178. let first = parseInt(urlParams.get('first'), 10) || 1;
  179. const query = urlParams.get('q');
  180. const resultsContainer = document.getElementById('b_results');
  181.  
  182. if (!query || !resultsContainer) {
  183. return;
  184. }
  185.  
  186. let isFetching = false;
  187.  
  188. if (first === 1) {
  189. first = 11;
  190. } else {
  191. first += 10;
  192. }
  193.  
  194. const baseUrl = `${window.location.origin}${window.location.pathname}`;
  195.  
  196. const throttledScrollHandler = throttle(() => {
  197. if (!isFetching && (window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 2400)) {
  198. isFetching = true;
  199.  
  200. fetchResults(first).then(() => {
  201. first += 10;
  202. isFetching = false;
  203. }).catch(() => {
  204. isFetching = false;
  205. });
  206. }
  207. }, 300);
  208.  
  209. window.addEventListener('scroll', throttledScrollHandler);
  210.  
  211. function fetchResults(pageFirst) {
  212. const searchParams = new URLSearchParams();
  213. searchParams.set('q', query);
  214. searchParams.set('first', pageFirst);
  215.  
  216. urlParams.forEach((value, key) => {
  217. if (key !== 'q' && key !== 'first') {
  218. searchParams.set(key, value);
  219. }
  220. });
  221.  
  222. return fetch(`${baseUrl}?${searchParams.toString()}`)
  223. .then(response => {
  224. if (!response.ok) {
  225. throw new Error('Network response was not ok');
  226. }
  227. return response.text();
  228. })
  229. .then(data => {
  230. const parser = new DOMParser();
  231. const doc = parser.parseFromString(data, 'text/html');
  232. const newResultsContainer = doc.getElementById('b_results');
  233. if (!newResultsContainer) return;
  234.  
  235. const newResults = newResultsContainer.querySelectorAll('.b_algo');
  236. if (newResults.length === 0) return;
  237.  
  238. const fragment = document.createDocumentFragment();
  239.  
  240. newResults.forEach(result => {
  241. const cloned = result.cloneNode(true);
  242. cloned.style.opacity = '0';
  243. cloned.style.transition = 'opacity 0.5s ease';
  244. fragment.appendChild(cloned);
  245. });
  246.  
  247. const anchorSelector = 'style[data-bm="15"]';
  248. const targetAnchor = document.body.querySelector(anchorSelector);
  249.  
  250. if (targetAnchor) {
  251. targetAnchor.before(fragment);
  252. } else {
  253. resultsContainer.appendChild(fragment);
  254. }
  255.  
  256. document.body.querySelectorAll('.b_algo[style*="opacity: 0"]').forEach(cloned => {
  257. requestAnimationFrame(() => {
  258. cloned.style.opacity = '1';
  259. });
  260. });
  261. })
  262. .catch(error => {
  263. throw error;
  264. });
  265. }
  266. }
  267.  
  268. function cleanWideWideAlgo(context = document) {
  269. const captionCards = context.querySelectorAll('.captionMediaCard');
  270. captionCards.forEach(card => {
  271. const wideElements = card.querySelectorAll('.b_imagePair.wide_wideAlgo');
  272. wideElements.forEach(elem => {
  273. elem.classList.remove('wide_wideAlgo');
  274. });
  275. });
  276. }
  277.  
  278. updateClassForBAlgoElements();
  279. elementsToRemove.forEach(selector => removeElement(selector, document));
  280. replace();
  281. processBingSearchPage();
  282. cleanWideWideAlgo();
  283.  
  284. const observer = new MutationObserver(mutations => {
  285. elementsToRemove.forEach(selector => removeElement(selector, document));
  286.  
  287. mutations.forEach(mutation => {
  288. mutation.addedNodes.forEach(node => {
  289. if (node.nodeType === 1) {
  290. cleanWideWideAlgo(node);
  291. replace(node);
  292. }
  293. });
  294. });
  295. });
  296. observer.observe(document.body, { childList: true, subtree: true });
  297.  
  298. const _pushState = window.history.pushState;
  299. window.history.pushState = function () {
  300. replace();
  301. return _pushState.apply(this, arguments);
  302. };
  303. })();
  304.  
  305. // 新增的 Bing URL 解码功能
  306. (async function() {
  307. 'use strict';
  308. let throttle_callback;
  309.  
  310. function throttle(callback, limit) {
  311. return function () {
  312. const context = this, args = arguments;
  313. clearTimeout(throttle_callback);
  314. throttle_callback = setTimeout(function () {
  315. callback.apply(context, args);
  316. }, limit);
  317. };
  318. }
  319.  
  320. function decodeUtf8Base64Url(encodedUrl) {
  321. const bytes = Uint8Array.from(atob(encodedUrl), c => c.charCodeAt(0));
  322. return new TextDecoder().decode(bytes);
  323. }
  324.  
  325. if (/https?:\/\/(?:[\w]+\.)?bing\.com\//.test(location.href)) {
  326.  
  327. const observer = new MutationObserver(throttle((mutations, obs) => {
  328. document.querySelectorAll('[href^="https://www.bing.com/ck/a"]').forEach(element => {
  329. const match = element.href.match(/&u=([^&]+)/);
  330. const encodedUrl = match[1].slice(2);
  331. if (match && /^[A-Za-z0-9=_-]+$/.test(encodedUrl)) {
  332. try {
  333. const decodedUrl = decodeUtf8Base64Url(encodedUrl
  334. .replace(/_/g, "/")
  335. .replace(/-/g, "+"));
  336. element.href = decodedUrl;
  337. } catch (e) {
  338. console.info('Bing URL Decode Error:', encodedUrl);
  339. }
  340. }
  341. });
  342. }, 2));
  343.  
  344. const config = { childList: true, subtree: true };
  345. observer.observe(document.body, config);
  346. }
  347. })();