Initial commit: Feep Block pricing calculator with page multiplier

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ar.badiei
2026-07-20 20:36:35 +03:30
co-authored by Cursor
commit 255ac8cb40
8 changed files with 5338 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules/
.DS_Store
Thumbs.db
*.log
.env
.env.*
+2679
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.4.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 69.9 34.2" style="enable-background:new 0 0 69.9 34.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M63.5,9.2c-1.2,0-2.5-0.1-3.8-0.1c-2.2,0.3-3.5,2-4,4c0,0.1,0,0.2-0.1,0.3c-0.3,1-0.5,2-0.8,3.1
c-6.4,0.3-6.5-0.8-6.1-2.4c0-0.1,0.1-0.2,0.1-0.3c0.4-1.6,0.3-1.3,0.5-2.3c0.1-0.2,0.1-0.5,0.1-0.7c0-0.8-0.1-1.4-0.5-2
c-0.4-0.5-0.9-1-1.6-1.3c-1-0.5-1.5-0.3-1.7,0.8c0,0.2-0.1,0.4-0.1,0.7C45.1,10.4,44,15,44,15c-0.4,1.3-0.8,1.5-2.6,1.5
c-1.2,0-5.3,0-5.3,0c-1.5-0.2-2.2-0.9-1.9-2.4c0-0.1,0.1-0.2,0.1-0.3c0.4-1.6,0.3-1.3,0.5-2.3c0.1-0.2,0.1-0.5,0.1-0.7
c0-0.8-0.1-1.4-0.5-2c-0.4-0.5-0.9-1-1.6-1.3c-1-0.5-1.5-0.3-1.7,0.8c0,0.2-0.1,0.4-0.1,0.7c-0.3,1.5-1.4,6.1-1.4,6.1
c-0.4,1.3-0.8,1.5-2.6,1.5c-3.4-0.1-17.9,0.1-21.2,0c-2.5-0.1-3.3,1.4-3.7,2.9l-0.5,1.8c4.6,0.1,27.4,0,27.5,0
c0.5,0,0.9-0.1,1.3-0.3c0.7-0.2,1.2-0.6,1.7-1.1c0.6,0.6,1.4,1,2.3,1.2c0.1,0,9.1,0.1,9.2,0.1c1.2-0.1,2.2-0.6,2.9-1.3
c0.7,0.7,1.6,1.1,2.7,1.2c0,0,13.7,0.2,13.9,0.2c1.7-0.1,2.5-1.1,3.1-3c0.1-0.5,0.3-0.9,0.4-1.3c0.3-1,0.6-1.9,0.8-2.9
c0-0.1,0-0.2,0-0.2C67.9,11.1,66.4,9.3,63.5,9.2z M63,14.2c-0.6,2.4-0.3,2.4-2.6,2.3c-0.2,0-0.2,0-0.5,0c-0.8,0-0.6-0.6-0.6-0.8
c0.4-1.9,0.8-2.2,2.7-2.2c0.3,0,1.1,0,1.1,0S63.1,13.9,63,14.2z"/>
<g>
<path class="st0" d="M17.9,24.3c-2.9,0-5,0-7.6,0c-2.6,0-2.9,1.5-3.3,2.6l-0.6,1.7c2.7,0,5.5,0,8.2,0c0.7,0,1.4,0,2.2,0
c0.6,0,0.9,0.2,0.7,0.8l-1.1,3.3c0.5,0,1.5,0,2,0c2.2,0,2.6-1,3.2-3.1C23.2,25.3,20.9,24.2,17.9,24.3z"/>
</g>
<path class="st0" d="M64,5.9l-2.7-0.2c-0.5-0.1-0.8-0.6-0.7-1.1l0.6-2.4c0.1-0.5,0.6-0.8,1.1-0.7l2.7,0.2c0.5,0.1,0.8,0.6,0.7,1.1
l-0.6,2.4C65,5.7,64.5,6,64,5.9z"/>
<g>
<path class="st0" d="M42.1,24.7c-0.9,0-5.7,0-8.3,0c-1.8,0-1.9,1.3-1.9,1.3l-0.7,3.2l7.8,0c2.3,0,4-1.1,4.3-3.4
C43.3,25.1,43,24.7,42.1,24.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+2431
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
{
"name": "feep-price-page",
"version": "1.0.0",
"private": true,
"description": "ماشین‌حساب تعرفه Block — فیپ",
"scripts": {
"dev": "node server.js",
"start": "node server.js"
},
"engines": {
"node": ">=18"
}
}
+1
View File
@@ -0,0 +1 @@
404: Not Found
+25
View File
File diff suppressed because one or more lines are too long
+155
View File
@@ -0,0 +1,155 @@
'use strict';
const http = require('http');
const fs = require('fs');
const path = require('path');
const { URL } = require('url');
const PREFERRED_PORT = Number(process.env.PORT) || 3000;
const MAX_PORT_ATTEMPTS = process.env.PORT ? 1 : 10;
const ROOT = __dirname;
const FETCH_TIMEOUT_MS = 25000;
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.svg': 'image/svg+xml',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.ico': 'image/x-icon',
'.woff2': 'font/woff2'
};
function send(res, status, body, headers) {
res.writeHead(status, headers || {});
res.end(body);
}
function isValidFetchUrl(raw) {
try {
const parsed = new URL(raw);
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
} catch (err) {
return false;
}
}
async function fetchRemotePage(url) {
const controller = new AbortController();
const timer = setTimeout(function () {
controller.abort();
}, FETCH_TIMEOUT_MS);
try {
const response = await fetch(url, {
signal: controller.signal,
redirect: 'follow',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 FeepPricePage/1.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'fa,en-US;q=0.9,en;q=0.8'
}
});
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
return response.text();
} finally {
clearTimeout(timer);
}
}
function serveStatic(req, res) {
const reqUrl = new URL(req.url, 'http://localhost');
let pathname = decodeURIComponent(reqUrl.pathname);
if (pathname === '/') pathname = '/index.html';
const filePath = path.normalize(path.join(ROOT, pathname));
if (!filePath.startsWith(ROOT)) {
send(res, 403, 'Forbidden');
return;
}
fs.readFile(filePath, function (err, data) {
if (err) {
send(res, err.code === 'ENOENT' ? 404 : 500, err.code === 'ENOENT' ? 'Not Found' : 'Server Error');
return;
}
const ext = path.extname(filePath).toLowerCase();
send(res, 200, data, {
'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
'Cache-Control': 'no-cache'
});
});
}
const server = http.createServer(async function (req, res) {
const reqUrl = new URL(req.url, 'http://localhost');
if (reqUrl.pathname === '/api/fetch-page' && req.method === 'GET') {
const target = reqUrl.searchParams.get('url');
if (!target || !isValidFetchUrl(target)) {
send(res, 400, JSON.stringify({ error: 'Invalid URL' }), {
'Content-Type': 'application/json; charset=utf-8'
});
return;
}
try {
const html = await fetchRemotePage(target);
send(res, 200, html, {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store'
});
} catch (err) {
send(res, 502, JSON.stringify({
error: err && err.message ? err.message : 'Fetch failed'
}), {
'Content-Type': 'application/json; charset=utf-8'
});
}
return;
}
if (req.method !== 'GET' && req.method !== 'HEAD') {
send(res, 405, 'Method Not Allowed');
return;
}
serveStatic(req, res);
});
function startServer(port, attemptsLeft) {
function onError(err) {
server.removeListener('listening', onListening);
if (err.code === 'EADDRINUSE' && attemptsLeft > 1) {
startServer(port + 1, attemptsLeft - 1);
return;
}
console.error('Failed to start server:', err.message);
process.exit(1);
}
function onListening() {
server.removeListener('error', onError);
console.log('Feep price page → http://localhost:' + port);
if (port !== PREFERRED_PORT) {
console.log('Port ' + PREFERRED_PORT + ' was busy; using ' + port + ' instead.');
}
}
server.once('error', onError);
server.once('listening', onListening);
server.listen(port);
}
startServer(PREFERRED_PORT, MAX_PORT_ATTEMPTS);