Add 2 files
Browse files- index.html +329 -156
- prompts.txt +2 -1
index.html
CHANGED
@@ -37,7 +37,15 @@
|
|
37 |
50% { opacity: 1; }
|
38 |
100% { opacity: 0.6; }
|
39 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
</style>
|
|
|
|
|
41 |
</head>
|
42 |
<body class="bg-gray-900 text-gray-200 min-h-screen font-sans">
|
43 |
<div class="container mx-auto px-4 py-8 max-w-6xl">
|
@@ -65,7 +73,8 @@
|
|
65 |
type="text"
|
66 |
id="profileUrl"
|
67 |
class="w-full bg-gray-700 border border-gray-600 rounded-lg pl-10 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500"
|
68 |
-
placeholder="Enter Reddit
|
|
|
69 |
>
|
70 |
</div>
|
71 |
<button
|
@@ -77,9 +86,8 @@
|
|
77 |
</button>
|
78 |
</div>
|
79 |
<div class="mt-4 flex flex-wrap gap-2">
|
80 |
-
<span class="text-xs bg-gray-700 px-2 py-1 rounded">Reddit: u/username</span>
|
81 |
-
<span class="text-xs bg-gray-700 px-2 py-1 rounded">
|
82 |
-
<span class="text-xs bg-gray-700 px-2 py-1 rounded">WordPress: example.wordpress.com</span>
|
83 |
</div>
|
84 |
</div>
|
85 |
</header>
|
@@ -96,7 +104,7 @@
|
|
96 |
<i class="fas fa-link text-purple-400 text-xl"></i>
|
97 |
</div>
|
98 |
<h3 class="text-lg font-medium mb-2">1. Input Profile</h3>
|
99 |
-
<p class="text-gray-400">Provide any public social media
|
100 |
</div>
|
101 |
<div class="bg-gray-800 p-6 rounded-xl hover:bg-gray-750 transition-all">
|
102 |
<div class="w-12 h-12 bg-pink-900 rounded-lg flex items-center justify-center mb-4">
|
@@ -132,7 +140,7 @@
|
|
132 |
<div class="bg-gray-800 rounded-xl p-6 mb-8">
|
133 |
<div class="flex flex-col md:flex-row gap-6">
|
134 |
<div class="flex-shrink-0">
|
135 |
-
<div class="w-24 h-24 rounded-full bg-gradient-to-br from-purple-600 to-pink-500 flex items-center justify-center text-4xl font-bold">
|
136 |
<span id="avatarInitial">?</span>
|
137 |
</div>
|
138 |
</div>
|
@@ -143,7 +151,7 @@
|
|
143 |
<p id="profileHandle" class="text-gray-400">@unknown</p>
|
144 |
</div>
|
145 |
<div class="mt-2 md:mt-0">
|
146 |
-
<span id="profilePlatform" class="bg-gray-700 px-3 py-1 rounded-full text-sm">
|
147 |
</div>
|
148 |
</div>
|
149 |
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
@@ -187,13 +195,13 @@
|
|
187 |
<i class="fas fa-brain mr-2 text-pink-400"></i>
|
188 |
Sentiment Evolution
|
189 |
</h3>
|
190 |
-
<div class="h-64 bg-gray-700 rounded-lg
|
191 |
-
<
|
192 |
</div>
|
193 |
</div>
|
194 |
|
195 |
<!-- Topic Cloud -->
|
196 |
-
<div class="bg-gray-800 rounded-xl p-6">
|
197 |
<h3 class="text-lg font-medium mb-4 flex items-center">
|
198 |
<i class="fas fa-cloud mr-2 text-indigo-400"></i>
|
199 |
Topic Cloud
|
@@ -202,6 +210,17 @@
|
|
202 |
<!-- Topics will be added here by JavaScript -->
|
203 |
</div>
|
204 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
</section>
|
206 |
|
207 |
<!-- Loading State (Initially Hidden) -->
|
@@ -229,10 +248,18 @@
|
|
229 |
<a href="#" class="hover:text-gray-300">Contact</a>
|
230 |
</div>
|
231 |
</div>
|
|
|
|
|
|
|
232 |
</footer>
|
233 |
</div>
|
234 |
|
235 |
<script>
|
|
|
|
|
|
|
|
|
|
|
236 |
document.addEventListener('DOMContentLoaded', function() {
|
237 |
const analyzeBtn = document.getElementById('analyzeBtn');
|
238 |
const profileUrl = document.getElementById('profileUrl');
|
@@ -240,181 +267,327 @@
|
|
240 |
const loadingState = document.getElementById('loadingState');
|
241 |
const downloadReport = document.getElementById('downloadReport');
|
242 |
|
243 |
-
// Sample data for demonstration
|
244 |
-
const sampleTopics = [
|
245 |
-
{text: "politics", weight: 0.9},
|
246 |
-
{text: "technology", weight: 0.7},
|
247 |
-
{text: "philosophy", weight: 0.6},
|
248 |
-
{text: "economics", weight: 0.5},
|
249 |
-
{text: "culture", weight: 0.4},
|
250 |
-
{text: "science", weight: 0.4},
|
251 |
-
{text: "religion", weight: 0.3},
|
252 |
-
{text: "psychology", weight: 0.3}
|
253 |
-
];
|
254 |
-
|
255 |
-
const sampleTimeline = [
|
256 |
-
{
|
257 |
-
year: "2015-2017",
|
258 |
-
title: "Libertarian Phase",
|
259 |
-
description: "Strong emphasis on individual freedom, skepticism of government. Frequent mentions of Austrian economics.",
|
260 |
-
sentiment: "neutral",
|
261 |
-
tags: ["libertarian", "economics", "skepticism"]
|
262 |
-
},
|
263 |
-
{
|
264 |
-
year: "2018-2019",
|
265 |
-
title: "Political Awakening",
|
266 |
-
description: "Began engaging with more radical political content. Shift toward populist rhetoric and anti-establishment views.",
|
267 |
-
sentiment: "negative",
|
268 |
-
tags: ["populism", "anti-establishment", "anger"]
|
269 |
-
},
|
270 |
-
{
|
271 |
-
year: "2020-2021",
|
272 |
-
title: "Ideological Crisis",
|
273 |
-
description: "Evident cognitive dissonance in posts. Frequent deletions and contradictions. Increased conspiracy language.",
|
274 |
-
sentiment: "negative",
|
275 |
-
tags: ["conspiracy", "confusion", "isolation"]
|
276 |
-
},
|
277 |
-
{
|
278 |
-
year: "2022-Present",
|
279 |
-
title: "Radicalization",
|
280 |
-
description: "Clear ideological framework emerges. Dogmatic language, in-group signaling, and dehumanizing rhetoric toward out-groups.",
|
281 |
-
sentiment: "negative",
|
282 |
-
tags: ["radical", "tribalism", "authoritarian"]
|
283 |
-
}
|
284 |
-
];
|
285 |
-
|
286 |
analyzeBtn.addEventListener('click', function() {
|
287 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
|
289 |
-
if (!
|
290 |
-
alert('Please enter a valid
|
291 |
return;
|
292 |
}
|
293 |
|
294 |
// Show loading state
|
295 |
loadingState.classList.remove('hidden');
|
|
|
296 |
|
297 |
-
//
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
-
//
|
305 |
-
|
306 |
-
|
307 |
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
}
|
312 |
-
}, 300);
|
313 |
-
|
314 |
-
function showResults() {
|
315 |
-
loadingState.classList.add('hidden');
|
316 |
-
resultsSection.classList.remove('hidden');
|
317 |
|
318 |
-
//
|
319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
|
321 |
-
//
|
322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
}
|
324 |
-
}
|
325 |
-
|
326 |
-
downloadReport.addEventListener('click', function() {
|
327 |
-
alert('PDF report generation would be implemented here. This is a demo.');
|
328 |
-
});
|
329 |
|
330 |
-
function
|
331 |
-
//
|
332 |
-
|
333 |
-
let platform = "Unknown";
|
334 |
-
let avatarInitial = "?";
|
335 |
|
336 |
try {
|
337 |
-
const
|
338 |
-
|
339 |
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
} else if (host.includes('tumblr.com')) {
|
348 |
-
platform = "Tumblr";
|
349 |
-
username = host.split('.')[0];
|
350 |
-
avatarInitial = username.charAt(0).toUpperCase();
|
351 |
-
} else {
|
352 |
-
platform = "Blog";
|
353 |
-
username = host;
|
354 |
-
avatarInitial = username.charAt(0).toUpperCase();
|
355 |
-
}
|
356 |
-
} catch (e) {
|
357 |
-
console.error("Error parsing URL", e);
|
358 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
// Populate timeline
|
371 |
-
const timelineContainer = document.getElementById('timelineContainer');
|
372 |
-
timelineContainer.innerHTML = '';
|
373 |
|
374 |
-
|
375 |
-
|
376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
377 |
|
378 |
-
|
379 |
-
|
380 |
-
|
|
|
381 |
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
</div>
|
386 |
-
<div class="mb-2">
|
387 |
-
<span class="text-sm font-medium bg-gray-700 px-2 py-1 rounded">${item.year}</span>
|
388 |
-
<h4 class="text-lg font-semibold mt-1">${item.title}</h4>
|
389 |
-
</div>
|
390 |
-
<p class="text-gray-400 mb-3">${item.description}</p>
|
391 |
-
<div class="flex flex-wrap gap-2">
|
392 |
-
${item.tags.map(tag => `<span class="text-xs bg-gray-700 px-2 py-1 rounded">${tag}</span>`).join('')}
|
393 |
-
</div>
|
394 |
-
`;
|
395 |
|
396 |
-
|
|
|
|
|
397 |
});
|
398 |
|
399 |
-
//
|
400 |
-
|
401 |
-
|
|
|
|
|
|
|
|
|
|
|
402 |
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
|
408 |
-
const
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
topicElement.textContent = topic.text;
|
413 |
|
414 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
}
|
417 |
-
|
418 |
-
|
419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
</html>
|
|
|
37 |
50% { opacity: 1; }
|
38 |
100% { opacity: 0.6; }
|
39 |
}
|
40 |
+
.sentiment-bar {
|
41 |
+
transition: width 1s ease-in-out;
|
42 |
+
}
|
43 |
+
#sentimentChart {
|
44 |
+
min-height: 300px;
|
45 |
+
}
|
46 |
</style>
|
47 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
48 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/builds/compromise.min.js"></script>
|
49 |
</head>
|
50 |
<body class="bg-gray-900 text-gray-200 min-h-screen font-sans">
|
51 |
<div class="container mx-auto px-4 py-8 max-w-6xl">
|
|
|
73 |
type="text"
|
74 |
id="profileUrl"
|
75 |
class="w-full bg-gray-700 border border-gray-600 rounded-lg pl-10 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500"
|
76 |
+
placeholder="Enter Reddit username (e.g., u/spez or just spez)"
|
77 |
+
value="u/spez"
|
78 |
>
|
79 |
</div>
|
80 |
<button
|
|
|
86 |
</button>
|
87 |
</div>
|
88 |
<div class="mt-4 flex flex-wrap gap-2">
|
89 |
+
<span class="text-xs bg-gray-700 px-2 py-1 rounded">Reddit: u/username or username</span>
|
90 |
+
<span class="text-xs bg-gray-700 px-2 py-1 rounded">Coming soon: Twitter, Tumblr</span>
|
|
|
91 |
</div>
|
92 |
</div>
|
93 |
</header>
|
|
|
104 |
<i class="fas fa-link text-purple-400 text-xl"></i>
|
105 |
</div>
|
106 |
<h3 class="text-lg font-medium mb-2">1. Input Profile</h3>
|
107 |
+
<p class="text-gray-400">Provide any public social media username. We'll analyze their posting history.</p>
|
108 |
</div>
|
109 |
<div class="bg-gray-800 p-6 rounded-xl hover:bg-gray-750 transition-all">
|
110 |
<div class="w-12 h-12 bg-pink-900 rounded-lg flex items-center justify-center mb-4">
|
|
|
140 |
<div class="bg-gray-800 rounded-xl p-6 mb-8">
|
141 |
<div class="flex flex-col md:flex-row gap-6">
|
142 |
<div class="flex-shrink-0">
|
143 |
+
<div id="avatarContainer" class="w-24 h-24 rounded-full bg-gradient-to-br from-purple-600 to-pink-500 flex items-center justify-center text-4xl font-bold">
|
144 |
<span id="avatarInitial">?</span>
|
145 |
</div>
|
146 |
</div>
|
|
|
151 |
<p id="profileHandle" class="text-gray-400">@unknown</p>
|
152 |
</div>
|
153 |
<div class="mt-2 md:mt-0">
|
154 |
+
<span id="profilePlatform" class="bg-gray-700 px-3 py-1 rounded-full text-sm">Reddit</span>
|
155 |
</div>
|
156 |
</div>
|
157 |
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
195 |
<i class="fas fa-brain mr-2 text-pink-400"></i>
|
196 |
Sentiment Evolution
|
197 |
</h3>
|
198 |
+
<div class="h-64 bg-gray-700 rounded-lg">
|
199 |
+
<canvas id="sentimentChart"></canvas>
|
200 |
</div>
|
201 |
</div>
|
202 |
|
203 |
<!-- Topic Cloud -->
|
204 |
+
<div class="bg-gray-800 rounded-xl p-6 mb-8">
|
205 |
<h3 class="text-lg font-medium mb-4 flex items-center">
|
206 |
<i class="fas fa-cloud mr-2 text-indigo-400"></i>
|
207 |
Topic Cloud
|
|
|
210 |
<!-- Topics will be added here by JavaScript -->
|
211 |
</div>
|
212 |
</div>
|
213 |
+
|
214 |
+
<!-- Controversial Posts -->
|
215 |
+
<div class="bg-gray-800 rounded-xl p-6">
|
216 |
+
<h3 class="text-lg font-medium mb-4 flex items-center">
|
217 |
+
<i class="fas fa-fire mr-2 text-red-400"></i>
|
218 |
+
Most Controversial Posts
|
219 |
+
</h3>
|
220 |
+
<div id="controversialPosts" class="space-y-4">
|
221 |
+
<!-- Controversial posts will be added here -->
|
222 |
+
</div>
|
223 |
+
</div>
|
224 |
</section>
|
225 |
|
226 |
<!-- Loading State (Initially Hidden) -->
|
|
|
248 |
<a href="#" class="hover:text-gray-300">Contact</a>
|
249 |
</div>
|
250 |
</div>
|
251 |
+
<div class="mt-4 text-xs text-gray-600">
|
252 |
+
<p>Disclaimer: This tool analyzes publicly available data for research purposes only. Results should not be considered definitive psychological assessments.</p>
|
253 |
+
</div>
|
254 |
</footer>
|
255 |
</div>
|
256 |
|
257 |
<script>
|
258 |
+
// Global variables
|
259 |
+
let sentimentChart;
|
260 |
+
let allPosts = [];
|
261 |
+
let analyzedData = {};
|
262 |
+
|
263 |
document.addEventListener('DOMContentLoaded', function() {
|
264 |
const analyzeBtn = document.getElementById('analyzeBtn');
|
265 |
const profileUrl = document.getElementById('profileUrl');
|
|
|
267 |
const loadingState = document.getElementById('loadingState');
|
268 |
const downloadReport = document.getElementById('downloadReport');
|
269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
270 |
analyzeBtn.addEventListener('click', function() {
|
271 |
+
const input = profileUrl.value.trim();
|
272 |
+
|
273 |
+
if (!input) {
|
274 |
+
alert('Please enter a valid Reddit username');
|
275 |
+
return;
|
276 |
+
}
|
277 |
+
|
278 |
+
// Extract username (handle u/ prefix or not)
|
279 |
+
let username = input.startsWith('u/') ? input.substring(2) : input;
|
280 |
+
username = username.split('/')[0].split('?')[0].trim();
|
281 |
|
282 |
+
if (!username) {
|
283 |
+
alert('Please enter a valid Reddit username');
|
284 |
return;
|
285 |
}
|
286 |
|
287 |
// Show loading state
|
288 |
loadingState.classList.remove('hidden');
|
289 |
+
resultsSection.classList.add('hidden');
|
290 |
|
291 |
+
// Start analysis
|
292 |
+
analyzeRedditUser(username);
|
293 |
+
});
|
294 |
+
|
295 |
+
downloadReport.addEventListener('click', function() {
|
296 |
+
if (!analyzedData.username) return;
|
297 |
+
generatePDFReport();
|
298 |
+
});
|
299 |
+
|
300 |
+
async function analyzeRedditUser(username) {
|
301 |
+
try {
|
302 |
+
// Reset progress
|
303 |
+
document.getElementById('progressBar').style.width = '0%';
|
304 |
|
305 |
+
// Step 1: Fetch user data
|
306 |
+
updateLoadingMessage(`Fetching profile data for u/${username}...`);
|
307 |
+
await simulateProgress(10);
|
308 |
|
309 |
+
const userData = await fetchRedditUserData(username);
|
310 |
+
if (!userData) {
|
311 |
+
throw new Error('User not found or private profile');
|
312 |
}
|
|
|
|
|
|
|
|
|
|
|
313 |
|
314 |
+
// Step 2: Fetch user posts
|
315 |
+
updateLoadingMessage(`Fetching posts by u/${username}...`);
|
316 |
+
await simulateProgress(20);
|
317 |
+
|
318 |
+
const posts = await fetchRedditUserPosts(username);
|
319 |
+
if (!posts || posts.length === 0) {
|
320 |
+
throw new Error('No public posts found');
|
321 |
+
}
|
322 |
+
|
323 |
+
allPosts = posts;
|
324 |
+
|
325 |
+
// Step 3: Analyze content
|
326 |
+
updateLoadingMessage(`Analyzing ${posts.length} posts...`);
|
327 |
+
await simulateProgress(40);
|
328 |
+
|
329 |
+
const analysisResults = await analyzePosts(posts);
|
330 |
|
331 |
+
// Step 4: Process results
|
332 |
+
updateLoadingMessage(`Compiling ideological fingerprint...`);
|
333 |
+
await simulateProgress(70);
|
334 |
+
|
335 |
+
// Complete progress
|
336 |
+
document.getElementById('progressBar').style.width = '100%';
|
337 |
+
|
338 |
+
// Display results
|
339 |
+
displayResults(username, userData, analysisResults);
|
340 |
+
|
341 |
+
// Hide loading state after a brief delay
|
342 |
+
setTimeout(() => {
|
343 |
+
loadingState.classList.add('hidden');
|
344 |
+
resultsSection.classList.remove('hidden');
|
345 |
+
|
346 |
+
// Scroll to results
|
347 |
+
resultsSection.scrollIntoView({ behavior: 'smooth' });
|
348 |
+
}, 500);
|
349 |
+
|
350 |
+
} catch (error) {
|
351 |
+
console.error('Analysis failed:', error);
|
352 |
+
loadingState.classList.add('hidden');
|
353 |
+
alert(`Analysis failed: ${error.message}`);
|
354 |
}
|
355 |
+
}
|
|
|
|
|
|
|
|
|
356 |
|
357 |
+
async function fetchRedditUserData(username) {
|
358 |
+
// In a real implementation, you would use the Reddit API with proper authentication
|
359 |
+
// For this demo, we'll use a proxy service to avoid CORS issues
|
|
|
|
|
360 |
|
361 |
try {
|
362 |
+
const response = await fetch(`https://www.reddit.com/user/${username}/about.json`);
|
363 |
+
if (!response.ok) throw new Error('User not found');
|
364 |
|
365 |
+
const data = await response.json();
|
366 |
+
if (data.error) throw new Error(data.message);
|
367 |
+
|
368 |
+
return data.data;
|
369 |
+
} catch (error) {
|
370 |
+
console.error('Error fetching user data:', error);
|
371 |
+
throw error;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
}
|
373 |
+
}
|
374 |
+
|
375 |
+
async function fetchRedditUserPosts(username) {
|
376 |
+
// Fetch recent posts (in a real app you'd paginate to get more)
|
377 |
+
try {
|
378 |
+
const response = await fetch(`https://www.reddit.com/user/${username}/submitted.json?limit=100`);
|
379 |
+
if (!response.ok) throw new Error('Failed to fetch posts');
|
380 |
+
|
381 |
+
const data = await response.json();
|
382 |
+
if (data.error) throw new Error(data.message);
|
383 |
+
|
384 |
+
return data.data.children.map(child => child.data);
|
385 |
+
} catch (error) {
|
386 |
+
console.error('Error fetching posts:', error);
|
387 |
+
throw error;
|
388 |
+
}
|
389 |
+
}
|
390 |
+
|
391 |
+
async function analyzePosts(posts) {
|
392 |
+
// This is where the real analysis would happen
|
393 |
+
// For this demo, we'll do basic sentiment analysis and topic extraction
|
394 |
|
395 |
+
const results = {
|
396 |
+
totalPosts: posts.length,
|
397 |
+
earliestPost: new Date(Math.min(...posts.map(p => p.created_utc * 1000))),
|
398 |
+
latestPost: new Date(Math.max(...posts.map(p => p.created_utc * 1000))),
|
399 |
+
sentimentScores: [],
|
400 |
+
topics: {},
|
401 |
+
controversialPosts: [],
|
402 |
+
timeline: []
|
403 |
+
};
|
|
|
|
|
|
|
|
|
404 |
|
405 |
+
// Process each post
|
406 |
+
posts.forEach(post => {
|
407 |
+
// Basic sentiment analysis (very simplified)
|
408 |
+
const text = post.title + ' ' + (post.selftext || '');
|
409 |
+
const sentiment = analyzeSentiment(text);
|
410 |
+
results.sentimentScores.push({
|
411 |
+
date: new Date(post.created_utc * 1000),
|
412 |
+
score: sentiment.score,
|
413 |
+
magnitude: sentiment.magnitude
|
414 |
+
});
|
415 |
+
|
416 |
+
// Track controversial posts
|
417 |
+
if (post.downvotes > 0 || post.controversiality > 0) {
|
418 |
+
results.controversialPosts.push({
|
419 |
+
title: post.title,
|
420 |
+
text: post.selftext,
|
421 |
+
score: post.score,
|
422 |
+
url: `https://reddit.com${post.permalink}`,
|
423 |
+
date: new Date(post.created_utc * 1000)
|
424 |
+
});
|
425 |
+
}
|
426 |
|
427 |
+
// Extract topics (using compromise.js NLP)
|
428 |
+
const doc = window.nlp(text);
|
429 |
+
const nouns = doc.nouns().out('array');
|
430 |
+
const adjectives = doc.adjectives().out('array');
|
431 |
|
432 |
+
nouns.forEach(noun => {
|
433 |
+
results.topics[noun] = (results.topics[noun] || 0) + 1;
|
434 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
435 |
|
436 |
+
adjectives.forEach(adj => {
|
437 |
+
results.topics[adj] = (results.topics[adj] || 0) + 1;
|
438 |
+
});
|
439 |
});
|
440 |
|
441 |
+
// Sort controversial posts
|
442 |
+
results.controversialPosts.sort((a, b) => b.score - a.score);
|
443 |
+
|
444 |
+
// Process topics - get top 20
|
445 |
+
const topicEntries = Object.entries(results.topics)
|
446 |
+
.filter(([word]) => word.length > 3) // ignore short words
|
447 |
+
.sort((a, b) => b[1] - a[1])
|
448 |
+
.slice(0, 20);
|
449 |
|
450 |
+
results.topTopics = topicEntries.map(([text, count]) => ({
|
451 |
+
text,
|
452 |
+
weight: count / posts.length // normalize weight
|
453 |
+
}));
|
454 |
+
|
455 |
+
// Create timeline segments (group by year)
|
456 |
+
const postsByYear = {};
|
457 |
+
posts.forEach(post => {
|
458 |
+
const year = new Date(post.created_utc * 1000).getFullYear();
|
459 |
+
if (!postsByYear[year]) postsByYear[year] = [];
|
460 |
+
postsByYear[year].push(post);
|
461 |
+
});
|
462 |
+
|
463 |
+
// Create timeline items for each year with significant activity
|
464 |
+
Object.entries(postsByYear).forEach(([year, yearPosts]) => {
|
465 |
+
if (yearPosts.length < 3) return; // Skip years with few posts
|
466 |
+
|
467 |
+
// Analyze this year's posts
|
468 |
+
const yearText = yearPosts.map(p => p.title + ' ' + (p.selftext || '')).join(' ');
|
469 |
+
const yearSentiment = analyzeSentiment(yearText);
|
470 |
+
|
471 |
+
// Get top topics for this year
|
472 |
+
const yearDoc = window.nlp(yearText);
|
473 |
+
const yearNouns = yearDoc.nouns().out('array');
|
474 |
+
const yearTopics = {};
|
475 |
+
|
476 |
+
yearNouns.forEach(noun => {
|
477 |
+
if (noun.length > 3) {
|
478 |
+
yearTopics[noun] = (yearTopics[noun] || 0) + 1;
|
479 |
+
}
|
480 |
+
});
|
481 |
|
482 |
+
const topYearTopics = Object.entries(yearTopics)
|
483 |
+
.sort((a, b) => b[1] - a[1])
|
484 |
+
.slice(0, 3)
|
485 |
+
.map(([topic]) => topic);
|
|
|
486 |
|
487 |
+
// Create timeline item
|
488 |
+
results.timeline.push({
|
489 |
+
year: year.toString(),
|
490 |
+
title: getTimelineTitle(year, yearSentiment.score),
|
491 |
+
description: getTimelineDescription(year, yearSentiment.score, topYearTopics),
|
492 |
+
sentiment: yearSentiment.score > 0.2 ? 'positive' : yearSentiment.score < -0.2 ? 'negative' : 'neutral',
|
493 |
+
tags: topYearTopics
|
494 |
+
});
|
495 |
+
});
|
496 |
+
|
497 |
+
return results;
|
498 |
+
}
|
499 |
+
|
500 |
+
function analyzeSentiment(text) {
|
501 |
+
// Very basic sentiment analysis
|
502 |
+
// In a real implementation, you'd use a proper NLP library
|
503 |
+
const positiveWords = ['good', 'great', 'awesome', 'happy', 'love', 'best', 'excellent', 'positive'];
|
504 |
+
const negativeWords = ['bad', 'terrible', 'awful', 'hate', 'worst', 'negative', 'angry', 'sad'];
|
505 |
+
|
506 |
+
const words = text.toLowerCase().split(/\s+/);
|
507 |
+
let positiveCount = 0;
|
508 |
+
let negativeCount = 0;
|
509 |
+
let total = 0;
|
510 |
+
|
511 |
+
words.forEach(word => {
|
512 |
+
if (positiveWords.includes(word)) {
|
513 |
+
positiveCount++;
|
514 |
+
total++;
|
515 |
+
} else if (negativeWords.includes(word)) {
|
516 |
+
negativeCount++;
|
517 |
+
total++;
|
518 |
+
}
|
519 |
});
|
520 |
+
|
521 |
+
const score = total > 0 ? (positiveCount - negativeCount) / total : 0;
|
522 |
+
const magnitude = total > 0 ? (positiveCount + negativeCount) / total : 0;
|
523 |
+
|
524 |
+
return { score, magnitude };
|
525 |
+
}
|
526 |
+
|
527 |
+
function getTimelineTitle(year, sentimentScore) {
|
528 |
+
const descriptors = [
|
529 |
+
"Neutral Period",
|
530 |
+
"Active Year",
|
531 |
+
"Productive Phase",
|
532 |
+
"Engagement Spike"
|
533 |
+
];
|
534 |
+
|
535 |
+
if (sentimentScore > 0.3) {
|
536 |
+
return "Positive Outlook";
|
537 |
+
} else if (sentimentScore < -0.3) {
|
538 |
+
return "Negative Phase";
|
539 |
+
}
|
540 |
+
|
541 |
+
return descriptors[year % descriptors.length];
|
542 |
+
}
|
543 |
+
|
544 |
+
function getTimelineDescription(year, sentimentScore, topics) {
|
545 |
+
let desc = `In ${year}, the user frequently discussed ${topics.join(', ')}. `;
|
546 |
+
|
547 |
+
if (sentimentScore > 0.3) {
|
548 |
+
desc += "Their language was predominantly positive, showing optimism in their posts.";
|
549 |
+
} else if (sentimentScore < -0.3) {
|
550 |
+
desc += "Their language showed signs of negativity, with critical or pessimistic tones.";
|
551 |
+
} else {
|
552 |
+
desc += "Their language was generally neutral, with balanced emotional expression.";
|
553 |
+
}
|
554 |
+
|
555 |
+
return desc;
|
556 |
}
|
557 |
+
|
558 |
+
function displayResults(username, userData, analysisResults) {
|
559 |
+
// Store for PDF generation
|
560 |
+
analyzedData = {
|
561 |
+
username,
|
562 |
+
userData,
|
563 |
+
analysisResults
|
564 |
+
};
|
565 |
+
|
566 |
+
// Set profile info
|
567 |
+
document.getElementById('profileName').textContent = userData.name || username;
|
568 |
+
document.getElementById('profileHandle').textContent = `u/${username}`;
|
569 |
+
document.getElementById('avatarInitial').textContent = username.charAt(0).toUpperCase();
|
570 |
+
|
571 |
+
// Use Reddit icon if available
|
572 |
+
if (userData.icon_img) {
|
573 |
+
document.getElementById('avatarContainer').innerHTML = `
|
574 |
+
<img src="${userData.icon_img}" class="w-full h-full rounded-full object-cover" alt="Profile">
|
575 |
+
`;
|
576 |
+
}
|
577 |
+
|
578 |
+
// Set stats
|
579 |
+
document.getElementById('postCount').textContent = analysisResults.totalPosts;
|
580 |
+
|
581 |
+
const yearsActive = analysisResults.latestPost.getFullYear() - analysisResults.earliestPost.getFullYear() + 1;
|
582 |
+
document.getElementById('timeSpan').textContent = `${yearsActive} year${yearsActive !== 1 ? 's' : ''}`;
|
583 |
+
|
584 |
+
// Calculate ideology score (simplified)
|
585 |
+
const avgSentiment = analysisResults.sentimentScores.reduce((sum, item) => sum + item.score, 0) / analysisResults.sentimentScores.length;
|
586 |
+
const ideologyScore = Math.round(50 + (avgSentiment * 50)); // Convert to 0-100 scale
|
587 |
+
document.getElementById('ideologyScore').textContent = `${ideologyScore}/100`;
|
588 |
+
|
589 |
+
// Calculate volatility (standard deviation of sentiment)
|
590 |
+
const variance = analysisResults.sentimentScores.reduce((sum, item) => {
|
591 |
+
return sum + Math.pow(item.score - avgSentiment, 2);
|
592 |
+
}, 0) / analysisResults.s
|
593 |
</html>
|
prompts.txt
CHANGED
@@ -1 +1,2 @@
|
|
1 |
-
WRAITHPATH A user fingerprinting engine disguised as a minimalist blog reader. Input someone’s Reddit, Tumblr, or blog and the site outputs a timeline of shifting mental states and ideological drift. Monetization: Sell PDF reports of “ideological pathology” or mental OSINT digests. Tagline: “Their posts were a breadcrumb trail to who they really are.”
|
|
|
|
1 |
+
WRAITHPATH A user fingerprinting engine disguised as a minimalist blog reader. Input someone’s Reddit, Tumblr, or blog and the site outputs a timeline of shifting mental states and ideological drift. Monetization: Sell PDF reports of “ideological pathology” or mental OSINT digests. Tagline: “Their posts were a breadcrumb trail to who they really are.”
|
2 |
+
take it to the next level- instead of simulating data make it real
|