From 545b8d844f10f4a050e33ce1451e2d1f36094f42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:15:27 +0000 Subject: [PATCH 01/33] Initial plan From d0d396f62def778ef0a0ee0a8d2a88084a1199d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:18:01 +0000 Subject: [PATCH 02/33] Add th-live.online phone number validation documentation Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-phone-validation.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 th-live-phone-validation.txt diff --git a/th-live-phone-validation.txt b/th-live-phone-validation.txt new file mode 100644 index 0000000..c0958c3 --- /dev/null +++ b/th-live-phone-validation.txt @@ -0,0 +1,24 @@ +th-live.online Phone Number Validation (Registration) +====================================================== + +Source: https://th-live.online/js/app.af6c1ab3.js + +Validation regex: /^0\d{9}$/ + +Rules: +- Must start with the digit 0 +- Followed by exactly 9 more digits +- Total length: exactly 10 digits +- Only digits are accepted (non-digit characters are stripped automatically) + +Example of a valid phone number: 0812345678 + +This matches the standard Thai mobile phone number format (e.g. 08x, 09x, 06x numbers). + +The field has a maxlength of 10 characters and strips any non-digit input via onKeyup handler: + registerForm.phone = registerForm.phone.replace(/[^\d]/g, "") + +If the phone number does not match the regex, the site shows the error: + "กรุณากรอกหมายเลขโทรศัพท์ที่ถูกต้อง" (TH) - "Please enter a valid phone number" + "请输入正确的电话号码" (ZH) + "Mời nhập chính xác số điện thoại" (VN) From 0d96f179a0a77ff5b11eeb16b2b2d50840e877eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:27:34 +0000 Subject: [PATCH 03/33] Expand th-live phone validation doc: add section on obtaining a valid Thai number Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-phone-validation.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/th-live-phone-validation.txt b/th-live-phone-validation.txt index c0958c3..b993415 100644 --- a/th-live-phone-validation.txt +++ b/th-live-phone-validation.txt @@ -22,3 +22,24 @@ If the phone number does not match the regex, the site shows the error: "กรุณากรอกหมายเลขโทรศัพท์ที่ถูกต้อง" (TH) - "Please enter a valid phone number" "请输入正确的电话号码" (ZH) "Mời nhập chính xác số điện thoại" (VN) + +Where to get a valid number +--------------------------- + +Because registration requires an SMS OTP, you need a real or virtual Thai +mobile number. Options: + +1. Thai SIM card — If you are in Thailand, any prepaid SIM from AIS, DTAC, + or TRUE Move H gives you a 10-digit Thai number starting with 0. + +2. Online virtual-number services — Several services sell one-time-use Thai + numbers that can receive SMS. Examples include: + - https://sms-activate.org (search for "Thailand") + - https://smspva.com (search for "TH") + - https://5sim.net (search for "Thailand") + Choose a number, buy it, enter it on the registration form, then retrieve + the OTP from the service's dashboard and enter it to complete sign-up. + +Note: virtual numbers are sometimes already registered on a site. If the +site returns "this number has already been registered", try a different +number from the same service. From 46abe7680d4f603e2db0188b0477614faad5af8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:40:08 +0000 Subject: [PATCH 04/33] Add free virtual-number services section to th-live phone validation doc Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-phone-validation.txt | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/th-live-phone-validation.txt b/th-live-phone-validation.txt index b993415..a4bdffe 100644 --- a/th-live-phone-validation.txt +++ b/th-live-phone-validation.txt @@ -32,7 +32,7 @@ mobile number. Options: 1. Thai SIM card — If you are in Thailand, any prepaid SIM from AIS, DTAC, or TRUE Move H gives you a 10-digit Thai number starting with 0. -2. Online virtual-number services — Several services sell one-time-use Thai +2. Paid virtual-number services — Several services sell one-time-use Thai numbers that can receive SMS. Examples include: - https://sms-activate.org (search for "Thailand") - https://smspva.com (search for "TH") @@ -40,6 +40,27 @@ mobile number. Options: Choose a number, buy it, enter it on the registration form, then retrieve the OTP from the service's dashboard and enter it to complete sign-up. -Note: virtual numbers are sometimes already registered on a site. If the -site returns "this number has already been registered", try a different -number from the same service. +3. Free virtual-number services — Some sites provide temporary phone numbers + at no cost. The numbers are shared/public (everyone can read incoming + messages), so they work best for low-sensitivity registrations. + Thai numbers are available on: + - https://receive-smss.com (browse Thai numbers under "Thailand") + - https://receivesms.co (filter by "Thailand") + - https://quackr.io (filter by "Thailand") + - https://sms24.me (filter by "Thailand") + Steps: + a. Open one of the sites above and pick any displayed Thai number. + b. Enter that number in the registration form. + c. Reload the site's inbox page until the OTP SMS arrives, then enter + the code to complete registration. + + ⚠ Caveats for free services: + - Numbers are shared publicly — anyone visiting the page can see your OTP. + Complete registration quickly after requesting the OTP. + - Popular numbers are often already registered on many sites. If the site + says the number is taken, choose a different number from the list. + - Free services sometimes remove numbers without notice; if a number no + longer appears, try another service from the list above. + +Note: if all virtual numbers (free or paid) are rejected as already +registered, a physical Thai SIM card (option 1) is the most reliable fallback. From d8f48c99e1141ae6efa4810e586b63a218083adc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:59:22 +0000 Subject: [PATCH 05/33] Add th-live-guest-browsing.txt documenting login requirements for viewing models Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-guest-browsing.txt | 78 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 th-live-guest-browsing.txt diff --git a/th-live-guest-browsing.txt b/th-live-guest-browsing.txt new file mode 100644 index 0000000..be6ef3a --- /dev/null +++ b/th-live-guest-browsing.txt @@ -0,0 +1,78 @@ +th-live.online — Viewing Models Without Logging In +==================================================== + +Short answer +------------ +Partially. You can SEE the model list (thumbnails, nicknames, live viewer +count) on the home page without an account, but you CANNOT enter or watch +any live stream without being logged in. + +Detail +------ + +1. What you can do WITHOUT logging in + - Open https://th-live.online in a browser. + - The home page loads and shows the "Live" tab with a grid of model cards, + each showing: + * The model's avatar/thumbnail + * Their nickname + * Viewer count (rq) + This list refreshes automatically. + - No personal information is exposed and no account is required for this + view. + +2. What triggers the login redirect + Clicking on any model card calls the internal goLive() function, which + contains the check: + + if (!this.member) return this.$router.push("/login"); + + This means every attempt to enter a live room immediately redirects an + unauthenticated visitor to the /login page. There is no "watch as guest" + option in the official UI. + +3. Is there a direct URL that bypasses login? + The live room is a Vue SPA route. Navigating directly to a URL such as + https://th-live.online/#/liveRoom (or similar) still loads the same Vue + app, which checks for a logged-in session before rendering the stream. + Without a valid session token in localStorage/sessionStorage the + navigation guard redirects you back to /login. + + No known public API endpoint exposes the raw stream URL without + authentication. + +4. Can developer tools help? + Some users inspect network traffic (e.g. via browser DevTools → Network + tab) while watching a stream on a device that is logged in, to find the + raw CDN/HLS stream URL. If the CDN URL itself is not token-protected it + may be viewable directly. However: + * Stream URLs are typically short-lived signed URLs. + * This requires access to a logged-in account first. + * Whether this is permitted by the site's terms of service is unclear. + +Summary table +------------- + + Action | Login required? + -------------------------------|---------------- + View model list on home page | No + See model thumbnail / nickname | No + Enter a live room / watch | Yes + Send gifts or chat | Yes + Play casino / lottery games | Yes + +Practical options +----------------- +If you want to watch streams without creating an account, there is no +built-in guest mode. Your options are: + + a) Create a free account using one of the free virtual Thai numbers + listed in th-live-phone-validation.txt (section 3). + b) Use a paid virtual number service from the same file (section 2) if + free numbers have already been registered. + c) Use a physical Thai SIM (section 1 of that file) as the most reliable + fallback. + +Once registered and logged in, you can watch streams freely without +additional payment (watching itself is free; gifts and betting cost in-app +currency). From fa9a7cc1816c5322c1449af66daebff854a8ed3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:06:04 +0000 Subject: [PATCH 06/33] Add th-live-userscript.txt: userscript analysis and proof-of-concept for login bypass Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-userscript.txt | 227 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 th-live-userscript.txt diff --git a/th-live-userscript.txt b/th-live-userscript.txt new file mode 100644 index 0000000..58c1701 --- /dev/null +++ b/th-live-userscript.txt @@ -0,0 +1,227 @@ +th-live.online — Userscript: Removing the Login Requirement +============================================================ + +Short answer +------------ +A userscript CAN suppress the client-side redirect to /login, but it +CANNOT make the server serve live-stream data without a real session +token. Read the full analysis below before deciding whether a +userscript is worth writing. + + +Architecture of the login wall +------------------------------- +There are TWO independent layers of authentication: + + Layer 1 — Client-side Vuex state guard (bypassable) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Every action that requires a session runs: + + if (!this.member) return this.$router.push("/login"); + + or + + if (!sessionStorage.getItem("token")) { + this.$store.commit("setState", { member: null }); + this.$router.push("/login"); + } + + `this.member` is a Vuex state object persisted to sessionStorage under + the key "project-live" (via vuex-persistedstate). + + A userscript can forge both values to bypass all such checks. + + Layer 2 — Server-side HTTP authentication (NOT bypassable) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Every API request sent by the app (Axios) includes: + + Authorization: HSBox + + where = sessionStorage.getItem("token"). + + The server validates this token. If the token is missing or invalid, + the API returns one of the internal error codes: + + 991 / 992 / 993 / 1040 / 424 + + On receiving any of those codes, the response interceptor executes: + + router.replace("/login"); + store.dispatch("loginOutTm"); // clear session + logout + + Even if the userscript forges the client state, every live-room API + call (fetching stream URLs, room info, chat tokens, etc.) will come + back with an auth error and the app will clear state and redirect to + /login again. + + There is no way for a client-side script to forge a server-accepted + token without knowing the server's secret — this requires an actual + account. + + +What a userscript can realistically achieve +-------------------------------------------- +1. See the home page model grid — Already works without any userscript; + no login is required for the home page list (see th-live-guest-browsing.txt). + +2. Navigate to /liveRoom without being immediately redirected — The + client-side guard fires synchronously on page load. A userscript + that injects a fake member object can suppress that redirect. + +3. See the live-room UI shell — Components that only inspect + this.member for rendering decisions (e.g. displaying the anchor's + name, gift buttons) will render. + +4. Cause all data-loading API calls to fail silently — The stream + player, chat log, room statistics, etc. all require server data. + Without a valid token every one of those calls will fail. The room + will be an empty skeleton with no video and no chat. + + +Proof-of-concept Tampermonkey script +-------------------------------------- +The script below demonstrates exactly what is (and is not) possible. +It does the following: + + a) Before the Vue app boots, writes a minimal fake "member" object and + a dummy token into sessionStorage so the app never fires the + client-side login redirect. + + b) Intercepts (monkey-patches) XMLHttpRequest.open to detect when the + server returns auth-error codes and silently drops the resulting + router.replace("/login") call — otherwise the app's own response + interceptor would clear state and send the user back to /login. + + c) Opens the URL of your choosing directly. + +IMPORTANT: Even with this script active, the live room will show an +empty state (no video, no chat, no room info) because the server will +reject all API calls that carry the fake token. + +-------------------------------------------------------------------- +// ==UserScript== +// @name th-live.online — No-Login Demo +// @namespace https://th-live.online +// @version 0.1 +// @description Demonstrates the client-side login guard bypass. +// NOTE: live streams will NOT work (server rejects fake token). +// @match https://th-live.online/* +// @run-at document-start +// @grant none +// ==/UserScript== + +(function () { + 'use strict'; + + // ── Step 1: Inject fake Vuex persisted state ───────────────────────────── + // The app uses vuex-persistedstate with key "project-live". + // We create a minimal member object that passes all client-side checks. + const fakeMember = { + uid: 0, + nickname: 'Guest', + avatar: '', + phone: '', + email: '', + token: '', + imToken: '', + goldCoin: 0, + badgeList: [], + areaCode: 66, + needCashPassword: false + }; + const REJECTED_TOKEN = 'FAKE'; // server will reject this + + const existingState = JSON.parse(sessionStorage.getItem('project-live') || '{}'); + if (!existingState.member) { + existingState.member = fakeMember; + sessionStorage.setItem('project-live', JSON.stringify(existingState)); + } + if (!sessionStorage.getItem('token')) { + sessionStorage.setItem('token', REJECTED_TOKEN); + } + + // ── Step 2: Silence server-side auth redirects ─────────────────────────── + // The app's Axios response interceptor calls router.replace("/login") + // when the server returns codes 991/992/993/1040/424. + // We intercept the Vue Router after it mounts and prevent that navigation. + // + // Strategy: override history.replaceState (Vue Router 4 uses the History API). + const _replaceState = history.replaceState.bind(history); + history.replaceState = function (state, title, url) { + if (typeof url === 'string' && url.includes('/login')) { + // Silently drop the server-triggered redirect to /login. + // The page will stay put but all API-loaded content will be empty. + console.warn('[th-live-nologin] Suppressed router.replace("/login")'); + return; + } + return _replaceState(state, title, url); + }; + + // ── Step 3: Notify the user ─────────────────────────────────────────────── + window.addEventListener('load', function () { + console.info( + '[th-live-nologin] Client-side guard bypassed.\n' + + 'The live room UI will load but streams/chat will be empty\n' + + 'because the server does not accept a fake token.' + ); + }); +})(); +-------------------------------------------------------------------- + + +Why the script still cannot play streams +----------------------------------------- +After the script runs you will observe: + + - You can navigate to any live-room URL without being sent to /login. + - The live-room page renders (anchor name from URL params, UI chrome). + - The video player sends a request to the API for the HLS stream URL. + The server returns error code 991/992 (invalid token). + The script suppresses the redirect, so you stay on the page — + but the video player receives no URL and shows a loading spinner + or error state forever. + - The Tencent IM chat SDK also requires a real `imToken` (separate + from the session token), which the server will not issue to a fake + account. Chat history will be empty. + +The script can be extended to also suppress the push (not replace) to +/login by overriding history.pushState in the same way, but this changes +nothing about the server-side reality. + + +The only scenario where the userscript IS useful +------------------------------------------------- +If you already have a valid token from an existing logged-in session +(e.g. copied from another browser tab's sessionStorage), you can inject +that real token into a fresh tab: + + sessionStorage.setItem('token', ''); + + // Also copy the full member object from the logged-in tab: + sessionStorage.setItem('project-live', ''); + +Then reload the page. The app will treat the tab as logged in and all +API calls will succeed. This is just a manual session copy, not a true +bypass, and requires you to have a valid account in the first place. + + +Recommended approach +--------------------- +Register a free account. The th-live-phone-validation.txt file lists +several free virtual Thai phone number services (section 3) that can +receive the SMS OTP needed for sign-up at no cost. Once registered, +watching streams is free. + + +Summary +------- + + Capability | Userscript can do this? + ------------------------------------|------------------------ + Suppress client-side /login redirect| YES (Step 1+2 above) + Navigate to live-room URL | YES + Render live-room UI chrome | YES + Play the actual video stream | NO (server rejects fake token) + Load chat history | NO (IM SDK requires real imToken) + Use any feature requiring the API | NO + Share a session from a real account | YES (with real token copy) From 529f3ea6dcdab10d29e9f09994bd79ea57a14352 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:22:27 +0000 Subject: [PATCH 07/33] Add th-live-us-numbers.txt: US number SMS registration analysis Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-us-numbers.txt | 123 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 th-live-us-numbers.txt diff --git a/th-live-us-numbers.txt b/th-live-us-numbers.txt new file mode 100644 index 0000000..474f3ee --- /dev/null +++ b/th-live-us-numbers.txt @@ -0,0 +1,123 @@ +th-live.online — Can the Registration Page SMS US Numbers? +========================================================== + +Short answer +------------ +No. The phone registration tab accepts only Thai-format numbers +(10 digits, leading zero), so a US number will always fail client-side +validation before any SMS is ever sent. However, the site offers a +second registration path — email — that requires no phone number at +all, which is the practical solution for users without a Thai number. + + +Why a US number cannot work on the phone tab +--------------------------------------------- +Source: https://th-live.online/js/app.af6c1ab3.js + +1. Hard-coded Thai phone regex + The "Get code" button is wired to getAccountCode(), which runs: + + let e = /^0\d{9}$/; + e.test(this.registerForm.phone) + ? this.isRegiste(0) + : this.$toast(this.$t("register.checkPhoneError")); + + The regex /^0\d{9}$/ requires: + - Exactly 10 digits + - First digit must be 0 + + A US 10-digit number (e.g. 2125551234) starts with a non-zero digit + → test() returns false → error toast, no OTP is requested. + +2. Input field constraints + The phone input has: + - maxlength: 10 (cannot type more than 10 characters) + - onKeyup: strips all non-digit characters + registerForm.phone = registerForm.phone.replace(/[^\d]/g, "") + There is no "+" or country code prefix accepted anywhere in the field. + +3. No country code selector + There is no dropdown, picker, or any UI element on the registration + page to choose a country calling code. The phone tab is designed + exclusively for Thai mobile numbers (08x, 09x, 06x prefix range). + +4. No areaCode sent in the OTP request + When the phone validation passes (Thai number), the OTP is sent via: + + POST /order/sms/config/send/vcode + { mobile: , type: 1, os: 0, ... } + + No areaCode field is included. The backend therefore treats the + number as a bare Thai local number (the implicit country is Thailand). + Even if the client-side regex were somehow bypassed, the SMS backend + would not know to route an SMS to the US (+1) country code. + + +The email registration path (no phone required) +------------------------------------------------ +The registration page has two tabs controlled by the `active` state +variable (0 = phone, non-zero = email). When the email tab is active +the OTP flow switches entirely: + + Phone tab (active = 0): + OTP endpoint → POST /order/sms/config/send/vcode + Validate OTP → POST /center-client/sys/auth/phone/reg/codeValidate + Create account→ POST /center-client/sys/auth/phone/reg/info + + Email tab (active ≠ 0): + OTP endpoint → POST /order/email/config/send/vcode + Validate OTP → POST /center-client/sys/auth/email/reg/codeValidate + Create account→ POST /center-client/sys/auth/email/reg/info + +The email tab has no phone-format validation at all. Any valid e-mail +address works. A free Gmail, Outlook, ProtonMail, or similar address +is sufficient — no Thai or US phone number is involved. + +Steps to register via email: + 1. Open https://th-live.online and tap the register / sign-up link. + 2. On the registration page look for a tab or toggle labelled: + "ลงทะเบียนผ่านอีเมล" (TH) + "邮箱注册" (ZH) + "Đăng ký email" (VN) + 3. Enter any valid e-mail address. + 4. Tap "Get code" — an OTP is sent to the e-mail inbox. + 5. Enter the OTP, choose a password, pick a nickname, and submit. + +Note: if the email tab is not visible on your UI, try switching the +app language to Chinese (ZH), which consistently exposes the email +option in the observed source. + + +Could a userscript bypass the phone regex for US numbers? +---------------------------------------------------------- +Technically a Tampermonkey script could: + a) Intercept the "Get code" click before getAccountCode() fires. + b) Skip the /^0\d{9}$/ check and call isRegiste(0) directly. + c) Inject an areaCode of 1 (US) and a 10-digit US number into the + POST /order/sms/config/send/vcode payload. + +However this does not mean the SMS would be delivered: + - The server-side SMS gateway is configured for Thai numbers. Sending + to a +1 (US) destination would require the gateway to have a US + SMS route, which is not a standard configuration for a Thai-market + live streaming app. + - Even if the OTP is somehow delivered, the server validates the OTP + against the number that was originally registered, so the US number + would need to pass all server-side checks too. + +In short, bypassing the client-side regex is easy but the server is +highly unlikely to route an SMS to a US number. The email path is +simpler, more reliable, and requires no scripting. + + +Summary +------- + + Question | Answer + ----------------------------------------------|------- + Phone tab accepts US numbers? | No + Is there a country code selector? | No + Can userscript bypass the regex? | Yes (client-side only) + Will the SMS actually reach a US number? | Almost certainly not + Is there a phone-free registration path? | Yes — email tab + Does email registration work from any country?| Yes From ddd8a633594872cf877ba37ec262c376b1fc32ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:42:11 +0000 Subject: [PATCH 08/33] Correct email-tab claim; add th-live-email-tab-missing.txt Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-email-tab-missing.txt | 276 ++++++++++++++++++++++++++++++++++ th-live-us-numbers.txt | 74 +++++---- 2 files changed, 310 insertions(+), 40 deletions(-) create mode 100644 th-live-email-tab-missing.txt diff --git a/th-live-email-tab-missing.txt b/th-live-email-tab-missing.txt new file mode 100644 index 0000000..e5193a3 --- /dev/null +++ b/th-live-email-tab-missing.txt @@ -0,0 +1,276 @@ +th-live.online — "I Don't See the Email Registration Tab" +========================================================== + +Short answer +------------ +The email registration tab does not exist in the current production +version of th-live.online. It was removed from the UI at some point +but left orphaned code and translation strings behind in the app +bundle. No language setting, URL parameter, or browser workaround +can bring it back — the HTML is simply never rendered. + + +Evidence from the app source +----------------------------- +Source: https://th-live.online/js/app.af6c1ab3.js + +1. Registration render function (Vl / component Br, route "/register") + The actual rendered HTML template is produced by a single Vue3 + render function (function Tr, bytes 177987–190674 in the bundle). + That render function: + + - Contains ZERO occurrences of "email", "emailRegister", or + "van-tab" / "van-tabs". + - Renders only: title bar, avatar picker, nickname, sex toggle, + phone field (maxlength 10), OTP field + "Get code" button, + password, confirm-password, and submit. + - Has no tab bar, no toggle, and no conditional branches that + would show a different form based on language, device, or any + flag. + +2. i18n translation strings exist — but are never called + The bundle contains JSON translation blobs (ZH / TH / VN) that + define these keys: + + register.emailRegister — "邮箱注册" / "ลงทะเบียนผ่านอีเมล" / "Đăng ký email" + register.suEmailTitle — subtitle prompting for email address + register.entryEmailTip — input placeholder for email + register.confirmEmailTips + register.checkEmailError + register.hasEmailRegister + + Not one of these keys is ever called via $t() in any render + function. They are orphaned strings left over from a previous + version. + +3. registerForm data object has no email field + The component's data() initialiser: + + registerForm: { + phone: "", code: "", password: "", confirmPassword: "", + nickname: "", phoneHide: "" + } + + There is no email property. Any code branch that referenced + this.registerForm.email would receive undefined. + +4. Dead-code branches in the component logic + The component's methods (isRegiste, sendVcoderegister, regInfo, + codeValidate) all contain branches like: + + 0 == this.active + ? (o.mobile = this.registerForm.phone, t = phoneAPI) + : (o.email = this.registerForm.email, t = emailAPI) + + Because the render function never shows a tab-switching UI element, + this.active is always 0 and the email branch is permanently + unreachable. + +5. Login page also limited to Thai numbers + The login page's username field has maxlength:10 — too short for + any email address — even though the login() method has the same + phone-or-email branching logic in its code. + +6. There is only one registration route + The Vue Router configuration contains exactly one registration + route: + + { path: "/register", name: "register", component: Br } + + There is no /register2, no /register?type=email, and no alternate + path. The bundle has no lazy-loaded chunks for an email registration + page. + + +Why does this happen? +---------------------- +A feature-flag approach appears to have been used during development: +the code was written to support both phone and email registration, but +the product configuration for this deployment removed the email tab +from the UI while leaving the underlying API calls and translation +strings intact. The backend APIs for email registration still exist: + + POST /center-client/sys/user/email/isRegiste + POST /order/email/config/send/vcode + POST /center-client/sys/auth/email/reg/codeValidate + POST /center-client/sys/auth/email/reg/info + +They are simply unreachable from the UI. + + +Can a userscript restore the email tab? +---------------------------------------- +Technically, a Tampermonkey script could: + + a) After Vue mounts, locate the component instance for the + /register route (via __vue_app__ or similar). + b) Force this.active to a non-zero value. + c) Inject an email field into this.registerForm. + d) Intercept the "Get code" click to call sendVcoderegister() + instead of getAccountCode(). + +If the backend email APIs are live and accepting requests this would +allow email registration. However: + + - Injecting into a production minified Vue3 app's component instance + requires non-trivial tooling (finding the fiber/internal, etc.). + - The backend may have the email OTP endpoint disabled server-side + even if the route exists. + - It is far simpler to use a free virtual Thai number (see + th-live-phone-validation.txt) which takes about 2 minutes. + +A proof-of-concept userscript is shown below. Run it with +Tampermonkey, then navigate to https://th-live.online/register. +It adds a "Register with Email" button that patches the component +state and calls the email OTP flow directly. + +---------------------------------------------------------------------- +// ==UserScript== +// @name th-live.online — Email Registration Attempt +// @namespace https://th-live.online +// @version 0.1 +// @description Tries to restore the removed email registration tab. +// The backend may or may not honour the email OTP call. +// @match https://th-live.online/* +// @run-at document-idle +// @grant none +// ==/UserScript== + +(function () { + 'use strict'; + + // Wait for Vue to mount and the route to be /register + function tryPatch() { + if (location.hash !== '#/register' && !location.pathname.endsWith('/register')) { + return; + } + + // Find the Vue app root + const root = document.querySelector('#app') || document.querySelector('[data-v-app]'); + if (!root || !root.__vue_app__) return; + + // Walk the component tree to find the register component instance + // (it has a registerForm property in its data) + function findComp(vnode) { + if (!vnode) return null; + const comp = vnode.component; + if (comp && comp.data && comp.data.registerForm !== undefined) return comp; + if (vnode.subTree) { + const r = findComp(vnode.subTree); + if (r) return r; + } + if (vnode.children && Array.isArray(vnode.children)) { + for (const c of vnode.children) { + const r = findComp(c); + if (r) return r; + } + } + return null; + } + + const app = root.__vue_app__; + const instance = findComp(app._instance.subTree); + if (!instance) { + console.warn('[email-reg] Could not find register component instance.'); + return; + } + + const proxy = instance.proxy; // the public `this` equivalent in Vue3 + + // Inject email field into registerForm if missing + if (!('email' in proxy.registerForm)) { + proxy.registerForm.email = ''; + } + + // Build a simple email input + button overlay + if (document.getElementById('__email_reg_overlay')) return; + const overlay = document.createElement('div'); + overlay.id = '__email_reg_overlay'; + overlay.style.cssText = [ + 'position:fixed', 'top:80px', 'left:50%', 'transform:translateX(-50%)', + 'background:#fff', 'border:2px solid #eb457e', 'border-radius:12px', + 'padding:16px', 'z-index:9999', 'width:90vw', 'max-width:360px', + 'box-shadow:0 4px 20px rgba(0,0,0,.2)', 'font-family:sans-serif' + ].join(';'); + + overlay.innerHTML = ` +

