summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorakiyamn2023-10-04 23:29:19 +1100
committerakiyamn2023-10-04 23:29:19 +1100
commita2d1c179f88e056265321b426a51907a3941044a (patch)
tree3804227ee072265a1cf1121cc59eb1fbdc74a3e2 /src
parent4e87195739f2a5d9a05451b48773c8afdc680765 (diff)
downloadprice-tracker-worker-a2d1c179f88e056265321b426a51907a3941044a.tar.gz
price-tracker-worker-a2d1c179f88e056265321b426a51907a3941044a.zip
Kind of worksmaster
Diffstat (limited to 'src')
-rw-r--r--src/cron.ts56
-rw-r--r--src/gather.ts111
-rw-r--r--src/index.ts49
-rw-r--r--src/store.ts10
4 files changed, 220 insertions, 6 deletions
diff --git a/src/cron.ts b/src/cron.ts
new file mode 100644
index 0000000..fb016a3
--- /dev/null
+++ b/src/cron.ts
@@ -0,0 +1,56 @@
+import { fetchColesProduct, fetchWooliesProduct, ProductInfo } from "./gather"
+import { storePriceRecord } from "./store"
+
+export interface ProductRequest {
+ productId: string,
+ companyName: string
+}
+
+export async function cacheAllPrices(db: D1Database, products: ProductRequest[]): Promise<boolean> {
+ // return getAllPrices(products).then().map(storePriceRecord)
+ return (await getAllPrices(products))
+ .map(x => {console.log("getting prices", x); return x})
+ .filter(x => x != null)
+ .map(product => storePriceRecord(db, product as ProductInfo))
+ .every(async x => await x === true)
+
+ // for (let p of products) {
+ // console.log(p)
+ // const prices = await getPrice(p)
+ // console.warn(prices)
+ // }
+
+ // let out = []
+ // for (let price of prices) {
+ // if (price !== null) {
+ // const result = await storePriceRecord(db, price)
+ // console.log(price, result)
+ // out.push(result)
+ // }
+ // }
+ // return out.every(x => !!x)
+ // return true
+
+
+}
+
+async function getAllPrices(products: ProductRequest[]): Promise<(ProductInfo | null)[]> {
+ return await Promise.all(products.map(getPrice))
+ // let out = []
+ // for (let p of products) {
+ // out.push(await getPrice(p))
+ // }
+ // return out
+ // return [await getPrice(products[0])]
+}
+
+async function getPrice(product: ProductRequest): Promise<ProductInfo | null> {
+ switch (product.companyName) {
+ case "Coles":
+ return await fetchColesProduct(product.productId)
+ case "Woolies":
+ return await fetchWooliesProduct(product.productId)
+ default:
+ throw Error("Invalid store name")
+ }
+} \ No newline at end of file
diff --git a/src/gather.ts b/src/gather.ts
new file mode 100644
index 0000000..7252418
--- /dev/null
+++ b/src/gather.ts
@@ -0,0 +1,111 @@
+
+export interface ProductInfo {
+ productId: string,
+ companyName: string,
+ productName: string,
+ price: number,
+ accessDate: string,
+ picture: string | null
+
+}
+
+interface WooliesResponse {
+ Product: {
+ Stockcode: number,
+ DisplayName: string,
+ Price: number,
+ LargeImageFile: string
+ }
+}
+
+interface ColesResponse {
+ pricing: {
+ now: number,
+ }
+ id: number,
+ name: string,
+ imageUris: [{
+ uri: string
+ }]
+}
+
+// const COLES_ROOT_URL = "apigw.coles.com.au"
+// const WOOLIES_ROOT_URL = "www.woolworths.com.au"
+
+const COLES_ROOT_URL = "localhost:3002"
+const WOOLIES_ROOT_URL = "localhost:3002"
+
+
+
+export async function fetchWooliesProduct(productID: string): Promise<ProductInfo | null> {
+ const options = {
+ method: 'GET',
+ // credentials: 'include',
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0',
+ "Cookie": '_abck=8D926507FE8BB3E2030100E0A40908AD~-1~YAAQDV8wF4yrp+WKAQAAcBa1+QqOLH6Cr1PPGhoBK6dzldNoO6fT9E5Yh2JWjug69wmscVVDCxTl3AtqhLO700yB1l/aMekoSEMfXWdB1vAkiaNMKVPNjc+x4hh7Yon6NLceF4NYulFEKqcjo1BEgGtnYYv8xgJW0jcO0i4kIdNAZaszCrXxefAMTskVsvWcZvmAuqcLr72EmDY6BNBSCQgzm2KX4uAM7D79DQVKY5TDR79ITzKmCZghdYGdNrXtSxksNB8rllTWUT+5UD45fpB13jYdxmwy6Lu51gBNuYrQ/lmQuJdFIzc3czVYvQI64uDlWbA17Swz/9En3cCF0/IGtuAUx/6PfJVfAdFyfZrcD3d7Sl+vdzlhiZ3smo/1/ocITzZggDurPckyVMvy5DPmZDx1mwvkn83yOt0cP7c=~-1~-1~-1;'
+ }
+
+
+ };
+
+ const url = `http://${WOOLIES_ROOT_URL}/apis/ui/product/detail/${productID}?isMobile=false&useVariant=false`
+ const response = fetch(url, options)
+ // .json()
+ // .then(response => console.log(response))
+ .then(response => response.json())
+ .then(x => {console.log(url, x); return x})
+ .then(data => data as WooliesResponse)
+ .then(formatWooliesProduct)
+ // .catch(err => console.error(err));
+
+ return response
+}
+
+export async function fetchColesProduct(productID: string): Promise<ProductInfo | null> {
+ const options = {
+ method: 'GET',
+ headers: {
+ 'Ocp-Apim-Subscription-Key': 'dd6ae58532d743978508555a59a199ac',
+ 'User-Agent': 'okhttp/4.10.0'
+ }
+ };
+
+ const url = `https://${COLES_ROOT_URL}/digital/colesappbff/v3/api/2/products/${productID}?type=sku&storeId=697&shoppingMethod=clickAndCollect&includeLiquor=true`
+ // const url = `http://localhost:3002/apis/ui/product/detail/${productID}?isMobile=false&useVariant=false`
+ const response = fetch(url, options)
+ // .json()
+ // .then(response => console.log(response))
+ .then(x => {console.log(x); return x})
+ .then(response => response.json())
+ .then(data => data as WooliesResponse)
+ .then(formatWooliesProduct)
+ // .catch(err => console.error(err));
+
+ return response
+}
+
+
+function formatWooliesProduct(json: WooliesResponse): ProductInfo {
+ const data = json.Product
+ return {
+ "productId": String(data.Stockcode),
+ "companyName": "Woolies",
+ "productName": data.DisplayName,
+ "price": data.Price,
+ "accessDate": String(Date.now()),
+ "picture": data.LargeImageFile
+ }
+}
+
+
+function formatColesProduct(json: ColesResponse): ProductInfo {
+ return {
+ "productId": String(json.id),
+ "companyName": "Coles",
+ "productName": json.name,
+ "price": json.pricing.now,
+ "accessDate": String(Date.now()),
+ "picture": json.imageUris[0]?.uri
+ }
+} \ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 674339b..3008e35 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,4 @@
+import { cacheAllPrices, ProductRequest } from "./cron";
/**
* Welcome to Cloudflare Workers!
*
@@ -29,10 +30,27 @@ export interface Env {
// MY_QUEUE: Queue;
//
// Example binding to a D1 Database. Learn more at https://developers.cloudflare.com/workers/platform/bindings/#d1-database-bindings
- // DB: D1Database
+ DB: D1Database
}
export default {
+
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
+ const { pathname } = new URL(request.url);
+ switch (pathname) {
+ case "/api/history":
+ const { results } = await env.DB.prepare(
+ "SELECT * FROM Prices"
+ )
+ .all();
+ return Response.json(results);
+ case "/":
+ return new Response('🀄');
+ default:
+ return new Response("404 💩", { status: 404 })
+ }
+ },
+
// The scheduled handler is invoked at the interval set in our wrangler.toml's
// [[triggers]] configuration.
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
@@ -40,11 +58,30 @@ export default {
// publish to a Queue, query a D1 Database, and much more.
//
// We'll keep it simple and make an API call to a Cloudflare API:
- let resp = await fetch('https://api.cloudflare.com/client/v4/ips');
- let wasSuccessful = resp.ok ? 'success' : 'fail';
+ // let resp = await fetch('https://api.cloudflare.com/client/v4/ips');
+ // let wasSuccessful = resp.ok ? 'success' : 'fail';
+
+
+
+ const products : ProductRequest[] = [
+ {productId: "888140", companyName: "Woolies"},
+ // {productId: "8150288", companyName: "Coles"},
+ {productId: "581170", companyName: "Woolies"},
+ // {productId: "4901345", companyName: "Coles"},
+ // {productId: "165367", companyName: "Woolies"},
+ // {productId: "5178633", companyName: "Coles"},
+ // {productId: "165367", companyName: "Woolies"},
+ // {productId: "5178633", companyName: "Coles"},
+ // {productId: "778171", companyName: "Woolies"},
+ // {productId: "2271188", companyName: "Coles"},
+ // {productId: "829281", companyName: "Woolies"},
+ // {productId: "2236788", companyName: "Coles"},
+ ]
+ const success = await cacheAllPrices(env.DB, products)
+ console.log(`Fetching and caching ${products.length} products. Success: ${success}`)
- // You could store this result in KV, write to a D1 Database, or publish to a Queue.
- // In this template, we'll just log the result:
- console.log(`trigger fired at ${event.cron}: ${wasSuccessful}`);
+ // // You could store this result in KV, write to a D1 Database, or publish to a Queue.
+ // // In this template, we'll just log the result:
+ // console.log(`trigger fired at ${event.cron}: ${ok}`);
},
};
diff --git a/src/store.ts b/src/store.ts
new file mode 100644
index 0000000..9e98254
--- /dev/null
+++ b/src/store.ts
@@ -0,0 +1,10 @@
+import { ProductInfo } from "./gather";
+
+export async function storePriceRecord(db: D1Database, product: ProductInfo): Promise<boolean> {
+ const query = 'INSERT INTO Prices (CompanyName, ProductName, Price, AccessDate, Picture) VALUES (?1, ?2, ?3, ?4, ?5)'
+ const { results, success } = await db.prepare(query)
+ .bind(product.companyName, product.productName, product.price, product.accessDate, product.picture)
+ .run();
+ console.log(results, success)
+ return success
+} \ No newline at end of file