Size
16.1 KB
Version
1.0.1
Created
Oct 28, 2025
Updated
17 days ago
1// ==UserScript==
2// @name Reddit AI Content Generator
3// @description Generate AI-powered content for Reddit posts with one click
4// @version 1.0.1
5// @match https://*.reddit.com/*
6// @icon https://www.redditstatic.com/shreddit/assets/favicon/64x64.png
7// @grant GM.getValue
8// @grant GM.setValue
9// ==/UserScript==
10(function() {
11 'use strict';
12
13 console.log('Reddit AI Content Generator initialized');
14
15 // Debounce function to prevent excessive calls
16 function debounce(func, wait) {
17 let timeout;
18 return function executedFunction(...args) {
19 const later = () => {
20 clearTimeout(timeout);
21 func(...args);
22 };
23 clearTimeout(timeout);
24 timeout = setTimeout(later, wait);
25 };
26 }
27
28 // Show loading indicator
29 function showLoadingIndicator(text = 'AI is generating content...') {
30 let loader = document.getElementById('reddit-ai-loader');
31 if (!loader) {
32 loader = document.createElement('div');
33 loader.id = 'reddit-ai-loader';
34 loader.style.cssText = `
35 position: fixed;
36 top: 20px;
37 right: 20px;
38 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39 color: white;
40 padding: 16px 24px;
41 border-radius: 8px;
42 z-index: 999999;
43 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
44 font-size: 14px;
45 font-weight: 500;
46 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
47 display: flex;
48 align-items: center;
49 gap: 12px;
50 `;
51 document.body.appendChild(loader);
52 }
53 loader.innerHTML = `
54 <svg width="20" height="20" viewBox="0 0 20 20" style="animation: spin 1s linear infinite;">
55 <circle cx="10" cy="10" r="8" fill="none" stroke="white" stroke-width="2" stroke-dasharray="25 15"/>
56 </svg>
57 <span>${text}</span>
58 `;
59 loader.style.display = 'flex';
60
61 // Add spin animation
62 if (!document.getElementById('reddit-ai-spinner-style')) {
63 const style = document.createElement('style');
64 style.id = 'reddit-ai-spinner-style';
65 style.textContent = `
66 @keyframes spin {
67 from { transform: rotate(0deg); }
68 to { transform: rotate(360deg); }
69 }
70 `;
71 document.head.appendChild(style);
72 }
73 }
74
75 // Hide loading indicator
76 function hideLoadingIndicator() {
77 const loader = document.getElementById('reddit-ai-loader');
78 if (loader) {
79 loader.style.display = 'none';
80 }
81 }
82
83 // Show notification
84 function showNotification(message, type = 'success') {
85 const notification = document.createElement('div');
86 notification.style.cssText = `
87 position: fixed;
88 top: 20px;
89 right: 20px;
90 background: ${type === 'success' ? '#10b981' : '#ef4444'};
91 color: white;
92 padding: 16px 24px;
93 border-radius: 8px;
94 z-index: 999999;
95 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
96 font-size: 14px;
97 font-weight: 500;
98 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
99 animation: slideIn 0.3s ease-out;
100 `;
101 notification.textContent = message;
102 document.body.appendChild(notification);
103
104 setTimeout(() => {
105 notification.style.animation = 'slideOut 0.3s ease-out';
106 setTimeout(() => notification.remove(), 300);
107 }, 3000);
108 }
109
110 // Generate AI content
111 async function generateAIContent(prompt, contentType) {
112 try {
113 showLoadingIndicator();
114 console.log('Generating AI content for:', contentType, 'with prompt:', prompt);
115
116 let aiPrompt = '';
117 let responseFormat = null;
118
119 if (contentType === 'title') {
120 aiPrompt = `Generate a catchy, engaging Reddit post title based on this topic: "${prompt}". The title should be attention-grabbing, clear, and follow Reddit best practices. Keep it under 300 characters.`;
121 } else if (contentType === 'post') {
122 aiPrompt = `Write an engaging Reddit post about: "${prompt}". Make it conversational, authentic, and valuable to readers. Include relevant details and maintain a friendly tone. Format it with proper paragraphs.`;
123 responseFormat = {
124 type: "json_schema",
125 json_schema: {
126 name: "reddit_post",
127 schema: {
128 type: "object",
129 properties: {
130 content: { type: "string" },
131 suggestions: {
132 type: "array",
133 items: { type: "string" },
134 description: "3 tips for improving the post"
135 }
136 },
137 required: ["content"]
138 }
139 }
140 };
141 } else if (contentType === 'comment') {
142 aiPrompt = `Write a thoughtful, engaging Reddit comment in response to: "${prompt}". Be helpful, conversational, and add value to the discussion. Keep it concise but meaningful.`;
143 }
144
145 const result = await RM.aiCall(aiPrompt, responseFormat);
146 console.log('AI response received:', result);
147
148 hideLoadingIndicator();
149
150 if (contentType === 'post' && typeof result === 'object') {
151 return result.content;
152 }
153
154 return result;
155 } catch (error) {
156 console.error('AI generation failed:', error);
157 hideLoadingIndicator();
158 showNotification('Failed to generate content. Please try again.', 'error');
159 return null;
160 }
161 }
162
163 // Add AI button to title field
164 function addAIButtonToTitle() {
165 const titleInput = document.querySelector('textarea[name="title"]');
166 if (!titleInput || document.getElementById('ai-title-btn')) {
167 return;
168 }
169
170 console.log('Adding AI button to title field');
171
172 const container = titleInput.closest('div');
173 if (!container) return;
174
175 const aiButton = document.createElement('button');
176 aiButton.id = 'ai-title-btn';
177 aiButton.type = 'button';
178 aiButton.innerHTML = `
179 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
180 <path d="M12 2L2 7l10 5 10-5-10-5z"/>
181 <path d="M2 17l10 5 10-5"/>
182 <path d="M2 12l10 5 10-5"/>
183 </svg>
184 <span>Generate Title with AI</span>
185 `;
186 aiButton.style.cssText = `
187 display: flex;
188 align-items: center;
189 gap: 8px;
190 margin-top: 8px;
191 padding: 8px 16px;
192 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
193 color: white;
194 border: none;
195 border-radius: 6px;
196 font-size: 13px;
197 font-weight: 500;
198 cursor: pointer;
199 transition: all 0.2s;
200 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
201 `;
202
203 aiButton.addEventListener('mouseenter', () => {
204 aiButton.style.transform = 'translateY(-1px)';
205 aiButton.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
206 });
207
208 aiButton.addEventListener('mouseleave', () => {
209 aiButton.style.transform = 'translateY(0)';
210 aiButton.style.boxShadow = 'none';
211 });
212
213 aiButton.addEventListener('click', async () => {
214 const currentTitle = titleInput.value.trim();
215 const prompt = currentTitle || 'Generate an interesting Reddit post title';
216
217 const generatedTitle = await generateAIContent(prompt, 'title');
218 if (generatedTitle) {
219 titleInput.value = generatedTitle;
220 titleInput.dispatchEvent(new Event('input', { bubbles: true }));
221 titleInput.dispatchEvent(new Event('change', { bubbles: true }));
222 showNotification('Title generated successfully!');
223 }
224 });
225
226 container.appendChild(aiButton);
227 }
228
229 // Add AI button to post content field
230 function addAIButtonToContent() {
231 const contentEditor = document.querySelector('div[contenteditable="true"][data-lexical-editor="true"]');
232 if (!contentEditor || document.getElementById('ai-content-btn')) {
233 return;
234 }
235
236 console.log('Adding AI button to content field');
237
238 const container = contentEditor.closest('div')?.parentElement;
239 if (!container) return;
240
241 const aiButton = document.createElement('button');
242 aiButton.id = 'ai-content-btn';
243 aiButton.type = 'button';
244 aiButton.innerHTML = `
245 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
246 <path d="M12 2L2 7l10 5 10-5-10-5z"/>
247 <path d="M2 17l10 5 10-5"/>
248 <path d="M2 12l10 5 10-5"/>
249 </svg>
250 <span>Generate Post with AI</span>
251 `;
252 aiButton.style.cssText = `
253 display: flex;
254 align-items: center;
255 gap: 8px;
256 margin-top: 12px;
257 padding: 10px 20px;
258 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
259 color: white;
260 border: none;
261 border-radius: 6px;
262 font-size: 14px;
263 font-weight: 500;
264 cursor: pointer;
265 transition: all 0.2s;
266 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
267 `;
268
269 aiButton.addEventListener('mouseenter', () => {
270 aiButton.style.transform = 'translateY(-1px)';
271 aiButton.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
272 });
273
274 aiButton.addEventListener('mouseleave', () => {
275 aiButton.style.transform = 'translateY(0)';
276 aiButton.style.boxShadow = 'none';
277 });
278
279 aiButton.addEventListener('click', async () => {
280 const titleInput = document.querySelector('textarea[name="title"]');
281 const currentContent = contentEditor.textContent.trim();
282 const titleText = titleInput ? titleInput.value.trim() : '';
283
284 const prompt = currentContent || titleText || 'Write an engaging Reddit post';
285
286 const generatedContent = await generateAIContent(prompt, 'post');
287 if (generatedContent) {
288 // Clear existing content
289 contentEditor.innerHTML = '';
290
291 // Insert generated content
292 const paragraphs = generatedContent.split('\n\n');
293 paragraphs.forEach((para, index) => {
294 const p = document.createElement('p');
295 p.textContent = para;
296 contentEditor.appendChild(p);
297 if (index < paragraphs.length - 1) {
298 contentEditor.appendChild(document.createElement('br'));
299 }
300 });
301
302 contentEditor.dispatchEvent(new Event('input', { bubbles: true }));
303 contentEditor.dispatchEvent(new Event('change', { bubbles: true }));
304 showNotification('Post content generated successfully!');
305 }
306 });
307
308 container.appendChild(aiButton);
309 }
310
311 // Add AI button to comment field
312 function addAIButtonToComment() {
313 const commentEditors = document.querySelectorAll('div[contenteditable="true"][data-lexical-editor="true"]');
314
315 commentEditors.forEach((editor) => {
316 // Skip if already has button or is a post editor
317 if (editor.closest('[data-test-id="comment-submission-form-richtext"]') &&
318 !editor.parentElement?.querySelector('.ai-comment-btn')) {
319
320 console.log('Adding AI button to comment field');
321
322 const container = editor.parentElement;
323 if (!container) return;
324
325 const aiButton = document.createElement('button');
326 aiButton.className = 'ai-comment-btn';
327 aiButton.type = 'button';
328 aiButton.innerHTML = `
329 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
330 <path d="M12 2L2 7l10 5 10-5-10-5z"/>
331 <path d="M2 17l10 5 10-5"/>
332 <path d="M2 12l10 5 10-5"/>
333 </svg>
334 <span>AI Generate</span>
335 `;
336 aiButton.style.cssText = `
337 display: flex;
338 align-items: center;
339 gap: 6px;
340 margin-top: 8px;
341 padding: 6px 12px;
342 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
343 color: white;
344 border: none;
345 border-radius: 4px;
346 font-size: 12px;
347 font-weight: 500;
348 cursor: pointer;
349 transition: all 0.2s;
350 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
351 `;
352
353 aiButton.addEventListener('mouseenter', () => {
354 aiButton.style.transform = 'translateY(-1px)';
355 aiButton.style.boxShadow = '0 2px 8px rgba(102, 126, 234, 0.4)';
356 });
357
358 aiButton.addEventListener('mouseleave', () => {
359 aiButton.style.transform = 'translateY(0)';
360 aiButton.style.boxShadow = 'none';
361 });
362
363 aiButton.addEventListener('click', async () => {
364 // Get post title and content for context
365 const postTitle = document.querySelector('h1')?.textContent || '';
366 const currentComment = editor.textContent.trim();
367
368 const prompt = currentComment || `Write a thoughtful comment about: ${postTitle}`;
369
370 const generatedComment = await generateAIContent(prompt, 'comment');
371 if (generatedComment) {
372 editor.innerHTML = '';
373 const p = document.createElement('p');
374 p.textContent = generatedComment;
375 editor.appendChild(p);
376
377 editor.dispatchEvent(new Event('input', { bubbles: true }));
378 editor.dispatchEvent(new Event('change', { bubbles: true }));
379 showNotification('Comment generated successfully!');
380 }
381 });
382
383 container.appendChild(aiButton);
384 }
385 });
386 }
387
388 // Initialize the extension
389 function init() {
390 console.log('Initializing Reddit AI Content Generator');
391
392 // Use MutationObserver to detect when post/comment forms appear
393 const observer = new MutationObserver(debounce(() => {
394 addAIButtonToTitle();
395 addAIButtonToContent();
396 addAIButtonToComment();
397 }, 500));
398
399 observer.observe(document.body, {
400 childList: true,
401 subtree: true
402 });
403
404 // Initial check
405 setTimeout(() => {
406 addAIButtonToTitle();
407 addAIButtonToContent();
408 addAIButtonToComment();
409 }, 1000);
410 }
411
412 // Start when DOM is ready
413 if (document.readyState === 'loading') {
414 document.addEventListener('DOMContentLoaded', init);
415 } else {
416 init();
417 }
418})();