Greasy Fork

Greasy Fork is available in English.

Torrent Quick Search

Toggle for Searching Torrents via Search aggegrator

当前为 2022-10-24 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Torrent Quick Search
// @namespace  https://github.com/TMD20/torrent-quick-search
// @supportURL https://github.com/TMD20/torrent-quick-search
// @version     1.57
// @description Toggle for Searching Torrents via Search aggegrator
// @icon        https://cdn2.iconfinder.com/data/icons/flat-icons-19/512/Eye.png
// @author      tmd
// @noframes
// @run-at document-end
// @require https://openuserjs.org/src/libs/sizzle/GM_config.min.js
// @grant              GM_getValue
// @grant              GM_setValue
// @grant GM.xmlhttpRequest
// @grant GM.registerMenuCommand
// @grant GM.notification
// @match https://animebytes.tv/requests.php?action=viewrequest&id=*
// @match https://animebytes.tv/series.php?id=*
// @match https://animebytes.tv/torrents.php?id=*
// @match https://blutopia.xyz/requests/*
// @match https://blutopia.xyz/torrents/*
// @match https://beyond-hd.me/requests/*
// @match https://beyond-hd.me/torrents/*
// @match https://beyond-hd.me/library/title/*
// @match https://imdb.com/title/*
// @match https://www.imdb.com/title/*
// @match https://www.themoviedb.org/movie/*
// @match https://www.themoviedb.org/tv/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/lib/semaphore.min.js
// @license MIT
// ==/UserScript==

`
General Functions
Functions that don't fit in any other catergory
`



function recreateController()
{
	controller = new AbortController();
}
function semaphoreLeave(){
  if(sem &&sem.current>0){
    sem.leave()
  }
}

searchObj = {
	ready: true,
	search()
	{
		if (controller.signal.aborted)
		{
			return Promise.reject(AbortError)
		}
		return new Promise(async (resolve, reject) =>
			{
				controller.signal.addEventListener("abort", () =>
				{
					reject(AbortError)
				});

				document.querySelector("#torrent-quicksearch-msgnode").textContent = "Loading"
				indexers = await getIndexers()
      	document.querySelector("#torrent-quicksearch-msgnode").textContent = "Fetching Results From Indexers"
        imdb=await setIMDBNode()
        setTitleNode()
				//reset count
				let count = []
				let length = indexers.length
				let data = []
				let x = Number.MAX_VALUE
				while (indexers.length)
				{
					// x at a time
					newData = await Promise.allSettled(indexers.splice(0, Math.min(indexers.length, x)).map((e) => searchIndexer(e, imdb, length, count)))
					data = [...data, ...newData]
				}
				console.log(data)
				errorMsgs = data.filter((e) => e["status"] == "rejected").map((e) => e["reason"].message)
				errorMsgs = [...new Set(errorMsgs)]
				if (errorMsgs.length > 0)
				{
					reject(errorMsgs.join("\n"))
				}
				resolve()

			}


		)


	},

	cancel()
	{
		controller.abort()
	},




	async setup()
	{
		this.searchPromise = new Promise((resolve, reject) =>
		{
			this.timeout = setTimeout(async () =>
			{
				try
				{
					resolve(await this.search())
				}
				catch (e)
				{
					reject(e)
				}



			}, 1000)
		})
	},

	async doSearch()
	{
			showDisplay()
      recreateController()
			await this.setup()

    setTimeout(()=>{
            	resetResultList()
	resetSearchDOM()
            getTableHead()
    },0)



    setTimeout(async()=>{
      //reset
      sem=semaphore(10)




      try{
      await this.searchPromise
      this.finalize()

      }
    		catch (error)
		{
			if (error.message.match(/aborted!/i) === null)
			{
				GM.notification(error.message, program, searchIcon)
			}

			console.log(error)

		}

    },100)


	},
	finalize()
	{
		if (Array.from(document.querySelectorAll(".torrent-quicksearch-resultitem")).length == 0)
		{
			this.nomatchID = setTimeout(() => document.querySelector("#torrent-quicksearch-resultlist").textContent = "No Matches", 1000)
		}
		this.finalmsgID = setTimeout(() => document.querySelector("#torrent-quicksearch-msgnode").textContent = "Finished", 1000)
		this.removemsgnodeID = setTimeout(() => {
      document.querySelector("#torrent-quicksearch-msgnode").style.display = "none", 3000
            document.querySelector("#torrent-quicksearch-msgnode").textContent = ""

    })



	},
	async toggleSearch()
	{
		content = document.querySelector("#torrent-quicksearch-box")
		if (content.style.display === "inline-block")
		{
			hideDisplay()
			searchObj.cancel()
		}
		else if ((content.style.display === "none" || content.style.display === ""))
		{
      customSearch = false
			await this.doSearch()


		}
	}
}

function searchIndexer(indexerObj, imdb, total, count)
{
	if (controller.signal.aborted)
	{
		return Promise.reject(AbortError)
	}
	return new Promise(async (resolve, reject) =>
	{

		msg = null
		controller.signal.addEventListener("abort", () =>
		{
			reject(AbortError)
		});
		searchprograms = GM_config.get('searchprogram')
		let data = null
		if (searchprogram == "Prowlarr")
		{
			data = await searchProwlarrIndexer(indexerObj, controller)
		}
		else if (searchprogram == "Jackett")
		{
			data = await searchJackettIndexer(indexerObj)
		}
		else if (searchprogram == "NZBHydra2")
		{

			data = await searchHydra2Indexer(indexerObj)
		}


		msg = `Results fetched fom ${indexerObj["Name"]}:${count.length+1}/${total} Indexers completed`
		data = data.filter((e) => imdbFilter(e, imdbCleanup(imdb)))
		data.forEach((e) =>
		{
			if (e["ImdbId"] == 0 || e["ImdbId"] == null)
			{
				e["ImdbId"] = imdbParserFail
			}

		})
		data = data.filter((e) => currSiteFilter(e["InfoUrl"]))


		addResultsTable(data)
		count.push(indexerObj["ID"])
		document.querySelector("#torrent-quicksearch-msgnode").textContent = msg
		console.log(msg)
		resolve(data)

	})


}

async function searchProwlarrIndexer(indexer)
{
	console.log(getSearchURLProwlarr(indexer["ID"]))
  req = await fetch(getSearchURLProwlarr(indexer["ID"]),
	{
		"timeout": indexerSearchTimeout
	})
	let data = JSON.parse(req.responseText) ||[]
  let dataCopy=[...data]
  let promiseArray=[]
  let x=Number.MAX_VALUE
	while(dataCopy.length){
 newData  = await Promise.allSettled(dataCopy.splice(0, Math.min(dataCopy.length, x)).map(async (e) =>
	{
		return {
			"Title": e["title"],
			"Indexer": e["indexer"],
			"Grabs": e["grabs"],
			"PublishDate": e["publishDate"],
			"Size": e["size"],
			"Leechers": e["leechers"],
			"Seeders": e["seeders"],
			"InfoUrl": e["infoUrl"],
			"DownloadUrl": e["downloadUrl"],
			"ImdbId": e["imdbId"],
			"Cost": e["indexerFlags"].includes("freeleech") == "100% Freeleech" ? "100% Freeleech" : "Cost Unknown With Prowlarr",
			"Protocol": e["protocol"],
		}

	}))
  promiseArray=[...promiseArray,...newData]
  }
	return promiseArray.map((e) => e["value"]).filter((e) => e != null)






}

async function searchJackettIndexer(indexer)
{
	req = await fetch(getSearchURLJackett(indexer["ID"]),
	{
		"timeout": indexerSearchTimeout
	})
	let data = JSON.parse(req.responseText)["Results"] ||[]
  let dataCopy=[...data]
  let promiseArray=[]
  let x=Number.MAX_VALUE
	while(dataCopy.length){
 newData  = await Promise.allSettled(dataCopy.splice(0, Math.min(dataCopy.length, x)).map(async (e) =>
	{
		return {
			"Title": e["Title"],
			"Indexer": e["Tracker"],
			"Grabs": e["Grabs"],
			"PublishDate": e["PublishDate"],
			"Size": e["Size"],
			"Leechers": e["Peers"],
			"Seeders": e["Seeders"],
			"InfoUrl": e["Details"],
			"DownloadUrl": e["Link"],
			"ImdbId": e["Imdb"],
			"Cost": `${(1-e["DownloadVolumeFactor"])*100}% Freeleech`,
			"Protocol": "torrent",
		}
	}))
  promiseArray=[...promiseArray,...newData]
  }
	return promiseArray.map((e) => e["value"]).filter((e) => e != null)
}
async function searchHydra2Indexer(indexer)
{
	req = await fetch(getSearchURLHydraTor(indexer["ID"]),
	{
		"timeout": indexerSearchTimeout
	})
	req2 = await fetch(getSearchURLHydraNZB(indexer["ID"]),
	{
		"timeout": indexerSearchTimeout
	})
	parser = new DOMParser();
	let data = [...Array.from(parser.parseFromString(req.responseText, "text/xml").querySelectorAll("channel>item")), ...Array.from(parser.parseFromString(req2.responseText, "text/xml").querySelectorAll("channel>item"))]
  let dataCopy=[...data]
  let promiseArray=[]
  let x=Number.MAX_VALUE



	while(dataCopy.length){
 newData  = await Promise.allSettled(dataCopy.splice(0, Math.min(dataCopy.length, x)).map(async (e) =>
			//array is final dictkey,queryselector,attribute

	{
			t = [
				["Title", "title", "textContent"],
				["Indexer", "[name=hydraIndexerName]", "null"],
				["Leechers", "[name=peers]", "null"],
				["Seeders", "[name=seeders]", "null"],
				["Cost", "[name=downloadvolumefactor]", "null"],
				["PublishDate", "pubDate", "textContent"],
				["Size", "size", "textContent"],
				["InfoUrl", "comments", "textContent"],
				["DownloadUrl", "link", "textContent"],
				["ImdbId", "[name=imdb]", "null"]
			]
			out = {}
			out["Grabs"] = "Hydra Does not Report"

			for (i in t)
			{
				key = t[i][0]
				node = e.querySelector(t[i][1])
				textContent = (t[i][2] == "textContent")
				if (!node)
				{
					continue
				}

				if (textContent)
				{
					out[key] = node.textContent
				}
				else if (key == "cost")
				{
					out[key] = `${(1-node.getAttribute("value"))*100}% Freeleech`
				}
				else
				{
					out[key] = node.getAttribute("value")
				}

			}
			out["Protocol"] = data[0].querySelector("enclosure").getAttribute("type") == "application/x-bittorrent" ? "torrent" : "usenet"
			return out

		}

	))
  promiseArray=[...promiseArray,...newData]
  }
	return promiseArray.map((e) => e["value"]).filter((e) => e != null)
}

function fetch(url,
{
	method = "GET",
	data = null,
	headers = {},
	timeout = 90000,
  semaphore=true
} = {})

{
    async function semforeFetch(){
           return new Promise((resolve, reject) =>{
           sem.take(async()=>{


			controller.signal.addEventListener("abort", () =>
			{
				reject(AbortError)
			});
			setTimeout(() => reject(AbortError), timeout)
			GM.xmlhttpRequest(
			{
				'method': method,
				'url': url,
				'data': data,
				'headers': headers,
				onload: response =>
				{
					semaphoreLeave()
          resolve(response)
				},
				onerror: response =>
				{
           semaphoreLeave()
					reject(response.responseText)
				},
			})
		})})}










      async function normalFetch(){

      return new Promise((resolve,reject)=>{
       		controller.signal.addEventListener("abort", () =>
			{
				reject(AbortError)
			});
			setTimeout(() => reject(AbortError), timeout)
			GM.xmlhttpRequest(
			{
				'method': method,
				'url': url,
				'data': data,
				'headers': headers,
				onload: response =>
				{
          resolve(response)
				},
				onerror: response =>
				{
					reject(response.responseText)
				},
			})
      })}




  if(semaphore){
   return semforeFetch()

  }


  else{
    return normalFetch()
  }



}

function getParser()
{
	siteName = standardNames[window.location.host] || window.location.host
	data = infoParser[siteName]
	if (data === undefined)
	{
		msg = "Could not get Parser"
		GM.notification(msg, program, searchIcon)
		throw new Error(msg);

	}
	return data
}

function verifyConfig()
{
	if (GM_config.get('searchapi', "null") == "null" || GM_config.get('searchurl', "null") == "null")
	{
		return false
	}

	if (GM_config.get('searchapi', "null") == "" || GM_config.get('searchurl', "null") == "")
	{
		return false
	}
	return true

}

`
DOM Manipulators

These Functions are used to manipulate the DOM
`

function setTitleNode(){
  	if (customSearch == false)
				{
					document.querySelector("#torrent-quicksearch-customsearch").value = getTitle()
				}

}
async function setIMDBNode(){
    		imdb = null
				//Get Old IMDB
				if (document.querySelector("#torrent-quicksearch-imdbinfo").textContent != imdbParserFail &&
					document.querySelector("#torrent-quicksearch-imdbinfo").textContent.length != 0 &&
					document.querySelector("#torrent-quicksearch-imdbinfo").textContent != "None"
				)
				{
					imdb = document.querySelector("#torrent-quicksearch-imdbinfo").textContent
				}
				//Else get New IMDB
				else
				{
					imdb = await getIMDB()
					document.querySelector("#torrent-quicksearch-imdbinfo").textContent = imdb || imdbParserFail
				}
    return imdb
  }
function resetSearchDOM()
{
	document.querySelector("#torrent-quicksearch-imdbinfo").textContent = "None"
	document.querySelector("#torrent-quicksearch-msgnode").textContent = "Waiting"
}

