playttm 0.0.4 → 0.0.6
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.d.ts +21 -1
- package/dist/lib.js +227 -15
- package/package.json +1 -1
- package/src/lib.ts +239 -16
package/dist/lib.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export declare const isBrowserEnvironment: () => boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Extract transaction amount from EMVCo / ThaiQR payload (Tag 54)
|
|
4
|
+
*/
|
|
5
|
+
export declare const parseThaiQRAmount: (qrCode?: string) => string | null;
|
|
1
6
|
export interface TTMConfig {
|
|
2
7
|
cookies?: string;
|
|
3
8
|
headers?: Record<string, string>;
|
|
@@ -5,6 +10,8 @@ export interface TTMConfig {
|
|
|
5
10
|
timeout?: number;
|
|
6
11
|
endpoint?: string;
|
|
7
12
|
onProgress?: (step: string, message: string, data?: any) => void;
|
|
13
|
+
isBrowser?: boolean;
|
|
14
|
+
adapter?: any;
|
|
8
15
|
}
|
|
9
16
|
export interface RoundInfo {
|
|
10
17
|
id: string;
|
|
@@ -71,11 +78,24 @@ export interface QuickBookResult {
|
|
|
71
78
|
}
|
|
72
79
|
export declare class ThaiTicketMajor {
|
|
73
80
|
private client;
|
|
74
|
-
private jar
|
|
81
|
+
private jar?;
|
|
75
82
|
private endpoint;
|
|
76
83
|
private defaultHeaders;
|
|
77
84
|
private onProgressCallback?;
|
|
85
|
+
inBrowser: boolean;
|
|
86
|
+
private rawCookies?;
|
|
78
87
|
constructor(config?: TTMConfig);
|
|
88
|
+
static readonly BOT_PROTECTION_COOKIES: Set<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Synchronize a cookie string into Chrome Extension's cookie store (chrome.cookies API).
|
|
91
|
+
* Automatically ignores bot protection cookies (Akamai _abck, bm_*, WAF) to prevent Access Denied (403) bans.
|
|
92
|
+
*/
|
|
93
|
+
static syncCookiesToChrome(cookieString: string, domainUrl?: string): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
|
|
96
|
+
*/
|
|
97
|
+
static setupExtensionRules(): Promise<void>;
|
|
98
|
+
syncCookies(domainUrl?: string): Promise<void>;
|
|
79
99
|
private emitProgress;
|
|
80
100
|
/**
|
|
81
101
|
* Step 1: ดึงรอบการแสดงทั้งหมดจากหน้าหลักคอนเสิร์ต
|
package/dist/lib.js
CHANGED
|
@@ -45,30 +45,202 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
45
45
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
46
|
};
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
-
exports.displayQRCode = exports.ThaiTicketMajor = void 0;
|
|
48
|
+
exports.displayQRCode = exports.ThaiTicketMajor = exports.parseThaiQRAmount = exports.isBrowserEnvironment = void 0;
|
|
49
49
|
const axios_1 = __importDefault(require("axios"));
|
|
50
50
|
const cheerio = __importStar(require("cheerio"));
|
|
51
51
|
const axios_cookiejar_support_1 = require("axios-cookiejar-support");
|
|
52
52
|
const tough_cookie_1 = require("tough-cookie");
|
|
53
|
+
const isBrowserEnvironment = () => {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
if (typeof window !== "undefined" && typeof window.document !== "undefined") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (typeof globalThis !== "undefined" && Boolean((_b = (_a = globalThis.chrome) === null || _a === void 0 ? void 0 : _a.runtime) === null || _b === void 0 ? void 0 : _b.id)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (typeof process === "undefined" || !process.versions || !process.versions.node) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
};
|
|
66
|
+
exports.isBrowserEnvironment = isBrowserEnvironment;
|
|
67
|
+
/**
|
|
68
|
+
* Extract transaction amount from EMVCo / ThaiQR payload (Tag 54)
|
|
69
|
+
*/
|
|
70
|
+
const parseThaiQRAmount = (qrCode) => {
|
|
71
|
+
if (!qrCode)
|
|
72
|
+
return null;
|
|
73
|
+
const match = qrCode.match(/(?:^|[^0-9])54(\d{2})([0-9.]+)/);
|
|
74
|
+
if (match) {
|
|
75
|
+
const len = parseInt(match[1], 10);
|
|
76
|
+
const amountStr = match[2].substring(0, len);
|
|
77
|
+
if (!isNaN(parseFloat(amountStr))) {
|
|
78
|
+
return amountStr;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
exports.parseThaiQRAmount = parseThaiQRAmount;
|
|
53
84
|
// ==========================================
|
|
54
85
|
// Main Library Class
|
|
55
86
|
// ==========================================
|
|
56
87
|
class ThaiTicketMajor {
|
|
57
88
|
constructor(config = {}) {
|
|
58
|
-
this.
|
|
89
|
+
this.inBrowser =
|
|
90
|
+
typeof config.isBrowser === "boolean"
|
|
91
|
+
? config.isBrowser
|
|
92
|
+
: (0, exports.isBrowserEnvironment)();
|
|
93
|
+
this.rawCookies = config.cookies;
|
|
59
94
|
this.endpoint = config.endpoint || "https://booking.thaiticketmajor.com/booking/3m";
|
|
60
95
|
this.onProgressCallback = config.onProgress;
|
|
61
96
|
const defaultUA = config.userAgent ||
|
|
62
97
|
"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";
|
|
63
|
-
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"
|
|
64
|
-
if (
|
|
65
|
-
this.
|
|
98
|
+
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" }, (config.headers || {}));
|
|
99
|
+
if (!this.inBrowser) {
|
|
100
|
+
this.jar = new tough_cookie_1.CookieJar();
|
|
101
|
+
this.defaultHeaders["user-agent"] = defaultUA;
|
|
102
|
+
this.defaultHeaders["referer"] = "https://booking.thaiticketmajor.com";
|
|
103
|
+
if (config.cookies) {
|
|
104
|
+
this.defaultHeaders["cookie"] = config.cookies;
|
|
105
|
+
}
|
|
106
|
+
this.client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create(Object.assign({ jar: this.jar, withCredentials: true, timeout: config.timeout || 30000 }, (config.adapter ? { adapter: config.adapter } : {}))));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Browser / Chrome Extension environment
|
|
110
|
+
this.client = axios_1.default.create(Object.assign({ withCredentials: true, timeout: config.timeout || 30000 }, (config.adapter ? { adapter: config.adapter } : {})));
|
|
111
|
+
// Interceptor to strip unsafe headers forbidden by browsers (Fetch / XHR specification)
|
|
112
|
+
const unsafeHeaders = ["user-agent", "referer", "cookie", "origin", "host", "connection"];
|
|
113
|
+
this.client.interceptors.request.use((reqConfig) => {
|
|
114
|
+
if (reqConfig.headers) {
|
|
115
|
+
for (const header of unsafeHeaders) {
|
|
116
|
+
delete reqConfig.headers[header];
|
|
117
|
+
delete reqConfig.headers[header.toLowerCase()];
|
|
118
|
+
delete reqConfig.headers[header.toUpperCase()];
|
|
119
|
+
const titleCase = header.charAt(0).toUpperCase() + header.slice(1);
|
|
120
|
+
delete reqConfig.headers[titleCase];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return reqConfig;
|
|
124
|
+
});
|
|
66
125
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Synchronize a cookie string into Chrome Extension's cookie store (chrome.cookies API).
|
|
129
|
+
* Automatically ignores bot protection cookies (Akamai _abck, bm_*, WAF) to prevent Access Denied (403) bans.
|
|
130
|
+
*/
|
|
131
|
+
static syncCookiesToChrome(cookieString_1) {
|
|
132
|
+
return __awaiter(this, arguments, void 0, function* (cookieString, domainUrl = "https://booking.thaiticketmajor.com") {
|
|
133
|
+
var _a;
|
|
134
|
+
if (typeof chrome === "undefined" || !((_a = chrome === null || chrome === void 0 ? void 0 : chrome.cookies) === null || _a === void 0 ? void 0 : _a.set)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const pairs = cookieString.split(";");
|
|
138
|
+
for (const pair of pairs) {
|
|
139
|
+
const trimmed = pair.trim();
|
|
140
|
+
if (!trimmed)
|
|
141
|
+
continue;
|
|
142
|
+
const eqIndex = trimmed.indexOf("=");
|
|
143
|
+
if (eqIndex === -1)
|
|
144
|
+
continue;
|
|
145
|
+
const name = trimmed.substring(0, eqIndex).trim();
|
|
146
|
+
const value = trimmed.substring(eqIndex + 1).trim();
|
|
147
|
+
if (!name)
|
|
148
|
+
continue;
|
|
149
|
+
// DO NOT overwrite Akamai / WAF bot tokens!
|
|
150
|
+
// Writing stale _abck or bm_* triggers Akamai Bot Protection -> Access Denied.
|
|
151
|
+
if (ThaiTicketMajor.BOT_PROTECTION_COOKIES.has(name.toLowerCase())) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
yield chrome.cookies.set({
|
|
156
|
+
url: domainUrl,
|
|
157
|
+
name,
|
|
158
|
+
value,
|
|
159
|
+
domain: ".thaiticketmajor.com",
|
|
160
|
+
path: "/",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (_b) {
|
|
164
|
+
try {
|
|
165
|
+
yield chrome.cookies.set({
|
|
166
|
+
url: domainUrl,
|
|
167
|
+
name,
|
|
168
|
+
value,
|
|
169
|
+
path: "/",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (_c) { }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
|
|
179
|
+
*/
|
|
180
|
+
static setupExtensionRules() {
|
|
181
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
182
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
183
|
+
if (typeof chrome === "undefined" || !((_a = chrome === null || chrome === void 0 ? void 0 : chrome.declarativeNetRequest) === null || _a === void 0 ? void 0 : _a.updateSessionRules)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
yield chrome.declarativeNetRequest.updateSessionRules({
|
|
188
|
+
removeRuleIds: [1001, 1002],
|
|
189
|
+
addRules: [
|
|
190
|
+
{
|
|
191
|
+
id: 1001,
|
|
192
|
+
priority: 1,
|
|
193
|
+
action: {
|
|
194
|
+
type: ((_b = chrome.declarativeNetRequest.RuleActionType) === null || _b === void 0 ? void 0 : _b.MODIFY_HEADERS) || "modifyHeaders",
|
|
195
|
+
requestHeaders: [
|
|
196
|
+
{
|
|
197
|
+
header: "Referer",
|
|
198
|
+
operation: ((_c = chrome.declarativeNetRequest.HeaderOperation) === null || _c === void 0 ? void 0 : _c.SET) || "set",
|
|
199
|
+
value: "https://booking.thaiticketmajor.com",
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
condition: {
|
|
204
|
+
urlFilter: "thaiticketmajor.com",
|
|
205
|
+
resourceTypes: [
|
|
206
|
+
((_d = chrome.declarativeNetRequest.ResourceType) === null || _d === void 0 ? void 0 : _d.XMLHTTPREQUEST) || "xmlhttprequest",
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 1002,
|
|
212
|
+
priority: 1,
|
|
213
|
+
action: {
|
|
214
|
+
type: ((_e = chrome.declarativeNetRequest.RuleActionType) === null || _e === void 0 ? void 0 : _e.MODIFY_HEADERS) || "modifyHeaders",
|
|
215
|
+
requestHeaders: [
|
|
216
|
+
{
|
|
217
|
+
header: "Referer",
|
|
218
|
+
operation: ((_f = chrome.declarativeNetRequest.HeaderOperation) === null || _f === void 0 ? void 0 : _f.SET) || "set",
|
|
219
|
+
value: "https://kpaymentgateway.kasikornbank.com/",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
condition: {
|
|
224
|
+
urlFilter: "kasikornbank.com",
|
|
225
|
+
resourceTypes: [
|
|
226
|
+
((_g = chrome.declarativeNetRequest.ResourceType) === null || _g === void 0 ? void 0 : _g.XMLHTTPREQUEST) || "xmlhttprequest",
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
console.warn("Could not set declarativeNetRequest session rules:", err);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
syncCookies(domainUrl) {
|
|
239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
if (this.rawCookies) {
|
|
241
|
+
yield ThaiTicketMajor.syncCookiesToChrome(this.rawCookies, domainUrl);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
72
244
|
}
|
|
73
245
|
emitProgress(step, message, data) {
|
|
74
246
|
if (this.onProgressCallback) {
|
|
@@ -641,11 +813,19 @@ class ThaiTicketMajor {
|
|
|
641
813
|
*/
|
|
642
814
|
quickBook(options) {
|
|
643
815
|
return __awaiter(this, void 0, void 0, function* () {
|
|
644
|
-
var _a, _b, _c, _d;
|
|
816
|
+
var _a, _b, _c, _d, _e;
|
|
645
817
|
const { eventUrl, roundIndex = 0, roundId, zoneId, ticketCount = 1, seatSelection = "random", attendees = [], deliveryType = "1", payType = "KBQR", paymentDelayMs = 1500, } = options;
|
|
646
818
|
if (options.onProgress) {
|
|
647
819
|
this.onProgressCallback = options.onProgress;
|
|
648
820
|
}
|
|
821
|
+
// In Chrome Extension environment, prepare cookies and network rules
|
|
822
|
+
if (this.inBrowser) {
|
|
823
|
+
this.emitProgress("INIT_EXTENSION", "Preparing Chrome extension cookies and network rules...");
|
|
824
|
+
if (this.rawCookies) {
|
|
825
|
+
yield this.syncCookies();
|
|
826
|
+
}
|
|
827
|
+
yield ThaiTicketMajor.setupExtensionRules();
|
|
828
|
+
}
|
|
649
829
|
// 1. ดึงรอบการแสดง
|
|
650
830
|
const rounds = yield this.getRounds(eventUrl);
|
|
651
831
|
if (rounds.length === 0) {
|
|
@@ -762,7 +942,14 @@ class ThaiTicketMajor {
|
|
|
762
942
|
const orderEncData = yield this.orderEncKBankQR(zoneData.k, payCfmData);
|
|
763
943
|
const kbankInfo = yield this.getKBankQR(orderEncData);
|
|
764
944
|
const kbankAuth = yield this.paymentKBankQR(orderEncData, kbankInfo);
|
|
765
|
-
const
|
|
945
|
+
const targetAmount = orderEncData.transactionamount ||
|
|
946
|
+
orderEncData.totalamount ||
|
|
947
|
+
orderEncData.amount ||
|
|
948
|
+
payCfmData.amount ||
|
|
949
|
+
kbankInfo.totalamount ||
|
|
950
|
+
paymentAllData["cal_totalamount"] ||
|
|
951
|
+
"0";
|
|
952
|
+
const qrResult = yield this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, targetAmount);
|
|
766
953
|
const qrRawCode = (qrResult === null || qrResult === void 0 ? void 0 : qrResult.paint_text) ||
|
|
767
954
|
(qrResult === null || qrResult === void 0 ? void 0 : qrResult.raw_qr_code) ||
|
|
768
955
|
(qrResult === null || qrResult === void 0 ? void 0 : qrResult.qr_code) ||
|
|
@@ -770,8 +957,14 @@ class ThaiTicketMajor {
|
|
|
770
957
|
((_c = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _c === void 0 ? void 0 : _c.qr_code) ||
|
|
771
958
|
((_d = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _d === void 0 ? void 0 : _d.raw_qr_code) ||
|
|
772
959
|
(typeof qrResult === "string" ? qrResult : "");
|
|
960
|
+
const qrAmount = (0, exports.parseThaiQRAmount)(qrRawCode);
|
|
961
|
+
const finalAmount = qrAmount ||
|
|
962
|
+
(targetAmount && targetAmount !== "0" ? targetAmount : null) ||
|
|
963
|
+
(qrResult === null || qrResult === void 0 ? void 0 : qrResult.amount) ||
|
|
964
|
+
((_e = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _e === void 0 ? void 0 : _e.amount) ||
|
|
965
|
+
"0";
|
|
773
966
|
const displayQR = (customTitle) => {
|
|
774
|
-
const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${
|
|
967
|
+
const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${finalAmount} บาท`;
|
|
775
968
|
ThaiTicketMajor.displayQRCode(qrRawCode, title);
|
|
776
969
|
};
|
|
777
970
|
if (options.printQR !== false && qrRawCode) {
|
|
@@ -780,7 +973,7 @@ class ThaiTicketMajor {
|
|
|
780
973
|
return {
|
|
781
974
|
success: true,
|
|
782
975
|
orderId: kbankAuth.orderId,
|
|
783
|
-
amount:
|
|
976
|
+
amount: String(finalAmount),
|
|
784
977
|
qrRawCode,
|
|
785
978
|
qrData: qrResult,
|
|
786
979
|
displayQR,
|
|
@@ -791,7 +984,7 @@ class ThaiTicketMajor {
|
|
|
791
984
|
venue: paymentAllData["venue"],
|
|
792
985
|
zone: targetZone.value,
|
|
793
986
|
ticketCount,
|
|
794
|
-
totalAmount:
|
|
987
|
+
totalAmount: String(finalAmount),
|
|
795
988
|
seats: summarySeats.length > 0 ? summarySeats : undefined,
|
|
796
989
|
},
|
|
797
990
|
rawPayload: {
|
|
@@ -823,5 +1016,24 @@ class ThaiTicketMajor {
|
|
|
823
1016
|
}
|
|
824
1017
|
}
|
|
825
1018
|
exports.ThaiTicketMajor = ThaiTicketMajor;
|
|
1019
|
+
ThaiTicketMajor.BOT_PROTECTION_COOKIES = new Set([
|
|
1020
|
+
"_abck",
|
|
1021
|
+
"bm_sz",
|
|
1022
|
+
"ak_bmsc",
|
|
1023
|
+
"bm_sv",
|
|
1024
|
+
"bm_mi",
|
|
1025
|
+
"hwwafsesid",
|
|
1026
|
+
"hwwafsestime",
|
|
1027
|
+
"__cf_bm",
|
|
1028
|
+
"cf_clearance",
|
|
1029
|
+
"_ga",
|
|
1030
|
+
"_gid",
|
|
1031
|
+
"_gcl_au",
|
|
1032
|
+
"_fbp",
|
|
1033
|
+
"_clck",
|
|
1034
|
+
"_clsk",
|
|
1035
|
+
"_ttp",
|
|
1036
|
+
"_twpid",
|
|
1037
|
+
]);
|
|
826
1038
|
exports.displayQRCode = ThaiTicketMajor.displayQRCode;
|
|
827
1039
|
exports.default = ThaiTicketMajor;
|
package/package.json
CHANGED
package/src/lib.ts
CHANGED
|
@@ -3,6 +3,37 @@ import * as cheerio from "cheerio";
|
|
|
3
3
|
import { wrapper } from "axios-cookiejar-support";
|
|
4
4
|
import { CookieJar } from "tough-cookie";
|
|
5
5
|
|
|
6
|
+
declare const chrome: any;
|
|
7
|
+
|
|
8
|
+
export const isBrowserEnvironment = (): boolean => {
|
|
9
|
+
if (typeof window !== "undefined" && typeof window.document !== "undefined") {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
if (typeof globalThis !== "undefined" && Boolean((globalThis as any).chrome?.runtime?.id)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (typeof process === "undefined" || !process.versions || !process.versions.node) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract transaction amount from EMVCo / ThaiQR payload (Tag 54)
|
|
23
|
+
*/
|
|
24
|
+
export const parseThaiQRAmount = (qrCode?: string): string | null => {
|
|
25
|
+
if (!qrCode) return null;
|
|
26
|
+
const match = qrCode.match(/(?:^|[^0-9])54(\d{2})([0-9.]+)/);
|
|
27
|
+
if (match) {
|
|
28
|
+
const len = parseInt(match[1], 10);
|
|
29
|
+
const amountStr = match[2].substring(0, len);
|
|
30
|
+
if (!isNaN(parseFloat(amountStr))) {
|
|
31
|
+
return amountStr;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
|
|
6
37
|
// ==========================================
|
|
7
38
|
// Interfaces & Types
|
|
8
39
|
// ==========================================
|
|
@@ -14,6 +45,8 @@ export interface TTMConfig {
|
|
|
14
45
|
timeout?: number;
|
|
15
46
|
endpoint?: string;
|
|
16
47
|
onProgress?: (step: string, message: string, data?: any) => void;
|
|
48
|
+
isBrowser?: boolean;
|
|
49
|
+
adapter?: any;
|
|
17
50
|
}
|
|
18
51
|
|
|
19
52
|
export interface RoundInfo {
|
|
@@ -91,13 +124,19 @@ export interface QuickBookResult {
|
|
|
91
124
|
|
|
92
125
|
export class ThaiTicketMajor {
|
|
93
126
|
private client: AxiosInstance;
|
|
94
|
-
private jar
|
|
127
|
+
private jar?: CookieJar;
|
|
95
128
|
private endpoint: string;
|
|
96
129
|
private defaultHeaders: Record<string, string>;
|
|
97
130
|
private onProgressCallback?: (step: string, message: string, data?: any) => void;
|
|
131
|
+
public inBrowser: boolean;
|
|
132
|
+
private rawCookies?: string;
|
|
98
133
|
|
|
99
134
|
constructor(config: TTMConfig = {}) {
|
|
100
|
-
this.
|
|
135
|
+
this.inBrowser =
|
|
136
|
+
typeof config.isBrowser === "boolean"
|
|
137
|
+
? config.isBrowser
|
|
138
|
+
: isBrowserEnvironment();
|
|
139
|
+
this.rawCookies = config.cookies;
|
|
101
140
|
this.endpoint = config.endpoint || "https://booking.thaiticketmajor.com/booking/3m";
|
|
102
141
|
this.onProgressCallback = config.onProgress;
|
|
103
142
|
|
|
@@ -108,22 +147,180 @@ export class ThaiTicketMajor {
|
|
|
108
147
|
this.defaultHeaders = {
|
|
109
148
|
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
110
149
|
"accept-language": "th,en-US;q=0.9,en;q=0.8",
|
|
111
|
-
"user-agent": defaultUA,
|
|
112
|
-
referer: "https://booking.thaiticketmajor.com",
|
|
113
150
|
...(config.headers || {}),
|
|
114
151
|
};
|
|
115
152
|
|
|
116
|
-
if (
|
|
117
|
-
this.
|
|
118
|
-
|
|
153
|
+
if (!this.inBrowser) {
|
|
154
|
+
this.jar = new CookieJar();
|
|
155
|
+
this.defaultHeaders["user-agent"] = defaultUA;
|
|
156
|
+
this.defaultHeaders["referer"] = "https://booking.thaiticketmajor.com";
|
|
157
|
+
if (config.cookies) {
|
|
158
|
+
this.defaultHeaders["cookie"] = config.cookies;
|
|
159
|
+
}
|
|
119
160
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
this.client = wrapper(
|
|
162
|
+
axios.create({
|
|
163
|
+
jar: this.jar,
|
|
164
|
+
withCredentials: true,
|
|
165
|
+
timeout: config.timeout || 30000,
|
|
166
|
+
...(config.adapter ? { adapter: config.adapter } : {}),
|
|
167
|
+
} as any) as any
|
|
168
|
+
) as any;
|
|
169
|
+
} else {
|
|
170
|
+
// Browser / Chrome Extension environment
|
|
171
|
+
this.client = axios.create({
|
|
123
172
|
withCredentials: true,
|
|
124
173
|
timeout: config.timeout || 30000,
|
|
125
|
-
|
|
126
|
-
|
|
174
|
+
...(config.adapter ? { adapter: config.adapter } : {}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Interceptor to strip unsafe headers forbidden by browsers (Fetch / XHR specification)
|
|
178
|
+
const unsafeHeaders = ["user-agent", "referer", "cookie", "origin", "host", "connection"];
|
|
179
|
+
this.client.interceptors.request.use((reqConfig) => {
|
|
180
|
+
if (reqConfig.headers) {
|
|
181
|
+
for (const header of unsafeHeaders) {
|
|
182
|
+
delete (reqConfig.headers as any)[header];
|
|
183
|
+
delete (reqConfig.headers as any)[header.toLowerCase()];
|
|
184
|
+
delete (reqConfig.headers as any)[header.toUpperCase()];
|
|
185
|
+
const titleCase = header.charAt(0).toUpperCase() + header.slice(1);
|
|
186
|
+
delete (reqConfig.headers as any)[titleCase];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return reqConfig;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public static readonly BOT_PROTECTION_COOKIES = new Set([
|
|
195
|
+
"_abck",
|
|
196
|
+
"bm_sz",
|
|
197
|
+
"ak_bmsc",
|
|
198
|
+
"bm_sv",
|
|
199
|
+
"bm_mi",
|
|
200
|
+
"hwwafsesid",
|
|
201
|
+
"hwwafsestime",
|
|
202
|
+
"__cf_bm",
|
|
203
|
+
"cf_clearance",
|
|
204
|
+
"_ga",
|
|
205
|
+
"_gid",
|
|
206
|
+
"_gcl_au",
|
|
207
|
+
"_fbp",
|
|
208
|
+
"_clck",
|
|
209
|
+
"_clsk",
|
|
210
|
+
"_ttp",
|
|
211
|
+
"_twpid",
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Synchronize a cookie string into Chrome Extension's cookie store (chrome.cookies API).
|
|
216
|
+
* Automatically ignores bot protection cookies (Akamai _abck, bm_*, WAF) to prevent Access Denied (403) bans.
|
|
217
|
+
*/
|
|
218
|
+
public static async syncCookiesToChrome(cookieString: string, domainUrl: string = "https://booking.thaiticketmajor.com"): Promise<void> {
|
|
219
|
+
if (typeof chrome === "undefined" || !chrome?.cookies?.set) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const pairs = cookieString.split(";");
|
|
224
|
+
for (const pair of pairs) {
|
|
225
|
+
const trimmed = pair.trim();
|
|
226
|
+
if (!trimmed) continue;
|
|
227
|
+
const eqIndex = trimmed.indexOf("=");
|
|
228
|
+
if (eqIndex === -1) continue;
|
|
229
|
+
|
|
230
|
+
const name = trimmed.substring(0, eqIndex).trim();
|
|
231
|
+
const value = trimmed.substring(eqIndex + 1).trim();
|
|
232
|
+
if (!name) continue;
|
|
233
|
+
|
|
234
|
+
// DO NOT overwrite Akamai / WAF bot tokens!
|
|
235
|
+
// Writing stale _abck or bm_* triggers Akamai Bot Protection -> Access Denied.
|
|
236
|
+
if (ThaiTicketMajor.BOT_PROTECTION_COOKIES.has(name.toLowerCase())) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
await chrome.cookies.set({
|
|
242
|
+
url: domainUrl,
|
|
243
|
+
name,
|
|
244
|
+
value,
|
|
245
|
+
domain: ".thaiticketmajor.com",
|
|
246
|
+
path: "/",
|
|
247
|
+
});
|
|
248
|
+
} catch {
|
|
249
|
+
try {
|
|
250
|
+
await chrome.cookies.set({
|
|
251
|
+
url: domainUrl,
|
|
252
|
+
name,
|
|
253
|
+
value,
|
|
254
|
+
path: "/",
|
|
255
|
+
});
|
|
256
|
+
} catch {}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
|
|
263
|
+
*/
|
|
264
|
+
public static async setupExtensionRules(): Promise<void> {
|
|
265
|
+
if (typeof chrome === "undefined" || !chrome?.declarativeNetRequest?.updateSessionRules) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await chrome.declarativeNetRequest.updateSessionRules({
|
|
271
|
+
removeRuleIds: [1001, 1002],
|
|
272
|
+
addRules: [
|
|
273
|
+
{
|
|
274
|
+
id: 1001,
|
|
275
|
+
priority: 1,
|
|
276
|
+
action: {
|
|
277
|
+
type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
|
|
278
|
+
requestHeaders: [
|
|
279
|
+
{
|
|
280
|
+
header: "Referer",
|
|
281
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
282
|
+
value: "https://booking.thaiticketmajor.com",
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
condition: {
|
|
287
|
+
urlFilter: "thaiticketmajor.com",
|
|
288
|
+
resourceTypes: [
|
|
289
|
+
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: 1002,
|
|
295
|
+
priority: 1,
|
|
296
|
+
action: {
|
|
297
|
+
type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
|
|
298
|
+
requestHeaders: [
|
|
299
|
+
{
|
|
300
|
+
header: "Referer",
|
|
301
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
302
|
+
value: "https://kpaymentgateway.kasikornbank.com/",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
condition: {
|
|
307
|
+
urlFilter: "kasikornbank.com",
|
|
308
|
+
resourceTypes: [
|
|
309
|
+
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
});
|
|
315
|
+
} catch (err) {
|
|
316
|
+
console.warn("Could not set declarativeNetRequest session rules:", err);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public async syncCookies(domainUrl?: string): Promise<void> {
|
|
321
|
+
if (this.rawCookies) {
|
|
322
|
+
await ThaiTicketMajor.syncCookiesToChrome(this.rawCookies, domainUrl);
|
|
323
|
+
}
|
|
127
324
|
}
|
|
128
325
|
|
|
129
326
|
private emitProgress(step: string, message: string, data?: any) {
|
|
@@ -928,6 +1125,15 @@ export class ThaiTicketMajor {
|
|
|
928
1125
|
this.onProgressCallback = options.onProgress;
|
|
929
1126
|
}
|
|
930
1127
|
|
|
1128
|
+
// In Chrome Extension environment, prepare cookies and network rules
|
|
1129
|
+
if (this.inBrowser) {
|
|
1130
|
+
this.emitProgress("INIT_EXTENSION", "Preparing Chrome extension cookies and network rules...");
|
|
1131
|
+
if (this.rawCookies) {
|
|
1132
|
+
await this.syncCookies();
|
|
1133
|
+
}
|
|
1134
|
+
await ThaiTicketMajor.setupExtensionRules();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
931
1137
|
// 1. ดึงรอบการแสดง
|
|
932
1138
|
const rounds = await this.getRounds(eventUrl);
|
|
933
1139
|
if (rounds.length === 0) {
|
|
@@ -1062,7 +1268,16 @@ export class ThaiTicketMajor {
|
|
|
1062
1268
|
const kbankInfo = await this.getKBankQR(orderEncData);
|
|
1063
1269
|
const kbankAuth = await this.paymentKBankQR(orderEncData, kbankInfo);
|
|
1064
1270
|
|
|
1065
|
-
const
|
|
1271
|
+
const targetAmount =
|
|
1272
|
+
orderEncData.transactionamount ||
|
|
1273
|
+
orderEncData.totalamount ||
|
|
1274
|
+
orderEncData.amount ||
|
|
1275
|
+
payCfmData.amount ||
|
|
1276
|
+
kbankInfo.totalamount ||
|
|
1277
|
+
paymentAllData["cal_totalamount"] ||
|
|
1278
|
+
"0";
|
|
1279
|
+
|
|
1280
|
+
const qrResult = await this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, targetAmount);
|
|
1066
1281
|
|
|
1067
1282
|
const qrRawCode =
|
|
1068
1283
|
qrResult?.paint_text ||
|
|
@@ -1073,8 +1288,16 @@ export class ThaiTicketMajor {
|
|
|
1073
1288
|
qrResult?.data?.raw_qr_code ||
|
|
1074
1289
|
(typeof qrResult === "string" ? qrResult : "");
|
|
1075
1290
|
|
|
1291
|
+
const qrAmount = parseThaiQRAmount(qrRawCode);
|
|
1292
|
+
const finalAmount =
|
|
1293
|
+
qrAmount ||
|
|
1294
|
+
(targetAmount && targetAmount !== "0" ? targetAmount : null) ||
|
|
1295
|
+
qrResult?.amount ||
|
|
1296
|
+
qrResult?.data?.amount ||
|
|
1297
|
+
"0";
|
|
1298
|
+
|
|
1076
1299
|
const displayQR = (customTitle?: string) => {
|
|
1077
|
-
const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${
|
|
1300
|
+
const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${finalAmount} บาท`;
|
|
1078
1301
|
ThaiTicketMajor.displayQRCode(qrRawCode, title);
|
|
1079
1302
|
};
|
|
1080
1303
|
|
|
@@ -1085,7 +1308,7 @@ export class ThaiTicketMajor {
|
|
|
1085
1308
|
return {
|
|
1086
1309
|
success: true,
|
|
1087
1310
|
orderId: kbankAuth.orderId,
|
|
1088
|
-
amount:
|
|
1311
|
+
amount: String(finalAmount),
|
|
1089
1312
|
qrRawCode,
|
|
1090
1313
|
qrData: qrResult,
|
|
1091
1314
|
displayQR,
|
|
@@ -1096,7 +1319,7 @@ export class ThaiTicketMajor {
|
|
|
1096
1319
|
venue: paymentAllData["venue"],
|
|
1097
1320
|
zone: targetZone.value,
|
|
1098
1321
|
ticketCount,
|
|
1099
|
-
totalAmount:
|
|
1322
|
+
totalAmount: String(finalAmount),
|
|
1100
1323
|
seats: summarySeats.length > 0 ? summarySeats : undefined,
|
|
1101
1324
|
},
|
|
1102
1325
|
rawPayload: {
|