// ==UserScript==
// @name Filmweb External Ratings
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Add IMDb, Rotten Tomatoes, and Metacritic ratings to Filmweb
// @author mrkkr
// @match http*://www.filmweb.pl/serial/*
// @match http*://www.filmweb.pl/film/*
// @match http*://www.filmweb.pl/tvshow/*
// @match http*://www.imdb.com/*
// @match http*://www.rottentomatoes.com/*
// @match http*://www.metacritic.com/*
// @grant GM_xmlhttpRequest
// @connect imdb.com
// @connect rottentomatoes.com
// @connect metacritic.com
// ==/UserScript==
(function() {
'use strict';
const ICONS = {
imdb: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA6ElEQVQ4ja2TsQ3CMBBF/0UMgSgYgBkyAANkBEZgBLqUkWgzAiPQMgINI7CBaGgQhbElY4JQPsnS6c7/7/587wB/GAA7ADcAKYAYJ+IL9bV0JoX73I0AKgmqAbQAngBGAE8JagCcdG5MUEQGqT0AGwBHOZ8A7LXxCmCpdR1LyVyqL3rj2mm6AnCQzEbzRZ1dVMWdU5VIHqPFKJc+c5MC0CvTWvPGBE34JV0JwFwL3GfqtKi7WQbVHlXY1Dwp/Gv3w2I6W6MjgK0UzRpHXX1qXQN4fwsITRmAfaSp86bKHNgF8L9kpXEeE/QBWUU9pW4s4WYAAAAASUVORK5CYII=',
rotten: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAABP0lEQVQ4jZWSPUsDQRCGn7vceSpiYSMIAUEQRLCxsLSxFgv/goWNjYU/wcI/YGlhYWdlYyEIFoJgIQiCIBgQBU0uzd3u7FjcJZcYc+oLL8w+M/PO7MwK/hm+5wngxvdcqT1nwRmwD/QkSS7DMLyW1k5cK8ltxJmwNgQuAC0iQ2BDRC6ttdPGUkQi4AfYBHaAHeAd6AFC0zQP1tqRqvzFHSilWiKyBawppUZhGN7XwDmXK6UcMPX9WVFV6jWIyJox5tj3/XPf91/CMHwDMMaMAE9ExsCZMebUWvtVOVBKrQJdYB5fVBTFR3WmtR4Dpz+Ps8YLlNYqpZaAh6IoPqvAOZc75144514rQGayB6K1XhGRO+dc3DRutdYbwKDMZO4EVdtF8gocAQNjzKQEp8AYeAL6ZSZzG4lI2xgzKRtXRG6/AduYkKWrJqV7AAAAAElFTkSuQmCC',
metacritic: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAtUlEQVQ4jWNgoBAwQun/+BSzYBH7j0UNXgW4DPgPBTjlYQrwGYBNDqcBMAOI0YzTAGI1YzcAj2asBhDQjGEAIc3oBox/u3b3PwMDw38GBob/x95s2Y1LM7IBr7+c3//405n9/xkYGP4ffX1i/pH3x3bj0oxiwJsvF/Yfen9s938GBob/h94d2X3o3ZHd2DQjG3Dg3eHdB94d3v2fgYHh/4G3B3cfendkNy7NSAacs7XlGXEpBgDZMJSlUEFhkwAAAABJRU5ErkJggg=='
};
async function getIMDbRating(title, year) {
try {
const imdbIdMatch = document.querySelector('a[href*="www.imdb.com/title/tt"]');
if (!imdbIdMatch) return null;
const imdbId = imdbIdMatch.href.match(/tt\d+/)[0];
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://www.imdb.com/title/${imdbId}/`,
onload: function(response) {
const ratingMatch = response.responseText.match(/"ratingValue":\s*"([^"]+)"/);
if (!ratingMatch) resolve(null);
resolve({
rating: ratingMatch[1],
url: `https://www.imdb.com/title/${imdbId}/`
});
},
onerror: function(error) {
console.error('Error fetching IMDb rating:', error);
resolve(null);
}
});
});
} catch (error) {
console.error('Error fetching IMDb rating:', error);
return null;
}
}
async function getRottenTomatoesRating(title, year) {
try {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://www.rottentomatoes.com/search?search=${encodeURIComponent(title)}`,
onload: async function(response) {
try {
const rtUrlMatch = response.responseText.match(new RegExp(`href="([^"]+${year}[^"]+)"`));
if (!rtUrlMatch) {
resolve(null);
return;
}
const rtUrl = 'https://www.rottentomatoes.com' + rtUrlMatch[1];
GM_xmlhttpRequest({
method: 'GET',
url: rtUrl,
onload: function(movieResponse) {
const ratingMatch = movieResponse.responseText.match(/tomatometer">(\d+)%/);
if (!ratingMatch) {
resolve(null);
return;
}
resolve({
rating: ratingMatch[1] + '%',
url: rtUrl
});
},
onerror: function(error) {
console.error('Error fetching RT movie page:', error);
resolve(null);
}
});
} catch (error) {
console.error('Error processing RT response:', error);
resolve(null);
}
},
onerror: function(error) {
console.error('Error fetching RT search:', error);
resolve(null);
}
});
});
} catch (error) {
console.error('Error in RT rating function:', error);
return null;
}
}
async function getMetacriticRating(title, year) {
try {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://www.metacritic.com/search/movie/${encodeURIComponent(title)}/results`,
onload: async function(response) {
try {
const mcUrlMatch = response.responseText.match(new RegExp(`href="([^"]+${year}[^"]+)"`));
if (!mcUrlMatch) {
resolve(null);
return;
}
const mcUrl = 'https://www.metacritic.com' + mcUrlMatch[1];
GM_xmlhttpRequest({
method: 'GET',
url: mcUrl,
onload: function(movieResponse) {
const ratingMatch = movieResponse.responseText.match(/metascore_w[^>]+>(\d+)</);
if (!ratingMatch) {
resolve(null);
return;
}
resolve({
rating: ratingMatch[1],
url: mcUrl
});
},
onerror: function(error) {
console.error('Error fetching Metacritic movie page:', error);
resolve(null);
}
});
} catch (error) {
console.error('Error processing Metacritic response:', error);
resolve(null);
}
},
onerror: function(error) {
console.error('Error fetching Metacritic search:', error);
resolve(null);
}
});
});
} catch (error) {
console.error('Error in Metacritic rating function:', error);
return null;
}
}
function createRatingElement(icon, rating, url) {
const container = document.createElement('div');
container.style.cssText = `
display: inline-flex;
align-items: center;
margin: 0 10px;
cursor: pointer;
padding: 5px 10px;
border-radius: 4px;
background: rgba(0,0,0,0.05);
transition: background 0.2s;
`;
container.onmouseover = () => container.style.background = 'rgba(0,0,0,0.1)';
container.onmouseout = () => container.style.background = 'rgba(0,0,0,0.05)';
const img = document.createElement('img');
img.src = icon;
img.style.cssText = `
width: 16px;
height: 16px;
margin-right: 8px;
`;
const span = document.createElement('span');
span.textContent = rating || 'N/A';
span.style.cssText = `
font-weight: bold;
color: #333;
`;
container.appendChild(img);
container.appendChild(span);
if (url) {
container.addEventListener('click', () => window.open(url, '_blank'));
}
return container;
}
async function init() {
if (!document.querySelector('.filmCoverSection__title')) {
setTimeout(init, 100);
return;
}
const titleElement = document.querySelector('.filmCoverSection__title');
if (!titleElement) return;
const title = titleElement.textContent.trim();
const year = document.querySelector('.filmCoverSection__year')?.textContent.trim() || '';
const ratingsContainer = document.createElement('div');
ratingsContainer.style.cssText = `
margin-top: 15px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
`;
const filmCoverSection = document.querySelector('.filmCoverSection__card');
if (!filmCoverSection) return;
const placeholders = {
imdb: createRatingElement(ICONS.imdb, 'Loading...', null),
rotten: createRatingElement(ICONS.rotten, 'Loading...', null),
metacritic: createRatingElement(ICONS.metacritic, 'Loading...', null)
};
Object.values(placeholders).forEach(el => ratingsContainer.appendChild(el));
filmCoverSection.appendChild(ratingsContainer);
try {
const [imdb, rotten, metacritic] = await Promise.all([
getIMDbRating(title, year),
getRottenTomatoesRating(title, year),
getMetacriticRating(title, year)
]);
if (imdb) placeholders.imdb.replaceWith(createRatingElement(ICONS.imdb, imdb.rating, imdb.url));
if (rotten) placeholders.rotten.replaceWith(createRatingElement(ICONS.rotten, rotten.rating, rotten.url));
if (metacritic) placeholders.metacritic.replaceWith(createRatingElement(ICONS.metacritic, metacritic.rating, metacritic.url));
} catch (error) {
console.error('Error fetching ratings:', error);
}
}
init();
})();