function hideDisplay()
{
	document.querySelector("#torrent-quicksearch-overlay").style.setProperty('--icon-size', `${iconSmall}%`);
	document.querySelector("#torrent-quicksearch-customsearch").value = ""
	document.querySelector("#torrent-quicksearch-box").style.display = "none"

}

function showDisplay()
{
	document.querySelector("#torrent-quicksearch-msgnode").textContent = ""

	document.querySelector("#torrent-quicksearch-msgnode").style.display = "block"
	document.querySelector("#torrent-quicksearch-overlay").style.setProperty('--icon-size', `${iconLarge}%`);
	content.style.display = "inline-block";
}


function getTableHead()
{
	node = document.querySelector("#torrent-quicksearch-resultheader");
	node.innerHTML = `
       <span class="torrent-quicksearch-resultcell"  >Links</span>
       <span class="torrent-quicksearch-resultcell"  >Clients</span>
    <span class="torrent-quicksearch-resultcell"  >Title</span>
    <span class="torrent-quicksearch-resultcell"  >Indexer</span>
    <span class="torrent-quicksearch-resultcell"  >Grabs</span>
    <span class="torrent-quicksearch-resultcell"  >Seeders</span>
    <span class="torrent-quicksearch-resultcell"  >Leechers</span>
  <span class="torrent-quicksearch-resultcell"  >DLCost</span>
    <span class="torrent-quicksearch-resultcell"  >Date</span>
    <span class="torrent-quicksearch-resultcell">Size</span>
    <span class="torrent-quicksearch-resultcell">IMDB</span>

`

	Array.from(node.children).forEach((e, i) =>
	{
		e.style.gridColumnStart = i + 1
		e.style.fontSize = `${GM_config.get("fontsize",12)}px`
	})

}

function addResultsTable(data)
{
	if (data.length == 0)
	{
		return
	}
	resultList = document.querySelector("#torrent-quicksearch-resultlist")
	tempFrag = new DocumentFragment()

	data.forEach((e, i) =>
	{
		let node = document.createElement("span");
		node.setAttribute("class", "torrent-quicksearch-resultitem")
		node.innerHTML = `
    <span class="torrent-quicksearch-resultcell torrent-quicksearch-links"  style='grid-column-start:1' >
        <a href=${e['DownloadUrl']}>Download</a>
         <br>
        <br>
        <a href=${e['InfoUrl']}>Details</a>
  </span>
    <span style='grid-column-start:2'>
      <form>
      <span>
      <select class=torrent-quicksearch-clientSelect>
      </select>
      </span>
        <span>
          <span class="tooltip">
        <button class=torrent-quicksearch-clientSubmit>Send</button>
  <span class="tooltiptext">Arr Clients imdbID sent from entry if null then page</span>
      </span>
        </span>
       </form>



  </span>
    <span class="torrent-quicksearch-resultcell" style='grid-column-start:3' >${e['Title']}</span>
    <span class="torrent-quicksearch-resultcell" style='grid-column-start:4'  >${e['Indexer']}</span>
    <span class="torrent-quicksearch-resultcell" style='grid-column-start:5'>${e['Grabs']||"No Data"} </span>
  <span class="torrent-quicksearch-resultcell" style='grid-column-start:6'>${e['Seeders']||"No Data"} </span>
  <span class="torrent-quicksearch-resultcell" style='grid-column-start:7' >${e['Leechers']||"No Data"} </span>
 <span class="torrent-quicksearch-resultcell" style='grid-column-start:8'>${e['Cost']} </span>
 <span class="torrent-quicksearch-resultcell" style='grid-column-start:9' >${new Date(e['PublishDate']).toLocaleString("en-CA")}</span>
 <span class="torrent-quicksearch-resultcell" style='grid-column-start:10' >${(parseInt(e['Size'])/1073741824).toFixed(2)} GB</span>
<span class="torrent-quicksearch-resultcell" style='grid-column-start:11' >${e['ImdbId']}</span>`

      let selNode=node.querySelector("select")

       JSON.parse(GM_config.getValue("downloadClients","[]")).forEach((e)=>{
        let optnode=document.createElement("option")
        optnode.setAttribute("id",e.clientID)
        optnode.setAttribute("value",e.clientID)
         optnode.textContent=e.clientName
         selNode.appendChild(optnode)

      })
    node.querySelector("form").addEventListener("submit",clientFactory(e))

		tempFrag.append(node)
	})

	resultList.appendChild(tempFrag)

}

function resetResultList()
{
	document.querySelector("#torrent-quicksearch-resultheader").textContent = ""
	document.querySelector("#torrent-quicksearch-resultlist").textContent = ""
}





