Video time tracker (Firestore)

Save and restore video playback time using Firestore

  1. // ==UserScript==
  2. // @name Video time tracker (Firestore)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Save and restore video playback time using Firestore
  6. // @author Bui Quoc Dung
  7. // @match *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @run-at document-end
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. "use strict";
  14.  
  15. const FIRESTORE_URL = "PASTE YOUR FIRESTORE LINK"; // Firestore API endpoint URL
  16. const SAVE_INTERVAL = 30 * 1000; // seconds *1000 - Time between saves in milliseconds
  17. const MIN_TRACK_TIME = 20 * 60; // minutes * 60 - Minimum video duration to track (seconds)
  18. const REMOVE_TIME_INTERVAL = 2; // Days before data removal
  19. const REMOVE_TIME = "08:30"; // Daily cleanup time (24h format)
  20.  
  21. let video = null; // HTML video element reference
  22. let VIDEO_ID = ""; // Current video's unique identifier
  23. let lastSaveTime = 0; // Timestamp of last save operation
  24.  
  25. function getVideoID() {
  26. const url = new URL(window.location.href);
  27.  
  28. // Ưu tiên lấy ID từ tham số truy vấn dạng ?v= hoặc ?id=
  29. const queryID = url.searchParams.get("v") || url.searchParams.get("id");
  30. if (queryID) return queryID;
  31.  
  32. // Nếu không có tham số, lấy phần cuối của URL nếu có dạng số hoặc chữ cái
  33. const pathSegments = url.pathname.split('/');
  34. const lastSegment = pathSegments.pop() || pathSegments.pop(); // Tránh dấu '/' ở cuối
  35. if (/^[a-zA-Z0-9]+$/.test(lastSegment)) return lastSegment;
  36.  
  37. return null; // Không tìm thấy ID hợp lệ
  38. }
  39.  
  40.  
  41. function findVideo() { // Find video element and initialize tracking
  42. video = document.querySelector("video") || detectPlyrVideo();
  43. if (video) {
  44. if (video.duration < MIN_TRACK_TIME) return;
  45. initializeVideo();
  46. } else setTimeout(findVideo, 1000);
  47. }
  48.  
  49. function detectPlyrVideo() { // Detect Plyr player video element
  50. if (typeof Plyr !== "undefined" && Plyr.instances.length > 0) {
  51. return Plyr.instances[0].elements.container.querySelector("video");
  52. }
  53. return null;
  54. }
  55.  
  56. function loadSavedProgress() { // Load saved playback time from Firestore
  57. GM_xmlhttpRequest({
  58. method: "GET",
  59. url: `${FIRESTORE_URL}/${VIDEO_ID}`,
  60. onload: function (response) {
  61. try {
  62. const data = JSON.parse(response.responseText);
  63. if (data?.fields?.time?.integerValue) {
  64. video.currentTime = parseInt(data.fields.time.integerValue);
  65. }
  66. } catch (error) {}
  67. }
  68. });
  69. }
  70.  
  71. function savePlaybackProgress() { // Save current time to Firestore
  72. if (!video || video.paused || video.ended || video.duration < MIN_TRACK_TIME) return;
  73.  
  74. const currentTime = Math.floor(video.currentTime);
  75. const currentDate = new Date().toISOString().split('T')[0];
  76.  
  77. if (Date.now() - lastSaveTime >= SAVE_INTERVAL) {
  78. GM_xmlhttpRequest({
  79. method: "PATCH",
  80. url: `${FIRESTORE_URL}/${VIDEO_ID}`,
  81. headers: { "Content-Type": "application/json" },
  82. data: JSON.stringify({
  83. fields: {
  84. time: { integerValue: currentTime },
  85. date: { stringValue: currentDate }
  86. }
  87. }),
  88. onload: () => lastSaveTime = Date.now()
  89. });
  90. }
  91. }
  92.  
  93. function monitorUrlChanges() { // Watch for URL changes to detect new videos
  94. let previousUrl = location.href;
  95. setInterval(() => {
  96. if (location.href !== previousUrl) {
  97. previousUrl = location.href;
  98. VIDEO_ID = getVideoID();
  99. findVideo();
  100. }
  101. }, 1000);
  102. }
  103.  
  104. function initializeVideo() { // Set up video event listeners
  105. loadSavedProgress();
  106. video.addEventListener("timeupdate", savePlaybackProgress);
  107. video.addEventListener("seeked", savePlaybackProgress);
  108. }
  109.  
  110. function shouldDeleteData(videoDate) { // Check if data exceeds retention period
  111. const today = new Date();
  112. const savedDate = new Date(videoDate);
  113. const timeDifference = (today - savedDate) / (1000 * 60 * 60 * 24);
  114. return timeDifference > REMOVE_TIME_INTERVAL;
  115. }
  116.  
  117. function removeOldData() { // Delete expired documents from Firestore
  118. GM_xmlhttpRequest({
  119. method: "GET",
  120. url: `${FIRESTORE_URL}`,
  121. onload: function (response) {
  122. try {
  123. const data = JSON.parse(response.responseText);
  124. if (data.documents) {
  125. data.documents.forEach((doc) => {
  126. const videoDate = doc.fields?.date?.stringValue;
  127. if (videoDate && shouldDeleteData(videoDate)) {
  128. GM_xmlhttpRequest({
  129. method: "DELETE",
  130. url: `${FIRESTORE_URL}/${doc.name.split("/").pop()}`,
  131. });
  132. }
  133. });
  134. }
  135. } catch (error) {}
  136. }
  137. });
  138. }
  139.  
  140. function scheduleDailyCleanup() { // Schedule daily data cleanup
  141. setInterval(() => {
  142. const now = new Date();
  143. const currentTime = now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0");
  144. if (currentTime === REMOVE_TIME) {
  145. removeOldData();
  146. }
  147. }, 60 * 1000);
  148. }
  149.  
  150. function init() { // Main initialization function
  151. VIDEO_ID = getVideoID();
  152. findVideo();
  153. monitorUrlChanges();
  154. scheduleDailyCleanup();
  155. }
  156.  
  157. init();
  158. })();

QingJ © 2025

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