tesla-inventory 1.8.47 → 2.0.2
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/package.json +14 -13
- package/{codes.json → src/codes.json} +0 -0
- package/src/index.js +52 -0
- package/src/inventories/euro.js +20 -0
- package/src/inventories/index.js +26 -0
- package/src/prices/cad.json +11 -0
- package/{prices/europe.json → src/prices/eur.json} +0 -0
- package/{prices/america.json → src/prices/usd.json} +0 -0
- package/index.js +0 -37
- package/inventories.js +0 -26
- package/scripts/codes.js +0 -35
- package/scripts/prices/america.js +0 -14
- package/scripts/prices/europe.js +0 -16
- package/scripts/prices/index.js +0 -76
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"name": "tesla-inventory",
|
|
3
3
|
"description": "Retrieve real-time data from Tesla Inventory.",
|
|
4
4
|
"homepage": "https://nicedoc.io/Kikobeats/tesla-inventory",
|
|
5
|
-
"version": "
|
|
6
|
-
"main": "index.js",
|
|
5
|
+
"version": "2.0.2",
|
|
6
|
+
"main": "src/index.js",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "josefrancisco.verdu@gmail.com",
|
|
9
9
|
"name": "Kiko Beats",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"json-future": "latest",
|
|
53
53
|
"lodash": "latest",
|
|
54
54
|
"markdown-tables-to-json": "latest",
|
|
55
|
+
"meow": "9",
|
|
55
56
|
"nano-staged": "latest",
|
|
56
57
|
"npm-check-updates": "latest",
|
|
57
58
|
"prettier-standard": "latest",
|
|
@@ -64,25 +65,15 @@
|
|
|
64
65
|
"node": ">= 14"
|
|
65
66
|
},
|
|
66
67
|
"files": [
|
|
67
|
-
"
|
|
68
|
-
"index.js",
|
|
69
|
-
"inventories.js",
|
|
70
|
-
"prices",
|
|
71
|
-
"scripts"
|
|
68
|
+
"src"
|
|
72
69
|
],
|
|
73
70
|
"scripts": {
|
|
74
71
|
"clean": "rm -rf node_modules",
|
|
75
|
-
"codes": "(node scripts/codes.js && git add codes.json && git commit -m 'build(update): codes' --no-verify && git push) || true",
|
|
76
72
|
"contributors": "(git-authors-cli && finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
77
|
-
"cronjob": "npm run prices && npm run codes && npm run healthcheck",
|
|
78
|
-
"healthcheck": "curl -sL https://hc-ping.com/b0792794-6548-42d5-9981-b50a401d7667",
|
|
79
73
|
"lint": "standard-markdown README.md && standard",
|
|
80
74
|
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
81
75
|
"prerelease": "npm run update:check && npm run contributors",
|
|
82
76
|
"pretest": "npm run lint",
|
|
83
|
-
"prices": "npm run prices:europe && npm run prices:america",
|
|
84
|
-
"prices:america": "(DEBUG=tesla-inventory* node scripts/prices/america.js && git add prices/america.json && git commit -m 'build(update): prices for North America' --no-verify && git push) || true",
|
|
85
|
-
"prices:europe": "(DEBUG=tesla-inventory* node scripts/prices/europe.js && git add prices/europe.json && git commit -m 'build(update): prices for Europe' --no-verify && git push) || true",
|
|
86
77
|
"release": "standard-version -a",
|
|
87
78
|
"release:github": "conventional-github-releaser -p angular",
|
|
88
79
|
"release:tags": "git push --follow-tags origin HEAD:master",
|
|
@@ -97,6 +88,16 @@
|
|
|
97
88
|
"@commitlint/config-conventional"
|
|
98
89
|
]
|
|
99
90
|
},
|
|
91
|
+
"exports": {
|
|
92
|
+
".": "./src/index.js",
|
|
93
|
+
"./codes": "./src/codes.json",
|
|
94
|
+
"./inventories": "./src/inventories/index.js",
|
|
95
|
+
"./inventories/euro": "./src/inventories/euro.js",
|
|
96
|
+
"./package.json": "./package.json",
|
|
97
|
+
"./prices/cad": "./src/prices/cad.json",
|
|
98
|
+
"./prices/eur": "./src/prices/eur.json",
|
|
99
|
+
"./prices/usd": "./src/prices/usd.json"
|
|
100
|
+
},
|
|
100
101
|
"nano-staged": {
|
|
101
102
|
"*.js": [
|
|
102
103
|
"prettier-standard"
|
|
File without changes
|
package/src/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const inventories = require('./inventories')
|
|
4
|
+
|
|
5
|
+
const got = require('got').extend({
|
|
6
|
+
url: 'https://www.tesla.com/inventory/api/v1/inventory-results',
|
|
7
|
+
responseType: 'json',
|
|
8
|
+
resolveBodyOnly: true
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const toLowerCase = ({ results: items, total_matches_found: totalMatchesFound }) => ({
|
|
12
|
+
items,
|
|
13
|
+
total: Number(totalMatchesFound)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const ITEMS_PER_PAGE = 50
|
|
17
|
+
|
|
18
|
+
module.exports = async (inventory, opts, { headers, ...gotOpts } = {}) => {
|
|
19
|
+
if (!inventories[inventory]) {
|
|
20
|
+
throw new TypeError(`Tesla inventory \`${inventory}\` not found!`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const inventoryProps = inventories[inventory]
|
|
24
|
+
|
|
25
|
+
if (opts.model && !opts.model.startsWith('m')) {
|
|
26
|
+
opts.model = `m${opts.model}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const paginate = (outsideOffset = 0) =>
|
|
30
|
+
got({
|
|
31
|
+
searchParams: {
|
|
32
|
+
query: JSON.stringify({
|
|
33
|
+
outsideOffset,
|
|
34
|
+
count: 0,
|
|
35
|
+
query: {
|
|
36
|
+
...inventoryProps,
|
|
37
|
+
...opts
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
...gotOpts,
|
|
42
|
+
headers: { 'user-agent': undefined, ...headers }
|
|
43
|
+
}).then(toLowerCase)
|
|
44
|
+
|
|
45
|
+
const page = await paginate()
|
|
46
|
+
if (page.total < ITEMS_PER_PAGE) return page.items
|
|
47
|
+
|
|
48
|
+
const nRequests = Math.ceil(page.total / ITEMS_PER_PAGE) - 1
|
|
49
|
+
const offsets = [...Array(nRequests).keys()].map(n => (n + 1) * page.items.length)
|
|
50
|
+
const pages = await Promise.all(offsets.map(paginate))
|
|
51
|
+
return pages.reduce((acc, { items }) => acc.concat(items), page.items)
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const EURO_COUNTRIES = [
|
|
4
|
+
'Österreich',
|
|
5
|
+
'Belgium',
|
|
6
|
+
'Deutschland',
|
|
7
|
+
'Spain',
|
|
8
|
+
'Finland',
|
|
9
|
+
'France',
|
|
10
|
+
'Ireland',
|
|
11
|
+
'Italy',
|
|
12
|
+
'Luxembourg',
|
|
13
|
+
'Netherlands',
|
|
14
|
+
'Portugal',
|
|
15
|
+
'România'
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
module.exports = Object.fromEntries(
|
|
19
|
+
Object.entries(require('.')).filter(([, { country }]) => EURO_COUNTRIES.includes(country))
|
|
20
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
at: { country: 'Österreich', language: 'de', market: 'AT', super_region: 'europe' },
|
|
3
|
+
be: { country: 'Belgium', language: 'nl', market: 'BE', super_region: 'europe' },
|
|
4
|
+
ca: { country: 'Canada', language: 'en', market: 'CA', super_region: 'north america' },
|
|
5
|
+
ch: { country: 'Switzerland', language: 'de', market: 'CH', super_region: 'europe' },
|
|
6
|
+
cz: { country: 'Česko', language: 'cs', market: 'CZ', super_region: 'europe' },
|
|
7
|
+
de: { country: 'Deutschland', language: 'de', market: 'DE', super_region: 'europe' },
|
|
8
|
+
dk: { country: 'Danmark', language: 'da', market: 'DK', super_region: 'europe' },
|
|
9
|
+
es: { country: 'Spain', language: 'es', market: 'ES', super_region: 'europe' },
|
|
10
|
+
fi: { country: 'Finland', language: 'fi', market: 'FI', super_region: 'europe' },
|
|
11
|
+
fr: { country: 'France', language: 'fr', market: 'FR', super_region: 'europe' },
|
|
12
|
+
gb: { country: 'United Kingdom', language: 'en', market: 'GB', super_region: 'europe' },
|
|
13
|
+
hu: { country: 'Magyarország', language: 'hu', market: 'HU', super_region: 'europe' },
|
|
14
|
+
ie: { country: 'Ireland', language: 'en', market: 'IE', super_region: 'europe' },
|
|
15
|
+
is: { country: 'Island', language: 'is', market: 'IS', super_region: 'europe' },
|
|
16
|
+
it: { country: 'Italy', language: 'it', market: 'IT', super_region: 'europe' },
|
|
17
|
+
lu: { country: 'Luxembourg', language: 'fr', market: 'LU', super_region: 'europe' },
|
|
18
|
+
mx: { country: 'Mexico', language: 'es', market: 'MX', super_region: 'north america' },
|
|
19
|
+
nl: { country: 'Netherlands', language: 'nl', market: 'NL', super_region: 'europe' },
|
|
20
|
+
no: { country: 'Norway', language: 'no', market: 'NO', super_region: 'europe' },
|
|
21
|
+
pr: { country: 'Puerto Rico', language: 'es', market: 'PR', super_region: 'north america' },
|
|
22
|
+
pt: { country: 'Portugal', language: 'pt', market: 'PT', super_region: 'europe' },
|
|
23
|
+
ro: { country: 'România', language: 'ro', market: 'RO', super_region: 'europe' },
|
|
24
|
+
se: { country: 'Sweden', language: 'sv', market: 'SE', super_region: 'europe' },
|
|
25
|
+
us: { country: 'United States', language: 'en', market: 'US', super_region: 'north america' }
|
|
26
|
+
}
|
|
File without changes
|
|
File without changes
|
package/index.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const got = require('got')
|
|
4
|
-
|
|
5
|
-
const inventories = require('./inventories')
|
|
6
|
-
|
|
7
|
-
const TESLA_INVENTORY_API =
|
|
8
|
-
'https://www.tesla.com/inventory/api/v1/inventory-results'
|
|
9
|
-
|
|
10
|
-
module.exports = async (inventory, opts, { headers, ...gotOpts } = {}) => {
|
|
11
|
-
const inventoryProps = inventories[inventory]
|
|
12
|
-
|
|
13
|
-
if (!inventoryProps) {
|
|
14
|
-
throw new TypeError(`Tesla inventory \`${inventory}\` not found!`)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (opts.model && !opts.model.startsWith('m')) {
|
|
18
|
-
opts.model = `m${opts.model}`
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const { body } = await got(TESLA_INVENTORY_API, {
|
|
22
|
-
responseType: 'json',
|
|
23
|
-
searchParams: {
|
|
24
|
-
query: JSON.stringify({
|
|
25
|
-
count: 0,
|
|
26
|
-
query: {
|
|
27
|
-
...inventoryProps,
|
|
28
|
-
...opts
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
},
|
|
32
|
-
...gotOpts,
|
|
33
|
-
headers: { 'user-agent': undefined, ...headers }
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
return body.results.filter(result => result.Model === opts.model)
|
|
37
|
-
}
|
package/inventories.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
at: { country: 'Österreich', language: 'de', market: 'AT', region: 'Europe' },
|
|
3
|
-
be: { country: 'Belgium', language: 'nl', market: 'BE', region: 'Europe' },
|
|
4
|
-
ca: { country: 'Canada', language: 'en', market: 'CA', region: 'North America' },
|
|
5
|
-
ch: { country: 'Switzerland', language: 'de', market: 'CH', region: 'Europe' },
|
|
6
|
-
cz: { country: 'Česko', language: 'cs', market: 'CZ', region: 'Europe' },
|
|
7
|
-
de: { country: 'Deutschland', language: 'de', market: 'DE', region: 'Europe' },
|
|
8
|
-
dk: { country: 'Danmark', language: 'da', market: 'DK', region: 'Europe' },
|
|
9
|
-
es: { country: 'Spain', language: 'es', market: 'ES', region: 'Europe' },
|
|
10
|
-
fi: { country: 'Finland', language: 'fi', market: 'FI', region: 'Europe' },
|
|
11
|
-
fr: { country: 'France', language: 'fr', market: 'FR', region: 'Europe' },
|
|
12
|
-
gb: { country: 'United Kingdom', language: 'en', market: 'GB', region: 'Europe' },
|
|
13
|
-
hu: { country: 'Magyarország', language: 'hu', market: 'HU', region: 'Europe' },
|
|
14
|
-
ie: { country: 'Ireland', language: 'en', market: 'IE', region: 'Europe' },
|
|
15
|
-
is: { country: 'Island', language: 'is', market: 'IS', region: 'Europe' },
|
|
16
|
-
it: { country: 'Italy', language: 'it', market: 'IT', region: 'Europe' },
|
|
17
|
-
lu: { country: 'Luxembourg', language: 'fr', market: 'LU', region: 'Europe' },
|
|
18
|
-
mx: { country: 'Mexico', language: 'es', market: 'MX', region: 'North America' },
|
|
19
|
-
nl: { country: 'Netherlands', language: 'nl', market: 'NL', region: 'Europe' },
|
|
20
|
-
no: { country: 'Norway', language: 'no', market: 'NO', region: 'Europe' },
|
|
21
|
-
pr: { country: 'Puerto Rico', language: 'es', market: 'PR', region: 'North America' },
|
|
22
|
-
pt: { country: 'Portugal', language: 'pt', market: 'PT', region: 'Europe' },
|
|
23
|
-
ro: { country: 'România', language: 'ro', market: 'RO', region: 'Europe' },
|
|
24
|
-
se: { country: 'Sweden', language: 'sv', market: 'SE', region: 'Europe' },
|
|
25
|
-
us: { country: 'United States', language: 'en', market: 'US', region: 'North America' }
|
|
26
|
-
}
|
package/scripts/codes.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { Extractor } = require('markdown-tables-to-json')
|
|
4
|
-
const { decodeHTML } = require('entities')
|
|
5
|
-
|
|
6
|
-
const jsonFuture = require('json-future')
|
|
7
|
-
const { chain } = require('lodash')
|
|
8
|
-
const got = require('got')
|
|
9
|
-
|
|
10
|
-
const sortObjectByKey = obj =>
|
|
11
|
-
chain(obj)
|
|
12
|
-
.toPairs()
|
|
13
|
-
.sortBy(0)
|
|
14
|
-
.fromPairs()
|
|
15
|
-
.value()
|
|
16
|
-
|
|
17
|
-
const main = async () => {
|
|
18
|
-
const markdown = await got(
|
|
19
|
-
'https://raw.githubusercontent.com/timdorr/tesla-api/master/docs/vehicle/optioncodes.md',
|
|
20
|
-
{ resolveBodyOnly: true }
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
const json = Extractor.extractObject(markdown, 'rows', false)
|
|
24
|
-
|
|
25
|
-
const optionCodes = Object.keys(json).reduce((acc, code) => {
|
|
26
|
-
const { Title: title, Description: description } = json[code]
|
|
27
|
-
return { ...acc, [code]: decodeHTML(title || description) }
|
|
28
|
-
}, {})
|
|
29
|
-
|
|
30
|
-
jsonFuture.save('codes.json', sortObjectByKey(optionCodes))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
main()
|
|
34
|
-
.catch(err => console.error(err) && process.exit(1))
|
|
35
|
-
.then(process.exit)
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const path = require('path')
|
|
4
|
-
|
|
5
|
-
const inventories = Object.fromEntries(
|
|
6
|
-
Object.entries(require('../../inventories')).filter(
|
|
7
|
-
([, { region }]) => region === 'North America'
|
|
8
|
-
)
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
require('.')({
|
|
12
|
-
filepath: path.resolve(__dirname, '../../prices/america.json'),
|
|
13
|
-
inventories
|
|
14
|
-
})
|
package/scripts/prices/europe.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const path = require('path')
|
|
4
|
-
|
|
5
|
-
const NON_EURO_COUNTRIES = ['cz', 'se', 'dk', 'hu', 'is', 'ch', 'gb', 'no']
|
|
6
|
-
|
|
7
|
-
const inventories = Object.fromEntries(
|
|
8
|
-
Object.entries(require('../../inventories')).filter(
|
|
9
|
-
([code, { region }]) => region === 'Europe' && !NON_EURO_COUNTRIES.includes(code)
|
|
10
|
-
)
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
require('.')({
|
|
14
|
-
filepath: path.resolve(__dirname, '../../prices/europe.json'),
|
|
15
|
-
inventories
|
|
16
|
-
})
|
package/scripts/prices/index.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const debug = require('debug-logfmt')('tesla-inventory:price')
|
|
4
|
-
const jsonFuture = require('json-future')
|
|
5
|
-
const { chain } = require('lodash')
|
|
6
|
-
|
|
7
|
-
const teslaInventory = require('../..')
|
|
8
|
-
|
|
9
|
-
const GOT_OPTS = {
|
|
10
|
-
headers: {
|
|
11
|
-
'user-agent': 'googlebot'
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const MODEL_LETTER = ['s', '3', 'x', 'y']
|
|
16
|
-
|
|
17
|
-
const MODEL_CONDITION = ['used', 'new']
|
|
18
|
-
|
|
19
|
-
const sortObjectByKey = obj =>
|
|
20
|
-
chain(obj)
|
|
21
|
-
.toPairs()
|
|
22
|
-
.sortBy(0)
|
|
23
|
-
.fromPairs()
|
|
24
|
-
.value()
|
|
25
|
-
|
|
26
|
-
const run = async ({ pricesByCode, inventories }) => {
|
|
27
|
-
const addItem = item => {
|
|
28
|
-
if (item.price) {
|
|
29
|
-
const trimCode = item.code.replace('$', '')
|
|
30
|
-
if (!trimCode.startsWith('MDL')) {
|
|
31
|
-
if (!pricesByCode[trimCode] || pricesByCode[trimCode] > item.price) {
|
|
32
|
-
debug('adding', { code: trimCode, price: item.price })
|
|
33
|
-
pricesByCode[trimCode] = item.price
|
|
34
|
-
debug(item)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (const inventoryCode in inventories) {
|
|
41
|
-
for (const model of MODEL_LETTER) {
|
|
42
|
-
for (const condition of MODEL_CONDITION) {
|
|
43
|
-
try {
|
|
44
|
-
const results = await teslaInventory(
|
|
45
|
-
inventoryCode,
|
|
46
|
-
{
|
|
47
|
-
model,
|
|
48
|
-
condition
|
|
49
|
-
},
|
|
50
|
-
GOT_OPTS
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
debug({ inventoryCode, model, condition })
|
|
54
|
-
|
|
55
|
-
results.forEach(result => {
|
|
56
|
-
result.FlexibleOptionsData.forEach(addItem)
|
|
57
|
-
result.OptionCodeData.forEach(addItem)
|
|
58
|
-
})
|
|
59
|
-
} catch (err) {
|
|
60
|
-
debug.error(err.message || err, { inventoryCode, model, condition })
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return pricesByCode
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
module.exports = ({ inventories, filepath }) => {
|
|
70
|
-
const pricesByCode = require(filepath)
|
|
71
|
-
|
|
72
|
-
run({ pricesByCode, inventories }).then(data => {
|
|
73
|
-
jsonFuture.save(filepath, sortObjectByKey(data))
|
|
74
|
-
process.exit()
|
|
75
|
-
})
|
|
76
|
-
}
|