function createMainDOM()
{
	const box = document.createElement("div");
	box.setAttribute("id", "torrent-quicksearch-overlay");
	rowSplit = 12
	contentWidth = 70
	boxMinHeight = 5
	boxMaxHeight = 100
	boxHeight = 40
	boxWidth = 70
	boxMaxWidth = 150
	box.innerHTML = `
 <div>
  <img id="torrent-quicksearch-toggle" src="${searchIcon}"></img>
<div id="torrent-quicksearch-box">
<div id="torrent-quicksearch-content">
  <div>
  <div id="torrent-quicksearch-msgnode"></div>
  <div id="torrent-quicksearch-custombox">
        <div>
      <label>Title:</label>
    <input type="text" id="torrent-quicksearch-customsearch">
     <label>IMDB:</label>
    <div id="torrent-quicksearch-imdbinfo">None</div>
    <button id="torrent-quicksearch-customsearchbutton">Custom Search</button>
        </div>
  </div>
    <div id="torrent-quicksearch-resultheader"></div>
</div>


  <div id="torrent-quicksearch-resultlist">
  </div>

    </div>
</div>
<style>
  /*     Variables */
  #torrent-quicksearch-overlay {
  --grid-size: max(calc(50vw/${rowSplit}),calc(100%/${rowSplit}));
  --icon-size:${iconSmall}%;
    --icon-padding:${paddingSmall}%;

  }
   #torrent-quicksearch-overlay {
       position: sticky;
       display: flex;
       flex-direction: column;
       gap: 10px;
       top: 40vh;
       pointer-events: none;
       z-index: 900000;
   }


    #torrent-quicksearch-overlay> div:first-of-type {
       position: absolute;
       left:80vw;

   }

 * {
    font-size:${GM_config.get("fontsize",12)}px;
  }



  #torrent-quicksearch-toggle {
  margin-left: auto;
  display:block;
  cursor: pointer;
  pointer-events:all;
  width:  var(--icon-size);
  height:  var(--icon-size);
  padding-top: var(--icon-padding);
  padding-bottom:var(--icon-padding);
    margin-bottom:calc(${paddingLarge}vh - var(--icon-padding));
  margin-top:calc(${paddingLarge}vh - var(--icon-padding));
}
    #torrent-quicksearch-box{
  resize:both;
  direction:rtl;
   right:5vw;
  margin-right:auto;
  position:absolute;
  display:none;
  min-height: ${boxMinHeight}vh;
  max-height:${boxMaxHeight}vh;
  height: ${boxHeight}vh;
  width: ${boxWidth}vw;
  max-width: ${boxMaxWidth}vw;
  overflow:hidden;
  border:solid black 5px;
  }


  #torrent-quicksearch-msgnode{
    background-color:#FFFFFF;
    width:calc(var(--grid-size)*${rowSplit});
    display:none;
  height:calc(((${GM_config.get("fontsize",12)}em) + 2em)/16);

  }

     #torrent-quicksearch-custombox {
      background-color:#FFFFFF;
      width:calc(var(--grid-size)*${rowSplit});
         pointer-events:all;
         height:calc(((${GM_config.get("fontsize",12)}em) + 2em) * (2/16));


   }

    #torrent-quicksearch-custombox>div {
       display: flex;
      background-color:#FFFFFF;
      flex-direction:row;
      justify-content: center;
       width: 100%

   }
  #torrent-quicksearch-custombox>div >label {
       margin-left:2.5%;
           margin-right:2.5%;

   }
    #torrent-quicksearch-custombox>div >button {
       margin-left:2.5%;

   }

    #torrent-quicksearch-customsearch{
    background-color:#FFFFFF;
   border:solid black 2px;
  flex-grow:1;

  }

  #torrent-quicksearch-custombox > label{
  margin-left:2%
  margin-right:2%
  }
  #torrent-quicksearch-customsearchbutton {
  background-color: #4CAF50;
  border: none;
  color: white;
  text-align: center;
  text-decoration: none;
  font-size: ${(GM_config.get("fontsize",12)+2)}px;
}

  #torrent-quicksearch-content {
  pointer-events:all;
  background-color:  #D7C49EFF;
   direction:ltr;
  height:100%;
  width:100%;
}

  #torrent-quicksearch-content>div:nth-child(2) {
  scrollbar-color: white;
  overflow:scroll;
  width:100%;
  height:calc(100% - ((${GM_config.get("fontsize",12)}em) + 2em)*(4/16));

}

    #torrent-quicksearch-content>div:nth-child(1) {
  width:100%;
   background-color: #B1D79E;
}



    #torrent-quicksearch-resultlist{
    border:solid white 5px;
    width:calc(var(--grid-size)*${rowSplit});

  }

  .torrent-quicksearch-resultitem,#torrent-quicksearch-resultheader{
  display: grid;
    grid-template-columns: repeat(${rowSplit},var(--grid-size));
    width:calc((var(--grid-size)*${rowSplit})-10);
  }

  .torrent-quicksearch-resultitem{
    font-size:${GM_config.get("fontsize",12)}px;
  }

  #torrent-quicksearch-resultlist>.torrent-quicksearch-resultitem:nth-child(even) {
  background-color: #D7C49EFF;
}
 #torrent-quicksearch-resultlist>.torrent-quicksearch-resultitem:nth-child(odd) {
  background-color: #d5cbcb;
}
  #torrent-quicksearch-resultheader{
background-color: #B1D79E;
  font-size:${GM_config.get("fontsize",12)+2}px;
      height:calc(((${GM_config.get("fontsize",12)}em) + 2em)*(2/16));

  }

  .torrent-quicksearch-resultcell{
  font-weight: bold;
        margin-left: 10%;
overflow-wrap:break-word;
  }


  .torrent-quicksearch-clientSubmit {
  background-color: white;
  border: none;
  text-align: center;
  text-decoration: none;
 font-size:${GM_config.get("fontsize",12)+2}px;
    font-weight: bold;
  overflow: hidden;
white-space: nowrap;
    width:100%''

}


  .torrent-quicksearch-clientSelect {
 font-size:${GM_config.get("fontsize",12)+2}px;
font-weight: bold;
       width:100%;

}


     .torrent-quicksearch-links *{
  color:blue;
  cursor:pointer;
  text-decoration: none;
  }
     .torrent-quicksearch-links *:hover{
  color:white;
  }

.torrent-quicksearch-links *:active,focus{
   animation: pulse 2s infinite;
  }


  @keyframes pulse {
  0% ,100%{
    color: blue;
  }
  50% {
    color: white;
  }
}
  ::-webkit-scrollbar-thumb{
  background-color:white;

  }
  /* Tooltip container */
.tooltip {
  position: relative;
  display: inline-block;
  border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
}

/* Tooltip text */
.tooltip .tooltiptext {
  visibility: hidden;
  width: 120px;
  background-color: black;
  color: #fff;
  text-align: center;
  padding: 5px 0;
  border-radius: 6px;

  /* Position the tooltip text - see examples below! */
  position: absolute;
  z-index: 1;
}

/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
  visibility: visible;
}




<style/>`

	box.querySelector("#torrent-quicksearch-toggle").addEventListener("mousedown", leftClickProcess)
	box.querySelector("#torrent-quicksearch-toggle").addEventListener("mouseup", mouseUpProcess)
	document.addEventListener("mouseup", resetMouse)


	box.querySelector("#torrent-quicksearch-customsearchbutton").addEventListener("click", () =>
	{
	searchObj.cancel()
  setTimeout(()=>{
     if (Date.now() - lastClick < clickLimit)
	{
		return
	}

    lastClick=Date.now()
    customSearch = true
    searchObj.doSearch()

  },0)

  })
	document.body.insertBefore(box, document.body.children[0])


}





`
Matching Function
These help with finding a Match
`

function getTitle()
{
	titleNode = document.querySelector(siteParser["title"])
	if (titleNode == null)
	{
		throw new Error("Title Node Not Found")
	}
	title = titleNode[siteParser["titleAttrib"]]
	title = titleCleanup(title)
	return title
}

async function getIMDB()
{
	let imdb = null

	if (standardNames[window.location.host] == "imdb.com")
	{
		imdb = window.location.href
	}
	else if (standardNames[window.location.host] == "themoviedb.org")
	{

		imdb = await tmdbPageIMDBParser()
	}
	else
	{
		imdbNode = document.querySelector(siteParser["imdb"])
		if (imdbNode == null)
		{
			return null
		}
		imdb = imdbNode[siteParser["imdbAttrib"]]
	}

	imdb = imdbCleanup(imdb)
	return imdb
}

function titleCleanup(title)
{
	title = title.trim().replaceAll(/\n/g, "")
	title = title.replaceAll(/ +/g, " ")
	return title
}

