vantuz 3.3.0 → 3.3.1
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/core/eia-monitor.js +14 -6
- package/core/scrapers/TrendyolScraper.js +74 -19
- package/package.json +91 -91
- package/plugins/vantuz/platforms/_request.js +34 -0
- package/plugins/vantuz/platforms/amazon.js +59 -35
- package/plugins/vantuz/platforms/ciceksepeti.js +14 -9
- package/plugins/vantuz/platforms/hepsiburada.js +14 -9
- package/plugins/vantuz/platforms/index.js +74 -54
- package/plugins/vantuz/platforms/n11.js +16 -8
- package/plugins/vantuz/platforms/pazarama.js +30 -17
- package/plugins/vantuz/platforms/pttavm.js +14 -9
- package/plugins/vantuz/platforms/trendyol.js +16 -11
package/core/eia-monitor.js
CHANGED
|
@@ -17,8 +17,9 @@ class EIAMonitor {
|
|
|
17
17
|
this.productUrls = this._parseUrls(this.env.EIA_PRODUCT_URLS);
|
|
18
18
|
this.competitorUrls = this._parseUrls(this.env.EIA_COMPETITOR_URLS);
|
|
19
19
|
this.targetProfitMargin = parseFloat(this.env.EIA_TARGET_PROFIT_MARGIN || '15'); // Default 15%
|
|
20
|
-
this.
|
|
21
|
-
|
|
20
|
+
this.baselineData = {};
|
|
21
|
+
this.scraperFactories = {
|
|
22
|
+
trendyol: () => new TrendyolScraper() // Initialize other scrapers here
|
|
22
23
|
};
|
|
23
24
|
log('INFO', 'EIA Monitor initialized');
|
|
24
25
|
}
|
|
@@ -101,9 +102,9 @@ class EIAMonitor {
|
|
|
101
102
|
*/
|
|
102
103
|
async startMonitoring(productId, platform, productUrl, dataType, cronSchedule) {
|
|
103
104
|
const jobName = `monitor-${platform}-${productId}-${dataType}`;
|
|
104
|
-
const
|
|
105
|
+
const createScraper = this.scraperFactories[platform];
|
|
105
106
|
|
|
106
|
-
if (!
|
|
107
|
+
if (!createScraper) {
|
|
107
108
|
log('ERROR', `No scraper found for platform: ${platform}`);
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
@@ -111,6 +112,7 @@ class EIAMonitor {
|
|
|
111
112
|
const task = async () => {
|
|
112
113
|
log('INFO', `Monitoring ${dataType} for product ${productId} on ${platform}`);
|
|
113
114
|
let rawData;
|
|
115
|
+
const scraper = createScraper();
|
|
114
116
|
try {
|
|
115
117
|
// Scrape Data
|
|
116
118
|
switch (dataType) {
|
|
@@ -144,6 +146,12 @@ class EIAMonitor {
|
|
|
144
146
|
|
|
145
147
|
} catch (error) {
|
|
146
148
|
log('ERROR', `Error during monitoring job ${jobName}: ${error.message}`, { error });
|
|
149
|
+
} finally {
|
|
150
|
+
try {
|
|
151
|
+
await scraper.close();
|
|
152
|
+
} catch (e) {
|
|
153
|
+
log('WARN', `Failed to close scraper for ${jobName}: ${e.message}`);
|
|
154
|
+
}
|
|
147
155
|
}
|
|
148
156
|
};
|
|
149
157
|
|
|
@@ -165,10 +173,10 @@ class EIAMonitor {
|
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
// Simple example: if a price changes from baseline
|
|
168
|
-
if (dataType === 'price_changes' && currentInsights.currentPrice !== this.baselineData
|
|
176
|
+
if (dataType === 'price_changes' && currentInsights.currentPrice !== this.baselineData[dataType][productId].currentPrice) {
|
|
169
177
|
return {
|
|
170
178
|
type: 'price_change',
|
|
171
|
-
oldPrice: this.baselineData
|
|
179
|
+
oldPrice: this.baselineData[dataType][productId].currentPrice,
|
|
172
180
|
newPrice: currentInsights.currentPrice
|
|
173
181
|
};
|
|
174
182
|
}
|
|
@@ -7,16 +7,34 @@ export class TrendyolScraper extends Scraper {
|
|
|
7
7
|
this.baseUrl = 'https://www.trendyol.com';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
_normalizePrice(text) {
|
|
11
|
+
if (!text) return null;
|
|
12
|
+
const cleaned = text.replace(/[^\d,]/g, '').replace(',', '.');
|
|
13
|
+
const num = parseFloat(cleaned);
|
|
14
|
+
return Number.isFinite(num) ? num : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async _extractFirstText(selectors) {
|
|
18
|
+
for (const selector of selectors) {
|
|
19
|
+
const text = await this.extractText(selector);
|
|
20
|
+
if (text) return text.trim();
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
async getProductPrice(productUrl) {
|
|
11
26
|
await this.init();
|
|
12
27
|
await this.goTo(productUrl);
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
const priceText = await this._extractFirstText([
|
|
30
|
+
'.prc-dsc',
|
|
31
|
+
'.prc-org',
|
|
32
|
+
'.prc-slg',
|
|
33
|
+
'.pr-new-price'
|
|
34
|
+
]);
|
|
17
35
|
|
|
18
36
|
await this.close();
|
|
19
|
-
return
|
|
37
|
+
return this._normalizePrice(priceText);
|
|
20
38
|
}
|
|
21
39
|
|
|
22
40
|
async searchProducts(query) {
|
|
@@ -39,38 +57,75 @@ export class TrendyolScraper extends Scraper {
|
|
|
39
57
|
async getPriceChanges(productUrl) {
|
|
40
58
|
await this.init();
|
|
41
59
|
await this.goTo(productUrl);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
|
|
61
|
+
const priceText = await this._extractFirstText([
|
|
62
|
+
'.prc-dsc',
|
|
63
|
+
'.prc-org',
|
|
64
|
+
'.prc-slg',
|
|
65
|
+
'.pr-new-price'
|
|
66
|
+
]);
|
|
67
|
+
const currentPrice = this._normalizePrice(priceText);
|
|
68
|
+
|
|
45
69
|
await this.close();
|
|
46
|
-
return { productUrl, currentPrice, status: '
|
|
70
|
+
return { productUrl, currentPrice, status: 'ok' };
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
async getBuyboxStatus(productUrl) {
|
|
50
74
|
await this.init();
|
|
51
75
|
await this.goTo(productUrl);
|
|
52
|
-
//
|
|
53
|
-
|
|
76
|
+
// Attempt to read seller name and buybox indicator text.
|
|
77
|
+
const sellerText = await this._extractFirstText([
|
|
78
|
+
'[data-testid="seller-name"]',
|
|
79
|
+
'.seller-name-text',
|
|
80
|
+
'.merchant-name'
|
|
81
|
+
]);
|
|
82
|
+
const buyboxText = await this._extractFirstText([
|
|
83
|
+
'[data-testid="buybox"]',
|
|
84
|
+
'.boutique-name',
|
|
85
|
+
'.buybox'
|
|
86
|
+
]);
|
|
54
87
|
await this.close();
|
|
55
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
productUrl,
|
|
90
|
+
seller: sellerText,
|
|
91
|
+
buybox: buyboxText,
|
|
92
|
+
won: !!sellerText,
|
|
93
|
+
status: 'ok'
|
|
94
|
+
};
|
|
56
95
|
}
|
|
57
96
|
|
|
58
97
|
async getStockMovements(productUrl) {
|
|
59
98
|
await this.init();
|
|
60
99
|
await this.goTo(productUrl);
|
|
61
|
-
|
|
62
|
-
|
|
100
|
+
const stockText = await this._extractFirstText([
|
|
101
|
+
'.out-of-stock',
|
|
102
|
+
'.sold-out',
|
|
103
|
+
'[data-testid="stock-info"]',
|
|
104
|
+
'.product-stock'
|
|
105
|
+
]);
|
|
106
|
+
const inStock = stockText ? !/tükendi|stokta yok|tuken/i.test(stockText) : true;
|
|
63
107
|
await this.close();
|
|
64
|
-
return { productUrl, status: '
|
|
108
|
+
return { productUrl, status: 'ok', inStock, stockText };
|
|
65
109
|
}
|
|
66
110
|
|
|
67
111
|
async getProductReviewsAndRatings(productUrl) {
|
|
68
112
|
await this.init();
|
|
69
113
|
await this.goTo(productUrl);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
114
|
+
|
|
115
|
+
const ratingText = await this._extractFirstText([
|
|
116
|
+
'[data-testid="rating-score"]',
|
|
117
|
+
'.rating-score',
|
|
118
|
+
'.pr-rnr-sm-p'
|
|
119
|
+
]);
|
|
120
|
+
const rating = this._normalizePrice(ratingText);
|
|
121
|
+
|
|
122
|
+
const reviewTexts = await this.extractAllText('.comment-text, .review-text, .rnr-com-tx');
|
|
123
|
+
const reviews = reviewTexts
|
|
124
|
+
.map(t => t?.trim())
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.slice(0, 10);
|
|
127
|
+
|
|
74
128
|
await this.close();
|
|
75
|
-
return { productUrl, reviews, rating, status: '
|
|
129
|
+
return { productUrl, reviews, rating, status: 'ok' };
|
|
76
130
|
}
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vantuz",
|
|
3
|
+
"version": "3.3.1",
|
|
4
|
+
"description": "Yapay Zeka Destekli E-Ticaret Yönetim Platformu - 7 Pazaryeri + WhatsApp/Telegram",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vantuz": "cli.js",
|
|
9
|
+
"vantuz-onboard": "onboard.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "node cli.js tui",
|
|
13
|
+
"tui": "node cli.js tui",
|
|
14
|
+
"config": "node cli.js config",
|
|
15
|
+
"status": "node cli.js status",
|
|
16
|
+
"onboard": "node onboard.js",
|
|
17
|
+
"postinstall": "echo \u0027🐙 Vantuz kuruldu! Başlatmak için: npx vantuz-onboard\u0027",
|
|
18
|
+
"test": "node --test",
|
|
19
|
+
"lint": "eslint plugins/"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"ecommerce",
|
|
23
|
+
"e-ticaret",
|
|
24
|
+
"trendyol",
|
|
25
|
+
"hepsiburada",
|
|
26
|
+
"amazon",
|
|
27
|
+
"n11",
|
|
28
|
+
"ciceksepeti",
|
|
29
|
+
"pttavm",
|
|
30
|
+
"pazarama",
|
|
31
|
+
"marketplace",
|
|
32
|
+
"pazaryeri",
|
|
33
|
+
"ai",
|
|
34
|
+
"yapay-zeka",
|
|
35
|
+
"repricer",
|
|
36
|
+
"fiyatlama",
|
|
37
|
+
"whatsapp",
|
|
38
|
+
"telegram",
|
|
39
|
+
"enterprise",
|
|
40
|
+
"gateway",
|
|
41
|
+
"management",
|
|
42
|
+
"cli"
|
|
43
|
+
],
|
|
44
|
+
"author": {
|
|
45
|
+
"name": "Nuri Can Avşar",
|
|
46
|
+
"email": "nuricanavsar2000@gmail.com"
|
|
47
|
+
},
|
|
48
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/nuricanavsar/vantuz.git"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://vantuz.ai",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/nuricanavsar/vantuz/issues"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"axios": "^1.13.5",
|
|
59
|
+
"cors": "^2.8.6",
|
|
60
|
+
"cron": "^4.4.0",
|
|
61
|
+
"dotenv": "^16.0.0",
|
|
62
|
+
"express": "^5.2.1",
|
|
63
|
+
"openclaw": "^2026.2.9",
|
|
64
|
+
"playwright": "^1.58.2",
|
|
65
|
+
"sqlite3": "^5.1.7",
|
|
66
|
+
"xml2js": "^0.6.2"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"eslint": "^8.0.0"
|
|
70
|
+
},
|
|
71
|
+
"os": [
|
|
72
|
+
"darwin",
|
|
73
|
+
"linux",
|
|
74
|
+
"win32"
|
|
75
|
+
],
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": "\u003e=18.0.0"
|
|
78
|
+
},
|
|
79
|
+
"files": [
|
|
80
|
+
"cli.js",
|
|
81
|
+
"onboard.js",
|
|
82
|
+
"core",
|
|
83
|
+
"server",
|
|
84
|
+
"platforms",
|
|
85
|
+
"plugins",
|
|
86
|
+
".openclaw",
|
|
87
|
+
"LICENSE",
|
|
88
|
+
"README.md",
|
|
89
|
+
"DOCS_TR.md"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// plugins/vantuz/platforms/_request.js
|
|
2
|
+
import { setTimeout as sleep } from 'timers/promises';
|
|
3
|
+
|
|
4
|
+
export async function requestWithRetry(axiosInstance, config, options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
retries = 3,
|
|
7
|
+
baseDelayMs = 500,
|
|
8
|
+
maxDelayMs = 5000,
|
|
9
|
+
retryOnStatuses = [429, 500, 502, 503, 504],
|
|
10
|
+
retryOnNetworkError = true
|
|
11
|
+
} = options;
|
|
12
|
+
|
|
13
|
+
let attempt = 0;
|
|
14
|
+
// eslint-disable-next-line no-constant-condition
|
|
15
|
+
while (true) {
|
|
16
|
+
try {
|
|
17
|
+
return await axiosInstance(config);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const status = error.response?.status;
|
|
20
|
+
const isNetworkError = !error.response;
|
|
21
|
+
const shouldRetry =
|
|
22
|
+
attempt < retries &&
|
|
23
|
+
((retryOnNetworkError && isNetworkError) || retryOnStatuses.includes(status));
|
|
24
|
+
|
|
25
|
+
if (!shouldRetry) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const delay = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt));
|
|
30
|
+
await sleep(delay);
|
|
31
|
+
attempt += 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* developer-docs.amazon.com/sp-api
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import axios from 'axios';
|
|
7
|
-
import crypto from 'crypto';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import { requestWithRetry } from './_request.js';
|
|
8
9
|
|
|
9
10
|
const REGIONS = {
|
|
10
11
|
eu: {
|
|
@@ -29,48 +30,70 @@ export class AmazonAPI {
|
|
|
29
30
|
this.clientSecret = config.clientSecret;
|
|
30
31
|
this.refreshToken = config.refreshToken;
|
|
31
32
|
|
|
32
|
-
this.regionConfig = REGIONS[this.region];
|
|
33
|
-
this.accessToken = null;
|
|
34
|
-
this.tokenExpiry = null;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
this.regionConfig = REGIONS[this.region];
|
|
34
|
+
this.accessToken = null;
|
|
35
|
+
this.tokenExpiry = null;
|
|
36
|
+
this._tokenPromise = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async _getAccessToken() {
|
|
38
40
|
if (this.accessToken && this.tokenExpiry > Date.now()) {
|
|
39
41
|
return this.accessToken;
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const response = await axios.post('https://api.amazon.com/auth/o2/token', {
|
|
44
|
-
grant_type: 'refresh_token',
|
|
45
|
-
refresh_token: this.refreshToken,
|
|
46
|
-
client_id: this.clientId,
|
|
47
|
-
client_secret: this.clientSecret
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
this.accessToken = response.data.access_token;
|
|
51
|
-
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000) - 60000;
|
|
52
|
-
return this.accessToken;
|
|
53
|
-
} catch (error) {
|
|
54
|
-
throw new Error('Token alınamadı: ' + error.message);
|
|
43
|
+
if (this._tokenPromise) {
|
|
44
|
+
return await this._tokenPromise;
|
|
55
45
|
}
|
|
46
|
+
|
|
47
|
+
this._tokenPromise = (async () => {
|
|
48
|
+
try {
|
|
49
|
+
const response = await requestWithRetry(axios, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
url: 'https://api.amazon.com/auth/o2/token',
|
|
52
|
+
data: {
|
|
53
|
+
grant_type: 'refresh_token',
|
|
54
|
+
refresh_token: this.refreshToken,
|
|
55
|
+
client_id: this.clientId,
|
|
56
|
+
client_secret: this.clientSecret
|
|
57
|
+
}
|
|
58
|
+
}, {
|
|
59
|
+
retries: 3,
|
|
60
|
+
baseDelayMs: 500,
|
|
61
|
+
maxDelayMs: 4000
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.accessToken = response.data.access_token;
|
|
65
|
+
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000) - 60000;
|
|
66
|
+
return this.accessToken;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new Error('Token alınamadı: ' + error.message);
|
|
69
|
+
} finally {
|
|
70
|
+
this._tokenPromise = null;
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
return await this._tokenPromise;
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
async _request(method, path, data = null, params = null) {
|
|
59
78
|
try {
|
|
60
79
|
const token = await this._getAccessToken();
|
|
61
|
-
const response = await axios
|
|
62
|
-
method,
|
|
63
|
-
url: `${this.regionConfig.endpoint}${path}`,
|
|
64
|
-
headers: {
|
|
65
|
-
'x-amz-access-token': token,
|
|
66
|
-
'Content-Type': 'application/json'
|
|
67
|
-
},
|
|
68
|
-
data,
|
|
69
|
-
params: {
|
|
70
|
-
...params,
|
|
71
|
-
MarketplaceIds: this.regionConfig.marketplace
|
|
72
|
-
}
|
|
73
|
-
}
|
|
80
|
+
const response = await requestWithRetry(axios, {
|
|
81
|
+
method,
|
|
82
|
+
url: `${this.regionConfig.endpoint}${path}`,
|
|
83
|
+
headers: {
|
|
84
|
+
'x-amz-access-token': token,
|
|
85
|
+
'Content-Type': 'application/json'
|
|
86
|
+
},
|
|
87
|
+
data,
|
|
88
|
+
params: {
|
|
89
|
+
...params,
|
|
90
|
+
MarketplaceIds: this.regionConfig.marketplace
|
|
91
|
+
}
|
|
92
|
+
}, {
|
|
93
|
+
retries: 3,
|
|
94
|
+
baseDelayMs: 500,
|
|
95
|
+
maxDelayMs: 4000
|
|
96
|
+
});
|
|
74
97
|
return { success: true, data: response.data };
|
|
75
98
|
} catch (error) {
|
|
76
99
|
return {
|
|
@@ -237,3 +260,4 @@ export const amazonApi = {
|
|
|
237
260
|
async updatePrice(sku, price, region) { return instances[region]?.updatePrice(sku, price); },
|
|
238
261
|
async getOrders(params, region) { return instances[region]?.getOrders(params); }
|
|
239
262
|
};
|
|
263
|
+
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* bayi.ciceksepeti.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import axios from 'axios';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { requestWithRetry } from './_request.js';
|
|
7
8
|
|
|
8
9
|
const BASE_URL = 'https://apis.ciceksepeti.com/api/v1';
|
|
9
10
|
|
|
@@ -22,14 +23,18 @@ export class CiceksepetiAPI {
|
|
|
22
23
|
|
|
23
24
|
async _request(method, endpoint, data = null, params = null) {
|
|
24
25
|
try {
|
|
25
|
-
const response = await axios
|
|
26
|
-
method,
|
|
27
|
-
url: `${BASE_URL}${endpoint}`,
|
|
28
|
-
headers: this._headers(),
|
|
29
|
-
data,
|
|
30
|
-
params
|
|
31
|
-
}
|
|
32
|
-
|
|
26
|
+
const response = await requestWithRetry(axios, {
|
|
27
|
+
method,
|
|
28
|
+
url: `${BASE_URL}${endpoint}`,
|
|
29
|
+
headers: this._headers(),
|
|
30
|
+
data,
|
|
31
|
+
params
|
|
32
|
+
}, {
|
|
33
|
+
retries: 3,
|
|
34
|
+
baseDelayMs: 500,
|
|
35
|
+
maxDelayMs: 4000
|
|
36
|
+
});
|
|
37
|
+
return { success: true, data: response.data };
|
|
33
38
|
} catch (error) {
|
|
34
39
|
return {
|
|
35
40
|
success: false,
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* developer.hepsiburada.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import axios from 'axios';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { requestWithRetry } from './_request.js';
|
|
7
8
|
|
|
8
9
|
const BASE_URL = 'https://mpop-sit.hepsiburada.com'; // Production: mpop.hepsiburada.com
|
|
9
10
|
const LISTING_URL = 'https://listing-external.hepsiburada.com';
|
|
@@ -27,14 +28,18 @@ export class HepsiburadaAPI {
|
|
|
27
28
|
|
|
28
29
|
async _request(method, url, data = null, params = null) {
|
|
29
30
|
try {
|
|
30
|
-
const response = await axios
|
|
31
|
-
method,
|
|
32
|
-
url,
|
|
33
|
-
headers: this._headers(),
|
|
34
|
-
data,
|
|
35
|
-
params
|
|
36
|
-
}
|
|
37
|
-
|
|
31
|
+
const response = await requestWithRetry(axios, {
|
|
32
|
+
method,
|
|
33
|
+
url,
|
|
34
|
+
headers: this._headers(),
|
|
35
|
+
data,
|
|
36
|
+
params
|
|
37
|
+
}, {
|
|
38
|
+
retries: 3,
|
|
39
|
+
baseDelayMs: 500,
|
|
40
|
+
maxDelayMs: 4000
|
|
41
|
+
});
|
|
42
|
+
return { success: true, data: response.data };
|
|
38
43
|
} catch (error) {
|
|
39
44
|
return {
|
|
40
45
|
success: false,
|
|
@@ -133,68 +133,88 @@ export const platformHub = {
|
|
|
133
133
|
/**
|
|
134
134
|
* Çoklu platform fiyat güncelle
|
|
135
135
|
*/
|
|
136
|
-
async updatePriceMulti(barcode, price, targetPlatforms = 'all') {
|
|
137
|
-
const targets = targetPlatforms === 'all'
|
|
138
|
-
? this.getConnected()
|
|
139
|
-
: targetPlatforms.split(',').map(p => PLATFORM_ALIASES[p.trim()]).filter(Boolean);
|
|
140
|
-
|
|
141
|
-
const results = {};
|
|
142
|
-
|
|
143
|
-
const api = platforms[platform];
|
|
144
|
-
if (api?.isConnected()) {
|
|
145
|
-
results[platform] =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
136
|
+
async updatePriceMulti(barcode, price, targetPlatforms = 'all') {
|
|
137
|
+
const targets = targetPlatforms === 'all'
|
|
138
|
+
? this.getConnected()
|
|
139
|
+
: targetPlatforms.split(',').map(p => PLATFORM_ALIASES[p.trim()]).filter(Boolean);
|
|
140
|
+
|
|
141
|
+
const results = {};
|
|
142
|
+
const tasks = targets.map(async (platform) => {
|
|
143
|
+
const api = platforms[platform];
|
|
144
|
+
if (!api?.isConnected()) {
|
|
145
|
+
results[platform] = { success: false, error: 'Not connected' };
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
results[platform] = await api.updatePrice(barcode, price);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
results[platform] = { success: false, error: error.message };
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
await Promise.allSettled(tasks);
|
|
155
|
+
return results;
|
|
156
|
+
},
|
|
150
157
|
|
|
151
158
|
/**
|
|
152
159
|
* Çoklu platform stok güncelle
|
|
153
160
|
*/
|
|
154
|
-
async updateStockMulti(barcode, quantity, targetPlatforms = 'all') {
|
|
155
|
-
const targets = targetPlatforms === 'all'
|
|
156
|
-
? this.getConnected()
|
|
157
|
-
: targetPlatforms.split(',').map(p => PLATFORM_ALIASES[p.trim()]).filter(Boolean);
|
|
158
|
-
|
|
159
|
-
const results = {};
|
|
160
|
-
|
|
161
|
-
const api = platforms[platform];
|
|
162
|
-
if (api?.isConnected()) {
|
|
163
|
-
results[platform] =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
async updateStockMulti(barcode, quantity, targetPlatforms = 'all') {
|
|
162
|
+
const targets = targetPlatforms === 'all'
|
|
163
|
+
? this.getConnected()
|
|
164
|
+
: targetPlatforms.split(',').map(p => PLATFORM_ALIASES[p.trim()]).filter(Boolean);
|
|
165
|
+
|
|
166
|
+
const results = {};
|
|
167
|
+
const tasks = targets.map(async (platform) => {
|
|
168
|
+
const api = platforms[platform];
|
|
169
|
+
if (!api?.isConnected()) {
|
|
170
|
+
results[platform] = { success: false, error: 'Not connected' };
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
results[platform] = await api.updateStock(barcode, quantity);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
results[platform] = { success: false, error: error.message };
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
await Promise.allSettled(tasks);
|
|
180
|
+
return results;
|
|
181
|
+
},
|
|
168
182
|
|
|
169
183
|
/**
|
|
170
184
|
* Tüm platformlardan sipariş çek
|
|
171
185
|
*/
|
|
172
|
-
async getAllOrders(params = {}) {
|
|
173
|
-
const connected = this.getConnected();
|
|
174
|
-
const allOrders = [];
|
|
175
|
-
|
|
176
|
-
const orderPromises = connected.map(async (platform) => {
|
|
177
|
-
const api = platforms[platform];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
186
|
+
async getAllOrders(params = {}) {
|
|
187
|
+
const connected = this.getConnected();
|
|
188
|
+
const allOrders = [];
|
|
189
|
+
|
|
190
|
+
const orderPromises = connected.map(async (platform) => {
|
|
191
|
+
const api = platforms[platform];
|
|
192
|
+
try {
|
|
193
|
+
const result = await api.getOrders(params);
|
|
194
|
+
if (result?.success) {
|
|
195
|
+
const orders = result.data.content || result.data.orders || result.data || [];
|
|
196
|
+
return orders.map(order => ({
|
|
197
|
+
...order,
|
|
198
|
+
_platform: platform,
|
|
199
|
+
_icon: this.getIcon(platform)
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
return [];
|
|
203
|
+
} catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const results = await Promise.allSettled(orderPromises);
|
|
209
|
+
results.forEach(platformOrders => {
|
|
210
|
+
if (platformOrders.status === 'fulfilled') {
|
|
211
|
+
allOrders.push(...platformOrders.value);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Tarihe göre sırala
|
|
216
|
+
allOrders.sort((a, b) => new Date(b.createdDate || b.orderDate) - new Date(a.createdDate || a.orderDate));
|
|
217
|
+
return allOrders;
|
|
198
218
|
}
|
|
199
219
|
};
|
|
200
220
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* api.n11.com/ws/*.wsdl
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import axios from 'axios';
|
|
7
|
-
import { parseStringPromise, Builder } from 'xml2js';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { parseStringPromise, Builder } from 'xml2js';
|
|
8
|
+
import { requestWithRetry } from './_request.js';
|
|
8
9
|
|
|
9
10
|
const WSDL_URLS = {
|
|
10
11
|
product: 'https://api.n11.com/ws/ProductService.wsdl',
|
|
@@ -50,12 +51,19 @@ export class N11API {
|
|
|
50
51
|
async _soapRequest(service, method, body = '') {
|
|
51
52
|
try {
|
|
52
53
|
const envelope = this._buildSoapEnvelope(service, method, body);
|
|
53
|
-
const response = await axios
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const response = await requestWithRetry(axios, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
url: SOAP_ENDPOINTS[service],
|
|
57
|
+
data: envelope,
|
|
58
|
+
headers: {
|
|
59
|
+
'Content-Type': 'text/xml; charset=utf-8',
|
|
60
|
+
'SOAPAction': `${method}`
|
|
61
|
+
}
|
|
62
|
+
}, {
|
|
63
|
+
retries: 3,
|
|
64
|
+
baseDelayMs: 500,
|
|
65
|
+
maxDelayMs: 4000
|
|
66
|
+
});
|
|
59
67
|
|
|
60
68
|
const result = await parseStringPromise(response.data, { explicitArray: false });
|
|
61
69
|
const bodyKey = Object.keys(result['SOAP-ENV:Envelope']['SOAP-ENV:Body'])[0];
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* sellers.pazarama.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import axios from 'axios';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { requestWithRetry } from './_request.js';
|
|
7
8
|
|
|
8
9
|
const BASE_URL = 'https://isortagimapi.pazarama.com/api';
|
|
9
10
|
const AUTH_URL = 'https://isortagimapi.pazarama.com/oauth/token';
|
|
@@ -22,11 +23,19 @@ export class PazaramaAPI {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
try {
|
|
25
|
-
const response = await axios
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const response = await requestWithRetry(axios, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
url: AUTH_URL,
|
|
29
|
+
data: {
|
|
30
|
+
grant_type: 'client_credentials',
|
|
31
|
+
client_id: this.clientId,
|
|
32
|
+
client_secret: this.clientSecret
|
|
33
|
+
}
|
|
34
|
+
}, {
|
|
35
|
+
retries: 3,
|
|
36
|
+
baseDelayMs: 500,
|
|
37
|
+
maxDelayMs: 4000
|
|
38
|
+
});
|
|
30
39
|
|
|
31
40
|
this.accessToken = response.data.access_token;
|
|
32
41
|
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000) - 60000;
|
|
@@ -39,17 +48,21 @@ export class PazaramaAPI {
|
|
|
39
48
|
async _request(method, endpoint, data = null, params = null) {
|
|
40
49
|
try {
|
|
41
50
|
const token = await this._getAccessToken();
|
|
42
|
-
const response = await axios
|
|
43
|
-
method,
|
|
44
|
-
url: `${BASE_URL}${endpoint}`,
|
|
45
|
-
headers: {
|
|
46
|
-
'Authorization': `Bearer ${token}`,
|
|
47
|
-
'Content-Type': 'application/json'
|
|
48
|
-
},
|
|
49
|
-
data,
|
|
50
|
-
params
|
|
51
|
-
}
|
|
52
|
-
|
|
51
|
+
const response = await requestWithRetry(axios, {
|
|
52
|
+
method,
|
|
53
|
+
url: `${BASE_URL}${endpoint}`,
|
|
54
|
+
headers: {
|
|
55
|
+
'Authorization': `Bearer ${token}`,
|
|
56
|
+
'Content-Type': 'application/json'
|
|
57
|
+
},
|
|
58
|
+
data,
|
|
59
|
+
params
|
|
60
|
+
}, {
|
|
61
|
+
retries: 3,
|
|
62
|
+
baseDelayMs: 500,
|
|
63
|
+
maxDelayMs: 4000
|
|
64
|
+
});
|
|
65
|
+
return { success: true, data: response.data };
|
|
53
66
|
} catch (error) {
|
|
54
67
|
return {
|
|
55
68
|
success: false,
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* pttavm.com/entegrasyon
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import axios from 'axios';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { requestWithRetry } from './_request.js';
|
|
7
8
|
|
|
8
9
|
const BASE_URL = 'https://api.pttavm.com/v1';
|
|
9
10
|
|
|
@@ -24,14 +25,18 @@ export class PttavmAPI {
|
|
|
24
25
|
|
|
25
26
|
async _request(method, endpoint, data = null, params = null) {
|
|
26
27
|
try {
|
|
27
|
-
const response = await axios
|
|
28
|
-
method,
|
|
29
|
-
url: `${BASE_URL}${endpoint}`,
|
|
30
|
-
headers: this._headers(),
|
|
31
|
-
data,
|
|
32
|
-
params
|
|
33
|
-
}
|
|
34
|
-
|
|
28
|
+
const response = await requestWithRetry(axios, {
|
|
29
|
+
method,
|
|
30
|
+
url: `${BASE_URL}${endpoint}`,
|
|
31
|
+
headers: this._headers(),
|
|
32
|
+
data,
|
|
33
|
+
params
|
|
34
|
+
}, {
|
|
35
|
+
retries: 3,
|
|
36
|
+
baseDelayMs: 500,
|
|
37
|
+
maxDelayMs: 4000
|
|
38
|
+
});
|
|
39
|
+
return { success: true, data: response.data };
|
|
35
40
|
} catch (error) {
|
|
36
41
|
return {
|
|
37
42
|
success: false,
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Rate Limit: 50 req / 10 sec per endpoint
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import axios from 'axios';
|
|
12
|
-
import { log } from '../../../core/ai-provider.js';
|
|
11
|
+
import axios from 'axios';
|
|
12
|
+
import { log } from '../../../core/ai-provider.js';
|
|
13
|
+
import { requestWithRetry } from './_request.js';
|
|
13
14
|
|
|
14
15
|
const BASE_URL = 'https://apigw.trendyol.com/integration';
|
|
15
16
|
const STAGE_URL = 'https://stageapigw.trendyol.com/integration';
|
|
@@ -56,15 +57,19 @@ export class TrendyolAPI {
|
|
|
56
57
|
headers['Content-Type'] = 'application/json';
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
const response = await this.client
|
|
60
|
-
method,
|
|
61
|
-
url,
|
|
62
|
-
headers,
|
|
63
|
-
data,
|
|
64
|
-
params,
|
|
65
|
-
timeout: 30000
|
|
66
|
-
}
|
|
67
|
-
|
|
60
|
+
const response = await requestWithRetry(this.client, {
|
|
61
|
+
method,
|
|
62
|
+
url,
|
|
63
|
+
headers,
|
|
64
|
+
data,
|
|
65
|
+
params,
|
|
66
|
+
timeout: 30000
|
|
67
|
+
}, {
|
|
68
|
+
retries: 3,
|
|
69
|
+
baseDelayMs: 500,
|
|
70
|
+
maxDelayMs: 4000
|
|
71
|
+
});
|
|
72
|
+
return { success: true, data: response.data };
|
|
68
73
|
} catch (error) {
|
|
69
74
|
const errorMsg = error.response?.data?.errors?.[0]?.message || error.message;
|
|
70
75
|
const statusCode = error.response?.status;
|