Full Date format for Youtube

Show full upload dates in DD/MM/YYYY HH:MMam/pm format

当前为 2024-11-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Full Date format for Youtube
  3. // @version 0.4
  4. // @description Show full upload dates in DD/MM/YYYY HH:MMam/pm format
  5. // @author Ignacio Albiol
  6. // @namespace https://greasyfork.org/en/users/1304094
  7. // @match https://www.youtube.com/*
  8. // @iconURL https://seekvectors.com/files/download/youtube-icon-yellow-01.jpg
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. function getUploadDate() {
  17. let el = document.body.querySelector('player-microformat-renderer script');
  18. if (el) {
  19. let parts = el.textContent.split('"startDate":"',2);
  20. if (parts.length == 2) {
  21. return parts[1].split('"',1)[0];
  22. }
  23. parts = el.textContent.split('"uploadDate":"',2);
  24. if (parts.length == 2) {
  25. return parts[1].split('"',1)[0];
  26. }
  27. }
  28. return null;
  29. }
  30.  
  31. function getIsLiveBroadcast() {
  32. let el = document.body.querySelector('player-microformat-renderer script');
  33. if (!el) {
  34. return null;
  35. }
  36.  
  37. let parts = el.textContent.split('"isLiveBroadcast":',2);
  38. if (parts.length != 2) {
  39. return false;
  40. }
  41.  
  42. let isLiveBroadcast = !!parts[1].split(',',1)[0];
  43. if (!isLiveBroadcast) {
  44. return false;
  45. }
  46.  
  47. parts = el.textContent.split('"endDate":"',2);
  48. if (parts.length == 2) {
  49. return false;
  50. }
  51.  
  52. return true;
  53. }
  54.  
  55. function urlToVideoId(url) {
  56. let parts = url.split('/shorts/',2);
  57. if (parts.length === 2) {
  58. url = parts[1];
  59. } else {
  60. url = parts[0];
  61. }
  62.  
  63. parts = url.split('v=',2);
  64. if (parts.length === 2) {
  65. url = parts[1];
  66. } else {
  67. url = parts[0];
  68. }
  69.  
  70. return url.split('&',1)[0];
  71. }
  72.  
  73. function getRemoteUploadDate(videoId, callback) {
  74. let body = {"context":{"client":{"clientName":"WEB","clientVersion":"2.20240416.01.00"}},"videoId":videoId};
  75.  
  76. fetch('https://www.youtube.com/youtubei/v1/player?prettyPrint=false', {
  77. method: 'POST',
  78. headers: {
  79. 'Content-Type': 'application/json'
  80. },
  81. body: JSON.stringify(body)
  82. })
  83. .then(response => {
  84. if (!response.ok) {
  85. throw new Error('Network response was not ok');
  86. }
  87. return response.json();
  88. })
  89. .then(data => {
  90. let object = data.microformat.playerMicroformatRenderer;
  91.  
  92. if (object.liveBroadcastDetails?.isLiveNow) {
  93. callback(object.liveBroadcastDetails.startTimestamp);
  94. return;
  95. } else if (object.publishDate) {
  96. callback(object.publishDate);
  97. return;
  98. }
  99.  
  100. callback(object.uploadDate);
  101. })
  102. .catch(error => {
  103. console.error('There was a problem with the fetch operation:', error);
  104. });
  105. }
  106.  
  107. function isoToDate(iso) {
  108. let date = new Date(iso);
  109.  
  110. // Format date components
  111. let day = ("0" + date.getDate()).slice(-2);
  112. let month = ("0" + (date.getMonth() + 1)).slice(-2);
  113. let year = date.getFullYear();
  114.  
  115. // Format time components
  116. let hours = date.getHours();
  117. let minutes = ("0" + date.getMinutes()).slice(-2);
  118. let ampm = hours >= 12 ? 'pm' : 'am';
  119.  
  120. // Convert hours to 12-hour format
  121. hours = hours % 12;
  122. hours = hours ? hours : 12; // Convert 0 to 12
  123.  
  124. // Combine all components
  125. return `${day}/${month}/${year} ${hours}:${minutes}${ampm}`;
  126. }
  127.  
  128. function startTimers() {
  129. /* video page description */
  130. setInterval(() => {
  131. let uploadDate = getUploadDate();
  132. if (!uploadDate) {
  133. return;
  134. }
  135.  
  136. uploadDate = isoToDate(uploadDate);
  137. let isLiveBroadcast = getIsLiveBroadcast();
  138.  
  139. if (isLiveBroadcast) {
  140. document.body.classList.add('ytud-description-live');
  141. } else {
  142. document.body.classList.remove('ytud-description-live');
  143. }
  144.  
  145. let el = document.querySelector('#info-container > #info > b');
  146. if (!el) {
  147. let span = document.querySelector('#info-container > #info > span:nth-child(1)');
  148. if (!span) {
  149. return;
  150. }
  151. el = document.createElement('b');
  152. el.textContent = uploadDate;
  153. span.insertAdjacentElement('afterend', el);
  154. } else {
  155. if (el.parentNode.children[1] !== el) {
  156. let container = el.parentNode;
  157. el = container.removeChild(el);
  158. container.children[0].insertAdjacentElement('afterend', el);
  159. }
  160. if (el.firstChild.nodeValue === uploadDate) {
  161. return;
  162. }
  163. el.firstChild.nodeValue = uploadDate;
  164. }
  165. }, 1000);
  166.  
  167. /* video page sidebar list */
  168. setInterval(() => {
  169. let vids = document.querySelectorAll('#items.ytd-watch-next-secondary-results-renderer ytd-compact-video-renderer');
  170. if (vids.length === 0) {
  171. return;
  172. }
  173.  
  174. vids.forEach((el) => {
  175. let holders = el.querySelectorAll('#metadata-line > span');
  176.  
  177. let holder;
  178. if (holders.length === 1) {
  179. let copy = document.createElement('span');
  180. copy.className = 'inline-metadata-item style-scope ytd-video-meta-block';
  181. let textNode = document.createTextNode('');
  182. copy.appendChild(textNode);
  183. holders[0].insertAdjacentElement('afterend', copy);
  184. holder = copy;
  185. } else {
  186. holder = holders[1];
  187. }
  188.  
  189. let dateText = holder.firstChild.nodeValue;
  190. let text = el.getAttribute('data-text');
  191.  
  192. if (text !== null && text == dateText) {
  193. return;
  194. }
  195.  
  196. el.setAttribute('data-text', dateText);
  197. let link = el.querySelector('a#thumbnail').getAttribute('href');
  198. let videoId = urlToVideoId(link);
  199. getRemoteUploadDate(videoId, (uploadDate) => {
  200. uploadDate = isoToDate(uploadDate);
  201. holder.firstChild.nodeValue = uploadDate;
  202. el.setAttribute('data-text', uploadDate);
  203. });
  204. })
  205. }, 1000);
  206.  
  207. /* homepage list - videos */
  208. setInterval(() => {
  209. let vids = document.querySelectorAll('#content > ytd-rich-grid-media > #dismissible > #details > #meta > ytd-video-meta-block > #metadata');
  210. if (vids.length === 0) {
  211. return;
  212. }
  213.  
  214. vids.forEach((el) => {
  215. let holders = el.querySelectorAll('#metadata-line > span');
  216. if (holders.length === 0) {
  217. return;
  218. }
  219.  
  220. let holder;
  221. if (holders.length === 1) {
  222. let copy = document.createElement('span');
  223. copy.className = 'inline-metadata-item style-scope ytd-video-meta-block';
  224. let textNode = document.createTextNode('');
  225. copy.appendChild(textNode);
  226. holders[0].insertAdjacentElement('afterend', copy);
  227. holder = copy;
  228. } else {
  229. holder = holders[1];
  230. }
  231.  
  232. let dateText = holder.firstChild.nodeValue;
  233. let text = el.getAttribute('data-text');
  234.  
  235. if (text !== null && text === dateText) {
  236. return;
  237. }
  238.  
  239. el.setAttribute('data-text', dateText);
  240. let link = el.closest('#meta').querySelector('h3 > a#video-title-link').getAttribute('href');
  241. let videoId = urlToVideoId(link);
  242. getRemoteUploadDate(videoId, (uploadDate) => {
  243. uploadDate = isoToDate(uploadDate);
  244. holder.firstChild.nodeValue = uploadDate;
  245. el.setAttribute('data-text', uploadDate);
  246. });
  247. })
  248. }, 1000);
  249.  
  250. /* homepage list - shorts */
  251. setInterval(() => {
  252. let vids = document.querySelectorAll('#content > ytd-rich-grid-slim-media > #dismissible > #details > ytd-video-meta-block > #metadata');
  253. if (vids.length === 0) {
  254. return;
  255. }
  256.  
  257. vids.forEach((el) => {
  258. let holders = el.querySelectorAll('#metadata-line > span');
  259. if (holders.length === 0) {
  260. return;
  261. }
  262. let holder;
  263. if (holders.length === 1) {
  264. let copy = document.createElement('span');
  265. copy.className = 'inline-metadata-item style-scope ytd-video-meta-block';
  266. let textNode = document.createTextNode('');
  267. copy.appendChild(textNode);
  268. holders[0].insertAdjacentElement('afterend', copy);
  269. holder = copy;
  270. } else {
  271. holder = holders[1];
  272. }
  273.  
  274. let dateText = holder.firstChild.nodeValue;
  275. let text = el.getAttribute('data-text');
  276.  
  277. if (text !== null && text === dateText) {
  278. return;
  279. }
  280.  
  281. el.setAttribute('data-text', dateText);
  282. let link = el.closest('#details').querySelector('h3 > a').getAttribute('href');
  283. let videoId = urlToVideoId(link);
  284. getRemoteUploadDate(videoId, (uploadDate) => {
  285. uploadDate = isoToDate(uploadDate);
  286. holder.firstChild.nodeValue = uploadDate;
  287. el.setAttribute('data-text', uploadDate);
  288. });
  289. })
  290. }, 1000);
  291.  
  292. /* search list - videos */
  293. setInterval(() => {
  294. let vids = document.querySelectorAll('#contents ytd-video-renderer #metadata');
  295. if (vids.length === 0) {
  296. return;
  297. }
  298.  
  299. vids.forEach((el) => {
  300. let holders = el.querySelectorAll('#metadata-line > span');
  301. if (holders.length === 0) {
  302. return;
  303. }
  304. let holder;
  305. if (holders.length === 1) {
  306. let copy = document.createElement('span');
  307. copy.className = 'inline-metadata-item style-scope ytd-video-meta-block';
  308. let textNode = document.createTextNode('');
  309. copy.appendChild(textNode);
  310. holders[0].insertAdjacentElement('afterend', copy);
  311. holder = copy;
  312. } else {
  313. holder = holders[1];
  314. }
  315.  
  316. let dateText = holder.firstChild.nodeValue;
  317. let text = el.getAttribute('data-text');
  318.  
  319. if (text !== null && text === dateText) {
  320. return;
  321. }
  322.  
  323. el.setAttribute('data-text', dateText);
  324. let link = el.closest('#dismissible').querySelector('a#thumbnail').getAttribute('href');
  325. let videoId = urlToVideoId(link);
  326. getRemoteUploadDate(videoId, (uploadDate) => {
  327. uploadDate = isoToDate(uploadDate);
  328. holder.firstChild.nodeValue = uploadDate;
  329. el.setAttribute('data-text', uploadDate);
  330. });
  331. })
  332. }, 1000);
  333.  
  334. /* search list - shorts */
  335. setInterval(() => {
  336. let vids = document.querySelectorAll('#scroll-container > #items > ytd-reel-item-renderer > #dismissible > #details > ytd-video-meta-block > #metadata');
  337. if (vids.length === 0) {
  338. return;
  339. }
  340.  
  341. vids.forEach((el) => {
  342. let holders = el.querySelectorAll('#metadata-line > span');
  343. if (holders.length === 0) {
  344. return;
  345. }
  346. let holder;
  347. if (holders.length === 1) {
  348. let copy = document.createElement('span');
  349. copy.className = 'inline-metadata-item style-scope ytd-video-meta-block';
  350. let textNode = document.createTextNode('');
  351. copy.appendChild(textNode);
  352. holders[0].insertAdjacentElement('afterend', copy);
  353. holder = copy;
  354. } else {
  355. holder = holders[1];
  356. }
  357.  
  358. let dateText = holder.firstChild.nodeValue;
  359. let text = el.getAttribute('data-text');
  360.  
  361. if (text !== null && text === dateText) {
  362. return;
  363. }
  364.  
  365. el.setAttribute('data-text', dateText);
  366. let link = el.closest('#details').querySelector('h3 > a').getAttribute('href');
  367. let videoId = urlToVideoId(link);
  368. getRemoteUploadDate(videoId, (uploadDate) => {
  369. uploadDate = isoToDate(uploadDate);
  370. holder.firstChild.nodeValue = uploadDate;
  371. el.setAttribute('data-text', uploadDate);
  372. });
  373. })
  374. }, 1000);
  375.  
  376. /* search list - topic in sidebar */
  377. setInterval(() => {
  378. let vids = document.querySelectorAll('#contents > ytd-universal-watch-card-renderer > #sections > ytd-watch-card-section-sequence-renderer > #lists > ytd-vertical-watch-card-list-renderer > #items > ytd-watch-card-compact-video-renderer');
  379. if (vids.length === 0) {
  380. return;
  381. }
  382.  
  383. vids.forEach((el) => {
  384. let holders = el.querySelectorAll('div.text-wrapper > yt-formatted-string.subtitle');
  385. if (holders.length === 0) {
  386. return;
  387. }
  388.  
  389. let holder = holders[0];
  390. let separator = ' • ';
  391. let parts = holder.firstChild.nodeValue.split(separator, 2);
  392. if (parts.length < 2) {
  393. return;
  394. }
  395. let prefix = parts[0] + separator;
  396. let dateText = parts[1];
  397. let text = el.getAttribute('data-text');
  398.  
  399. if (text !== null && text === dateText) {
  400. return;
  401. }
  402.  
  403. el.setAttribute('data-text', dateText);
  404. let link = el.querySelector('a#thumbnail').getAttribute('href');
  405. let videoId = urlToVideoId(link);
  406. getRemoteUploadDate(videoId, (uploadDate) => {
  407. uploadDate = isoToDate(uploadDate);
  408. holder.firstChild.nodeValue = prefix + uploadDate;
  409. el.setAttribute('data-text', uploadDate);
  410. });
  411. })
  412. }, 1000);
  413.  
  414. /* channel page - home (featured video) */
  415. setInterval(() => {
  416. let vids = document.querySelectorAll('#contents > ytd-channel-video-player-renderer > #content > #metadata-container > ytd-video-meta-block > #metadata');
  417. if (vids.length === 0) {
  418. return;
  419. }
  420.  
  421. vids.forEach((el) => {
  422. let holders = el.querySelectorAll('#metadata-line > span');
  423. if (holders.length === 0) {
  424. return;
  425. }
  426.  
  427. let holder = holders[1];
  428. let dateText = holder.firstChild.nodeValue;
  429. let text = el.getAttribute('data-text');
  430.  
  431. if (text !== null && text === dateText) {
  432. return;
  433. }
  434.  
  435. el.setAttribute('data-text', dateText);
  436. let link = el.closest('#metadata-container').querySelector('yt-formatted-string > a').getAttribute('href');
  437. let videoId = urlToVideoId(link);
  438. getRemoteUploadDate(videoId, (uploadDate) => {
  439. uploadDate = isoToDate(uploadDate);
  440. holder.firstChild.nodeValue = uploadDate;
  441. el.setAttribute('data-text', uploadDate);
  442. });
  443. })
  444. }, 1000);
  445.  
  446. /* channel page - home (for you videos) */
  447. setInterval(() => {
  448. let vids = document.querySelectorAll('#dismissible > #details > #text-metadata > #meta > #metadata-container > #metadata');
  449. if (vids.length === 0) {
  450. return;
  451. }
  452.  
  453. vids.forEach((el) => {
  454. let holders = el.querySelectorAll('#metadata-line > span');
  455. if (holders.length === 0) {
  456. return;
  457. }
  458.  
  459. let holder;
  460. if (holders.length === 1) {
  461. let copy = document.createElement('span');
  462. copy.className = 'style-scope ytd-grid-video-renderer';
  463. let textNode = document.createTextNode('');
  464. copy.appendChild(textNode);
  465. holders[0].insertAdjacentElement('afterend', copy);
  466. holder = copy;
  467. } else {
  468. holder = holders[1];
  469. }
  470.  
  471. let dateText = holder.firstChild.nodeValue;
  472. let text = el.getAttribute('data-text');
  473.  
  474. if (text !== null && text === dateText) {
  475. return;
  476. }
  477.  
  478. el.setAttribute('data-text', dateText);
  479. let link = el.closest('#meta').querySelector('h3 > a#video-title').getAttribute('href');
  480. let videoId = urlToVideoId(link);
  481. getRemoteUploadDate(videoId, (uploadDate) => {
  482. uploadDate = isoToDate(uploadDate);
  483. holder.firstChild.nodeValue = uploadDate;
  484. el.setAttribute('data-text', uploadDate);
  485. });
  486. })
  487. }, 1000);
  488.  
  489. /* video playlist */
  490. setInterval(() => {
  491. let vids = document.querySelectorAll('#content > #container > #meta > ytd-video-meta-block > #metadata > #byline-container');
  492. if (vids.length === 0) {
  493. return;
  494. }
  495.  
  496. vids.forEach((el) => {
  497. let holders = el.querySelectorAll('#video-info > span');
  498. if (holders.length <= 1) {
  499. return;
  500. }
  501.  
  502. let holder;
  503. let prefix = '';
  504. if (holders.length === 2) {
  505. let copy = document.createElement('span');
  506. copy.className = 'style-scope yt-formatted-string';
  507. copy.setAttribute('dir', 'auto');
  508. let textNode = document.createTextNode('');
  509. copy.appendChild(textNode);
  510. holders[1].insertAdjacentElement('afterend', copy);
  511. holder = copy;
  512. prefix = ' • ';
  513. } else {
  514. holder = holders[2];
  515. }
  516.  
  517. let dateText = holder.firstChild.nodeValue;
  518. let text = el.getAttribute('data-text');
  519.  
  520. if (text !== null && text === dateText) {
  521. return;
  522. }
  523.  
  524. el.setAttribute('data-text', dateText);
  525. let link = el.closest('#meta').querySelector('h3 > a').getAttribute('href');
  526. let videoId = urlToVideoId(link);
  527. getRemoteUploadDate(videoId, (uploadDate) => {
  528. uploadDate = prefix + isoToDate(uploadDate);
  529. holder.firstChild.nodeValue = uploadDate;
  530. el.setAttribute('data-text', uploadDate);
  531. });
  532. })
  533. }, 1000);
  534. }
  535.  
  536. startTimers();
  537.  
  538. let styleTag = document.createElement('style');
  539. let cssCode = "#info > span:nth-child(3) {display:none !important;}"
  540. + "#info > span:nth-child(4) {display:none !important;}"
  541. + "#info > b {font-weight:500 !important;margin-left:6px !important;}"
  542. + "#date-text {display:none !important;}"
  543. + ".ytud-description-live #info > span:nth-child(1) {display:none !important;}"
  544. + ".ytud-description-live #info > b {margin-left:0 !important;margin-right:6px !important;}";
  545. styleTag.textContent = cssCode;
  546. document.head.appendChild(styleTag);
  547. })();