function imdbCleanup(imdb)
{
	if (imdb === null ||
		imdb === undefined ||
		imdb === 0 ||
		imdb === "0")
	{
		return imdb
	}
	imdb = String(imdb)
	imdb = imdb.match(/[0-9]+/).toString()
	imdb = imdb.trim().replaceAll(/\n/g, "")
	imdb = imdb.replace(/imdb/i, "")
	imdb = imdb.replace(/tt/, "")
	imdb = imdb.replace(/[:|&|*|\(|\)|!|@|#|$|%|^|\*|\\|\/]/, "")
	imdb = imdb.replaceAll(/ +/g, "")
	imdb = imdb.replace(/^0+/, "")
	imdb = parseInt(imdb)
	return imdb

}

function imdbFilter(entry, imdb)
{
	if (imdb === null || imdb === "IMDB Not Provided")
	{
		return true
	}
	else if (entry["ImdbId"] == 0)
	{
		return true
	}

	else if (entry["ImdbId"] == imdb)
	{
		return true
	}
	return false

}
async function tmdbExternalMedia(type, id)
{
	key = GM_config.get('tmdbapi', "null")
	if (key == "null")
	{
		return null
	}
	let baseURL =  new URL(`/3/movie/${id}/external_ids`,`https://api.themoviedb.org`).toString()
	let params = new URLSearchParams();
	params.append("api_key", key)
	if (type == "tv")
	{
		baseURL = new URL(`/3/tv/${id}/external_ids`,`https://api.themoviedb.org`).toString()
	}

  searchURL = `${baseURL}?${params.toString()}`
	req = await fetch(searchURL)
	return JSON.parse(req.responseText)

}

async function imdbTMDBConvertor(imdb)
{
	let key = GM_config.get('tmdbapi', "null")
	imdb = String(imdb)
	if (key == "null")
	{
		return null
	}
	if (imdb.match(/tt/i) == null)
	{
		imdb = `tt${imdb}`
	}
	let baseURL = new URL(`/3/find/${imdb}`,`https://api.themoviedb.org`).toString()
	let params = new URLSearchParams();

	params.append("api_key", key)
	params.append("external_source", "imdb_id")
	searchURL = `${baseURL}?${params.toString()}`
	let req = await fetch(searchURL)
	let data=JSON.parse(req.responseText)
  let output=data['tv_results']
  if (data['movie_results'].length>output.length)
    output=data['movie_results']
  return output[0]
}
// First call to tmdbapi should be removed once we parse Movies vs TV


async function tmdbTVDBConvertor(imdb)
{
	key = GM_config.get('tmdbapi', "null")
	if (key == "null")
	{
		return null
	}
	let helperData = await imdbTMDBConvertor(imdb)
  if (helperData==null){
     return
  }

	else if (helperData["media_type"]=="tv")
	{
		return (await tmdbExternalMedia("tv", helperData.id))["tvdb_id"]
	}
	else if (helperData["media_type"]=="movie")
	{
		return (await tmdbExternalMedia("movie", helperData.id))["tvdb_id"]
	}

}

async function tmdbPageIMDBParser()
{
	id = window.location.href.match(/\/[0-9]+/).toString().substring(1)
	if (window.location.href.match(/\/tv\//))
	{
		return (await tmdbExternalMedia("tv", id))["imdb_id"]
	}
	else
	{
		return (await tmdbExternalMedia("movie", id))["imdb_id"]
	}

}



function currSiteFilter(entryURL)
{
	if (GM_config.get('sitefilter') == "false")
	{
		return true
	}
	if (new URL(entryURL).hostname.replace(/\..*$/, "") == window.location.host.replace(/\..*$/, ""))
	{
		return false
	}
	return true

}
`
URL Processing Indexer

Functions Used to Get Indexer Info

`

async function getIndexers()
{
	document.querySelector("#torrent-quicksearch-msgnode").textContent = "Getting Indexers"
	searchprogram = GM_config.get('searchprogram')
	indexers = null
	if (searchprogram == "Prowlarr")
	{
		indexers = await getIndexersProwlarr()
	}
	else if (searchprogram == "Jackett")
	{
		indexers = await getIndexersJackett()
	}

	else if (searchprogram == "NZBHydra2")
	{
		indexers = await getIndexersHydra()
	}

	indexerCacheHelper(indexers)
	return listFilter(indexers)

}


async function getIndexersJackett()
{
	key="jackettIndexers"
  cachedIndexers = GM_getValue(key, "none")
  if(cachedIndexers=="none"){
    null
  }
	else if (Date.now() - (cachedIndexers?.date || 0) < day)
	{
		return cachedIndexers["indexers"]
	}
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));
	//Invalid Category Search
	params.append("Category[]", 20130)
	params.append("Query", "''")
	baseURL = new URL(`/api/v2.0/indexers/all/results`,`${GM_config.get('searchurl')}`).toString()
	indexerURL = `${baseURL}?${params.toString()}`
	req = await fetch(indexerURL)
	data = JSON.parse(req.responseText)['Indexers']
	output=data.map((e)=>{
    let dict={}
    dict["Name"]=e["Name"]
    dict["ID"]=e["ID"]
    return dict
  })
	GM_setValue(key,
	{
		"date": Date.now(),
		"indexers": output
	})
	return output

}

async function getIndexersProwlarr()
{
	key="prowlarrIndexers",
  cachedIndexers = GM_getValue(key,"none")
    if(cachedIndexers=="none"){
    null
  }
	else if (Date.now() - (cachedIndexers?.date || 0) < day)
	{
		return cachedIndexers["indexers"]
	}
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));

	baseURL = new URL(`/api/v1/indexer`,`${GM_config.get('searchurl')}`).toString()

	indexerURL = `${baseURL}?${params.toString()}`
	req = await fetch(indexerURL)
	data = JSON.parse(req.responseText)
	data = data.sort(prowlarIndexSortHelper)
    output=data.map((e)=>{
    let dict={}
    dict["Name"]=e["name"]
    dict["ID"]=e["id"]
    return dict
  })
	GM_setValue(key,
	{
		"date": Date.now(),
		"indexers": output
	})
	return output

}

function prowlarIndexSortHelper(a, b)
{
	if (a["priority"] > b["priority"])
	{
		return -1;
	}
	if (a["priority"] < b["priority"])
	{
		return 1;
	}
	return 0;
}

async function getIndexersHydra()
{
	key="hydraIndexers"
  cachedIndexers = GM_getValue(key, "none")
  if(cachedIndexers=="none"){
    null
  }
	else if (Date.now() - (cachedIndexers?.date || 0) < day)
	{
		return cachedIndexers["indexers"]
	}
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));
	baseURL = new URL(`/api/stats/indexers/`,`${GM_config.get('searchurl')}`).toString()
	indexerURL = `${baseURL}?${params.toString()}`
	req = await fetch(indexerURL)
	data = JSON.parse(req.responseText)
  output=data.map((e)=>{
    let dict={}
    dict["Name"]=e["indexer"]
    dict["ID"]=e["indexer"]
    return dict
  })

	GM_setValue(key,
	{
		"date": Date.now(),
		"indexers": output
	})
	return output

}

function listFilter(allIndexers)
{
	let selectedIndexers = null
	if (GM_config.get('listType') == "black")
	{
		selectedIndexers = blackListHelper(allIndexers)
	}
	else
	{
		selectedIndexers = whiteListHelper(allIndexers)
	}

	output = []

	for (let i in allIndexers)
	{
		if (selectedIndexers.has(allIndexers[i]["ID"]))
		{
			output.push(allIndexers[i])
		}

	}

	return output

}

