Play-Scrapper / templates /index2.html
WebashalarForML's picture
Upload 6 files
3e89456 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PlayPulse | Scraper</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #0b0e14;
--surface: #151921;
--surface2: #1c2333;
--border: #232a35;
--accent: #3b82f6;
--accent-dim: rgba(59,130,246,0.12);
--green: #22c55e;
--green-dim: rgba(34,197,94,0.12);
--amber: #f59e0b;
--text: #f1f5f9;
--muted: #64748b;
--muted2: #94a3b8;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
header { padding: 20px; border-bottom: 1px solid var(--border); background: var(--surface); text-align: center; }
h1 { font-size: 24px; font-weight: 800; color: var(--accent); }
.container { flex: 1; max-width: 1000px; margin: 0 auto; width: 100%; padding: 20px; display: flex; flex-direction: column; gap: 20px; overflow-y: auto; }
.search-container { position: relative; width: 100%; }
input[type="text"] { width: 100%; padding: 16px 20px; border-radius: 12px; background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-size: 16px; outline: none; transition: border 0.2s; }
input[type="text"]:focus { border-color: var(--accent); }
.controls { display: flex; gap: 10px; flex-wrap: wrap; }
select, button { padding: 12px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface2); color: var(--text); outline: none; font-size: 14px; cursor: pointer; }
button.primary { background: var(--accent); color: #fff; border: none; font-weight: 600; }
button.primary:hover { background: #2563eb; }
/* Suggestions Dropdown */
#suggestions { position: absolute; top: calc(100% + 8px); left: 0; right: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; z-index: 50; overflow: hidden; display: none; box-shadow: 0 10px 25px rgba(0,0,0,0.5); }
.suggestion-card { display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer; border-bottom: 1px solid var(--border); transition: background 0.2s; }
.suggestion-card:last-child { border-bottom: none; }
.suggestion-card:hover { background: var(--surface2); }
.suggestion-icon { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; }
.suggestion-info { flex: 1; display: flex; flex-direction: column; }
.suggestion-title { font-weight: 600; font-size: 14px; }
.suggestion-dev { font-size: 12px; color: var(--muted); }
/* Results */
#results { flex: 1; background: var(--surface); border-radius: 12px; border: 1px solid var(--border); padding: 20px; }
.app-header { display: flex; gap: 20px; margin-bottom: 24px; padding-bottom: 24px; border-bottom: 1px solid var(--border); align-items: center;}
.app-header img { width: 80px; height: 80px; border-radius: 16px; }
.app-title { font-size: 20px; font-weight: 700; }
.app-stats { color: var(--muted); font-size: 14px; margin-top: 4px; }
.review-card { background: var(--surface2); padding: 16px; border-radius: 12px; margin-bottom: 16px; border: 1px solid var(--border); }
.review-header { display: flex; justify-content: space-between; margin-bottom: 12px; }
.reviewer { display: flex; gap: 10px; align-items: center; font-weight: 600; }
.reviewer img { width: 32px; height: 32px; border-radius: 50%; }
.stars { color: var(--amber); }
.review-date { color: var(--muted); font-size: 12px; }
.review-body { font-size: 14px; line-height: 1.5; color: var(--text); }
.loading { display: none; text-align: center; color: var(--muted); padding: 20px; }
</style>
</head>
<body>
<header>
<h1>PlayPulse</h1>
</header>
<div class="container">
<div class="search-container">
<input type="text" id="target" placeholder="Search app name or paste URL..." autocomplete="off" />
<div id="suggestions">
<div id="searchGrid"></div>
</div>
</div>
<div class="controls">
<select id="review_count">
<option value="50">50 Reviews</option>
<option value="150" selected>150 Reviews</option>
<option value="500">500 Reviews</option>
</select>
<select id="sort_order">
<option value="NEWEST">Newest</option>
<option value="MOST_RELEVANT" selected>Most Relevant</option>
<option value="RATING">Rating</option>
</select>
<button class="primary" onclick="run()">Scrape Reviews</button>
</div>
<div class="loading" id="loading">Scraping reviews... Please wait.</div>
<div id="results">
<div style="color: var(--muted); text-align: center; margin-top: 40px;">Search for an app to see reviews.</div>
</div>
</div>
<script>
const targetEl = document.getElementById('target');
const suggestionsBox = document.getElementById('suggestions');
const searchGrid = document.getElementById('searchGrid');
let debounceTimer;
targetEl.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
const query = e.target.value.trim();
if (query.length < 2) {
hideSuggestions();
return;
}
debounceTimer = setTimeout(async () => {
try {
const res = await fetch('/search-suggestions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});
const data = await res.json();
if (!data.results || data.results.length === 0) {
hideSuggestions();
return;
}
suggestionsBox.style.display = 'block';
searchGrid.innerHTML = data.results.map(app => `
<div class="suggestion-card" onclick="selectSuggestion('${app.appId}', '${app.title.replace(/'/g, "\\'")}')">
<img src="${app.icon}" class="suggestion-icon" alt="icon" />
<div class="suggestion-info">
<span class="suggestion-title">${app.title}</span>
<span class="suggestion-dev">${app.developer} • ${app.score} ★</span>
</div>
</div>
`).join('');
} catch (err) {
console.error(err);
}
}, 400);
});
function hideSuggestions() {
suggestionsBox.style.display = 'none';
}
// --- THE SMART MOVE ---
// Instead of passing "com.whatsapp", we construct the FULL Play Store URL.
// This allows the old reliable `extract_app_id` to parse out the ?id= properly.
function selectSuggestion(appId, title) {
const validId = appId && appId !== 'null' && appId !== 'None' && !appId.includes('None');
const query = validId ? `https://play.google.com/store/apps/details?id=${appId}` : title;
targetEl.value = query;
hideSuggestions();
run(); // auto-start scraping
}
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-container')) {
hideSuggestions();
}
});
async function run() {
const identifier = targetEl.value.trim();
if (!identifier) return;
document.getElementById('loading').style.display = 'block';
document.getElementById('results').innerHTML = '';
hideSuggestions();
try {
const res = await fetch('/scrape', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier,
review_count: document.getElementById('review_count').value,
sort_order: document.getElementById('sort_order').value,
star_ratings: 'all'
})
});
const data = await res.json();
document.getElementById('loading').style.display = 'none';
if (data.error) {
document.getElementById('results').innerHTML = `<div style="color: var(--amber);">${data.error}</div>`;
return;
}
let html = `
<div class="app-header">
<img src="${data.app_info.icon}" alt="App Icon" />
<div>
<div class="app-title">${data.app_info.title}</div>
<div class="app-stats">${data.app_info.score} ★ • ${data.app_info.reviews} Total Reviews</div>
</div>
</div>
`;
data.reviews.forEach(r => {
const stars = '★'.repeat(r.score) + '☆'.repeat(5 - r.score);
const date = new Date(r.at).toLocaleDateString();
html += `
<div class="review-card">
<div class="review-header">
<div class="reviewer">
<img src="${r.userImage}" alt="User" onerror="this.src='https://via.placeholder.com/32'" />
<span>${r.userName}</span>
</div>
<div class="stars">${stars}</div>
</div>
<div class="review-body">${r.content}</div>
<div class="review-date">${date}</div>
</div>
`;
});
document.getElementById('results').innerHTML = html;
} catch (err) {
document.getElementById('loading').style.display = 'none';
document.getElementById('results').innerHTML = `<div style="color: var(--amber);">Error: ${err.message}</div>`;
}
}
</script>
</body>
</html>