1window.ImdbWidget = (function() { 2 'use strict'; 3 4 function getCache(key) { 5 if (window.ApiRateLimiter) { 6 return window.ApiRateLimiter.getCache(key); 7 } 8 try { 9 var data = localStorage.getItem(key); 10 if (!data) return null; 11 var parsed = JSON.parse(data); 12 if (parsed && parsed.timestamp && (Date.now() - parsed.timestamp < (60 * 60 * 1000))) { 13 return parsed.value; 14 } 15 } catch(e) {} 16 return null; 17 } 18 19 function setCache(key, value) { 20 if (window.ApiRateLimiter) { 21 window.ApiRateLimiter.setCache(key, value); 22 return; 23 } 24 try { 25 localStorage.setItem(key, JSON.stringify({ 26 timestamp: Date.now(), 27 value: value 28 })); 29 } catch(e) {} 30 } 31 32 function clearCache(key) { 33 if (window.ApiRateLimiter) { 34 window.ApiRateLimiter.clearCache(key); 35 return; 36 } 37 try { 38 localStorage.removeItem(key); 39 } catch(e) {} 40 } 41 42 function renderLoading(element) { 43 if (!element) return; 44 var container = element.querySelector('.project-live-stats'); 45 if (!container) { 46 container = document.createElement('span'); 47 container.className = 'project-live-stats imdb-live-stats'; 48 element.appendChild(container); 49 } 50 container.innerHTML = '<span class="stat-chip stat-loading" title="Loading live stats..."><span class="stat-icon" style="-webkit-mask-image: url(\'/icons/hourglass.svg\'); mask-image: url(\'/icons/hourglass.svg\');"></span></span>'; 51 } 52 53 function renderError(element, onRetry) { 54 if (!element) return; 55 var container = element.querySelector('.project-live-stats'); 56 if (!container) { 57 container = document.createElement('span'); 58 container.className = 'project-live-stats imdb-live-stats'; 59 element.appendChild(container); 60 } 61 container.innerHTML = '<span class="stat-chip stat-error" title="Data Couldn\'t Load (Right-click to retry)"><span class="stat-icon" style="-webkit-mask-image: url(\'/icons/xmark.svg\'); mask-image: url(\'/icons/xmark.svg\');"></span></span>'; 62 63 var errChip = container.querySelector('.stat-error'); 64 if (errChip && typeof onRetry === 'function') { 65 errChip.addEventListener('contextmenu', function(e) { 66 e.preventDefault(); 67 e.stopPropagation(); 68 onRetry(); 69 }); 70 } 71 } 72 73 function makeIcon(file) { 74 return '<span class="stat-icon" style="-webkit-mask-image: url(\'/icons/' + file + '\'); mask-image: url(\'/icons/' + file + '\');"></span>'; 75 } 76 77 function renderInline(element, stats) { 78 if (!element || !stats) return; 79 var container = element.querySelector('.project-live-stats'); 80 if (!container) { 81 container = document.createElement('span'); 82 container.className = 'project-live-stats imdb-live-stats'; 83 element.appendChild(container); 84 } 85 86 var html = ''; 87 if (stats.rating) { 88 var rText = stats.rating.replace(/IMDb/i, '').trim(); 89 if (stats.rating_count) { 90 rText += ' (' + stats.rating_count + ')'; 91 } 92 html += '<span class="stat-chip stat-rating" title="IMDb Rating">' + makeIcon('star.svg') + ' ' + rText + '</span>'; 93 } else if (stats.title) { 94 html += '<span class="stat-chip stat-title" title="IMDb">' + makeIcon('imdb.svg') + ' ' + stats.title + '</span>'; 95 } 96 container.innerHTML = html; 97 } 98 99 function renderCard(element, imdbId, rating, creator, year, category) { 100 if (!element) return; 101 var container = element.querySelector('.imdb-widget-container'); 102 if (!container) { 103 container = document.createElement('div'); 104 container.className = 'imdb-widget-container'; 105 element.appendChild(container); 106 } 107 108 var imdbUrl = imdbId ? ('https://www.imdb.com/title/' + imdbId + '/') : ''; 109 var html = '<div class="imdb-widget-bar"><div class="imdb-widget-left">'; 110 111 if (rating) { 112 var scoreStr = rating.replace(/IMDb/i, '').trim(); 113 html += '<span class="imdb-stat-chip stat-imdb-score" title="IMDb Rating">' + makeIcon('star.svg') + ' ' + scoreStr + '</span>'; 114 } 115 116 if (category) { 117 html += '<span class="imdb-stat-chip stat-imdb-cat" title="Category">' + makeIcon('film.svg') + ' ' + category.toUpperCase() + '</span>'; 118 } 119 120 if (creator) { 121 html += '<span class="imdb-stat-chip stat-imdb-creator" title="Director / Creator">' + makeIcon('clapperboard.svg') + ' ' + creator + '</span>'; 122 } 123 124 html += '</div>'; 125 126 if (imdbUrl) { 127 html += '<div class="imdb-widget-right">'; 128 html += '<a href="' + imdbUrl + '" target="_blank" class="accolade-link-btn imdb-record-btn" title="View Title Record on IMDb">' + makeIcon('imdb.svg') + '<span>OPEN IMDb RECORD</span></a>'; 129 html += '</div>'; 130 } 131 132 html += '</div>'; 133 container.innerHTML = html; 134 } 135 136 function fetchImdb(imdbId, onSuccess, onError) { 137 var baseUrl = (window.ApiRateLimiter && window.ApiRateLimiter.getApiBaseUrl) ? window.ApiRateLimiter.getApiBaseUrl() : ''; 138 var proxyUrl = baseUrl + '/api/proxy/imdb?id=' + imdbId; 139 var taskFn = function() { 140 return fetch(proxyUrl) 141 .then(function(res) { 142 if (!res.ok) throw new Error('API Proxy returned ' + res.status); 143 return res.json(); 144 }); 145 }; 146 147 if (window.ApiRateLimiter) { 148 window.ApiRateLimiter.enqueue(proxyUrl, taskFn, onSuccess, onError); 149 } else { 150 taskFn().then(onSuccess).catch(onError); 151 } 152 } 153 154 function initElement(el) { 155 var imdbId = el.getAttribute('data-imdb-id'); 156 var rating = el.getAttribute('data-imdb-rating'); 157 var creator = el.getAttribute('data-imdb-creator'); 158 var year = el.getAttribute('data-imdb-year'); 159 var category = el.getAttribute('data-imdb-category'); 160 161 var isCard = (rating || creator || category || el.classList.contains('fav-player-section') || el.querySelector('.imdb-widget-container')); 162 163 if (isCard) { 164 renderCard(el, imdbId, rating, creator, year, category); 165 } 166 167 if (!imdbId) return; 168 169 var cacheKey = 'solvin_imdb_stats_v1_' + imdbId; 170 var cached = getCache(cacheKey); 171 if (cached) { 172 if (!isCard) { 173 renderInline(el, cached); 174 } else if (cached.rating) { 175 var scoreEl = el.querySelector('.stat-imdb-score'); 176 if (scoreEl) { 177 var scoreStr = cached.rating.replace(/IMDb/i, '').trim(); 178 if (cached.rating_count) scoreStr += ' (' + cached.rating_count + ')'; 179 scoreEl.innerHTML = makeIcon('star.svg') + ' ' + scoreStr; 180 } 181 } 182 return; 183 } 184 185 if (!isCard) { 186 renderLoading(el); 187 } 188 189 var baseUrl = (window.ApiRateLimiter && window.ApiRateLimiter.getApiBaseUrl) ? window.ApiRateLimiter.getApiBaseUrl() : ''; 190 var proxyUrl = baseUrl + '/api/proxy/imdb?id=' + imdbId; 191 192 function handleRetry() { 193 clearCache(cacheKey); 194 if (window.ApiRateLimiter) { 195 window.ApiRateLimiter.resetDomain(proxyUrl); 196 } 197 198 if (!isCard) renderLoading(el); 199 fetchImdb(imdbId, function(stats) { 200 setCache(cacheKey, stats); 201 if (!isCard) { 202 renderInline(el, stats); 203 } else if (stats.rating) { 204 var scoreEl = el.querySelector('.stat-imdb-score'); 205 if (scoreEl) { 206 var scoreStr = stats.rating.replace(/IMDb/i, '').trim(); 207 if (stats.rating_count) scoreStr += ' (' + stats.rating_count + ')'; 208 scoreEl.innerHTML = makeIcon('star.svg') + ' ' + scoreStr; 209 } 210 } 211 }, function(err) { 212 if (!isCard) renderError(el, handleRetry); 213 }); 214 } 215 216 fetchImdb(imdbId, function(stats) { 217 setCache(cacheKey, stats); 218 if (!isCard) { 219 renderInline(el, stats); 220 } else if (stats.rating) { 221 var scoreEl = el.querySelector('.stat-imdb-score'); 222 if (scoreEl) { 223 var scoreStr = stats.rating.replace(/IMDb/i, '').trim(); 224 if (stats.rating_count) scoreStr += ' (' + stats.rating_count + ')'; 225 scoreEl.innerHTML = makeIcon('star.svg') + ' ' + scoreStr; 226 } 227 } 228 }, function(err) { 229 if (!isCard) renderError(el, handleRetry); 230 }); 231 } 232
233 function init() { 234 var elements = document.querySelectorAll('[data-imdb-id], [data-imdb-rating]'); 235 elements.forEach(function(el) { 236 if (el.classList.contains('fav-collection-item-chip') || (el.closest && el.closest('.fav-collection-item-chip'))) return; 237 initElement(el); 238 }); 239 } 240 241 return { init: init, initElement: initElement, render: renderCard }; 242})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.