function indexerCacheHelper(allIndexers)
{
	if (GM_config.get('indexers') == '')
	{
		return
	}
	searchprogram = GM_config.get('searchprogram')
	indexerNames = GM_config.get('indexers').split(",").map((e) => e.trim().toLowerCase())
	for (let j in indexerNames)
	{
		key = `${searchprogram}_${indexerNames[j]}`
		cached = GM_getValue(key, "none")
		if (cached != "none")
		{
			continue
		}
		for (let i in allIndexers)
		{

			if (allIndexers[i]["Name"].match(new RegExp(indexerNames[j], 'i')))
			{
				GM_setValue(key, allIndexers[i]["ID"])
			}
		}

	}
}

function blackListHelper(allIndexers)
{
	indexerID = new Set(allIndexers.map((e) => e["ID"]))
	indexerNames = GM_config.get('indexers').split(",").map((e) => e.trim())

	for (let j in indexerNames)
	{
		key = `${searchprogram}_${indexerNames[j]}`
		cached = GM_getValue(key, "none")
		if (cached != "none")
		{
			indexerID.delete(cached)

		}

	}
	return indexerID

}

function whiteListHelper(allIndexers)
{
	indexerID = new Set()
	indexerNames = GM_config.get('indexers').split(",").map((e) => e.trim())

	for (let j in indexerNames)
	{
		key = `${searchprogram}_${indexerNames[j]}`
		cached = GM_getValue(key, "none")
		if (cached != "none")
		{
			indexerID.add(cached)
		}

	}
	return indexerID
}

`
URL Processing Search

Functions Used to Produce URL for Search
`

function getSearchURLProwlarr(indexer)
{
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));
	params.append("query", `${document.querySelector("#torrent-quicksearch-customsearch").value}`)
	params.append("IndexerIds", indexer)
	baseURL = new URL('/api/v1/search', GM_config.get('searchurl')).toString()
	return `${baseURL}?${params.toString()}`

}

function getSearchURLJackett(indexer)
{
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));
	params.append("Query", `${document.querySelector("#torrent-quicksearch-customsearch").value}`)
	baseURL = new URL(`/api/v2.0/indexers/${indexer}/results`, GM_config.get('searchurl')).toString()
	params.append("cachetime", "20")
	return `${baseURL}?${params.toString()}`

}

function getSearchURLHydraNZB(indexer)
{
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));
	params.append("q", `${document.querySelector("#torrent-quicksearch-customsearch").value}`)
	params.append("indexers", indexer)
	params.append("t", "search")
	params.append("o", "xml")
	params.append("cachetime", "20")
	baseURL = new URL('/api', GM_config.get('searchurl')).toString()
	return `${baseURL}?${params.toString()}`
}

function getSearchURLHydraTor(indexer)
{
	let params = new URLSearchParams();
	params.append("apikey", GM_config.get('searchapi'));
	params.append("q", `${document.querySelector("#torrent-quicksearch-customsearch").value}`)
	params.append("indexers", indexer)
	params.append("t", "search")
	params.append("o", "xml")
	//hydra likes to send no data
	params.append("cachetime", "20")
	baseURL = new URL('/torznab/api', GM_config.get('searchurl')).toString()

	return `${baseURL}?${params.toString()}`
}
`
client Functions
Functions to support Clients


`

function getSonarrURL(clientURL,clientAPI)
{
	let params = new URLSearchParams();
	params.append("apikey", clientAPI);
	baseURL = new URL('/api/v3/release/push', clientURL).toString()
	return `${baseURL}?${params.toString()}`


}

function getRadarrURL(clientURL,clientAPI)
{
	let params = new URLSearchParams();
	params.append("apikey",clientAPI);
	baseURL = new URL('/api/v3/release/push', clientURL).toString()
	return `${baseURL}?${params.toString()}`
}
`
Events

These Functions Create Events to be used by script
`

function leftClickProcess(e)
{
	e.preventDefault()
	e.stopPropagation()
	if (e.button != 0)
	{
		return
	}
	mouseState = "down"
	document.addEventListener("mousemove", mouseDragProcess)
	document.querySelector("#torrent-quicksearch-overlay").style.setProperty('--icon-padding', `${paddingLarge}vh`);

}

function mouseUpProcess(e)
{
	e.preventDefault()
	e.stopPropagation()
	mouseClicksProcess()
	resetMouse()

}

async function mouseClicksProcess()
{
	if (mouseState == "dragged")
	{

		return
	}
	else if (Date.now() - lastClick < clickLimit)
	{
		return
	}

	else if (verifyConfig() == false)
	{
		GM.notification("At Minimum You Need to Set\nSearch URl\nSearch API\nSearch Program", program, searchIcon)
		GM_config.open()
		return
	}
	lastClick = Date.now()
	await searchObj.toggleSearch()
}
//Reset Mouse Events
function resetMouse()
{
	mouseState = "up"
	document.querySelector("#torrent-quicksearch-overlay").style.setProperty('--icon-padding', `${paddingSmall}vh`);
	document.removeEventListener("mousemove", mouseDragProcess)
}

function mouseDragProcess(e)
{
	mouseState = "dragged"
	//check mouse state on enter
	if (mouseState == "up" || e.buttons == 0)
	{
		return
	}
	//poll mouse state
	setInterval(() =>
		{
			if (mouseState == "up" || e.buttons == 0)
			{
				return
			}

		}

		, 1000)

	dragState = true
	toggleHeight = parseInt(getComputedStyle(document.querySelector("#torrent-quicksearch-toggle")).height.replaceAll(/[^0-9.]/g, ''))
	startMousePosition = parseInt(e.clientY)
	offsetMousePosition = startMousePosition - (toggleHeight / 2)
	viewport = (offsetMousePosition / window.innerHeight) * 100
	viewport = Math.max(viewport, -20)
	viewport = Math.min(viewport, 89)
	document.querySelector("#torrent-quicksearch-overlay").style.top = `${viewport}vh`

}


`
Client Functions
Functions For Sending Downloads to Clients
`

function arrIMDBHelper(releaseData){
      pageIMDB= document.querySelector("#torrent-quicksearch-imdbinfo").textContent
     if(releaseData["ImdbId"]!=imdbParserFail){
     return parseInt(releaseData["ImdbId"])
    }
  else if (pageIMDB!=imdbParserFail&&pageIMDB!=null){
      return parseInt(pageIMDB)
  }
  else{
    return
  }

}

