Edna.cz Future Schedule with Enhanced Countdown

Adds upcoming episodes to the main page summary

  1. // ==UserScript==
  2. // @name Edna.cz Future Schedule with Enhanced Countdown
  3. // @name:cs Edna.cz Budoucí Epizody s Odpočtem
  4. // @namespace https://gf.qytechs.cn/en/scripts/530726-edna-cz-future-schedule-with-enhanced-countdown
  5. // @version 0.93
  6. // @description Adds upcoming episodes to the main page summary
  7. // @description:cs Přidá budoucí epizody do přehledu na hlavní stránce
  8. // @author Setcher
  9. // @match https://www.edna.cz/
  10. // @match https://www.edna.cz/?*
  11. // @match https://www.edna.cz/#
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_getResourceText
  16. // @grant GM_addStyle
  17. // @require https://code.jquery.com/jquery-3.6.0.min.js
  18. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.2/jquery.modal.min.js
  19. // @resource modalCSS https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.2/jquery.modal.min.css
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. // Add modal CSS
  26. GM_addStyle(GM_getResourceText("modalCSS"));
  27. GM_addStyle(`
  28. .future-episode {
  29. opacity: 0.9;
  30. }
  31. .countdown-cell {
  32. text-align: right !important;
  33. white-space: nowrap;
  34. min-width: 120px;
  35. }
  36. .seen-episode {
  37. display: none;
  38. }
  39. #edna-settings-btn {
  40. margin-left: 0.5rem;
  41. }
  42. /* Dark mode for modal */
  43. #edna-settings-modal {
  44. color: #f8f9fa;
  45. }
  46. #edna-settings-modal > div {
  47. background-color: #343a40 !important;
  48. border: 1px solid #495057;
  49. }
  50. #edna-settings-modal h2 {
  51. color: #f8f9fa !important;
  52. }
  53. #edna-settings-modal label {
  54. color: #dee2e6 !important;
  55. }
  56. #edna-settings-modal input[type="number"],
  57. #edna-settings-modal input[type="checkbox"] {
  58. background-color: #495057;
  59. border-color: #6c757d;
  60. color: #f8f9fa;
  61. }
  62. #edna-settings-modal .btn-secondary {
  63. background-color: #6c757d;
  64. border-color: #6c757d;
  65. }
  66. #edna-settings-modal .btn-primary {
  67. background-color: #0d6efd;
  68. border-color: #0d6efd;
  69. }
  70. /* Combine rating and seen columns for countdown */
  71. .combined-countdown-column {
  72. display: flex;
  73. justify-content: space-between;
  74. align-items: center;
  75. min-width: 120px;
  76. }
  77. .combined-countdown-column .stars {
  78. margin-right: 8px;
  79. }
  80. `);
  81.  
  82. // Settings
  83. const defaultSettings = {
  84. maxDaysInFuture: 7,
  85. maxFutureItems: 7,
  86. countdownFormat: 'DD:HH:MM:SS',
  87. hideSeenEpisodes: true
  88. };
  89.  
  90. let settings = {
  91. ...defaultSettings,
  92. ...{
  93. maxDaysInFuture: GM_getValue('maxDaysInFuture', defaultSettings.maxDaysInFuture),
  94. maxFutureItems: GM_getValue('maxFutureItems', defaultSettings.maxFutureItems),
  95. countdownFormat: GM_getValue('countdownFormat', defaultSettings.countdownFormat),
  96. hideSeenEpisodes: GM_getValue('hideSeenEpisodes', defaultSettings.hideSeenEpisodes)
  97. }
  98. };
  99.  
  100. // Create settings modal
  101. function createSettingsModal() {
  102. const modalHTML = `
  103. <div id="edna-settings-modal" class="modal" style="display: none;">
  104. <div style="padding: 20px; border-radius: 5px; max-width: 500px; margin: 0 auto;">
  105. <h2 style="margin-top: 0;">Nastavení budoucích epizod</h2>
  106.  
  107. <div style="margin-bottom: 15px;">
  108. <label style="display: block; margin-bottom: 5px; font-weight: bold;">Maximální dní dopředu:</label>
  109. <input type="number" id="edna-max-days" value="${settings.maxDaysInFuture}" min="1" max="60"
  110. style="width: 100%; padding: 8px; border-radius: 4px;">
  111. </div>
  112.  
  113. <div style="margin-bottom: 15px;">
  114. <label style="display: block; margin-bottom: 5px; font-weight: bold;">Maximálně epizod:</label>
  115. <input type="number" id="edna-max-items" value="${settings.maxFutureItems}" min="1" max="100"
  116. style="width: 100%; padding: 8px; border-radius: 4px;">
  117. </div>
  118.  
  119. <div style="margin-bottom: 15px;">
  120. <label style="display: block; margin-bottom: 8px; font-weight: bold;">Formát odpočtu:</label>
  121. <div style="display: flex; flex-direction: column; gap: 8px;">
  122. <label style="display: flex; align-items: center; gap: 8px;">
  123. <input type="radio" name="countdownFormat" value="DD:HH" ${settings.countdownFormat === 'DD:HH' ? 'checked' : ''}>
  124. <span>DD:HH (Dny a hodiny)</span>
  125. </label>
  126. <label style="display: flex; align-items: center; gap: 8px;">
  127. <input type="radio" name="countdownFormat" value="DD:HH:MM" ${settings.countdownFormat === 'DD:HH:MM' ? 'checked' : ''}>
  128. <span>DD:HH:MM (Dny, hodiny, minuty)</span>
  129. </label>
  130. <label style="display: flex; align-items: center; gap: 8px;">
  131. <input type="radio" name="countdownFormat" value="DD:HH:MM:SS" ${settings.countdownFormat === 'DD:HH:MM:SS' ? 'checked' : ''}>
  132. <span>DD:HH:MM:SS (Přesný odpočet)</span>
  133. </label>
  134. </div>
  135. </div>
  136.  
  137. <div style="margin-bottom: 20px;">
  138. <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
  139. <input type="checkbox" id="edna-hide-seen" ${settings.hideSeenEpisodes ? 'checked' : ''}>
  140. <span style="font-weight: bold;">Skrýt již zhlédnuté epizody</span>
  141. </label>
  142. </div>
  143.  
  144. <div style="display: flex; justify-content: flex-end; gap: 10px;">
  145. <button id="edna-cancel-settings" class="btn btn-secondary">Zrušit</button>
  146. <button id="edna-save-settings" class="btn btn-primary">Uložit</button>
  147. </div>
  148. </div>
  149. </div>
  150. `;
  151.  
  152. $('body').append(modalHTML);
  153.  
  154. // Add settings button to controls
  155. const settingsBtn = $(`
  156. <a href="#" class="nav-link" id="edna-settings-btn">
  157. <i class="bi bi-gear-fill"></i> Nastavení
  158. </a>
  159. `);
  160. $('.custom-tabs').append(settingsBtn);
  161.  
  162. // Modal handlers
  163. $('#edna-settings-btn').click(function(e) {
  164. e.preventDefault();
  165. $('#edna-settings-modal').modal({
  166. escapeClose: true,
  167. clickClose: false,
  168. showClose: false
  169. });
  170. });
  171.  
  172. $('#edna-save-settings').click(function() {
  173. settings.maxDaysInFuture = parseInt($('#edna-max-days').val());
  174. settings.maxFutureItems = parseInt($('#edna-max-items').val());
  175. settings.countdownFormat = $('input[name="countdownFormat"]:checked').val();
  176. settings.hideSeenEpisodes = $('#edna-hide-seen').is(':checked');
  177.  
  178. GM_setValue('maxDaysInFuture', settings.maxDaysInFuture);
  179. GM_setValue('maxFutureItems', settings.maxFutureItems);
  180. GM_setValue('countdownFormat', settings.countdownFormat);
  181. GM_setValue('hideSeenEpisodes', settings.hideSeenEpisodes);
  182.  
  183. $.modal.close();
  184. location.reload();
  185. });
  186.  
  187. $('#edna-cancel-settings').click(function() {
  188. $.modal.close();
  189. });
  190. }
  191.  
  192. // Main function to load and process future schedule
  193. function processFutureSchedule() {
  194. // Wait for the main schedule table to load
  195. const checkTable = setInterval(function() {
  196. const $scheduleTable = $('table.table-responsive');
  197. if ($scheduleTable.length && $('#snippet--episodes').length) {
  198. clearInterval(checkTable);
  199. loadFutureSchedule();
  200. }
  201. }, 200);
  202. }
  203.  
  204. // Fix episode images (replace placeholder with data-src)
  205. function fixImages($element) {
  206. $element.find('img[data-src]').each(function() {
  207. const $img = $(this);
  208. if ($img.attr('src') !== $img.attr('data-src')) {
  209. $img.attr('src', $img.attr('data-src'));
  210. }
  211. });
  212. }
  213.  
  214. // Convert timestamp to countdown string
  215. function getCountdownString(timestamp) {
  216. const now = new Date().getTime();
  217. const diff = timestamp - now;
  218.  
  219. if (diff < 0) return "Právě vysíláno";
  220.  
  221. const days = Math.floor(diff / (1000 * 60 * 60 * 24));
  222. const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  223. const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
  224. const seconds = Math.floor((diff % (1000 * 60)) / 1000);
  225.  
  226. switch(settings.countdownFormat) {
  227. case 'DD:HH':
  228. return `${days}d ${hours}h`;
  229. case 'DD:HH:MM':
  230. return `${days}d ${hours}h ${minutes}m`;
  231. case 'DD:HH:MM:SS':
  232. default:
  233. return `${days}d ${hours}h ${minutes}m ${seconds}s`;
  234. }
  235. }
  236.  
  237. // Update all countdowns
  238. function updateCountdowns() {
  239. $('.countdown-text').each(function() {
  240. const $cell = $(this);
  241. const timestamp = parseInt($cell.attr('data-countdown'));
  242. $cell.text(getCountdownString(timestamp));
  243. });
  244. }
  245.  
  246. // Hide seen episodes if setting is enabled
  247. function handleSeenEpisodes() {
  248. if (settings.hideSeenEpisodes) {
  249. $('a[title^="Označit jako viděno"], a[title^="Označit jako neviděno"]').each(function() {
  250. const $link = $(this);
  251. const isWatched = $link.attr('title').startsWith("Označit jako neviděno");
  252. if (isWatched) {
  253. $link.closest('tr').addClass('seen-episode');
  254. }
  255. });
  256. } else {
  257. $('.seen-episode').removeClass('seen-episode');
  258. }
  259. }
  260.  
  261. // Load and merge future episodes
  262. function loadFutureSchedule() {
  263. GM_xmlhttpRequest({
  264. method: "GET",
  265. url: "https://www.edna.cz/vysilani/nadchazejici/",
  266. onload: function(response) {
  267. const $futureHtml = $($.parseHTML(response.responseText));
  268. const $scheduleTable = $('table.table-responsive');
  269. const $episodesContainer = $('#snippet--episodes');
  270.  
  271. if (!$scheduleTable.length || !$episodesContainer.length) {
  272. console.error('Required elements not found');
  273. return;
  274. }
  275.  
  276. const now = new Date();
  277. const maxDate = new Date(now.getTime() + (settings.maxDaysInFuture * 24 * 60 * 60 * 1000));
  278. let addedItems = 0;
  279.  
  280. $futureHtml.find('#snippet--episodes table.episodes tr').each(function() {
  281. if (addedItems >= settings.maxFutureItems) return false;
  282.  
  283. const $row = $(this);
  284. const $timeCell = $row.find('td[data-countdown]');
  285.  
  286. if ($timeCell.length) {
  287. const timestamp = parseInt($timeCell.attr('data-countdown'));
  288. const itemDate = new Date(timestamp);
  289.  
  290. if (itemDate > now && itemDate <= maxDate) {
  291. // Extract show information
  292. const $showLink = $row.find('td:nth-child(3) a');
  293. const showLink = $showLink.attr('href');
  294. const showTitle = $showLink.text();
  295. const episodeCode = $row.find('td:nth-child(4)').text();
  296. const imgSrc = $row.find('img').attr('data-src');
  297. const dateText = $timeCell.text().trim().split(' | ')[0];
  298. const shortDate = dateText.split('.').slice(0, 2).join('.');
  299.  
  300. // Create new row matching the current layout
  301. const $newRow = $(`
  302. <tr class="future-episode">
  303. <td class="text-secondary text-nowrap ps-3 text-start" title="${dateText}">${shortDate}</td>
  304. <td onclick="location.href='${showLink}'" class="cursor-pointer" href="${showLink}">
  305. <div class="d-flex align-items-center">
  306. <div class="latest-series-image position-relative rounded-circle overflow-hidden">
  307. <img src="${imgSrc}" alt="${showTitle}" class="position-absolute top-0 start-0 w-100">
  308. </div>
  309. <div class="ms-3">
  310. <a class="text-reset text-decoration-none" href="${showLink}">
  311. <p class="m-0 my-auto text-start text-white">${episodeCode} ${showTitle}</p>
  312. </a>
  313. </div>
  314. </div>
  315. </td>
  316. <td class="pe-3 combined-countdown-column">
  317. <span class="countdown-text" data-countdown="${timestamp}" title="${$timeCell.text().trim()}"></span>
  318. </td>
  319. </tr>
  320. `);
  321.  
  322. $episodesContainer.prepend($newRow);
  323. fixImages($newRow);
  324. addedItems++;
  325. }
  326. }
  327. });
  328.  
  329. if (addedItems > 0) {
  330. handleSeenEpisodes();
  331. updateCountdowns();
  332. const updateInterval = settings.countdownFormat === 'DD:HH:MM:SS' ? 1000 : 60000;
  333. setInterval(updateCountdowns, updateInterval);
  334. }
  335. },
  336. onerror: function(error) {
  337. console.error('Error loading future schedule:', error);
  338. }
  339. });
  340. }
  341.  
  342. // Initialize
  343. $(document).ready(function() {
  344. createSettingsModal();
  345. // Wait a bit longer to ensure all elements are loaded
  346. setTimeout(processFutureSchedule, 1500);
  347. });
  348. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址