Track Filament
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"php-mqtt/client": "^2.2"
|
"php-mqtt/client": "^2.2",
|
||||||
|
"fabpot/goutte": "^4.0",
|
||||||
|
"symfony/polyfill-ctype": "^1.31"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1108
composer.lock
generated
1108
composer.lock
generated
File diff suppressed because it is too large
Load Diff
194
public/addFilament.php
Normal file
194
public/addFilament.php
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="light">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<!--start page content -->
|
||||||
|
|
||||||
|
<!--breadcrumb-->
|
||||||
|
<div class="page-breadcrumb d-none d-sm-flex align-items-center mb-3">
|
||||||
|
<div class="breadcrumb-title pe-3">Filament Settings</div>
|
||||||
|
<div class="ps-3">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb mb-0 p-0">
|
||||||
|
<li class="breadcrumb-item"><a href="index.php"><i class="bx bx-home-alt"></i></a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Add Filament</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end breadcrumb-->
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 mx-auto">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header px-4 py-3">
|
||||||
|
<h5 class="mb-0">Add New Filament to Track</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<!-- Alert Banner (Hidden Initially) -->
|
||||||
|
<div id="alertBanner" class="alert d-none" role="alert"></div>
|
||||||
|
|
||||||
|
<form id="filamentForm">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="productName" class="col-sm-3 col-form-label">Filament Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="productName" name="productName" placeholder="Brand / Type of Filament" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="productUrl" class="col-sm-3 col-form-label">Amazon URL</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="url" class="form-control" id="productUrl" name="productUrl" placeholder="https://www.amazon.co.uk/example" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-sm-3 col-form-label"></label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="d-md-flex d-grid align-items-center gap-3">
|
||||||
|
<button type="submit" class="btn btn-primary px-4">Track Filament</button>
|
||||||
|
<button type="reset" class="btn btn-light px-4">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery & AJAX Script -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#filamentForm').on('submit', function(event) {
|
||||||
|
event.preventDefault(); // Prevent traditional form submission
|
||||||
|
|
||||||
|
// Collect form data
|
||||||
|
let formData = $(this).serialize();
|
||||||
|
|
||||||
|
// AJAX request to submit form
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '../src/filamentTracker/addFilament.php',
|
||||||
|
data: formData,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
let banner = $('#alertBanner');
|
||||||
|
if (response.status === 'success') {
|
||||||
|
banner.removeClass('d-none alert-danger')
|
||||||
|
.addClass('alert-success')
|
||||||
|
.text(response.message);
|
||||||
|
} else {
|
||||||
|
banner.removeClass('d-none alert-success')
|
||||||
|
.addClass('alert-danger')
|
||||||
|
.text(response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$('#alertBanner').removeClass('d-none alert-success')
|
||||||
|
.addClass('alert-danger')
|
||||||
|
.text('An error occurred while adding the filament.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--end page content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
<footer class="page-footer">
|
||||||
|
<p class="mb-0">Copyright © 2024. All right reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
131
src/filamentTracker/addFilament.php
Normal file
131
src/filamentTracker/addFilament.php
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
// Check if the user is logged in
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
use Goutte\Client;
|
||||||
|
|
||||||
|
// Start session to get user ID
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for POST data
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$filamentName = $_POST['productName'] ?? '';
|
||||||
|
$amazonUrl = $_POST['productUrl'] ?? '';
|
||||||
|
$userId = $_SESSION['userId'];
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (empty($filamentName) || empty($amazonUrl)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Filament name and URL are required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
try {
|
||||||
|
// Scrape the Amazon page for initial price and other details
|
||||||
|
$crawler = $client->request('GET', $amazonUrl);
|
||||||
|
|
||||||
|
// Scrape Price
|
||||||
|
$whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0';
|
||||||
|
$fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00';
|
||||||
|
|
||||||
|
$whole = preg_replace('/[^0-9]/', '', $whole);
|
||||||
|
$fraction = preg_replace('/[^0-9]/', '', $fraction);
|
||||||
|
$fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2);
|
||||||
|
$totalPrice = floatval($whole . '.' . $fraction);
|
||||||
|
|
||||||
|
// Scrape Filament Details from Table
|
||||||
|
$details = [];
|
||||||
|
$crawler->filter('table.a-normal tr')->each(function ($node) use (&$details) {
|
||||||
|
$label = trim($node->filter('td.a-span3')->text());
|
||||||
|
$value = trim($node->filter('td.a-span9')->text());
|
||||||
|
$details[$label] = $value;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Extract details or use default if not found
|
||||||
|
$brand = $details['Brand'] ?? 'Unknown';
|
||||||
|
$material = $details['Material'] ?? 'Unknown';
|
||||||
|
$color = $details['Colour'] ?? 'Unknown';
|
||||||
|
$filamentWeight = isset($details['Item weight']) ? preg_replace('/[^0-9.]/', '', $details['Item weight']) : 1;
|
||||||
|
if (stripos($details['Item weight'], 'kilograms') !== false || stripos($details['Item weight'], 'kg') !== false) {
|
||||||
|
$filamentWeight = $filamentWeight * 1000; // Convert KG to G
|
||||||
|
}
|
||||||
|
$itemDiameter = isset($details['Item diameter']) ? preg_replace('/[^0-9.]/', '', $details['Item diameter']) : 1.75;
|
||||||
|
|
||||||
|
// Start a transaction to ensure consistency
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Check if filament already exists
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM filamentTracker WHERE amazonUrl = :amazonUrl");
|
||||||
|
$stmt->execute([':amazonUrl' => $amazonUrl]);
|
||||||
|
$existingFilament = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($existingFilament) {
|
||||||
|
// Filament exists – just update the price history
|
||||||
|
$filamentId = $existingFilament['id'];
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPriceHistory (filamentId, price)
|
||||||
|
VALUES (:filamentId, :price)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':filamentId' => $filamentId,
|
||||||
|
':price' => $totalPrice
|
||||||
|
]);
|
||||||
|
$message = 'Filament price updated successfully.';
|
||||||
|
} else {
|
||||||
|
// Insert new filament into filamentTracker
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentTracker (userId, filamentName, amazonUrl, filamentWeight, brand, material, color, itemDiameter)
|
||||||
|
VALUES (:userId, :filamentName, :amazonUrl, :filamentWeight, :brand, :material, :color, :itemDiameter)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':userId' => $userId,
|
||||||
|
':filamentName' => $filamentName,
|
||||||
|
':amazonUrl' => $amazonUrl,
|
||||||
|
':filamentWeight' => $filamentWeight,
|
||||||
|
':brand' => $brand,
|
||||||
|
':material' => $material,
|
||||||
|
':color' => $color,
|
||||||
|
':itemDiameter' => $itemDiameter
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Get the last inserted filament ID
|
||||||
|
$filamentId = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Insert initial price into filamentPriceHistory
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPriceHistory (filamentId, price)
|
||||||
|
VALUES (:filamentId, :price)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':filamentId' => $filamentId,
|
||||||
|
':price' => $totalPrice
|
||||||
|
]);
|
||||||
|
|
||||||
|
$message = $filamentName . ' Filament added successfully and price tracked.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the transaction
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => $message]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to scrape or insert data. Error: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
68
src/filamentTracker/getFilamentPrices.php
Normal file
68
src/filamentTracker/getFilamentPrices.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
// Check if the user is logged in
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Fetch all filament prices from the database
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("
|
||||||
|
SELECT
|
||||||
|
ft.filamentName,
|
||||||
|
ft.brand,
|
||||||
|
ft.material,
|
||||||
|
ft.color,
|
||||||
|
fp.price,
|
||||||
|
fp.recordedAt
|
||||||
|
FROM
|
||||||
|
filamentTracker ft
|
||||||
|
JOIN
|
||||||
|
filamentPriceHistory fp ON ft.id = fp.filamentId
|
||||||
|
ORDER BY
|
||||||
|
ft.filamentName,
|
||||||
|
fp.recordedAt ASC
|
||||||
|
");
|
||||||
|
|
||||||
|
$filaments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
// Format data for charts (grouped by filament)
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
$name = $filament['filamentName'];
|
||||||
|
|
||||||
|
if (!isset($result[$name])) {
|
||||||
|
$result[$name] = [
|
||||||
|
'brand' => $filament['brand'],
|
||||||
|
'material' => $filament['material'],
|
||||||
|
'color' => $filament['color'],
|
||||||
|
'prices' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[$name]['prices'][] = [
|
||||||
|
'price' => $filament['price'],
|
||||||
|
'recordedAt' => $filament['recordedAt']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return as JSON
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $result
|
||||||
|
], JSON_PRETTY_PRINT);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Failed to fetch filament prices: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
98
src/filamentTracker/trackFilamentPrices.php
Normal file
98
src/filamentTracker/trackFilamentPrices.php
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
use Goutte\Client;
|
||||||
|
|
||||||
|
function scrapeFilamentPrice($productName, $productUrl) {
|
||||||
|
$client = new Client();
|
||||||
|
$crawler = $client->request('GET', $productUrl);
|
||||||
|
|
||||||
|
// Extract the current price
|
||||||
|
$price = $crawler->filter('span.a-price-whole')->first()->text();
|
||||||
|
$price = str_replace(',', '', $price); // Clean the price string
|
||||||
|
|
||||||
|
// Extract the original price (if available)
|
||||||
|
$originalPriceNode = $crawler->filter('span.a-price.a-text-price')->first();
|
||||||
|
$originalPrice = $originalPriceNode->count() ? str_replace(',', '', $originalPriceNode->text()) : null;
|
||||||
|
|
||||||
|
// Calculate the discount percentage
|
||||||
|
$discountPercentage = null;
|
||||||
|
if ($originalPrice) {
|
||||||
|
$discountPercentage = round(100 - (($price / $originalPrice) * 100), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$currency = 'GBP';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'productName' => $productName,
|
||||||
|
'productUrl' => $productUrl,
|
||||||
|
'price' => $price,
|
||||||
|
'originalPrice' => $originalPrice,
|
||||||
|
'discountPercentage' => $discountPercentage,
|
||||||
|
'currency' => $currency
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Form Submission (Add New Filament to Track)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$productName = $_POST['productName'];
|
||||||
|
$productUrl = $_POST['productUrl'];
|
||||||
|
|
||||||
|
$result = scrapeFilamentPrice($productName, $productUrl);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPrices (productName, productUrl, price, originalPrice, discountPercentage, currency)
|
||||||
|
VALUES (:productName, :productUrl, :price, :originalPrice, :discountPercentage, :currency)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':productName' => $result['productName'],
|
||||||
|
':productUrl' => $result['productUrl'],
|
||||||
|
':price' => $result['price'],
|
||||||
|
':originalPrice' => $result['originalPrice'],
|
||||||
|
':discountPercentage' => $result['discountPercentage'],
|
||||||
|
':currency' => $result['currency']
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Filament price recorded successfully!']);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to record price: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Scheduled Scraping for Existing URLs (Cron Job)
|
||||||
|
if (isset($argv[1]) && $argv[1] === 'cron') {
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("SELECT * FROM filamentPrices GROUP BY productUrl");
|
||||||
|
$filaments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
$result = scrapeFilamentPrice($filament['productName'], $filament['productUrl']);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPrices (productName, productUrl, price, originalPrice, discountPercentage, currency)
|
||||||
|
VALUES (:productName, :productUrl, :price, :originalPrice, :discountPercentage, :currency)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':productName' => $result['productName'],
|
||||||
|
':productUrl' => $result['productUrl'],
|
||||||
|
':price' => $result['price'],
|
||||||
|
':originalPrice' => $result['originalPrice'],
|
||||||
|
':discountPercentage' => $result['discountPercentage'],
|
||||||
|
':currency' => $result['currency']
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "Recorded: {$result['productName']} - £{$result['price']} (Discount: {$result['discountPercentage']}%)\n";
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Failed to fetch filament data: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
63
src/filamentTracker/updateFilamentPrices.php
Normal file
63
src/filamentTracker/updateFilamentPrices.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php';
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/db.php';
|
||||||
|
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
use Goutte\Client;
|
||||||
|
|
||||||
|
// Function to scrape price
|
||||||
|
function scrapePrice($url) {
|
||||||
|
$client = new Client(); // Reinitialize client for each request
|
||||||
|
try {
|
||||||
|
$crawler = $client->request('GET', $url);
|
||||||
|
|
||||||
|
$whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0';
|
||||||
|
$fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00';
|
||||||
|
|
||||||
|
$whole = preg_replace('/[^0-9]/', '', $whole);
|
||||||
|
$fraction = preg_replace('/[^0-9]/', '', $fraction);
|
||||||
|
$fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2);
|
||||||
|
|
||||||
|
$totalPrice = floatval($whole . '.' . $fraction);
|
||||||
|
|
||||||
|
return $totalPrice;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all filaments
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("SELECT * FROM filamentTracker");
|
||||||
|
$filaments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
$amazonUrl = $filament['amazonUrl'];
|
||||||
|
$filamentId = $filament['id'];
|
||||||
|
|
||||||
|
// Scrape price
|
||||||
|
$totalPrice = scrapePrice($amazonUrl);
|
||||||
|
|
||||||
|
if ($totalPrice !== null && $totalPrice > 0) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPriceHistory (filamentId, price)
|
||||||
|
VALUES (:filamentId, :price)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':filamentId' => $filamentId,
|
||||||
|
':price' => $totalPrice
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "Updated price for {$filament['filamentName']}: £{$totalPrice}\n";
|
||||||
|
} else {
|
||||||
|
echo "Failed to update {$filament['filamentName']} (no price found or £0).\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a small random delay between 1 to 5 seconds
|
||||||
|
sleep(rand(1, 5));
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user