async function sendSonarrClient(releaseData,clientData)
{
    releaseData["ImdbId"]=arrIMDBHelper(releaseData)
    if(GM_getValue(key, "none")!="none"){
     		releaseData["TvdbId"]=await tmdbTVDBConvertor(releaseData["ImdbId"]),
		releaseData["tmdbId"]	=(await imdbTMDBConvertor(releaseData["ImdbId"]))?.id
    }





		res = await fetch(getSonarrURL(clientData.clientURL,clientData.clientAPI),
		{
			"method": "post",
			"data": JSON.stringify(releaseData),
      "semaphore":false
		})

		if (res.status != 200)
		{
			GM.notification(res.responseText, program, searchIcon)
			return
		}
		let data = JSON.parse(res.responseText)
		initValue = ""
		finalMsg = data.reduce(
			(prev, curr, index) =>
			{
				if (curr["rejections"].length>0)
				{
					epNums = curr["mappedEpisodeNumbers"].length > 0 ? curr["mappedEpisodeNumbers"].join(",") : "No Episodes"
					errorMsg = [`${curr["seriesTitle"]} Season ${curr["seasonNumber"]} Episodes ${epNums}`, `Status Rejected: ${curr["rejections"].join(",")}`]
					return `${prev}\n\[${errorMsg.join("\n")}\]`
				}
				else if (curr["approved"] == true)
				{
					acceptMsg = `Added ${curr["title"]} to client`
					return `${prev}\n${acceptMsg}`
				}

			}, initValue)
		GM.notification(finalMsg, program, searchIcon)



}

async function sendRadarrClient(releaseData,clientData)
{




   releaseData["ImdbId"]=arrIMDBHelper(releaseData)
    if(GM_getValue(key, "none")!="none"){
     		releaseData["TvdbId"]=await tmdbTVDBConvertor(releaseData["ImdbId"]),
		releaseData["tmdbId"]	=(await imdbTMDBConvertor(releaseData["ImdbId"]))?.id
    }
  res = await fetch(getRadarrURL(clientData.clientURL,clientData.clientAPI),

		{
			"method": "post",
			"data": JSON.stringify(releaseData),
      "headers":{"content-type":"application/json"},
      "semaphore":false

		})

		if (res.status != 200)
		{
			GM.notification(res.responseText, program, searchIcon)
			return
		}
		let data = JSON.parse(res.responseText)
		initValue = ""

		finalMsg = data.reduce(
			(prev, curr, index) =>
			{
				if (curr["rejected"] == true)
				{
					errorMsg = [`${curr["movieTitles"].join(",")}`, `Status Rejected: ${curr["rejections"].join(",")}`]
					return `${prev}\n\[${errorMsg.join("\n")}\]`
				}
				if (curr["approved"] == true)
				{
					acceptMsg = `Added ${curr["title"]} to client`
					return `${prev}\n${acceptMsg}`
				}

			}, initValue)
		GM.notification(finalMsg, program, searchIcon)


}



function clientFactory(releaseData)
{
	clientEvent = async function (e)
	{
		e.preventDefault()
		e.stopPropagation()
    clientData=JSON.parse(GM_getValue("downloadClients","[]")).filter((e)=>e.clientID==event.target.querySelector("select").value)[0]
    if(clientData.clientType=="Sonarr"){
      sendSonarrClient(releaseData,clientData)
    }
    else if(clientData.clientType=="Radarr"){
      sendRadarrClient(releaseData,clientData)
    }



  }

	return clientEvent

}




`
GM_Config Functions
`


function saveOptions(values){
  saveDownloadClient()
 recreateDownloadClientNode()



}

function saveDownloadClient(){
wrapper=GM_config.fields["downloadclients"].wrapper
   function verify(obj){
        missingName="Could Not add new Client\nClientName is missing"
        missingValue=`Could Not add new Client\nClientType ${obj["clientType"]} is missing one of it's required values`

        if(Object.values(obj).filter(e=>e!="").length<=3){
          return false
        }

        if(obj["clientName"]==""){
          GM.notification(missingName, program, searchIcon)
          return false
          }

          if(obj["clientType"]=="Sonarr"||obj["clientType"]=="Radarr"){
             if(obj["clientURL"]!=""&&obj["clientAPI"]!=""){
             return true
          }
            GM.notification( missingValue, program, searchIcon)

            return false
          }

          //Add More conditionals for other clients
          else{
              if(obj["clientURL"]!=""&&obj["clientAPI"]!=""){
             return true
          }
            GM.notification( missingValue, program, searchIcon)
            return false
          }



          }
          if (wrapper) {
            let inputs = wrapper.querySelectorAll("input,select")
            let val=JSON.parse(GM_config.getValue("downloadClients","[]"))
            let outdict={}
            outdict["clientID"]=Array(10).fill().map(()=>"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".charAt(Math.random()*62)).join("")

            for(i in inputs){
              ele=inputs[i]
              outdict[ele.id]=ele.value
            }
            if(verify(outdict)){
            val.push(outdict)
            GM_config.setValue("downloadClients",JSON.stringify(val))
            }


}
}

function getCurrentDownloadClientsNode(){
   let parent=document.createElement("div")
   parent.setAttribute("id","torrent-quicksearch-downloadclientsParent")
   titleNode=document.createElement("h1")
   titleNode.textContent="Current Clients"
   parent.append(titleNode)
   let clients=JSON.parse(GM_config.getValue("downloadClients","[]"))
   for(i in clients){
     //delete button
     button=document.createElement("button")
     button.setAttribute("class","torrent-quicksearch-downloadclientsDelete")
     button.textContent="Delete Client"
     button.addEventListener("click",deleteClient)
     parent.append(button)

     //client info box
     let client=clients[i]
     keys=Object.keys(client)
     let section=document.createElement("div")
     section.setAttribute("class","torrent-quicksearch-downloadclients")
     section.append()

     for (j in keys){
       key=keys[j]
       value=client[key]
       let node=document.createElement("div")
       let keyNode=document.createElement("span")
       keyNode.textContent=`${key}: `
       keyNode.style.display="inline-block"
       keyNode.style.fontWeight="bold"
       keyNode.style.marginRight="5px"


       let valNode=document.createElement("span")
       valNode.textContent=`${value}`
       valNode.style.display="inline-block"
       node.append(keyNode)
       node.append(valNode)
       section.append(node)
     }

     parent.append(section)
   }
  return parent
}
function deleteClient(e){
  let clientNode=e.target.nextElementSibling
 let clientID=Array.from(clientNode.childNodes).filter((e)=>e.textContent.match(/clientID/))[0].childNodes[1].textContent
  let clients=JSON.parse(GM_config.getValue("downloadClients","[]"))
  GM_config.setValue("downloadClients",JSON.stringify(clients.filter((e)=>e.clientID!=clientID)))
  recreateDownloadClientNode()

}

function recreateDownloadClientNode(){
  let wrapper=GM_config.fields["downloadclients"].wrapper
  let oldParent=wrapper.querySelector("#torrent-quicksearch-downloadclientsParent")
  let newParent=getCurrentDownloadClientsNode()
  oldParent.parentElement.replaceChild(newParent,oldParent)

}

