blocket.js 1.2.1 → 1.3.0
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/dist/client/index.js +8 -30
- package/dist/client/index.js.map +1 -1
- package/dist/client/request.js +8 -3
- package/dist/client/request.js.map +1 -1
- package/dist/client/token.js +9 -1
- package/dist/client/token.js.map +1 -1
- package/dist/types/config.d.ts +9 -4
- package/dist/types/index.d.ts +33 -12
- package/lib/client/index.ts +5 -10
- package/lib/client/request.ts +15 -3
- package/lib/client/token.ts +10 -1
- package/lib/types/config.ts +9 -4
- package/lib/types/index.ts +33 -12
- package/package.json +6 -2
package/dist/client/index.js
CHANGED
|
@@ -8,13 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
-
var m = o[Symbol.asyncIterator], i;
|
|
14
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
-
};
|
|
18
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
12
|
exports.find = find;
|
|
20
13
|
exports.findById = findById;
|
|
@@ -34,6 +27,7 @@ function remapQueryParams(params) {
|
|
|
34
27
|
status: 'status',
|
|
35
28
|
geolocation: 'gl',
|
|
36
29
|
include: 'include',
|
|
30
|
+
page: 'page',
|
|
37
31
|
};
|
|
38
32
|
const remapped = {};
|
|
39
33
|
for (const key in params) {
|
|
@@ -55,7 +49,6 @@ function remapQueryParams(params) {
|
|
|
55
49
|
*/
|
|
56
50
|
function find(query, fetchOptions) {
|
|
57
51
|
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
-
var _a, e_1, _b, _c;
|
|
59
52
|
if (!query.query)
|
|
60
53
|
throw new Error('Query string is required');
|
|
61
54
|
const config = (0, config_1.getBaseConfig)();
|
|
@@ -72,31 +65,16 @@ function find(query, fetchOptions) {
|
|
|
72
65
|
}
|
|
73
66
|
const allAds = [...firstPageResponse.data];
|
|
74
67
|
const totalPages = firstPageResponse.total_page_count;
|
|
75
|
-
|
|
68
|
+
// Optimized pagination: Direct page parameter requests without delay
|
|
69
|
+
for (let page = 2; page <= totalPages; page++) {
|
|
76
70
|
const pageParams = Object.assign(Object.assign({}, params), { page });
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for (var _d = true, pagePromises_1 = __asyncValues(pagePromises), pagePromises_1_1; pagePromises_1_1 = yield pagePromises_1.next(), _a = pagePromises_1_1.done, !_a; _d = true) {
|
|
81
|
-
_c = pagePromises_1_1.value;
|
|
82
|
-
_d = false;
|
|
83
|
-
const response = _c;
|
|
84
|
-
// Prevent rate limiting by adding a delay between requests
|
|
85
|
-
yield new Promise((resolve) => setTimeout(resolve, 200));
|
|
86
|
-
if (response && response.data && Array.isArray(response.data)) {
|
|
87
|
-
allAds.push(...response.data);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
throw new Error(`Unexpected Blocket API response structure in paginated results, expected array of ads`);
|
|
91
|
-
}
|
|
71
|
+
const response = yield (0, request_1.apiRequest)(config.apiBaseUrl, Object.assign({ query: pageParams }, fetchOptions));
|
|
72
|
+
if (response && response.data && Array.isArray(response.data)) {
|
|
73
|
+
allAds.push(...response.data);
|
|
92
74
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
finally {
|
|
96
|
-
try {
|
|
97
|
-
if (!_d && !_a && (_b = pagePromises_1.return)) yield _b.call(pagePromises_1);
|
|
75
|
+
else {
|
|
76
|
+
throw new Error(`Unexpected Blocket API response structure in paginated results, expected array of ads`);
|
|
98
77
|
}
|
|
99
|
-
finally { if (e_1) throw e_1.error; }
|
|
100
78
|
}
|
|
101
79
|
return allAds;
|
|
102
80
|
});
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/client/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/client/index.ts"],"names":[],"mappings":"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"}
|
package/dist/client/request.js
CHANGED
|
@@ -26,11 +26,16 @@ function apiRequest(url_1) {
|
|
|
26
26
|
const config = (0, config_1.getBaseConfig)();
|
|
27
27
|
const token = yield (0, token_1.fetchToken)();
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const response = yield (0, ofetch_1.ofetch)(url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({ 'Accept': 'application/json', 'Accept-Language': 'sv-SE,sv;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Referer': 'https://www.blocket.se/' }, options.headers), { Authorization: `Bearer ${token}` }) }));
|
|
30
|
+
return response;
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// Enhanced error handling for HTTP status codes
|
|
34
|
+
if ((error === null || error === void 0 ? void 0 : error.status) >= 400) {
|
|
35
|
+
(0, config_1.logger)('error', `HTTP ${error.status}: ${error.statusText || 'Request failed'}`);
|
|
36
|
+
}
|
|
37
|
+
if (((error === null || error === void 0 ? void 0 : error.status) === 401 || ((_a = error === null || error === void 0 ? void 0 : error.data) === null || _a === void 0 ? void 0 : _a.status_code) === 401) && retryCount < config.retryAttempts) {
|
|
38
|
+
(0, config_1.logger)('info', `Token expired (${error.status}). Retrying request (${retryCount + 1}/${config.retryAttempts}).`);
|
|
34
39
|
const newToken = yield (0, token_1.fetchToken)(true);
|
|
35
40
|
(0, token_1.setCachedToken)(newToken);
|
|
36
41
|
return apiRequest(url, options, retryCount + 1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../lib/client/request.ts"],"names":[],"mappings":";;;;;;;;;;;AAYA,
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../lib/client/request.ts"],"names":[],"mappings":";;;;;;;;;;;AAYA,gCA0CC;AAtDD,mCAAmD;AAEnD,mCAAqD;AACrD,sCAAkD;AAElD;;;;;;GAMG;AACH,SAAsB,UAAU;yDAC9B,GAAW,EACX,UAAqC,EAAE,EACvC,aAAqB,CAAC;;QAEtB,MAAM,MAAM,GAAG,IAAA,sBAAa,GAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,IAAA,kBAAU,GAAE,CAAC;QAEjC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAA,eAAM,EAAI,GAAG,kCAC/B,OAAO,KACV,OAAO,gCACL,QAAQ,EAAE,kBAAkB,EAC5B,iBAAiB,EAAE,yBAAyB,EAC5C,iBAAiB,EAAE,mBAAmB,EACtC,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,yBAAyB,IACjC,OAAO,CAAC,OAAO,KAClB,aAAa,EAAE,UAAU,KAAK,EAAE,OAElC,CAAC;YAEH,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,gDAAgD;YAChD,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,MAAM,KAAI,GAAG,EAAE,CAAC;gBACzB,IAAA,eAAM,EAAC,OAAO,EAAE,QAAQ,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,IAAI,gBAAgB,EAAE,CAAC,CAAC;YACnF,CAAC;YAED,IAAI,CAAC,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,MAAM,MAAK,GAAG,IAAI,CAAA,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,0CAAE,WAAW,MAAK,GAAG,CAAC,IAAI,UAAU,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;gBACrG,IAAA,eAAM,EACJ,MAAM,EACN,kBAAkB,KAAK,CAAC,MAAM,wBAAwB,UAAU,GAAG,CAAC,IAClE,MAAM,CAAC,aACT,IAAI,CACL,CAAC;gBACF,MAAM,QAAQ,GAAG,MAAM,IAAA,kBAAU,EAAC,IAAI,CAAC,CAAC;gBACxC,IAAA,sBAAc,EAAC,QAAQ,CAAC,CAAC;gBACzB,OAAO,UAAU,CAAI,GAAG,EAAE,OAAO,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CAAA"}
|
package/dist/client/token.js
CHANGED
|
@@ -37,7 +37,15 @@ const fetchToken = (...args_1) => __awaiter(void 0, [...args_1], void 0, functio
|
|
|
37
37
|
return cachedToken;
|
|
38
38
|
(0, config_1.logger)('debug', 'Fetching new Blocket API token.');
|
|
39
39
|
const config = (0, config_1.getBaseConfig)();
|
|
40
|
-
const tokenData = yield (0, ofetch_1.ofetch)(config.tokenEndpoint
|
|
40
|
+
const tokenData = yield (0, ofetch_1.ofetch)(config.tokenEndpoint, {
|
|
41
|
+
headers: {
|
|
42
|
+
'Accept': 'application/json',
|
|
43
|
+
'Accept-Language': 'sv-SE,sv;q=0.9,en;q=0.8',
|
|
44
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
45
|
+
'Connection': 'keep-alive',
|
|
46
|
+
'Referer': 'https://www.blocket.se/',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
41
49
|
if (!tokenData || !tokenData.bearerToken) {
|
|
42
50
|
throw new Error('Failed to retrieve Blocket API token.');
|
|
43
51
|
}
|
package/dist/client/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../lib/client/token.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mCAAgC;AAEhC,sCAAkD;AAIlD,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC;;;GAGG;AACI,MAAM,cAAc,GAAG,GAAkB,EAAE,CAAC,WAAW,CAAC;AAAlD,QAAA,cAAc,kBAAoC;AAE/D;;;GAGG;AACI,MAAM,cAAc,GAAG,CAAC,KAAa,EAAQ,EAAE;IACpD,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC,CAAC;AAFW,QAAA,cAAc,kBAEzB;AAEF;;;;GAIG;AACI,MAAM,UAAU,GAAG,YAEP,EAAE,mDADnB,eAAwB,KAAK;IAE7B,IAAI,CAAC,YAAY,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAErD,IAAA,eAAM,EAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;IAEnD,MAAM,MAAM,GAAG,IAAA,sBAAa,GAAE,CAAC;IAE/B,MAAM,SAAS,GAAG,MAAM,IAAA,eAAM,EAAqB,MAAM,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../lib/client/token.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mCAAgC;AAEhC,sCAAkD;AAIlD,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC;;;GAGG;AACI,MAAM,cAAc,GAAG,GAAkB,EAAE,CAAC,WAAW,CAAC;AAAlD,QAAA,cAAc,kBAAoC;AAE/D;;;GAGG;AACI,MAAM,cAAc,GAAG,CAAC,KAAa,EAAQ,EAAE;IACpD,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC,CAAC;AAFW,QAAA,cAAc,kBAEzB;AAEF;;;;GAIG;AACI,MAAM,UAAU,GAAG,YAEP,EAAE,mDADnB,eAAwB,KAAK;IAE7B,IAAI,CAAC,YAAY,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAErD,IAAA,eAAM,EAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;IAEnD,MAAM,MAAM,GAAG,IAAA,sBAAa,GAAE,CAAC;IAE/B,MAAM,SAAS,GAAG,MAAM,IAAA,eAAM,EAAqB,MAAM,CAAC,aAAa,EAAE;QACvE,OAAO,EAAE;YACP,QAAQ,EAAE,kBAAkB;YAC5B,iBAAiB,EAAE,yBAAyB;YAC5C,iBAAiB,EAAE,mBAAmB;YACtC,YAAY,EAAE,YAAY;YAC1B,SAAS,EAAE,yBAAyB;SACrC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;IACpC,OAAO,WAAW,CAAC;AACrB,CAAC,CAAA,CAAC;AAzBW,QAAA,UAAU,cAyBrB"}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface BlocketQueryConfig {
|
|
|
43
43
|
/**
|
|
44
44
|
* The sorting order of the results.
|
|
45
45
|
*/
|
|
46
|
-
sort?: 'rel';
|
|
46
|
+
sort?: 'rel' | 'date' | 'price_asc' | 'price_desc';
|
|
47
47
|
/**
|
|
48
48
|
* The type of listing to search for. `s` for selling, `b` for buying, `a` for all.
|
|
49
49
|
* @default 's'
|
|
@@ -53,7 +53,7 @@ export interface BlocketQueryConfig {
|
|
|
53
53
|
* The status of the ad. `active`, `inactive`, or `all`.
|
|
54
54
|
* @default 'active'
|
|
55
55
|
*/
|
|
56
|
-
status?: 'active' | 'deleted' | 'hidden_by_user';
|
|
56
|
+
status?: 'active' | 'deleted' | 'hidden_by_user' | 'all';
|
|
57
57
|
/**
|
|
58
58
|
* The maximum distance in kilometers from the search location.
|
|
59
59
|
*/
|
|
@@ -62,6 +62,10 @@ export interface BlocketQueryConfig {
|
|
|
62
62
|
* Additional filters or fields to include in the response.
|
|
63
63
|
*/
|
|
64
64
|
include?: 'extend_with_shipping' | string;
|
|
65
|
+
/**
|
|
66
|
+
* Page number for pagination (starts from 1).
|
|
67
|
+
*/
|
|
68
|
+
page?: number;
|
|
65
69
|
}
|
|
66
70
|
/**
|
|
67
71
|
* Native Blocket query parameters for API requests.
|
|
@@ -69,9 +73,10 @@ export interface BlocketQueryConfig {
|
|
|
69
73
|
export interface BlocketQueryParamsNative {
|
|
70
74
|
q: string;
|
|
71
75
|
lim?: number;
|
|
72
|
-
sort?: 'rel';
|
|
76
|
+
sort?: 'rel' | 'date' | 'price_asc' | 'price_desc';
|
|
73
77
|
st?: 's' | 'b' | 'a';
|
|
74
|
-
status?: 'active' | 'deleted' | 'hidden_by_user';
|
|
78
|
+
status?: 'active' | 'deleted' | 'hidden_by_user' | 'all';
|
|
75
79
|
gl?: number;
|
|
76
80
|
include?: string;
|
|
81
|
+
page?: number;
|
|
77
82
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -38,25 +38,46 @@ export interface BlocketAd {
|
|
|
38
38
|
ad_id: string;
|
|
39
39
|
ad_status: 'active' | 'inactive' | string;
|
|
40
40
|
advertiser: {
|
|
41
|
-
account_id
|
|
42
|
-
contact_methods:
|
|
41
|
+
account_id?: string;
|
|
42
|
+
contact_methods: {
|
|
43
|
+
phone: boolean;
|
|
44
|
+
sms: boolean;
|
|
45
|
+
};
|
|
43
46
|
name: string;
|
|
44
|
-
public_profile
|
|
45
|
-
|
|
47
|
+
public_profile?: Record<string, any>;
|
|
48
|
+
store_name?: string;
|
|
49
|
+
type: 'private' | 'business' | 'store';
|
|
46
50
|
};
|
|
47
51
|
body: string;
|
|
48
|
-
category:
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
category: Array<{
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
}>;
|
|
56
|
+
co2_text?: string;
|
|
57
|
+
images: Array<{
|
|
58
|
+
height: number;
|
|
59
|
+
type: string;
|
|
60
|
+
url: string;
|
|
61
|
+
width: number;
|
|
62
|
+
}>;
|
|
63
|
+
infopage?: {
|
|
64
|
+
text: string;
|
|
65
|
+
url: string;
|
|
66
|
+
};
|
|
51
67
|
list_id: string;
|
|
52
68
|
list_time: string;
|
|
53
|
-
location:
|
|
69
|
+
location: Array<{
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
parent_id?: string;
|
|
73
|
+
}>;
|
|
54
74
|
map_url: string;
|
|
55
|
-
parameter_groups
|
|
56
|
-
parameters_raw
|
|
57
|
-
is_shipping_buy_now_enabled
|
|
58
|
-
shipping_enabled
|
|
75
|
+
parameter_groups?: Record<string, any>[];
|
|
76
|
+
parameters_raw?: {
|
|
77
|
+
is_shipping_buy_now_enabled?: Record<string, any>;
|
|
78
|
+
shipping_enabled?: Record<string, any>;
|
|
59
79
|
};
|
|
80
|
+
partner_info?: any;
|
|
60
81
|
price: {
|
|
61
82
|
suffix: string;
|
|
62
83
|
value: number;
|
package/lib/client/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ function remapQueryParams(
|
|
|
29
29
|
status: 'status',
|
|
30
30
|
geolocation: 'gl',
|
|
31
31
|
include: 'include',
|
|
32
|
+
page: 'page',
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const remapped: Partial<BlocketQueryParamsNative> = {};
|
|
@@ -88,23 +89,17 @@ export async function find(
|
|
|
88
89
|
const allAds = [...firstPageResponse.data];
|
|
89
90
|
const totalPages = firstPageResponse.total_page_count;
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(_, i) => i + 2
|
|
94
|
-
).map((page) => {
|
|
92
|
+
// Optimized pagination: Direct page parameter requests without delay
|
|
93
|
+
for (let page = 2; page <= totalPages; page++) {
|
|
95
94
|
const pageParams = {
|
|
96
95
|
...params,
|
|
97
96
|
page,
|
|
98
97
|
};
|
|
99
|
-
|
|
98
|
+
|
|
99
|
+
const response = await apiRequest<BlocketApiResponse>(config.apiBaseUrl, {
|
|
100
100
|
query: pageParams,
|
|
101
101
|
...fetchOptions,
|
|
102
102
|
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
for await (const response of pagePromises) {
|
|
106
|
-
// Prevent rate limiting by adding a delay between requests
|
|
107
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
108
103
|
|
|
109
104
|
if (response && response.data && Array.isArray(response.data)) {
|
|
110
105
|
allAds.push(...response.data);
|
package/lib/client/request.ts
CHANGED
|
@@ -19,18 +19,30 @@ export async function apiRequest<T>(
|
|
|
19
19
|
const token = await fetchToken();
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
|
-
|
|
22
|
+
const response = await ofetch<T>(url, {
|
|
23
23
|
...options,
|
|
24
24
|
headers: {
|
|
25
|
+
'Accept': 'application/json',
|
|
26
|
+
'Accept-Language': 'sv-SE,sv;q=0.9,en;q=0.8',
|
|
27
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
28
|
+
'Connection': 'keep-alive',
|
|
29
|
+
'Referer': 'https://www.blocket.se/',
|
|
25
30
|
...options.headers,
|
|
26
31
|
Authorization: `Bearer ${token}`,
|
|
27
32
|
},
|
|
28
33
|
});
|
|
34
|
+
|
|
35
|
+
return response;
|
|
29
36
|
} catch (error: any) {
|
|
30
|
-
|
|
37
|
+
// Enhanced error handling for HTTP status codes
|
|
38
|
+
if (error?.status >= 400) {
|
|
39
|
+
logger('error', `HTTP ${error.status}: ${error.statusText || 'Request failed'}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if ((error?.status === 401 || error?.data?.status_code === 401) && retryCount < config.retryAttempts) {
|
|
31
43
|
logger(
|
|
32
44
|
'info',
|
|
33
|
-
`Token expired. Retrying request (${retryCount + 1}/${
|
|
45
|
+
`Token expired (${error.status}). Retrying request (${retryCount + 1}/${
|
|
34
46
|
config.retryAttempts
|
|
35
47
|
}).`
|
|
36
48
|
);
|
package/lib/client/token.ts
CHANGED
|
@@ -34,7 +34,16 @@ export const fetchToken = async (
|
|
|
34
34
|
|
|
35
35
|
const config = getBaseConfig();
|
|
36
36
|
|
|
37
|
-
const tokenData = await ofetch<BlocketAccessToken>(config.tokenEndpoint
|
|
37
|
+
const tokenData = await ofetch<BlocketAccessToken>(config.tokenEndpoint, {
|
|
38
|
+
headers: {
|
|
39
|
+
'Accept': 'application/json',
|
|
40
|
+
'Accept-Language': 'sv-SE,sv;q=0.9,en;q=0.8',
|
|
41
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
42
|
+
'Connection': 'keep-alive',
|
|
43
|
+
'Referer': 'https://www.blocket.se/',
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
38
47
|
if (!tokenData || !tokenData.bearerToken) {
|
|
39
48
|
throw new Error('Failed to retrieve Blocket API token.');
|
|
40
49
|
}
|
package/lib/types/config.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface BlocketQueryConfig {
|
|
|
44
44
|
/**
|
|
45
45
|
* The sorting order of the results.
|
|
46
46
|
*/
|
|
47
|
-
sort?: 'rel';
|
|
47
|
+
sort?: 'rel' | 'date' | 'price_asc' | 'price_desc';
|
|
48
48
|
/**
|
|
49
49
|
* The type of listing to search for. `s` for selling, `b` for buying, `a` for all.
|
|
50
50
|
* @default 's'
|
|
@@ -54,7 +54,7 @@ export interface BlocketQueryConfig {
|
|
|
54
54
|
* The status of the ad. `active`, `inactive`, or `all`.
|
|
55
55
|
* @default 'active'
|
|
56
56
|
*/
|
|
57
|
-
status?: 'active' | 'deleted' | 'hidden_by_user';
|
|
57
|
+
status?: 'active' | 'deleted' | 'hidden_by_user' | 'all';
|
|
58
58
|
/**
|
|
59
59
|
* The maximum distance in kilometers from the search location.
|
|
60
60
|
*/
|
|
@@ -63,6 +63,10 @@ export interface BlocketQueryConfig {
|
|
|
63
63
|
* Additional filters or fields to include in the response.
|
|
64
64
|
*/
|
|
65
65
|
include?: 'extend_with_shipping' | string;
|
|
66
|
+
/**
|
|
67
|
+
* Page number for pagination (starts from 1).
|
|
68
|
+
*/
|
|
69
|
+
page?: number;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
/**
|
|
@@ -71,9 +75,10 @@ export interface BlocketQueryConfig {
|
|
|
71
75
|
export interface BlocketQueryParamsNative {
|
|
72
76
|
q: string;
|
|
73
77
|
lim?: number;
|
|
74
|
-
sort?: 'rel';
|
|
78
|
+
sort?: 'rel' | 'date' | 'price_asc' | 'price_desc';
|
|
75
79
|
st?: 's' | 'b' | 'a';
|
|
76
|
-
status?: 'active' | 'deleted' | 'hidden_by_user';
|
|
80
|
+
status?: 'active' | 'deleted' | 'hidden_by_user' | 'all';
|
|
77
81
|
gl?: number;
|
|
78
82
|
include?: string;
|
|
83
|
+
page?: number;
|
|
79
84
|
}
|
package/lib/types/index.ts
CHANGED
|
@@ -42,25 +42,46 @@ export interface BlocketAd {
|
|
|
42
42
|
ad_id: string;
|
|
43
43
|
ad_status: 'active' | 'inactive' | string;
|
|
44
44
|
advertiser: {
|
|
45
|
-
account_id
|
|
46
|
-
contact_methods:
|
|
45
|
+
account_id?: string;
|
|
46
|
+
contact_methods: {
|
|
47
|
+
phone: boolean;
|
|
48
|
+
sms: boolean;
|
|
49
|
+
};
|
|
47
50
|
name: string;
|
|
48
|
-
public_profile
|
|
49
|
-
|
|
51
|
+
public_profile?: Record<string, any>;
|
|
52
|
+
store_name?: string; // For business listings
|
|
53
|
+
type: 'private' | 'business' | 'store';
|
|
50
54
|
};
|
|
51
55
|
body: string;
|
|
52
|
-
category:
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
category: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
}>;
|
|
60
|
+
co2_text?: string;
|
|
61
|
+
images: Array<{
|
|
62
|
+
height: number;
|
|
63
|
+
type: string;
|
|
64
|
+
url: string;
|
|
65
|
+
width: number;
|
|
66
|
+
}>;
|
|
67
|
+
infopage?: {
|
|
68
|
+
text: string;
|
|
69
|
+
url: string;
|
|
70
|
+
}; // External links for businesses
|
|
55
71
|
list_id: string;
|
|
56
72
|
list_time: string; // ISO date string
|
|
57
|
-
location:
|
|
73
|
+
location: Array<{
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
parent_id?: string;
|
|
77
|
+
}>;
|
|
58
78
|
map_url: string;
|
|
59
|
-
parameter_groups
|
|
60
|
-
parameters_raw
|
|
61
|
-
is_shipping_buy_now_enabled
|
|
62
|
-
shipping_enabled
|
|
79
|
+
parameter_groups?: Record<string, any>[];
|
|
80
|
+
parameters_raw?: {
|
|
81
|
+
is_shipping_buy_now_enabled?: Record<string, any>;
|
|
82
|
+
shipping_enabled?: Record<string, any>;
|
|
63
83
|
};
|
|
84
|
+
partner_info?: any;
|
|
64
85
|
price: {
|
|
65
86
|
suffix: string;
|
|
66
87
|
value: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blocket.js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A user-friendly js wrapper for blocket.se",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"blocket",
|
|
@@ -40,7 +40,11 @@
|
|
|
40
40
|
"build"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"
|
|
43
|
+
"@playwright/test": "^1.55.0",
|
|
44
|
+
"@types/node": "^24.5.0",
|
|
45
|
+
"ofetch": "^1.4.1",
|
|
46
|
+
"playwright": "^1.55.0",
|
|
47
|
+
"ts-node": "^10.9.2"
|
|
44
48
|
},
|
|
45
49
|
"devDependencies": {
|
|
46
50
|
"pre-push": "^0.1.4",
|