Play-Scrapper / static /js /main.js
WebashalarForML's picture
Upload 5 files
74c31b0 verified
document.addEventListener('DOMContentLoaded', () => {
// Initialize Lucide Icons
lucide.createIcons();
// Elements
const themeToggle = document.getElementById('themeToggle');
const startScrape = document.getElementById('startScrape');
const userInput = document.getElementById('userInput');
const landingHero = document.getElementById('landingHero');
const loadingState = document.getElementById('loadingState');
const resultsSection = document.getElementById('resultsSection');
const statusText = document.getElementById('statusText');
const progressBar = document.getElementById('progressBar');
// Config Elements
const reviewCountType = document.getElementById('reviewCountType');
const sortOrder = document.getElementById('sortOrder');
const starRating = document.getElementById('starRating');
// Theme Logic
themeToggle.addEventListener('click', () => {
const html = document.documentElement;
if (html.classList.contains('dark')) {
html.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
html.classList.add('dark');
localStorage.setItem('theme', 'dark');
}
});
// Load Saved Theme
if (localStorage.getItem('theme') === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
// Scrape Logic
startScrape.addEventListener('click', async () => {
const identifier = userInput.value.trim();
if (!identifier) {
alert('Please enter an app name or URL');
return;
}
// 1. Transition to Loading
landingHero.classList.add('hidden');
loadingState.classList.remove('hidden');
// 2. Mock Progress Simulation
let progress = 0;
const progressInterval = setInterval(() => {
if (progress < 90) {
progress += Math.random() * 5;
progressBar.style.width = `${progress}%`;
if (progress < 30) statusText.innerText = 'Connecting to Google Play...';
else if (progress < 60) statusText.innerText = 'Bypassing Protection...';
else statusText.innerText = 'Parsing Reviews...';
}
}, 500);
// 3. Actual API Call
try {
const countValue = reviewCountType.value === 'fixed_500' ? 500 : (reviewCountType.value === 'all' ? 'all' : 150);
const response = await fetch('/scrape', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier,
review_count_type: reviewCountType.value === 'all' ? 'all' : 'fixed',
review_count: countValue,
sort_order: sortOrder.value,
star_rating: starRating.value
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to fetch reviews');
}
// 4. Success - Render Results
clearInterval(progressInterval);
progressBar.style.width = '100%';
statusText.innerText = 'Done!';
setTimeout(() => {
renderResults(data);
loadingState.classList.add('hidden');
resultsSection.classList.remove('hidden');
}, 600);
} catch (err) {
clearInterval(progressInterval);
alert(err.message);
loadingState.classList.add('hidden');
landingHero.classList.remove('hidden');
progressBar.style.width = '0%';
}
});
function renderResults(data) {
const { app_info, reviews } = data;
// Update Header
document.getElementById('appIcon').src = app_info.icon;
document.getElementById('appTitle').innerText = app_info.title;
document.getElementById('appScore').innerText = app_info.score.toFixed(1);
document.getElementById('appReviewCount').innerText = formatNumber(app_info.reviews);
document.getElementById('appDesc').innerText = app_info.summary;
document.getElementById('reviewStats').innerText = `Showing ${reviews.length} results`;
// Stars
const stars = Math.round(app_info.score);
const starContainer = document.getElementById('starContainer');
starContainer.innerHTML = '';
for (let i = 0; i < 5; i++) {
const starIcon = document.createElement('i');
starIcon.setAttribute('data-lucide', 'star');
starIcon.classList.add('w-5', 'h-5');
if (i < stars) starIcon.classList.add('fill-current');
starContainer.appendChild(starIcon);
}
// Review Feed
const feed = document.getElementById('reviewFeed');
feed.innerHTML = '';
reviews.forEach(review => {
const card = document.createElement('div');
card.className = 'bg-white dark:bg-slate-900 p-6 rounded-2xl border border-slate-200 dark:border-slate-800 space-y-3 transition-hover hover:border-primary/30';
card.innerHTML = `
<div class="flex justify-between items-start">
<div class="flex items-center gap-3">
<img src="${review.userImage || 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y'}"
class="w-10 h-10 rounded-full border border-slate-200 dark:border-slate-700">
<div>
<div class="font-bold text-sm">${review.userName}</div>
<div class="text-xs text-slate-400">${new Date(review.at).toLocaleDateString()}</div>
</div>
</div>
<div class="flex text-yellow-500">
${Array(review.score).fill('<i data-lucide="star" class="w-3 h-3 fill-current"></i>').join('')}
</div>
</div>
<p class="text-slate-600 dark:text-slate-400 text-sm leading-relaxed">${review.content}</p>
<div class="flex items-center gap-4 text-xs font-bold text-slate-400">
<span class="flex items-center gap-1"><i data-lucide="thumbs-up" class="w-3 h-3"></i> ${review.thumbsUpCount}</span>
</div>
`;
feed.appendChild(card);
});
lucide.createIcons();
// Download Action
document.getElementById('downloadCSV').onclick = () => {
downloadCSV(data.reviews, `${app_info.appId}_reviews.csv`);
};
}
function downloadCSV(arr, filename) {
const headers = Object.keys(arr[0]).join(',');
const rows = arr.map(obj => Object.values(obj).map(val => `"${val}"`).join(',')).join('\\n');
const csvContent = "data:text/csv;charset=utf-8," + headers + '\\n' + rows;
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function formatNumber(num) {
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num;
}
});