function downloadClientNode(configId) {
          var field = this.settings,
              id = this.id,
              create = this.create,

              retNode = create('div', { className: 'config_var',
                id: configId + '_' + id + '_var',
                title: field.title || '' });
              currentClients=getCurrentDownloadClientsNode()
              newSubmissionForm=document.createElement("div")
              newSubmissionForm.innerHTML=
              `
              <h1>Add New Client</h1>
              <form>
              <div>
              <label for="client-select">Client Type:</label>

<select id="clientType">
    <option value="Sonarr">Sonarr</option>
    <option value="Radarr"option>Radarr</option>
</select>

              </div>
  <br>
              <div>
                                              <label for="Name">Client Name:</label>

                <input type="text" placeholder="Name"  id="clientName">

              </div>
              <br>
              <div>
              <label for="clientURL">Client URL:</label>

              <input type="text" placeholder="URL" id="clientURL">

            </div>
  <br>
                    <div>
              <label for="clientAPI">Client API:</label>

              <input type="text" placeholder="API" id="clientAPI">

            </div>
<br>

        <div>
              <label for="clientUserName">Client Username:</label>
              <input type="text" placeholder="Username" id="clientUserName">

            </div>
<br>
        <div>
              <label for="clientPassword">Client Password:</label>

              <input type="" placeholder="Password" id="clientPassword">

            </div>
  </form>
`
          retNode.appendChild(currentClients)
          retNode.appendChild(newSubmissionForm)

          return retNode;
        }
function openMenu()
{


	hideDisplay()
	resetResultList()
	resetSearchDOM()
	document.querySelector("#torrent-quicksearch-overlay").style.setProperty('--icon-size', `${iconLarge}%`);
	document.querySelector("#torrent-quicksearch-toggle").removeEventListener("mousedown", leftClickProcess)
	document.querySelector("#torrent-quicksearch-toggle").removeEventListener("mouseup", mouseUpProcess)

	searchObj.cancel()
}

function closeMenu()
{

/*	url = GM_config.get('searchurl')
	if (url.match(/htt(p|ps):\/\//) == null)
	{
		url = `http://${url}`
	}
	GM_config.set('searchurl', url)
	GM_config.save()*/
	hideDisplay()
  document.querySelector("#torrent-quicksearch-overlay").style.setProperty('--icon-size', `${iconSmall}%`);

	document.querySelector("#torrent-quicksearch-toggle").addEventListener("mousedown", leftClickProcess)
	document.querySelector("#torrent-quicksearch-toggle").addEventListener("mouseup", mouseUpProcess)

}
function initConfig(){
GM_config.init(
{
	'id': 'torrent-quick-search',
  'title': 'Torrent Quick Search Settings', // Panel Title
  'fields':

  {

			'tmdbapi':
			{
				'label': 'TMDB API Key',
				'type': 'text',
				'title': 'TMDB Key For TMDB/IMDB conversion'
			},

			'searchurl':
			{

				'label': 'Search URL',
				'section': ['Search'],
				'type': 'text',
				'title': 'Base URl for search program'
			},
			'searchapi':
			{
				'label': 'API Key',
				'type': 'text',
				'title': 'API key for search program'

			},
			'searchprogram':
			{
				'label': 'Search Program',

				'type': 'select',
				'options': ['Prowlarr', 'Jackett', "NZBHydra2"],
				'title': 'Which search program'
			},

			'indexers':
			{
				'label': 'Indexers',
				'section': ['Indexers'],
				'type': 'text',
				'title': 'Comma Seperated List of Indexers Names'
			},

			'listType':
			{
				'type': 'radio',
				'options': ['black', 'white'],
				'label': 'Indexers ListType',
				'title': 'Use White list if you want to manually approve indexers\nUse Black list if you want to use all Indexers, and just have few or none to disable',
         'default': 'black'

			},

			'sitefilter':
			{
				'label': 'Filter Current Site',

				'type': 'radio',
				'options': ['true', 'false'],
				'title': 'Should Results From Current Site be Filtered Out',
        'default': 'false'
			},


			'fontsize':
			{
				'label': 'Font Size',
				'section': ['GUI'],
				'type': 'int',
				'title': 'fontsize',
				'default': 12
			},


    'downloadclients': {
      'section': ['Download Clients'],
      'type': 'downloadclient',
    }
  },
  'types':
  {
    'downloadclient': {
      'default': null,
        toNode: downloadClientNode,
        toValue: function() {
         return
        },
        reset: function() {
          GM_config.setValue("downloadClients","[]")

        }
    }
  },
   		'events':
		{
			'open': openMenu,
			'close': closeMenu,
      'save':saveOptions
		},
'css':
` .torrent-quicksearch-downloadclients{
     border:solid black 5px;
     margin-bottom:5px;
  }
  .torrent-quicksearch-downloadclientsDelete{
  margin-bottom:5px;
  background-color: gray;
  border: none;
  color: white;
  text-align: center;
  text-decoration: none;
  font-size: 20px;
  }
  `
});

GM.registerMenuCommand('Torrent Quick Search Settings', function ()
	{
		GM_config.open();
	});

}






`
Globals + Main Function
`
searchIcon = "data:image/png;base64,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"
iconLarge = 50
iconSmall = 25
paddingSmall = 0
paddingLarge = 4
let mouseState = "up"
imdbParserFail = "Could Not Parse"
AbortError = new DOMException(
	'aborted!',
	'AbortError')
var controller = new AbortController()
program = "Torrent Quick Search"
clickLimit = 500
lastClick = Date.now() - clickLimit
indexerSearchTimeout = 30000
day = 86400000
customSearch = false

let sem = null






//keep track of whether program was recently dragged

//Normalize Site Name so we don't have repeat keys in larger infoparser dict
standardNames = {
	"www.imdb.com": "imdb.com",
	"www.themoviedb.org": "themoviedb.org"
}

infoParser = {
	"animebytes.tv":
	{
		"title": "h2>a[href*=series]",
		"titleAttrib": "textContent"
	},
	"blutopia.xyz":
	{
		"title": "h1>a[href*=torrents\\/similar]",
		"titleAttrib": "textContent",
		"imdb": "div[class*=movie-details]>span>a[title=IMDB]",
		"imdbAttrib": "textContent"
	},

	"beyond-hd.me":
	{
		"title": "h1[class=movie-heading]",
		"titleAttrib": "textContent",
		"imdb": "ul[class*=movie-details]>li>span>a[href*=imdb]",
		"imdbAttrib": "href"
	},
	"imdb.com":
	{
		"title": "h1",
		"titleAttrib": "textContent",
	},

	"themoviedb.org":
	{
		"title": "h2",
		"titleAttrib": "textContent",
	},
}

siteParser = getParser()
function overideBuiltins()
{
  URL = class extends URL{
     constructor(url,base) {
        if(url==undefined&&base==undefined){
          null
        }
       	else if(base!=undefined &&base.match(/(http|https)/)==null){
          base=`http://${base}`
        }

       	else if(base==undefined &&url.match(/(http|https)/)==null){
          url=`http://${url}`
        }
        super(url,base);

     }
   };

}





function main()
{
overideBuiltins()
  initConfig()
createMainDOM()


}


main()