Register with Email (experimental)

+ + +
+ + +
+

+

Note: backend may not honour this request.

+ `; + document.body.appendChild(overlay); + + document.getElementById('__email_send').onclick = function () { + const email = document.getElementById('__email_inp').value.trim(); + if (!email) return; + proxy.registerForm.email = email; + // Switch to email mode + proxy.active = 1; + // Call sendVcoderegister which now uses email branch + proxy.sendVcoderegister(); + document.getElementById('__email_status').textContent = 'OTP sent (if backend allows)…'; + }; + + document.getElementById('__email_verify').onclick = function () { + const code = document.getElementById('__email_code').value.trim(); + if (!code) return; + proxy.registerForm.code = code; + proxy.codeValidate(); + document.getElementById('__email_status').textContent = 'Attempting verification…'; + }; + + console.info('[email-reg] Email registration overlay injected.'); + } + + // Poll until the register page mounts + const timer = setInterval(() => { + tryPatch(); + }, 1000); + + // Stop polling after 60 s + setTimeout(() => clearInterval(timer), 60000); +})(); +---------------------------------------------------------------------- + +What to expect when running the script +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + - If the backend /order/email/config/send/vcode endpoint is live, + "Send OTP" will request an OTP to the entered address and the app + will show its "Code sent successfully" toast. + - If the backend has that endpoint disabled, the app will show an + error toast with the server's error message. + - If the OTP is received and verified, registration proceeds normally + (the codeValidate → reg/info flow is the same as for phone). + + +Practical recommendation +------------------------- +The fastest, most reliable path is still a virtual Thai number: + + 1. Go to https://sms-activate.org (paid) or https://receive-smss.com + (free, public/shared numbers) and get a Thai mobile number that + starts with 0. + 2. Enter it in the normal registration form. + 3. Retrieve the OTP from the service's inbox page. + 4. Complete registration. + +Total time: ≈2 minutes. See th-live-phone-validation.txt for a full +list of virtual-number services and important caveats. + +Security note: free virtual-number services use publicly visible +shared inboxes — anyone browsing the site can read the OTP. Complete +registration promptly after requesting the code, and do not reuse the +same number for any account where security matters. For higher +security, use a paid one-time-use number or a physical Thai SIM. diff --git a/th-live-us-numbers.txt b/th-live-us-numbers.txt index 474f3ee..23b5c93 100644 --- a/th-live-us-numbers.txt +++ b/th-live-us-numbers.txt @@ -3,15 +3,15 @@ th-live.online — Can the Registration Page SMS US Numbers? Short answer ------------ -No. The phone registration tab accepts only Thai-format numbers -(10 digits, leading zero), so a US number will always fail client-side -validation before any SMS is ever sent. However, the site offers a -second registration path — email — that requires no phone number at -all, which is the practical solution for users without a Thai number. +No. The registration page accepts only Thai-format phone numbers +(10 digits, leading zero). There is no alternative registration path +in the current production UI. The only way to register is with a real +or virtual Thai mobile number. See th-live-phone-validation.txt for +services that supply one-time-use Thai numbers at low or no cost. -Why a US number cannot work on the phone tab ---------------------------------------------- +Why a US number cannot work +--------------------------- Source: https://th-live.online/js/app.af6c1ab3.js 1. Hard-coded Thai phone regex @@ -38,7 +38,7 @@ Source: https://th-live.online/js/app.af6c1ab3.js 3. No country code selector There is no dropdown, picker, or any UI element on the registration - page to choose a country calling code. The phone tab is designed + page to choose a country calling code. The form is designed exclusively for Thai mobile numbers (08x, 09x, 06x prefix range). 4. No areaCode sent in the OTP request @@ -53,39 +53,32 @@ Source: https://th-live.online/js/app.af6c1ab3.js would not know to route an SMS to the US (+1) country code. -The email registration path (no phone required) ------------------------------------------------- -The registration page has two tabs controlled by the `active` state -variable (0 = phone, non-zero = email). When the email tab is active -the OTP flow switches entirely: +Is there an email registration option? +--------------------------------------- +No — not in the current production UI. - Phone tab (active = 0): - OTP endpoint → POST /order/sms/config/send/vcode - Validate OTP → POST /center-client/sys/auth/phone/reg/codeValidate - Create account→ POST /center-client/sys/auth/phone/reg/info +The JS bundle does contain i18n strings for email registration (keys +"emailRegister", "suEmailTitle", etc.) in all three app languages +(ZH/TH/VN), and the component code has dead-code branches for an email +OTP flow. But: - Email tab (active ≠ 0): - OTP endpoint → POST /order/email/config/send/vcode - Validate OTP → POST /center-client/sys/auth/email/reg/codeValidate - Create account→ POST /center-client/sys/auth/email/reg/info + - The registration render function contains ZERO email references and + ZERO tab-switching elements. The emailRegister i18n keys appear + only inside the JSON translation blobs; no $t("register.emailRegister") + call exists anywhere in any rendered template. + - The registerForm data object contains {phone, code, password, + confirmPassword, nickname, phoneHide} — no email field. + - The login page similarly has maxlength:10 on the username field, + which is too short for any email address despite having email login + code in its logic layer. + - Switching the app language to Chinese (or any language) does NOT + reveal an email tab; the tab was removed from the UI but orphaned + strings and logic were left in the bundle. -The email tab has no phone-format validation at all. Any valid e-mail -address works. A free Gmail, Outlook, ProtonMail, or similar address -is sufficient — no Thai or US phone number is involved. +There is currently no route, no URL parameter, and no language setting +that exposes email registration. A Thai phone number is the only path. -Steps to register via email: - 1. Open https://th-live.online and tap the register / sign-up link. - 2. On the registration page look for a tab or toggle labelled: - "ลงทะเบียนผ่านอีเมล" (TH) - "邮箱注册" (ZH) - "Đăng ký email" (VN) - 3. Enter any valid e-mail address. - 4. Tap "Get code" — an OTP is sent to the e-mail inbox. - 5. Enter the OTP, choose a password, pick a nickname, and submit. - -Note: if the email tab is not visible on your UI, try switching the -app language to Chinese (ZH), which consistently exposes the email -option in the observed source. +See th-live-email-tab-missing.txt for the full technical analysis. Could a userscript bypass the phone regex for US numbers? @@ -115,9 +108,10 @@ Summary Question | Answer ----------------------------------------------|------- - Phone tab accepts US numbers? | No + Registration page accepts US numbers? | No Is there a country code selector? | No Can userscript bypass the regex? | Yes (client-side only) Will the SMS actually reach a US number? | Almost certainly not - Is there a phone-free registration path? | Yes — email tab - Does email registration work from any country?| Yes + Is there an email registration tab/path? | No (removed; dead code remains) + Does switching language reveal email tab? | No + Practical solution without a Thai number? | Virtual Thai SIM (see th-live-phone-validation.txt) From 300a87a325c1246d21a973c0a5e3e0a23fb412b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:58:56 +0000 Subject: [PATCH 09/33] Fix userscript null-crash and Vue3 VNode traversal (v0.2) Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-email-tab-missing.txt | 141 ++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 66 deletions(-) diff --git a/th-live-email-tab-missing.txt b/th-live-email-tab-missing.txt index e5193a3..6435426 100644 --- a/th-live-email-tab-missing.txt +++ b/th-live-email-tab-missing.txt @@ -128,9 +128,9 @@ state and calls the email OTP flow directly. // ==UserScript== // @name th-live.online — Email Registration Attempt // @namespace https://th-live.online -// @version 0.1 +// @version 0.2 // @description Tries to restore the removed email registration tab. -// The backend may or may not honour the email OTP call. +// The backend may or may not honor the email OTP call. // @match https://th-live.online/* // @run-at document-idle // @grant none @@ -139,52 +139,73 @@ state and calls the email OTP flow directly. (function () { 'use strict'; - // Wait for Vue to mount and the route to be /register - function tryPatch() { - if (location.hash !== '#/register' && !location.pathname.endsWith('/register')) { - return; + var patchTimer; + + // Walk a Vue3 VNode tree to find the first component instance whose + // data() contains a 'registerForm' property. + // + // Vue3 VNode structure: + // vnode.component — present when the vnode represents a component; + // this is the component *instance* object + // componentInstance.subTree + // — the VNode tree rendered by that component + // vnode.children — child VNodes for plain element / Fragment nodes + function findComp(node) { + if (!node) return null; + + // Component VNode: check the instance, then recurse into its rendered output + if (node.component) { + var ci = node.component; + if (ci.data && 'registerForm' in ci.data) return ci; + var r = findComp(ci.subTree); + if (r) return r; } - // Find the Vue app root - const root = document.querySelector('#app') || document.querySelector('[data-v-app]'); - if (!root || !root.__vue_app__) return; - - // Walk the component tree to find the register component instance - // (it has a registerForm property in its data) - function findComp(vnode) { - if (!vnode) return null; - const comp = vnode.component; - if (comp && comp.data && comp.data.registerForm !== undefined) return comp; - if (vnode.subTree) { - const r = findComp(vnode.subTree); - if (r) return r; - } - if (vnode.children && Array.isArray(vnode.children)) { - for (const c of vnode.children) { - const r = findComp(c); - if (r) return r; + // Plain element / Fragment: recurse into child VNodes + if (Array.isArray(node.children)) { + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + if (child && typeof child === 'object') { + var r2 = findComp(child); + if (r2) return r2; } } - return null; } - const app = root.__vue_app__; - const instance = findComp(app._instance.subTree); - if (!instance) { - console.warn('[email-reg] Could not find register component instance.'); + return null; + } + + function tryPatch() { + if (location.hash !== '#/register' && !location.pathname.endsWith('/register')) { return; } - const proxy = instance.proxy; // the public `this` equivalent in Vue3 + // Find the Vue app root element + var root = document.querySelector('#app') || document.querySelector('[data-v-app]'); + if (!root || !root.__vue_app__) return; + + var app = root.__vue_app__; + + // _instance is null until Vue has fully mounted the root component — + // return silently and wait for the next poll tick. + if (!app._instance || !app._instance.subTree) return; + + var instance = findComp(app._instance.subTree); + if (!instance) return; // not on /register yet, keep polling + + // Component found — stop polling + clearInterval(patchTimer); + + var proxy = instance.proxy; // the public Vue3 component `this` // Inject email field into registerForm if missing if (!('email' in proxy.registerForm)) { proxy.registerForm.email = ''; } - // Build a simple email input + button overlay + // Build an email input + OTP overlay (only once) if (document.getElementById('__email_reg_overlay')) return; - const overlay = document.createElement('div'); + var overlay = document.createElement('div'); overlay.id = '__email_reg_overlay'; overlay.style.cssText = [ 'position:fixed', 'top:80px', 'left:50%', 'transform:translateX(-50%)', @@ -193,54 +214,42 @@ state and calls the email OTP flow directly. 'box-shadow:0 4px 20px rgba(0,0,0,.2)', 'font-family:sans-serif' ].join(';'); - overlay.innerHTML = ` -

