GGn Title Formatter

Formats title, sets alias if applicable and has buttons to undo. Adds buttons in edit page to format name and alias. Easily fix title in group pages

  1. // ==UserScript==
  2. // @name GGn Title Formatter
  3. // @namespace none
  4. // @version 34
  5. // @description Formats title, sets alias if applicable and has buttons to undo. Adds buttons in edit page to format name and alias. Easily fix title in group pages
  6. // @author ingts
  7. // @match https://gazellegames.net/upload.php
  8. // @match https://gazellegames.net/torrents.php?id=*
  9. // @match https://gazellegames.net/torrents.php?action=editgroup&groupid=*
  10. // @match https://gazellegames.net/upload.php?action=copy&groupid=*
  11. // @exclude https://gazellegames.net/upload.php?groupid=*
  12. // @grant unsafeWindow
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @require https://cdn.jsdelivr.net/npm/diff@8.0.2
  16. // @require https://update.greasyfork.org/scripts/540511/1614945/GGn%20Formatters.js
  17. // ==/UserScript==
  18.  
  19. if (typeof GM_getValue('enable_on_upload') === 'undefined')
  20. GM_setValue('enable_on_upload', true)
  21. if (typeof GM_getValue('group_auto_check') === 'undefined')
  22. GM_setValue('group_auto_check', true)
  23.  
  24. let titleInput, alias
  25.  
  26. function formatText() {
  27. let origTitle = titleInput.value
  28. alias = document.getElementById('aliases')
  29. let origAlias = alias.value
  30.  
  31. let titleAfterTitleCase = formatTitle(titleInput.value, alias.value)
  32. titleInput.value = titleAfterTitleCase
  33.  
  34. if (document.getElementById('categories').value === 'Games') {
  35. const excludePattern = /[^a-zA-Z0-9 .~?’!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/]+/g
  36. let excluded = titleInput.value.match(excludePattern)
  37.  
  38. if (excluded) {
  39. if (excluded.length === 1) {
  40. alias.value ? alias.value += ', ' + excluded.join('') : alias.value = excluded.join('')
  41. titleInput.value = titleInput.value.replace(excludePattern, "").trim()
  42. } else {
  43. alias.value ? alias.value += ', ' + titleInput.value : titleInput.value
  44. titleInput.value = ''
  45. startTextFormat(false)
  46. return
  47. }
  48. }
  49. }
  50.  
  51. if (titleAfterTitleCase !== origTitle || alias.value !== origAlias) {
  52. document.querySelector("#title_tr > td.label").insertAdjacentHTML('beforeend', `<span style="color: #ebaf51;display: block;">Undo Title Formatter</span>
  53. <div id="tf-undo-buttons"></div>`)
  54.  
  55. const buttonDiv = document.getElementById('tf-undo-buttons')
  56.  
  57. if (titleAfterTitleCase !== origTitle) {
  58. const button1 = document.createElement('button')
  59. button1.textContent = 'Formatting'
  60. button1.type = 'button'
  61. button1.onclick = () => {
  62. titleInput.value = origTitle
  63. }
  64. buttonDiv.append(button1)
  65. }
  66. if (alias.value !== origAlias) {
  67. const button2 = document.createElement('button')
  68. button2.textContent = 'Alias'
  69. button2.type = 'button'
  70. button2.onclick = () => {
  71. titleInput.value = titleAfterTitleCase
  72. alias.value = origAlias
  73. }
  74. buttonDiv.append(button2)
  75. }
  76. }
  77. }
  78.  
  79. function startTextFormat(wait) {
  80. const tInterval = setInterval(() => {
  81. if (document.activeElement === titleInput || !titleInput.value)
  82. return
  83. clearInterval(tInterval)
  84. if (wait) {
  85. // to allow upload scripts that use the title input's value to set the title before formatting
  86. setTimeout(formatText, 2000)
  87. } else formatText()
  88. }, 500)
  89. }
  90.  
  91. function addButton(input) {
  92. const button = document.createElement('button')
  93. button.type = 'button'
  94. button.textContent = 'Format'
  95. button.addEventListener('click', () => {
  96. input.value = formatTitle(input.value, alias?.value || alias?.textContent)
  97. })
  98. input.after(button)
  99. }
  100.  
  101. if (location.href.includes('upload.php') && GM_getValue('enable_on_upload')) {
  102. titleInput = document.getElementById('title')
  103.  
  104. // changing the category changes the form using a server request and the title input is replaced
  105. document.getElementById('categories').addEventListener('change', () => {
  106. new MutationObserver((mutations, observer) => {
  107. titleInput = document.getElementById('title')
  108. startTextFormat(true)
  109. observer.disconnect()
  110. }).observe(document.getElementById('dynamic_form'), {childList: true, subtree: true})
  111. })
  112. startTextFormat(true)
  113. } else if (location.href.includes('editgroup')) {
  114. titleInput = document.querySelector("input[name=name]")
  115. alias = document.querySelector('input[name=aliases]')
  116. addButton(alias)
  117. addButton(titleInput)
  118. } else { // group page
  119. if (GM_getValue('group_auto_check')) {
  120. const nameContainer = document.getElementById('display_name')
  121. const titleEl = nameContainer.childNodes[0].nodeType === Node.TEXT_NODE ? nameContainer.childNodes[0] : nameContainer.childNodes[1]
  122. const title = titleEl.textContent
  123. .replace(/^ - /, '').replace(/ \(.*$/, '').replace(/ \([^)(]*\)$/, '')
  124. const formatted = formatTitle(title, getGroupAlias())
  125. const diffChars = Diff.diffChars(title, formatted)
  126.  
  127. if (diffChars.length > 1) {
  128. nameContainer.insertAdjacentHTML('beforeend', `<div style="font-size: initial;" id="title-formatter-check">
  129. <div style="
  130. display: flex;
  131. flex-direction: row;
  132. justify-content: start;
  133. column-gap: 3px;
  134. margin-top: 3px;
  135. ">
  136. <button type="button" style="width: fit-content;" id="title-formatter-close">Close</button>
  137. <button type="button" style="width: fit-content;background-color: #2f742f;" id="title-formatter-accept">Accept</button>
  138. <span id="title-formatter-diff" style="margin-left: 5px;"></span>
  139. </div>
  140. </div>`)
  141. const span = document.getElementById('title-formatter-diff')
  142. for (const diffChar of diffChars) {
  143. let style = ""
  144. if (diffChar.removed) {
  145. if (!/^\s*$/.test(diffChar.value)) { // not space only
  146. continue
  147. }
  148. style = "background-color: red"
  149. } else {
  150. style = 'color: ' + (diffChar.added ? 'lightgreen' : 'rgb(165 146 146)')
  151. }
  152. const s = document.createElement('span')
  153. s.style.cssText = style
  154. s.textContent = diffChar.value
  155. span.append(s)
  156. }
  157.  
  158. const mainContainer = document.getElementById('title-formatter-check')
  159. document.getElementById('title-formatter-close').onclick = () => mainContainer.remove()
  160. document.getElementById('title-formatter-accept').onclick = () => {
  161. fetch('torrents.php', {
  162. method: 'POST',
  163. body: new URLSearchParams(`action=rename&auth=${authkey}&groupid=${/\d+/.exec(location.href)[0]}&name=${formatted}`),
  164. }).then(r => {
  165. if (r.redirected) {
  166. mainContainer.remove()
  167. titleEl.textContent = titleEl.textContent.replace(title, formatted)
  168. } else alert('Rename failed')
  169. })
  170. }
  171. }
  172. }
  173. // wait to make sure the editor helper loads first
  174. setTimeout(() => {
  175. const editHelperRename = document.getElementById('titleEdit')
  176.  
  177. if (editHelperRename) {
  178. editHelperRename.addEventListener('click', () => {
  179. titleInput = document.querySelector("input[name=name]")
  180. alias = getGroupAlias()
  181. addButton(titleInput)
  182. })
  183. }
  184. }, 80)
  185. }
  186.  
  187. function getGroupAlias() {
  188. const childNodes = document.getElementById('group_aliases')?.childNodes
  189. if (childNodes) {
  190. return childNodes[childNodes.length - 1].data
  191. }
  192. return null
  193. }