amaprice 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,6 +49,8 @@ amaprice list
49
49
  - piped stdin (`echo "<url-or-asin>" | amaprice price`)
50
50
  - interactive prompt (run command without argument)
51
51
 
52
+ Short links from Amazon apps (for example `amzn.eu`, `amzn.to`, `a.co`) are accepted and resolved automatically.
53
+
52
54
  ## Commands
53
55
 
54
56
  | Command | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amaprice",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "CLI tool to scrape and track Amazon product prices",
5
5
  "main": "src/scraper.js",
6
6
  "type": "commonjs",
@@ -1,4 +1,4 @@
1
- const { extractAsin } = require('../url');
1
+ const { extractAsin, normalizeAmazonInput } = require('../url');
2
2
  const { getPriceHistory } = require('../db');
3
3
  const { formatPrice } = require('../format');
4
4
 
@@ -9,7 +9,11 @@ module.exports = function (program) {
9
9
  .option('--limit <n>', 'Number of entries to show', '30')
10
10
  .option('--json', 'Output as JSON')
11
11
  .action(async (urlOrAsin, opts) => {
12
- const asin = extractAsin(urlOrAsin);
12
+ let asin = extractAsin(urlOrAsin);
13
+ if (!asin) {
14
+ const normalized = await normalizeAmazonInput(urlOrAsin);
15
+ asin = normalized?.asin ?? null;
16
+ }
13
17
  if (!asin) {
14
18
  console.error('Error: Could not extract ASIN from input.');
15
19
  process.exit(1);
@@ -11,7 +11,7 @@ module.exports = function (program) {
11
11
  .option('--json', 'Output as JSON')
12
12
  .action(async (inputParts, opts) => {
13
13
  const input = await resolveCliInput(inputParts);
14
- const normalized = normalizeAmazonInput(input);
14
+ const normalized = await normalizeAmazonInput(input);
15
15
  if (!normalized) {
16
16
  console.error('Error: Input must be an Amazon product URL or a valid ASIN.');
17
17
  process.exit(1);
@@ -1,4 +1,4 @@
1
- const { extractAsin } = require('../url');
1
+ const { extractAsin, normalizeAmazonInput } = require('../url');
2
2
  const { getProductByAsin, updateProductByAsin } = require('../db');
3
3
  const { normalizeTier, computeNextScrapeAt } = require('../tiering');
4
4
 
@@ -12,7 +12,11 @@ module.exports = function (program) {
12
12
  .option('--deactivate', 'Disable background sync for this product')
13
13
  .option('--json', 'Output as JSON')
14
14
  .action(async (urlOrAsin, tierArg, opts) => {
15
- const asin = extractAsin(urlOrAsin);
15
+ let asin = extractAsin(urlOrAsin);
16
+ if (!asin) {
17
+ const normalized = await normalizeAmazonInput(urlOrAsin);
18
+ asin = normalized?.asin ?? null;
19
+ }
16
20
  if (!asin) {
17
21
  console.error('Error: Could not extract ASIN from input.');
18
22
  process.exit(1);
@@ -72,4 +76,3 @@ module.exports = function (program) {
72
76
  }
73
77
  });
74
78
  };
75
-
@@ -15,7 +15,7 @@ module.exports = function (program) {
15
15
  .option('--json', 'Output as JSON')
16
16
  .action(async (inputParts, opts) => {
17
17
  const input = await resolveCliInput(inputParts);
18
- const normalized = normalizeAmazonInput(input);
18
+ const normalized = await normalizeAmazonInput(input);
19
19
  if (!normalized) {
20
20
  console.error('Error: Input must be an Amazon product URL or a valid ASIN.');
21
21
  process.exit(1);
package/src/url.js CHANGED
@@ -3,6 +3,7 @@ const AMAZON_DOMAINS = [
3
3
  'amazon.it', 'amazon.es', 'amazon.nl', 'amazon.co.jp',
4
4
  'amazon.ca', 'amazon.com.au', 'amazon.in', 'amazon.com.br',
5
5
  ];
6
+ const AMAZON_SHORT_DOMAINS = ['amzn.eu', 'amzn.to', 'a.co'];
6
7
 
7
8
  const ASIN_REGEX = /(?:\/(?:dp|gp\/product|ASIN)\/)([A-Z0-9]{10})/i;
8
9
 
@@ -15,6 +16,16 @@ function isAmazonUrl(url) {
15
16
  }
16
17
  }
17
18
 
19
+ function isAmazonShortUrl(url) {
20
+ try {
21
+ const parsed = new URL(url);
22
+ const hostname = parsed.hostname.replace(/^www\./, '');
23
+ return AMAZON_SHORT_DOMAINS.includes(hostname);
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
18
29
  function extractAsin(urlOrAsin) {
19
30
  // If it's already a bare ASIN (10 alphanumeric chars)
20
31
  if (/^[A-Z0-9]{10}$/i.test(urlOrAsin)) {
@@ -37,7 +48,47 @@ function canonicalUrl(asin, domain = 'amazon.de') {
37
48
  return `https://www.${domain}/dp/${asin}`;
38
49
  }
39
50
 
40
- function normalizeAmazonInput(input, defaultDomain = 'amazon.de') {
51
+ async function resolveAmazonShortUrl(url, maxRedirects = 8) {
52
+ if (!isAmazonShortUrl(url)) return url;
53
+
54
+ let current = url;
55
+ for (let i = 0; i < maxRedirects; i += 1) {
56
+ let response;
57
+ try {
58
+ response = await fetch(current, {
59
+ method: 'GET',
60
+ redirect: 'manual',
61
+ });
62
+ } catch {
63
+ return current;
64
+ }
65
+
66
+ const location = response.headers?.get ? response.headers.get('location') : null;
67
+ try {
68
+ await response.body?.cancel?.();
69
+ } catch {
70
+ // Ignore body cancellation failures.
71
+ }
72
+
73
+ if (!location || response.status < 300 || response.status > 399) {
74
+ return current;
75
+ }
76
+
77
+ try {
78
+ current = new URL(location, current).toString();
79
+ } catch {
80
+ return current;
81
+ }
82
+
83
+ if (isAmazonUrl(current) && extractAsin(current)) {
84
+ return current;
85
+ }
86
+ }
87
+
88
+ return current;
89
+ }
90
+
91
+ async function normalizeAmazonInput(input, defaultDomain = 'amazon.de') {
41
92
  const raw = String(input || '').trim();
42
93
  if (!raw) return null;
43
94
 
@@ -51,14 +102,28 @@ function normalizeAmazonInput(input, defaultDomain = 'amazon.de') {
51
102
  };
52
103
  }
53
104
 
105
+ const resolved = await resolveAmazonShortUrl(raw);
106
+ const resolvedAsin = extractAsin(resolved);
107
+ if (resolvedAsin) {
108
+ const domain = isAmazonUrl(resolved) ? extractDomain(resolved) : defaultDomain;
109
+ return {
110
+ asin: resolvedAsin,
111
+ domain,
112
+ url: canonicalUrl(resolvedAsin, domain),
113
+ };
114
+ }
115
+
54
116
  return null;
55
117
  }
56
118
 
57
119
  module.exports = {
58
120
  isAmazonUrl,
121
+ isAmazonShortUrl,
59
122
  extractAsin,
60
123
  extractDomain,
61
124
  canonicalUrl,
125
+ resolveAmazonShortUrl,
62
126
  normalizeAmazonInput,
63
127
  AMAZON_DOMAINS,
128
+ AMAZON_SHORT_DOMAINS,
64
129
  };