tixbit 0.1.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/LICENSE +21 -0
- package/README.md +237 -0
- package/dist/cli.js +646 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +317 -0
- package/package.json +68 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/** Configuration for the TixBit client. */
|
|
2
|
+
interface TixBitConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Base URL of the TixBit web app.
|
|
5
|
+
* @default "https://tixbit.com"
|
|
6
|
+
*/
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Optional request timeout in milliseconds.
|
|
10
|
+
* @default 15000
|
|
11
|
+
*/
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Optional API key for authenticated endpoints (future use).
|
|
15
|
+
*/
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
}
|
|
18
|
+
interface SearchEventsParams {
|
|
19
|
+
/** Free-text search query (e.g. "Hawks", "Taylor Swift"). */
|
|
20
|
+
query?: string;
|
|
21
|
+
/** City name (e.g. "Atlanta"). */
|
|
22
|
+
city?: string;
|
|
23
|
+
/** Two-letter US state code (e.g. "GA"). */
|
|
24
|
+
state?: string;
|
|
25
|
+
/** Category slug (e.g. "nba-basketball", "mlb-baseball"). */
|
|
26
|
+
category?: string;
|
|
27
|
+
/** ISO date string — only events on or after this date. */
|
|
28
|
+
startDate?: string;
|
|
29
|
+
/** ISO date string — only events on or before this date. */
|
|
30
|
+
endDate?: string;
|
|
31
|
+
/** Page number (1-indexed). @default 1 */
|
|
32
|
+
page?: number;
|
|
33
|
+
/** Page size. @default 25 */
|
|
34
|
+
size?: number;
|
|
35
|
+
}
|
|
36
|
+
interface TixBitEvent {
|
|
37
|
+
id: string;
|
|
38
|
+
external_event_id: string;
|
|
39
|
+
slug: string;
|
|
40
|
+
name: string;
|
|
41
|
+
date: string;
|
|
42
|
+
venue_name: string | null;
|
|
43
|
+
venue_city: string | null;
|
|
44
|
+
venue_state: string | null;
|
|
45
|
+
image_url: string | null;
|
|
46
|
+
category_name: string | null;
|
|
47
|
+
category_event_type: string | null;
|
|
48
|
+
has_listings: boolean;
|
|
49
|
+
inventory: {
|
|
50
|
+
total_available: number;
|
|
51
|
+
min_price: number;
|
|
52
|
+
max_price: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
interface SearchEventsResult {
|
|
56
|
+
events: TixBitEvent[];
|
|
57
|
+
pagination: {
|
|
58
|
+
page: number;
|
|
59
|
+
size: number;
|
|
60
|
+
total: number;
|
|
61
|
+
totalPages: number;
|
|
62
|
+
hasNext: boolean;
|
|
63
|
+
hasPrev: boolean;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
interface GetListingsParams {
|
|
67
|
+
/** External event ID (from search results). */
|
|
68
|
+
eventId: string;
|
|
69
|
+
/** Page size. @default 50 */
|
|
70
|
+
size?: number;
|
|
71
|
+
/** Page number. @default 1 */
|
|
72
|
+
page?: number;
|
|
73
|
+
/** Sort direction for price. @default "asc" */
|
|
74
|
+
orderByDirection?: "asc" | "desc";
|
|
75
|
+
}
|
|
76
|
+
interface TixBitListing {
|
|
77
|
+
id: string;
|
|
78
|
+
price: number;
|
|
79
|
+
quantity: number;
|
|
80
|
+
quantities_list: number[];
|
|
81
|
+
section: string | null;
|
|
82
|
+
row: string | null;
|
|
83
|
+
seat_numbers: string | null;
|
|
84
|
+
listing_hash: string;
|
|
85
|
+
notes: string | null;
|
|
86
|
+
delivery_method: string | null;
|
|
87
|
+
splits: number[];
|
|
88
|
+
raw: Record<string, unknown>;
|
|
89
|
+
}
|
|
90
|
+
interface GetListingsResult {
|
|
91
|
+
listings: TixBitListing[];
|
|
92
|
+
meta: {
|
|
93
|
+
total: number;
|
|
94
|
+
page: number;
|
|
95
|
+
size: number;
|
|
96
|
+
cacheSource?: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
interface CheckoutParams {
|
|
100
|
+
/** Listing ID to purchase (from getListings results). */
|
|
101
|
+
listingId: string;
|
|
102
|
+
/** Number of tickets to buy. */
|
|
103
|
+
quantity: number;
|
|
104
|
+
}
|
|
105
|
+
/** A checkout link that the user opens in a browser to complete purchase. */
|
|
106
|
+
interface CheckoutLink {
|
|
107
|
+
/** Full URL to the checkout page on tixbit.com. */
|
|
108
|
+
url: string;
|
|
109
|
+
/** Listing ID being purchased. */
|
|
110
|
+
listingId: string;
|
|
111
|
+
/** Ticket quantity. */
|
|
112
|
+
quantity: number;
|
|
113
|
+
}
|
|
114
|
+
interface BrowseEventsParams {
|
|
115
|
+
/** User latitude for location-aware results. */
|
|
116
|
+
latitude?: number;
|
|
117
|
+
/** User longitude for location-aware results. */
|
|
118
|
+
longitude?: number;
|
|
119
|
+
/** Preferred city. */
|
|
120
|
+
city?: string;
|
|
121
|
+
/** Preferred state. */
|
|
122
|
+
state?: string;
|
|
123
|
+
/** Number of results. @default 18 */
|
|
124
|
+
size?: number;
|
|
125
|
+
/** Category event type: SPORT, CONCERT, THEATER. */
|
|
126
|
+
categoryEventType?: "SPORT" | "CONCERT" | "THEATER" | "ALL";
|
|
127
|
+
}
|
|
128
|
+
interface BrowseEventsResult {
|
|
129
|
+
events: TixBitEvent[];
|
|
130
|
+
total: number;
|
|
131
|
+
}
|
|
132
|
+
interface GetSeatmapParams {
|
|
133
|
+
/** External event ID (from search results). */
|
|
134
|
+
eventId: string;
|
|
135
|
+
}
|
|
136
|
+
/** A single section in the venue's seating chart. */
|
|
137
|
+
interface SeatmapSection {
|
|
138
|
+
/** Section ID (e.g. "80841"). */
|
|
139
|
+
id: string;
|
|
140
|
+
/** Human-readable section name (e.g. "101", "FLOOR3", "201"). */
|
|
141
|
+
name: string;
|
|
142
|
+
/**
|
|
143
|
+
* Center coordinates of the section label on the map.
|
|
144
|
+
* Useful for understanding relative position.
|
|
145
|
+
*/
|
|
146
|
+
x: number;
|
|
147
|
+
y: number;
|
|
148
|
+
}
|
|
149
|
+
/** A zone grouping sections (e.g. "Section", "Floor", "Suite"). */
|
|
150
|
+
interface SeatmapZone {
|
|
151
|
+
id: string;
|
|
152
|
+
name: string;
|
|
153
|
+
sections: SeatmapSection[];
|
|
154
|
+
}
|
|
155
|
+
/** Venue metadata returned with the seatmap. */
|
|
156
|
+
interface SeatmapVenue {
|
|
157
|
+
name: string;
|
|
158
|
+
address?: string;
|
|
159
|
+
city: string;
|
|
160
|
+
region: string;
|
|
161
|
+
country: string;
|
|
162
|
+
time_zone: string;
|
|
163
|
+
}
|
|
164
|
+
/** Result from the seating chart API. */
|
|
165
|
+
interface SeatmapResult {
|
|
166
|
+
success: boolean;
|
|
167
|
+
event_id: string;
|
|
168
|
+
venue_id: string;
|
|
169
|
+
venue_name: string;
|
|
170
|
+
configuration_id: string;
|
|
171
|
+
configuration_name: string;
|
|
172
|
+
/** URL to the background SVG image of the venue map. */
|
|
173
|
+
background_image: string | null;
|
|
174
|
+
/** URL to the coordinates JSON (section polygons). */
|
|
175
|
+
coordinates_url: string | null;
|
|
176
|
+
has_coordinates: boolean;
|
|
177
|
+
capacity: number | null;
|
|
178
|
+
venue: SeatmapVenue;
|
|
179
|
+
/** Parsed section data from coordinates (when available). */
|
|
180
|
+
zones: SeatmapZone[];
|
|
181
|
+
/** All section names for quick reference. */
|
|
182
|
+
section_names: string[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
declare class TixBitClient {
|
|
186
|
+
private readonly baseUrl;
|
|
187
|
+
private readonly timeoutMs;
|
|
188
|
+
private readonly apiKey;
|
|
189
|
+
constructor(config?: TixBitConfig);
|
|
190
|
+
private request;
|
|
191
|
+
private qs;
|
|
192
|
+
/**
|
|
193
|
+
* Search for events by keyword, city, state, category, or date range.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* const results = await client.searchEvents({ query: "Hawks", state: "GA" });
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
searchEvents(params?: SearchEventsParams): Promise<SearchEventsResult>;
|
|
201
|
+
/**
|
|
202
|
+
* Browse upcoming events near a location (homepage-style).
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* const nearby = await client.browse({ city: "Atlanta", state: "GA" });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
browse(params?: BrowseEventsParams): Promise<BrowseEventsResult>;
|
|
210
|
+
/**
|
|
211
|
+
* Get available ticket listings for an event.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* const listings = await client.getListings({ eventId: "abc123" });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
getListings(params: GetListingsParams): Promise<GetListingsResult>;
|
|
219
|
+
/**
|
|
220
|
+
* Create a checkout link for a listing.
|
|
221
|
+
*
|
|
222
|
+
* Returns a URL to the TixBit checkout page where the user can
|
|
223
|
+
* sign in and complete their purchase (card, crypto, etc.).
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* const link = client.createCheckoutLink({
|
|
228
|
+
* listingId: "P2JO5OBX",
|
|
229
|
+
* quantity: 2,
|
|
230
|
+
* });
|
|
231
|
+
*
|
|
232
|
+
* console.log(link.url);
|
|
233
|
+
* // → "https://tixbit.com/checkout/process?listing=P2JO5OBX&quantity=2"
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
createCheckoutLink(params: CheckoutParams): CheckoutLink;
|
|
237
|
+
/**
|
|
238
|
+
* Build the direct URL to an event page on tixbit.com.
|
|
239
|
+
*/
|
|
240
|
+
eventUrl(slugOrId: string): string;
|
|
241
|
+
/**
|
|
242
|
+
* Get the seating chart for an event's venue.
|
|
243
|
+
*
|
|
244
|
+
* Returns section-level data including section names, positions, and
|
|
245
|
+
* the venue background image URL. Use this to help users understand
|
|
246
|
+
* where their tickets are located.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* const seatmap = await client.getSeatmap({ eventId: "4BKJMDZ" });
|
|
251
|
+
* console.log(seatmap.venue_name); // "State Farm Arena"
|
|
252
|
+
* console.log(seatmap.section_names); // ["101", "102", ...]
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
getSeatmap(params: GetSeatmapParams): Promise<SeatmapResult>;
|
|
256
|
+
/**
|
|
257
|
+
* Fetch the coordinates JSON and parse it into zones/sections.
|
|
258
|
+
*/
|
|
259
|
+
private fetchAndParseCoordinates;
|
|
260
|
+
}
|
|
261
|
+
declare class TixBitApiError extends Error {
|
|
262
|
+
readonly status: number;
|
|
263
|
+
readonly url: string;
|
|
264
|
+
constructor(message: string, status: number, url: string);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { type BrowseEventsParams, type BrowseEventsResult, type CheckoutLink, type CheckoutParams, type GetListingsParams, type GetListingsResult, type GetSeatmapParams, type SearchEventsParams, type SearchEventsResult, type SeatmapResult, type SeatmapSection, type SeatmapVenue, type SeatmapZone, TixBitApiError, TixBitClient, type TixBitConfig, type TixBitEvent, type TixBitListing };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://tixbit.com";
|
|
3
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
4
|
+
var USER_AGENT = "@tixbit/sdk";
|
|
5
|
+
var TixBitClient = class {
|
|
6
|
+
baseUrl;
|
|
7
|
+
timeoutMs;
|
|
8
|
+
apiKey;
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
11
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
12
|
+
this.apiKey = config.apiKey;
|
|
13
|
+
}
|
|
14
|
+
// ── HTTP helpers ──────────────────────────────────────────────────────────
|
|
15
|
+
async request(path, init) {
|
|
16
|
+
const url = `${this.baseUrl}${path}`;
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
19
|
+
const headers = {
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
"User-Agent": USER_AGENT,
|
|
22
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
...init,
|
|
27
|
+
headers: { ...headers, ...init?.headers },
|
|
28
|
+
signal: controller.signal
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
const text = await res.text().catch(() => "");
|
|
32
|
+
throw new TixBitApiError(
|
|
33
|
+
`${res.status} ${res.statusText}: ${text.slice(0, 300)}`,
|
|
34
|
+
res.status,
|
|
35
|
+
url
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return await res.json();
|
|
39
|
+
} finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
qs(params) {
|
|
44
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
|
|
45
|
+
return entries.length ? `?${entries.join("&")}` : "";
|
|
46
|
+
}
|
|
47
|
+
// ── Search Events ─────────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Search for events by keyword, city, state, category, or date range.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const results = await client.searchEvents({ query: "Hawks", state: "GA" });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
async searchEvents(params = {}) {
|
|
57
|
+
const query = this.qs({
|
|
58
|
+
q: params.query,
|
|
59
|
+
city: params.city,
|
|
60
|
+
state: params.state,
|
|
61
|
+
category: params.category,
|
|
62
|
+
startDate: params.startDate,
|
|
63
|
+
endDate: params.endDate,
|
|
64
|
+
page: params.page,
|
|
65
|
+
size: params.size ?? 25
|
|
66
|
+
});
|
|
67
|
+
const data = await this.request(`/api/events/search${query}`);
|
|
68
|
+
return {
|
|
69
|
+
events: (data.events ?? []).map(normalizeEvent),
|
|
70
|
+
pagination: data.pagination
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// ── Browse (Location-Aware) ───────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Browse upcoming events near a location (homepage-style).
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const nearby = await client.browse({ city: "Atlanta", state: "GA" });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
async browse(params = {}) {
|
|
83
|
+
const query = this.qs({
|
|
84
|
+
size: params.size ?? 18,
|
|
85
|
+
context: "homepage",
|
|
86
|
+
recommendation: "upcoming",
|
|
87
|
+
nearLat: params.latitude,
|
|
88
|
+
nearLng: params.longitude,
|
|
89
|
+
preferCity: params.city,
|
|
90
|
+
preferState: params.state,
|
|
91
|
+
categoryEventType: params.categoryEventType
|
|
92
|
+
});
|
|
93
|
+
const data = await this.request(`/api/events${query}`);
|
|
94
|
+
return {
|
|
95
|
+
events: (data.events ?? []).map(normalizeEvent),
|
|
96
|
+
total: data.total
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// ── Listings ──────────────────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Get available ticket listings for an event.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const listings = await client.getListings({ eventId: "abc123" });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
async getListings(params) {
|
|
109
|
+
const query = this.qs({
|
|
110
|
+
size: params.size ?? 50,
|
|
111
|
+
page: params.page ?? 1,
|
|
112
|
+
order_by_direction: params.orderByDirection ?? "asc"
|
|
113
|
+
});
|
|
114
|
+
const data = await this.request(`/api/events/${encodeURIComponent(params.eventId)}/listings${query}`);
|
|
115
|
+
const listings = (data.data ?? []).map(normalizeListing);
|
|
116
|
+
return {
|
|
117
|
+
listings,
|
|
118
|
+
meta: {
|
|
119
|
+
total: data.meta?.total ?? listings.length,
|
|
120
|
+
page: data.meta?.page ?? params.page ?? 1,
|
|
121
|
+
size: data.meta?.size ?? params.size ?? 50,
|
|
122
|
+
cacheSource: data.meta?.cacheSource
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// ── Checkout Link ──────────────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Create a checkout link for a listing.
|
|
129
|
+
*
|
|
130
|
+
* Returns a URL to the TixBit checkout page where the user can
|
|
131
|
+
* sign in and complete their purchase (card, crypto, etc.).
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const link = client.createCheckoutLink({
|
|
136
|
+
* listingId: "P2JO5OBX",
|
|
137
|
+
* quantity: 2,
|
|
138
|
+
* });
|
|
139
|
+
*
|
|
140
|
+
* console.log(link.url);
|
|
141
|
+
* // → "https://tixbit.com/checkout/process?listing=P2JO5OBX&quantity=2"
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
createCheckoutLink(params) {
|
|
145
|
+
const quantity = Math.max(1, Math.min(8, Math.round(params.quantity)));
|
|
146
|
+
const url = `${this.baseUrl}/checkout/process?listing=${encodeURIComponent(params.listingId)}&quantity=${quantity}`;
|
|
147
|
+
return {
|
|
148
|
+
url,
|
|
149
|
+
listingId: params.listingId,
|
|
150
|
+
quantity
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// ── Event URL helper ──────────────────────────────────────────────────────
|
|
154
|
+
/**
|
|
155
|
+
* Build the direct URL to an event page on tixbit.com.
|
|
156
|
+
*/
|
|
157
|
+
eventUrl(slugOrId) {
|
|
158
|
+
return `${this.baseUrl}/events/${slugOrId}`;
|
|
159
|
+
}
|
|
160
|
+
// ── Seatmap ───────────────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Get the seating chart for an event's venue.
|
|
163
|
+
*
|
|
164
|
+
* Returns section-level data including section names, positions, and
|
|
165
|
+
* the venue background image URL. Use this to help users understand
|
|
166
|
+
* where their tickets are located.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* const seatmap = await client.getSeatmap({ eventId: "4BKJMDZ" });
|
|
171
|
+
* console.log(seatmap.venue_name); // "State Farm Arena"
|
|
172
|
+
* console.log(seatmap.section_names); // ["101", "102", ...]
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
async getSeatmap(params) {
|
|
176
|
+
const data = await this.request(`/api/events/${encodeURIComponent(params.eventId)}/seating-chart`);
|
|
177
|
+
let zones = [];
|
|
178
|
+
let sectionNames = [];
|
|
179
|
+
if (data.has_coordinates && data.coordinates) {
|
|
180
|
+
try {
|
|
181
|
+
zones = await this.fetchAndParseCoordinates(data.coordinates);
|
|
182
|
+
sectionNames = zones.flatMap(
|
|
183
|
+
(z) => z.sections.map((s) => s.name)
|
|
184
|
+
);
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
success: data.success,
|
|
190
|
+
event_id: data.event_id,
|
|
191
|
+
venue_id: data.venue_id,
|
|
192
|
+
venue_name: data.venue_name,
|
|
193
|
+
configuration_id: data.configuration_id,
|
|
194
|
+
configuration_name: data.configuration_name,
|
|
195
|
+
background_image: data.background_image ?? null,
|
|
196
|
+
coordinates_url: data.coordinates ?? null,
|
|
197
|
+
has_coordinates: data.has_coordinates,
|
|
198
|
+
capacity: data.capacity ?? null,
|
|
199
|
+
venue: data.venue_data,
|
|
200
|
+
zones,
|
|
201
|
+
section_names: sectionNames
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Fetch the coordinates JSON and parse it into zones/sections.
|
|
206
|
+
*/
|
|
207
|
+
async fetchAndParseCoordinates(coordinatesUrl) {
|
|
208
|
+
const fullUrl = coordinatesUrl.startsWith("http") ? coordinatesUrl : `${this.baseUrl}${coordinatesUrl}`;
|
|
209
|
+
const controller = new AbortController();
|
|
210
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
211
|
+
try {
|
|
212
|
+
const res = await fetch(fullUrl, {
|
|
213
|
+
headers: {
|
|
214
|
+
Accept: "application/json",
|
|
215
|
+
"User-Agent": USER_AGENT
|
|
216
|
+
},
|
|
217
|
+
signal: controller.signal
|
|
218
|
+
});
|
|
219
|
+
if (!res.ok) return [];
|
|
220
|
+
const coords = await res.json();
|
|
221
|
+
if (!coords.zones) return [];
|
|
222
|
+
return coords.zones.map((zone) => ({
|
|
223
|
+
id: zone.id,
|
|
224
|
+
name: zone.name,
|
|
225
|
+
sections: (zone.sections ?? []).map((section) => {
|
|
226
|
+
const label = section.labels?.[0];
|
|
227
|
+
return {
|
|
228
|
+
id: section.id,
|
|
229
|
+
name: section.name,
|
|
230
|
+
x: label?.x ?? 0,
|
|
231
|
+
y: label?.y ?? 0
|
|
232
|
+
};
|
|
233
|
+
})
|
|
234
|
+
}));
|
|
235
|
+
} finally {
|
|
236
|
+
clearTimeout(timer);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function normalizeEvent(raw) {
|
|
241
|
+
const e = raw;
|
|
242
|
+
return {
|
|
243
|
+
id: str(e.id) ?? str(e.external_event_id) ?? "",
|
|
244
|
+
external_event_id: str(e.external_event_id) ?? str(e.externalEventId) ?? str(e.id) ?? "",
|
|
245
|
+
slug: str(e.slug) ?? str(e.external_event_id) ?? "",
|
|
246
|
+
name: str(e.name) ?? str(e.performer) ?? "",
|
|
247
|
+
date: resolveDate(e),
|
|
248
|
+
venue_name: str(e.venue_name) ?? str(e.venueName) ?? null,
|
|
249
|
+
venue_city: str(e.venue_city) ?? str(e.venueCity) ?? null,
|
|
250
|
+
venue_state: str(e.venue_state) ?? str(e.venueState) ?? null,
|
|
251
|
+
image_url: str(e.image_url) ?? str(e.imageUrl) ?? null,
|
|
252
|
+
category_name: str(e.category_name) ?? str(e.categoryName) ?? null,
|
|
253
|
+
category_event_type: str(e.category_event_type) ?? str(e.categoryEventType) ?? null,
|
|
254
|
+
has_listings: Boolean(e.has_listings),
|
|
255
|
+
inventory: normalizeInventory(e.inventory)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function normalizeInventory(raw) {
|
|
259
|
+
if (!raw || typeof raw !== "object") {
|
|
260
|
+
return { total_available: 0, min_price: 0, max_price: 0 };
|
|
261
|
+
}
|
|
262
|
+
const inv = raw;
|
|
263
|
+
return {
|
|
264
|
+
total_available: num(inv.total_available) ?? 0,
|
|
265
|
+
min_price: num(inv.min_price) ?? 0,
|
|
266
|
+
max_price: num(inv.max_price) ?? 0
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function normalizeListing(raw) {
|
|
270
|
+
const outer = raw;
|
|
271
|
+
const attrs = outer.attributes ?? outer;
|
|
272
|
+
return {
|
|
273
|
+
id: str(outer.id) ?? str(attrs.id) ?? "",
|
|
274
|
+
// price_per_ticket is the fee-inclusive per-ticket price (in dollars, not cents)
|
|
275
|
+
price: num(attrs.price_per_ticket) ?? num(attrs.price) ?? 0,
|
|
276
|
+
quantity: num(attrs.quantity) ?? num(attrs.available_quantity) ?? 0,
|
|
277
|
+
quantities_list: Array.isArray(attrs.quantities_list) ? attrs.quantities_list : [],
|
|
278
|
+
section: str(attrs.section) ?? null,
|
|
279
|
+
row: str(attrs.row) ?? null,
|
|
280
|
+
seat_numbers: str(attrs.seat_numbers) ?? null,
|
|
281
|
+
listing_hash: str(attrs.listing_hash) ?? "",
|
|
282
|
+
notes: str(attrs.notes) ?? null,
|
|
283
|
+
delivery_method: str(attrs.delivery_method) ?? str(attrs.delivery_type) ?? null,
|
|
284
|
+
splits: Array.isArray(attrs.splits) ? attrs.splits : [],
|
|
285
|
+
raw: outer
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function resolveDate(e) {
|
|
289
|
+
if (typeof e.date === "string") return e.date;
|
|
290
|
+
if (typeof e.date === "number") return new Date(e.date).toISOString();
|
|
291
|
+
if (e.date && typeof e.date === "object") {
|
|
292
|
+
const d = e.date;
|
|
293
|
+
if (d.month && d.day && d.year) {
|
|
294
|
+
return `${d.month} ${d.day}, ${d.year}`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (typeof e.date_ms === "number") return new Date(e.date_ms).toISOString();
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
function str(v) {
|
|
301
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
302
|
+
}
|
|
303
|
+
function num(v) {
|
|
304
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
305
|
+
}
|
|
306
|
+
var TixBitApiError = class extends Error {
|
|
307
|
+
constructor(message, status, url) {
|
|
308
|
+
super(message);
|
|
309
|
+
this.status = status;
|
|
310
|
+
this.url = url;
|
|
311
|
+
this.name = "TixBitApiError";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
export {
|
|
315
|
+
TixBitApiError,
|
|
316
|
+
TixBitClient
|
|
317
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tixbit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Search events, view seatmaps, browse listings, and buy tickets on TixBit",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"tixbit": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/*.js",
|
|
19
|
+
"dist/*.d.ts",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^13.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"tsup": "^8.4.0",
|
|
36
|
+
"typescript": "^5.7.0",
|
|
37
|
+
"vitest": "^3.1.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"tixbit",
|
|
44
|
+
"tickets",
|
|
45
|
+
"events",
|
|
46
|
+
"seatmap",
|
|
47
|
+
"seating-chart",
|
|
48
|
+
"cli",
|
|
49
|
+
"sdk",
|
|
50
|
+
"concerts",
|
|
51
|
+
"sports",
|
|
52
|
+
"nba",
|
|
53
|
+
"mlb",
|
|
54
|
+
"nfl"
|
|
55
|
+
],
|
|
56
|
+
"homepage": "https://tixbit.com",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/tixbit/sdk.git"
|
|
61
|
+
},
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/tixbit/sdk/issues"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|