PageSourceSearch

https://sol.vin/js/widgets/imdb_widget.js

js sol.vin collected 2026-10-09 21:58:38 UTC 8,470 bytes, 242 lines download raw bytes

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.