From c257844fa2990e6de40309561e518fa2f9a47355 Mon Sep 17 00:00:00 2001 From: leca Date: Sat, 26 Apr 2025 01:57:11 +0300 Subject: [PATCH] staging & TODO --- TODO | 5 ++++ db_schema.psql | 17 ++++++++++-- public/js/index.js | 2 +- src/controllers/api.js | 38 ------------------------- src/controllers/captcha.js | 57 ++++++++++++++++++++++++++++++++++++++ src/controllers/proxy.js | 31 +++++++++++++++++++++ src/index.js | 4 +-- src/routers/api.js | 11 -------- src/routers/captcha.js | 11 ++++++++ src/routers/proxy.js | 11 ++++++++ src/services/captcha.js | 18 ++++++++++-- src/services/proxy.js | 23 +++++++++++++++ 12 files changed, 171 insertions(+), 57 deletions(-) create mode 100644 TODO delete mode 100644 src/controllers/api.js create mode 100644 src/controllers/captcha.js create mode 100644 src/controllers/proxy.js delete mode 100644 src/routers/api.js create mode 100644 src/routers/captcha.js create mode 100644 src/routers/proxy.js create mode 100644 src/services/proxy.js diff --git a/TODO b/TODO new file mode 100644 index 0000000..c75f338 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +[ ] Fully implement and test proxies +[ ] Add a counter of tries for a captcha +[ ] Make new setting "max tries per captcha" +[ ] Unclaim proxy and delete captcha on max tries reached +[ ] Add retry to fronted when captcha is entered incorrectly \ No newline at end of file diff --git a/db_schema.psql b/db_schema.psql index 1af85e0..a4a6058 100644 --- a/db_schema.psql +++ b/db_schema.psql @@ -1,5 +1,18 @@ -CREATE TABLE IF NOT EXISTS captchas( +CREATE TABLE IF NOT EXISTS captchas ( id SERIAL PRIMARY KEY, + proxy_id INTEGER REFERENCES proxies(id), hash CHAR(32), solution CHAR(6) -); \ No newline at end of file +); + +CREATE TYPE IF NOT EXISTS protocol_type AS ENUM ('http', 'https'); + +CREATE TABLE IF NOT EXISTS proxies ( + id SERIAL PRIMARY KEY, + proto protocol_type, + claimed BOOLEAN DEFAULT FALSE, + host INET NOT NULL, + port SMALLINT NOT NULL, + username VARCHAR(16), + password VARCHAR(32) +) \ No newline at end of file diff --git a/public/js/index.js b/public/js/index.js index 7d40619..7cc88e6 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,5 +1,5 @@ window.onload = async () => { - let id = (await (await fetch("/api/captcha/new", {method: "POST"})).json()).id + let id = (await (await fetch("/api/captcha", {method: "POST"})).json()).id console.log(id); fetch(`/api/captcha/${id}`).then(response => response.blob()) .then(blob => { diff --git a/src/controllers/api.js b/src/controllers/api.js deleted file mode 100644 index acb8ae9..0000000 --- a/src/controllers/api.js +++ /dev/null @@ -1,38 +0,0 @@ -import CaptchaService from "../services/captcha.js"; -import config from '../config.js'; -import fs from 'fs/promises'; - -class ApiController { - async new(req, res) { - const id = await CaptchaService.new(); - - return res.status(200).send({"id": id}); - } - - async get(req, res) { - const id = req.params.id; - - const hash = await CaptchaService.get(id); - if (hash == undefined) { - return res.status(404).send({"message": "captcha not found"}); - } - const image = await fs.readFile(`${config.data_dir}/${hash}.jpeg`); - - return res.status(200).send(image) - } - - async solve (req, res) { - const id = req.params.id; - const solution = req.body["solution"]; - if (solution == undefined || solution.length != 6) { - return res.status(400).send({"message": 'please, send a valid solution. Example: {"solution":"123456"}'}); - } - - if (!await CaptchaService.check_and_save_solution(id, solution)) - return res.status(409).send({"message": "Solution is not correct"}); - - return res.status(200).send({"message": "Successful"}); - } -} - -export default new ApiController(); \ No newline at end of file diff --git a/src/controllers/captcha.js b/src/controllers/captcha.js new file mode 100644 index 0000000..c82e89c --- /dev/null +++ b/src/controllers/captcha.js @@ -0,0 +1,57 @@ +import CaptchaService from "../services/captcha.js"; +import ProxyService from "../services/proxy.js"; +import config from '../config.js'; +import fs from 'fs/promises'; + +class CaptchaController { + async new(req, res) { + try { + const proxy = await ProxyService.take(); + const id = await CaptchaService.new(proxy); + + return res.status(200).send({"id": id}); + } catch (e) { + console.log(e) + return res.status(500).send({"message": "Unknown server error"}); + } + } + + async get(req, res) { + try { + const id = req.params.id; + + const hash = await CaptchaService.get(id); + if (hash == undefined) { + return res.status(404).send({"message": "captcha not found"}); + } + const image = await fs.readFile(`${config.data_dir}/${hash}.jpeg`); + + return res.status(200).send(image) + } catch (e) { + console.log(e) + return res.status(500).send({"message": "Unknown server error"}); + } + } + + async solve (req, res) { + try { + const id = req.params.id; + const solution = req.body["solution"]; + if (solution == undefined || solution.length != 6) { + return res.status(400).send({"message": 'please, send a valid solution. Example: {"solution":"123456"}'}); + } + + if (!await CaptchaService.check_and_save_solution(id, solution)) + return res.status(409).send({"message": "Solution is not correct"}); + + let proxy_id = await CaptchaService.get_assigned_proxy(id); + await ProxyService.give_back(id); + return res.status(200).send({"message": "Successful"}); + } catch (e) { + console.log(e) + return res.status(500).send({"message": "Unknown server error"}); + } + } +} + +export default new CaptchaController(); \ No newline at end of file diff --git a/src/controllers/proxy.js b/src/controllers/proxy.js new file mode 100644 index 0000000..5b6e0b3 --- /dev/null +++ b/src/controllers/proxy.js @@ -0,0 +1,31 @@ +import ProxyService from '../services/proxy.js'; + +class CaptchaController { + async add(req, res) { + try { + const { host, port, user: username, password } = req.body; + if (!host || !port) return res.status(400).send({"message":"You must specify host and port!"}); + + const id = await ProxyService.add(host, port, username, password); + + return res.status(200).send({"id": id}); + } catch (e) { + console.log(e) + return res.status(500).send({"message": "Unknown server error"}); + } + } + + async delete (req, res) { + try { + const id = req.params.id; + await ProxyService.delete(id) + return res.status(200).send({"message": "Successful"}); + } catch (e) { + console.log(e) + return res.status(500).send({"message": "Unknown server error"}); + } + + } +} + +export default new CaptchaController(); \ No newline at end of file diff --git a/src/index.js b/src/index.js index c25be88..3959764 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import express from 'express'; import path from 'path'; -import ApiRouter from './routers/api.js'; +import CaptchaRouter from './routers/captcha.js'; import FrontendRouter from './routers/frontend.js'; import config from './config.js'; @@ -14,7 +14,7 @@ app.use(express.json()); app.set('view engine', 'pug'); -app.use('/api', ApiRouter); +app.use('/api', CaptchaRouter); app.use('/', FrontendRouter); const server = app.listen(config.app_port, () => { diff --git a/src/routers/api.js b/src/routers/api.js deleted file mode 100644 index fd444fc..0000000 --- a/src/routers/api.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Router } from 'express'; - -import ApiController from '../controllers/api.js'; - -const ApiRouter = new Router(); - -ApiRouter.post('/captcha/new', ApiController.new); -ApiRouter.get('/captcha/:id', ApiController.get); -ApiRouter.patch('/captcha/:id', ApiController.solve); - -export default ApiRouter; \ No newline at end of file diff --git a/src/routers/captcha.js b/src/routers/captcha.js new file mode 100644 index 0000000..4c5a6f5 --- /dev/null +++ b/src/routers/captcha.js @@ -0,0 +1,11 @@ +import { Router } from 'express'; + +import CaptchaController from '../controllers/captcha.js'; + +const CaptchaRouter = new Router(); + +CaptchaRouter.post('/captcha', CaptchaController.new); +CaptchaRouter.get('/captcha/:id', CaptchaController.get); +CaptchaRouter.patch('/captcha/:id', CaptchaController.solve); + +export default CaptchaRouter; \ No newline at end of file diff --git a/src/routers/proxy.js b/src/routers/proxy.js new file mode 100644 index 0000000..5b9057e --- /dev/null +++ b/src/routers/proxy.js @@ -0,0 +1,11 @@ +import { Router } from 'express'; + +import ProxyController from '../controllers/proxy.js'; + +const ProxyRouter = new Router(); + +ProxyRouter.post('/proxy', ProxyController.add); +ProxyRouter.get('/proxy/:id', ProxyController.get); +ProxyRouter.patch('/captcha/:id', ProxyController.solve); + +export default ProxyRouter; \ No newline at end of file diff --git a/src/services/captcha.js b/src/services/captcha.js index 7f6d6c2..35a762c 100644 --- a/src/services/captcha.js +++ b/src/services/captcha.js @@ -1,18 +1,26 @@ import db from '../db.js'; import fs from 'fs/promises'; +import { ProxyAgent } from 'undici'; import config from '../config.js'; import { createHash } from 'crypto'; class CaptchaService { - async new() { + async new(proxy) { try { - const captcha = await (await fetch(config.captcha_source_url)).blob(); + let dispatcher; + if (proxy.username) { + dispatcher = new ProxyAgent(`${proxy.proto}://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port}`) + } else { + dispatcher = new ProxyAgent(`${proxy.proto}://${proxy.host}:${proxy.port}`) + } + + const captcha = await (await fetch(config.captcha_source_url, dispatcher)).blob(); const buffer = Buffer.from(await captcha.arrayBuffer()); const hash = createHash('md5').update(buffer).digest('hex'); await fs.writeFile(`${config.data_dir}/${hash}.jpeg`, buffer); - const id = (await db.query("INSERT INTO captchas (hash) VALUES ($1) RETURNING id", [hash])).rows[0].id; + const id = (await db.query("INSERT INTO captchas (hash, proxy_id) VALUES ($1, $2) RETURNING id", [hash, proxy.id])).rows[0].id; return id } catch(e) { @@ -56,6 +64,10 @@ class CaptchaService { await db.query("UPDATE captchas SET solution = $1 WHERE id = $2", [solution, id]); return true; } + + async get_assigned_proxy(id) { + return (await db.query("SELECT proxy_id FROM captchas WHERE id = $1", [id])).rows[0].proxy_id; + } } export default new CaptchaService(); \ No newline at end of file diff --git a/src/services/proxy.js b/src/services/proxy.js new file mode 100644 index 0000000..2811c92 --- /dev/null +++ b/src/services/proxy.js @@ -0,0 +1,23 @@ +import db from '../db.js'; + +class ProxyService { + async add(proto, host, port, username, password) { + await db.query("INSERT INTO proxies (proto, host, port, username, password) VALUES ($1, $2, $3, $4, $5)", [proto, host, port, username, password]); + } + + async take() { + let proxy = (await db.query("UPDATE proxies SET claimed = TRUE WHERE id = ( SELECT id FROM proxies WHERE claimed = FALSE ORDER BY id LIMIT 1 ) RETURNING proto, host, port, username, password")).rows[0] + if (proxy == undefined) return undefined; + return proxy; + } + + async give_back(id) { + await db.query("UPDATE proxies SET claimed = false WHERE id = $1", [id]); + } + + async delete(id) { + await db.query("DELETE FRM proxies WHERE id = $1", [id]); + } +} + +export default new ProxyService(); \ No newline at end of file