playttm 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.js ADDED
@@ -0,0 +1,830 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.displayQRCode = exports.ThaiTicketMajor = void 0;
49
+ const axios_1 = __importDefault(require("axios"));
50
+ const cheerio = __importStar(require("cheerio"));
51
+ const axios_cookiejar_support_1 = require("axios-cookiejar-support");
52
+ const tough_cookie_1 = require("tough-cookie");
53
+ const qrcode_terminal_1 = __importDefault(require("qrcode-terminal"));
54
+ // ==========================================
55
+ // Main Library Class
56
+ // ==========================================
57
+ class ThaiTicketMajor {
58
+ constructor(config = {}) {
59
+ this.jar = new tough_cookie_1.CookieJar();
60
+ this.endpoint = config.endpoint || "https://booking.thaiticketmajor.com/booking/3m";
61
+ this.onProgressCallback = config.onProgress;
62
+ const defaultUA = config.userAgent ||
63
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
64
+ this.defaultHeaders = Object.assign({ accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "accept-language": "th,en-US;q=0.9,en;q=0.8", "user-agent": defaultUA, referer: "https://booking.thaiticketmajor.com" }, (config.headers || {}));
65
+ if (config.cookies) {
66
+ this.defaultHeaders["cookie"] = config.cookies;
67
+ }
68
+ this.client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({
69
+ jar: this.jar,
70
+ withCredentials: true,
71
+ timeout: config.timeout || 30000,
72
+ }));
73
+ }
74
+ emitProgress(step, message, data) {
75
+ if (this.onProgressCallback) {
76
+ this.onProgressCallback(step, message, data);
77
+ }
78
+ }
79
+ // ==========================================
80
+ // Low-Level API Methods
81
+ // ==========================================
82
+ /**
83
+ * Step 1: ดึงรอบการแสดงทั้งหมดจากหน้าหลักคอนเสิร์ต
84
+ */
85
+ getRounds(eventUrl) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ this.emitProgress("GET_ROUNDS", `Fetching rounds from ${eventUrl}`);
88
+ const response = yield this.client.get(eventUrl, {
89
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { referer: "https://www.thaiticketmajor.com/all-event/" }),
90
+ });
91
+ const $ = cheerio.load(response.data);
92
+ const rounds = [];
93
+ $("a[data-button]").each((_, el) => {
94
+ var _a, _b, _c, _d;
95
+ const link = $(el);
96
+ const href = (_a = link.attr("href")) !== null && _a !== void 0 ? _a : "";
97
+ const onclick = (_b = link.attr("onclick")) !== null && _b !== void 0 ? _b : "";
98
+ const dataUrl = (_c = link.attr("data-url")) !== null && _c !== void 0 ? _c : "";
99
+ const dataHref = (_d = link.attr("data-href")) !== null && _d !== void 0 ? _d : "";
100
+ const allLinkAttrs = `${href} ${onclick} ${dataUrl} ${dataHref}`;
101
+ const queryMatch = allLinkAttrs.match(/query=([0-9a-zA-Z_-]+)/);
102
+ const query = queryMatch ? queryMatch[1] : "";
103
+ const time = link.find(".item-show").text().trim();
104
+ const date = link.closest(".row").find(".date").text().trim();
105
+ const isDisabled = link.is(":disabled") || link.attr("disabled") !== undefined || !query;
106
+ rounds.push({
107
+ id: query || link.attr("data-button") || "",
108
+ label: `${date} ${time}`.trim(),
109
+ date,
110
+ time,
111
+ url: href,
112
+ query,
113
+ isDisabled,
114
+ });
115
+ });
116
+ return rounds;
117
+ });
118
+ }
119
+ /**
120
+ * Step 2: ตรวจสอบและยอมรับเงื่อนไขการซื้อบัตร
121
+ */
122
+ verifyCheckCondition(query) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ if (!query) {
125
+ throw new Error("Query ID is missing. The selected round might be disabled or not open for sale yet.");
126
+ }
127
+ this.emitProgress("VERIFY_CONDITION", `Verifying condition for query ${query}`);
128
+ const response = yield this.client.post(`${this.endpoint}/verify_checkcondition.php`, new URLSearchParams({
129
+ rdagree: "1",
130
+ rdId: "",
131
+ autopopup: "",
132
+ query: query,
133
+ }), { headers: this.defaultHeaders });
134
+ return response.data;
135
+ });
136
+ }
137
+ /**
138
+ * Step 3: ดึงข้อมูลโซน, Token (k, tk) และรอบการแสดง
139
+ */
140
+ getZones(query) {
141
+ return __awaiter(this, void 0, void 0, function* () {
142
+ this.emitProgress("GET_ZONES", `Fetching zone data and tokens for query ${query}`);
143
+ const response = yield this.client.get(`${this.endpoint}/zones.php?query=${query}`, {
144
+ headers: this.defaultHeaders,
145
+ });
146
+ const $ = cheerio.load(response.data);
147
+ const rawInputs = {};
148
+ $("#frm input[type='hidden']").each((_, el) => {
149
+ var _a;
150
+ const name = $(el).attr("name");
151
+ const value = (_a = $(el).attr("value")) !== null && _a !== void 0 ? _a : "";
152
+ if (name) {
153
+ rawInputs[name] = value;
154
+ }
155
+ });
156
+ const rounds = $("#rdId option")
157
+ .filter((_, el) => !!$(el).attr("value"))
158
+ .map((_, el) => {
159
+ var _a;
160
+ const opt = $(el);
161
+ return {
162
+ id: (_a = opt.attr("value")) !== null && _a !== void 0 ? _a : "",
163
+ label: opt.text().trim(),
164
+ };
165
+ })
166
+ .get();
167
+ return {
168
+ k: rawInputs["k"] || "",
169
+ tk: rawInputs["tk"] || "",
170
+ query,
171
+ rounds,
172
+ rawInputs,
173
+ };
174
+ });
175
+ }
176
+ /**
177
+ * Step 4: ตรวจสอบสถานะที่นั่งว่างของแต่ละโซน
178
+ */
179
+ getZoneAvailability(roundId, tk, k, query) {
180
+ return __awaiter(this, void 0, void 0, function* () {
181
+ this.emitProgress("GET_ZONE_AVAIL", `Checking zone availability for round ${roundId}`);
182
+ const response = yield this.client.get(`${this.endpoint}/zonesavail.php?round=${roundId}&tk=${tk}`, {
183
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { referer: `${this.endpoint}/zones.php?query=${query}` }),
184
+ });
185
+ const $ = cheerio.load(response.data);
186
+ const zones = [];
187
+ $(".table tbody tr").each((_, el) => {
188
+ var _a, _b, _c, _d;
189
+ const row = $(el);
190
+ const zoneId = (_a = row.find("td:nth-child(1) a").attr("id")) !== null && _a !== void 0 ? _a : "";
191
+ const onclick = (_b = row.attr("onclick")) !== null && _b !== void 0 ? _b : "";
192
+ const match = onclick.match(/gonextstep\('([^']+)','([^']+)'/);
193
+ const page = ((_c = match === null || match === void 0 ? void 0 : match[1]) !== null && _c !== void 0 ? _c : "");
194
+ const zoneValue = (_d = match === null || match === void 0 ? void 0 : match[2]) !== null && _d !== void 0 ? _d : "";
195
+ const statusText = row.find("td:nth-child(2) a").text().trim();
196
+ let available = 0;
197
+ let isAvailable = false;
198
+ if (statusText.toLowerCase() === "available") {
199
+ available = 1;
200
+ isAvailable = true;
201
+ }
202
+ else {
203
+ const num = Number(statusText);
204
+ if (!Number.isNaN(num) && num > 0) {
205
+ available = num;
206
+ isAvailable = true;
207
+ }
208
+ }
209
+ zones.push({
210
+ id: zoneId,
211
+ value: zoneValue,
212
+ available,
213
+ page,
214
+ isAvailable,
215
+ });
216
+ });
217
+ return zones;
218
+ });
219
+ }
220
+ // --- Flow บัตรนั่ง (Fixed Seats) ---
221
+ /**
222
+ * Step 5 (Fixed): ดึงผังที่นั่งว่างและ form payload
223
+ */
224
+ getFixedSeats(k, zone, roundId) {
225
+ return __awaiter(this, void 0, void 0, function* () {
226
+ this.emitProgress("GET_FIXED_SEATS", `Fetching seat layout for zone ${zone}`);
227
+ const response = yield this.client.get(`${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`, {
228
+ headers: this.defaultHeaders,
229
+ });
230
+ const $ = cheerio.load(response.data);
231
+ const seats = $("#tableseats .seatuncheck")
232
+ .map((_, el) => {
233
+ var _a, _b, _c;
234
+ const seat = $(el);
235
+ return {
236
+ id: (_a = seat.attr("id")) !== null && _a !== void 0 ? _a : "",
237
+ seat: (_b = seat.attr("data-seat")) !== null && _b !== void 0 ? _b : "",
238
+ seatk: (_c = seat.attr("data-seatk")) !== null && _c !== void 0 ? _c : "",
239
+ };
240
+ })
241
+ .get();
242
+ const form = {};
243
+ $("#frmPayment input").each((_, el) => {
244
+ var _a;
245
+ const name = $(el).attr("name");
246
+ if (name) {
247
+ form[name] = (_a = $(el).attr("value")) !== null && _a !== void 0 ? _a : "";
248
+ }
249
+ });
250
+ return { seats, form };
251
+ });
252
+ }
253
+ /**
254
+ * Step 6 (Fixed): Validate ที่นั่งทีละที่นั่งตามลำดับ
255
+ */
256
+ validateFixedSeats(k_1, zone_1, payload_1, chkSeats_1) {
257
+ return __awaiter(this, arguments, void 0, function* (k, zone, payload, chkSeats, bookType = "fix") {
258
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
259
+ this.emitProgress("VALIDATE_FIXED_SEATS", `Validating ${chkSeats.length} fixed seats`);
260
+ let lastResponse = null;
261
+ const accumulatedSeats = [];
262
+ for (let i = 0; i < chkSeats.length; i++) {
263
+ const currentSeat = chkSeats[i];
264
+ const priceVal = currentSeat.price || "5335";
265
+ const seatEntry = `${currentSeat.seat}*${priceVal}`;
266
+ accumulatedSeats.unshift(seatEntry);
267
+ const formData = new URLSearchParams();
268
+ for (const seat of accumulatedSeats) {
269
+ formData.append("chkSeats[]", seat);
270
+ }
271
+ formData.append("ehId", (_a = payload.ehId) !== null && _a !== void 0 ? _a : "");
272
+ formData.append("curentdate", (_b = payload.curentdate) !== null && _b !== void 0 ? _b : "");
273
+ formData.append("max_payment", (_c = payload.max_payment) !== null && _c !== void 0 ? _c : "6");
274
+ formData.append("payment_cnt", (_d = payload.payment_cnt) !== null && _d !== void 0 ? _d : "0");
275
+ formData.append("paytype", (_e = payload.paytype) !== null && _e !== void 0 ? _e : "BOAI");
276
+ formData.append("performance", (_f = payload.performance) !== null && _f !== void 0 ? _f : "");
277
+ formData.append("pricelist", "");
278
+ formData.append("rdId", (_g = payload.rdId) !== null && _g !== void 0 ? _g : "");
279
+ formData.append("seatlist", "");
280
+ formData.append("showdate", (_h = payload.showdate) !== null && _h !== void 0 ? _h : "");
281
+ formData.append("showtime", (_j = payload.showtime) !== null && _j !== void 0 ? _j : "");
282
+ formData.append("venue", (_k = payload.venue) !== null && _k !== void 0 ? _k : "");
283
+ formData.append("zone", (_l = payload.zone) !== null && _l !== void 0 ? _l : "");
284
+ formData.append("zoneDesc", (_m = payload.zoneDesc) !== null && _m !== void 0 ? _m : "");
285
+ formData.append("travelChild1", "");
286
+ formData.append("travelChild2", "");
287
+ formData.append("travelChild2", "");
288
+ formData.append("enroll_val", (_o = payload.enroll_val) !== null && _o !== void 0 ? _o : "");
289
+ formData.append("dval", (_p = payload.dval) !== null && _p !== void 0 ? _p : "");
290
+ formData.append("companyid", (_q = payload.companyid) !== null && _q !== void 0 ? _q : "");
291
+ formData.append("ks", (_r = payload.ks) !== null && _r !== void 0 ? _r : "");
292
+ formData.append("inclvat", "");
293
+ const seatParts = currentSeat.seat.split("-");
294
+ const row = seatParts[0];
295
+ const seatNo = seatParts[1] || "";
296
+ formData.append("row", row);
297
+ formData.append("seat", seatNo);
298
+ formData.append("book_type", bookType);
299
+ const response = yield this.client.post(`${this.endpoint}/validateseat.php?k=${k}&zw=${zone}`, formData.toString(), {
300
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}` }),
301
+ });
302
+ lastResponse = response.data;
303
+ if (!lastResponse.result) {
304
+ throw new Error(`Validate seat failed (${currentSeat.seat}): ${lastResponse.message || "Unknown error"}`);
305
+ }
306
+ }
307
+ return lastResponse;
308
+ });
309
+ }
310
+ /**
311
+ * Step 7 (Fixed): จองที่นั่ง Fixed
312
+ */
313
+ bookFixedSeats(k, payload, seats) {
314
+ return __awaiter(this, void 0, void 0, function* () {
315
+ this.emitProgress("BOOK_FIXED_SEATS", `Booking fixed seats`);
316
+ let pricelistvalue = "";
317
+ let seatlistvalue = "";
318
+ let seatklistvalue = "";
319
+ seats.forEach((s) => {
320
+ const parts = s.seat.split("*");
321
+ pricelistvalue += `${parts[1] || ""},`;
322
+ seatlistvalue += `${parts[0]},`;
323
+ seatklistvalue += `${s.seatk},`;
324
+ });
325
+ const bodyParams = new URLSearchParams();
326
+ bodyParams.set("ehId", payload.ehId || "");
327
+ bodyParams.set("curentdate", payload.curentdate || "");
328
+ bodyParams.set("max_payment", payload.max_payment || "6");
329
+ bodyParams.set("payment_cnt", payload.payment_cnt || "0");
330
+ bodyParams.set("paytype", payload.paytype || "BOAI");
331
+ bodyParams.set("performance", payload.performance || "");
332
+ bodyParams.set("pricelist", pricelistvalue);
333
+ bodyParams.set("rdId", payload.rdId || "");
334
+ bodyParams.set("seatlist", seatlistvalue);
335
+ bodyParams.set("showdate", payload.showdate || "");
336
+ bodyParams.set("showtime", payload.showtime || "");
337
+ bodyParams.set("venue", payload.venue || "");
338
+ bodyParams.set("zone", payload.zone || "");
339
+ bodyParams.set("zoneDesc", payload.zoneDesc || "");
340
+ bodyParams.set("travelChild1", "");
341
+ bodyParams.set("travelChild2", "");
342
+ bodyParams.append("travelChild2", "");
343
+ bodyParams.set("enroll_val", String(seats.length));
344
+ bodyParams.set("dval", payload.dval || "");
345
+ bodyParams.set("companyid", payload.companyid || "");
346
+ bodyParams.set("ks", "");
347
+ bodyParams.set("inclvat", "");
348
+ bodyParams.set("seatklist", seatklistvalue);
349
+ const response = yield this.client.post(`${this.endpoint}/bookingseats.php?k=${k}`, bodyParams.toString(), {
350
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { accept: "application/json, text/javascript, */*; q=0.01", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`, origin: "https://booking.thaiticketmajor.com" }),
351
+ });
352
+ return {
353
+ data: response.data,
354
+ nextPayload: bodyParams,
355
+ };
356
+ });
357
+ }
358
+ // --- Flow บัตรยืน (Festival / Standing) ---
359
+ /**
360
+ * Step 5 (Festival): ดึงข้อมูลหน้าบัตรยืน
361
+ */
362
+ getFestival(k, tk, query, zone, roundId) {
363
+ return __awaiter(this, void 0, void 0, function* () {
364
+ this.emitProgress("GET_FESTIVAL", `Fetching festival zone ${zone}`);
365
+ const response = yield this.client.get(`${this.endpoint}/festival.php?k=${k}&zone=${zone}&round=${roundId}`, {
366
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { referer: `${this.endpoint}/zones.php?rdId=${roundId}&k=${k}&tk=${tk}&query=${query}` }),
367
+ });
368
+ const $ = cheerio.load(response.data);
369
+ const formData = {};
370
+ $("#frm input").each((_, el) => {
371
+ var _a;
372
+ const name = $(el).attr("name");
373
+ if (name) {
374
+ formData[name] = (_a = $(el).attr("value")) !== null && _a !== void 0 ? _a : "";
375
+ }
376
+ });
377
+ return formData;
378
+ });
379
+ }
380
+ /**
381
+ * Step 6 (Festival): Validate บัตรยืน
382
+ */
383
+ validateFestivalSeats(k, payload, ticketCount) {
384
+ return __awaiter(this, void 0, void 0, function* () {
385
+ this.emitProgress("VALIDATE_FESTIVAL", `Validating ${ticketCount} festival tickets`);
386
+ const preparePayload = Object.assign(Object.assign({}, payload), { book_cnt: ticketCount.toString(), book_type: "fest" });
387
+ const formData = new URLSearchParams(preparePayload);
388
+ const response = yield this.client.post(`${this.endpoint}/validateseat.php?k=${k}&zw=${payload.zone}`, formData.toString(), {
389
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}` }),
390
+ });
391
+ return response.data;
392
+ });
393
+ }
394
+ /**
395
+ * Step 7 (Festival): จองบัตรยืน
396
+ */
397
+ bookFestivalSeats(k, payload, ticketCount) {
398
+ return __awaiter(this, void 0, void 0, function* () {
399
+ this.emitProgress("BOOK_FESTIVAL", `Booking ${ticketCount} festival tickets`);
400
+ const preparePayload = Object.assign(Object.assign({}, payload), { book_cnt: ticketCount.toString() });
401
+ const formData = new URLSearchParams(preparePayload);
402
+ const response = yield this.client.post(`${this.endpoint}/bookingfestival.php?k=${k}`, formData.toString(), {
403
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}` }),
404
+ });
405
+ return response.data;
406
+ });
407
+ }
408
+ // --- Flow การลงทะเบียนผู้เข้าชม (Enrollment) ---
409
+ /**
410
+ * Step 8 (Enroll): ดึง Form ลงทะเบียนผู้เข้าชม
411
+ */
412
+ enroll(k, zone, roundId, payload) {
413
+ return __awaiter(this, void 0, void 0, function* () {
414
+ this.emitProgress("GET_ENROLL", `Fetching enrollment form`);
415
+ const response = yield this.client.post(`${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`, payload.toString(), {
416
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { referer: `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}` }),
417
+ });
418
+ const $ = cheerio.load(response.data);
419
+ const formData = {};
420
+ $("#form input, #form select").each((_, el) => {
421
+ var _a, _b;
422
+ const name = $(el).attr("name");
423
+ if (name) {
424
+ formData[name] = (_b = (_a = $(el).attr("value")) !== null && _a !== void 0 ? _a : $(el).find("option:selected").attr("value")) !== null && _b !== void 0 ? _b : "";
425
+ }
426
+ });
427
+ return formData;
428
+ });
429
+ }
430
+ /**
431
+ * Step 9 (Enroll Process): ส่งข้อมูลผู้เข้าชม
432
+ */
433
+ enrollProcess(k, payload, attendees) {
434
+ return __awaiter(this, void 0, void 0, function* () {
435
+ var _a;
436
+ this.emitProgress("ENROLL_PROCESS", `Submitting info for ${attendees.length} attendees`);
437
+ const formData = new URLSearchParams();
438
+ for (const user of attendees) {
439
+ formData.append("txt_fullname[]", user.fullname || "");
440
+ formData.append("txt_email[]", user.email || "");
441
+ formData.append("txt_firstname[]", user.firstname || "");
442
+ formData.append("txt_lastname[]", user.lastname || "");
443
+ formData.append("txt_phone[]", user.phone || "");
444
+ formData.append("sel_options[]", user.options || "");
445
+ formData.append("zones[]", (_a = payload.zone) !== null && _a !== void 0 ? _a : "");
446
+ }
447
+ const excludedKeys = new Set([
448
+ "txt_fullname[]",
449
+ "txt_email[]",
450
+ "txt_firstname[]",
451
+ "txt_lastname[]",
452
+ "txt_phone[]",
453
+ "sel_options[]",
454
+ "zones[]",
455
+ ]);
456
+ for (const [key, value] of Object.entries(payload)) {
457
+ if (!excludedKeys.has(key)) {
458
+ formData.append(key, value !== undefined && value !== null ? String(value) : "");
459
+ }
460
+ }
461
+ const response = yield this.client.post(`${this.endpoint}/enroll_process.php?k=${k}`, formData.toString(), {
462
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/enroll.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}` }),
463
+ });
464
+ return {
465
+ data: response.data,
466
+ nextPayload: formData,
467
+ };
468
+ });
469
+ }
470
+ // --- Flow การชำระเงิน (Payment) ---
471
+ /**
472
+ * Step 10: ดึงข้อมูลหน้าสรุปการชำระเงินและที่อยู่จัดส่ง
473
+ */
474
+ getPaymentDetails(k_1, formData_1, zone_1, roundId_1) {
475
+ return __awaiter(this, arguments, void 0, function* (k, formData, zone, roundId, delayMs = 1000) {
476
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
477
+ var _k, _l, _m, _o, _p, _q, _r, _s, _t;
478
+ this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`);
479
+ const body = formData instanceof URLSearchParams ? formData.toString() : new URLSearchParams(formData).toString();
480
+ const response = yield this.client.post(`${this.endpoint}/paymentall.php?k=${k}`, body, {
481
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}` }),
482
+ });
483
+ const $ = cheerio.load(response.data);
484
+ const hiddenInputs = {};
485
+ $("#frm-confirm input").each((_, el) => {
486
+ var _a;
487
+ var _b, _c;
488
+ const key = (_b = $(el).attr("name")) !== null && _b !== void 0 ? _b : $(el).attr("id");
489
+ if (key) {
490
+ hiddenInputs[key] = (_c = (_a = $(el).val()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _c !== void 0 ? _c : "";
491
+ }
492
+ });
493
+ const addressForm = $("#frm-address");
494
+ if (addressForm.length > 0) {
495
+ const fname = (_k = (_a = addressForm.find("input[name='c_fname_m']").val()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _k !== void 0 ? _k : "";
496
+ const lname = (_l = (_b = addressForm.find("input[name='c_lname_m']").val()) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _l !== void 0 ? _l : "";
497
+ const address = (_m = (_c = addressForm.find("textarea[name='c_address_m']").val()) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _m !== void 0 ? _m : "";
498
+ const ctId = (_o = (_d = addressForm.find("select[name='c_ctCode'] option:selected").val()) === null || _d === void 0 ? void 0 : _d.toString()) !== null && _o !== void 0 ? _o : "";
499
+ const pvId = (_p = (_e = addressForm.find("select[name='c_pvId'] option:selected").val()) === null || _e === void 0 ? void 0 : _e.toString()) !== null && _p !== void 0 ? _p : "";
500
+ const amId = (_q = (_f = addressForm.find("select[name='c_amId'] option:selected").val()) === null || _f === void 0 ? void 0 : _f.toString()) !== null && _q !== void 0 ? _q : "";
501
+ const zipcode = (_r = (_g = addressForm.find("input[name='c_PostCode']").val()) === null || _g === void 0 ? void 0 : _g.toString()) !== null && _r !== void 0 ? _r : "";
502
+ let rawPhone = (_s = (_h = addressForm.find("input[name='c_ContactRecipient']").val()) === null || _h === void 0 ? void 0 : _h.toString()) !== null && _s !== void 0 ? _s : "";
503
+ if (rawPhone.startsWith("66")) {
504
+ rawPhone = rawPhone.slice(2);
505
+ }
506
+ const phoneArea = (_t = (_j = addressForm.find("input[name='telephoneArea']").val()) === null || _j === void 0 ? void 0 : _j.toString()) !== null && _t !== void 0 ? _t : "66";
507
+ hiddenInputs["adr_fname"] = fname;
508
+ hiddenInputs["adr_lname"] = lname;
509
+ hiddenInputs["adr_address"] = address;
510
+ hiddenInputs["adr_ctId"] = ctId;
511
+ hiddenInputs["adr_pvId"] = pvId;
512
+ hiddenInputs["adr_amId"] = amId;
513
+ hiddenInputs["adr_zipcode"] = zipcode;
514
+ hiddenInputs["adr_mobile"] = rawPhone.replace(/(\d{2})(\d{3})(\d{4})/, "$1 $2 $3");
515
+ hiddenInputs["adr_mobilearea"] = phoneArea;
516
+ }
517
+ if (delayMs > 0) {
518
+ yield new Promise((resolve) => setTimeout(resolve, delayMs));
519
+ }
520
+ return hiddenInputs;
521
+ });
522
+ }
523
+ /**
524
+ * Step 11: คำนวณยอดเงินรวมสุทธิและยืนยันคำสั่งซื้อ
525
+ */
526
+ confirmPayment(k_1, payload_1) {
527
+ return __awaiter(this, arguments, void 0, function* (k, payload, deliver = "1", payType = "KBQR") {
528
+ this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`);
529
+ const cntTicket = parseInt(payload.cal_cntticket || "0", 10);
530
+ const amountCode = parseFloat(payload.amountcode || "0");
531
+ const deliverFee = parseFloat(payload.cal_deliverfee || payload.val_deliverfee || "80");
532
+ const addCharge = parseFloat(payload.cal_addcharge || "0");
533
+ const discount = parseFloat(payload.cal_discount || "0");
534
+ const voucher = parseFloat(payload.cal_voucher || "0");
535
+ const ticketFee = cntTicket * 191; // Ticket Fee 191 THB per ticket
536
+ const totalAmount = amountCode + deliverFee + ticketFee + addCharge - discount - voucher;
537
+ const computedPayload = Object.assign(Object.assign({}, payload), { paytype: payType, deliver: deliver, cal_ticketfee: ticketFee.toString(), cal_totalamount: Math.round(totalAmount).toString() });
538
+ const formData = new URLSearchParams();
539
+ for (const [key, value] of Object.entries(computedPayload)) {
540
+ if (key !== "check-protect") {
541
+ formData.append(key, value !== undefined && value !== null ? String(value) : "");
542
+ }
543
+ }
544
+ const response = yield this.client.post(`${this.endpoint}/paycfmall.php?k=${k}`, formData.toString(), {
545
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/paymentall.php?k=${k}` }),
546
+ });
547
+ const $ = cheerio.load(response.data);
548
+ const nextJsonData = {};
549
+ $("#payallfrm input[type='hidden']").each((_, el) => {
550
+ var _a;
551
+ const name = $(el).attr("name");
552
+ const value = (_a = $(el).attr("value")) !== null && _a !== void 0 ? _a : "";
553
+ if (name) {
554
+ nextJsonData[name] = value;
555
+ }
556
+ });
557
+ return nextJsonData;
558
+ });
559
+ }
560
+ /**
561
+ * Step 12: ดึง payload เข้ารหัสสำหรับ KBank
562
+ */
563
+ orderEncKBankQR(k, payCfmData) {
564
+ return __awaiter(this, void 0, void 0, function* () {
565
+ this.emitProgress("ORDER_ENC_KBANK", `Encrypting order payload for KBank`);
566
+ const formData = new URLSearchParams(payCfmData);
567
+ const response = yield this.client.post(`${this.endpoint}/orderenc_kbankqr.php`, formData.toString(), {
568
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/paycfmall.php?k=${k}` }),
569
+ });
570
+ const $ = cheerio.load(response.data);
571
+ const kbankqrData = {};
572
+ $("#kbankqr input[type='hidden']").each((_, el) => {
573
+ var _a;
574
+ const name = $(el).attr("name");
575
+ const value = (_a = $(el).attr("value")) !== null && _a !== void 0 ? _a : "";
576
+ if (name) {
577
+ kbankqrData[name] = value;
578
+ }
579
+ });
580
+ return kbankqrData;
581
+ });
582
+ }
583
+ /**
584
+ * Step 13: เรียก getkbankqr.php
585
+ */
586
+ getKBankQR(orderEncData) {
587
+ return __awaiter(this, void 0, void 0, function* () {
588
+ this.emitProgress("GET_KBANK_QR", `Calling KBank Gateway info`);
589
+ const formData = new URLSearchParams(orderEncData);
590
+ const response = yield this.client.post(`${this.endpoint}/getkbankqr.php`, formData.toString(), {
591
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/orderenc_kbankqr.php` }),
592
+ });
593
+ return response.data;
594
+ });
595
+ }
596
+ /**
597
+ * Step 14: ทำรายการ payment_kbankqr.php เพื่อดึง Order ID และ API Key
598
+ */
599
+ paymentKBankQR(orderEncData, kbankPayload) {
600
+ return __awaiter(this, void 0, void 0, function* () {
601
+ var _a, _b;
602
+ this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`);
603
+ const preparePayload = Object.assign(Object.assign({}, orderEncData), { reasoncode: kbankPayload.reasoncode, eventname: kbankPayload.eventname, rounddetail: kbankPayload.rounddetail, totalamount: kbankPayload.totalamount, orderstatus: kbankPayload.orderstatus, kbankqr_txExpiry: kbankPayload.kbankqr_txExpiry });
604
+ const formData = new URLSearchParams(preparePayload);
605
+ const response = yield this.client.post(`${this.endpoint}/payment_kbankqr.php`, formData.toString(), {
606
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/orderenc_kbankqr.php` }),
607
+ });
608
+ const $ = cheerio.load(response.data);
609
+ const script = $("#kbankpost script[data-order-id]");
610
+ const orderId = (_a = script.attr("data-order-id")) !== null && _a !== void 0 ? _a : "";
611
+ const apiKey = (_b = script.attr("data-apikey")) !== null && _b !== void 0 ? _b : "";
612
+ if (!orderId || !apiKey) {
613
+ throw new Error("Unable to extract KBank orderId or apiKey from response");
614
+ }
615
+ return { orderId, apiKey };
616
+ });
617
+ }
618
+ /**
619
+ * Step 15: ยิงตรง Kasikorn Bank Gateway เพื่อสร้าง ThaiQR Code
620
+ */
621
+ generateThaiQR(orderId, apiKey, amount) {
622
+ return __awaiter(this, void 0, void 0, function* () {
623
+ this.emitProgress("GENERATE_THAI_QR", `Requesting ThaiQR code from Kasikorn Bank`);
624
+ const payload = {
625
+ amount: amount,
626
+ currency: "THB",
627
+ metadata: [],
628
+ order_id: orderId,
629
+ sof: "ThaiQR",
630
+ };
631
+ const response = yield this.client.post("https://kpaymentgateway-services.kasikornbank.com/qr/v2/qr", payload, {
632
+ headers: Object.assign(Object.assign({}, this.defaultHeaders), { referer: "https://kpaymentgateway.kasikornbank.com/", "x-api-key": apiKey }),
633
+ });
634
+ return response.data;
635
+ });
636
+ }
637
+ // ==========================================
638
+ // High-Level All-In-One Method
639
+ // ==========================================
640
+ /**
641
+ * ทำการจองแบบครบวงจร (End-to-End) ตั้งแต่เลือกรอบจนได้ QR Code
642
+ */
643
+ quickBook(options) {
644
+ return __awaiter(this, void 0, void 0, function* () {
645
+ var _a, _b, _c, _d;
646
+ const { eventUrl, roundIndex = 0, roundId, zoneId, ticketCount = 1, seatSelection = "random", attendees = [], deliveryType = "1", payType = "KBQR", paymentDelayMs = 1500, } = options;
647
+ if (options.onProgress) {
648
+ this.onProgressCallback = options.onProgress;
649
+ }
650
+ // 1. ดึงรอบการแสดง
651
+ const rounds = yield this.getRounds(eventUrl);
652
+ if (rounds.length === 0) {
653
+ throw new Error("No rounds found for this event");
654
+ }
655
+ let selectedRound;
656
+ if (roundId) {
657
+ selectedRound = rounds.find((r) => r.id === roundId || r.query === roundId);
658
+ }
659
+ else if (options.roundIndex !== undefined) {
660
+ selectedRound = rounds[options.roundIndex];
661
+ }
662
+ else {
663
+ // Default: pick the first available active round
664
+ selectedRound = rounds.find((r) => !r.isDisabled && !!r.query) || rounds[0];
665
+ }
666
+ if (!selectedRound) {
667
+ throw new Error(`Target round not found (Index: ${roundIndex}, ID: ${roundId})`);
668
+ }
669
+ if (selectedRound.isDisabled || !selectedRound.query) {
670
+ throw new Error(`Selected round "${selectedRound.label || selectedRound.id}" is currently disabled or not open for sale yet.`);
671
+ }
672
+ // 2. ตรวจสอบเงื่อนไข
673
+ const verifyRes = yield this.verifyCheckCondition(selectedRound.query);
674
+ if (!verifyRes.result) {
675
+ throw new Error(`Condition check failed: ${verifyRes.message || "Unknown error"}`);
676
+ }
677
+ // 3. ดึง Tokens และ Zone Map
678
+ const zoneData = yield this.getZones(selectedRound.query);
679
+ // หารอบการแสดงใน zone_json
680
+ let targetPerformRound = zoneData.rounds[0];
681
+ if (roundId) {
682
+ const match = zoneData.rounds.find((r) => r.id === roundId);
683
+ if (match)
684
+ targetPerformRound = match;
685
+ }
686
+ else {
687
+ const dateNum = (_a = selectedRound.date.match(/\d+/)) === null || _a === void 0 ? void 0 : _a[0];
688
+ if (dateNum) {
689
+ const match = zoneData.rounds.find((r) => r.label.includes(dateNum));
690
+ if (match)
691
+ targetPerformRound = match;
692
+ }
693
+ }
694
+ if (!targetPerformRound) {
695
+ throw new Error("Target performance round ID not found in zone list");
696
+ }
697
+ // 4. เช็คความพร้อมของโซน
698
+ const zoneAvails = yield this.getZoneAvailability(targetPerformRound.id, zoneData.tk, zoneData.k, selectedRound.query);
699
+ const targetZone = zoneAvails.find((z) => z.id.toLowerCase() === zoneId.toLowerCase() || z.value.toLowerCase() === zoneId.toLowerCase());
700
+ if (!targetZone) {
701
+ throw new Error(`Zone "${zoneId}" not found in available zones`);
702
+ }
703
+ let summarySeats = [];
704
+ let paymentAllData;
705
+ // 5. ดำเนินการตามประเภทโซน (Fixed vs Festival)
706
+ if (targetZone.page === "fixed.php") {
707
+ const fixedData = yield this.getFixedSeats(zoneData.k, targetZone.value, targetPerformRound.id);
708
+ const availableSeats = fixedData.seats;
709
+ if (availableSeats.length < ticketCount) {
710
+ throw new Error(`Not enough seats available in zone ${zoneId} (Available: ${availableSeats.length}, Required: ${ticketCount})`);
711
+ }
712
+ let selectedSeats = [];
713
+ if (Array.isArray(seatSelection)) {
714
+ if (typeof seatSelection[0] === "string") {
715
+ selectedSeats = availableSeats.filter((s) => seatSelection.includes(s.seat));
716
+ }
717
+ else {
718
+ selectedSeats = seatSelection;
719
+ }
720
+ }
721
+ else if (seatSelection === "first") {
722
+ selectedSeats = availableSeats.slice(0, ticketCount);
723
+ }
724
+ else {
725
+ // Random
726
+ const shuffled = [...availableSeats].sort(() => 0.5 - Math.random());
727
+ selectedSeats = shuffled.slice(0, ticketCount);
728
+ }
729
+ if (selectedSeats.length < ticketCount) {
730
+ throw new Error(`Could not fulfill requested seats selection.`);
731
+ }
732
+ summarySeats = selectedSeats.map((s) => s.seat);
733
+ // Validate ที่นั่ง
734
+ yield this.validateFixedSeats(zoneData.k, targetZone.value, fixedData.form, selectedSeats);
735
+ // Booking
736
+ const bookingRes = yield this.bookFixedSeats(zoneData.k, fixedData.form, selectedSeats);
737
+ const enrollVal = bookingRes.nextPayload.get("enroll_val");
738
+ if (enrollVal && enrollVal !== "0" && attendees.length > 0) {
739
+ const enrollForm = yield this.enroll(zoneData.k, targetZone.value, targetPerformRound.id, bookingRes.nextPayload);
740
+ const enrollProcessRes = yield this.enrollProcess(zoneData.k, enrollForm, attendees);
741
+ paymentAllData = yield this.getPaymentDetails(zoneData.k, enrollProcessRes.nextPayload, targetZone.value, targetPerformRound.id, paymentDelayMs);
742
+ }
743
+ else {
744
+ paymentAllData = yield this.getPaymentDetails(zoneData.k, bookingRes.nextPayload, targetZone.value, targetPerformRound.id, paymentDelayMs);
745
+ }
746
+ }
747
+ else {
748
+ // Festival Flow
749
+ const festivalForm = yield this.getFestival(zoneData.k, zoneData.tk, selectedRound.query, targetZone.value, targetPerformRound.id);
750
+ yield this.validateFestivalSeats(zoneData.k, festivalForm, ticketCount);
751
+ const bookingFestivalRes = yield this.bookFestivalSeats(zoneData.k, festivalForm, ticketCount);
752
+ if (festivalForm.enroll_val && festivalForm.enroll_val !== "0" && attendees.length > 0) {
753
+ const enrollProcessRes = yield this.enrollProcess(zoneData.k, festivalForm, attendees);
754
+ paymentAllData = yield this.getPaymentDetails(zoneData.k, enrollProcessRes.nextPayload, festivalForm.zone || targetZone.value, festivalForm.rdId || targetPerformRound.id, paymentDelayMs);
755
+ }
756
+ else {
757
+ paymentAllData = yield this.getPaymentDetails(zoneData.k, bookingFestivalRes, festivalForm.zone || targetZone.value, festivalForm.rdId || targetPerformRound.id, paymentDelayMs);
758
+ }
759
+ }
760
+ // 6. Confirm Payment
761
+ const payCfmData = yield this.confirmPayment(zoneData.k, paymentAllData, deliveryType, payType);
762
+ // 7. KBank Flow & Generate QR
763
+ const orderEncData = yield this.orderEncKBankQR(zoneData.k, payCfmData);
764
+ const kbankInfo = yield this.getKBankQR(orderEncData);
765
+ const kbankAuth = yield this.paymentKBankQR(orderEncData, kbankInfo);
766
+ const qrResult = yield this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, orderEncData.amount);
767
+ const qrRawCode = (qrResult === null || qrResult === void 0 ? void 0 : qrResult.paint_text) ||
768
+ (qrResult === null || qrResult === void 0 ? void 0 : qrResult.raw_qr_code) ||
769
+ (qrResult === null || qrResult === void 0 ? void 0 : qrResult.qr_code) ||
770
+ ((_b = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _b === void 0 ? void 0 : _b.paint_text) ||
771
+ ((_c = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _c === void 0 ? void 0 : _c.qr_code) ||
772
+ ((_d = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _d === void 0 ? void 0 : _d.raw_qr_code) ||
773
+ (typeof qrResult === "string" ? qrResult : "");
774
+ const displayQR = (customTitle) => {
775
+ const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${orderEncData.amount || paymentAllData["cal_totalamount"]} บาท`;
776
+ ThaiTicketMajor.displayQRCode(qrRawCode, title);
777
+ };
778
+ if (options.printQR !== false && qrRawCode) {
779
+ displayQR();
780
+ }
781
+ return {
782
+ success: true,
783
+ orderId: kbankAuth.orderId,
784
+ amount: orderEncData.amount || paymentAllData["cal_totalamount"],
785
+ qrRawCode,
786
+ qrData: qrResult,
787
+ displayQR,
788
+ summary: {
789
+ performance: paymentAllData["performance"] || selectedRound.label,
790
+ showDate: paymentAllData["showdate"] || selectedRound.date,
791
+ showTime: paymentAllData["showtime"] || selectedRound.time,
792
+ venue: paymentAllData["venue"],
793
+ zone: targetZone.value,
794
+ ticketCount,
795
+ totalAmount: orderEncData.amount || paymentAllData["cal_totalamount"],
796
+ seats: summarySeats.length > 0 ? summarySeats : undefined,
797
+ },
798
+ rawPayload: {
799
+ orderEncData,
800
+ kbankAuth,
801
+ },
802
+ };
803
+ });
804
+ }
805
+ /**
806
+ * แสดงผล QR Code บนหน้าจอ Terminal / Console เพื่อให้สแกนได้ทันที
807
+ */
808
+ static displayQRCode(qrString, title) {
809
+ if (!qrString) {
810
+ console.warn("No QR code string provided to display.");
811
+ return;
812
+ }
813
+ console.log("\n" + "=".repeat(46));
814
+ if (title) {
815
+ console.log(`📱 ${title}`);
816
+ }
817
+ else {
818
+ console.log("📱 กรุณาสแกน QR Code ด้านล่างเพื่อชำระเงิน:");
819
+ }
820
+ console.log("=".repeat(46));
821
+ qrcode_terminal_1.default.generate(qrString, { small: true });
822
+ console.log("=".repeat(46) + "\n");
823
+ }
824
+ displayQRCode(qrString, title) {
825
+ ThaiTicketMajor.displayQRCode(qrString, title);
826
+ }
827
+ }
828
+ exports.ThaiTicketMajor = ThaiTicketMajor;
829
+ exports.displayQRCode = ThaiTicketMajor.displayQRCode;
830
+ exports.default = ThaiTicketMajor;