KickSkip - Jump to Timestamps on Kick.com Videos

copy, paste, and jump to specific video timestamps effortlessly.

  1. // ==UserScript==
  2. // @name KickSkip - Jump to Timestamps on Kick.com Videos
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description copy, paste, and jump to specific video timestamps effortlessly.
  6. // @match https://kick.com/*/videos/*
  7. // @grant none
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const styles = `
  15. .timestamp-overlay {
  16. position: fixed;
  17. top: 0;
  18. left: 0;
  19. width: 100%;
  20. height: 100%;
  21. background-color: rgba(0, 0, 0, 0.5);
  22. display: none;
  23. opacity: 0;
  24. justify-content: center;
  25. align-items: center;
  26. z-index: 10000;
  27. backdrop-filter: blur(2px);
  28. }
  29.  
  30. .timestamp-overlay.visible {
  31. opacity: 1;
  32. }
  33.  
  34. .timestamp-modal {
  35. background-color: #1a1a1a;
  36. border-radius: 12px;
  37. padding: 24px;
  38. width: 420px;
  39. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.24);
  40. text-align: center;
  41. opacity: 0;
  42. transform: scale(0.95);
  43. transition: all 0.2s ease;
  44. }
  45.  
  46. .timestamp-overlay.visible .timestamp-modal {
  47. opacity: 1;
  48. transform: scale(1);
  49. }
  50.  
  51. .timestamp-header {
  52. position: relative;
  53. margin-bottom: 20px;
  54. display: flex;
  55. justify-content: center;
  56. align-items: center;
  57. }
  58.  
  59. .timestamp-title {
  60. color: white;
  61. font-size: 18px;
  62. font-weight: 600;
  63. margin: 0;
  64. text-align: center;
  65. }
  66.  
  67. .close-button {
  68. position: absolute;
  69. right: 0;
  70. top: 50%;
  71. transform: translateY(-50%);
  72. background: none;
  73. border: none;
  74. color: #777;
  75. font-size: 24px;
  76. cursor: pointer;
  77. padding: 0;
  78. line-height: 1;
  79. }
  80.  
  81. .close-button:hover {
  82. color: #fff;
  83. }
  84.  
  85. .timestamp-form {
  86. display: flex;
  87. flex-direction: column;
  88. gap: 16px;
  89. align-items: center;
  90. }
  91.  
  92. .input-group {
  93. display: flex;
  94. gap: 8px;
  95. justify-content: center;
  96. }
  97.  
  98. .time-input {
  99. background-color: #2a2a2a;
  100. border: 1px solid #3a3a3a;
  101. border-radius: 8px;
  102. color: white;
  103. padding: 12px;
  104. width: 70px;
  105. text-align: center;
  106. font-size: 16px;
  107. }
  108.  
  109. .time-input:focus {
  110. outline: none;
  111. border-color: #666;
  112. background-color: #333;
  113. }
  114.  
  115. .time-input::placeholder {
  116. color: #666;
  117. }
  118.  
  119. .time-input:focus::placeholder {
  120. color: #888;
  121. }
  122.  
  123. .action-buttons {
  124. display: flex;
  125. gap: 12px;
  126. margin-top: 8px;
  127. width: 100%;
  128. }
  129.  
  130. .timestamp-button {
  131. flex: 1;
  132. padding: 12px;
  133. border-radius: 8px;
  134. border: none;
  135. font-size: 14px;
  136. font-weight: 500;
  137. cursor: pointer;
  138. }
  139.  
  140. .primary-button {
  141. background-color: #1db954;
  142. color: black;
  143. width: 100%;
  144. }
  145.  
  146. .primary-button:hover {
  147. background-color: #1ed760;
  148. transform: translateY(-1px);
  149. }
  150.  
  151. .secondary-button {
  152. background-color: #2a2a2a;
  153. color: white;
  154. }
  155.  
  156. .secondary-button:hover {
  157. background-color: #3a3a3a;
  158. transform: translateY(-1px);
  159. }
  160.  
  161. .button-row {
  162. display: flex;
  163. gap: 8px;
  164. width: 100%;
  165. }
  166.  
  167. .button-row .timestamp-button {
  168. flex: 1;
  169. }
  170.  
  171. #custom-timestamp-button {
  172. background: none;
  173. border: none;
  174. width: 32px;
  175. height: 32px;
  176. padding: 0;
  177. cursor: pointer;
  178. fill: white;
  179. position: relative;
  180. }
  181.  
  182. #custom-timestamp-button .tooltip {
  183. display: none;
  184. position: absolute;
  185. bottom: 40px;
  186. left: 50%;
  187. transform: translateX(-50%);
  188. background-color: white;
  189. color: black;
  190. padding: 5px 10px;
  191. border-radius: 4px;
  192. font-size: 14px;
  193. font-weight: 600;
  194. white-space: nowrap;
  195. z-index: 10001;
  196. }
  197.  
  198. #custom-timestamp-button:hover .tooltip {
  199. display: block;
  200. }
  201.  
  202. .tooltip::after {
  203. content: "";
  204. position: absolute;
  205. top: 100%;
  206. left: 50%;
  207. margin-left: -5px;
  208. border-width: 5px;
  209. border-style: solid;
  210. border-color: white transparent transparent transparent;
  211. }
  212.  
  213. .toast-message {
  214. position: fixed;
  215. bottom: 20px;
  216. left: 50%;
  217. transform: translateX(-50%) translateY(20px);
  218. background-color: #1db954; /* Reverted back to original color */
  219. color: black;
  220. padding: 12px 24px;
  221. border-radius: 8px;
  222. font-size: 14px;
  223. z-index: 10001;
  224. opacity: 0;
  225. transition: all 0.2s ease;
  226. }
  227.  
  228. .toast-message.visible {
  229. opacity: 1;
  230. transform: translateX(-50%) translateY(0);
  231. }
  232. `;
  233.  
  234. let toastElement;
  235. let toastTimeout;
  236.  
  237. function showToast(message, duration = 1200) {
  238. // If a toast is currently visible, clear the timeout and remove the toast
  239. if (toastTimeout) {
  240. clearTimeout(toastTimeout);
  241. }
  242. if (toastElement) {
  243. toastElement.remove();
  244. }
  245.  
  246. // Create and display the new toast message
  247. toastElement = document.createElement('div');
  248. toastElement.className = 'toast-message';
  249. toastElement.textContent = message;
  250. document.body.appendChild(toastElement);
  251.  
  252. // Trigger reflow to ensure the animation plays
  253. toastElement.offsetHeight;
  254. toastElement.classList.add('visible');
  255.  
  256. // Set a timeout to hide and remove the toast
  257. toastTimeout = setTimeout(() => {
  258. toastElement.classList.remove('visible');
  259. // Wait for the fade-out animation to complete before removing
  260. setTimeout(() => {
  261. toastElement.remove();
  262. toastElement = null; // Reset the toast element reference
  263. }, 300);
  264. }, duration);
  265. }
  266.  
  267.  
  268. function formatTime(seconds) {
  269. const d = Math.floor(seconds / (24 * 3600));
  270. const h = Math.floor((seconds % (24 * 3600)) / 3600);
  271. const m = Math.floor((seconds % 3600) / 60);
  272. const s = Math.floor(seconds % 60);
  273. return {
  274. days: String(d).padStart(2, '0'),
  275. hours: String(h).padStart(2, '0'),
  276. minutes: String(m).padStart(2, '0'),
  277. seconds: String(s).padStart(2, '0')
  278. };
  279. }
  280.  
  281. function parseTimestamp(str) {
  282. const match = str.match(/^(\d+:)?(\d+:)?(\d+:)?(\d+)$/);
  283. if (!match) return null;
  284.  
  285. const parts = str.split(':').map(Number);
  286. let seconds = 0;
  287. if (parts.length === 4) {
  288. seconds = parts[0] * 24 * 3600 + parts[1] * 3600 + parts[2] * 60 + parts[3];
  289. } else if (parts.length === 3) {
  290. seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
  291. } else if (parts.length === 2) {
  292. seconds = parts[0] * 60 + parts[1];
  293. } else {
  294. seconds = parts[0];
  295. }
  296. return seconds;
  297. }
  298.  
  299. function createTimestampModal() {
  300. const modal = document.createElement('div');
  301. modal.innerHTML = `
  302. <div class="timestamp-overlay">
  303. <div class="timestamp-modal">
  304. <div class="timestamp-header">
  305. <h2 class="timestamp-title">Input Time</h2>
  306. <button class="close-button">×</button>
  307. </div>
  308. <div class="timestamp-form">
  309. <div class="input-group">
  310. <input type="text" class="time-input" id="days" placeholder="DD" maxlength="2">
  311. <input type="text" class="time-input" id="hours" placeholder="HH" maxlength="2">
  312. <input type="text" class="time-input" id="minutes" placeholder="MM" maxlength="2">
  313. <input type="text" class="time-input" id="seconds" placeholder="SS" maxlength="2">
  314. </div>
  315. <div class="button-row">
  316. <button class="timestamp-button secondary-button" id="copy-current">Copy Current</button>
  317. <button class="timestamp-button secondary-button" id="paste-timestamp">Paste</button>
  318. </div>
  319. <button class="timestamp-button primary-button" id="jump-to">Jump to Time</button>
  320. </div>
  321. </div>
  322. </div>
  323. `;
  324.  
  325. document.body.appendChild(modal);
  326.  
  327. const overlay = document.querySelector('.timestamp-overlay');
  328. const closeButton = document.querySelector('.close-button');
  329. const jumpButton = document.getElementById('jump-to');
  330. const copyCurrentButton = document.getElementById('copy-current');
  331. const pasteButton = document.getElementById('paste-timestamp');
  332. const inputs = document.querySelectorAll('.time-input');
  333.  
  334. function showModal() {
  335. overlay.style.display = 'flex';
  336. overlay.offsetHeight; // Trigger reflow
  337. overlay.classList.add('visible');
  338. }
  339.  
  340. function hideModal() {
  341. overlay.classList.remove('visible');
  342. setTimeout(() => {
  343. overlay.style.display = 'none';
  344. }, 200);
  345. }
  346.  
  347. overlay.addEventListener('click', (e) => {
  348. if (e.target === overlay) {
  349. hideModal();
  350. }
  351. });
  352.  
  353. closeButton.addEventListener('click', () => {
  354. hideModal();
  355. });
  356.  
  357. inputs.forEach((input, index) => {
  358. input.addEventListener('input', (e) => {
  359. if (e.target.value.length === 2 && index < inputs.length - 1) {
  360. inputs[index + 1].focus();
  361. }
  362. });
  363.  
  364. input.addEventListener('keypress', (e) => {
  365. if (!/[0-9]/.test(e.key)) {
  366. e.preventDefault();
  367. }
  368. });
  369. });
  370.  
  371. jumpButton.addEventListener('click', () => {
  372. const videoPlayer = document.querySelector('video');
  373. if (!videoPlayer) return;
  374.  
  375. const days = parseInt(document.getElementById('days').value) || 0;
  376. const hours = parseInt(document.getElementById('hours').value) || 0;
  377. const minutes = parseInt(document.getElementById('minutes').value) || 0;
  378. const seconds = parseInt(document.getElementById('seconds').value) || 0;
  379.  
  380. const totalSeconds = (days * 24 * 3600) + (hours * 3600) + (minutes * 60) + seconds;
  381. if (totalSeconds >= 0) {
  382. videoPlayer.currentTime = totalSeconds;
  383. hideModal();
  384. }
  385. });
  386.  
  387. copyCurrentButton.addEventListener('click', async () => {
  388. const videoPlayer = document.querySelector('video');
  389. if (!videoPlayer) return;
  390.  
  391. const time = formatTime(videoPlayer.currentTime);
  392. const timeString = `${time.days}:${time.hours}:${time.minutes}:${time.seconds}`;
  393.  
  394. try {
  395. await navigator.clipboard.writeText(timeString);
  396. showToast('Timestamp copied to clipboard');
  397. } catch (err) {
  398. showToast('Failed to copy timestamp');
  399. }
  400. });
  401.  
  402. pasteButton.addEventListener('click', async () => {
  403. try {
  404. const text = await navigator.clipboard.readText();
  405. const seconds = parseTimestamp(text);
  406.  
  407. if (seconds !== null) {
  408. const time = formatTime(seconds);
  409. document.getElementById('days').value = time.days;
  410. document.getElementById('hours').value = time.hours;
  411. document.getElementById('minutes').value = time.minutes;
  412. document.getElementById('seconds').value = time.seconds;
  413. showToast('Timestamp pasted');
  414. } else {
  415. showToast('Invalid timestamp format');
  416. }
  417. } catch (err) {
  418. showToast('Failed to paste timestamp');
  419. }
  420. });
  421.  
  422. return { overlay, showModal, hideModal };
  423. }
  424.  
  425. function addTimestampButton() {
  426. const controlsContainer = document.querySelector('.z-controls .flex.flex-row.items-center.gap-2');
  427. if (controlsContainer && !document.getElementById('custom-timestamp-button')) {
  428. const button = document.createElement('button');
  429. button.id = 'custom-timestamp-button';
  430. button.innerHTML =
  431. `<img src="https://i.ibb.co/nP8sLjf/kickship.png" alt="Logo" style="width: 36px; height: 36px; margin: 0 auto; display: block;" />` +
  432. `<div class="tooltip">KickSkip</div>`;
  433.  
  434. // Add styles for the button to align it correctly
  435. button.style.display = "flex";
  436. button.style.alignItems = "center";
  437. button.style.justifyContent = "center";
  438. button.style.width = "36px"; // Set the width of the button
  439. button.style.height = "36px"; // Set the height of the button
  440. button.style.margin = "0 4px"; // Adjust margin as needed
  441.  
  442. button.addEventListener('click', function() {
  443. const { showModal } = createTimestampModal();
  444. showModal();
  445. });
  446.  
  447. // Find the clip button using its SVG path and place the new button before it
  448. const clipButton = Array.from(controlsContainer.querySelectorAll('button')).find(button =>
  449. button.querySelector('svg path[d="M1.82739 7.28856L27.0598 1.71777L28.2433 7.07867L3.01097 12.6495L1.82739 7.28856ZM3.03003 28.9699V13.6999H28.96V28.9699H3.03003ZM19.98 21.3299L13.47 16.7299V25.9299L19.98 21.3299Z"]')
  450. );
  451.  
  452. if (clipButton) {
  453. controlsContainer.insertBefore(button, clipButton);
  454. } else {
  455. controlsContainer.appendChild(button); // Fallback if clip button not found
  456. }
  457. }
  458. }
  459.  
  460. // Add styles
  461. const styleElement = document.createElement('style');
  462. styleElement.textContent = styles;
  463. document.head.appendChild(styleElement);
  464.  
  465. // Observe changes in the controls container
  466. const observer = new MutationObserver((mutations, obs) => {
  467. addTimestampButton();
  468. });
  469.  
  470. observer.observe(document.body, {
  471. childList: true,
  472. subtree: true
  473. });
  474.  
  475. // Initial call to add the button if controls are already loaded
  476. addTimestampButton();
  477.  
  478. })();

QingJ © 2025

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