TorrentBD - Easy mentioning in Shoutbox (No longer maintained)

In Shoutbox, clicking on the time behind a Username will pass that username over to the Shout/Chat input making it easier to mention anyone.

  1. // ==UserScript==
  2. // @name TorrentBD - Easy mentioning in Shoutbox (No longer maintained)
  3. // @namespace Violentmonkey Scripts
  4. // @match https://www.torrentbd.com/*
  5. // @match https://www.torrentbd.me/*
  6. // @match https://www.torrentbd.net/*
  7. // @match https://www.torrentbd.org/*
  8. // @exclude *?account-login
  9. // @grant GM_addStyle
  10. // @version 1.2.5
  11. // @run-at document-end
  12. // @author BENZiN
  13. // @license MIT
  14. // @description In Shoutbox, clicking on the time behind a Username will pass that username over to the Shout/Chat input making it easier to mention anyone.
  15. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjhEQ0QxMEFEM0FFRTExRUFBMzAwQzI2REE1MEFBNTJEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjhEQ0QxMEFFM0FFRTExRUFBMzAwQzI2REE1MEFBNTJEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OERDRDEwQUIzQUVFMTFFQUEzMDBDMjZEQTUwQUE1MkQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OERDRDEwQUMzQUVFMTFFQUEzMDBDMjZEQTUwQUE1MkQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4l3CaDAAADIUlEQVR42rRXv09aURQ+FsHWCg2RIAwSm6okJZSmi4tLKUZjw+TC6H9RN7vRTu3CYOzSxXY0JozCQgJ1KNSENKQ1jRB/1GJMKaBAyOs5z3vN8/mAdwVO8oXAu/d8H/f8uOcNgH4bQbxEPEc8RTxEPGDP/iJ+ITKIOCKKKEOPbBKxzhxKOlFmeya7Ib6LeIOoCRCrQXvfMl9C9giR7oJYjbTIaTxDHPeQnOOY+e4Y75M+kHOctDuJeyyLpT7jG+OSbVAh4DXC1+mILBYLGAwGMBqNMDw8DENDQ5eOBi9dXVxcQK1Wg3q9Do1GQ0a5fK0inzCuV/RlQJF03xHGa4U/MiKTOJ1O8Pl8MDMzA4FAAKanp3VncyaTgXg8DolEArLZLBSLRRLVKJVKj/HxT75unR8RkkpIKIVCISkWi0n9ML/fT1wfOLmZNxkiDwaDUr9tfn6eNyszBW4RcV9WYjbD1tZWT9pntVqFzc1N2N/fh7GxMZibm4Px8XEIh8Ows7MDjHORBLzgmyje3VoqlYLl5WU4OjoCjPON5FUlpcyd4vG3Wq1SpVK5cWT5fF5aXV2V8yIajbY82nQ6LfsQKEnihj/KHz0ej5x8uVxOWllZkdxut4Tqr56Pjo62FICVItoTiFvssqFETSaTmgLsdrvwZXVHNMYUv/Pzc82koyYkaiSgJLKBksnr9fZq1iiRgD2RHZTJNpvtxu+3rKA9ErArsoPugHbiBG2XBGyL7DCZTC0bT7PZFBWwfa0V68HExIRmBVCvEKyACnHTCfxDfBbJAS07ODgQ/fefiJuXIQ2fDT27+L2vtsPDQxHyBhtWgQuge/ldN/VEl46AvUf8UArgE9HubQUUCgXdmc+4QC2A2tsS78+iRrefDiPfS5izVS0BgA8oFAuI36ICzs7OOi0hnwuMAzQFMBFf8WNWNBynp6edjn2W+W5tuOAK7HWKMrWuvq61jOZIjVqv81czpW/mv70AxcIpNrRS45DnA7VFIhH5mlYQV9nQOaXlU0iAYgO9nodcLtdHnOvynHxjY6PpcDiK+OwLYo3W0Np2vpQC/gswALqtfOgnVs/UAAAAAElFTkSuQmCC
  16. // ==/UserScript==
  17.  
  18. let enableEasyMention = 1
  19. let enableUrlBtn = 1
  20.  
  21. //Global Styles
  22. let css = ``
  23.  
  24. //function specific css
  25. let easyMentionStyles = `
  26. .shout-user{
  27. user-select: none;
  28. }
  29. .chromium span.shout-time:has(+ .shout-user [href^="account"]){
  30. cursor: pointer;
  31. position: relative;
  32. }
  33. .chromium span.shout-time:has(+ .shout-user [href^="account"]):hover{
  34. margin-left: -1.5em;
  35. }
  36. .chromium span.shout-time:has(+ .shout-user [href^="account"])::after{
  37. content: "";
  38. margin-left: .5em;
  39. height: 1em;
  40. rotate: 180deg;
  41. display: none;
  42. width: 1em;
  43. }
  44. .chromium span.shout-time:has(+ .shout-user [href^="account"]):hover::after{
  45. display: inline-block;
  46. }
  47.  
  48. .firefox span.shout-time{
  49. cursor: pointer;
  50. position: relative;
  51. }
  52. .firefox span.shout-time:hover{
  53. margin-left: -1.5em;
  54. }
  55. .firefox span.shout-time::after{
  56. content: "";
  57. margin-left: .5em;
  58. height: 1em;
  59. rotate: 180deg;
  60. display: none;
  61. width: 1em;
  62. }
  63. .firefox span.shout-time:hover::after{
  64. display: inline-block;
  65. }
  66.  
  67. .dark-scheme.chromium span.shout-time:has(+ .shout-user [href^="account"])::after{
  68. background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(238, 238, 238);"/></svg>');
  69. }
  70. .light-scheme.chromium span.shout-time:has(+ .shout-user [href^="account"])::after{
  71. background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(68 ,68, 68);"/></svg>');
  72. }
  73. .dark-scheme.firefox span.shout-time::after{
  74. background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(238, 238, 238);"/></svg>');
  75. }
  76. .light-scheme.firefox span.shout-time::after{
  77. background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(68 ,68, 68);"/></svg>');
  78. }
  79. `
  80.  
  81. let urlBtnStyles = `
  82. #shout-ibb-container{
  83. display: flex;
  84. gap: 0 .5rem;
  85. margin-right: 0;
  86. padding-right: .5rem;
  87. right: 0;
  88. position: absolute;
  89. }
  90. #shout-send-container{
  91. position: relative;
  92. }
  93. #urlWindow {
  94. position: absolute;
  95. width: 100%;
  96. left: 0;
  97. flex-flow: wrap;
  98. bottom: 7px;
  99. gap: 0.5rem;
  100. display: flex;
  101. transition: visibility 0s linear .2s, opacity .1s linear .1s, translate .2s linear;
  102. visibility: hidden;
  103. background: var(--main-bg);
  104. border-top: 1px solid var(--border-color);
  105. border-bottom: 1px solid var(--border-color);
  106. padding-top: 0.5rem;
  107. padding-bottom: 0.5rem;
  108. opacity: 0;
  109.  
  110. }
  111. .spotlight #urlWindow{
  112. bottom: 28px;
  113. }
  114. #urlWindow.show{
  115. visibility: visible;
  116. translate: 0 -30px;
  117. transition-delay: 0s;
  118. opacity: 1;
  119. }
  120. .url-inputs {
  121. height: 37px;
  122. color: var(--text-color) !important;
  123. background: var(--main-bg);
  124. padding: 0px .5rem;
  125. border-top:1px solid var(--border-color);
  126. border-bottom:1px solid var(--border-color);
  127. border-left: none;
  128. border-right: none;
  129. outline: 0;
  130. }
  131. .spotlight .url-inputs{
  132. height: 60px;
  133. }
  134. .url-inputs:focus{
  135. }
  136.  
  137. div#urlWindow {
  138. }
  139.  
  140. #urlField {
  141. flex: 1 1 100%;
  142. }
  143. #labelField {
  144. flex: 4 1 auto;
  145. border-right: 1px solid var(--border-color);
  146. }
  147. #submitURL {
  148. flex: 1 1 auto; background: transparent;
  149. border: 1px solid var(--border-color);
  150. border-right: none;
  151. color: var(--text-color);
  152. font-weight: 600;
  153. font-size: 0.9rem;
  154. }
  155. #urlBtn i{
  156. line-height: 37px;
  157. font-size: 0.9rem;
  158. font-weight: 600;
  159. font-family: inherit;
  160. user-select: none;
  161. }
  162. input.shoutbox-text{
  163. width: auto !important;
  164. }
  165. input#shout_text{
  166. padding-right: 170px;
  167. }
  168. .spotlight input#shout_text {
  169. padding-left: .5rem;
  170. padding-right: calc(220px - .5rem);
  171. }
  172. @media(max-width: 767px){
  173. .spotlight input#shout_text {
  174. padding-right: calc(200px - .5rem);
  175. }
  176. .spotlight #shout-ibb-container{
  177. padding-right: .5rem;
  178. }
  179. }
  180.  
  181. `
  182.  
  183. ////////////////////////////////////////////////////////////////////////////////////////////////
  184. //Defining easyMention
  185. const easyMention = () => {
  186. if (window.location.pathname !== "/") return
  187. css += easyMentionStyles
  188. const shout = document.querySelector("#shout_text")
  189. //Checking browser name
  190. if(navigator.vendor == ""){
  191. if (!document.body.classList.contains("firefox")) document.body.classList.add("firefox")
  192. } else {
  193. if (!document.body.classList.contains("chromium")) document.body.classList.add("chromium")
  194. }
  195. let name
  196.  
  197. document.addEventListener("click", e => {
  198. if (document.body.classList.contains("chromium")){
  199. if (!e.target.matches(".chromium .shout-time:has(+ .shout-user [href^='account'])")) return
  200. } else if (document.body.classList.contains("firefox")){
  201. if (!e.target.matches(".firefox .shout-time")) return
  202. }
  203. //Querying username and pasting it into shout_text input field
  204. if (name = e.target.nextElementSibling.querySelector('[href^="account"] span')) {
  205. if (shout.value != "" && shout.value.slice(-1) != " ") shout.value += " "
  206. shout.value += "@" + name.innerText.trim() + " "
  207. }
  208. })
  209. }
  210.  
  211. ////////////////////////////////////
  212. //Defining URL function
  213. const urlBtn = () => {
  214. if (window.location.pathname !== "/") return
  215. css += urlBtnStyles
  216. if (window.location.search.includes("spotlight")) document.body.classList.add("spotlight")
  217.  
  218. const shout = document.querySelector("#shout_text")
  219. const ibbCont = document.querySelector("#shout-ibb-container")
  220. const shoutCont = document.querySelector("#shout-send-container")
  221.  
  222. const urlWindow = document.createElement("div")
  223. urlWindow.id = "urlWindow"
  224. shoutCont.appendChild(urlWindow)
  225.  
  226. const showUrlWindow = () => {
  227. urlWindow.classList.add("show")
  228.  
  229. //closing other emoji windows
  230. shoutCont.querySelectorAll("[id^='spotlight']").forEach(spotlight => {
  231. if (spotlight.classList.contains("shiner")){
  232. spotlight.classList.remove("shiner")
  233. spotlight.classList.add("fader")
  234. }
  235. })
  236. }
  237.  
  238. const hideUrlWindow = () => urlWindow.classList.remove("show")
  239.  
  240. const toggleUrlWindow = () => {
  241. if (urlWindow.classList.contains("show")){
  242. hideUrlWindow()
  243. } else {
  244. showUrlWindow()
  245. }
  246. }
  247.  
  248. let initHeight = window.innerHeight
  249. window.onresize = () => {
  250. if (window.innerHeight < initHeight && urlWindow.classList.contains("show")) shoutCont.scrollIntoView(false)
  251. }
  252.  
  253. const urlField = document.createElement("input")
  254. urlField.id = "urlField"
  255. urlField.classList.add("url-inputs")
  256. urlField.placeholder = "URL"
  257. urlWindow.appendChild(urlField)
  258. urlField.onmouseover = () => urlField.focus()
  259.  
  260. const labelField = document.createElement("input")
  261. labelField.id = "labelField"
  262. labelField.classList.add("url-inputs")
  263. labelField.placeholder = "Label (Optional)"
  264. urlWindow.appendChild(labelField)
  265.  
  266. const submitURL = document.createElement("input")
  267. submitURL.type = "button"
  268. submitURL.id = "submitURL"
  269. submitURL.value = "Submit"
  270. urlWindow.appendChild(submitURL)
  271.  
  272. const urlBtn = document.createElement("span")
  273. urlBtn.id = "urlBtn"
  274. urlBtn.innerHTML = `<i class="material-icons">URL</i>`
  275. urlBtn.classList.add("inline-submit-btn")
  276. ibbCont.insertBefore(urlBtn, ibbCont.childNodes[4])
  277.  
  278. document.addEventListener("click", e => {
  279. if(!e.target.matches("#urlBtn i")) return
  280. if (shout.value != "") shout.value += " "
  281. toggleUrlWindow()
  282. urlField.focus()
  283. })
  284.  
  285. document.addEventListener("click", e => {
  286. if(!e.target.matches(".inline-submit-btn:not(#urlBtn) i")) return
  287. hideUrlWindow()
  288. })
  289.  
  290. const clearFields = () => {
  291. urlField.value = ""
  292. labelField.value = ""
  293. }
  294.  
  295. const urlTagCreate = () => {
  296. let rawURL = urlField.value.trim()
  297. let label = labelField.value
  298.  
  299. //url checks
  300. if (rawURL.length > 150) return "URL is too long.\nConsider shortening it using URL shorteners."
  301. if (!/^https:\/\//i.test(rawURL)) return "Please enter a safe https URL."
  302. if (!/^https:\/\/.*\./i.test(rawURL)) return "Please make sure the URL is correct."
  303.  
  304. //Submitting url
  305. if (shout.value != "" && shout.value.slice(-1) != " ") shout.value += " "
  306.  
  307. if (label != ""){
  308. shout.value += `[url=${rawURL}]${label}[/url] `
  309. } else {
  310. shout.value += `[url]${rawURL}[/url] `
  311. }
  312. hideUrlWindow()
  313. clearFields()
  314. shout.focus()
  315. }
  316.  
  317. submitURL.onclick = () => {
  318. let error = urlTagCreate()
  319. if (typeof error != "undefined" || error != null){
  320. alert(error)
  321. clearFields()
  322. }
  323. }
  324. document.addEventListener("keypress", e => {
  325. if (e.target.matches(".url-inputs")){
  326. if (e.key === "Enter"){
  327. let error = urlTagCreate()
  328. if (typeof error != "undefined" || error != null){
  329. alert(error)
  330. clearFields()
  331. }
  332. }
  333. }
  334. })
  335.  
  336. }
  337.  
  338. ////////////////////////////////////////////////////////////////////////////////////////////////
  339. //Function calls
  340.  
  341. if (enableEasyMention) easyMention()
  342. if (enableUrlBtn) urlBtn()
  343. //appending css to the document
  344. if (typeof css !== "undefined" || css !== null){
  345. if (typeof GM_addStyle !== "undefined") {
  346. GM_addStyle(css)
  347. } else {
  348. let styleNode = document.createElement("style")
  349. styleNode.appendChild(document.createTextNode(css))
  350. ;(document.querySelector("head") || document.documentElement).appendChild(styleNode)
  351. }
  352. }
  353.  
  354. ////////////////////////////////////
  355. //Some extras
  356.  
  357. // Making sure On Screen Keyboard shows Capital letters at the start of sentences (beta)
  358. document.querySelector("#shout_text")?.setAttribute("autocapitalize", "on")
  359.  
  360. //Adding meta for Iceberg Android to fix auto zoom issues
  361. document.querySelector("meta[name='viewport']")?.setAttribute("content", "width=device-width, height=device-height, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no")
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.