Greasy Fork 还支持 简体中文。

Backloggd Utility with Settings

Adds customizable buttons to backloggd.

目前為 2024-03-27 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Backloggd Utility with Settings
  3. // @namespace http://vers.works/
  4. // @version 1.0
  5. // @icon https://pbs.twimg.com/profile_images/1541908760607821824/3Am5dmsx_400x400.jpg
  6. // @description Adds customizable buttons to backloggd.
  7. // @author VERS
  8. // @match https://www.backloggd.com/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. const whitelist = ['Steam', 'SteamDB', 'SteamGridDB', 'Epic Games Store', 'GOG', 'Twitch', 'Youtube', 'XBOX', 'Playstation', 'Nintendo',];
  18.  
  19. let buttons = GM_getValue('buttons', [
  20. {
  21. name: 'Steam',
  22. iconUrl: 'https://i.imgur.com/9NFBdz4.png',
  23. searchUrl: 'https://store.steampowered.com/search/?term=',
  24. color: '#1a9fff',
  25. status: true
  26. },
  27. {
  28. name: 'SteamDB',
  29. iconUrl: 'https://i.imgur.com/t6rV9xA.png',
  30. searchUrl: 'https://steamdb.info/search/?a=all&q=',
  31. color: '#3e76cb',
  32. status: true
  33. },
  34. {
  35. name: 'SteamGridDB',
  36. iconUrl: 'https://i.imgur.com/oluhFHS.png',
  37. searchUrl: 'https://www.steamgriddb.com/search/grids?term=',
  38. color: '#3a6e92',
  39. status: true
  40. },
  41. {
  42. name: 'Epic Games Store',
  43. iconUrl: 'https://cdn2.unrealengine.com/Unreal+Engine%2Feg-logo-filled-1255x1272-0eb9d144a0f981d1cbaaa1eb957de7a3207b31bb.png',
  44. searchUrl: 'https://store.epicgames.com/en-US/expanded-search-results?q=',
  45. color: '#000000',
  46. status: true
  47. },
  48. {
  49. name: 'GOG',
  50. iconUrl: 'https://yt3.googleusercontent.com/wr5HXJvdt0_B0bFgxaO5Fczjl3wsofggnPGKRuLe8AWkvyRUnedkjJIKQGmztxD-Xue8qiBC=s900-c-k-c0x00ffffff-no-rj',
  51. searchUrl: 'https://www.gog.com/en/games?query=',
  52. color: '#ffffff',
  53. status: true
  54. },
  55. {
  56. name: 'Twitch',
  57. iconUrl: 'https://i.imgur.com/UVuf0iF.png',
  58. searchUrl: 'https://www.twitch.tv/search?term=',
  59. color: '#9046fd',
  60. status: true
  61. },
  62. {
  63. name: 'Youtube',
  64. iconUrl: 'https://i.imgur.com/C0Ux2Y3.png',
  65. searchUrl: 'https://www.youtube.com/results?search_query=',
  66. color: '#ff0000',
  67. status: true
  68. },
  69. {
  70. name: 'XBOX',
  71. iconUrl: 'https://i.imgur.com/jrItCUM.png',
  72. searchUrl: 'https://www.xbox.com/Search/Results?q=',
  73. color: '#107b10',
  74. status: true
  75. },
  76. {
  77. name: 'Playstation',
  78. iconUrl: 'https://i.imgur.com/wvB5DF8.png',
  79. searchUrl: 'https://www.playstation.com/search/?q=',
  80. color: '#0070d1',
  81. status: true
  82. },
  83. {
  84. name: 'Nintendo',
  85. iconUrl: 'https://i.imgur.com/7cGs7D6.png',
  86. searchUrl: 'https://www.nintendo.com/us/search/#q=',
  87. color: '#e70819',
  88. status: true
  89. },
  90. {
  91. name: 'Settings',
  92. iconUrl: 'https://i.imgur.com/WvM8EHQ.png',
  93. color: '#16181c',
  94. status: true,
  95. isSettings: true
  96. },
  97. {
  98. name: 'Remove Button',
  99. iconUrl: 'https://via.placeholder.com/24',
  100. color: '#ff0000',
  101. status: true,
  102. isSettings: true,
  103. isRemovable: false
  104. }
  105. ]);
  106.  
  107. function addButton(iconUrl, searchUrl, color, status, name) {
  108. if (!status) {
  109. return;
  110. }
  111.  
  112. const button = document.createElement('button');
  113. button.className = 'btn btn-main journal-btn custom-button';
  114. button.style.width = '34px';
  115. button.style.height = '34px';
  116. button.style.margin = '7px 4px 3px 4px';
  117. button.style.backgroundColor = color;
  118. button.style.border = 'none';
  119. button.style.display = 'inline-flex';
  120. button.style.justifyContent = 'center';
  121. button.style.alignItems = 'center';
  122.  
  123. const icon = document.createElement('img');
  124. icon.src = iconUrl;
  125. icon.alt = 'Search';
  126. icon.style.width = '24px';
  127. icon.style.height = '24px';
  128. button.appendChild(icon);
  129.  
  130. button.addEventListener('click', function() {
  131. if (name === 'Settings') {
  132. openSettingsModal();
  133. } else {
  134. const gameTitleElement = document.querySelector('#title h1');
  135. if (gameTitleElement) {
  136. const gameTitle = encodeURIComponent(gameTitleElement.textContent.trim());
  137. const searchLink = searchUrl + gameTitle;
  138. window.open(searchLink, '_blank');
  139. } else {
  140. console.error('Game title element not found');
  141. }
  142. }
  143. });
  144.  
  145. const journalButtonContainer = document.querySelector('.journal-button-container');
  146. if (journalButtonContainer) {
  147. const existingButton = journalButtonContainer.querySelector(`.custom-button[data-search="${searchUrl}"]`);
  148. if (!existingButton) {
  149. button.setAttribute('data-search', searchUrl);
  150. button.setAttribute('data-name', name);
  151. journalButtonContainer.appendChild(button);
  152. }
  153. } else {
  154. console.error('Journal button container not found');
  155. }
  156. }
  157.  
  158. function addSpacer() {
  159. const spacer = document.createElement('div');
  160. spacer.style.height = '6px';
  161. const journalButtonContainer = document.querySelector('.journal-button-container');
  162. if (journalButtonContainer) {
  163. journalButtonContainer.insertBefore(spacer, journalButtonContainer.firstChild);
  164. }
  165. }
  166.  
  167. function updateButtons() {
  168. buttons.forEach(button => {
  169. addButton(button.iconUrl, button.searchUrl, button.color, button.status, button.name);
  170. });
  171. }
  172.  
  173. function openSettingsModal() {
  174. const settingsModal = document.createElement('div');
  175. settingsModal.className = 'settings-modal';
  176. settingsModal.style.position = 'fixed';
  177. settingsModal.style.top = '50%';
  178. settingsModal.style.left = '50%';
  179. settingsModal.style.transform = 'translate(-50%, -50%)';
  180. settingsModal.style.backgroundColor = '#16181c';
  181. settingsModal.style.padding = '20px';
  182. settingsModal.style.zIndex = '9999';
  183. settingsModal.innerHTML = `
  184. <h2>Button Settings</h2>
  185. <ul>
  186. ${buttons
  187. .filter(button => !button.isSettings && button.name !== 'Remove Button')
  188. .map(
  189. button =>
  190. `<li>
  191. <label>
  192. <input type="checkbox" id="${button.name}-checkbox" ${
  193. button.status ? 'checked' : ''
  194. }>
  195. ${button.name}
  196. </label>
  197. </li>`
  198. )
  199. .join('')}
  200. </ul>
  201. <button id="add-button" style="background-color: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer;">Add Custom Button</button>
  202. <button id="remove-button" style="background-color: #ffcccc; border: none; color: #000; padding: 8px 16px; cursor: pointer;">Remove Custom Button</button>
  203. <button id="save-settings-btn" style="background-color: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer;">Save Settings</button>
  204. `;
  205.  
  206. document.body.appendChild(settingsModal);
  207.  
  208. const addButtonBtn = document.getElementById('add-button');
  209. addButtonBtn.addEventListener('click', () => {
  210. openAddButtonModal();
  211. });
  212.  
  213. const removeButtonBtn = document.getElementById('remove-button');
  214. removeButtonBtn.addEventListener('click', () => {
  215. openRemoveButtonModal();
  216. });
  217.  
  218. const saveSettingsBtn = document.getElementById('save-settings-btn');
  219. saveSettingsBtn.addEventListener('click', () => {
  220. buttons.forEach(button => {
  221. if (!button.isSettings) {
  222. const checkbox = document.getElementById(`${button.name}-checkbox`);
  223. button.status = checkbox.checked;
  224. }
  225. });
  226.  
  227. GM_setValue('buttons', buttons);
  228.  
  229. window.location.reload();
  230. });
  231. }
  232.  
  233. function openRemoveButtonModal() {
  234. const removeButtonModal = document.createElement('div');
  235. removeButtonModal.className = 'remove-button-modal';
  236. removeButtonModal.style.position = 'fixed';
  237. removeButtonModal.style.top = '50%';
  238. removeButtonModal.style.left = '50%';
  239. removeButtonModal.style.transform = 'translate(-50%, -50%)';
  240. removeButtonModal.style.backgroundColor = '#010101';
  241. removeButtonModal.style.padding = '20px';
  242. removeButtonModal.style.zIndex = '9999';
  243. removeButtonModal.innerHTML = `
  244. <h2>Remove Button</h2>
  245. <ul>
  246. ${buttons
  247. .filter(button => !button.isSettings && !whitelist.includes(button.name))
  248. .map(
  249. button =>
  250. `<li>
  251. ${button.name}
  252. <button id="remove-${button.name}" style="margin-left: 10px; background-color: #ffcccc; border: none; color: #000; padding: 5px 10px; cursor: pointer;">x</button>
  253. </li>`
  254. )
  255. .join('')}
  256. </ul>
  257. <button id="close-remove-modal" style="background-color: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer; margin-top: 10px;">Close</button>
  258. `;
  259.  
  260. document.body.appendChild(removeButtonModal);
  261.  
  262. buttons
  263. .filter(button => !button.isSettings && !whitelist.includes(button.name))
  264. .forEach(button => {
  265. const removeButton = document.getElementById(`remove-${button.name}`);
  266. removeButton.addEventListener('click', () => {
  267. // Remove button logic
  268. removeButtonFromList(button.name);
  269. removeButton.parentElement.remove();
  270. });
  271. });
  272.  
  273. const closeModalBtn = document.getElementById('close-remove-modal');
  274. closeModalBtn.addEventListener('click', () => {
  275. removeButtonModal.remove();
  276. });
  277. }
  278.  
  279. function removeButtonFromList(buttonName) {
  280. buttons = buttons.filter(button => button.name !== buttonName);
  281. GM_setValue('buttons', buttons);
  282.  
  283. const existingButtons = document.querySelectorAll('.custom-button');
  284. existingButtons.forEach(button => {
  285. button.remove();
  286. });
  287.  
  288. addSpacer();
  289. updateButtons();
  290. }
  291.  
  292. function openAddButtonModal() {
  293. const addButtonModal = document.createElement('div');
  294. addButtonModal.className = 'add-button-modal';
  295. addButtonModal.style.position = 'fixed';
  296. addButtonModal.style.top = '50%';
  297. addButtonModal.style.left = '50%';
  298. addButtonModal.style.transform = 'translate(-50%, -50%)';
  299. addButtonModal.style.backgroundColor = '#010101';
  300. addButtonModal.style.padding = '20px';
  301. addButtonModal.style.zIndex = '9999';
  302. addButtonModal.innerHTML = `
  303. <h2>Add New Button</h2>
  304. <div>
  305. <label for="website-name">Website Name:</label>
  306. <input type="text" id="website-name" required>
  307. </div>
  308. <div>
  309. <label for="icon-url">Icon URL:</label>
  310. <input type="text" id="icon-url" required>
  311. </div>
  312. <div>
  313. <label for="search-url">Search URL:</label>
  314. <input type="text" id="search-url" required>
  315. </div>
  316. <div>
  317. <label for="color">Button Color:</label>
  318. <input type="color" id="color" value="#1a9fff">
  319. </div>
  320. <button id="save-new-button" style="background-color: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer; margin-top: 10px;">Save Button</button>
  321. `;
  322.  
  323. document.body.appendChild(addButtonModal);
  324.  
  325. const saveNewButtonBtn = document.getElementById('save-new-button');
  326. saveNewButtonBtn.addEventListener('click', () => {
  327. const websiteNameInput = document.getElementById('website-name').value;
  328. const iconUrlInput = document.getElementById('icon-url').value;
  329. const searchUrlInput = document.getElementById('search-url').value;
  330. const colorInput = document.getElementById('color').value;
  331.  
  332. const newButton = {
  333. name: websiteNameInput,
  334. iconUrl: iconUrlInput,
  335. searchUrl: searchUrlInput,
  336. color: colorInput,
  337. status: true
  338. };
  339.  
  340. buttons.push(newButton);
  341. GM_setValue('buttons', buttons);
  342.  
  343. addButton(newButton.iconUrl, newButton.searchUrl, newButton.color, newButton.status, newButton.name);
  344.  
  345. window.location.reload();
  346. });
  347. }
  348.  
  349. function waitForElement(selector, callback) {
  350. const interval = setInterval(() => {
  351. const element = document.querySelector(selector);
  352. if (element) {
  353. clearInterval(interval);
  354. callback(element);
  355. }
  356. }, 100);
  357. }
  358.  
  359.  
  360. function addBadgeToElement(element) {
  361. const badge = document.createElement('span');
  362. badge.className = 'vers-badge';
  363. badge.textContent = 'SCRIPT\nDEV'; // Two words split into two lines
  364. badge.style.backgroundColor = '#1aba7c';
  365. badge.style.color = '#16181c';
  366. badge.style.fontSize = '.7rem';
  367. badge.style.borderRadius = '4px';
  368. badge.style.fontWeight = '600';
  369. badge.style.padding = '3px';
  370. badge.style.marginLeft = '5px'; // Adjust margin as needed
  371.  
  372. // Check if the element is a <p>, set display to inline-block
  373. if (element.tagName.toLowerCase() === 'p') {
  374. badge.textContent = 'SCRIPTDEV'; // Two words split into two lines
  375. }
  376.  
  377. // Insert badge after the text node
  378. const textNode = element.firstChild;
  379. element.insertBefore(badge, textNode.nextSibling);
  380. }
  381.  
  382. function processElements() {
  383. console.log('Processing elements...');
  384. const h3Elements = document.querySelectorAll('div.row > div.col-auto > h3.main-header');
  385. const pElements = document.querySelectorAll('div.col > div.row.mb-1 > a > div.col-auto > p.mb-0');
  386.  
  387. h3Elements.forEach(element => {
  388. if (element.textContent.trim() === 'VERS' && !element.classList.contains('vers-badge')) {
  389. console.log('Adding badge to h3 element:', element);
  390. addBadgeToElement(element);
  391. }
  392. });
  393.  
  394. pElements.forEach(element => {
  395. if (element.textContent.trim() === 'VERS' && !element.classList.contains('vers-badge')) {
  396. console.log('Adding badge to p element:', element);
  397. addBadgeToElement(element);
  398. }
  399. });
  400. }
  401.  
  402. // Initial processing on page load
  403. processElements();
  404.  
  405. // Mutation Observer to handle dynamically added content
  406. const observer = new MutationObserver(mutations => {
  407. mutations.forEach(mutation => {
  408. if (mutation.type === 'childList') {
  409. const addedNodes = Array.from(mutation.addedNodes);
  410. addedNodes.forEach(node => {
  411. if (node.nodeType === Node.ELEMENT_NODE) {
  412. processElements();
  413. }
  414. });
  415. }
  416. });
  417. });
  418.  
  419. // Observe changes to the body and its subtree
  420. observer.observe(document.body, {
  421. childList: true,
  422. subtree: true
  423. });
  424.  
  425. addSpacer();
  426. updateButtons();
  427.  
  428. waitForElement('#title h1', (element) => {
  429. const observer = new MutationObserver(function(mutationsList) {
  430. for (let mutation of mutationsList) {
  431. if (mutation.type === 'childList' && mutation.target.nodeName === 'HTML') {
  432. const existingButtons = document.querySelectorAll('.custom-button');
  433. existingButtons.forEach(button => {
  434. button.remove();
  435. });
  436. addSpacer();
  437. updateButtons();
  438. }
  439. }
  440. });
  441.  
  442. observer.observe(document, { childList: true, subtree: true });
  443. });
  444.  
  445. window.onhashchange = function() {
  446. const existingButtons = document.querySelectorAll('.custom-button');
  447. existingButtons.forEach(button => {
  448. button.remove();
  449. });
  450. updateButtons();
  451. };
  452. })();