Records all user interactions including clicks, inputs, navigation, and scrolling
Size
11.1 KB
Version
1.0.1
Created
Oct 23, 2025
Updated
about 7 hours ago
1// ==UserScript==
2// @name YouTube User Activity Recorder
3// @description Records all user interactions including clicks, inputs, navigation, and scrolling
4// @version 1.0.1
5// @match https://*.youtube.com/*
6// @icon https://www.youtube.com/s/desktop/83278897/img/favicon_32x32.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 // Initialize activity log storage
12 async function init() {
13 console.log('YouTube User Activity Recorder initialized');
14
15 // Load existing activity log
16 let activityLog = await GM.getValue('activityLog', []);
17
18 // Helper function to save activity
19 async function logActivity(activityType, details) {
20 const timestamp = new Date().toISOString();
21 const activity = {
22 timestamp,
23 type: activityType,
24 url: window.location.href,
25 ...details
26 };
27
28 activityLog.push(activity);
29
30 // Keep only last 1000 activities to prevent storage overflow
31 if (activityLog.length > 1000) {
32 activityLog = activityLog.slice(-1000);
33 }
34
35 await GM.setValue('activityLog', activityLog);
36 console.log('Activity logged:', activity);
37 }
38
39 // Track all clicks
40 document.addEventListener('click', async (event) => {
41 const target = event.target;
42 const details = {
43 tagName: target.tagName,
44 id: target.id || null,
45 className: target.className || null,
46 text: target.textContent?.substring(0, 100) || null,
47 href: target.href || null,
48 coordinates: {
49 x: event.clientX,
50 y: event.clientY
51 }
52 };
53
54 await logActivity('click', details);
55 }, true);
56
57 // Track all input changes
58 document.addEventListener('input', async (event) => {
59 const target = event.target;
60 const details = {
61 tagName: target.tagName,
62 id: target.id || null,
63 name: target.name || null,
64 type: target.type || null,
65 value: target.value?.substring(0, 100) || null,
66 placeholder: target.placeholder || null
67 };
68
69 await logActivity('input', details);
70 }, true);
71
72 // Track form submissions
73 document.addEventListener('submit', async (event) => {
74 const target = event.target;
75 const details = {
76 formId: target.id || null,
77 formAction: target.action || null,
78 formMethod: target.method || null
79 };
80
81 await logActivity('form_submit', details);
82 }, true);
83
84 // Track scrolling with debounce
85 let scrollTimeout;
86 let lastScrollPosition = { x: window.scrollX, y: window.scrollY };
87
88 window.addEventListener('scroll', () => {
89 clearTimeout(scrollTimeout);
90 scrollTimeout = setTimeout(async () => {
91 const details = {
92 scrollX: window.scrollX,
93 scrollY: window.scrollY,
94 scrollHeight: document.documentElement.scrollHeight,
95 clientHeight: document.documentElement.clientHeight,
96 scrollPercentage: Math.round((window.scrollY / (document.documentElement.scrollHeight - document.documentElement.clientHeight)) * 100)
97 };
98
99 // Only log if scroll position changed significantly (more than 50px)
100 if (Math.abs(window.scrollY - lastScrollPosition.y) > 50 ||
101 Math.abs(window.scrollX - lastScrollPosition.x) > 50) {
102 await logActivity('scroll', details);
103 lastScrollPosition = { x: window.scrollX, y: window.scrollY };
104 }
105 }, 500);
106 }, true);
107
108 // Track navigation (URL changes)
109 let lastUrl = window.location.href;
110
111 const navigationObserver = new MutationObserver(async () => {
112 if (window.location.href !== lastUrl) {
113 const details = {
114 from: lastUrl,
115 to: window.location.href,
116 title: document.title
117 };
118
119 await logActivity('navigation', details);
120 lastUrl = window.location.href;
121 }
122 });
123
124 navigationObserver.observe(document.body, {
125 childList: true,
126 subtree: true
127 });
128
129 // Track page load
130 await logActivity('page_load', {
131 title: document.title,
132 referrer: document.referrer || null
133 });
134
135 // Track page unload
136 window.addEventListener('beforeunload', async () => {
137 await logActivity('page_unload', {
138 title: document.title,
139 timeOnPage: Date.now() - performance.timing.navigationStart
140 });
141 });
142
143 // Add UI to view and export logs
144 createLogViewerUI();
145 }
146
147 // Create UI for viewing and exporting logs
148 function createLogViewerUI() {
149 const button = document.createElement('button');
150 button.textContent = '📊 Activity Log';
151 button.style.cssText = `
152 position: fixed;
153 bottom: 20px;
154 right: 20px;
155 z-index: 10000;
156 background: #ff0000;
157 color: white;
158 border: none;
159 border-radius: 50%;
160 width: 60px;
161 height: 60px;
162 font-size: 24px;
163 cursor: pointer;
164 box-shadow: 0 4px 8px rgba(0,0,0,0.3);
165 transition: transform 0.2s;
166 `;
167
168 button.addEventListener('mouseenter', () => {
169 button.style.transform = 'scale(1.1)';
170 });
171
172 button.addEventListener('mouseleave', () => {
173 button.style.transform = 'scale(1)';
174 });
175
176 button.addEventListener('click', async () => {
177 const activityLog = await GM.getValue('activityLog', []);
178 showLogViewer(activityLog);
179 });
180
181 document.body.appendChild(button);
182 }
183
184 // Show log viewer modal
185 function showLogViewer(activityLog) {
186 const modal = document.createElement('div');
187 modal.style.cssText = `
188 position: fixed;
189 top: 0;
190 left: 0;
191 width: 100%;
192 height: 100%;
193 background: rgba(0,0,0,0.8);
194 z-index: 10001;
195 display: flex;
196 justify-content: center;
197 align-items: center;
198 `;
199
200 const content = document.createElement('div');
201 content.style.cssText = `
202 background: white;
203 color: black;
204 padding: 30px;
205 border-radius: 10px;
206 max-width: 90%;
207 max-height: 90%;
208 overflow: auto;
209 box-shadow: 0 8px 16px rgba(0,0,0,0.5);
210 `;
211
212 const title = document.createElement('h2');
213 title.textContent = 'Activity Log';
214 title.style.cssText = 'margin-top: 0; color: #ff0000;';
215
216 const stats = document.createElement('div');
217 stats.style.cssText = 'margin: 20px 0; padding: 15px; background: #f5f5f5; border-radius: 5px;';
218 stats.innerHTML = `
219 <strong>Total Activities:</strong> ${activityLog.length}<br>
220 <strong>Clicks:</strong> ${activityLog.filter(a => a.type === 'click').length}<br>
221 <strong>Inputs:</strong> ${activityLog.filter(a => a.type === 'input').length}<br>
222 <strong>Scrolls:</strong> ${activityLog.filter(a => a.type === 'scroll').length}<br>
223 <strong>Navigations:</strong> ${activityLog.filter(a => a.type === 'navigation').length}
224 `;
225
226 const logContainer = document.createElement('pre');
227 logContainer.style.cssText = `
228 background: #f5f5f5;
229 padding: 15px;
230 border-radius: 5px;
231 max-height: 400px;
232 overflow: auto;
233 font-size: 12px;
234 white-space: pre-wrap;
235 word-wrap: break-word;
236 `;
237 logContainer.textContent = JSON.stringify(activityLog, null, 2);
238
239 const buttonContainer = document.createElement('div');
240 buttonContainer.style.cssText = 'margin-top: 20px; display: flex; gap: 10px;';
241
242 const exportButton = document.createElement('button');
243 exportButton.textContent = 'Export as JSON';
244 exportButton.style.cssText = `
245 background: #ff0000;
246 color: white;
247 border: none;
248 padding: 10px 20px;
249 border-radius: 5px;
250 cursor: pointer;
251 font-size: 14px;
252 `;
253 exportButton.addEventListener('click', () => {
254 const dataStr = JSON.stringify(activityLog, null, 2);
255 const dataBlob = new Blob([dataStr], { type: 'application/json' });
256 const url = URL.createObjectURL(dataBlob);
257 const link = document.createElement('a');
258 link.href = url;
259 link.download = `youtube-activity-log-${new Date().toISOString()}.json`;
260 link.click();
261 URL.revokeObjectURL(url);
262 });
263
264 const clearButton = document.createElement('button');
265 clearButton.textContent = 'Clear Log';
266 clearButton.style.cssText = `
267 background: #666;
268 color: white;
269 border: none;
270 padding: 10px 20px;
271 border-radius: 5px;
272 cursor: pointer;
273 font-size: 14px;
274 `;
275 clearButton.addEventListener('click', async () => {
276 if (confirm('Are you sure you want to clear all activity logs?')) {
277 await GM.setValue('activityLog', []);
278 modal.remove();
279 alert('Activity log cleared!');
280 }
281 });
282
283 const closeButton = document.createElement('button');
284 closeButton.textContent = 'Close';
285 closeButton.style.cssText = `
286 background: #333;
287 color: white;
288 border: none;
289 padding: 10px 20px;
290 border-radius: 5px;
291 cursor: pointer;
292 font-size: 14px;
293 `;
294 closeButton.addEventListener('click', () => {
295 modal.remove();
296 });
297
298 buttonContainer.appendChild(exportButton);
299 buttonContainer.appendChild(clearButton);
300 buttonContainer.appendChild(closeButton);
301
302 content.appendChild(title);
303 content.appendChild(stats);
304 content.appendChild(logContainer);
305 content.appendChild(buttonContainer);
306 modal.appendChild(content);
307 document.body.appendChild(modal);
308
309 modal.addEventListener('click', (e) => {
310 if (e.target === modal) {
311 modal.remove();
312 }
313 });
314 }
315
316 // Start the extension
317 init();
318})();