Register with Email (experimental)

- - -
- - -
-

-

Note: backend may not honour this request.

- `; + overlay.innerHTML = + '

Register with Email (experimental)

' + + '' + + '' + + '
' + + '' + + '' + + '
' + + '

' + + '

Note: backend may not honor this request.

'; + document.body.appendChild(overlay); document.getElementById('__email_send').onclick = function () { - const email = document.getElementById('__email_inp').value.trim(); + var email = document.getElementById('__email_inp').value.trim(); if (!email) return; proxy.registerForm.email = email; - // Switch to email mode - proxy.active = 1; - // Call sendVcoderegister which now uses email branch - proxy.sendVcoderegister(); - document.getElementById('__email_status').textContent = 'OTP sent (if backend allows)…'; + proxy.active = 1; // switch to email mode + proxy.sendVcoderegister(); // request OTP via email branch + document.getElementById('__email_status').textContent = 'OTP sent (if backend allows)\u2026'; }; document.getElementById('__email_verify').onclick = function () { - const code = document.getElementById('__email_code').value.trim(); + var code = document.getElementById('__email_code').value.trim(); if (!code) return; proxy.registerForm.code = code; proxy.codeValidate(); - document.getElementById('__email_status').textContent = 'Attempting verification…'; + document.getElementById('__email_status').textContent = 'Attempting verification\u2026'; }; - - console.info('[email-reg] Email registration overlay injected.'); } - // Poll until the register page mounts - const timer = setInterval(() => { - tryPatch(); - }, 1000); - - // Stop polling after 60 s - setTimeout(() => clearInterval(timer), 60000); + // Poll every 500 ms; stop automatically after 60 s + patchTimer = setInterval(tryPatch, 500); + setTimeout(function () { clearInterval(patchTimer); }, 60000); })(); ---------------------------------------------------------------------- From be30e527a3c346e2f6f14df7e281903196fca448 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:12:02 +0000 Subject: [PATCH 10/33] Fix overlay never appearing (v0.3): decouple from component scan, lazy resolution, check setupState; add clones section Co-authored-by: Zero3K20 <258969903+Zero3K20@users.noreply.github.com> --- th-live-email-tab-missing.txt | 161 +++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 43 deletions(-) diff --git a/th-live-email-tab-missing.txt b/th-live-email-tab-missing.txt index 6435426..50926f9 100644 --- a/th-live-email-tab-missing.txt +++ b/th-live-email-tab-missing.txt @@ -128,7 +128,7 @@ state and calls the email OTP flow directly. // ==UserScript== // @name th-live.online — Email Registration Attempt // @namespace https://th-live.online -// @version 0.2 +// @version 0.3 // @description Tries to restore the removed email registration tab. // The backend may or may not honor the email OTP call. // @match https://th-live.online/* @@ -141,27 +141,37 @@ state and calls the email OTP flow directly. var patchTimer; - // Walk a Vue3 VNode tree to find the first component instance whose - // data() contains a 'registerForm' property. + // ── Vue3 component-instance lookup ────────────────────────────────────────── // // Vue3 VNode structure: - // vnode.component — present when the vnode represents a component; - // this is the component *instance* object - // componentInstance.subTree - // — the VNode tree rendered by that component + // vnode.component — present when the vnode is a component node; + // value is the component *instance* object + // instance.subTree — the VNode tree that component rendered // vnode.children — child VNodes for plain element / Fragment nodes + // + // State location differs by API style: + // Options API → instance.data (set by data() function) + // Composition → instance.setupState (set by setup() /