olx-lebanon-sdk 0.3.0 → 0.4.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/CHANGELOG.md +30 -0
- package/README.md +5 -1
- package/dist/account/AccountApi.d.ts +19 -0
- package/dist/account/AccountApi.js +26 -0
- package/dist/account/AccountApi.js.map +1 -0
- package/dist/catalog/OlxCatalog.d.ts +5 -0
- package/dist/catalog/OlxCatalog.js +9 -0
- package/dist/catalog/OlxCatalog.js.map +1 -1
- package/dist/cli/commands/ads.d.ts +2 -0
- package/dist/cli/commands/ads.js +97 -0
- package/dist/cli/commands/ads.js.map +1 -0
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/client.d.ts +6 -0
- package/dist/client.js +14 -2
- package/dist/client.js.map +1 -1
- package/dist/endpoints.d.ts +7 -2
- package/dist/endpoints.js +34 -2
- package/dist/endpoints.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +5 -2
- package/dist/errors.js.map +1 -1
- package/dist/http/HttpClient.d.ts +2 -1
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/listings/AdsApi.d.ts +24 -1
- package/dist/listings/AdsApi.js +43 -0
- package/dist/listings/AdsApi.js.map +1 -1
- package/dist/listings/ListingsApi.d.ts +30 -1
- package/dist/listings/ListingsApi.js +65 -2
- package/dist/listings/ListingsApi.js.map +1 -1
- package/dist/listings/actions.d.ts +8 -0
- package/dist/listings/actions.js +29 -0
- package/dist/listings/actions.js.map +1 -0
- package/dist/listings/adapter.js +5 -1
- package/dist/listings/adapter.js.map +1 -1
- package/dist/listings/olxLebanon.d.ts +3 -0
- package/dist/listings/olxLebanon.js +62 -2
- package/dist/listings/olxLebanon.js.map +1 -1
- package/dist/listings/olxResponses.d.ts +68 -0
- package/dist/listings/olxResponses.js +158 -0
- package/dist/listings/olxResponses.js.map +1 -0
- package/dist/listings/types.d.ts +49 -1
- package/dist/listings/types.js.map +1 -1
- package/dist/mapping/CatalogMapper.d.ts +7 -0
- package/dist/mapping/CatalogMapper.js +19 -2
- package/dist/mapping/CatalogMapper.js.map +1 -1
- package/dist/orders/OrdersApi.d.ts +20 -0
- package/dist/orders/OrdersApi.js +29 -0
- package/dist/orders/OrdersApi.js.map +1 -0
- package/dist/server/app.d.ts +3 -0
- package/dist/server/app.js +40 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/openapi.d.ts +380 -4
- package/dist/server/openapi.js +120 -4
- package/dist/server/openapi.js.map +1 -1
- package/dist/server/queue.d.ts +12 -4
- package/dist/server/queue.js.map +1 -1
- package/dist/server/start.js +18 -5
- package/dist/server/start.js.map +1 -1
- package/dist/sync/SyncService.d.ts +44 -2
- package/dist/sync/SyncService.js +129 -5
- package/dist/sync/SyncService.js.map +1 -1
- package/openapi.json +482 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
The rest of a seller's ad lifecycle, insights, and OLX delivery orders.
|
|
6
|
+
|
|
7
|
+
### Verified live (26 Oct 2026)
|
|
8
|
+
|
|
9
|
+
- Deactivate: the backend `state` turns `inactive` at once, while the website may show `limited`. Deactivating again answers `ad_already_inactive`, which the SDK treats as done.
|
|
10
|
+
- Republish of a deactivated ad brings it back and renews its expiry (to `limited` on an account with no free slot left).
|
|
11
|
+
- Activate: `needs_package` for a limited ad with no free slot left, and `not_limited` for a deactivated one, which comes back with republish instead.
|
|
12
|
+
- Mark as sold: `state` turns `sold` at once; the website's state follows within about 20 seconds.
|
|
13
|
+
- Metrics, wallet, exchange rate and posting-limit offers read correctly. A new ad without a photo is refused by OLX, which is now checked before sending.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `olx.ads`: `deactivate`, `activate` (a `limited` ad with a free slot: `activated`, `needs_package` or `not_limited`), `markSold({ soldOn: 'olx' | 'elsewhere' })`, `republish` (an inactive or expired ad), `rejectionReason`, `metrics` (views and leads per ad), and `actions(ad)` / `adActions()`: what the website offers for an ad in its status.
|
|
18
|
+
- `OlxAd` has `state` (OLX's own state), `promotion` (`featured` / `elite`, and whether it's pending), `rejection` (`soft` / `hard`) and `expiresAt`.
|
|
19
|
+
- `olx.sync`: `deactivate`, `activate`, `markSold`, and `refresh()`, which re-reads every linked ad's status from My ads in one pass. `upsert` on an inactive or expired ad republishes it (`action: 'republished'`).
|
|
20
|
+
- `olx.account`: `profile`, `wallet`, `postingLimitOffers(categoryId)` (read only; the SDK never buys).
|
|
21
|
+
- `olx.orders` (experimental): OLX delivery listings, the orders on an ad, and `update(orderId, 'accept' | 'reject' | 'cancel' | 'out_for_delivery' | 'delivered')`.
|
|
22
|
+
- `olx.catalog`: `exchangeRate()` (USD → LBP) and `deliveryConfig()`, both public.
|
|
23
|
+
- REST: `POST …/listing/deactivate`, `…/activate`, `…/sold`, `GET …/listing/metrics`, `POST /v1/listings/refresh`; job types `deactivate`, `activate`, `mark_sold`, `refresh`.
|
|
24
|
+
- A new ad needs at least one photo, as OLX requires ("The images field is required"). It's checked before sending; a catalog's `limits.minImages: 0` turns the check off.
|
|
25
|
+
|
|
26
|
+
### Changed (breaking)
|
|
27
|
+
|
|
28
|
+
- `AdStatus` has two new values: `inactive` (deactivated) and `sold`. Generic status names like `deactivated` or `disabled` now read as `inactive`, where they read as `deleted` before.
|
|
29
|
+
- OLX's states are read as OLX defines them: `featured` and `elite` are `active` with a `promotion`, and a soft rejection is `rejected` with `rejection: 'soft'`. The backend `state` decides `inactive` and `sold`, since OLX's display state lags behind and can show a deactivated ad as `limited`.
|
|
30
|
+
- A sold product can't be edited: `upsert` fails with `reconcile_required` (`reason: 'ad_sold'`). `forget` it to post a new ad; `forget` no longer needs `force` for a sold or deleted ad.
|
|
31
|
+
- An endpoints file (`OLX_ENDPOINTS_FILE`) now overrides only the entries it lists, so endpoints added in new versions keep working without editing it.
|
|
32
|
+
|
|
3
33
|
## 0.3.0 — 2026-10-26
|
|
4
34
|
|
|
5
35
|
A library you install in a Node.js or Next.js app and call directly.
|
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ console.log(ad.id, ad.status); // "117146151" "active"
|
|
|
34
34
|
|
|
35
35
|
- **Typed client.** Create, edit, delete and list your ads, with TypeScript types and editor docs for everything.
|
|
36
36
|
- **Product sync.** `sync.upsert(productId, ad)` keeps one ad per product: it posts once, edits in place, and skips unchanged products. Links live in your own database through a four-method store.
|
|
37
|
+
- **The whole ad lifecycle.** Deactivate and bring back, republish expired ads, mark as sold, activate `limited` ads with a free slot, and read why an ad was rejected. `ads.actions(ad)` says what each status allows, as the website does.
|
|
38
|
+
- **Insights.** Views and leads per ad (phone, chat, WhatsApp), your wallet, what OLX charges past the free limit, and its USD/LBP rate. OLX delivery orders too (experimental).
|
|
37
39
|
- **No duplicate ads.** Writes are never retried. When OLX's answer is lost, the product is parked for you to check instead of being posted again.
|
|
38
40
|
- **Human pace.** At least a minute between writes and at most 20 an hour by default, so the account behaves like a person.
|
|
39
41
|
- **Catalog lookups.** Categories, each category's fields and choices, and all 662 locations (with Arabic names, offline), to find ids or build your own forms.
|
|
@@ -95,6 +97,7 @@ The same from code: `olx.catalog.findCategories('laptop')`, `olx.catalog.fields(
|
|
|
95
97
|
| `olx-lb token` | Prints the access token for your app's environment variables |
|
|
96
98
|
| `olx-lb check [--product file.json]` | Read-only check of the session, the account, your newest ads, and how a product maps |
|
|
97
99
|
| `olx-lb catalog categories\|fields\|locations` | Category ids, a category's fields and choices, area ids. `--json` for scripts |
|
|
100
|
+
| `olx-lb ads [list\|deactivate\|activate\|sold\|delete\|reason]` | Your ads with status, expiry, views and leads; change one ad's state |
|
|
98
101
|
| `olx-lb test-post create\|edit\|delete` | Walks one test ad through posting, editing and deleting |
|
|
99
102
|
| `olx-lb serve` | Runs the REST service |
|
|
100
103
|
| `olx-lb capture [shapes]` | For maintainers: records the OLX website to follow a change ([guide](docs/ENDPOINT-CAPTURE.md)) |
|
|
@@ -110,7 +113,8 @@ npx olx-lb init && npx olx-lb login && npx olx-lb serve # http://127.0.0.1:87
|
|
|
110
113
|
```bash
|
|
111
114
|
curl -X PUT http://127.0.0.1:8787/v1/products/SKU-1042/listing \
|
|
112
115
|
-H "X-Api-Key: $OLX_SDK_API_KEY" -H "Content-Type: application/json" \
|
|
113
|
-
-d '{"title":"Dell Latitude 5440","description":"16 GB RAM","price":899,"categoryId":"175","locationId":"47",
|
|
116
|
+
-d '{"title":"Dell Latitude 5440","description":"16 GB RAM","price":899,"categoryId":"175","locationId":"47",
|
|
117
|
+
"attributes":{"make":"3","type":"1","new_used":"1"},"images":[{"url":"https://shop.example.com/dell-5440.jpg"}]}'
|
|
114
118
|
# 202 {"jobId":"6f0c…","status":"queued"}
|
|
115
119
|
```
|
|
116
120
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OlxHttpClient } from '../http/HttpClient.js';
|
|
2
|
+
import type { ListingsApi } from '../listings/ListingsApi.js';
|
|
3
|
+
import { type PackageOffer, type Wallet } from '../listings/olxResponses.js';
|
|
4
|
+
import type { AccountInfo } from '../listings/types.js';
|
|
5
|
+
/** Your OLX account: profile, wallet and package offers. Read only: the SDK never buys or spends. */
|
|
6
|
+
export declare class AccountApi {
|
|
7
|
+
private readonly http;
|
|
8
|
+
private readonly listings;
|
|
9
|
+
constructor(http: OlxHttpClient, listings: ListingsApi);
|
|
10
|
+
/** The signed-in account: its public id, display name and phone. */
|
|
11
|
+
profile(): Promise<AccountInfo>;
|
|
12
|
+
/** Prepaid credit on the account, in USD. */
|
|
13
|
+
wallet(): Promise<Wallet>;
|
|
14
|
+
/**
|
|
15
|
+
* What OLX charges to post past the free limit in a category (for ads that come back
|
|
16
|
+
* `limited`). Prices are in USD. Read only.
|
|
17
|
+
*/
|
|
18
|
+
postingLimitOffers(categoryId: string | number): Promise<PackageOffer[]>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parsePackageOffers, parseWallet } from '../listings/olxResponses.js';
|
|
2
|
+
/** Your OLX account: profile, wallet and package offers. Read only: the SDK never buys or spends. */
|
|
3
|
+
export class AccountApi {
|
|
4
|
+
http;
|
|
5
|
+
listings;
|
|
6
|
+
constructor(http, listings) {
|
|
7
|
+
this.http = http;
|
|
8
|
+
this.listings = listings;
|
|
9
|
+
}
|
|
10
|
+
/** The signed-in account: its public id, display name and phone. */
|
|
11
|
+
profile() {
|
|
12
|
+
return this.listings.getAccount();
|
|
13
|
+
}
|
|
14
|
+
/** Prepaid credit on the account, in USD. */
|
|
15
|
+
async wallet() {
|
|
16
|
+
return parseWallet(await this.http.call('wallet'));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What OLX charges to post past the free limit in a category (for ads that come back
|
|
20
|
+
* `limited`). Prices are in USD. Read only.
|
|
21
|
+
*/
|
|
22
|
+
async postingLimitOffers(categoryId) {
|
|
23
|
+
return parsePackageOffers(await this.http.call('postingLimitOffers', { params: { categoryId: String(categoryId) } }));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=AccountApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccountApi.js","sourceRoot":"","sources":["../../src/account/AccountApi.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAkC,MAAM,6BAA6B,CAAC;AAG9G,qGAAqG;AACrG,MAAM,OAAO,UAAU;IAEF;IACA;IAFnB,YACmB,IAAmB,EACnB,QAAqB;QADrB,SAAI,GAAJ,IAAI,CAAe;QACnB,aAAQ,GAAR,QAAQ,CAAa;IACrC,CAAC;IAEJ,oEAAoE;IACpE,OAAO;QACL,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAED,6CAA6C;IAC7C,KAAK,CAAC,MAAM;QACV,OAAO,WAAW,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,kBAAkB,CAAC,UAA2B;QAClD,OAAO,kBAAkB,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACxH,CAAC;CACF","sourcesContent":["import type { OlxHttpClient } from '../http/HttpClient.js';\nimport type { ListingsApi } from '../listings/ListingsApi.js';\nimport { parsePackageOffers, parseWallet, type PackageOffer, type Wallet } from '../listings/olxResponses.js';\nimport type { AccountInfo } from '../listings/types.js';\n\n/** Your OLX account: profile, wallet and package offers. Read only: the SDK never buys or spends. */\nexport class AccountApi {\n constructor(\n private readonly http: OlxHttpClient,\n private readonly listings: ListingsApi,\n ) {}\n\n /** The signed-in account: its public id, display name and phone. */\n profile(): Promise<AccountInfo> {\n return this.listings.getAccount();\n }\n\n /** Prepaid credit on the account, in USD. */\n async wallet(): Promise<Wallet> {\n return parseWallet(await this.http.call('wallet'));\n }\n\n /**\n * What OLX charges to post past the free limit in a category (for ads that come back\n * `limited`). Prices are in USD. Read only.\n */\n async postingLimitOffers(categoryId: string | number): Promise<PackageOffer[]> {\n return parsePackageOffers(await this.http.call('postingLimitOffers', { params: { categoryId: String(categoryId) } }));\n }\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DeliveryConfig, type ExchangeRate } from '../listings/olxResponses.js';
|
|
1
2
|
export interface Category {
|
|
2
3
|
/** The id ads use (`categoryId`). */
|
|
3
4
|
id: string;
|
|
@@ -71,4 +72,8 @@ export declare class OlxCatalog {
|
|
|
71
72
|
location(id: string | number): Location | undefined;
|
|
72
73
|
/** When the shipped location snapshot was taken. */
|
|
73
74
|
readonly locationsTakenAt = "2026-10-26";
|
|
75
|
+
/** OLX's current USD → LBP rate, as the website uses it (public; not cached). */
|
|
76
|
+
exchangeRate(): Promise<ExchangeRate>;
|
|
77
|
+
/** OLX delivery's limits for sellers: maximum price and weight, days to ship (public). */
|
|
78
|
+
deliveryConfig(): Promise<DeliveryConfig>;
|
|
74
79
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { OlxSdkError } from '../errors.js';
|
|
7
7
|
import { OLX_LB_LOCATION_ROWS, OLX_LB_LOCATIONS_TAKEN_AT } from '../data/locations.js';
|
|
8
|
+
import { parseDeliveryConfig, parseExchangeRate } from '../listings/olxResponses.js';
|
|
8
9
|
import { OLX_LB_CATALOG_API, postableFields } from '../tools/catalogFields.js';
|
|
9
10
|
const PRICE_FIELDS = new Set(['price', 'secondary_price', 'price_type']);
|
|
10
11
|
const CACHE_MS = 60 * 60 * 1000;
|
|
@@ -109,6 +110,14 @@ export class OlxCatalog {
|
|
|
109
110
|
}
|
|
110
111
|
/** When the shipped location snapshot was taken. */
|
|
111
112
|
locationsTakenAt = OLX_LB_LOCATIONS_TAKEN_AT;
|
|
113
|
+
/** OLX's current USD → LBP rate, as the website uses it (public; not cached). */
|
|
114
|
+
async exchangeRate() {
|
|
115
|
+
return parseExchangeRate(await this.get('/exchange-rate'));
|
|
116
|
+
}
|
|
117
|
+
/** OLX delivery's limits for sellers: maximum price and weight, days to ship (public). */
|
|
118
|
+
async deliveryConfig() {
|
|
119
|
+
return parseDeliveryConfig(await this.get('/delivery-config'));
|
|
120
|
+
}
|
|
112
121
|
}
|
|
113
122
|
function choicesOf(f) {
|
|
114
123
|
const raw = Array.isArray(f.choices) ? f.choices : f.choices ? Object.values(f.choices).flat() : [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OlxCatalog.js","sourceRoot":"","sources":["../../src/catalog/OlxCatalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OLX Lebanon's public catalog: categories, the fields each category's posting form asks\n * for, and locations. No login is needed. Use it to look up ids, or to build forms and\n * dropdowns in your own app.\n */\nimport { OlxSdkError } from '../errors.js';\nimport { OLX_LB_LOCATION_ROWS, OLX_LB_LOCATIONS_TAKEN_AT } from '../data/locations.js';\nimport { OLX_LB_CATALOG_API, postableFields, type OlxCategoryNode, type OlxField } from '../tools/catalogFields.js';\n\nexport interface Category {\n /** The id ads use (`categoryId`). */\n id: string;\n name: string;\n /** Names from the top level down, e.g. [\"Electronics & Home Appliances\", \"Laptops, Tablets, Computers\"]. */\n path: string[];\n /** Ads can only be posted in categories without subcategories. */\n isLeaf: boolean;\n children: Category[];\n}\n\nexport interface FieldChoice {\n /** What you send in `attributes`. */\n value: string;\n label: string;\n}\n\nexport interface CategoryField {\n /** The key in an ad's `attributes`. */\n attribute: string;\n label: string;\n type: 'enum' | 'float' | 'integer' | 'string' | 'boolean' | (string & {});\n required: boolean;\n /** For `enum` fields: the allowed values. */\n choices: FieldChoice[];\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n /** price, price_type and secondary_price are filled from the ad's `price` and `currency`. */\n filledFromPrice: boolean;\n}\n\nexport interface Location {\n /** The id ads use (`locationId`). */\n id: string;\n name: string;\n nameAr?: string;\n /** 0 country, 1 district, 2 area, 3 sub-area. Ads need level 2 or 3. */\n level: number;\n /** e.g. [\"Beirut\", \"Hamra\"] (the country is left out). */\n path: string[];\n /** Whether an ad can use this location. */\n postable: boolean;\n}\n\nconst PRICE_FIELDS = new Set(['price', 'secondary_price', 'price_type']);\nconst CACHE_MS = 60 * 60 * 1000;\n\nexport interface OlxCatalogOptions {\n /** Defaults to the global fetch. */\n fetch?: typeof fetch;\n apiBase?: string;\n timeoutMs?: number;\n}\n\nfunction toCategory(n: OlxCategoryNode, path: string[]): Category {\n const p = [...path, n.name];\n const children = (n.children ?? []).map((c) => toCategory(c, p));\n return { id: n.externalID, name: n.name, path: p, isLeaf: children.length === 0, children };\n}\n\nfunction flatten(cats: Category[]): Category[] {\n return cats.flatMap((c) => [c, ...flatten(c.children)]);\n}\n\nconst LOCATIONS: Location[] = (() => {\n const byId = new Map(OLX_LB_LOCATION_ROWS.map((r) => [r[0], r]));\n return OLX_LB_LOCATION_ROWS.map(([id, , name, nameAr, level, parentId]) => {\n const path = [name];\n for (let p = parentId; p !== null;) {\n const parent = byId.get(p);\n if (!parent || parent[4] === 0) break;\n path.unshift(parent[2]);\n p = parent[5];\n }\n return { id: String(id), name, ...(nameAr ? { nameAr } : {}), level, path, postable: level >= 2 };\n });\n})();\nconst LOCATION_BY_ID = new Map(LOCATIONS.map((l) => [l.id, l]));\n\n/** Lowercase, no accents or punctuation, so \"Achrafiyeh\", \"achrafieh\" and \"Ashrafieh\" stay close. */\nconst norm = (s: string) =>\n s\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, ' ')\n .trim();\n\nexport class OlxCatalog {\n private categoriesCache?: { at: number; value: Promise<Category[]> };\n private readonly fieldsCache = new Map<string, { at: number; value: Promise<CategoryField[]> }>();\n\n constructor(private readonly o: OlxCatalogOptions = {}) {}\n\n private async get<T>(path: string): Promise<T> {\n const fetchImpl = this.o.fetch ?? fetch;\n const res = await fetchImpl((this.o.apiBase ?? OLX_LB_CATALOG_API) + path, {\n headers: { accept: 'application/json', 'accept-language': 'en', origin: 'https://www.olx.com.lb' },\n redirect: 'error',\n signal: AbortSignal.timeout(this.o.timeoutMs ?? 30_000),\n });\n if (!res.ok) throw new OlxSdkError(`OLX returned HTTP ${res.status} for the catalog (${path.split('?')[0]})`);\n return (await res.json()) as T;\n }\n\n /** The category tree (cached for an hour). */\n categories(): Promise<Category[]> {\n if (!this.categoriesCache || Date.now() - this.categoriesCache.at > CACHE_MS) {\n const value = this.get<OlxCategoryNode[]>('/categories').then((tree) => tree.map((n) => toCategory(n, [])));\n value.catch(() => {\n if (this.categoriesCache?.value === value) this.categoriesCache = undefined;\n });\n this.categoriesCache = { at: Date.now(), value };\n }\n return this.categoriesCache.value;\n }\n\n /** Categories whose name contains the text (all of them without text). Leaves only by default. */\n async findCategories(text?: string, opts: { leavesOnly?: boolean } = {}): Promise<Category[]> {\n const needle = text ? norm(text) : '';\n return flatten(await this.categories()).filter(\n (c) => (opts.leavesOnly === false || c.isLeaf) && (!needle || norm(c.name).includes(needle)),\n );\n }\n\n async category(id: string | number): Promise<Category | undefined> {\n return flatten(await this.categories()).find((c) => c.id === String(id));\n }\n\n /** The fields a category's posting form shows, with their choices (cached for an hour). */\n fields(categoryId: string | number): Promise<CategoryField[]> {\n const id = String(categoryId);\n const hit = this.fieldsCache.get(id);\n if (hit && Date.now() - hit.at <= CACHE_MS) return hit.value;\n const value = this.get<Record<string, { flatFields?: OlxField[] }>>(\n `/categoryFields?categoryExternalIDs=${encodeURIComponent(id)}&groupChoicesBySection=true`,\n ).then((data) => {\n const flat = data[id]?.flatFields;\n if (!flat) throw new OlxSdkError(`OLX has no fields for category ${id}; check the id with catalog.findCategories()`);\n return postableFields(flat).map(toField);\n });\n value.catch(() => {\n if (this.fieldsCache.get(id)?.value === value) this.fieldsCache.delete(id);\n });\n this.fieldsCache.set(id, { at: Date.now(), value });\n return value;\n }\n\n /**\n * Locations from the snapshot shipped with the SDK (no network). With text, those whose\n * name or path contains it; postable areas only unless `includeDistricts` is set.\n */\n locations(text?: string, opts: { includeDistricts?: boolean } = {}): Location[] {\n const needle = text ? norm(text) : '';\n return LOCATIONS.filter(\n (l) =>\n (opts.includeDistricts || l.postable) &&\n (!needle || norm(l.path.join(' ')).includes(needle) || (l.nameAr?.includes(text!) ?? false)),\n );\n }\n\n location(id: string | number): Location | undefined {\n return LOCATION_BY_ID.get(String(id));\n }\n\n /** When the shipped location snapshot was taken. */\n readonly locationsTakenAt = OLX_LB_LOCATIONS_TAKEN_AT;\n}\n\nfunction choicesOf(f: OlxField): FieldChoice[] {\n const raw = Array.isArray(f.choices) ? f.choices : f.choices ? Object.values(f.choices).flat() : [];\n return raw.map((c) => ({ value: String(c.value), label: c.label.trim() }));\n}\n\nfunction toField(f: OlxField): CategoryField {\n return {\n attribute: f.attribute,\n label: f.name,\n type: f.valueType,\n required: f.isMandatory,\n choices: choicesOf(f),\n ...(f.minValue != null ? { min: f.minValue } : {}),\n ...(f.maxValue != null ? { max: f.maxValue } : {}),\n ...(f.minLength != null ? { minLength: f.minLength } : {}),\n ...(f.maxLength != null ? { maxLength: f.maxLength } : {}),\n filledFromPrice: PRICE_FIELDS.has(f.attribute),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"OlxCatalog.js","sourceRoot":"","sources":["../../src/catalog/OlxCatalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OLX Lebanon's public catalog: categories, the fields each category's posting form asks\n * for, and locations. No login is needed. Use it to look up ids, or to build forms and\n * dropdowns in your own app.\n */\nimport { OlxSdkError } from '../errors.js';\nimport { OLX_LB_LOCATION_ROWS, OLX_LB_LOCATIONS_TAKEN_AT } from '../data/locations.js';\nimport { parseDeliveryConfig, parseExchangeRate, type DeliveryConfig, type ExchangeRate } from '../listings/olxResponses.js';\nimport { OLX_LB_CATALOG_API, postableFields, type OlxCategoryNode, type OlxField } from '../tools/catalogFields.js';\n\nexport interface Category {\n /** The id ads use (`categoryId`). */\n id: string;\n name: string;\n /** Names from the top level down, e.g. [\"Electronics & Home Appliances\", \"Laptops, Tablets, Computers\"]. */\n path: string[];\n /** Ads can only be posted in categories without subcategories. */\n isLeaf: boolean;\n children: Category[];\n}\n\nexport interface FieldChoice {\n /** What you send in `attributes`. */\n value: string;\n label: string;\n}\n\nexport interface CategoryField {\n /** The key in an ad's `attributes`. */\n attribute: string;\n label: string;\n type: 'enum' | 'float' | 'integer' | 'string' | 'boolean' | (string & {});\n required: boolean;\n /** For `enum` fields: the allowed values. */\n choices: FieldChoice[];\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n /** price, price_type and secondary_price are filled from the ad's `price` and `currency`. */\n filledFromPrice: boolean;\n}\n\nexport interface Location {\n /** The id ads use (`locationId`). */\n id: string;\n name: string;\n nameAr?: string;\n /** 0 country, 1 district, 2 area, 3 sub-area. Ads need level 2 or 3. */\n level: number;\n /** e.g. [\"Beirut\", \"Hamra\"] (the country is left out). */\n path: string[];\n /** Whether an ad can use this location. */\n postable: boolean;\n}\n\nconst PRICE_FIELDS = new Set(['price', 'secondary_price', 'price_type']);\nconst CACHE_MS = 60 * 60 * 1000;\n\nexport interface OlxCatalogOptions {\n /** Defaults to the global fetch. */\n fetch?: typeof fetch;\n apiBase?: string;\n timeoutMs?: number;\n}\n\nfunction toCategory(n: OlxCategoryNode, path: string[]): Category {\n const p = [...path, n.name];\n const children = (n.children ?? []).map((c) => toCategory(c, p));\n return { id: n.externalID, name: n.name, path: p, isLeaf: children.length === 0, children };\n}\n\nfunction flatten(cats: Category[]): Category[] {\n return cats.flatMap((c) => [c, ...flatten(c.children)]);\n}\n\nconst LOCATIONS: Location[] = (() => {\n const byId = new Map(OLX_LB_LOCATION_ROWS.map((r) => [r[0], r]));\n return OLX_LB_LOCATION_ROWS.map(([id, , name, nameAr, level, parentId]) => {\n const path = [name];\n for (let p = parentId; p !== null;) {\n const parent = byId.get(p);\n if (!parent || parent[4] === 0) break;\n path.unshift(parent[2]);\n p = parent[5];\n }\n return { id: String(id), name, ...(nameAr ? { nameAr } : {}), level, path, postable: level >= 2 };\n });\n})();\nconst LOCATION_BY_ID = new Map(LOCATIONS.map((l) => [l.id, l]));\n\n/** Lowercase, no accents or punctuation, so \"Achrafiyeh\", \"achrafieh\" and \"Ashrafieh\" stay close. */\nconst norm = (s: string) =>\n s\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, ' ')\n .trim();\n\nexport class OlxCatalog {\n private categoriesCache?: { at: number; value: Promise<Category[]> };\n private readonly fieldsCache = new Map<string, { at: number; value: Promise<CategoryField[]> }>();\n\n constructor(private readonly o: OlxCatalogOptions = {}) {}\n\n private async get<T>(path: string): Promise<T> {\n const fetchImpl = this.o.fetch ?? fetch;\n const res = await fetchImpl((this.o.apiBase ?? OLX_LB_CATALOG_API) + path, {\n headers: { accept: 'application/json', 'accept-language': 'en', origin: 'https://www.olx.com.lb' },\n redirect: 'error',\n signal: AbortSignal.timeout(this.o.timeoutMs ?? 30_000),\n });\n if (!res.ok) throw new OlxSdkError(`OLX returned HTTP ${res.status} for the catalog (${path.split('?')[0]})`);\n return (await res.json()) as T;\n }\n\n /** The category tree (cached for an hour). */\n categories(): Promise<Category[]> {\n if (!this.categoriesCache || Date.now() - this.categoriesCache.at > CACHE_MS) {\n const value = this.get<OlxCategoryNode[]>('/categories').then((tree) => tree.map((n) => toCategory(n, [])));\n value.catch(() => {\n if (this.categoriesCache?.value === value) this.categoriesCache = undefined;\n });\n this.categoriesCache = { at: Date.now(), value };\n }\n return this.categoriesCache.value;\n }\n\n /** Categories whose name contains the text (all of them without text). Leaves only by default. */\n async findCategories(text?: string, opts: { leavesOnly?: boolean } = {}): Promise<Category[]> {\n const needle = text ? norm(text) : '';\n return flatten(await this.categories()).filter(\n (c) => (opts.leavesOnly === false || c.isLeaf) && (!needle || norm(c.name).includes(needle)),\n );\n }\n\n async category(id: string | number): Promise<Category | undefined> {\n return flatten(await this.categories()).find((c) => c.id === String(id));\n }\n\n /** The fields a category's posting form shows, with their choices (cached for an hour). */\n fields(categoryId: string | number): Promise<CategoryField[]> {\n const id = String(categoryId);\n const hit = this.fieldsCache.get(id);\n if (hit && Date.now() - hit.at <= CACHE_MS) return hit.value;\n const value = this.get<Record<string, { flatFields?: OlxField[] }>>(\n `/categoryFields?categoryExternalIDs=${encodeURIComponent(id)}&groupChoicesBySection=true`,\n ).then((data) => {\n const flat = data[id]?.flatFields;\n if (!flat) throw new OlxSdkError(`OLX has no fields for category ${id}; check the id with catalog.findCategories()`);\n return postableFields(flat).map(toField);\n });\n value.catch(() => {\n if (this.fieldsCache.get(id)?.value === value) this.fieldsCache.delete(id);\n });\n this.fieldsCache.set(id, { at: Date.now(), value });\n return value;\n }\n\n /**\n * Locations from the snapshot shipped with the SDK (no network). With text, those whose\n * name or path contains it; postable areas only unless `includeDistricts` is set.\n */\n locations(text?: string, opts: { includeDistricts?: boolean } = {}): Location[] {\n const needle = text ? norm(text) : '';\n return LOCATIONS.filter(\n (l) =>\n (opts.includeDistricts || l.postable) &&\n (!needle || norm(l.path.join(' ')).includes(needle) || (l.nameAr?.includes(text!) ?? false)),\n );\n }\n\n location(id: string | number): Location | undefined {\n return LOCATION_BY_ID.get(String(id));\n }\n\n /** When the shipped location snapshot was taken. */\n readonly locationsTakenAt = OLX_LB_LOCATIONS_TAKEN_AT;\n\n /** OLX's current USD → LBP rate, as the website uses it (public; not cached). */\n async exchangeRate(): Promise<ExchangeRate> {\n return parseExchangeRate(await this.get<unknown>('/exchange-rate'));\n }\n\n /** OLX delivery's limits for sellers: maximum price and weight, days to ship (public). */\n async deliveryConfig(): Promise<DeliveryConfig> {\n return parseDeliveryConfig(await this.get<unknown>('/delivery-config'));\n }\n}\n\nfunction choicesOf(f: OlxField): FieldChoice[] {\n const raw = Array.isArray(f.choices) ? f.choices : f.choices ? Object.values(f.choices).flat() : [];\n return raw.map((c) => ({ value: String(c.value), label: c.label.trim() }));\n}\n\nfunction toField(f: OlxField): CategoryField {\n return {\n attribute: f.attribute,\n label: f.name,\n type: f.valueType,\n required: f.isMandatory,\n choices: choicesOf(f),\n ...(f.minValue != null ? { min: f.minValue } : {}),\n ...(f.maxValue != null ? { max: f.maxValue } : {}),\n ...(f.minLength != null ? { minLength: f.minLength } : {}),\n ...(f.maxLength != null ? { maxLength: f.maxLength } : {}),\n filledFromPrice: PRICE_FIELDS.has(f.attribute),\n };\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { OlxClient } from '../../client.js';
|
|
2
|
+
import { loadConfig } from '../../config.js';
|
|
3
|
+
import { silentLogger } from '../../logger.js';
|
|
4
|
+
import { UsageError, parseFlags, resolveFrom, table } from '../util.js';
|
|
5
|
+
const ACTIONS = ['deactivate', 'activate', 'sold', 'delete', 'reason'];
|
|
6
|
+
export const ads = {
|
|
7
|
+
summary: 'Your ads on OLX: status, views and leads; deactivate, activate, mark sold, delete',
|
|
8
|
+
usage: `Usage:
|
|
9
|
+
olx-lb ads [list] [--pages <n>] [--json] your ads with status, expiry, views and leads
|
|
10
|
+
olx-lb ads deactivate <adId> take an ad offline (not deleted)
|
|
11
|
+
olx-lb ads activate <adId> put a "limited" ad live with a free slot, if one is left
|
|
12
|
+
olx-lb ads sold <adId> --on olx|elsewhere mark an ad as sold
|
|
13
|
+
olx-lb ads delete <adId> delete an ad
|
|
14
|
+
olx-lb ads reason <adId> why OLX's review refused an ad
|
|
15
|
+
|
|
16
|
+
Changes are paced like any write (a minute or more apart). They act on the ad only: if a
|
|
17
|
+
product of yours is synced to it, use your app (or the REST service) so the link follows.`,
|
|
18
|
+
async run(args, ctx) {
|
|
19
|
+
const { flags, positionals } = parseFlags(args, { json: 'boolean', pages: 'string', on: 'string' });
|
|
20
|
+
const [what = 'list', adId] = positionals;
|
|
21
|
+
const config = loadConfig();
|
|
22
|
+
const client = OlxClient.fromConfig({ ...config, sessionFile: resolveFrom(ctx, config.sessionFile), catalogFile: resolveFrom(ctx, config.catalogFile) }, { logger: silentLogger });
|
|
23
|
+
if (what === 'list') {
|
|
24
|
+
const pages = Math.max(1, Math.min(50, Number(flags.pages ?? 1) || 1));
|
|
25
|
+
const found = [];
|
|
26
|
+
for (let page = 1; page <= pages; page++) {
|
|
27
|
+
const { ads: batch, hasMore } = await client.ads.list({ page, perPage: 20 });
|
|
28
|
+
found.push(...batch);
|
|
29
|
+
if (!hasMore)
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
const metrics = found.length ? await client.ads.metrics(found.map((a) => a.id)) : {};
|
|
33
|
+
if (flags.json) {
|
|
34
|
+
ctx.out(JSON.stringify(found.map(({ raw: _raw, ...a }) => ({ ...a, metrics: metrics[a.id] ?? null, actions: client.ads.actions(a) })), null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!found.length)
|
|
38
|
+
return void ctx.out('No ads on this account.');
|
|
39
|
+
const rows = found.map((a) => {
|
|
40
|
+
const m = metrics[a.id];
|
|
41
|
+
const detail = a.promotion
|
|
42
|
+
? ` (${a.promotion.type}${a.promotion.pending ? ', pending' : ''})`
|
|
43
|
+
: a.rejection
|
|
44
|
+
? ` (${a.rejection})`
|
|
45
|
+
: '';
|
|
46
|
+
return [
|
|
47
|
+
a.id,
|
|
48
|
+
`${a.status}${detail}`,
|
|
49
|
+
a.expiresAt?.slice(0, 10) ?? '',
|
|
50
|
+
m ? String(m.views) : '',
|
|
51
|
+
m ? String(m.leads) : '',
|
|
52
|
+
(a.title ?? '').slice(0, 40),
|
|
53
|
+
];
|
|
54
|
+
});
|
|
55
|
+
ctx.out(table([['id', 'status', 'expires', 'views', 'leads', 'title'], ...rows]).join('\n'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!ACTIONS.includes(what))
|
|
59
|
+
throw new UsageError(`Unknown ads command "${what}"`);
|
|
60
|
+
if (!adId || !/^\d{5,15}$/.test(adId))
|
|
61
|
+
throw new UsageError('Give the ad id (the digits in its OLX link)');
|
|
62
|
+
switch (what) {
|
|
63
|
+
case 'deactivate':
|
|
64
|
+
await client.ads.deactivate(adId);
|
|
65
|
+
ctx.out(`deactivated ad ${adId}`);
|
|
66
|
+
return;
|
|
67
|
+
case 'activate': {
|
|
68
|
+
const result = await client.ads.activate(adId);
|
|
69
|
+
ctx.out(result === 'activated'
|
|
70
|
+
? `activated ad ${adId}`
|
|
71
|
+
: result === 'needs_package'
|
|
72
|
+
? `ad ${adId} needs a package: the account has no free ad left in its category (buy one on OLX)`
|
|
73
|
+
: `ad ${adId} isn't limited; nothing to do`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case 'sold': {
|
|
77
|
+
if (flags.on !== 'olx' && flags.on !== 'elsewhere')
|
|
78
|
+
throw new UsageError('Say where it sold: --on olx or --on elsewhere');
|
|
79
|
+
await client.ads.markSold(adId, { soldOn: flags.on });
|
|
80
|
+
ctx.out(`marked ad ${adId} as sold (${flags.on})`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
case 'delete':
|
|
84
|
+
await client.ads.delete(adId);
|
|
85
|
+
ctx.out(`deleted ad ${adId}`);
|
|
86
|
+
return;
|
|
87
|
+
case 'reason': {
|
|
88
|
+
const reason = await client.ads.rejectionReason(adId);
|
|
89
|
+
ctx.out(reason
|
|
90
|
+
? [reason.message, ...reason.rules.map((r) => `- ${r}`)].join('\n')
|
|
91
|
+
: 'OLX has no rejection reason on file for this ad.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=ads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ads.js","sourceRoot":"","sources":["../../../src/cli/commands/ads.ts"],"names":[],"mappings":"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","sourcesContent":["import { OlxClient } from '../../client.js';\nimport { loadConfig } from '../../config.js';\nimport { silentLogger } from '../../logger.js';\nimport type { OlxAd } from '../../listings/types.js';\nimport type { Command } from '../index.js';\nimport { UsageError, parseFlags, resolveFrom, table } from '../util.js';\n\nconst ACTIONS = ['deactivate', 'activate', 'sold', 'delete', 'reason'] as const;\n\nexport const ads: Command = {\n summary: 'Your ads on OLX: status, views and leads; deactivate, activate, mark sold, delete',\n usage: `Usage:\n olx-lb ads [list] [--pages <n>] [--json] your ads with status, expiry, views and leads\n olx-lb ads deactivate <adId> take an ad offline (not deleted)\n olx-lb ads activate <adId> put a \"limited\" ad live with a free slot, if one is left\n olx-lb ads sold <adId> --on olx|elsewhere mark an ad as sold\n olx-lb ads delete <adId> delete an ad\n olx-lb ads reason <adId> why OLX's review refused an ad\n\nChanges are paced like any write (a minute or more apart). They act on the ad only: if a\nproduct of yours is synced to it, use your app (or the REST service) so the link follows.`,\n\n async run(args, ctx) {\n const { flags, positionals } = parseFlags(args, { json: 'boolean', pages: 'string', on: 'string' });\n const [what = 'list', adId] = positionals;\n const config = loadConfig();\n const client = OlxClient.fromConfig(\n { ...config, sessionFile: resolveFrom(ctx, config.sessionFile), catalogFile: resolveFrom(ctx, config.catalogFile) },\n { logger: silentLogger },\n );\n\n if (what === 'list') {\n const pages = Math.max(1, Math.min(50, Number(flags.pages ?? 1) || 1));\n const found: OlxAd[] = [];\n for (let page = 1; page <= pages; page++) {\n const { ads: batch, hasMore } = await client.ads.list({ page, perPage: 20 });\n found.push(...batch);\n if (!hasMore) break;\n }\n const metrics = found.length ? await client.ads.metrics(found.map((a) => a.id)) : {};\n if (flags.json) {\n ctx.out(\n JSON.stringify(\n found.map(({ raw: _raw, ...a }) => ({ ...a, metrics: metrics[a.id] ?? null, actions: client.ads.actions(a) })),\n null,\n 2,\n ),\n );\n return;\n }\n if (!found.length) return void ctx.out('No ads on this account.');\n const rows = found.map((a) => {\n const m = metrics[a.id];\n const detail = a.promotion\n ? ` (${a.promotion.type}${a.promotion.pending ? ', pending' : ''})`\n : a.rejection\n ? ` (${a.rejection})`\n : '';\n return [\n a.id,\n `${a.status}${detail}`,\n a.expiresAt?.slice(0, 10) ?? '',\n m ? String(m.views) : '',\n m ? String(m.leads) : '',\n (a.title ?? '').slice(0, 40),\n ];\n });\n ctx.out(table([['id', 'status', 'expires', 'views', 'leads', 'title'], ...rows]).join('\\n'));\n return;\n }\n\n if (!(ACTIONS as readonly string[]).includes(what)) throw new UsageError(`Unknown ads command \"${what}\"`);\n if (!adId || !/^\\d{5,15}$/.test(adId)) throw new UsageError('Give the ad id (the digits in its OLX link)');\n\n switch (what as (typeof ACTIONS)[number]) {\n case 'deactivate':\n await client.ads.deactivate(adId);\n ctx.out(`deactivated ad ${adId}`);\n return;\n case 'activate': {\n const result = await client.ads.activate(adId);\n ctx.out(\n result === 'activated'\n ? `activated ad ${adId}`\n : result === 'needs_package'\n ? `ad ${adId} needs a package: the account has no free ad left in its category (buy one on OLX)`\n : `ad ${adId} isn't limited; nothing to do`,\n );\n return;\n }\n case 'sold': {\n if (flags.on !== 'olx' && flags.on !== 'elsewhere') throw new UsageError('Say where it sold: --on olx or --on elsewhere');\n await client.ads.markSold(adId, { soldOn: flags.on });\n ctx.out(`marked ad ${adId} as sold (${flags.on})`);\n return;\n }\n case 'delete':\n await client.ads.delete(adId);\n ctx.out(`deleted ad ${adId}`);\n return;\n case 'reason': {\n const reason = await client.ads.rejectionReason(adId);\n ctx.out(\n reason\n ? [reason.message, ...reason.rules.map((r) => `- ${r}`)].join('\\n')\n : 'OLX has no rejection reason on file for this ad.',\n );\n return;\n }\n }\n },\n};\n"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -16,11 +16,12 @@ const COMMANDS = {
|
|
|
16
16
|
token: async () => (await import('./commands/login.js')).token,
|
|
17
17
|
catalog: async () => (await import('./commands/catalog.js')).catalog,
|
|
18
18
|
check: async () => (await import('./commands/check.js')).check,
|
|
19
|
+
ads: async () => (await import('./commands/ads.js')).ads,
|
|
19
20
|
'test-post': async () => (await import('./commands/testPost.js')).testPost,
|
|
20
21
|
serve: async () => (await import('./commands/serve.js')).serve,
|
|
21
22
|
capture: async () => (await import('./commands/capture.js')).capture,
|
|
22
23
|
};
|
|
23
|
-
const ORDER = ['init', 'login', 'token', 'catalog', 'check', 'test-post', 'serve', 'capture'];
|
|
24
|
+
const ORDER = ['init', 'login', 'token', 'catalog', 'check', 'ads', 'test-post', 'serve', 'capture'];
|
|
24
25
|
async function help(ctx, name) {
|
|
25
26
|
if (name && COMMANDS[name]) {
|
|
26
27
|
const cmd = await COMMANDS[name]();
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * The `olx-lb` command: setup, login, catalog lookup, checks and the REST service.\n * Run `olx-lb help` for the list of commands.\n */\nimport { loadEnvFile } from '../env.js';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { OlxSdkError } from '../errors.js';\nimport { CliError, UsageError, defaultContext, packageVersion, type CliContext } from './util.js';\n\nexport interface Command {\n summary: string;\n usage: string;\n run(args: string[], ctx: CliContext): Promise<number | void>;\n}\n\n/** Commands are loaded on demand, so `olx-lb help` doesn't load Playwright or Fastify. */\nconst COMMANDS: Record<string, () => Promise<Command>> = {\n init: async () => (await import('./commands/init.js')).init,\n login: async () => (await import('./commands/login.js')).login,\n token: async () => (await import('./commands/login.js')).token,\n catalog: async () => (await import('./commands/catalog.js')).catalog,\n check: async () => (await import('./commands/check.js')).check,\n 'test-post': async () => (await import('./commands/testPost.js')).testPost,\n serve: async () => (await import('./commands/serve.js')).serve,\n capture: async () => (await import('./commands/capture.js')).capture,\n};\n\nconst ORDER = ['init', 'login', 'token', 'catalog', 'check', 'test-post', 'serve', 'capture'];\n\nasync function help(ctx: CliContext, name?: string): Promise<void> {\n if (name && COMMANDS[name]) {\n const cmd = await COMMANDS[name]();\n ctx.out(`${cmd.summary}\\n\\n${cmd.usage}`);\n return;\n }\n ctx.out(`olx-lb ${packageVersion()} — post and manage your own OLX Lebanon listings\n\nUsage: olx-lb <command> [options]\n\nCommands:`);\n for (const n of ORDER) ctx.out(` ${n.padEnd(11)} ${(await COMMANDS[n]!()).summary}`);\n ctx.out(`\nGlobal options:\n --env-file <path> settings file to load (default: .env in the current folder)\n --debug show full error details\n -h, --help help for a command (olx-lb <command> --help)\n -v, --version print the version\n\nStart with: olx-lb init\n\nMade with ❤️ by Zaytoun Solutions · https://www.zaytounsolutions.com`);\n}\n\n/** Runs the CLI and returns the exit code (0 ok, 1 failure, 2 wrong usage). */\nexport async function main(argv: string[], ctx: CliContext = defaultContext()): Promise<number> {\n const args = [...argv];\n let envFile: string | undefined;\n for (let i = 0; i < args.length; i++) {\n const a = args[i]!;\n if (a === '--debug') {\n ctx.debug = true;\n args.splice(i--, 1);\n } else if (a === '--env-file' || a.startsWith('--env-file=')) {\n envFile = a.includes('=') ? a.slice(a.indexOf('=') + 1) : args[i + 1];\n args.splice(i--, a.includes('=') ? 1 : 2);\n }\n }\n\n const [name, ...rest] = args;\n if (!name || name === 'help' || name === '-h' || name === '--help') {\n await help(ctx, rest[0]);\n return 0;\n }\n if (name === '-v' || name === '--version' || name === 'version') {\n ctx.out(packageVersion());\n return 0;\n }\n const load = COMMANDS[name];\n if (!load) {\n ctx.err(`Unknown command \"${name}\". Run \\`olx-lb help\\` for the list of commands.`);\n return 2;\n }\n const cmd = await load();\n if (rest.includes('--help') || rest.includes('-h')) {\n ctx.out(`${cmd.summary}\\n\\n${cmd.usage}`);\n return 0;\n }\n\n const envPath = resolve(ctx.cwd, envFile ?? '.env');\n if (envFile && !existsSync(envPath)) {\n ctx.err(`Settings file not found: ${envPath}`);\n return 1;\n }\n loadEnvFile(envPath);\n\n try {\n const code = await cmd.run(rest, ctx);\n return typeof code === 'number' ? code : 0;\n } catch (err) {\n if (err instanceof UsageError) {\n ctx.err(`${err.message}\\n\\n${cmd.usage}`);\n return err.exitCode;\n }\n if (err instanceof CliError) {\n ctx.err(err.message);\n return err.exitCode;\n }\n const e = err as Error & { status?: number; body?: unknown };\n if (ctx.debug || !(err instanceof OlxSdkError)) {\n ctx.err(e.stack ?? String(err));\n } else {\n ctx.err(`${e.name}: ${e.message}`);\n }\n // OLX's error body names the field it rejected; it holds no secrets.\n if (e.body !== undefined) ctx.err(`OLX said: ${JSON.stringify(e.body).slice(0, 1500)}`);\n return 1;\n }\n}\n\n/** True when this file is the program being run (directly, or through npm's bin link). */\nfunction isMain(): boolean {\n const arg = process.argv[1];\n if (!arg) return false;\n try {\n return pathToFileURL(realpathSync(resolve(arg))).href === import.meta.url;\n } catch {\n return false;\n }\n}\n\nif (isMain()) {\n main(process.argv.slice(2)).then(\n (code) => {\n process.exitCode = code;\n },\n (err: unknown) => {\n process.stderr.write(`${(err as Error).stack ?? String(err)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * The `olx-lb` command: setup, login, catalog lookup, checks and the REST service.\n * Run `olx-lb help` for the list of commands.\n */\nimport { loadEnvFile } from '../env.js';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { OlxSdkError } from '../errors.js';\nimport { CliError, UsageError, defaultContext, packageVersion, type CliContext } from './util.js';\n\nexport interface Command {\n summary: string;\n usage: string;\n run(args: string[], ctx: CliContext): Promise<number | void>;\n}\n\n/** Commands are loaded on demand, so `olx-lb help` doesn't load Playwright or Fastify. */\nconst COMMANDS: Record<string, () => Promise<Command>> = {\n init: async () => (await import('./commands/init.js')).init,\n login: async () => (await import('./commands/login.js')).login,\n token: async () => (await import('./commands/login.js')).token,\n catalog: async () => (await import('./commands/catalog.js')).catalog,\n check: async () => (await import('./commands/check.js')).check,\n ads: async () => (await import('./commands/ads.js')).ads,\n 'test-post': async () => (await import('./commands/testPost.js')).testPost,\n serve: async () => (await import('./commands/serve.js')).serve,\n capture: async () => (await import('./commands/capture.js')).capture,\n};\n\nconst ORDER = ['init', 'login', 'token', 'catalog', 'check', 'ads', 'test-post', 'serve', 'capture'];\n\nasync function help(ctx: CliContext, name?: string): Promise<void> {\n if (name && COMMANDS[name]) {\n const cmd = await COMMANDS[name]();\n ctx.out(`${cmd.summary}\\n\\n${cmd.usage}`);\n return;\n }\n ctx.out(`olx-lb ${packageVersion()} — post and manage your own OLX Lebanon listings\n\nUsage: olx-lb <command> [options]\n\nCommands:`);\n for (const n of ORDER) ctx.out(` ${n.padEnd(11)} ${(await COMMANDS[n]!()).summary}`);\n ctx.out(`\nGlobal options:\n --env-file <path> settings file to load (default: .env in the current folder)\n --debug show full error details\n -h, --help help for a command (olx-lb <command> --help)\n -v, --version print the version\n\nStart with: olx-lb init\n\nMade with ❤️ by Zaytoun Solutions · https://www.zaytounsolutions.com`);\n}\n\n/** Runs the CLI and returns the exit code (0 ok, 1 failure, 2 wrong usage). */\nexport async function main(argv: string[], ctx: CliContext = defaultContext()): Promise<number> {\n const args = [...argv];\n let envFile: string | undefined;\n for (let i = 0; i < args.length; i++) {\n const a = args[i]!;\n if (a === '--debug') {\n ctx.debug = true;\n args.splice(i--, 1);\n } else if (a === '--env-file' || a.startsWith('--env-file=')) {\n envFile = a.includes('=') ? a.slice(a.indexOf('=') + 1) : args[i + 1];\n args.splice(i--, a.includes('=') ? 1 : 2);\n }\n }\n\n const [name, ...rest] = args;\n if (!name || name === 'help' || name === '-h' || name === '--help') {\n await help(ctx, rest[0]);\n return 0;\n }\n if (name === '-v' || name === '--version' || name === 'version') {\n ctx.out(packageVersion());\n return 0;\n }\n const load = COMMANDS[name];\n if (!load) {\n ctx.err(`Unknown command \"${name}\". Run \\`olx-lb help\\` for the list of commands.`);\n return 2;\n }\n const cmd = await load();\n if (rest.includes('--help') || rest.includes('-h')) {\n ctx.out(`${cmd.summary}\\n\\n${cmd.usage}`);\n return 0;\n }\n\n const envPath = resolve(ctx.cwd, envFile ?? '.env');\n if (envFile && !existsSync(envPath)) {\n ctx.err(`Settings file not found: ${envPath}`);\n return 1;\n }\n loadEnvFile(envPath);\n\n try {\n const code = await cmd.run(rest, ctx);\n return typeof code === 'number' ? code : 0;\n } catch (err) {\n if (err instanceof UsageError) {\n ctx.err(`${err.message}\\n\\n${cmd.usage}`);\n return err.exitCode;\n }\n if (err instanceof CliError) {\n ctx.err(err.message);\n return err.exitCode;\n }\n const e = err as Error & { status?: number; body?: unknown };\n if (ctx.debug || !(err instanceof OlxSdkError)) {\n ctx.err(e.stack ?? String(err));\n } else {\n ctx.err(`${e.name}: ${e.message}`);\n }\n // OLX's error body names the field it rejected; it holds no secrets.\n if (e.body !== undefined) ctx.err(`OLX said: ${JSON.stringify(e.body).slice(0, 1500)}`);\n return 1;\n }\n}\n\n/** True when this file is the program being run (directly, or through npm's bin link). */\nfunction isMain(): boolean {\n const arg = process.argv[1];\n if (!arg) return false;\n try {\n return pathToFileURL(realpathSync(resolve(arg))).href === import.meta.url;\n } catch {\n return false;\n }\n}\n\nif (isMain()) {\n main(process.argv.slice(2)).then(\n (code) => {\n process.exitCode = code;\n },\n (err: unknown) => {\n process.stderr.write(`${(err as Error).stack ?? String(err)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n"]}
|
package/dist/client.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { OlxHttpClient, type HttpClientOptions, type Transport } from './http/Ht
|
|
|
4
4
|
import { WriteRateLimiter, type RateLimiterClock } from './http/rateLimiter.js';
|
|
5
5
|
import { type OlxAdapter } from './listings/adapter.js';
|
|
6
6
|
import { OlxCatalog, type OlxCatalogOptions } from './catalog/OlxCatalog.js';
|
|
7
|
+
import { AccountApi } from './account/AccountApi.js';
|
|
7
8
|
import { AdsApi } from './listings/AdsApi.js';
|
|
9
|
+
import { OrdersApi } from './orders/OrdersApi.js';
|
|
8
10
|
import type { ContactSettings } from './listings/types.js';
|
|
9
11
|
import { type ImageUploaderOptions } from './listings/images.js';
|
|
10
12
|
import { ListingsApi } from './listings/ListingsApi.js';
|
|
@@ -52,6 +54,10 @@ export interface OlxClientOptions {
|
|
|
52
54
|
*/
|
|
53
55
|
export declare class OlxClient {
|
|
54
56
|
readonly ads: AdsApi;
|
|
57
|
+
/** Your account: profile, wallet, package offers (read only). */
|
|
58
|
+
readonly account: AccountApi;
|
|
59
|
+
/** OLX delivery orders on your listings (experimental). */
|
|
60
|
+
readonly orders: OrdersApi;
|
|
55
61
|
readonly catalog: OlxCatalog;
|
|
56
62
|
readonly session: SessionManager;
|
|
57
63
|
readonly http: OlxHttpClient;
|
package/dist/client.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { endpointStatus, loadEndpoints } from './endpoints.js';
|
|
3
|
+
import { endpointStatus, loadEndpointOverrides, loadEndpoints } from './endpoints.js';
|
|
4
4
|
import { OlxHttpClient } from './http/HttpClient.js';
|
|
5
5
|
import { WriteRateLimiter } from './http/rateLimiter.js';
|
|
6
6
|
import { placeholderAdapter } from './listings/adapter.js';
|
|
7
7
|
import { OlxCatalog } from './catalog/OlxCatalog.js';
|
|
8
|
+
import { AccountApi } from './account/AccountApi.js';
|
|
8
9
|
import { AdsApi } from './listings/AdsApi.js';
|
|
10
|
+
import { OrdersApi } from './orders/OrdersApi.js';
|
|
9
11
|
import { OLX_LB_ENDPOINTS, olxLebanonAdapter } from './listings/olxLebanon.js';
|
|
10
12
|
import { ImageUploader } from './listings/images.js';
|
|
11
13
|
import { ListingsApi } from './listings/ListingsApi.js';
|
|
@@ -24,6 +26,10 @@ import { JsonFileSyncStore } from './sync/SyncStore.js';
|
|
|
24
26
|
*/
|
|
25
27
|
export class OlxClient {
|
|
26
28
|
ads;
|
|
29
|
+
/** Your account: profile, wallet, package offers (read only). */
|
|
30
|
+
account;
|
|
31
|
+
/** OLX delivery orders on your listings (experimental). */
|
|
32
|
+
orders;
|
|
27
33
|
catalog;
|
|
28
34
|
session;
|
|
29
35
|
http;
|
|
@@ -77,6 +83,8 @@ export class OlxClient {
|
|
|
77
83
|
logger,
|
|
78
84
|
});
|
|
79
85
|
this.ads = new AdsApi(this.listings);
|
|
86
|
+
this.account = new AccountApi(this.http, this.listings);
|
|
87
|
+
this.orders = new OrdersApi(this.http);
|
|
80
88
|
this.catalog = new OlxCatalog(opts.catalogApi);
|
|
81
89
|
this.syncStore = opts.syncStore;
|
|
82
90
|
this.sync = new SyncService({ listings: this.listings, store: opts.syncStore, mapper: this.mapper, logger });
|
|
@@ -93,7 +101,11 @@ export class OlxClient {
|
|
|
93
101
|
path: resolve(config.catalogFile),
|
|
94
102
|
});
|
|
95
103
|
}
|
|
96
|
-
const endpoints = overrides.endpoints ??
|
|
104
|
+
const endpoints = overrides.endpoints ??
|
|
105
|
+
(config.adapter === 'placeholder'
|
|
106
|
+
? loadEndpoints(config.endpointsFile)
|
|
107
|
+
: // An endpoints file only overrides what it lists; the rest stay built in.
|
|
108
|
+
{ ...OLX_LB_ENDPOINTS, ...loadEndpointOverrides(config.endpointsFile) });
|
|
97
109
|
return new OlxClient({
|
|
98
110
|
baseUrl: config.baseUrl,
|
|
99
111
|
writeLimits: config.writeLimits,
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { OlxConfig, WriteLimits } from './config.js';\nimport { endpointStatus, loadEndpoints, type EndpointMap, type EndpointName } from './endpoints.js';\nimport { OlxHttpClient, type HttpClientOptions, type Transport } from './http/HttpClient.js';\nimport { WriteRateLimiter, type RateLimiterClock } from './http/rateLimiter.js';\nimport { placeholderAdapter, type OlxAdapter } from './listings/adapter.js';\nimport { OlxCatalog, type OlxCatalogOptions } from './catalog/OlxCatalog.js';\nimport { AdsApi } from './listings/AdsApi.js';\nimport { OLX_LB_ENDPOINTS, olxLebanonAdapter } from './listings/olxLebanon.js';\nimport type { ContactSettings } from './listings/types.js';\nimport { ImageUploader, type ImageUploaderOptions } from './listings/images.js';\nimport { ListingsApi } from './listings/ListingsApi.js';\nimport { FileRequestLog, createLogger, silentLogger, type Logger, type RequestLog } from './logger.js';\nimport { CatalogMapper, loadCatalog, type Catalog } from './mapping/CatalogMapper.js';\nimport { DEFAULT_CAPTURED_HEADERS, SessionManager } from './session/SessionManager.js';\nimport { FileSessionStore, MemorySessionStore, sessionFromAccessToken, type SessionStore } from './session/storage.js';\nimport { SyncService } from './sync/SyncService.js';\nimport { JsonFileSyncStore, type SyncStore } from './sync/SyncStore.js';\n\nexport interface OlxClientOptions {\n baseUrl: string;\n endpoints: EndpointMap;\n catalog: Catalog;\n sessionStore: SessionStore;\n syncStore: SyncStore;\n writeLimits: WriteLimits;\n /** Wire format. Defaults to the built-in OLX Lebanon adapter. */\n adapter?: OlxAdapter;\n /** Who buyers contact; empty fields fall back to the account profile. */\n contact?: ContactSettings;\n logger?: Logger;\n requestLog?: RequestLog;\n transport?: Transport;\n clock?: RateLimiterClock;\n allowedImageHosts?: string[];\n imageFetch?: typeof fetch;\n resolveHost?: (host: string) => Promise<string[]>;\n /** Non-OLX hosts whose requests are watched for auth headers at login. */\n extraHosts?: string[];\n /** Header names captured at login, in addition to the defaults. */\n captureHeaders?: string[];\n /** Tuning for calls to OLX. */\n http?: Pick<HttpClientOptions, 'timeoutMs' | 'maxReadRetries' | 'retryBaseMs' | 'maxRetryDelayMs'>;\n /** Tuning for product image downloads. */\n images?: Pick<ImageUploaderOptions, 'maxBytes' | 'timeoutMs' | 'totalTimeoutMs' | 'maxRedirects'>;\n /** Options for `client.catalog` (public category and location data). */\n catalogApi?: OlxCatalogOptions;\n}\n\n/**\n * Everything wired together. Most apps need three parts:\n * - `ads`: create, edit, delete and list your ads by OLX id;\n * - `sync`: keep one ad per product of your store (create once, edit in place, skip unchanged);\n * - `catalog`: OLX's categories, their fields, and locations.\n * Create one with `createOlxClient()`.\n */\nexport class OlxClient {\n readonly ads: AdsApi;\n readonly catalog: OlxCatalog;\n readonly session: SessionManager;\n readonly http: OlxHttpClient;\n readonly listings: ListingsApi;\n readonly sync: SyncService;\n readonly syncStore: SyncStore;\n readonly mapper: CatalogMapper;\n readonly limiter: WriteRateLimiter;\n readonly adapter: OlxAdapter;\n private readonly endpoints: EndpointMap;\n\n constructor(opts: OlxClientOptions) {\n const logger = opts.logger ?? silentLogger;\n const adapter = opts.adapter ?? olxLebanonAdapter;\n this.adapter = adapter;\n this.endpoints = opts.endpoints;\n this.session = new SessionManager({\n baseUrl: opts.baseUrl,\n store: opts.sessionStore,\n logger,\n extraHosts: opts.extraHosts,\n captureHeaders: [...DEFAULT_CAPTURED_HEADERS, ...(opts.captureHeaders ?? [])],\n });\n this.limiter = new WriteRateLimiter(opts.writeLimits, opts.clock);\n this.http = new OlxHttpClient({\n baseUrl: opts.baseUrl,\n endpoints: opts.endpoints,\n session: this.session,\n limiter: this.limiter,\n logger,\n requestLog: opts.requestLog,\n transport: opts.transport,\n extraHeaders: adapter.requestHeaders?.bind(adapter),\n ...opts.http,\n });\n this.mapper = new CatalogMapper(opts.catalog);\n const images = new ImageUploader({\n http: this.http,\n adapter,\n allowedHosts: opts.allowedImageHosts,\n fetchImpl: opts.imageFetch,\n resolveHost: opts.resolveHost,\n ...opts.images,\n });\n this.listings = new ListingsApi({\n http: this.http,\n mapper: this.mapper,\n adapter,\n images,\n contact: opts.contact,\n session: this.session,\n logger,\n });\n this.ads = new AdsApi(this.listings);\n this.catalog = new OlxCatalog(opts.catalogApi);\n this.syncStore = opts.syncStore;\n this.sync = new SyncService({ listings: this.listings, store: opts.syncStore, mapper: this.mapper, logger });\n }\n\n /**\n * Builds a client from `loadConfig()` output, using file-based stores. Logs to stderr at\n * LOG_LEVEL unless you pass your own `logger` (pass `silentLogger` to mute it).\n */\n static fromConfig(config: OlxConfig, overrides: Partial<Omit<OlxClientOptions, 'baseUrl' | 'writeLimits'>> = {}): OlxClient {\n const logger = overrides.logger ?? createLogger(config.logLevel);\n // Both files are optional: the endpoints are built in, and ads can use OLX ids directly.\n if (!overrides.catalog && !existsSync(config.catalogFile)) {\n logger.debug('No catalog file; categoryKey / locationKey are unavailable (OLX ids still work)', {\n path: resolve(config.catalogFile),\n });\n }\n const endpoints =\n overrides.endpoints ?? (existsSync(config.endpointsFile) ? loadEndpoints(config.endpointsFile) : OLX_LB_ENDPOINTS);\n return new OlxClient({\n baseUrl: config.baseUrl,\n writeLimits: config.writeLimits,\n allowedImageHosts: config.allowedImageHosts,\n extraHosts: config.extraHosts,\n captureHeaders: config.captureHeaders,\n adapter: config.adapter === 'placeholder' ? placeholderAdapter : olxLebanonAdapter,\n contact: config.contact,\n endpoints,\n catalog: overrides.catalog ?? loadCatalog(config.catalogFile),\n sessionStore:\n overrides.sessionStore ??\n (config.accessToken\n ? new MemorySessionStore(sessionFromAccessToken(config.accessToken))\n : new FileSessionStore(config.sessionFile)),\n syncStore: overrides.syncStore ?? new JsonFileSyncStore(config.syncStoreFile),\n requestLog: overrides.requestLog ?? new FileRequestLog(config.requestLogFile),\n ...overrides,\n logger,\n });\n }\n\n /** Which endpoints have been mapped so far. */\n endpointStatus(): Record<EndpointName, boolean> {\n return endpointStatus(this.endpoints);\n }\n\n /** Endpoints the adapter needs that aren't in the endpoints file. */\n missingEndpoints(): EndpointName[] {\n const status = this.endpointStatus();\n return (this.adapter.requiredEndpoints ?? []).filter((n) => !status[n]);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { OlxConfig, WriteLimits } from './config.js';\nimport { endpointStatus, loadEndpointOverrides, loadEndpoints, type EndpointMap, type EndpointName } from './endpoints.js';\nimport { OlxHttpClient, type HttpClientOptions, type Transport } from './http/HttpClient.js';\nimport { WriteRateLimiter, type RateLimiterClock } from './http/rateLimiter.js';\nimport { placeholderAdapter, type OlxAdapter } from './listings/adapter.js';\nimport { OlxCatalog, type OlxCatalogOptions } from './catalog/OlxCatalog.js';\nimport { AccountApi } from './account/AccountApi.js';\nimport { AdsApi } from './listings/AdsApi.js';\nimport { OrdersApi } from './orders/OrdersApi.js';\nimport { OLX_LB_ENDPOINTS, olxLebanonAdapter } from './listings/olxLebanon.js';\nimport type { ContactSettings } from './listings/types.js';\nimport { ImageUploader, type ImageUploaderOptions } from './listings/images.js';\nimport { ListingsApi } from './listings/ListingsApi.js';\nimport { FileRequestLog, createLogger, silentLogger, type Logger, type RequestLog } from './logger.js';\nimport { CatalogMapper, loadCatalog, type Catalog } from './mapping/CatalogMapper.js';\nimport { DEFAULT_CAPTURED_HEADERS, SessionManager } from './session/SessionManager.js';\nimport { FileSessionStore, MemorySessionStore, sessionFromAccessToken, type SessionStore } from './session/storage.js';\nimport { SyncService } from './sync/SyncService.js';\nimport { JsonFileSyncStore, type SyncStore } from './sync/SyncStore.js';\n\nexport interface OlxClientOptions {\n baseUrl: string;\n endpoints: EndpointMap;\n catalog: Catalog;\n sessionStore: SessionStore;\n syncStore: SyncStore;\n writeLimits: WriteLimits;\n /** Wire format. Defaults to the built-in OLX Lebanon adapter. */\n adapter?: OlxAdapter;\n /** Who buyers contact; empty fields fall back to the account profile. */\n contact?: ContactSettings;\n logger?: Logger;\n requestLog?: RequestLog;\n transport?: Transport;\n clock?: RateLimiterClock;\n allowedImageHosts?: string[];\n imageFetch?: typeof fetch;\n resolveHost?: (host: string) => Promise<string[]>;\n /** Non-OLX hosts whose requests are watched for auth headers at login. */\n extraHosts?: string[];\n /** Header names captured at login, in addition to the defaults. */\n captureHeaders?: string[];\n /** Tuning for calls to OLX. */\n http?: Pick<HttpClientOptions, 'timeoutMs' | 'maxReadRetries' | 'retryBaseMs' | 'maxRetryDelayMs'>;\n /** Tuning for product image downloads. */\n images?: Pick<ImageUploaderOptions, 'maxBytes' | 'timeoutMs' | 'totalTimeoutMs' | 'maxRedirects'>;\n /** Options for `client.catalog` (public category and location data). */\n catalogApi?: OlxCatalogOptions;\n}\n\n/**\n * Everything wired together. Most apps need three parts:\n * - `ads`: create, edit, delete and list your ads by OLX id;\n * - `sync`: keep one ad per product of your store (create once, edit in place, skip unchanged);\n * - `catalog`: OLX's categories, their fields, and locations.\n * Create one with `createOlxClient()`.\n */\nexport class OlxClient {\n readonly ads: AdsApi;\n /** Your account: profile, wallet, package offers (read only). */\n readonly account: AccountApi;\n /** OLX delivery orders on your listings (experimental). */\n readonly orders: OrdersApi;\n readonly catalog: OlxCatalog;\n readonly session: SessionManager;\n readonly http: OlxHttpClient;\n readonly listings: ListingsApi;\n readonly sync: SyncService;\n readonly syncStore: SyncStore;\n readonly mapper: CatalogMapper;\n readonly limiter: WriteRateLimiter;\n readonly adapter: OlxAdapter;\n private readonly endpoints: EndpointMap;\n\n constructor(opts: OlxClientOptions) {\n const logger = opts.logger ?? silentLogger;\n const adapter = opts.adapter ?? olxLebanonAdapter;\n this.adapter = adapter;\n this.endpoints = opts.endpoints;\n this.session = new SessionManager({\n baseUrl: opts.baseUrl,\n store: opts.sessionStore,\n logger,\n extraHosts: opts.extraHosts,\n captureHeaders: [...DEFAULT_CAPTURED_HEADERS, ...(opts.captureHeaders ?? [])],\n });\n this.limiter = new WriteRateLimiter(opts.writeLimits, opts.clock);\n this.http = new OlxHttpClient({\n baseUrl: opts.baseUrl,\n endpoints: opts.endpoints,\n session: this.session,\n limiter: this.limiter,\n logger,\n requestLog: opts.requestLog,\n transport: opts.transport,\n extraHeaders: adapter.requestHeaders?.bind(adapter),\n ...opts.http,\n });\n this.mapper = new CatalogMapper(opts.catalog);\n const images = new ImageUploader({\n http: this.http,\n adapter,\n allowedHosts: opts.allowedImageHosts,\n fetchImpl: opts.imageFetch,\n resolveHost: opts.resolveHost,\n ...opts.images,\n });\n this.listings = new ListingsApi({\n http: this.http,\n mapper: this.mapper,\n adapter,\n images,\n contact: opts.contact,\n session: this.session,\n logger,\n });\n this.ads = new AdsApi(this.listings);\n this.account = new AccountApi(this.http, this.listings);\n this.orders = new OrdersApi(this.http);\n this.catalog = new OlxCatalog(opts.catalogApi);\n this.syncStore = opts.syncStore;\n this.sync = new SyncService({ listings: this.listings, store: opts.syncStore, mapper: this.mapper, logger });\n }\n\n /**\n * Builds a client from `loadConfig()` output, using file-based stores. Logs to stderr at\n * LOG_LEVEL unless you pass your own `logger` (pass `silentLogger` to mute it).\n */\n static fromConfig(config: OlxConfig, overrides: Partial<Omit<OlxClientOptions, 'baseUrl' | 'writeLimits'>> = {}): OlxClient {\n const logger = overrides.logger ?? createLogger(config.logLevel);\n // Both files are optional: the endpoints are built in, and ads can use OLX ids directly.\n if (!overrides.catalog && !existsSync(config.catalogFile)) {\n logger.debug('No catalog file; categoryKey / locationKey are unavailable (OLX ids still work)', {\n path: resolve(config.catalogFile),\n });\n }\n const endpoints =\n overrides.endpoints ??\n (config.adapter === 'placeholder'\n ? loadEndpoints(config.endpointsFile)\n : // An endpoints file only overrides what it lists; the rest stay built in.\n { ...OLX_LB_ENDPOINTS, ...loadEndpointOverrides(config.endpointsFile) });\n return new OlxClient({\n baseUrl: config.baseUrl,\n writeLimits: config.writeLimits,\n allowedImageHosts: config.allowedImageHosts,\n extraHosts: config.extraHosts,\n captureHeaders: config.captureHeaders,\n adapter: config.adapter === 'placeholder' ? placeholderAdapter : olxLebanonAdapter,\n contact: config.contact,\n endpoints,\n catalog: overrides.catalog ?? loadCatalog(config.catalogFile),\n sessionStore:\n overrides.sessionStore ??\n (config.accessToken\n ? new MemorySessionStore(sessionFromAccessToken(config.accessToken))\n : new FileSessionStore(config.sessionFile)),\n syncStore: overrides.syncStore ?? new JsonFileSyncStore(config.syncStoreFile),\n requestLog: overrides.requestLog ?? new FileRequestLog(config.requestLogFile),\n ...overrides,\n logger,\n });\n }\n\n /** Which endpoints have been mapped so far. */\n endpointStatus(): Record<EndpointName, boolean> {\n return endpointStatus(this.endpoints);\n }\n\n /** Endpoints the adapter needs that aren't in the endpoints file. */\n missingEndpoints(): EndpointName[] {\n const status = this.endpointStatus();\n return (this.adapter.requiredEndpoints ?? []).filter((n) => !status[n]);\n }\n}\n"]}
|
package/dist/endpoints.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { z } from 'zod';
|
|
|
4
4
|
* hard-coded: OLX Lebanon has no public API, so you record them from your own
|
|
5
5
|
* browser session (`npm run capture`) and put them in config/endpoints.json.
|
|
6
6
|
*/
|
|
7
|
-
export declare const ENDPOINT_NAMES: readonly ["createAd", "updateAd", "deleteAd", "getAd", "listMyAds", "uploadImage", "signImageUpload", "currentUser"];
|
|
7
|
+
export declare const ENDPOINT_NAMES: readonly ["createAd", "updateAd", "deleteAd", "getAd", "listMyAds", "uploadImage", "signImageUpload", "currentUser", "deactivateAd", "activateAd", "markAdSold", "republishAd", "adRejectionReason", "adMetrics", "wallet", "postingLimitOffers", "sellingOrders", "sellingOrdersByAd", "updateDeliveryOrder"];
|
|
8
8
|
export type EndpointName = (typeof ENDPOINT_NAMES)[number];
|
|
9
9
|
declare const SpecSchema: z.ZodObject<{
|
|
10
10
|
method: z.ZodEnum<{
|
|
@@ -23,8 +23,13 @@ export declare function emptyEndpoints(): EndpointMap;
|
|
|
23
23
|
/** Validates a parsed endpoints file. Keys starting with `_` are treated as comments. */
|
|
24
24
|
export declare function parseEndpoints(raw: unknown): EndpointMap;
|
|
25
25
|
export declare function loadEndpoints(file: string): EndpointMap;
|
|
26
|
+
/**
|
|
27
|
+
* Only the endpoints a file actually lists (an entry set to `null` switches one off), to lay
|
|
28
|
+
* over the built-in map. New endpoints in later SDK versions then work without editing the file.
|
|
29
|
+
*/
|
|
30
|
+
export declare function loadEndpointOverrides(file: string): Partial<EndpointMap>;
|
|
26
31
|
export declare function requireEndpoint(map: EndpointMap, name: EndpointName): EndpointSpec;
|
|
27
32
|
export declare function endpointStatus(map: EndpointMap): Record<EndpointName, boolean>;
|
|
28
33
|
export declare const isSafePathParam: (value: string) => boolean;
|
|
29
|
-
export declare function buildUrl(baseUrl: string, spec: EndpointSpec, params?: Record<string, string>, query?: Record<string, string | number | boolean | undefined
|
|
34
|
+
export declare function buildUrl(baseUrl: string, spec: EndpointSpec, params?: Record<string, string>, query?: Record<string, string | number | boolean | undefined | ReadonlyArray<string | number>>): string;
|
|
30
35
|
export {};
|