{
"nid": "bad-boys-for-life",
"imdb": "tt1502397",
"type": "single",
"originalTitle": "Bad Boys for Life",
"title": "Bad Boys For Life",
"releaseYear": "2020",
"normalizedScore": "5.58",
"reviews": [
{
"link": "http://cinemazone.dk/review.asp?id=11799&area=1",
"source": {
"id": 0,
"name": "CinemaZone.dk",
"rank": 10,
"language": "DA"
},
"score": "4",
"maxScore": "6",
"displayScore": "4/6",
"normalizedScore": "6.67",
"quotes": [
{
"text": "Smith & Lawrence er fortsat fine sammen, og de nye på holdet vokser med filmen."
}
]
}
]
}
const MY_GUID = "[your GUID]"; // The GUID of your installation
const IMDB_ID = "tt1502397"; // The IMDB ID for the desired program
const SEASON_NUMBER = undefined;
window.onload = function() {
fetch(`https://api.lucidity.tv/json/${MY_GUID}/${IMDB_ID}.json`)
.then(response => response.json())
.then(json => addReviewsToPage({ seasonNumber: SEASON_NUMBER, data: json }))
.catch(error => {
// No reviews available
});
};
const REQUIRED_SCORE_FOR_AVERAGE = 5; // The average score will only be displayed if it is at least 5 or higher.
const REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE = 5; // The average score will only be displayed if we have at least 5 reviews with scores available.
const AVERAGE_EXPLANATION_TEXT = "Average score based on local reviews."; // The text displayed along with the average score.
const REQUIRED_REVIEW_SCORE = 2;// Only reviews with a normalised score of at least 2 will be displayed.
const NR_OF_QUOTES = 3; // We will display 3 quotes including scores if available.
const NR_OF_SCORES = 5; // We will display 5 scores if available.
const INCLUDED_LANGUAGES = ["DA", "EN"]; // Only include reviews from sources with these languages
function addReviewsToPage({ seasonNumber, data = {} }) {
let review = data;
// For series, there will be separate objects for each season, identified by the season number
if (seasonNumber !== undefined && Array.isArray(data)) {
review = data.find(r => r.seasonNumber === seasonNumber) ?? {};
}
const { normalizedScore, reviews = [] } = review;
if (!reviews?.length) {
return null;
}
const showNormalizedScore = parseFloat(normalizedScore) >= REQUIRED_SCORE_FOR_AVERAGE && reviews.length >= REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE;
const reviewsWithQuotes = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && r.quotes?.length).slice(0, NR_OF_QUOTES);
const otherReviews = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && !reviewsWithQuotes.includes(r)).slice(0, NR_OF_SCORES);
const container = _createElement("div", { className: "june-lucidity-program-reviews" });
if (showNormalizedScore) {
const scoreWrapper = _createElement("div", { className: "normalized-score" });
scoreWrapper.appendChild(_createElement("span", { innerText: roundToHalf(normalizedScore) }));
scoreWrapper.appendChild(_createElement("span", { innerText: AVERAGE_EXPLANATION_TEXT }));
container.appendChild(scoreWrapper);
}
reviewsWithQuotes.forEach(r => container.appendChild(getReviewElement(r, true)));
otherReviews.forEach(r => container.appendChild(getReviewElement(r, false)));
const productInfoContainer = document.querySelector("#Movie > div.Product__info > div > div > div.col-sm-9");
productInfoContainer.insertBefore(container, productInfoContainer.firstChild);
}
function getReviewElement(review, includeQuote) {
const reviewElement = _createElement("div", { className: "review" });
if (includeQuote) {
reviewElement.classList.add("include-quote");
reviewElement.appendChild(_createElement("span", { className: "quote", innerText: `"${review.quotes[0].text}"` }));
}
const scoreWrapper = _createElement("div", { className: "score-source" });
scoreWrapper.appendChild(getReviewScore(review));
scoreWrapper.appendChild(getReviewSource(review));
reviewElement.appendChild(scoreWrapper);
return reviewElement;
}
function getReviewScore(review) {
const image = review.assets?.find(a => a.format === "h30px");
const tagName = image ? "img" : "span";
return _createElement(tagName, {
className: "score",
src: image?.url,
innerText: !image && review.displayScore,
});
}
function getReviewSource(review) {
const sourceUrl = review.source.assets?.find(a => a.format === "h30px")?.url;
const element = _createElement("a", {
className: "source",
target: "_blank",
href: review.link,
});
if (sourceUrl) {
element.appendChild(_createElement("img", {
className: "source-image",
src: sourceUrl,
}));
} else {
element.innerText = review.source.name;
}
return element;
}
function roundToHalf(num) {
return Math.round(parseFloat(num) * 2) / 2;
}
function _createElement(tagName, properties) {
const element = document.createElement(tagName);
Object.entries(properties).forEach(([key, value]) => element[key] = value);
return element;
}
// This React example does not include fetching the data from the JSON file.
// Please see the "Plain JavaScript" example above to see an example of how that can be done.
const REQUIRED_SCORE_FOR_AVERAGE = 5; // The average score will only be displayed if it is at least 5 or higher.
const REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE = 5; // The average score will only be displayed if we have at least 5 reviews with scores available.
const AVERAGE_EXPLANATION_TEXT = "Average score based on local reviews."; // The text displayed along with the average score.
const REQUIRED_REVIEW_SCORE = 2;// Only reviews with a normalised score of at least 2 will be displayed.
const NR_OF_QUOTES = 3; // We will display 3 quotes including scores if available.
const NR_OF_SCORES = 5; // We will display 5 scores if available.
const INCLUDED_LANGUAGES = ["DA", "EN"]; // Only include reviews from sources with these languages
export function renderProgramReviews({ seasonNumber, data = {} }) {
let review = data;
// For series, there will be separate objects for each season, identified by the season number
if (seasonNumber !== undefined && Array.isArray(data)) {
review = data.find(r => r.seasonNumber === seasonNumber) ?? {};
}
const { normalizedScore, reviews = [] } = review;
if (!reviews?.length) {
return null;
}
const showNormalizedScore = parseFloat(normalizedScore) >= REQUIRED_SCORE_FOR_AVERAGE && reviews.length >= REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE;
const reviewsWithQuotes = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && r.quotes?.length).slice(0, NR_OF_QUOTES);
const otherReviews = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && !reviewsWithQuotes.includes(r)).slice(0, NR_OF_SCORES);
return (
<div className="program-reviews">
{showNormalizedScore && (
<div className="normalized-score">
<span>{roundToHalf(normalizedScore)}</span>
<span>{AVERAGE_EXPLANATION_TEXT}</span>
</div>
)}
{reviewsWithQuotes.map(r => renderReview(r, true))}
<br/>
{otherReviews.map(r => renderReview(r, false))}
</div>
);
}
function renderReview(review, includeQuote) {
return (
<div className={`review ${includeQuote ? "include-quote" : ""}`} key={review.source.name}>
{includeQuote && <span className="quote">{review.quotes[0].text}</span>}
<div className="score-source">
{renderReviewScore(review)}
{renderSource(review)}
</div>
</div>
);
}
function renderReviewScore(review) {
const image = review.assets?.find(a => a.format === "h30px");
return image ? <img className="score" src={image.url} /> : <span className="score">{review.displayScore}</span>;
}
function renderSource(review) {
const sourceUrl = review.source.assets?.find(a => a.format === "h30px")?.url;
const sourceImage = sourceUrl ? <img className="source-image" src={sourceUrl} /> : null;
return (
<a className="source" target="_blank" href={review.link}>{sourceImage ?? review.source.name}</a>
);
}
function roundToHalf(num) {
return Math.round(parseFloat(num) * 2) / 2;
}
You can find links to icons for the scores and sources in the JSON file. The list looks like this:
"assets": [
{
"name": "score",
"format": "h30px",
"url": "https://api.lucidity.tv/images/91/91_2_h30px.png"
},
{
"name": "score",
"format": "h100px",
"url": "https://api.lucidity.tv/images/91/91_2_h100px.png"
},
{
"name": "score",
"format": "h300px",
"url": "https://api.lucidity.tv/images/91/91_2_h300px.png"
},
{
"name": "score",
"format": "h900px",
"url": "https://api.lucidity.tv/images/91/91_2_h900px.png"
}
]
The "format" field indicates the dimensions, h100px means that the image height is 100px.