// ==UserScript==
// @name Trakt.tv | Enhanced Title Metadata
// @description Adds links of filtered search results to the metadata section (studios, networks, genres etc.) on title summary pages. Like the vip feature, only better. Also adds a country flag. See README for details.
// @version 0.8.2
// @namespace https://github.com/Fenn3c401
// @author Fenn3c401
// @license GPL-3.0-or-later
// @homepageURL https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection#readme
// @supportURL https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection/issues
// @updateURL https://raw.githubusercontent.com/Fenn3c401/Trakt.tv-Userscript-Collection/main/userscripts/meta/fyk2l3vj.meta.js
// @downloadURL https://raw.githubusercontent.com/Fenn3c401/Trakt.tv-Userscript-Collection/main/userscripts/dist/fyk2l3vj.min.user.js
// @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDQ4IDQ4Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmNscy0xIHsKICAgICAgICBmaWxsOiB1cmwoI3JhZGlhbC1ncmFkaWVudCk7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxyYWRpYWxHcmFkaWVudCBpZD0icmFkaWFsLWdyYWRpZW50IiBjeD0iNDguNDYiIGN5PSItLjk1IiBmeD0iNDguNDYiIGZ5PSItLjk1IiByPSI2NC44NCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5ZjQyYzYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuMjciIHN0b3AtY29sb3I9IiNhMDQxYzMiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNDIiIHN0b3AtY29sb3I9IiNhNDNlYmIiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNTMiIHN0b3AtY29sb3I9IiNhYTM5YWQiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNjQiIHN0b3AtY29sb3I9IiNiNDMzOWEiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNzMiIHN0b3AtY29sb3I9IiNjMDJiODEiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuODIiIHN0b3AtY29sb3I9IiNjZjIwNjEiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuOSIgc3RvcC1jb2xvcj0iI2UxMTQzYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9Ii45NyIgc3RvcC1jb2xvcj0iI2Y1MDYxMyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9InJlZCIvPgogICAgPC9yYWRpYWxHcmFkaWVudD4KICA8L2RlZnM+CiAgPGcgaWQ9Il94MkRfLXByb2R1Y3Rpb24iPgogICAgPGcgaWQ9ImxvZ29tYXJrLnNxdWFyZS5ncmFkaWVudCI+CiAgICAgIDxwYXRoIGlkPSJiYWNrZ3JvdW5kIiBjbGFzcz0iY2xzLTEiIGQ9Ik00OCwxMS4yNnYyNS40N2MwLDYuMjItNS4wNSwxMS4yNy0xMS4yNywxMS4yN0gxMS4yNmMtNi4yMiwwLTExLjI2LTUuMDUtMTEuMjYtMTEuMjdWMTEuMjZDMCw1LjA0LDUuMDQsMCwxMS4yNiwwaDI1LjQ3YzMuMzIsMCw2LjMsMS40Myw4LjM3LDMuNzIuNDcuNTIuODksMS4wOCwxLjI1LDEuNjguMTguMjkuMzQuNTkuNS44OS4zMy42OC42LDEuMzkuNzksMi4xNC4xLjM3LjE4Ljc2LjIzLDEuMTUuMDkuNTQuMTMsMS4xMS4xMywxLjY4WiIvPgogICAgICA8ZyBpZD0iY2hlY2tib3giPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTEzLjYyLDE3Ljk3bDcuOTIsNy45MiwxLjQ3LTEuNDctNy45Mi03LjkyLTEuNDcsMS40N1pNMjguMDEsMzIuMzdsMS40Ny0xLjQ2LTIuMTYtMi4xNiwyMC4zMi0yMC4zMmMtLjE5LS43NS0uNDYtMS40Ni0uNzktMi4xNGwtMjIuNDYsMjIuNDYsMy42MiwzLjYyWk0xMi45MiwxOC42N2wtMS40NiwxLjQ2LDE0LjQsMTQuNCwxLjQ2LTEuNDctNC4zMi00LjMxTDQ2LjM1LDUuNGMtLjM2LS42LS43OC0xLjE2LTEuMjUtMS42OGwtMjMuNTYsMjMuNTYtOC42Mi04LjYxWk00Ny44Nyw5LjU4bC0xOS4xNywxOS4xNywxLjQ3LDEuNDYsMTcuODMtMTcuODN2LTEuMTJjMC0uNTctLjA0LTEuMTQtLjEzLTEuNjhaTTI1LjE2LDIyLjI3bC03LjkyLTcuOTItMS40NywxLjQ3LDcuOTIsNy45MiwxLjQ3LTEuNDdaTTQxLjMyLDM1LjEyYzAsMy40Mi0yLjc4LDYuMi02LjIsNi4ySDEyLjg4Yy0zLjQyLDAtNi4yLTIuNzgtNi4yLTYuMlYxMi44OGMwLTMuNDIsMi43OC02LjIxLDYuMi02LjIxaDIwLjc4di0yLjA3SDEyLjg4Yy00LjU2LDAtOC4yOCwzLjcxLTguMjgsOC4yOHYyMi4yNGMwLDQuNTYsMy43MSw4LjI4LDguMjgsOC4yOGgyMi4yNGM0LjU2LDAsOC4yOC0zLjcxLDguMjgtOC4yOHYtMy41MWgtMi4wN3YzLjUxWiIvPjwhLS0gNDVkMjM4NWQzYWFjYmI1OTMyNmEzODYxNDljNWE4NzggLS0+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPg==
// @match https://trakt.tv/*
// @run-at document-start
// @grant unsafeWindow
// @grant GM_info
// @grant GM_addStyle
// @grant GM_openInTab
// @grant GM.getValue
// @grant GM.setValue
// ==/UserScript==
/* README
> Inspired by sergeyhist's [Trakt.tv Clickable Info](https://github.com/sergeyhist/trakt-scripts/blob/main/trakt-info.user.js) userscript.
### General
- By clicking on the label for languages, genres, networks and studios, you can make a search for all their respective values combined, ANDed for genres and languages, ORed for networks and studios.
For example if the genres are "Crime" and "Drama", then a label search will return a selection of other titles that also have the genres "Crime" AND "Drama".
- The search results default to either the "movies" or "shows" search category depending on the type of the current title.
- The title year and certification link to filtered search results as well.
- Mouse middle click opens the filtered search results (including those where the link is dynamically constructed) in a new background tab.
- Flags are not available for all countries.
- A "+ n more" button is added for networks when needed.
- Installing the [Trakt.tv | Unlocked Client-Side VIP Features](x70tru7b.md) userscript will allow free users to further modify the applied advanced filters, after accessing the filtered search results.
- For the time being this script won't work for vip users.
*/
"use strict";let $,toastr,trakt;const Logger=Object.freeze({_DEFAULT_PREFIX:GM_info.script.name.replace("Trakt.tv","Userscript")+": ",_DEFAULT_TOAST:!0,_printMsg(a,t,r,{data:c,prefix:k=Logger._DEFAULT_PREFIX,toast:x=Logger._DEFAULT_TOAST}={}){r=k+r,console[a](r,c||""),x&&toastr[t](r+(c?" See console for details.":""))},info:(a,t)=>Logger._printMsg("info","info",a,t),success:(a,t)=>Logger._printMsg("info","success",a,t),warning:(a,t)=>Logger._printMsg("warn","warning",a,t),error:(a,t)=>Logger._printMsg("error","error",a,t)});addStyles(),document.addEventListener("turbo:load",async()=>{if(!/^\/(shows|movies)\//.test(location.pathname)||($??=unsafeWindow.jQuery,toastr??=unsafeWindow.toastr,trakt??=unsafeWindow.userscriptTraktAPIModule?.isFulfilled?await unsafeWindow.userscriptTraktAPIModule:null,!$||!toastr))return;const a=$("#overview .additional-stats > li"),t=location.pathname.split("/").filter(Boolean);if(!a.length)return;const r=$("#summary-wrapper .year");r.parent().is("a")&&r.insertAfter(r.parent()),r.wrapAll(``);const c=$("#summary-wrapper div.certification");c.wrap(``);const k=a.filter(':has([itemprop="genre"])'),x=[];k.find('[itemprop="genre"]').each((n,e)=>{x[n]=$(e).text().toLowerCase().replaceAll(" ","-"),$(e).wrap(``)}),x.length>1&&k.find("label").wrap(``);const C=a.filter((n,e)=>$(e).find("label").text().toLowerCase()==="country");let m;if(C.length){const n=await getMapOfAllCountries(),e=C.contents().get(-1)?.textContent;if(m=n[e],m){const i=unsafeWindow.watchnowAllCountries?.[m]?.image;i&&C.children().last().after(`
`),C.contents().filter((o,s)=>!$(s).is("meta, label")).wrapAll(``)}else GM.setValue("allCountriesMap",null),Logger.error("Failed to match title country. Cached countries have been cleared. Reload page to try again.")}const y=a.filter((n,e)=>$(e).find("label").text().toLowerCase().startsWith("language")),_={};if(y.length){const n=await getSortedArrOfAllLanguages(),e=Object.fromEntries(n);let i=y.contents().get(-1).textContent;if(n.forEach(([o,s],l)=>{const w=new RegExp(`${RegExp.escape(s)}(, |$)`);w.test(i)&&(_[i.indexOf(s)]=o,i=i.replace(w,A=>" ".repeat(A.length)))}),i.trim())GM.setValue("allLanugagesArrSorted",null),Logger.error(`Failed to match all title languages (original: ${y.contents().get(-1).textContent} | remainder: ${i.trim()}). Cached languages have been cleared. Reload page to try again.`);else{const o=Object.values(_);y.contents().last().replaceWith(o.map(s=>`${e[s]}`).join(", ")),o.length>1&&y.find("label").wrap(``)}}const S=a.filter((n,e)=>$(e).find("label").text().toLowerCase().startsWith("network")),L=a.filter((n,e)=>/airs|aired|premiered/i.test($(e).find("label").text())).first();if(S.length&&t[3]!=="all"){const n={},e=await getSortedArrOfAllNetworks(),i=Object.fromEntries(e);let o=S.contents().get(-1).textContent;if(e.forEach(([s,{name:l,countryId:w}],A)=>{const d=new RegExp(`${RegExp.escape(l)}(, |$)`);d.test(o)&&(w===m||Object.hasOwn(_,w)||l!==e[A+1]?.[1].name)&&(n[o.indexOf(l)]=s,o=o.replace(d,u=>" ".repeat(u.length)))}),o.trim())GM.setValue("allNetworksArrSorted",null),Logger.error(`Failed to match all title networks (original: ${S.contents().get(-1).textContent} | remainder: ${o.trim()}). Cached networks have been cleared. Reload page to try again.`);else{const s=Object.values(n);S.contents().last().replaceWith(s.map(l=>`${i[l].name}${i[l].countryId?` (${i[l].countryId.toUpperCase()})`:""}`).join("")),s.length>1&&(S.find("label").wrap(``),$(` + ${s.length-1} more`).insertAfter(S.children().eq(1)).nextAll().wrapAll('')),S.find("a:not(:has(label), [onclick])").slice(1).before(", ")}}else if(L.text().includes(" on ")&&t[3]!=="all"){const n=await getSortedArrOfAllNetworks(),e=L.contents().last().text().split(" on ")[1],i=e?n.find(([o,{name:s,countryId:l}],w)=>new RegExp(`${RegExp.escape(s)}(, |$)`).test(e)&&(l===m||Object.hasOwn(_,l)||s!==n[w+1]?.[1].name)):null;i?(L.contents().last().remove(),L.append(` on ${i[1].name}${i[1].countryId?` (${i[1].countryId.toUpperCase()})`:""}`)):(GM.setValue("allNetworksArrSorted",null),Logger.error(`Failed to match title network (${e}). Cached networks have been cleared. Reload page to try again.`))}const g=a.filter((n,e)=>$(e).find("label").text().toLowerCase().startsWith("studio"));if(g.length)if(trakt){let n=!1;const e=async function(i){if(n)return;n=!0,i?.preventDefault(),unsafeWindow.showLoading?.();const o=await trakt[t[0]].studios({id:$(".summary-user-rating").attr(`data-${t[0].slice(0,-1)}-id`)}),s=o.map(l=>l.ids.trakt).join();if(unsafeWindow.hideLoading?.(),i){const l=`/search/${t[0]}?studio_ids=${$(this).find("label").length?s:o[0].ids.trakt}`;i.type==="click"?location.href=l:GM_openInTab(location.origin+l,{insert:!0,setParent:!0})}g.children().eq(0).attr("href",`/search/${t[0]}?studio_ids=${s}`),g.children().eq(1).attr("href",`/search/${t[0]}?studio_ids=${o[0].ids.trakt}`),g.find(".studios-more").html(o.slice(1).map(l=>`, ${l.name}`))};g.find("label").wrap($('').one("click auxclick",e)),g.contents().eq(1).wrap($('').one("click auxclick",e)),g.find(".studios-expand").one("click",()=>e())}else{const n=new Set,e=g.find(".studios-more"),i=g.find(".studios-expand"),o=e.text().split(", ").slice(1),s=+i.text().match(/\d+/)?.[0]||null,l=d=>fetch("/autocomplete/studios?query="+encodeURIComponent(d)).then(u=>u.json()).then(u=>Object.fromEntries(u.map(({label:f,value:p,tag:h})=>[f,+p,h?.toLowerCase()??null]).sort(([f,p,h],[M,b,O])=>f===M?(h&&(h===m||Object.hasOwn(_,h)))-(O&&(O===m||Object.hasOwn(_,O)))||b-p:0))),w=async function(d){d?.preventDefault(),$(this).off(),unsafeWindow.showLoading?.();const u=$(this).text(),f=await l(u),p=f[u];if(unsafeWindow.hideLoading?.(),p){n.add(p);const h=`/search/${t[0]}?studio_ids=${p}`;d&&(d.type==="click"?location.href=h:GM_openInTab(location.origin+h,{insert:!0,setParent:!0})),$(this).attr("href",h)}else Logger.error("Failed to match title studio: "+u,{data:f})},A=async()=>{if(n.size>1)return;unsafeWindow.showLoading?.();const d=await Promise.all(o.map(f=>l(f).then(p=>[f,p])));let u=-1;unsafeWindow.hideLoading?.(),e.html(d.map(([f,p],h)=>{if(h<=u)return null;let M;for(let b=h;b${M[0]}`;throw Logger.error("Failed to match all title studios. Could not match: "+d[h][0],{data:p}),new Error("Failed to match all title studios.")}).join(""))};g.contents().eq(1).wrap($('').on("click auxclick",w)),s&&(s===1?e.text(", ").append($(`${o.join(", ")}`).on("click auxclick",w)):s===o.length?(e.empty(),o.forEach(d=>e.append(", ",$(`${d}`).on("click auxclick",w)))):i.one("click",A),g.find("label").wrap('').parent().on("click auxclick",async function(d){d.preventDefault(),$(this).off(),await Promise.all([...g.find('a[href="#"]:not(:has(label), .studios-expand)').get().map(f=>w.call(f)),A()]);const u=`/search/${t[0]}?studio_ids=${Array.from(n).join(",")}`;d.type==="click"?location.href=u:GM_openInTab(location.origin+u,{insert:!0,setParent:!0}),$(this).attr("href",u)}))}},{capture:!0});async function getMapOfAllCountries(){let a=JSON.parse(await GM.getValue("allCountriesMap",null));if(!a){const t=await fetch("/search/movies").then(r=>r.text()).then(r=>new DOMParser().parseFromString(r,"text/html"));a=Object.fromEntries($(t).find("#filter-countries").children().get().map(r=>[$(r).text(),$(r).attr("value").toLowerCase()])),GM.setValue("allCountriesMap",JSON.stringify(a))}return a}async function getSortedArrOfAllLanguages(){let a=JSON.parse(await GM.getValue("allLanguagesArrSorted",null));if(!a){const t=await fetch("/search/movies").then(r=>r.text()).then(r=>new DOMParser().parseFromString(r,"text/html"));a=$(t).find("#filter-languages").children().get().map(r=>[$(r).attr("value"),$(r).text()]).sort(([,r],[,c])=>c.length-r.length),GM.setValue("allLanguagesArrSorted",JSON.stringify(a))}return a}async function getSortedArrOfAllNetworks(){let a=JSON.parse(await GM.getValue("allNetworksArrSorted",null));if(!a){const t=await fetch("/search/shows").then(c=>c.text()).then(c=>new DOMParser().parseFromString(c,"text/html")),r=new Intl.Collator;a=$(t).find("#filter-network_ids").children().get().map(c=>$(c).text()?[+$(c).attr("value"),{name:$(c).text(),countryId:$(c).attr("data-tag")?.toLowerCase()}]:null).filter(Boolean).sort(([c,{name:k,countryId:x}],[C,{name:m,countryId:y}])=>m.length-k.length||r.compare(k,m)||(y&&1)-(x&&1)||C-c),GM.setValue("allNetworksArrSorted",JSON.stringify(a))}return a}function addStyles(){GM_addStyle(`
#overview .additional-stats .country-flag {
width: 20px !important;
margin: -2px 5px 0 0 !important;
transition: transform .5s ease;
}
#overview .additional-stats a:hover > .country-flag {
transform: scale(1.1);
}
:is(#info-wrapper .additional-stats a > label, #summary-wrapper a > .year):hover {
color: var(--link-color) !important;
cursor: pointer !important;
}
#summary-wrapper a:has(> .certification):hover {
color: #fff !important;
}
`)}