From 05c7419b4c8225bbf3591305653640c987f22786 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Oct 2025 22:53:24 +0700 Subject: [PATCH] feat: stash --- README.md | 65 ++++++++++++++++++-- src/index.js | 164 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 221 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c34fe14..451b9c8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,67 @@ # miti-google-play-scraper Host [google-play-scraper](https://github.com/facundoolano/google-play-scraper) on [Vercel](https://vercel.com) -To develop locally: +## Available Methods +All methods from the google-play-scraper library are supported: +- `app`: Retrieves the full detail of an application +- `list`: Retrieves a list of applications from one of the collections at Google Play +- `search`: Retrieves a list of apps that results of searching by the given term +- `developer`: Returns the list of applications by the given developer name +- `suggest`: Given a string returns up to five suggestion to complete a search query term +- `reviews`: Retrieves a page of reviews for a specific application +- `similar`: Returns a list of similar apps to the one specified +- `permissions`: Returns the list of permissions an app has access to +- `datasafety`: Returns the data safety information of an app +- `categories`: Retrieve a full list of categories present from dropdown menu on Google Play +## Usage +The API accepts POST requests with raw JSON body parameters for each method. + +### Generic endpoint +``` +POST /scraper/:method +``` + +### Specific endpoints +``` +POST /scraper/app +POST /scraper/list +POST /scraper/search +POST /scraper/developer +POST /scraper/suggest +POST /scraper/reviews +POST /scraper/similar +POST /scraper/permissions +POST /scraper/datasafety +POST /scraper/categories +``` + +### Examples + +Get app details: +```bash +curl -X POST http://localhost:3000/scraper/app \ + -H "Content-Type: application/json" \ + -d '{"appId": "com.google.android.apps.translate"}' +``` + +Search for apps: +```bash +curl -X POST http://localhost:3000/scraper/search \ + -H "Content-Type: application/json" \ + -d '{"term": "panda", "num": 2}' +``` + +Get developer apps: +```bash +curl -X POST http://localhost:3000/scraper/developer \ + -H "Content-Type: application/json" \ + -d '{"devId": "Google LLC"}' +``` + +## Development + +To develop locally: ``` npm install vc dev @@ -13,15 +72,13 @@ open http://localhost:3000 ``` To build locally: - ``` npm install vc build ``` To deploy: - ``` npm install vc deploy -``` +``` \ No newline at end of file diff --git a/src/index.js b/src/index.js index b6336b2..41a207f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,169 @@ // Use "type: commonjs" in package.json to use CommonJS modules const express = require('express'); +const gplay = require('google-play-scraper'); const app = express(); const port = 3000; -// Define your routes +// Middleware to parse JSON bodies +app.use(express.json({ limit: '10mb' })); +app.use(express.text({ type: '*/*' })); + +// Health check endpoint app.get('/', (req, res) => { - res.json({ message: 'Hello from Express on Vercel!' }); + res.json({ + message: 'Google Play Scraper API is running!', + methods: [ + 'app', 'list', 'search', 'developer', + 'suggest', 'reviews', 'similar', + 'permissions', 'datasafety', 'categories' + ] + }); }); -app.listen(port, () => { - console.log(`Example app listening on port ${port}`); +// Generic endpoint to handle all Google Play Scraper methods +app.post('/scraper/:method', async (req, res) => { + try { + const method = req.params.method; + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + + // Validate method + if (!gplay[method]) { + return res.status(400).json({ + error: `Method '${method}' not supported`, + supportedMethods: Object.keys(gplay).filter(key => typeof gplay[key] === 'function') + }); + } + + // Execute the requested method with provided parameters + const result = await gplay[method](params); + + // Return raw result + res.json(result); + } catch (error) { + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } }); + +// Specific endpoints for each method (optional, for convenience) +app.post('/scraper/app', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.app(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/list', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.list(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/search', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.search(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/developer', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.developer(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/suggest', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.suggest(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/reviews', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.reviews(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/similar', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.similar(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/permissions', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.permissions(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/datasafety', async (req, res) => { + try { + const params = typeof req.body === 'string' ? JSON.parse(req.body) : req.body; + const result = await gplay.datasafety(params); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.post('/scraper/categories', async (req, res) => { + try { + const result = await gplay.categories(); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Error handling middleware +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).json({ error: 'Something went wrong!' }); +}); + +// 404 handler +app.use((req, res) => { + res.status(404).json({ error: 'Endpoint not found' }); +}); + +// Export for Vercel +module.exports = app; + +// Only start server if this file is run directly (not imported) +if (require.main === module) { + app.listen(port, () => { + console.log(`Google Play Scraper API listening on port ${port}`); + }); +} \ No newline at end of file