Backloggd Button Utility

Adds customizable buttons to backloggd.

当前为 2024-09-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Backloggd Button Utility
  3. // @namespace http://vers.works/
  4. // @version 2.4
  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. // Version 2.3:
  15. // Fixed aligning to make the buttons look better.
  16. // (Soon I will be adding conditions so steam button only shows for pc games etc.)
  17.  
  18.  
  19. (function() {
  20. 'use strict';
  21.  
  22. const whitelist = ['Steam', 'SteamDB', 'SteamGridDB', 'Epic Games Store', 'GOG', 'Twitch', 'Youtube', 'XBOX', 'Playstation', 'Nintendo',];
  23.  
  24. let buttons = GM_getValue('buttons', [
  25. {
  26. name: 'Steam',
  27. iconUrl: 'https://i.imgur.com/9NFBdz4.png',
  28. searchUrl: 'https://store.steampowered.com/search/?term=',
  29. color: '#1a9fff',
  30. status: true
  31. },
  32. {
  33. name: 'SteamDB',
  34. iconUrl: 'https://i.imgur.com/t6rV9xA.png',
  35. searchUrl: 'https://steamdb.info/search/?a=all&q=',
  36. color: '#3e76cb',
  37. status: true
  38. },
  39. {
  40. name: 'SteamGridDB',
  41. iconUrl: 'https://i.imgur.com/oluhFHS.png',
  42. searchUrl: 'https://www.steamgriddb.com/search/grids?term=',
  43. color: '#3a6e92',
  44. status: true
  45. },
  46. {
  47. name: 'Epic Games Store',
  48. iconUrl: 'https://cdn2.unrealengine.com/Unreal+Engine%2Feg-logo-filled-1255x1272-0eb9d144a0f981d1cbaaa1eb957de7a3207b31bb.png',
  49. searchUrl: 'https://store.epicgames.com/en-US/expanded-search-results?q=',
  50. color: '#000000',
  51. status: true
  52. },
  53. {
  54. name: 'GOG',
  55. iconUrl: 'https://yt3.googleusercontent.com/wr5HXJvdt0_B0bFgxaO5Fczjl3wsofggnPGKRuLe8AWkvyRUnedkjJIKQGmztxD-Xue8qiBC=s900-c-k-c0x00ffffff-no-rj',
  56. searchUrl: 'https://www.gog.com/en/games?query=',
  57. color: '#ffffff',
  58. status: true
  59. },
  60. {
  61. name: 'Twitch',
  62. iconUrl: 'https://i.imgur.com/UVuf0iF.png',
  63. searchUrl: 'https://www.twitch.tv/search?term=',
  64. color: '#9046fd',
  65. status: true
  66. },
  67. {
  68. name: 'Youtube',
  69. iconUrl: 'https://i.imgur.com/C0Ux2Y3.png',
  70. searchUrl: 'https://www.youtube.com/results?search_query=',
  71. color: '#ff0000',
  72. status: true
  73. },
  74. {
  75. name: 'XBOX',
  76. iconUrl: 'https://i.imgur.com/jrItCUM.png',
  77. searchUrl: 'https://www.xbox.com/Search/Results?q=',
  78. color: '#107b10',
  79. status: true
  80. },
  81. {
  82. name: 'Playstation',
  83. iconUrl: 'https://i.imgur.com/wvB5DF8.png',
  84. searchUrl: 'https://www.playstation.com/search/?q=',
  85. color: '#0070d1',
  86. status: true
  87. },
  88. {
  89. name: 'Nintendo',
  90. iconUrl: 'https://i.imgur.com/7cGs7D6.png',
  91. searchUrl: 'https://www.nintendo.com/us/search/#q=',
  92. color: '#e70819',
  93. status: true
  94. },
  95. {
  96. name: 'Settings',
  97. iconUrl: 'https://i.imgur.com/WvM8EHQ.png',
  98. color: '#16181c',
  99. status: true,
  100. isSettings: true
  101. },
  102. {
  103. name: 'Remove Button',
  104. iconUrl: 'https://via.placeholder.com/24',
  105. color: '#ff0000',
  106. status: true,
  107. isSettings: true,
  108. isRemovable: false
  109. }
  110. ]);
  111.  
  112. function addButton(iconUrl, searchUrl, color, status, name) {
  113. if (!status) {
  114. return;
  115. }
  116.  
  117. const button = document.createElement('button');
  118. button.className = 'btn btn-main journal-btn custom-button';
  119. button.style.width = '34px';
  120. button.style.height = '34px';
  121. button.style.margin = '7px 4px 3px 4px';
  122. button.style.backgroundColor = color;
  123. button.style.border = 'none';
  124. button.style.display = 'inline-flex';
  125. button.style.justifyContent = 'center';
  126. button.style.alignItems = 'center';
  127.  
  128. const icon = document.createElement('img');
  129. icon.src = iconUrl;
  130. icon.alt = 'Search';
  131. icon.style.width = '24px';
  132. icon.style.height = '24px';
  133. button.appendChild(icon);
  134.  
  135. button.addEventListener('click', function() {
  136. if (name === 'Settings') {
  137. openSettingsModal();
  138. } else {
  139. const gameTitleElement = document.querySelector('#title h1');
  140. if (gameTitleElement) {
  141. const gameTitle = encodeURIComponent(gameTitleElement.textContent.trim());
  142. const searchLink = searchUrl.includes('%s') ? searchUrl.replace('%s', gameTitle) : searchUrl + gameTitle;
  143. window.open(searchLink, '_blank');
  144. } else {
  145. console.error('Game title element not found');
  146. }
  147. }
  148. });
  149.  
  150. const journalButtonContainer = document.querySelector('.journal-button-container');
  151. if (journalButtonContainer) {
  152. const settingsButton = journalButtonContainer.querySelector('button[data-name="Settings"]');
  153. const existingButton = journalButtonContainer.querySelector(`.custom-button[data-search="${searchUrl}"]`);
  154.  
  155. if (!existingButton) {
  156. button.setAttribute('data-search', searchUrl);
  157. button.setAttribute('data-name', name);
  158. if (settingsButton) {
  159. journalButtonContainer.insertBefore(button, settingsButton); // Insert before Settings button
  160. } else {
  161. journalButtonContainer.appendChild(button); // Fallback in case settings button is not found
  162. }
  163. }
  164. } else {
  165. console.error('Journal button container not found');
  166. }
  167. }
  168.  
  169. function addSpacer() {
  170. const existingSpacer = document.querySelector('.journal-button-container > .spacer');
  171. if (!existingSpacer) {
  172. const spacer = document.createElement('div');
  173. spacer.className = 'spacer'; // Add a class to identify the spacer
  174. spacer.style.height = '10px'; // Adjust the height as needed
  175. const journalButtonContainer = document.querySelector('.journal-button-container');
  176. if (journalButtonContainer) {
  177. const firstButton = journalButtonContainer.querySelector('.custom-button');
  178. if (firstButton) {
  179. journalButtonContainer.insertBefore(spacer, firstButton);
  180. } else {
  181. journalButtonContainer.appendChild(spacer); // If no buttons exist yet, just append the spacer
  182. }
  183. } else {
  184. console.error('Journal button container not found');
  185. }
  186. }
  187. }
  188.  
  189.  
  190. function updateButtons() {
  191. buttons.forEach(button => {
  192. addButton(button.iconUrl, button.searchUrl, button.color, button.status, button.name);
  193. });
  194. }
  195.  
  196. function openSettingsModal() {
  197. const settingsModal = document.createElement('div');
  198. settingsModal.className = 'settings-modal';
  199. settingsModal.style.position = 'fixed';
  200. settingsModal.style.top = '50%';
  201. settingsModal.style.left = '50%';
  202. settingsModal.style.transform = 'translate(-50%, -50%)';
  203. settingsModal.style.backgroundColor = 'rgba(22, 24, 28)';
  204. settingsModal.style.borderRadius = '10px';
  205. settingsModal.style.padding = '20px';
  206. settingsModal.style.zIndex = '9999';
  207. settingsModal.innerHTML = `
  208. <h1>Backloggd Button Utility</h1>
  209. <p style="display: flex; align-items: center;">Script made by VERS.
  210. <a href="https://www.backloggd.com/u/VERS/" target="_blank">
  211. <img src="https://i.imgur.com/4FLnRC9.png" alt="Icon 1" style="margin-left: 10px;; width: 19px; height: 19px;">
  212. </a>
  213. <a href="https://twitter.com/versworks" target="_blank">
  214. <img src="https://i.imgur.com/yXPQwtv.png" alt="Icon 2" style="margin-left: 5px;; width: 19px; height: 19px;">
  215. </a>
  216. <a href="https://vers.works/" target="_blank">
  217. <img src="https://i.imgur.com/oePEdTt.png" alt="Icon 3" style="margin-left: 5px;; width: 19px; height: 19px;">
  218. </a>
  219. </p>
  220. <p style="font-size: 12px; font-style: italic; text-align: center;">
  221. If you have any issues or feedback you can contact me on discord (The_Vers) <br>or in this <a href="https://www.reddit.com/r/backloggd/comments/1bopg7t/introducing_button_utility_my_script_that_adds/" target="_blank">reddit post</a>.
  222. </p>
  223. <h2>Button Settings</h2>
  224. <ul>
  225. ${buttons
  226. .filter(button => !button.isSettings && button.name !== 'Remove Button')
  227. .map(
  228. button =>
  229. `<li>
  230. <label>
  231. <input type="checkbox" id="${button.name}-checkbox" ${
  232. button.status ? 'checked' : ''
  233. }>
  234. ${button.name}
  235. </label>
  236. </li>`
  237. )
  238. .join('')}
  239. </ul>
  240. <button id="add-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Add Custom Button</button>
  241. <button id="remove-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Remove Custom Button</button>
  242. <button id="save-settings-btn" style="background-color: #fc6399; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Save Settings</button>
  243. `;
  244.  
  245. document.body.appendChild(settingsModal);
  246.  
  247. const addButtonBtn = document.getElementById('add-button');
  248. addButtonBtn.addEventListener('click', () => {
  249. openAddButtonModal();
  250. });
  251.  
  252. const removeButtonBtn = document.getElementById('remove-button');
  253. removeButtonBtn.addEventListener('click', () => {
  254. openRemoveButtonModal();
  255. });
  256.  
  257. const saveSettingsBtn = document.getElementById('save-settings-btn');
  258. saveSettingsBtn.addEventListener('click', () => {
  259. buttons.forEach(button => {
  260. if (!button.isSettings) {
  261. const checkbox = document.getElementById(`${button.name}-checkbox`);
  262. button.status = checkbox.checked;
  263. }
  264. });
  265.  
  266. GM_setValue('buttons', buttons);
  267.  
  268. window.location.reload();
  269. });
  270. }
  271.  
  272.  
  273.  
  274. function openRemoveButtonModal() {
  275. const removeButtonModal = document.createElement('div');
  276. removeButtonModal.className = 'remove-button-modal';
  277. removeButtonModal.style.position = 'fixed';
  278. removeButtonModal.style.top = '50%';
  279. removeButtonModal.style.borderRadius = '10px';
  280. removeButtonModal.style.left = '50%';
  281. removeButtonModal.style.transform = 'translate(-50%, -50%)';
  282. removeButtonModal.style.backgroundColor = '#242832';
  283. removeButtonModal.style.padding = '20px';
  284. removeButtonModal.style.zIndex = '9999';
  285. removeButtonModal.innerHTML = `
  286. <h2>Remove Button</h2>
  287. <ul>
  288. ${buttons
  289. .filter(button => !button.isSettings && !whitelist.includes(button.name))
  290. .map(
  291. button =>
  292. `<li>
  293. <button id="remove-${button.name}" style="background-color: #fc6399; border: none; color: #ffffff; padding: 5px 5; cursor: pointer;">✖</button>
  294. <span style="margin-left: 10px;">${button.name}</span>
  295. </li>`
  296. )
  297. .join('')}
  298. </ul>
  299. <button id="close-remove-modal" style="background-color: #4a5e8d; border: none; color: #ffffff; padding: 8px 16px; cursor: pointer; margin-top: 10px;">Close</button>
  300. `;
  301.  
  302. document.body.appendChild(removeButtonModal);
  303.  
  304. buttons
  305. .filter(button => !button.isSettings && !whitelist.includes(button.name))
  306. .forEach(button => {
  307. const removeButton = document.getElementById(`remove-${button.name}`);
  308. removeButton.addEventListener('click', () => {
  309. removeButtonFromList(button.name);
  310. removeButton.parentElement.remove();
  311. });
  312. });
  313.  
  314. const closeModalBtn = document.getElementById('close-remove-modal');
  315. closeModalBtn.addEventListener('click', () => {
  316. removeButtonModal.remove();
  317. });
  318. }
  319.  
  320. function removeButtonFromList(buttonName) {
  321. buttons = buttons.filter(button => button.name !== buttonName);
  322. GM_setValue('buttons', buttons);
  323.  
  324. const existingButtons = document.querySelectorAll('.custom-button');
  325. existingButtons.forEach(button => {
  326. button.remove();
  327. });
  328.  
  329. addSpacer();
  330. updateButtons();
  331. }
  332.  
  333. function openAddButtonModal() {
  334. const addButtonModal = document.createElement('div');
  335. addButtonModal.className = 'add-button-modal';
  336. addButtonModal.style.position = 'fixed';
  337. addButtonModal.style.top = '50%';
  338. addButtonModal.style.left = '50%';
  339. addButtonModal.style.borderRadius = '10px';
  340. addButtonModal.style.transform = 'translate(-50%, -50%)';
  341. addButtonModal.style.backgroundColor = '#242832';
  342. addButtonModal.style.padding = '20px';
  343. addButtonModal.style.zIndex = '9999';
  344. addButtonModal.innerHTML = `
  345. <h2>Add Custom Button</h2>
  346. <div>
  347. <label for="website-name" style="width: 120px;">Website Name:</label>
  348. <button id="info-website-name" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  349. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  350. </button>
  351. <input type="text" id="website-name" style="width: 200px;" required>
  352. </div>
  353. <div>
  354. <label for="icon-url" style="width: 120px;">Icon URL:</label>
  355. <button id="info-icon-url" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  356. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  357. </button>
  358. <input type="text" id="icon-url" style="width: 200px;" required>
  359. </div>
  360. <div>
  361. <label for="search-url" style="width: 120px;">Search URL:</label>
  362. <button id="info-search-url" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  363. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  364. </button>
  365. <input type="text" id="search-url" style="width: 200px;" required>
  366. </div>
  367. <div>
  368. <label for="color" style="width: 120px;">Button Color:</label>
  369. <button id="info-color" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
  370. <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
  371. </button>
  372. <input type="color" id="color" value="#fc6399">
  373. </div>
  374. <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>
  375. <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>
  376. `;
  377.  
  378. document.body.appendChild(addButtonModal);
  379.  
  380. const saveNewButtonBtn = document.getElementById('save-new-button');
  381. const closeButton = document.getElementById('close-button');
  382.  
  383. function openInfoPopup(inputId, infoText) {
  384. const inputElement = document.getElementById(inputId);
  385. const infoButton = document.getElementById(`info-${inputId}`);
  386.  
  387. infoButton.addEventListener('click', () => {
  388. alert(infoText);
  389. inputElement.focus();
  390. });
  391. }
  392.  
  393. const infoTexts = {
  394. 'website-name': 'Used for enabling/disabling in the settings (Doesnt have to be exact)',
  395. 'icon-url': `Full URL path to a (preferably transparent) PNG.
  396. Correct: https://i.imgur.com/n04Muj1.jpeg
  397. Wrong: https://cdn-icons-png.flaticon.com/512/2815/2815428`,
  398. 'search-url': `This is a URL when you search something without the actual thing you searched.
  399.  
  400. Simple:
  401. Add the full URL without the thing you searched (Good if the searched term is at the end of the URL)
  402.  
  403. Example (On google.com):
  404. If you google test you would get: https://www.google.com/search?q=test
  405. Then you remove 'test' and you get this, which you put into this box: https://www.google.com/search?q=
  406.  
  407. Advanced:
  408. 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.
  409.  
  410. Example (Made-up case on Gamebanana):
  411. If the URL is:
  412. https://gamebanana.com/search?__sSearchString=TESTOrder=best_match&_idGameRow=18557
  413.  
  414. We replace the 'TEST' with %s, which we will get:
  415. https://gamebanana.com/search?__sSearchString=%sOrder=best_match&_idGameRow=18557`,
  416. 'color': 'Color for the button.'
  417. };
  418.  
  419. Object.keys(infoTexts).forEach(inputId => {
  420. openInfoPopup(inputId, infoTexts[inputId]);
  421. });
  422.  
  423. saveNewButtonBtn.addEventListener('click', () => {
  424. const websiteNameInput = document.getElementById('website-name').value;
  425. const iconUrlInput = document.getElementById('icon-url').value;
  426. const searchUrlInput = document.getElementById('search-url').value;
  427. const colorInput = document.getElementById('color').value;
  428.  
  429. const newButton = {
  430. name: websiteNameInput,
  431. iconUrl: iconUrlInput,
  432. searchUrl: searchUrlInput,
  433. color: colorInput,
  434. status: true
  435. };
  436.  
  437. buttons.push(newButton);
  438. GM_setValue('buttons', buttons);
  439.  
  440. addButton(newButton.iconUrl, newButton.searchUrl, newButton.color, newButton.status, newButton.name);
  441.  
  442. window.location.reload();
  443. });
  444.  
  445. closeButton.addEventListener('click', () => {
  446. addButtonModal.remove();
  447. });
  448. }
  449.  
  450.  
  451. function waitForElement(selector, callback) {
  452. const interval = setInterval(() => {
  453. const element = document.querySelector(selector);
  454. if (element) {
  455. clearInterval(interval);
  456. callback(element);
  457. }
  458. }, 100);
  459. }
  460.  
  461.  
  462. function addBadgeToElement(element) {
  463. const badge = document.createElement('span');
  464. badge.className = 'vers-badge';
  465. badge.textContent = 'SCRIPT\nDEV';
  466. badge.style.backgroundColor = '#1aba7c';
  467. badge.style.color = '#16181c';
  468. badge.style.fontSize = '.7rem';
  469. badge.style.borderRadius = '4px';
  470. badge.style.fontWeight = '600';
  471. badge.style.padding = '2px';
  472. badge.style.marginLeft = '5px';
  473.  
  474. if (element.tagName.toLowerCase() === 'p') {
  475. badge.textContent = 'SCRIPTDEV';
  476. }
  477.  
  478. const textNode = element.firstChild;
  479. element.insertBefore(badge, textNode.nextSibling);
  480. }
  481.  
  482. const observer = new MutationObserver(mutations => {
  483. mutations.forEach(mutation => {
  484. if (mutation.type === 'childList') {
  485. const addedNodes = Array.from(mutation.addedNodes);
  486. addedNodes.forEach(node => {
  487. if (node.nodeType === Node.ELEMENT_NODE) {
  488. processElements();
  489. }
  490. });
  491. }
  492. });
  493. });
  494.  
  495. observer.observe(document.body, {
  496. childList: true,
  497. subtree: true
  498. });
  499.  
  500. addSpacer();
  501. updateButtons();
  502.  
  503. waitForElement('#title h1', (element) => {
  504. const observer = new MutationObserver(function(mutationsList) {
  505. for (let mutation of mutationsList) {
  506. if (mutation.type === 'childList' && mutation.target.nodeName === 'HTML') {
  507. const existingButtons = document.querySelectorAll('.custom-button');
  508. existingButtons.forEach(button => {
  509. button.remove();
  510. });
  511. addSpacer();
  512. updateButtons();
  513. }
  514. }
  515. });
  516.  
  517. observer.observe(document, { childList: true, subtree: true });
  518. });
  519.  
  520. window.onhashchange = function() {
  521. const existingButtons = document.querySelectorAll('.custom-button');
  522. existingButtons.forEach(button => {
  523. button.remove();
  524. });
  525. updateButtons();
  526. };
  527. })();