Backloggd Button Utility

Adds customizable buttons to backloggd.

当前为 2024-03-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Backloggd Button Utility
  3. // @namespace http://vers.works/
  4. // @version 2.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.includes('%s') ? searchUrl.replace('%s', gameTitle) : 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 = 'rgba(22, 24, 28, 0.95)';
  181. settingsModal.style.borderRadius = '10px';
  182. settingsModal.style.padding = '20px';
  183. settingsModal.style.zIndex = '9999';
  184. settingsModal.innerHTML = `
  185. <h1>Backloggd Button Utility</h1>
  186. <p style="display: flex; align-items: center;">Script made by VERS.
  187. <a href="https://www.backloggd.com/u/VERS/" target="_blank">
  188. <img src="https://i.imgur.com/4FLnRC9.png" alt="Icon 1" style="margin-left: 10px;; width: 19px; height: 19px;">
  189. </a>
  190. <a href="https://twitter.com/versworks" target="_blank">
  191. <img src="https://i.imgur.com/yXPQwtv.png" alt="Icon 2" style="margin-left: 5px;; width: 19px; height: 19px;">
  192. </a>
  193. <a href="https://http://vers.works/" target="_blank">
  194. <img src="https://i.imgur.com/oePEdTt.png" alt="Icon 3" style="margin-left: 5px;; width: 19px; height: 19px;">
  195. </a>
  196. </p>
  197. <p style="font-size: 12px; font-style: italic; text-align: center;">
  198. If you have any issues or feedback you can contact me on discord (The_Vers) <br>or in this <a href="https://www.google.com" target="_blank">reddit post</a>.
  199. </p>
  200. <h2>Button Settings</h2>
  201. <ul>
  202. ${buttons
  203. .filter(button => !button.isSettings && button.name !== 'Remove Button')
  204. .map(
  205. button =>
  206. `<li>
  207. <label>
  208. <input type="checkbox" id="${button.name}-checkbox" ${
  209. button.status ? 'checked' : ''
  210. }>
  211. ${button.name}
  212. </label>
  213. </li>`
  214. )
  215. .join('')}
  216. </ul>
  217. <button id="add-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Add Custom Button</button>
  218. <button id="remove-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Remove Custom Button</button>
  219. <button id="save-settings-btn" style="background-color: #fc6399; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Save Settings</button>
  220. `;
  221.  
  222. document.body.appendChild(settingsModal);
  223.  
  224. const addButtonBtn = document.getElementById('add-button');
  225. addButtonBtn.addEventListener('click', () => {
  226. openAddButtonModal();
  227. });
  228.  
  229. const removeButtonBtn = document.getElementById('remove-button');
  230. removeButtonBtn.addEventListener('click', () => {
  231. openRemoveButtonModal();
  232. });
  233.  
  234. const saveSettingsBtn = document.getElementById('save-settings-btn');
  235. saveSettingsBtn.addEventListener('click', () => {
  236. buttons.forEach(button => {
  237. if (!button.isSettings) {
  238. const checkbox = document.getElementById(`${button.name}-checkbox`);
  239. button.status = checkbox.checked;
  240. }
  241. });
  242.  
  243. GM_setValue('buttons', buttons);
  244.  
  245. window.location.reload();
  246. });
  247. }
  248.  
  249.  
  250.  
  251. function openRemoveButtonModal() {
  252. const removeButtonModal = document.createElement('div');
  253. removeButtonModal.className = 'remove-button-modal';
  254. removeButtonModal.style.position = 'fixed';
  255. removeButtonModal.style.top = '50%';
  256. removeButtonModal.style.borderRadius = '10px';
  257. removeButtonModal.style.left = '50%';
  258. removeButtonModal.style.transform = 'translate(-50%, -50%)';
  259. removeButtonModal.style.backgroundColor = '#242832';
  260. removeButtonModal.style.padding = '20px';
  261. removeButtonModal.style.zIndex = '9999';
  262. removeButtonModal.innerHTML = `
  263. <h2>Remove Button</h2>
  264. <ul>
  265. ${buttons
  266. .filter(button => !button.isSettings && !whitelist.includes(button.name))
  267. .map(
  268. button =>
  269. `<li>
  270. <button id="remove-${button.name}" style="background-color: #fc6399; border: none; color: #ffffff; padding: 5px 5; cursor: pointer;">✖</button>
  271. <span style="margin-left: 10px;">${button.name}</span>
  272. </li>`
  273. )
  274. .join('')}
  275. </ul>
  276. <button id="close-remove-modal" style="background-color: #4a5e8d; border: none; color: #ffffff; padding: 8px 16px; cursor: pointer; margin-top: 10px;">Close</button>
  277. `;
  278.  
  279. document.body.appendChild(removeButtonModal);
  280.  
  281. buttons
  282. .filter(button => !button.isSettings && !whitelist.includes(button.name))
  283. .forEach(button => {
  284. const removeButton = document.getElementById(`remove-${button.name}`);
  285. removeButton.addEventListener('click', () => {
  286. removeButtonFromList(button.name);
  287. removeButton.parentElement.remove();
  288. });
  289. });
  290.  
  291. const closeModalBtn = document.getElementById('close-remove-modal');
  292. closeModalBtn.addEventListener('click', () => {
  293. removeButtonModal.remove();
  294. });
  295. }
  296.  
  297. function removeButtonFromList(buttonName) {
  298. buttons = buttons.filter(button => button.name !== buttonName);
  299. GM_setValue('buttons', buttons);
  300.  
  301. const existingButtons = document.querySelectorAll('.custom-button');
  302. existingButtons.forEach(button => {
  303. button.remove();
  304. });
  305.  
  306. addSpacer();
  307. updateButtons();
  308. }
  309.  
  310. function openAddButtonModal() {
  311. const addButtonModal = document.createElement('div');
  312. addButtonModal.className = 'add-button-modal';
  313. addButtonModal.style.position = 'fixed';
  314. addButtonModal.style.top = '50%';
  315. addButtonModal.style.left = '50%';
  316. addButtonModal.style.borderRadius = '10px';
  317. addButtonModal.style.transform = 'translate(-50%, -50%)';
  318. addButtonModal.style.backgroundColor = '#242832';
  319. addButtonModal.style.padding = '20px';
  320. addButtonModal.style.zIndex = '9999';
  321. addButtonModal.innerHTML = `
  322. <h2>Add Custom Button</h2>
  323. <div>
  324. <label for="website-name" style="width: 120px;">Website Name:</label>
  325. <button id="info-website-name" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  326. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  327. </button>
  328. <input type="text" id="website-name" style="width: 200px;" required>
  329. </div>
  330. <div>
  331. <label for="icon-url" style="width: 120px;">Icon URL:</label>
  332. <button id="info-icon-url" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  333. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  334. </button>
  335. <input type="text" id="icon-url" style="width: 200px;" required>
  336. </div>
  337. <div>
  338. <label for="search-url" style="width: 120px;">Search URL:</label>
  339. <button id="info-search-url" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  340. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  341. </button>
  342. <input type="text" id="search-url" style="width: 200px;" required>
  343. </div>
  344. <div>
  345. <label for="color" style="width: 120px;">Button Color:</label>
  346. <button id="info-color" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  347. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  348. </button>
  349. <input type="color" id="color" value="#fc6399">
  350. </div>
  351. <button id="save-new-button" style="background-color: #fc6399; border: none; color: #fff; padding: 8px 16px; cursor: pointer; margin-top: 10px; border-radius: 3px;">Add Button</button>
  352. <button id="close-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; margin-top: 10px; border-radius: 3px;">Close</button>
  353. `;
  354.  
  355. document.body.appendChild(addButtonModal);
  356.  
  357. const saveNewButtonBtn = document.getElementById('save-new-button');
  358. const closeButton = document.getElementById('close-button');
  359.  
  360. function openInfoPopup(inputId, infoText) {
  361. const inputElement = document.getElementById(inputId);
  362. const infoButton = document.getElementById(`info-${inputId}`);
  363.  
  364. infoButton.addEventListener('click', () => {
  365. alert(infoText);
  366. inputElement.focus();
  367. });
  368. }
  369.  
  370. const infoTexts = {
  371. 'website-name': 'Used for enabling/disabling in the settings (Doesnt have to be exact)',
  372. 'icon-url': `Full URL path to a (preferably transparent) PNG.
  373. Correct: https://i.imgur.com/n04Muj1.jpeg
  374. Wrong: https://cdn-icons-png.flaticon.com/512/2815/2815428`,
  375. 'search-url': `This is a URL when you search something without the actual thing you searched.
  376.  
  377. Simple:
  378. Add the full URL without the thing you searched (Good if the searched term is at the end of the URL)
  379.  
  380. Example (On google.com):
  381. If you google test you would get: https://www.google.com/search?q=test
  382. Then you remove 'test' and you get this, which you put into this box: https://www.google.com/search?q=
  383.  
  384. Advanced:
  385. In rare instances, when the searched term is not at the end of the URLm you can replace the searched term with %s, which is where the game name will be put into.
  386.  
  387. Example (Made-up case on Gamebanana):
  388. If the URL is:
  389. https://gamebanana.com/search?__sSearchString=TESTOrder=best_match&_idGameRow=18557
  390.  
  391. We replace the 'TEST' with %s, which we will get:
  392. https://gamebanana.com/search?__sSearchString=%sOrder=best_match&_idGameRow=18557`,
  393. 'color': 'Color for the button.'
  394. };
  395.  
  396. Object.keys(infoTexts).forEach(inputId => {
  397. openInfoPopup(inputId, infoTexts[inputId]);
  398. });
  399.  
  400. saveNewButtonBtn.addEventListener('click', () => {
  401. const websiteNameInput = document.getElementById('website-name').value;
  402. const iconUrlInput = document.getElementById('icon-url').value;
  403. const searchUrlInput = document.getElementById('search-url').value;
  404. const colorInput = document.getElementById('color').value;
  405.  
  406. const newButton = {
  407. name: websiteNameInput,
  408. iconUrl: iconUrlInput,
  409. searchUrl: searchUrlInput,
  410. color: colorInput,
  411. status: true
  412. };
  413.  
  414. buttons.push(newButton);
  415. GM_setValue('buttons', buttons);
  416.  
  417. addButton(newButton.iconUrl, newButton.searchUrl, newButton.color, newButton.status, newButton.name);
  418.  
  419. window.location.reload();
  420. });
  421.  
  422. closeButton.addEventListener('click', () => {
  423. addButtonModal.remove();
  424. });
  425. }
  426.  
  427.  
  428. function waitForElement(selector, callback) {
  429. const interval = setInterval(() => {
  430. const element = document.querySelector(selector);
  431. if (element) {
  432. clearInterval(interval);
  433. callback(element);
  434. }
  435. }, 100);
  436. }
  437.  
  438.  
  439. function addBadgeToElement(element) {
  440. const badge = document.createElement('span');
  441. badge.className = 'vers-badge';
  442. badge.textContent = 'SCRIPT\nDEV';
  443. badge.style.backgroundColor = '#1aba7c';
  444. badge.style.color = '#16181c';
  445. badge.style.fontSize = '.7rem';
  446. badge.style.borderRadius = '4px';
  447. badge.style.fontWeight = '600';
  448. badge.style.padding = '3px';
  449. badge.style.marginLeft = '5px';
  450.  
  451. if (element.tagName.toLowerCase() === 'p') {
  452. badge.textContent = 'SCRIPTDEV';
  453. }
  454.  
  455. const textNode = element.firstChild;
  456. element.insertBefore(badge, textNode.nextSibling);
  457. }
  458.  
  459. function processElements() {
  460. console.log('Processing elements...');
  461. const h3Elements = document.querySelectorAll('div.row > div.col-auto > h3.main-header');
  462. const pElements = document.querySelectorAll('div.col > div.row.mb-1 > a > div.col-auto > p.mb-0');
  463.  
  464. h3Elements.forEach(element => {
  465. if (element.textContent.trim() === 'VERS' && !element.classList.contains('vers-badge')) {
  466. console.log('Adding badge to h3 element:', element);
  467. addBadgeToElement(element);
  468. }
  469. });
  470.  
  471. pElements.forEach(element => {
  472. if (element.textContent.trim() === 'VERS' && !element.classList.contains('vers-badge')) {
  473. console.log('Adding badge to p element:', element);
  474. addBadgeToElement(element);
  475. }
  476. });
  477. }
  478.  
  479. processElements();
  480.  
  481. const observer = new MutationObserver(mutations => {
  482. mutations.forEach(mutation => {
  483. if (mutation.type === 'childList') {
  484. const addedNodes = Array.from(mutation.addedNodes);
  485. addedNodes.forEach(node => {
  486. if (node.nodeType === Node.ELEMENT_NODE) {
  487. processElements();
  488. }
  489. });
  490. }
  491. });
  492. });
  493.  
  494. observer.observe(document.body, {
  495. childList: true,
  496. subtree: true
  497. });
  498.  
  499. addSpacer();
  500. updateButtons();
  501.  
  502. waitForElement('#title h1', (element) => {
  503. const observer = new MutationObserver(function(mutationsList) {
  504. for (let mutation of mutationsList) {
  505. if (mutation.type === 'childList' && mutation.target.nodeName === 'HTML') {
  506. const existingButtons = document.querySelectorAll('.custom-button');
  507. existingButtons.forEach(button => {
  508. button.remove();
  509. });
  510. addSpacer();
  511. updateButtons();
  512. }
  513. }
  514. });
  515.  
  516. observer.observe(document, { childList: true, subtree: true });
  517. });
  518.  
  519. window.onhashchange = function() {
  520. const existingButtons = document.querySelectorAll('.custom-button');
  521. existingButtons.forEach(button => {
  522. button.remove();
  523. });
  524. updateButtons();
  525. };
  526. })();