Last active
June 13, 2024 19:35
-
-
Save thiagomgd/ddc36493dc934490fe3f4949e827e4bb to your computer and use it in GitHub Desktop.
Better TMDB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Better TMDB | |
// @namespace http://tampermonkey.net/ | |
// @match https://www.themoviedb.org/* | |
// @grant GM_xmlhttpRequest | |
// @grant GM_getResourceText | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @version 1.12.0 | |
// @author Geekosaur | |
// @description 2024-05-22, 09:25:21 a.m. | |
// ==/UserScript== | |
const config = GM_getValue("config", {}); | |
const AUTH_BEARER = config["AUTH_BEARER"]; | |
const API_KEY = config["API_KEY"]; | |
const ACCOUNT = config["ACCOUNT"]; | |
const ACTORS = config["ACTORS"]; | |
const CAST_AUTO_LOAD_MORE_DETAILS = true; | |
const HIGHLIGHT = "#dcefff"; | |
const HIGHLIGHT_RATED = "#efffdc"; | |
const HIGHLIGHT_WATCHLIST = "#dcefff"; | |
const CREW_ROLES = [ | |
"acting", | |
"actor", | |
"actress", | |
"director", | |
"screenplay", | |
"story", | |
"music", | |
"novel", | |
"comic book", | |
"producer", | |
"original music composer", | |
"animation director", | |
"writer", | |
"original story", | |
"songs", | |
]; | |
const MS_PER_DAY = 1000 * 60 * 60 * 24; | |
let CURRENT_FILTER = ""; | |
let CURRENT_EXT_LIST_PAGE = undefined; | |
let EXT_LIST_TOTAL_PAGES = undefined; | |
let LOADING_MY_LISTS = false; | |
let WATCH_OPTIONS_MEDIA = null; | |
const LISTS_IGNORE_STREAM = [ | |
8271845, 8271848, 8271832, 8271847, 8292656, 8293351, 8293350, 8293173, | |
8293168, 8271831, 8271844, 8292658, 8293354, 8293353, 8293352, 8293174, | |
8287234, 8293166, 8292657, | |
]; | |
const LONG_MOVIES_LIST = 8298359; | |
const SHORT_MOVIES_LIST = 8298358; | |
const LONG_DURATION = 115; | |
const SHORT_DURATION = 82; | |
const HEADERS = { | |
accept: "application/json", | |
Authorization: `Bearer ${AUTH_BEARER}`, | |
}; | |
function addGlobalStyle(css) { | |
var head, style; | |
head = document.getElementsByTagName("head")[0]; | |
if (!head) { | |
return; | |
} | |
style = document.createElement("style"); | |
style.type = "text/css"; | |
style.innerHTML = css; | |
head.appendChild(style); | |
} | |
function addObserver(selector, selector2, fn) { | |
const targetNode = document.querySelector(selector); | |
const config = { attributes: true, childList: true, subtree: true }; | |
const callback = (mutationList, observer) => { | |
for (const mutation of mutationList) { | |
if (mutation.type === "childList") { | |
// console.debug("A child node has been added or removed."); | |
// console.debug(mutation); | |
const newLinks = document.querySelectorAll(selector2); | |
if (newLinks.length > 0) { | |
// console.debug("CHANGED!!"); | |
observer.disconnect(); | |
setTimeout(fn, 1500); | |
} | |
} | |
} | |
}; | |
const observer = new MutationObserver(callback); | |
observer.observe(targetNode, config); | |
} | |
/* | |
HELPER FUNCTIONS | |
*/ | |
function toHoursAndMinutes(totalMinutes) { | |
const hours = Math.floor(totalMinutes / 60); | |
const minutes = totalMinutes % 60; | |
return `${hours}:${minutes.toString().padStart(2, "0")}`; | |
} | |
function ListPageShowAll() { | |
console.debug("SHOW ALL"); | |
const allMedia = document.querySelectorAll("li.item.visited"); | |
allMedia.forEach((el) => { | |
el.style.display = "list-item"; | |
}); | |
CURRENT_FILTER = ""; | |
} | |
function ListPageUnratedOnly() { | |
const allMedia = document.querySelectorAll("li.item.visited"); | |
allMedia.forEach((el) => { | |
if (el.classList.contains("rated")) { | |
el.style.display = "none"; | |
} else { | |
el.style.display = "list-item"; | |
} | |
}); | |
CURRENT_FILTER = "unrated"; | |
} | |
function ListPageRatedOnly() { | |
const allMedia = document.querySelectorAll("li.item.visited"); | |
allMedia.forEach((el) => { | |
if (el.classList.contains("rated")) { | |
el.style.display = "list-item"; | |
} else { | |
el.style.display = "none"; | |
} | |
}); | |
CURRENT_FILTER = "rated"; | |
} | |
function ListPageAdultOnly() { | |
const allMedia = document.querySelectorAll("ol.list_items li.visited"); | |
allMedia.forEach((el) => { | |
if (el.classList.contains("adult_false")) { | |
el.style.display = "none"; | |
} else { | |
el.style.display = "grid"; | |
} | |
}); | |
CURRENT_FILTER = "adult"; | |
} | |
function ListPageNotAdultOnly() { | |
const allMedia = document.querySelectorAll("ol.list_items li.visited"); | |
allMedia.forEach((el) => { | |
if (el.classList.contains("adult_true")) { | |
el.style.display = "none"; | |
} else { | |
el.style.display = "grid"; | |
} | |
}); | |
CURRENT_FILTER = "adult"; | |
} | |
function ListPageForeignOnly() { | |
const languageFilter = prompt("Language - empty for all foreign"); | |
const classFilter = languageFilter | |
? `${languageFilter}-language` | |
: "foreign-language"; | |
const allMedia = document.querySelectorAll("ol.list_items li.visited"); | |
allMedia.forEach((el) => { | |
if (el.classList.contains(classFilter)) { | |
el.style.display = "grid"; | |
} else { | |
el.style.display = "none"; | |
} | |
}); | |
CURRENT_FILTER = "rated"; | |
} | |
function ListPageStreamableOnly() { | |
const allMedia = document.querySelectorAll("ol.list_items li.visited"); | |
allMedia.forEach((el) => { | |
if (el.classList.contains("streamable")) { | |
el.style.display = "grid"; | |
} else { | |
el.style.display = "none"; | |
} | |
}); | |
CURRENT_FILTER = "streamable"; | |
} | |
/* | |
POST FUNCTIONS | |
*/ | |
function delete_from_tmdb_list(list_id, media_type, media_id) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "DELETE", | |
headers: { ...HEADERS, "content-type": "application/json" }, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/list/${list_id}/items`, | |
data: JSON.stringify({ | |
items: [{ media_type: media_type, media_id: media_id }], | |
}), | |
onload: (resp) => { | |
// console.debug("!!!!!", resp); | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
// console.debug('data!!!!: ', json); | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function remove_from_list(list_id, media_key) { | |
const [media_type, media_id] = media_key.split("-"); | |
const confirmation = confirm(`Delete this?`); | |
if (!confirmation) { | |
return; | |
} | |
const resp = await delete_from_tmdb_list(list_id, media_type, media_id); | |
// console.debug(resp); | |
if (resp.success) { | |
alert("DONE"); | |
} | |
} | |
function comment_tmdb_list(list_id, media_type, media_id, comment) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "PUT", | |
headers: { ...HEADERS, "content-type": "application/json" }, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/list/${list_id}/items`, | |
data: JSON.stringify({ | |
items: [ | |
{ media_type: media_type, media_id: media_id, comment: comment }, | |
], | |
}), | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
// console.debug('data!!!!: ', json); | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function add_comment_list(list_id, media_key) { | |
console.debug("add_comment_list"); | |
const [media_type, media_id] = media_key.split("-"); | |
const comment = prompt(`Comment`); | |
if (!comment) { | |
return; | |
} | |
const resp = await comment_tmdb_list(list_id, media_type, media_id, comment); | |
if (resp.success) { | |
alert("DONE"); | |
} | |
} | |
function add_to_tmdb_list(items, list_id) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "POST", | |
headers: { ...HEADERS, "content-type": "application/json" }, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/list/${list_id}/items`, | |
data: JSON.stringify({ | |
items: items, | |
}), | |
onload: (resp) => { | |
console.debug("!!!!!", resp); | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
// console.debug('data!!!!: ', json); | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function addMediaToList(media_type, media_id, list_id) { | |
const resp = await add_to_tmdb_list( | |
[{ media_type: media_type, media_id: media_id }], | |
list_id | |
); | |
if (resp.success) { | |
alert("DONE"); | |
} | |
} | |
/* | |
FETCH FUNCTIONS | |
*/ | |
function fetch_ext_lists(movie_id, page) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/3/movie/${movie_id}/lists?language=en-US&page=${page}`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
console.debug("response", json); | |
EXT_LIST_TOTAL_PAGES = json.total_pages; | |
CURRENT_EXT_LIST_PAGE = page; | |
// if (json.total_pages > page) { | |
// CURRENT_EXT_LIST_NEXT_PAGE = page + 1; | |
// } else { | |
// CURRENT_EXT_LIST_NEXT_PAGE = 0; | |
// } | |
resolve(json.results); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function loadExtLists(movie_id, page) { | |
console.debug("loadExtLists", movie_id, page); | |
// if (!CURRENT_EXT_LIST_NEXT_PAGE) { | |
// return; | |
// } | |
const lists = await fetch_ext_lists(movie_id, page); | |
const links = lists | |
.map((list) => { | |
return `<li><a href="https://www.themoviedb.org/list/${list.id}">${list.name}</a></li>`; | |
}) | |
.join(" "); | |
const el_listsUl = document.getElementById("extListsUl"); | |
el_listsUl.innerHTML = links; | |
// loadExtListsButtonDiv | |
let buttonsHtml = ""; | |
if (page > 1) { | |
buttonsHtml = `<button id="loadPrevExtListPage" class="myButton">prev</button>`; | |
} | |
if (page < EXT_LIST_TOTAL_PAGES) { | |
buttonsHtml = buttonsHtml.concat( | |
`<button id="loadNextExtListPage" class="myButton">next</button>` | |
); | |
} | |
console.debug(buttonsHtml); | |
const _el_button_div = document.getElementById("loadExtListsButtonDiv"); | |
console.debug(_el_button_div); | |
_el_button_div.innerHTML = buttonsHtml; | |
const _el_prev_page = document.getElementById("loadPrevExtListPage"); | |
const _el_next_page = document.getElementById("loadNextExtListPage"); | |
_el_prev_page && | |
_el_prev_page.addEventListener( | |
"click", | |
() => { | |
loadExtLists(movie_id, page - 1); | |
}, | |
false | |
); | |
_el_next_page && | |
_el_next_page.addEventListener( | |
"click", | |
() => { | |
loadExtLists(movie_id, page + 1); | |
}, | |
false | |
); | |
const _el_header = document.getElementById("externalListsH4"); | |
_el_header.textContent = `External Lists (${page}/${EXT_LIST_TOTAL_PAGES})`; | |
} | |
function fetch_person_movie_credits(person_id) { | |
console.debug("fetching credits"); | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/3/person/${person_id}/movie_credits?language=en-US`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
resolve(json.cast); | |
return; | |
}, | |
}); | |
}); | |
} | |
function fetch_movie_credits(movie_id) { | |
console.debug("fetching movie credits"); | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/3/movie/${movie_id}/credits?language=en-US`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
function fetch_movie_details_credits(movie_id) { | |
console.debug("fetching movie details + credits"); | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/3/movie/${movie_id}?language=en-US&append_to_response=credits,keywords`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function loadPersonPosters(actor_id) { | |
const movies_list = await fetch_person_movie_credits(actor_id); | |
const avg_global_rating = | |
movies_list.reduce((total, item) => total + item.vote_average, 0) / | |
movies_list.filter((item) => item.vote_average).length; | |
const movies = {}; | |
movies_list.forEach((el, index) => (movies[`/movie/${el.id}`] = el)); | |
const _el_sidebar = document.querySelector("section.left_column"); | |
let el_rating = document.createElement("p"); | |
el_rating.textContent = "Global Rating: " + avg_global_rating.toFixed(2); | |
_el_sidebar.firstChild.appendChild(el_rating); | |
const _el_role_links = document.querySelectorAll("td.role > a.tooltip"); | |
for (const roleLink of _el_role_links) { | |
const movieData = movies[roleLink.pathname]; | |
// console.debug(roleLink.pathname, movieData); | |
if (!movieData) continue; | |
if (movieData.original_language !== "en") { | |
roleLink.textContent = `[${movieData.original_language}] ${roleLink.textContent}`; | |
} | |
if (movieData.adult) { | |
roleLink.textContent = `[A] ${roleLink.textContent}`; | |
} | |
roleLink.closest("td").style.verticalAlign = "top"; | |
const _el_tr = roleLink.closest("tr"); | |
const el_data_td = document.createElement("td"); | |
el_data_td.innerHTML = `<td><span class="rating">Rating: ${movieData.vote_average}</span></br><span class="popularity">Pop.: ${movieData.popularity}</span></td>`; | |
el_data_td.style.verticalAlign = "top"; | |
el_data_td.style.float = "right"; | |
if (movieData.vote_average < avg_global_rating) { | |
el_data_td.firstChild.classList.add("less_avg"); | |
} | |
_el_tr.appendChild(el_data_td); | |
const el_image_td = document.createElement("td"); | |
el_image_td.style.width = "115px"; | |
el_image_td.innerHTML = `<td><img style="max-height:150px;float: right" src="https://image.tmdb.org/t/p/original${movieData.poster_path}"/></td>`; | |
_el_tr.appendChild(el_image_td); | |
} | |
} | |
function fetch_tmdb_lists_page(page) { | |
// TODO: add pagination | |
// https://api.themoviedb.org/4/account/{account_object_id}/lists | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/account/${ACCOUNT}/lists?api_key=${API_KEY}&page=${page}`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function fetch_tmdb_lists_list() { | |
const lists = []; | |
let page = 1, | |
pages = 1; | |
do { | |
const data = await fetch_tmdb_lists_page(page); | |
pages = data.total_pages; | |
page = page + 1; | |
lists.push( | |
...data.results.map((itm) => { | |
return { | |
id: itm.id, | |
name: itm.name, | |
}; | |
}) | |
); | |
} while (page <= pages); | |
return lists; | |
} | |
function fetch_list_items_page(list_id, page) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/list/${list_id}?api_key=${API_KEY}&page=${page}`, | |
onload: (resp) => { | |
// console.debug("!!!!!", resp); | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
// console.debug('data!!!!: ', json); | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
function fetch_ratings_page(media_type, page) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/account/${ACCOUNT}/${media_type}/rated?api_key=${API_KEY}&page=${page}`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
// console.debug('data!!!!: ', json); | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
function fetch_watchlist_page(media_type, page) { | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/4/account/${ACCOUNT}/${media_type}/watchlist?api_key=${API_KEY}&page=${page}`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
resolve("error"); | |
return; | |
} | |
// console.debug('data!!!!: ', json); | |
resolve(json); | |
return; | |
}, | |
}); | |
}); | |
} | |
async function fetch_tmdb_list_items(list_id) { | |
// TODO: add pagination | |
// https://api.themoviedb.org/4/account/{account_object_id}/lists | |
// console.debug('loading list items'); | |
const list_items = []; | |
let page = 1, | |
pages = 1; | |
do { | |
const data = await fetch_list_items_page(list_id, page); | |
pages = data.total_pages; | |
page = page + 1; | |
list_items.push( | |
...data.results.map((itm) => { | |
return { | |
id: itm.id, | |
media_type: itm.media_type, | |
// language: itm.original_language, | |
// year: itm.release_date ? itm.release_date.substring(0, 4) : null, | |
// runtime: itm.runtime, | |
}; | |
}) | |
); | |
} while (page <= pages); | |
return list_items; | |
} | |
function fetchMovieStreamOptions(movie_id) { | |
// console.debug("fetching stream options"); | |
return new Promise(function (resolve, reject) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
headers: HEADERS, | |
responseType: "json", | |
synchronous: false, | |
url: `https://api.themoviedb.org/3/movie/${movie_id}/watch/providers`, | |
onload: (resp) => { | |
let json = JSON.parse(resp.responseText); | |
if (json && json.Error) { | |
console.debug("Error: " + json.Error); | |
console.debug("error on movie id", movie_id); | |
resolve({}); | |
return; | |
} | |
resolve(json.results); | |
return; | |
}, | |
}); | |
}); | |
} | |
function getWatchOptionsMap() { | |
if (!WATCH_OPTIONS_MEDIA) { | |
WATCH_OPTIONS_MEDIA = GM_getValue("media_watch_options", {}); | |
} | |
return WATCH_OPTIONS_MEDIA; | |
} | |
function saveWatchOptionsMap() { | |
if (WATCH_OPTIONS_MEDIA) { | |
GM_setValue("media_watch_options", WATCH_OPTIONS_MEDIA); | |
} | |
} | |
async function getMediaStreamOptions(media_key) { | |
const [media_type, itemId] = media_key.split("-"); | |
if (media_type !== "movie") { | |
return; | |
} | |
const optionsMap = getWatchOptionsMap(); | |
if ( | |
optionsMap[media_key] && | |
(Date.now() - optionsMap[media_key].syncdate) / MS_PER_DAY | |
) { | |
return optionsMap[media_key].options; | |
} | |
const results = await fetchMovieStreamOptions(itemId); | |
if (!results) { | |
return "N"; | |
} | |
const result = results["CA"]; | |
if (!result) { | |
return "N"; | |
} | |
let options = ""; | |
if (result["ads"]) { | |
options += "F"; | |
} | |
if (result["flatrate"]) { | |
options += "S"; | |
} | |
if (result["rent"]) { | |
options += "R"; | |
} | |
WATCH_OPTIONS_MEDIA[media_key] = { options, syncdate: Date.now() }; | |
return options || "N"; | |
} | |
async function load_tmdb_lists() { | |
// get lists from local, or remote | |
// if (LOADING_MY_LISTS) { | |
// } | |
let lists = GM_getValue("user_lists", undefined); | |
const last_sync = GM_getValue("user_lists_sync", Date.now()); | |
const dateDiff = (Date.now() - last_sync) / MS_PER_DAY; | |
if (lists && dateDiff <= 1) { | |
return lists; | |
} | |
const list_array = await fetch_tmdb_lists_list(); | |
lists = {}; | |
const media_details = GM_getValue("media_details", {}); | |
for (let list of list_array) { | |
const { id: listId, name: listName, ...listData } = list; | |
const items = await fetch_tmdb_list_items(listId); | |
for (const item of items) { | |
const { id: itemId, media_type } = item; | |
if (media_type !== "movie" || media_details["movie-" + itemId]) continue; | |
const details = await fetch_movie_details_credits(itemId); | |
media_details[`${media_type}-${itemId}`] = { | |
adult: details.adult, | |
language: details.original_language, | |
year: details.release_date | |
? details.release_date.substring(0, 4) | |
: null, | |
runtime: details.runtime, | |
}; | |
} | |
lists[listId] = { | |
id: listId, | |
name: listName, | |
items: items.map((item) => `${item.media_type}-${item.id}`), | |
}; | |
} | |
GM_setValue("user_lists", lists); | |
GM_setValue("user_lists_sync", Date.now()); | |
GM_setValue("media_details", media_details); | |
return lists; | |
} | |
async function fetch_tmdb_ratings(media_type) { | |
console.debug("loading ratings", media_type); | |
const rating_items = []; | |
let page = 1, | |
pages = 1; | |
do { | |
const data = await fetch_ratings_page(media_type, page); | |
pages = data.total_pages; | |
page = page + 1; | |
rating_items.push(...data.results); | |
} while (page <= pages); | |
return rating_items; | |
} | |
async function fetch_tmdb_watchlist(media_type) { | |
console.debug("loading watchlist", media_type); | |
const watchlist_items = []; | |
let page = 1, | |
pages = 1; | |
do { | |
const data = await fetch_watchlist_page(media_type, page); | |
pages = data.total_pages; | |
page = page + 1; | |
watchlist_items.push(...data.results); | |
} while (page <= pages); | |
return watchlist_items; | |
} | |
function map_ratings_dict(media_type, ratings, media_ratings) { | |
for (const item of ratings) { | |
const rating = item.account_rating.value; | |
const key = `${media_type}-${item.id}`; | |
if (media_ratings[key]) { | |
console.error("DUPLICATE RATING", item); | |
} else { | |
media_ratings[key] = rating; | |
} | |
} | |
return media_ratings; | |
} | |
function map_watchlist_dict(media_type, watchlist, media_watchlist) { | |
for (const item of watchlist) { | |
const key = `${media_type}-${item.id}`; | |
if (media_watchlist[key]) { | |
console.error("DUPLICATE RATING", item); | |
} else { | |
media_watchlist[key] = true; | |
} | |
} | |
return media_watchlist; | |
} | |
async function load_tmdb_ratings() { | |
let ratings = GM_getValue("user_ratings", undefined); | |
const last_sync = GM_getValue("user_ratings_sync", Date.now()); | |
const dateDiff = (Date.now() - last_sync) / MS_PER_DAY; | |
if (ratings && dateDiff <= 1) { | |
return ratings; | |
} | |
const movie_ratings = await fetch_tmdb_ratings("movie"); | |
const tv_ratings = await fetch_tmdb_ratings("tv"); | |
console.debug( | |
"fetched ratings. Movies:", | |
movie_ratings.length, | |
"TV", | |
tv_ratings.length, | |
"Total", | |
movie_ratings.length + tv_ratings.length | |
); | |
console.debug(movie_ratings); | |
const media_ratings = {}; | |
map_ratings_dict("movie", movie_ratings, media_ratings); | |
map_ratings_dict("tv", tv_ratings, media_ratings); | |
GM_setValue("user_ratings", media_ratings); | |
GM_setValue("user_ratings_sync", Date.now()); | |
return media_ratings; | |
} | |
async function load_tmdb_watchlist() { | |
let watchlist = GM_getValue("user_watchlist", undefined); | |
const last_sync = GM_getValue("user_watchlist_sync", Date.now()); | |
const dateDiff = (Date.now() - last_sync) / MS_PER_DAY; | |
if (watchlist && dateDiff <= 1) { | |
return watchlist; | |
} | |
const movie_watchlist = await fetch_tmdb_watchlist("movie"); | |
const tv_watchlist = await fetch_tmdb_watchlist("tv"); | |
// console.debug("fetched ratings. Movies:", movie_ratings.length, "TV", tv_ratings.length, "Total", movie_ratings.length + tv_ratings.length) | |
const media_watchlist = {}; | |
map_watchlist_dict("movie", movie_watchlist, media_watchlist); | |
map_watchlist_dict("tv", tv_watchlist, media_watchlist); | |
GM_setValue("user_watchlist", media_watchlist); | |
GM_setValue("user_watchlist_sync", Date.now()); | |
return media_watchlist; | |
} | |
function map_lists_to_dict(lists) { | |
const media = {}; | |
for (const list of Object.values(lists)) { | |
const list_key = `${list.id}|${list.name}`; | |
for (const item of list.items || []) { | |
if (media[item]) { | |
media[item].push(list_key); | |
} else { | |
media[item] = [list_key]; | |
} | |
} | |
} | |
return media; | |
} | |
async function getMediaToListsMap() { | |
const lists = await load_tmdb_lists(); | |
return map_lists_to_dict(lists); | |
} | |
async function getListItems(listId) { | |
const lists = await load_tmdb_lists(); | |
return lists[listId]?.items || []; | |
} | |
async function getMediaDetails() { | |
return GM_getValue("media_details", {}); | |
} | |
async function getListItemsAndMedia(listId) { | |
const lists = await load_tmdb_lists(); | |
if (lists[listId]) { | |
return [lists[listId].items, await getMediaDetails()]; | |
} | |
console.debug("loading external list items"); | |
const media_details = {}; | |
const items = await fetch_tmdb_list_items(listId); | |
console.debug(items); | |
// TODO: GET DETAILS WITH RUNTIME | |
for (const item of items) { | |
const { id: itemId, media_type, ...details } = item; | |
media_details[`${media_type}-${itemId}`] = details; | |
} | |
return [items.map((item) => `${item.media_type}-${item.id}`), media_details]; | |
} | |
/* | |
IMPROVE PAGE FUNCTIONS | |
*/ | |
async function improvePersonPage() { | |
const media_to_list = await getMediaToListsMap(); | |
const media_ratings = await load_tmdb_ratings(); | |
const media_watchlist = await load_tmdb_watchlist(); | |
let total_rating = 0; | |
let total_rated = 0; | |
let total_listed = 0; | |
let total_watchlist = 0; | |
const tmdb_id = parseInt( | |
window.location.href | |
.split("https://www.themoviedb.org/person/")[1] | |
.split("-")[0] | |
); | |
// FLAG MEDIA ON MY LISTS | |
const movieLinks = document.querySelectorAll("a.tooltip"); | |
for (const movie of movieLinks) { | |
const key = movie.pathname | |
.split("/") | |
.slice(1) | |
.join("-") | |
.split("-") | |
.slice(0, 2) | |
.join("-"); | |
if (media_to_list[key]) { | |
total_listed += 1; | |
const TMDB_lists = []; | |
media_to_list[key].forEach((item) => { | |
TMDB_lists.push(item.split("|")[1]); | |
}); | |
movie.textContent = `${movie.textContent} [${TMDB_lists.join(", ")}]`; | |
movie.parentElement.parentElement.style.backgroundColor = HIGHLIGHT; | |
} | |
console.debug(key); | |
if (media_ratings[key]) { | |
total_rated += 1; | |
total_rating += media_ratings[key]; | |
const _el_check = | |
movie.parentElement.previousElementSibling.querySelector( | |
".circle-empty" | |
); | |
_el_check.classList.remove("circle-empty"); | |
_el_check.classList.add("circle-check"); | |
} else if (media_watchlist[key]) { | |
total_watchlist += 1; | |
movie.parentElement.parentElement.style.backgroundColor = HIGHLIGHT; | |
const _el_check = | |
movie.parentElement.previousElementSibling.querySelector( | |
".circle-empty" | |
); | |
_el_check.classList.remove("circle-empty"); | |
_el_check.classList.add("bookmark"); | |
} | |
} | |
// console.debug("TOTAL RATED", total_rated, 'AVERAGE', total_rating/total_rated); | |
let el_section_header = document.createElement("section"); | |
if (!CAST_AUTO_LOAD_MORE_DETAILS) { | |
// add option to load images | |
const el_imgs_btn = document.createElement("button"); | |
el_imgs_btn.setAttribute("id", "loadPostersBtn"); | |
el_imgs_btn.textContent = "Load Posters"; | |
el_section_header.appendChild(el_imgs_btn); | |
} | |
// ADD IF ACTOR IS ON MY LIST | |
if (ACTORS.includes(tmdb_id)) { | |
let el_following = document.createElement("p"); | |
el_following.innerText = `Following`; | |
el_section_header.appendChild(el_following); | |
} | |
let el_watchlist = document.createElement("p"); | |
el_watchlist.textContent = | |
"Total Watchlist: " + total_watchlist + "/" + movieLinks.length; | |
let el_listed = document.createElement("p"); | |
el_listed.textContent = | |
"Total Listed: " + total_listed + "/" + movieLinks.length; | |
let el_rated = document.createElement("p"); | |
el_rated.textContent = | |
"Total Rated: " + total_rated + "/" + movieLinks.length; | |
let el_rating = document.createElement("p"); | |
el_rating.textContent = | |
"Avg Rating: " + | |
(total_rating ? Number(total_rating / total_rated).toFixed(2) : 0); | |
let el_space = document.createElement("p"); | |
el_section_header.appendChild(el_watchlist); | |
el_section_header.appendChild(el_listed); | |
el_section_header.appendChild(el_rated); | |
el_section_header.appendChild(el_rating); | |
el_section_header.appendChild(el_space); | |
const _el_sidebar = document.querySelector("section.left_column"); | |
_el_sidebar.insertBefore(el_section_header, _el_sidebar.firstChild); | |
if (!CAST_AUTO_LOAD_MORE_DETAILS) { | |
document.getElementById("loadPostersBtn").addEventListener( | |
"click", | |
() => { | |
loadPersonPosters(tmdb_id); | |
}, | |
false | |
); | |
} else { | |
loadPersonPosters(tmdb_id); | |
} | |
} | |
function getRuntimeClass(runtime) { | |
if (runtime >= 105) return "longruntime"; | |
if (runtime <= 82) return "shortruntime"; | |
return "regruntime"; | |
} | |
async function addRatedToListPage(list_items, media_ratings) { | |
const _el_totalratings = document.getElementById("totalRatings"); | |
if (!_el_totalratings) { | |
let total_rated = 0; | |
for (const key of list_items) { | |
if (media_ratings[key]) { | |
total_rated += 1; | |
} | |
} | |
// const _el_info = document.querySelector("ul.list_info"); | |
// let el_ratings = document.createElement("li"); | |
// el_ratings.innerHTML = `<span><em>${total_rated}</em></span><br/> rated items`; | |
// el_ratings.setAttribute("id", "totalRatings"); | |
// // TODO: fix to insert as 2nd item | |
// _el_info.firstChild.after(el_ratings); | |
} | |
} | |
async function addMediaDetailsToListPage( | |
media_ratings, | |
media_details, | |
watchlist, | |
listId | |
) { | |
const mediaLinks = document.querySelectorAll( | |
"ol.list_items li:not(.visited) a.picture" | |
); | |
// console.debug("addMediaDetailsToListPage", mediaLinks); | |
const shouldGetWatchOptions = !LISTS_IGNORE_STREAM.includes(listId); | |
for (const media of mediaLinks) { | |
// const el_newInfoStrip = document.createElement("div"); | |
// el_newInfoStrip.classList.add("info_strip"); | |
// el_newInfoStrip.id = "newInfoStrip"; | |
// // console.debug(media.textContent); | |
const key = media.pathname | |
.split("/") | |
.slice(1) | |
.join("-") | |
.split("-") | |
.slice(0, 2) | |
.join("-"); | |
media.closest("li").classList.add("visited"); | |
// if (media_ratings[key]) { | |
// media.closest("li").classList.add("rated"); | |
// } | |
// console.debug("media key", key); | |
// if (!media_details[key]) { | |
// continue; | |
// } | |
const _el_li = media.closest("li"); | |
const _el_wrapper = _el_li.querySelector("div.p-4"); //.querySelector("div.info_wrapper"); | |
// console.debug(_el_wrapper); | |
const el_info = document.createElement("span"); | |
el_info.classList.add("text-sm"); | |
el_info.classList.add("block"); | |
el_info.classList.add("font-light"); | |
el_info.classList.add("border-t"); | |
el_info.classList.add("pt-2"); | |
el_info.classList.add("list-itm-extra-info"); | |
const details = media_details[key] || {}; | |
media.closest("li").classList.add(`adult_${details.adult}`); | |
if (details.year) { | |
const _el_text_link = _el_wrapper.querySelector("a"); | |
_el_text_link.textContent = `${_el_text_link.textContent} (${details.year})`; | |
} | |
if (details.language) { | |
const el_language = document.createElement("span"); | |
el_language.classList.add("language"); | |
el_language.textContent = details.language.toUpperCase(); | |
el_info.appendChild(el_language); | |
media.closest("li").classList.add(`${details.language}-language`); | |
if (details.language !== "en") { | |
media.closest("li").classList.add("foreign-language"); | |
} | |
} | |
if (details.language && details.runtime) { | |
const el_separator = document.createElement("span"); | |
el_separator.textContent = " | "; | |
el_info.appendChild(el_separator); | |
} | |
if (details.runtime) { | |
const el_runtime = document.createElement("span"); | |
const runtime = toHoursAndMinutes(details.runtime); | |
const runtime_class = getRuntimeClass(details.runtime); | |
el_runtime.classList.add(runtime_class); | |
el_runtime.textContent = runtime; | |
el_info.appendChild(el_runtime); | |
} | |
const el_separator_rt = document.createElement("span"); | |
el_separator_rt.textContent = " | "; | |
el_info.appendChild(el_separator_rt); | |
// console.debug("checkrated", key, media_ratings[key]); | |
if (media_ratings[key]) { | |
media.closest("li").classList.add("rated"); | |
media.closest("li").style.backgroundColor = HIGHLIGHT_RATED; | |
// const el_rating = document.createElement("span"); | |
// el_rating.classList.add("rating"); | |
// el_rating.textContent = media_ratings[key]; | |
// el_info.appendChild(el_rating); | |
} else { | |
const el_rating = document.createElement("span"); | |
el_rating.classList.add("no_rating"); | |
el_rating.textContent = "*"; | |
el_info.appendChild(el_rating); | |
} | |
// if (shouldGetWatchOptions && !media_ratings[key]) { | |
if (shouldGetWatchOptions) { | |
const watchOptions = await getMediaStreamOptions(key); | |
if (watchOptions) { | |
media.closest("li").classList.add("streamable"); | |
const el_separator_stream = document.createElement("span"); | |
el_separator_stream.textContent = " | "; | |
el_info.appendChild(el_separator_stream); | |
const el_stream = document.createElement("span"); | |
el_stream.classList.add("no_rating"); | |
el_stream.textContent = watchOptions; | |
el_info.appendChild(el_stream); | |
} | |
} | |
if (watchlist[key]) { | |
media.closest("li").classList.add("watchlist"); | |
media.closest("li").style.backgroundColor = HIGHLIGHT_WATCHLIST; | |
// const el_watchlist = document.createElement("span"); | |
// el_watchlist.classList.add("in_watchlist"); | |
// el_watchlist.textContent = "W"; | |
// const el_separator_rt = document.createElement("span"); | |
// el_separator_rt.textContent = " | "; | |
// el_info.appendChild(el_separator_rt); | |
// el_info.appendChild(el_watchlist); | |
} | |
_el_wrapper.children[0].after(el_info); //appendChild(el_info); | |
// ADD DELETE AND COMMENT OPTIONS | |
const _el_title_div = _el_wrapper.querySelector("div"); | |
_el_title_div.innerHTML += "</br>"; | |
// console.debug(_el_wrapper); | |
// console.debug(_el_title_div); | |
const _el_comment_button = document.createElement("button"); | |
_el_comment_button.onclick = () => { | |
add_comment_list(listId, key); | |
}; | |
_el_comment_button.textContent = "C"; | |
_el_comment_button.classList.add("defaultStyle"); | |
_el_title_div.appendChild(_el_comment_button); | |
// _el_title_div.innerHTML += ' - '; | |
const _el_remove_button = document.createElement("button"); | |
_el_remove_button.onclick = () => { | |
remove_from_list(listId, key); | |
}; | |
_el_remove_button.textContent = "X"; | |
_el_remove_button.classList.add("defaultStyle"); | |
_el_remove_button.style.float = "right"; | |
_el_title_div.appendChild(_el_remove_button); | |
} | |
if (shouldGetWatchOptions) { | |
saveWatchOptionsMap(); | |
} | |
addObserver( | |
"ol.list_items", | |
"ol.list_items li:not(.visited) a.picture", | |
improveListPage | |
); | |
} | |
function addToListPageOptions() { | |
// filter menu | |
const _el_filters = document.getElementById("dropdown_list_show_me") | |
.children[0]; | |
// console.debug(_el_filters); | |
if (document.getElementById("filterForeignOnly")) { | |
return; | |
} | |
const el_adult_only = document.createElement("a"); | |
el_adult_only.id = "filterAdultOnly"; | |
el_adult_only.classList = | |
"hover:bg-gray-200 rounded-md text-black-600 block text-nowrap whitespace-nowrap px-4 py-2 text-sm lg:text-md"; | |
el_adult_only.textContent = "Adult"; | |
// el_adult_only.href = '#'; | |
_el_filters.appendChild(el_adult_only); | |
const el_not_adult_only = document.createElement("a"); | |
el_not_adult_only.id = "filterNotAdultOnly"; | |
el_not_adult_only.classList = | |
"hover:bg-gray-200 rounded-md text-black-600 block text-nowrap whitespace-nowrap px-4 py-2 text-sm lg:text-md"; | |
el_not_adult_only.textContent = "Not Adult"; | |
_el_filters.appendChild(el_not_adult_only); | |
const el_foreign_only = document.createElement("a"); | |
el_foreign_only.id = "filterForeignOnly"; | |
el_foreign_only.classList = | |
"hover:bg-gray-200 rounded-md text-black-600 block text-nowrap whitespace-nowrap px-4 py-2 text-sm lg:text-md"; | |
el_foreign_only.textContent = "Language"; | |
// el_foreign_only.href = '#'; | |
_el_filters.appendChild(el_foreign_only); | |
const el_streamable_only = document.createElement("a"); | |
el_streamable_only.id = "filterStreamableOnly"; | |
el_streamable_only.classList = | |
"hover:bg-gray-200 rounded-md text-black-600 block text-nowrap whitespace-nowrap px-4 py-2 text-sm lg:text-md"; | |
el_streamable_only.textContent = "Streamable"; | |
// el_foreign_only.href = '#'; | |
_el_filters.appendChild(el_streamable_only); | |
// document | |
// .getElementById("filterShowAll") | |
// .addEventListener("click", ListPageShowAll, false); | |
document | |
.getElementById("filterAdultOnly") | |
.addEventListener("click", ListPageAdultOnly, false); | |
document | |
.getElementById("filterNotAdultOnly") | |
.addEventListener("click", ListPageNotAdultOnly, false); | |
document | |
.getElementById("filterForeignOnly") | |
.addEventListener("click", ListPageForeignOnly, false); | |
document | |
.getElementById("filterStreamableOnly") | |
.addEventListener("click", ListPageStreamableOnly, false); | |
const el_summaryButton = document.createElement("button"); | |
el_summaryButton.onclick = () => listReport(); | |
el_summaryButton.textContent = "summary"; | |
el_summaryButton.classList.add( | |
"flex", | |
"items-center", | |
"text-md", | |
"text-white/50", | |
"font-semibold" | |
); | |
const el_longShortButton = document.createElement("button"); | |
el_summaryButton.onclick = () => addToShortAndLongList(); | |
// el_summaryButton.onclick = () => exportRatingsLetterboxd(); | |
// el_summaryButton.onclick = () => exportWatchListLetterboxd(); | |
el_summaryButton.textContent = "long/short lists"; | |
el_summaryButton.classList.add( | |
"flex", | |
"items-center", | |
"text-md", | |
"text-white/50", | |
"font-semibold" | |
); | |
const el_exportButton = document.createElement("button"); | |
el_exportButton.onclick = () => exportListLetterboxd(); | |
el_exportButton.textContent = "exportIds"; | |
el_exportButton.classList.add( | |
"flex", | |
"items-center", | |
"text-md", | |
"text-white/50", | |
"font-semibold" | |
); | |
const _el_buttons_div = document.getElementById("share").closest("div"); | |
_el_buttons_div.appendChild(el_summaryButton); | |
_el_buttons_div.appendChild(el_longShortButton); | |
_el_buttons_div.appendChild(el_exportButton); | |
} | |
function addToListPageItemsOptions() {} | |
async function improveListPage() { | |
if (window.location.pathname.endsWith("/edit")) { | |
return; | |
} | |
// TODO - create another function for list view | |
if (!window.location.search.includes("view=grid")) { | |
console.debug("NOT GRID"); | |
return; | |
} | |
console.debug("improvelistpage"); | |
const media_ratings = await load_tmdb_ratings(); | |
const media_watchlist = await load_tmdb_watchlist(); | |
const tmdb_id = parseInt( | |
window.location.href | |
.split("https://www.themoviedb.org/list/")[1] | |
.split("-")[0] | |
); | |
const [list_items, media_details] = await getListItemsAndMedia(tmdb_id); | |
// TODO: fix function for new layout | |
// await addRatedToListPage(list_items, media_ratings); | |
addToListPageOptions(); | |
addToListPageItemsOptions(); | |
await addMediaDetailsToListPage( | |
media_ratings, | |
media_details, | |
media_watchlist, | |
tmdb_id | |
); | |
} | |
async function improveWatchListPage() { | |
console.debug("improveWatchListPage"); | |
const media_to_list = await getMediaToListsMap(); | |
const mediaLinks = document.querySelectorAll( | |
"div.title a:not(.visited):has(h2)" | |
); | |
for (const media of mediaLinks) { | |
// console.debug(media.textContent); | |
const key = media.pathname.split("/").slice(1).join("-"); | |
media.classList.add("visited"); | |
if (media_to_list[key]) { | |
const TMDB_lists = []; | |
media_to_list[key].forEach((item) => { | |
TMDB_lists.push(item.split("|")[1]); | |
}); | |
media.querySelector("h2").textContent = `${ | |
media.textContent | |
} [${TMDB_lists.join(", ")}]`; | |
} | |
} | |
const _el_load_more = document.querySelector("a.load_more"); | |
if (_el_load_more) { | |
_el_load_more.addEventListener("click", () => { | |
setTimeout(improveWatchListPage, 1000); | |
}); | |
} | |
addObserver( | |
"div.items_wrapper", | |
"div.items_wrapper div.title a:not(.visited):has(h2)", | |
improveWatchListPage | |
); | |
} | |
async function improveSearchPage() { | |
// console.debug('improve search page'); | |
const media_to_list = await getMediaToListsMap(); | |
// console.debug('>> loaded lists'); | |
const media_ratings = await load_tmdb_ratings(); | |
// console.debug('>> loaded ratings'); | |
const mediaLinks = document.querySelectorAll("a.result:has(h2)"); | |
// console.debug('>> links', mediaLinks); | |
for (const media of mediaLinks) { | |
const key = media.pathname.split("/").slice(1).join("-"); | |
if (media_to_list[key]) { | |
const TMDB_lists = []; | |
media_to_list[key].forEach((item) => { | |
TMDB_lists.push(item.split("|")[1]); | |
}); | |
media.querySelector("h2").textContent = `${ | |
media.textContent | |
} [${TMDB_lists.join(", ")}]`; | |
media.closest("div.details").style.backgroundColor = HIGHLIGHT; | |
// media.parentElement.parentElement.style.backgroundColor = 'aliceblue'; | |
} | |
if (media_ratings[key]) { | |
media.querySelector( | |
"h2" | |
).textContent = `${media.textContent} ${media_ratings[key]}*`; | |
// media.parentElement.parentElement.style.backgroundColor = 'aliceblue'; | |
} | |
} | |
} | |
async function improveMediaPage() { | |
console.debug("improve media page"); | |
const media_to_list = await getMediaToListsMap(); | |
// console.debug(media_to_list); | |
let content = 0; | |
let TMDB_id = 0; | |
let TMDB_lists = []; | |
if (window.location.href.includes("https://www.themoviedb.org/movie/")) { | |
content = "movie"; | |
TMDB_id = parseInt( | |
window.location.href | |
.split("https://www.themoviedb.org/movie/")[1] | |
.split("-")[0] | |
); | |
} else if (window.location.href.includes("https://www.themoviedb.org/tv/")) { | |
content = "tv"; | |
TMDB_id = parseInt( | |
window.location.href | |
.split("https://www.themoviedb.org/tv/")[1] | |
.split("-")[0] | |
); | |
} else { | |
return; | |
} | |
// (media_to_list[`${content}-${TMDB_id}`] || []).forEach((item) => { | |
// TMDB_lists.push(item.split("|")[1]); | |
// }); | |
let el_lists_header = document.createElement("h3"); | |
el_lists_header.innerText = `Lists:`; // ${TMDB_lists.join(", ")}`; | |
el_lists_header.classList.add("tagline"); | |
(media_to_list[`${content}-${TMDB_id}`] || []).forEach((item) => { | |
// TMDB_lists.push(item.split("|")[1]); | |
const el_list_link = document.createElement("a"); | |
el_list_link.textContent = item.split("|")[1]; | |
el_list_link.href = `https://www.themoviedb.org/list/${item.split("|")[0]}`; | |
el_lists_header.appendChild(el_list_link); | |
const el_space = document.createElement("span"); | |
el_space.innerHTML = "<span> </span>"; | |
el_lists_header.appendChild(el_space); | |
const el_list_button = document.createElement("button"); | |
el_list_button.textContent = "x"; | |
el_list_button.onclick = () => { | |
remove_from_list(item.split("|")[0], `${content}-${TMDB_id}`); | |
}; | |
el_lists_header.appendChild(el_list_button); | |
const el_divider = document.createElement("span"); | |
el_divider.innerHTML = "<span>, </span>"; | |
el_lists_header.appendChild(el_divider); | |
}); | |
let _el_header_info = document.getElementsByClassName("header_info")[0]; | |
if (_el_header_info) { | |
_el_header_info.insertBefore(el_lists_header, _el_header_info.firstChild); | |
} | |
if (TMDB_lists.length) { | |
let _el_list_icon = document.getElementsByClassName("thumbnails-list")[0]; | |
_el_list_icon.classList.remove("thumbnails-list"); | |
_el_list_icon.classList.add("plus"); | |
} | |
// console.debug(`${content}-${TMDB_id}`,media_to_list[`${content}-${TMDB_id}`]); | |
// external lists feature | |
if (content === "movie") { | |
const _el_sidebar = document.querySelector("div.column.no_bottom_pad"); | |
const el_lists_div = document.createElement("section"); | |
el_lists_div.setAttribute("id", "listsSection"); | |
el_lists_div.innerHTML = `<section> | |
<h4 id="externalListsH4">External Lists</h4> | |
<div id="loadExtListsButtonDiv"> | |
<button id="loadExtLists" class="myButton">Load Lists</button> | |
</div> | |
<ul id="extListsUl"></ul> | |
</section> | |
`; | |
// _el_sidebar.appendChild(el_lists_div); | |
_el_sidebar.insertBefore(el_lists_div, _el_sidebar.children[1]); | |
document.getElementById("loadExtLists").addEventListener( | |
"click", | |
() => { | |
loadExtLists(TMDB_id, 1); | |
}, | |
false | |
); | |
} | |
const _el_sidebar = document.querySelector("div.column.no_bottom_pad"); | |
const el_add_to_list_div = document.createElement("section"); | |
el_add_to_list_div.setAttribute("id", "addToListSection"); | |
const el_select_list = document.createElement("select"); | |
el_select_list.id = "addToListSelect"; | |
el_select_list.classList.add("defaultStyle"); | |
const myLists = await load_tmdb_lists(); | |
const orderedLists = Object.entries(myLists) | |
.map((itm) => { | |
return { id: itm[0], name: itm[1].name }; | |
}) | |
.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)); | |
for (const list of orderedLists) { | |
var option = document.createElement("option"); | |
option.text = list.name; | |
option.value = list.id; | |
el_select_list.add(option); | |
// el_select_list.add({value:1, text:'name'}); | |
} | |
el_add_to_list_div.appendChild(el_select_list); | |
const el_add_to_list_btn = document.createElement("button"); | |
el_add_to_list_btn.textContent = "Add To List"; | |
el_add_to_list_btn.id = "addToListBtn"; | |
el_add_to_list_btn.classList.add("myButton"); | |
el_add_to_list_div.appendChild(el_add_to_list_btn); | |
// _el_sidebar.appendChild(el_lists_div); | |
_el_sidebar.insertBefore(el_add_to_list_div, _el_sidebar.children[1]); | |
document.getElementById("addToListBtn").addEventListener( | |
"click", | |
() => { | |
addMediaToList( | |
content, | |
TMDB_id, | |
document.getElementById("addToListSelect").value | |
); | |
}, | |
false | |
); | |
} | |
async function improveKeywordPage() { | |
console.debug("improveKeywordtPage"); | |
const media_to_list = await getMediaToListsMap(); | |
const media_ratings = await load_tmdb_ratings(); | |
const media_watchlist = await load_tmdb_watchlist(); | |
const mediaLinks = document.querySelectorAll( | |
"div.items_wrapper div.card:not(.visited) div.details a" | |
); | |
// console.debug(mediaLinks); | |
for (const media of mediaLinks) { | |
// // console.debug(media.textContent); | |
const key = media.pathname.split("/").slice(1).join("-"); | |
media.closest("div.card").classList.add("visited"); | |
if (media_to_list[key]) { | |
const TMDB_lists = []; | |
media_to_list[key].forEach((item) => { | |
TMDB_lists.push(item.split("|")[1]); | |
}); | |
media.querySelector("h2").textContent = `${ | |
media.textContent | |
} [${TMDB_lists.join(", ")}]`; | |
if (media_ratings[key]) { | |
media.closest("div.card").style.backgroundColor = HIGHLIGHT_RATED; | |
} else if (media_watchlist[key]) { | |
media.closest("div.card").style.backgroundColor = HIGHLIGHT_WATCHLIST; | |
} | |
} | |
} | |
addObserver( | |
"div.items_wrapper", | |
"div.items_wrapper div.card:not(.visited)", | |
improveKeywordPage | |
); | |
} | |
async function improveRatingsPage() { | |
console.debug("improveRatingsPage"); | |
const _el_title_group = document.querySelector("div.title_group"); | |
const el_title = document.createElement("h3"); | |
const el_button = document.createElement("button"); | |
el_button.textContent = "Month Report"; | |
el_button.onclick = () => { | |
monthlyReport(); | |
}; | |
el_title.appendChild(el_button); | |
const el_button1 = document.createElement("button"); | |
el_button1.textContent = "Export 4 Lists"; | |
el_button1.onclick = () => { | |
exportWatchedLetterboxd(); | |
}; | |
el_title.appendChild(el_button1); | |
_el_title_group.appendChild(el_title); | |
} | |
/* | |
MAIN | |
*/ | |
async function main() { | |
// add global styles | |
addGlobalStyle(".less_avg { color: red }"); | |
// addGlobalStyle(".language { color: white }"); | |
addGlobalStyle(".shortruntime { color: green }"); | |
// addGlobalStyle(".regruntime { color: black }"); | |
addGlobalStyle(".longruntime { color: red }"); | |
addGlobalStyle("span.list-itm-extra-info { white-space: pre }"); | |
addGlobalStyle("button.myButton { all: revert }"); | |
addGlobalStyle(".defaultStyle { all: revert }"); | |
if (window.location.href.startsWith("https://www.themoviedb.org/movie/")) { | |
improveMediaPage(); | |
} else if ( | |
window.location.href.startsWith("https://www.themoviedb.org/tv/") | |
) { | |
improveMediaPage(); | |
} else if ( | |
window.location.href.startsWith("https://www.themoviedb.org/person/") | |
) { | |
improvePersonPage(); | |
} else if ( | |
window.location.href.startsWith("https://www.themoviedb.org/search?") | |
) { | |
improveSearchPage(); | |
} else if ( | |
window.location.href.startsWith("https://www.themoviedb.org/list/") | |
) { | |
improveListPage(); | |
} else if ( | |
window.location.href.startsWith( | |
"https://www.themoviedb.org/u/falconsensei/watchlist" | |
) | |
) { | |
improveWatchListPage(); | |
} else if ( | |
window.location.href.startsWith("https://www.themoviedb.org/keyword") | |
) { | |
improveKeywordPage(); | |
} else if ( | |
window.location.href.startsWith("https://www.themoviedb.org/u/") && | |
window.location.href.includes("ratings") | |
) { | |
improveRatingsPage(); | |
} | |
} | |
main(); | |
/* | |
HELPER FUNCTIONS FOR REPORTS | |
*/ | |
function getMdSummaries(mediaMap) { | |
const orderedCredits = {}; | |
let mdCredits = ""; | |
let mdLanguages = ""; | |
let mdDecades = ""; | |
let mdGenres = ""; | |
let mdKeywords = ""; | |
for (const job in mediaMap.credits) { | |
orderedCredits[job] = Object.values(mediaMap.credits[job]) | |
.filter((item) => item.total > 1 || job == "actress") | |
.sort((a, b) => | |
a.total != b.total ? b.total - a.total : a.name < b.name ? -1 : 1 | |
); | |
if (orderedCredits[job].length == 0) { | |
delete orderedCredits[job]; | |
continue; | |
} | |
mdCredits += ` \n${job}`; | |
for (const person of orderedCredits[job]) { | |
mdCredits += `\n- [${person.name}](https://www.themoviedb.org/person/${person.id}) - ${person.total}`; | |
} | |
mdCredits += "\n"; | |
} | |
const orderedLanguages = Object.entries(mediaMap.languages) | |
.sort((a, b) => (b[1] != a[1] ? b[1] - a[1] : a[0] < b[0] ? -1 : 1)) | |
.map(([key, value]) => { | |
return { lang: key, total: value }; | |
}); | |
const orderedDecades = Object.entries(mediaMap.decades) | |
.sort((a, b) => (b[1] != a[1] ? b[1] - a[1] : a[0] < b[0] ? -1 : 1)) | |
.map(([key, value]) => { | |
return { decade: key, total: value }; | |
}); | |
const orderedKeywords = Object.entries(mediaMap.keywords) | |
.sort((a, b) => (b[1] != a[1] ? b[1] - a[1] : a[0] < b[0] ? -1 : 1)) | |
.filter(([key, value]) => value > 1) | |
.map(([key, value]) => { | |
return { keyword: key, total: value }; | |
}); | |
const orderedGenres = Object.entries(mediaMap.genres) | |
.sort((a, b) => (b[1] != a[1] ? b[1] - a[1] : a[0] < b[0] ? -1 : 1)) | |
.map(([key, value]) => { | |
return { genre: key, total: value }; | |
}); | |
for (const language of orderedLanguages) { | |
mdLanguages += `\n- ${language.lang}: ${language.total}`; | |
} | |
for (const decade of orderedDecades) { | |
mdDecades += `\n- ${decade.decade}: ${decade.total}`; | |
} | |
for (const keyword of orderedKeywords) { | |
mdKeywords += `\n- ${keyword.keyword}: ${keyword.total}`; | |
} | |
for (const genre of orderedGenres) { | |
mdGenres += `\n- ${genre.genre}: ${genre.total}`; | |
} | |
return { | |
// orderedCredits, | |
mdCredits, | |
mdLanguages, | |
mdDecades, | |
mdGenres, | |
mdKeywords, | |
avg_rating: | |
mediaMap.rating.reduce((a, b) => a + b, 0) / mediaMap.rating.length, | |
avg_global_rating: | |
mediaMap.global_rating.reduce((a, b) => a + b, 0) / | |
mediaMap.global_rating.length, | |
}; | |
} | |
/* | |
MONTHLY REPORT | |
*/ | |
async function monthlyReport() { | |
let month = parseInt(prompt("Month")); | |
if (isNaN(month) || month < 1 || month > 12) { | |
alert("error", month); | |
} | |
month = month - 1; | |
const year = new Date().getFullYear(); | |
const rated_items = []; | |
const movieData = {}; | |
const mainData = await fetch_ratings_page("movie", 1); | |
const pages = mainData.total_pages; | |
let page = pages; | |
do { | |
const data = await fetch_ratings_page("movie", page); | |
page = page - 1; | |
// console.debug(data); | |
for (const item of data.results) { | |
const rating_date = new Date(item["account_rating"]["created_at"]); | |
// console.debug(rating_date); | |
if ( | |
rating_date.getFullYear() === year && | |
rating_date.getMonth() === month | |
) { | |
rated_items.push(`${item["id"]}=${item["title"]}`); | |
movieData[item["id"]] = item; | |
} | |
// API DOES NOT RETURN SORTED RATINGS; | |
// else if (rating_date.getMonth() < month) { | |
// console.debug("break", rating_date.getMonth(), month, item); | |
// page = 0; | |
// break; | |
// } | |
} | |
// rating_items.push(...data.results); | |
} while (page > 0); | |
// console.debug(rated_items); | |
alert(rated_items.join("|")); | |
const toUse = prompt("Items to consider"); | |
const movieIds = toUse.split("|").map((item) => item.split("=")[0]); | |
// console.debug("movieids", movieIds); | |
const allMovies = { | |
languages: {}, | |
decades: {}, | |
rating: [], | |
global_rating: [], | |
credits: {}, | |
keywords: {}, | |
genres: {}, | |
mdMovies: "", | |
}; | |
const ratings = await load_tmdb_ratings(); | |
for (const id of movieIds) { | |
const itm = await fetch_movie_details_credits(id); | |
const all_credits = itm["credits"]; | |
// console.debug(id, all_credits, itm); | |
const cast = all_credits.cast.concat(all_credits.crew); | |
const decade = itm.release_date | |
? itm.release_date.substring(0, 3) + "0s" | |
: "0"; | |
const originalTitle = | |
itm.title === itm.original_title ? "" : `(${itm.original_title})`; | |
const movieListItem = `\n- ${itm.title} (${ | |
itm.release_date ? itm.release_date.substring(0, 4) : "?" | |
}) ${originalTitle} [link](https://www.themoviedb.org/movie/${itm.id})`; | |
allMovies.mdMovies += movieListItem; | |
allMovies.decades[decade] = (allMovies.decades[decade] || 0) + 1; | |
allMovies.languages[itm.original_language] = | |
(allMovies.languages[itm.original_language] || 0) + 1; | |
ratings["movie-" + id] && allMovies.rating.push(ratings["movie-" + id]); | |
allMovies.global_rating.push(itm.vote_average); | |
for (const genre of itm.genres) { | |
allMovies.genres[genre.name] = (allMovies.genres[genre.name] || 0) + 1; | |
} | |
for (const keyword of itm.keywords.keywords) { | |
allMovies.keywords[keyword.name] = | |
(allMovies.keywords[keyword.name] || 0) + 1; | |
} | |
for (const person of cast) { | |
const tempJob = ( | |
person.job || | |
(person.character ? "acting" : `unknown-${person.department}`) | |
).toLowerCase(); | |
const job = | |
tempJob === "acting" | |
? person.gender == 1 | |
? "actress" | |
: "actor" | |
: tempJob; | |
// console.debug(job, !CREW_ROLES.includes(job)) | |
if (!CREW_ROLES.includes(job) && !job.startsWith("unknown-")) { | |
continue; | |
} | |
const creditDetails = { | |
id: person.id, | |
total: 0, | |
profile_path: person.profile_path, | |
gender: person.gender, | |
known_for_department: person.known_for_department, | |
department: person.department, | |
name: person.name, | |
original_name: person.original_name, | |
}; | |
if (!allMovies.credits[job]) { | |
allMovies.credits[job] = {}; | |
} | |
if (!allMovies.credits[job][person.id]) { | |
allMovies.credits[job][person.id] = { ...creditDetails }; | |
} | |
allMovies.credits[job][person.id].total += 1; | |
} | |
} | |
console.debug(allMovies.credits); | |
const allMovieSummaries = getMdSummaries(allMovies); | |
const allData = { | |
// rated: { | |
avg_rating: allMovies.avg_rating, | |
avg_global_rating: allMovies.avg_global_rating, | |
mdMovies: allMovies.mdMovies, | |
...allMovieSummaries, | |
// }, | |
}; | |
console.debug(allData); | |
} | |
/* | |
LIST REPORT | |
*/ | |
async function listReport() { | |
const listId = parseInt( | |
window.location.href | |
.split("https://www.themoviedb.org/list/")[1] | |
.split("-")[0] | |
); | |
// const [list_items, media_details] = await getListItemsAndMedia(tmdb_id); | |
const lists = await load_tmdb_lists(); | |
const ratings = await load_tmdb_ratings(); | |
const movieIds = lists[listId].items | |
.filter((itm) => itm.split("-")[0] === "movie") | |
.map((itm) => itm.split("-")[1]); | |
// lists[listId].items | |
// const movieData = {}; | |
const allMovies = { | |
languages: {}, | |
decades: {}, | |
rating: [], | |
global_rating: [], | |
credits: {}, | |
keywords: {}, | |
genres: {}, | |
mdMovies: "", | |
}; | |
const ratedMovies = { | |
languages: {}, | |
decades: {}, | |
rating: [], | |
global_rating: [], | |
credits: {}, | |
keywords: {}, | |
genres: {}, | |
mdMovies: "", | |
}; | |
for (const id of movieIds) { | |
const itm = await fetch_movie_details_credits(id); | |
const all_credits = itm["credits"]; | |
const cast = all_credits.cast.concat(all_credits.crew); | |
const decade = itm.release_date | |
? itm.release_date.substring(0, 3) + "0s" | |
: "0"; | |
const originalTitle = | |
itm.title === itm.original_title ? "" : `(${itm.original_title})`; | |
const movieListItem = `\n- ${itm.title} (${ | |
itm.release_date ? itm.release_date.substring(0, 4) : "?" | |
}) ${originalTitle} [link](https://www.themoviedb.org/movie/${itm.id})`; | |
allMovies.mdMovies += movieListItem; | |
allMovies.decades[decade] = (allMovies.decades[decade] || 0) + 1; | |
allMovies.languages[itm.original_language] = | |
(allMovies.languages[itm.original_language] || 0) + 1; | |
ratings["movie-" + id] && allMovies.rating.push(ratings["movie-" + id]); | |
allMovies.global_rating.push(itm.vote_average); | |
if (ratings["movie-" + id]) { | |
ratedMovies.mdMovies += movieListItem; | |
ratedMovies.decades[decade] = (ratedMovies.decades[decade] || 0) + 1; | |
ratedMovies.languages[itm.original_language] = | |
(ratedMovies.languages[itm.original_language] || 0) + 1; | |
ratings["movie-" + id] && ratedMovies.rating.push(ratings["movie-" + id]); | |
ratedMovies.global_rating.push(itm.vote_average); | |
} | |
for (const genre of itm.genres) { | |
allMovies.genres[genre.name] = (allMovies.genres[genre.name] || 0) + 1; | |
if (ratings["movie-" + id]) { | |
ratedMovies.genres[genre.name] = | |
(ratedMovies.genres[genre.name] || 0) + 1; | |
} | |
} | |
for (const keyword of itm.keywords.keywords) { | |
allMovies.keywords[keyword.name] = | |
(allMovies.keywords[keyword.name] || 0) + 1; | |
if (ratings["movie-" + id]) { | |
ratedMovies.keywords[keyword.name] = | |
(ratedMovies.keywords[keyword.name] || 0) + 1; | |
} | |
} | |
for (const person of cast) { | |
const tempJob = ( | |
person.job || | |
(person.character ? "acting" : `unknown-${person.department}`) | |
).toLowerCase(); | |
const job = | |
tempJob === "acting" | |
? person.gender == 1 | |
? "actress" | |
: "actor" | |
: tempJob; | |
// console.debug(job, !CREW_ROLES.includes(job)) | |
if (!CREW_ROLES.includes(job) && !job.startsWith("unknown-")) { | |
continue; | |
} | |
const creditDetails = { | |
id: person.id, | |
total: 0, | |
profile_path: person.profile_path, | |
gender: person.gender, | |
known_for_department: person.known_for_department, | |
department: person.department, | |
name: person.name, | |
original_name: person.original_name, | |
}; | |
if (!allMovies.credits[job]) { | |
allMovies.credits[job] = {}; | |
} | |
if (!allMovies.credits[job][person.id]) { | |
allMovies.credits[job][person.id] = { ...creditDetails }; | |
} | |
allMovies.credits[job][person.id].total += 1; | |
if (ratings["movie-" + id]) { | |
if (!ratedMovies.credits[job]) { | |
ratedMovies.credits[job] = {}; | |
} | |
if (!ratedMovies.credits[job][person.id]) { | |
ratedMovies.credits[job][person.id] = { ...creditDetails }; | |
} | |
ratedMovies.credits[job][person.id].total += 1; | |
} | |
} | |
} | |
const allMovieSummaries = getMdSummaries(allMovies); | |
const ratedMovieSummaries = getMdSummaries(ratedMovies); | |
const allData = { | |
rated: { | |
avg_rating: ratedMovies.avg_rating, | |
avg_global_rating: ratedMovies.avg_global_rating, | |
mdMovies: ratedMovies.mdMovies, | |
...ratedMovieSummaries, | |
}, | |
// all: { | |
// ...allMovies, | |
// ...allMovieSummaries, | |
// }, | |
}; | |
// console.debug(credits); | |
console.debug(allData); | |
} | |
/* | |
OTHER FUNCTIONS | |
*/ | |
async function addToShortAndLongList() { | |
// LONG_MOVIES_LIST, SHORT_MOVIES_LIST | |
const lists = await load_tmdb_lists(); | |
// all stored media details are from my lists, so can use that directly; | |
const mediaDetails = await getMediaDetails(); | |
const moviesToSkip = []; | |
// .concat is not working?? | |
moviesToSkip.push( | |
...lists[LONG_MOVIES_LIST].items | |
.filter((item) => item.startsWith("movie-")) | |
.map((item) => item.split("-")[1]) | |
); | |
moviesToSkip.push( | |
...lists[SHORT_MOVIES_LIST].items | |
.filter((item) => item.startsWith("movie-")) | |
.map((item) => item.split("-")[1]) | |
); | |
// console.debug(moviesToSkip); | |
// console.debug(mediaDetails); | |
const addToLong = []; | |
const addToShort = []; | |
for (const [key, item] of Object.entries(mediaDetails)) { | |
// console.debug(key, item); | |
const [type, id] = key.split("-"); | |
// console.debug("details:", key, type, id, item); | |
if (type !== "movie" || moviesToSkip.includes(id)) { | |
// console.debug("skipping", type, id); | |
continue; | |
} | |
// console.debug(item); | |
if (item.runtime <= SHORT_DURATION) { | |
// console.debug("add to short", item); | |
addToShort.push({ media_type: "movie", media_id: id }); | |
} else if (item.runtime >= LONG_DURATION) { | |
// console.debug("add to long", item); | |
addToLong.push({ media_type: "movie", media_id: id }); | |
} | |
} | |
console.debug("long", addToLong); | |
console.debug("short", addToShort); | |
addToShort.length && (await add_to_tmdb_list(addToShort, SHORT_MOVIES_LIST)); | |
addToLong.length && (await add_to_tmdb_list(addToLong, LONG_MOVIES_LIST)); | |
} | |
async function exportRatingsLetterboxd() { | |
const ratings = await load_tmdb_ratings(); | |
let exportedRatings = "tmdbID,Rating10\n"; | |
for (const [key, value] of Object.entries(ratings)) { | |
// console.debug(key, item); | |
const [type, id] = key.split("-"); | |
if (type !== "movie") { | |
continue; | |
} | |
exportedRatings += `${id},${value}\n`; | |
} | |
navigator.clipboard.writeText(exportedRatings); | |
alert(exportedRatings); | |
} | |
async function exportWatchedLetterboxd() { | |
const items = await load_tmdb_ratings(); | |
let exportedItems = "list,tmdbID,title\n"; | |
for (const [key, value] of Object.entries(items)) { | |
// console.debug(key, item); | |
const [type, id] = key.split("-"); | |
if (type !== "movie") { | |
continue; | |
} | |
const itm = await fetch_movie_details_credits(id); | |
exportedItems += `,${id},"${itm.title}"\n`; | |
} | |
alert(exportedItems); | |
navigator.clipboard.writeText(exportedItems); | |
} | |
async function exportListLetterboxd() { | |
const exportTitle = true; | |
const listId = parseInt( | |
window.location.href | |
.split("https://www.themoviedb.org/list/")[1] | |
.split("-")[0] | |
); | |
const items = await getListItems(listId); | |
let exportedItems = exportTitle ? "tmdbID,Title\n" : "tmdbID\n"; | |
for (const key of items) { | |
// console.debug(key, item); | |
const [type, id] = key.split("-"); | |
if (type !== "movie") { | |
continue; | |
} | |
if (exportTitle) { | |
const itm = await fetch_movie_details_credits(id); | |
exportedItems += `${id},"${itm.title}"\n`; | |
} else { | |
exportedItems += `${id}\n`; | |
} | |
} | |
alert(exportedItems); | |
navigator.clipboard.writeText(exportedItems); | |
} | |
async function exportWatchListLetterboxd() { | |
const items = await load_tmdb_watchlist(); | |
let exportedItems = "tmdbID,title\n"; | |
for (const [key, value] of Object.entries(items)) { | |
// console.debug(key, item); | |
const [type, id] = key.split("-"); | |
if (type !== "movie") { | |
continue; | |
} | |
const itm = await fetch_movie_details_credits(id); | |
exportedItems += `${id},"${itm.title}"\n`; | |
} | |
alert(exportedItems); | |
navigator.clipboard.writeText(exportedItems); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment