Spaces:
Running
Running
I want an in-house tool for hoopshype.com (match website desifn) to add quick reactions to NBA news. It should pull the most-reposted Bluesky nba accounts and posts from the last 3 hours about a topic I provide, filtered by a minimum repost count. I’ll enter: (1) the topic, (2) the look-back window in hours, and (3) the minimum reposts. The tool should return the top Bluesky posts, formatted and ready to paste into Presto. It must be legal to run and free to use. Access is internal and limited to my coworkers.
e82d5c4 verified | document.addEventListener('DOMContentLoaded', () => { | |
| const form = document.getElementById('searchForm'); | |
| const resultsContainer = document.getElementById('resultsContainer'); | |
| const resultsDiv = document.getElementById('results'); | |
| const loadingDiv = document.getElementById('loading'); | |
| const copyAllBtn = document.getElementById('copyAllBtn'); | |
| // Mock data - in a real implementation, this would call a Bluesky API | |
| const mockPosts = [ | |
| { | |
| author: { | |
| handle: "nba.bsky.social", | |
| displayName: "NBA Official", | |
| avatar: "https://static.photos/sport/200x200/1" | |
| }, | |
| text: "Breaking: The Lakers have just signed a new point guard to bolster their roster for the playoffs! 🏀🔥 #LakeShow", | |
| reposts: 42, | |
| timestamp: "2 hours ago", | |
| url: "https://bsky.app/profile/nba.bsky.social/post/123" | |
| }, | |
| { | |
| author: { | |
| handle: "wojespn.bsky.social", | |
| displayName: "Adrian Wojnarowski", | |
| avatar: "https://static.photos/people/200x200/2" | |
| }, | |
| text: "Sources: LeBron James expected to return from injury for the first round of playoffs. Big boost for LA.", | |
| reposts: 89, | |
| timestamp: "1 hour ago", | |
| url: "https://bsky.app/profile/wojespn.bsky.social/post/456" | |
| }, | |
| { | |
| author: { | |
| handle: "shams.bsky.social", | |
| displayName: "Shams Charania", | |
| avatar: "https://static.photos/people/200x200/3" | |
| }, | |
| text: "The Phoenix Suns are finalizing a deal with veteran center DeAndre Jordan, sources tell @TheAthletic.", | |
| reposts: 56, | |
| timestamp: "2.5 hours ago", | |
| url: "https://bsky.app/profile/shams.bsky.social/post/789" | |
| } | |
| ]; | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const topic = document.getElementById('topic').value.trim(); | |
| const hours = document.getElementById('hours').value; | |
| const minReposts = parseInt(document.getElementById('minReposts').value); | |
| if (!topic) { | |
| alert('Please enter an NBA topic to search for'); | |
| return; | |
| } | |
| // Show loading | |
| resultsContainer.classList.remove('hidden'); | |
| resultsDiv.innerHTML = ''; | |
| loadingDiv.classList.remove('hidden'); | |
| // Simulate API delay | |
| setTimeout(() => { | |
| loadingDiv.classList.add('hidden'); | |
| // Filter mock data based on reposts (in a real app, this would be done server-side) | |
| const filteredPosts = mockPosts.filter(post => post.reposts >= minReposts); | |
| if (filteredPosts.length === 0) { | |
| resultsDiv.innerHTML = ` | |
| <div class="bg-yellow-50 border-l-4 border-yellow-400 p-4"> | |
| <div class="flex"> | |
| <div class="flex-shrink-0"> | |
| <i data-feather="alert-triangle" class="h-5 w-5 text-yellow-400"></i> | |
| </div> | |
| <div class="ml-3"> | |
| <p class="text-sm text-yellow-700"> | |
| No posts found matching your criteria. Try widening your search parameters. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| feather.replace(); | |
| return; | |
| } | |
| // Display results | |
| filteredPosts.forEach((post, index) => { | |
| const postElement = document.createElement('div'); | |
| postElement.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200'; | |
| postElement.innerHTML = ` | |
| <div class="flex justify-between items-start mb-2"> | |
| <div class="flex items-center space-x-3"> | |
| <img src="${post.author.avatar}" alt="${post.author.displayName}" class="w-10 h-10 rounded-full object-cover"> | |
| <div> | |
| <p class="font-medium text-gray-900">${post.author.displayName}</p> | |
| <p class="text-sm text-gray-500">@${post.author.handle} · ${post.timestamp}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> | |
| <i data-feather="repeat" class="w-3 h-3 mr-1"></i> | |
| ${post.reposts} | |
| </span> | |
| <button class="copy-btn text-blue-600 hover:text-blue-800" data-content="${escapeHtml(post.text)}"> | |
| <i data-feather="copy" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <p class="text-gray-800 whitespace-pre-line mt-2">${post.text}</p> | |
| <div class="mt-3 pt-2 border-t border-gray-200 flex justify-end"> | |
| <a href="${post.url}" target="_blank" class="text-sm text-blue-600 hover:text-blue-800 flex items-center"> | |
| View on Bluesky <i data-feather="external-link" class="w-3 h-3 ml-1"></i> | |
| </a> | |
| </div> | |
| `; | |
| resultsDiv.appendChild(postElement); | |
| }); | |
| feather.replace(); | |
| }, 1500); // Simulate network delay | |
| }); | |
| // Copy all button | |
| copyAllBtn.addEventListener('click', () => { | |
| const posts = [...document.querySelectorAll('.copy-btn')].map(btn => btn.getAttribute('data-content')); | |
| if (posts.length === 0) return; | |
| const textToCopy = posts.join('\n\n---\n\n'); | |
| navigator.clipboard.writeText(textToCopy).then(() => { | |
| showNotification('All posts copied to clipboard!', copyAllBtn); | |
| }); | |
| }); | |
| // Event delegation for copy buttons | |
| resultsDiv.addEventListener('click', (e) => { | |
| if (e.target.closest('.copy-btn')) { | |
| const btn = e.target.closest('.copy-btn'); | |
| const text = btn.getAttribute('data-content'); | |
| navigator.clipboard.writeText(text).then(() => { | |
| showNotification('Copied to clipboard!', btn); | |
| }); | |
| } | |
| }); | |
| // Helper function to show notification | |
| function showNotification(message, element) { | |
| const notification = document.createElement('div'); | |
| notification.className = 'copied-notification absolute bg-green-600 text-white text-sm py-1 px-3 rounded-md shadow-lg'; | |
| notification.textContent = message; | |
| const rect = element.getBoundingClientRect(); | |
| notification.style.left = `${rect.left + window.scrollX}px`; | |
| notification.style.top = `${rect.top + window.scrollY - 30}px`; | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.remove(); | |
| }, 2000); | |
| } | |
| // Helper function to escape HTML | |
| function escapeHtml(unsafe) { | |
| return unsafe | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| } | |
| }); |