initial commit
This commit is contained in:
commit
5afe932247
11 changed files with 6667 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
.vscode
|
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import js from "@eslint/js";
|
||||
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ["dist"]
|
||||
},
|
||||
{
|
||||
...js.configs.recommended,
|
||||
...eslintPluginPrettierRecommended,
|
||||
rules: {
|
||||
"no-unused-vars": "warn",
|
||||
"no-undef": "warn",
|
||||
},
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
l: "readonly",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
49
lib/countries.js
Normal file
49
lib/countries.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
export const COUNTRIES = [
|
||||
{ countryCode: "ww", currencyCode: "usd" },
|
||||
{ countryCode: "ca", currencyCode: "cad" },
|
||||
// { countryCode: "us", currencyCode: "usd" },
|
||||
{ countryCode: "in", currencyCode: "inr" },
|
||||
// { countryCode: "za", currencyCode: "zar" },
|
||||
// { countryCode: "au", currencyCode: "aud" },
|
||||
// { countryCode: "hk", currencyCode: "hkd" },
|
||||
// { countryCode: "jp", currencyCode: "EUR" },
|
||||
// { countryCode: "my", currencyCode: "EUR" },
|
||||
// { countryCode: "nz", currencyCode: "EUR" },
|
||||
// { countryCode: "ph", currencyCode: "EUR" },
|
||||
// { countryCode: "sg", currencyCode: "EUR" },
|
||||
// { countryCode: "tw", currencyCode: "EUR" },
|
||||
// { countryCode: "th", currencyCode: "EUR" },
|
||||
// { countryCode: "at", currencyCode: "EUR" },
|
||||
// { countryCode: "be", currencyCode: "EUR" },
|
||||
// { countryCode: "cy", currencyCode: "EUR" },
|
||||
// { countryCode: "cz", currencyCode: "EUR" },
|
||||
// { countryCode: "dk", currencyCode: "EUR" },
|
||||
// { countryCode: "ee", currencyCode: "EUR" },
|
||||
// { countryCode: "fi", currencyCode: "EUR" },
|
||||
// { countryCode: "fr", currencyCode: "EUR" },
|
||||
// { countryCode: "de", currencyCode: "EUR" },
|
||||
// { countryCode: "gr", currencyCode: "EUR" },
|
||||
// { countryCode: "ie", currencyCode: "EUR" },
|
||||
// { countryCode: "it", currencyCode: "EUR" },
|
||||
// { countryCode: "lt", currencyCode: "EUR" },
|
||||
// { countryCode: "lu", currencyCode: "EUR" },
|
||||
// { countryCode: "mt", currencyCode: "EUR" },
|
||||
// { countryCode: "nl", currencyCode: "EUR" },
|
||||
// { countryCode: "no", currencyCode: "EUR" },
|
||||
// { countryCode: "pl", currencyCode: "EUR" },
|
||||
// { countryCode: "pt", currencyCode: "EUR" },
|
||||
// { countryCode: "ro", currencyCode: "EUR" },
|
||||
// { countryCode: "sk", currencyCode: "EUR" },
|
||||
// { countryCode: "si", currencyCode: "EUR" },
|
||||
// { countryCode: "es", currencyCode: "EUR" },
|
||||
// { countryCode: "se", currencyCode: "EUR" },
|
||||
// { countryCode: "ch", currencyCode: "EUR" },
|
||||
// { countryCode: "tr", currencyCode: "EUR" },
|
||||
// { countryCode: "gb", currencyCode: "EUR" },
|
||||
// { countryCode: "ar", currencyCode: "EUR" },
|
||||
// { countryCode: "br", currencyCode: "EUR" },
|
||||
// { countryCode: "cl", currencyCode: "EUR" },
|
||||
// { countryCode: "co", currencyCode: "EUR" },
|
||||
// { countryCode: "mx", currencyCode: "EUR" },
|
||||
// { countryCode: "pe", currencyCode: "EUR" },
|
||||
];
|
20
lib/currency.js
Normal file
20
lib/currency.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import currency from "currency.js";
|
||||
|
||||
export const baseCurrency = "usd";
|
||||
|
||||
export const convertCurrency = async (price, outCurrency, rates) => {
|
||||
return currency(price).divide(rates[baseCurrency][outCurrency]);
|
||||
};
|
||||
|
||||
export const loadCurrencyRates = async () => {
|
||||
l("loading currency rates");
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/${baseCurrency}.json`,
|
||||
);
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
l("error", e);
|
||||
return null;
|
||||
}
|
||||
};
|
56
lib/getPriceForCountry.js
Normal file
56
lib/getPriceForCountry.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
const timeout = (duration) => new Promise((r) => setTimeout(r, duration));
|
||||
|
||||
const observePriceOnPage = (page) =>
|
||||
new Promise((res, rej) => {
|
||||
timeout(10000).then(() => rej(""));
|
||||
|
||||
var observer = new MutationObserver(() => {
|
||||
const price = page
|
||||
.querySelector(".active-price")
|
||||
.querySelector("span").textContent;
|
||||
if (price) {
|
||||
observer.disconnect();
|
||||
res(price);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(page, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
});
|
||||
});
|
||||
|
||||
const bookUrlForCountry = (country) => {
|
||||
const urlPattern = /^https:\/\/www\.kobo\.com\/../;
|
||||
const newPath = `https://www.kobo.com/${country}`;
|
||||
|
||||
return window.location.href.replace(urlPattern, newPath);
|
||||
};
|
||||
|
||||
export const getPriceForCountry = (country) =>
|
||||
new Promise((res) => {
|
||||
const url = bookUrlForCountry(country);
|
||||
|
||||
l("going to", url);
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
|
||||
iframe.src = url;
|
||||
|
||||
iframe.hidden = true;
|
||||
|
||||
document.body.append(iframe);
|
||||
|
||||
iframe.contentWindow.onload = () => {
|
||||
l("starting observing price on", url);
|
||||
observePriceOnPage(iframe.contentDocument.body, url)
|
||||
.then(res)
|
||||
.catch(() => {
|
||||
l(`failed to find price for ${url}`);
|
||||
res("");
|
||||
})
|
||||
.finally(() => document.body.removeChild(iframe));
|
||||
};
|
||||
});
|
39
lib/index.js
Normal file
39
lib/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import './logger'
|
||||
import { COUNTRIES } from "./countries";
|
||||
import { getPriceForCountry } from "./getPriceForCountry";
|
||||
import { loadCurrencyRates, convertCurrency } from "./currency";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const rates = await loadCurrencyRates();
|
||||
|
||||
l('currency rates', rates)
|
||||
|
||||
const prices = await Promise.all(
|
||||
COUNTRIES.map(async (c) => {
|
||||
l("looking price for", c.countryCode);
|
||||
const originalPrice = await getPriceForCountry(c.countryCode);
|
||||
const convertedPrice = await convertCurrency(
|
||||
originalPrice,
|
||||
c.currencyCode,
|
||||
rates,
|
||||
);
|
||||
|
||||
return { ...c, originalPrice, convertedPrice };
|
||||
}),
|
||||
);
|
||||
|
||||
l(prices);
|
||||
} catch (e) {
|
||||
l("error", e);
|
||||
} finally {
|
||||
l("done");
|
||||
}
|
||||
}
|
||||
|
||||
l("starting...");
|
||||
|
||||
window.onload = () => {
|
||||
l("page is fully loaded");
|
||||
void main();
|
||||
};
|
1
lib/logger.js
Normal file
1
lib/logger.js
Normal file
|
@ -0,0 +1 @@
|
|||
globalThis.l = (...m) => console.log("KOBOPRICE", ...m);
|
17
manifest.json
Normal file
17
manifest.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "Kobo Price",
|
||||
"description": "Lowest book price finder",
|
||||
"version": "1.0",
|
||||
"manifest_version": 3,
|
||||
"permissions": ["activeTab"],
|
||||
"content_scripts": [
|
||||
{
|
||||
"js": ["dist/index.js"],
|
||||
"run_at": "document_end",
|
||||
"matches": [
|
||||
"https://www.kobo.com/*/*/ebook/*",
|
||||
"https://www.kobo.com/*/*/audiobook/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
6422
package-lock.json
generated
Normal file
6422
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
31
package.json
Normal file
31
package.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "koboprice",
|
||||
"version": "1.0.0",
|
||||
"description": "find lowest price on Kobo",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "eslint --fix",
|
||||
"build": "esbuild lib/index.js --bundle --minify --sourcemap --target=chrome58,firefox57,safari11,edge16 --outfile=dist/index.js",
|
||||
"watch-build": "npm run build -- --watch",
|
||||
"watch-ext": "web-ext run",
|
||||
"watch": "concurrently npm:watch-build npm:watch-ext"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.6.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"esbuild": "0.23.0",
|
||||
"eslint": "^9.6.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"globals": "^15.8.0",
|
||||
"prettier": "3.3.2",
|
||||
"web-ext": "^8.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"currency.js": "^2.0.4"
|
||||
}
|
||||
}
|
6
prettierrc.json
Normal file
6
prettierrc.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
Loading…
Reference in a new issue