feat(playlist): auto-detect import method that parses pasted provider messages - #1445
feat(playlist): auto-detect import method that parses pasted provider messages#14454gray wants to merge 17 commits into
Conversation
… messages Adds a deterministic parser (detectProviderImportCandidates in libs/shared/interfaces) that scans a pasted provider message for URLs, MAC addresses, and labeled credentials/identity fields (username, password, serial, device IDs, signatures), classifies the source as Xtream, Stalker, or an M3U link/body, and returns ranked candidates. Every extracted value is a verbatim substring or a canonical form from an existing shared normalizer; detection only proposes — the existing forms and their behavioral probes (portal discovery, connection test) stay authoritative. The Add playlist dialog gains a sixth "Auto-detect" method with a textarea that runs the parser on every edit and renders candidate cards; picking one switches to the matching import form with the fields prefilled. Detection runs fully locally; pasted text never leaves the app. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
… methods The auto-detect surface is destroyed by the dialog's method switch, so inspecting a prefilled form and coming back lost the paste. The text now lives on the dialog and is fed back into the surface on re-entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
…port Reseller messages dress labels in Unicode math alphabets and arrow separators to slip past chat spam filters. The detector now NFKC-normalizes the pasted text before scanning, so decorated labels fold back to the plain ASCII the vocabulary matches, and accepts arrow/geometric/dingbat glyphs, '>' and a space-surrounded dash as label separators alongside ':' and '='. A hyphen glued inside a word stays prose, so hyphenated words are still not read as labeled values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
…s in auto-detect Extends the deterministic detector for three real reseller message shapes: - Negative-circled / squared / regional-indicator 'font' capitals and circled digits (🅤🅢🅔🅡, 🅜➌🅤) that NFKC leaves alone are folded to the ASCII they depict before scanning, so decorated USER/PASS/URL labels are recognized. - 'DEVICE ID=> 1&2 <hex>' hands one hex value to both device-ID slots. - 'S N', 'S/N', 'S.N' serial labels, the Turkish 'MAC ADRESİ' label, and a guard so 'ADULT PASS' (the parental PIN) is not read as the account password. All three pasted messages are covered verbatim by new parser tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
Regional-indicator runs of exactly two are real flag emoji (🇳🇱); folding them glued letters onto adjacent text and could break a following label's word boundary. Only runs of three or more — fancy-text words like 🇺🇸🇪🇷 — fold to ASCII now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
…, decorated dual device IDs
Second corpus batch (14 real reseller messages; 11 already parsed). New:
- Bare 'URL, token, token' handouts with no labels at all become a
low-confidence Xtream guess — only when the message contains exactly those
three lines, a single generic URL, and no label/MAC produced anything.
- Separator-less serials ('SN 38415545307A3') are accepted when the value is
hex-shaped, so prose cannot match.
- The dual device-ID marker now allows any symbol between 1 and 2 (¹💥², 1/2)
and whitespace-only label separators, again guarded by the hex value shape.
- A separately labeled port completes a port-less 'Portal: http://host' URL.
Nine messages from the batch join the test corpus verbatim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
Greptile SummaryThe PR adds an auto-detect playlist-import method that parses pasted provider messages and prefills the existing M3U, Xtream, or Stalker forms.
Confidence Score: 4/5The PR should not merge until multi-account messages stop silently losing every MAC account after the twelfth. The earlier candidate-limit issue remains reachable because extractMacAddresses still slices detected accounts to twelve before candidate assembly, so larger provider handouts produce incomplete import choices despite the claimed fix. Files Needing Attention: libs/shared/interfaces/src/lib/provider-import-scan.util.ts Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Paste provider message] --> B[Normalize and scan]
B --> C[Rank import candidates]
C --> D{Selected candidate}
D -->|Xtream| E[Prefilled Xtream form]
D -->|Stalker| F[Prefilled Stalker form]
D -->|M3U URL| G[Prefilled URL form]
D -->|M3U body| H[Prefilled text form]
E --> I[Existing validation and import]
F --> I
G --> I
H --> I
Prompt To Fix All With AI### Issue 1
libs/shared/interfaces/src/lib/provider-import-scan.util.ts:404
**Account extraction still truncates**
When a provider message contains thirteen or more distinct MAC accounts, `extractMacAddresses` keeps only the first twelve before candidate assembly, causing every later account to disappear from the selectable import results.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (2): Last reviewed commit: "ci: retrigger checks after category-mana..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e17a4ba641
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const portal = pickStalkerPortalUrl(urls, labeled); | ||
| for (const macAddress of macs) { |
There was a problem hiding this comment.
Keep each Stalker record correlated with its MAC
When the pasted message contains multiple complete Stalker accounts, the portal and labeled identity fields are selected once before this loop and then reused for every detected MAC. For example, two portal/MAC/device-ID blocks produce a second candidate containing the first block's portal and device ID, which can make authentication fail or submit another device's identity with that MAC. Parse and associate fields per account block rather than combining every MAC with the first global values.
Useful? React with 👍 / 👎.
| */ | ||
| const LABEL_SEPARATOR = String.raw`(?:\s*(?:[:=>]|[←-⇿■-◿☀-➿⬀-⯿])+\s*|\s+[-–—]+\s+)`; | ||
|
|
||
| const DEFAULT_VALUE = String.raw`[^\s,;|]+`; |
There was a problem hiding this comment.
Preserve punctuation in labeled credentials
When a labeled username or password contains a comma, semicolon, or pipe, this value pattern stops at that character and silently prefills a truncated credential—for example, PASS: abc,123 becomes abc. These characters are accepted by the target forms, and the masked password field makes the corruption easy to miss, resulting in failed connection checks; delimiters should be identified from the field boundary without removing valid credential characters.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Deliberate trade-off rather than an oversight. ,/;/| double as inline pair delimiters in real handouts (User: a, Pass: b on one line — present in the collected corpus), and there is no deterministic way to tell that comma from one inside a password. The cost asymmetry drives the choice: panel software (Xtream-UI/XUI) generates alphanumeric passwords, so comma-in-password is rare, while inline pairs are common — and the truncated value lands in an editable form the user reviews before anything runs (the form shows the password in clear, only the candidate card masks it). If corpus samples with punctuated passwords show up, the planned fix is boundary-aware capture (delimiter only when followed by whitespace or a known label), not dropping the delimiters.
Generated by Claude Code
There was a problem hiding this comment.
Pull request overview
Adds an Auto-detect method to the Add playlist dialog, enabling users to paste a provider message and have IPTVnator detect likely import sources (Xtream/Stalker/M3U) and prefill the appropriate existing import form.
Changes:
- Introduces deterministic provider-message scanning + candidate ranking via
detectProviderImportCandidatesinlibs/shared/interfaces. - Adds the Auto-detect UI surface (textarea + candidate cards) and dialog handoff/prefill wiring.
- Extends playlist-type unions, adds i18n strings across languages, updates docs, and adds a release note.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| libs/workspace/shell/util/src/lib/workspace-shell-actions.ts | Extends workspace playlist-type union to include auto. |
| libs/portal/shared/util/src/lib/portal-shell-actions.ts | Extends portal playlist-type union to include auto. |
| libs/playlist/shared/ui/src/lib/add-playlist-menu/playlist-type.ts | Extends shared playlist type to include auto. |
| libs/shared/interfaces/src/lib/provider-import-scan.util.ts | Adds low-level scanners (URLs, labeled fields, MACs, Unicode folding) for auto-detect. |
| libs/shared/interfaces/src/lib/provider-import-detection.util.ts | Adds detectProviderImportCandidates candidate assembly/ranking and helpers. |
| libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts | Adds comprehensive detection tests covering multiple real-world message shapes. |
| libs/shared/interfaces/src/index.ts | Exports provider-import-detection.util from shared interfaces package. |
| libs/playlist/import/feature/src/lib/auto-import/auto-import.component.ts | Implements auto-detect UI component (paste area + candidate list + selection emit). |
| libs/playlist/import/feature/src/lib/auto-import/auto-import.component.html | Template for auto-detect textarea and candidate cards. |
| libs/playlist/import/feature/src/lib/auto-import/auto-import.component.scss | Styles for the auto-detect surface and candidate cards. |
| libs/playlist/import/feature/src/lib/auto-import/auto-import.component.spec.ts | Unit tests for candidate computation, restoring text, and event emission. |
| libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.ts | Wires auto-detect into the dialog and applies prefills to target forms. |
| libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.html | Adds auto switch case rendering the auto-detect component. |
| libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.scss | Updates method grid layout to accommodate 6 methods. |
| libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.spec.ts | Adds tests for auto-detect candidate handoff and form prefills. |
| CLAUDE.md | Updates repo documentation to mention the new auto-detect import method. |
| apps/web/src/assets/i18n/en.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/de.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/el.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/es.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/fr.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/hu.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/it.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/ja.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/ko.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/nl.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/pl.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/pt.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/ru.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/tr.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/zh.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/zhtw.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/ar.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/ary.json | Adds auto-detect method strings and auto-detect UI strings. |
| apps/web/src/assets/i18n/by.json | Adds auto-detect method strings and auto-detect UI strings. |
| .changes/playlist-auto-detect-import.md | Adds a user-facing release note for the new auto-detect import method. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const WRAPPING_CHARS = /^["'`«<([]+|["'`»>)\]]+$/g; | ||
|
|
There was a problem hiding this comment.
The pattern is an alternation, not a single anchored expression: /^[wrappers]+|[wrappers]+$/g — with the g flag, replace removes the leading run and the trailing run in one pass. Verified: stripWrapping('"user"') === 'user', stripWrapping('(pass)') === 'pass', stripWrapping('«val»') === 'val'. An anchored-whole-string reading would require /^[wrappers]+$/, which this is not.
Generated by Claude Code
| const key = [ | ||
| candidate.kind, | ||
| candidate.url ?? '', | ||
| candidate.serverUrl ?? '', | ||
| candidate.username ?? '', | ||
| candidate.portalUrl ?? '', | ||
| candidate.macAddress ?? '', | ||
| ].join('|'); |
…MACs, honest caps, password in dedupe key - Multi-MAC messages no longer attach globally scanned serial/device-ID/ signature/credential labels to every MAC: with several accounts in one message there is no reliable owner for a first-match label, and a device ID submitted with the wrong MAC is pinned by the portal permanently. Identity fields now attach only when exactly one MAC is present. - MAC and candidate caps raised (12/16) so real multi-account lists are never silently truncated; the caps remain only as pathological-paste guards. - The candidate dedupe key includes the password, so two accounts sharing a server and username (e.g. a rotation message) both stay selectable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@codex review Round-1 findings are addressed in 98c3b44 (no identity cross-pairing across multiple MACs, raised truncation caps, password included in the candidate dedupe key); the two declined threads carry replies with reasoning. Requesting a fresh pass on the current head. Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98c3b442f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const portal = pickStalkerPortalUrl(urls, labeled); | ||
| const identity: LabeledFields = macs.length === 1 ? labeled : {}; | ||
| for (const macAddress of macs) { | ||
| candidates.push( | ||
| compact({ | ||
| kind: 'stalker', | ||
| confidence: portal?.confidence ?? 'low', | ||
| portalUrl: portal?.url, |
There was a problem hiding this comment.
Correlate each Stalker portal with its MAC
When a paste contains two complete Stalker blocks using different portals, portal is still selected once from the first URL and then assigned to every detected MAC, so the second candidate is prefilled with the first account's portal and cannot authenticate. The 98c3b44 follow-up only suppresses identity fields for multi-MAC input; it leaves this cross-pair intact. Associate portals and MACs by account block, or omit the portal when the association is ambiguous.
Useful? React with 👍 / 👎.
| const normalized = foldDecorativeAlphabets(raw.normalize('NFKC')); | ||
| const urls = extractUrls(normalized); | ||
| const labeled = extractLabeledFields(normalized, urls); |
There was a problem hiding this comment.
Preserve credential bytes while normalizing labels
When a provider uses a valid Unicode compatibility character in a username or password—for example PASS: pass123—normalizing the entire paste before extraction rewrites the credential to pass123, and selecting the candidate submits different bytes so authentication fails. Normalize only text used to recognize labels, or retain source spans and copy credential values from the original paste.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Deliberate, and documented at the fold site: the whole point of the NFKC+decorative-fold pass is that chat handouts dress ASCII in compatibility glyphs — labels (𝙋𝙰𝚂𝚂) and values alike. Panel software (Xtream-UI/XUI) generates ASCII credentials; a fullwidth pass123 in a Telegram paste is the same decoration artifact as the labels around it, and folding it back to pass123 recovers the bytes the panel actually knows — submitting the fullwidth original is what would fail. Characters NFKC leaves alone (Cyrillic, Arabic, plain ASCII) pass through untouched. Span-mapping values back through a length-changing normalization would add real complexity to guard a shape absent from the 19-message corpus this was built against; if a genuinely non-compatibility credential shows up there, this gets revisited with evidence.
Generated by Claude Code
|
|
||
| const WRAPPING_CHARS = /^["'`«<([]+|["'`»>)\]]+$/g; | ||
|
|
||
| const MAX_URLS = 8; |
There was a problem hiding this comment.
Do not discard URLs before applying the candidate cap
When a provider message contains nine or more distinct playlist links, extractUrls stops at eight, so later links never become candidates even though the public candidate cap was raised to 16. A nine-link paste currently returns only eight results; raise this scanner cap consistently or apply a single cap after candidate assembly.
Useful? React with 👍 / 👎.
…nner and candidate caps - Several MACs meeting portal-shaped URLs on different hosts now yield MAC-only candidates: there is no deterministic owner for either portal, and prefilling half the accounts with the wrong panel only fails later at authentication. Same-host Real/Panel pairs and one portal shared by a MAC list stay unambiguous. - The URL scanner cap is raised to match the candidate cap (16), so a nine-link handout is no longer truncated to eight before assembly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.
Suppressed comments (2)
libs/playlist/import/feature/src/lib/auto-import/auto-import.component.ts:135
- The candidate summary for
m3u-urlrenders the full URL verbatim. For common Xtream-style links (get.php?...username=...&password=...) this exposes credentials in the UI even though passwords are otherwise masked on candidate cards. Consider masking sensitive URL userinfo/query params (at leastusername/password) for display, while keeping the originalcandidate.urlunchanged for prefilling the form.
switch (candidate.kind) {
case 'm3u-url':
push('HOME.URL_UPLOAD.PLAYLIST_URL', candidate.url);
break;
libs/shared/interfaces/src/lib/provider-import-detection.util.ts:29
- The doc comment claims every detected value is a verbatim substring of the NFKC-normalized input (or an existing normalizer output), but the implementation can also synthesize URLs via
labeledHostUrl()(addshttp://and/or a separately labeled port). Updating the comment to mention this exception would keep the contract accurate for future maintainers.
The packaged frame-copy smoke on the linux portable build timed out waiting for the first rendered frame (llvmpipe runner); the same job passed on the previous commits of this branch and nothing in this PR touches playback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b58ed505a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const detected: DetectedUrl[] = []; | ||
| const seen = new Set<string>(); | ||
| for (const match of allMatches(URL_PATTERN, text)) { | ||
| const raw = match[0].replace(TRAILING_PUNCTUATION, ''); |
There was a problem hiding this comment.
Preserve terminal punctuation in URL credentials
When an Xtream URL contains an unescaped password ending in valid URL punctuation, such as get.php?username=u&password=abc!, this unconditional cleanup strips the final character before query parsing and prefills abc instead of abc!; it also alters the fallback M3U URL. Strip punctuation only when it is provably prose surrounding the URL, not from a successfully parsed query credential.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Declined as the lesser harm, deterministically unresolvable either way: …password=abc! (credential ends in !) and Check http://host/get.php?username=u&password=abc! (sentence exclamation after the URL) are byte-identical inputs — no parser can tell them apart. The trade-off picks by frequency and cost: URLs wrapped in prose punctuation are pervasive in chat handouts (the corpus includes one), panel-generated passwords are alphanumeric, and a truncated credential fails visibly at Test Connection in an editable field. Stripping only "provably prose" punctuation is not implementable — the proof does not exist in the string. If corpus samples with punctuation-terminal query credentials appear, the candidate fix is offering both the stripped and unstripped variant, not disabling the strip.
Generated by Claude Code
| creds?.username ?? | ||
| (macs.length === 0 ? labeled.username : undefined); | ||
| const password = | ||
| creds?.password ?? | ||
| (macs.length === 0 ? labeled.password : undefined); |
There was a problem hiding this comment.
Correlate labeled Xtream credentials per account
When a paste contains multiple Xtream blocks whose API URLs do not carry query credentials, every URL receives the first globally extracted username and password. For example, separate one.test/player_api.php/alice and two.test/player_api.php/bob blocks produce a second candidate for two.test with alice's credentials, so the prefilled connection fails; associate labeled fields with their surrounding URL/account block rather than reusing labeled for the whole paste.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Declined for the inverse reason the MAC case was fixed. Multiple API URLs sharing one labeled credential pair is the COMMON real shape — reseller "main + backup server" handouts, one account valid on both hosts — and suppressing or first-URL-only attachment would break exactly that. Two separate accounts as separate labeled blocks in one paste is absent from the 19-message corpus. The cost asymmetry also differs from the Stalker fix: a wrong Xtream credential fails visibly at Test Connection in an editable form and costs nothing, while a device ID submitted with the wrong MAC is pinned by the portal permanently — that irreversibility is why multi-MAC got the conservative treatment and this stays as-is. Revisited if a multi-account Xtream block sample lands in the corpus.
Generated by Claude Code
| const stalkerHosts = new Set( | ||
| urls | ||
| .filter((url) => url.role === 'stalker') | ||
| .map((url) => url.parsed.hostname) | ||
| ); |
There was a problem hiding this comment.
Include generic portals in the ambiguity check
When multiple MAC blocks use different root or otherwise generic portal URLs, this host set remains empty because it includes only URLs already classified as stalker; pickStalkerPortalUrl nevertheless accepts the first generic URL, so every MAC is still paired with that first portal. Fresh evidence in the current tip is this mismatch between the new ambiguity guard and the existing generic-URL fallback; include generic portal candidates in the multi-host ambiguity check or omit the portal in this case.
Useful? React with 👍 / 👎.
… ambiguity guard The round-2 guard judged ambiguity over portal-shaped URLs only, while the portal picker falls back to a generic URL when no shaped one exists — two root-URL panels with several MACs still cross-paired through that fallback. The guard now inspects the exact pool the picker draws from. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
One predicate timeout in category-management.e2e.ts on the macOS runner (115 passed / 1 failed); the same suite is green on Ubuntu and Windows in the same run, and nothing in this PR touches category management. The earlier frame-copy smoke flake passed on this run, confirming both as runner timing flakes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@greptileai review Requesting fresh passes on the current head (c0df0df). Note for the Greptile summary above: its 3/5 verdict reviewed e17a4ba and is stale — both blocking P1s are fixed since 98c3b44 (identity fields no longer attach across multiple MACs; MAC/URL/candidate caps raised to 12/16/16 so multi-account lists surface fully), plus follow-ups 1d59932/3248c54 extend the same guard to ambiguous portals including the generic-URL fallback. Declined threads carry replies with reasoning. Generated by Claude Code |
| for (const match of allMatches(BARE_INFOMIR_MAC_PATTERN, text)) { | ||
| push(normalizeStalkerMacAddress(match[0])); | ||
| } | ||
| return macs.slice(0, MAX_MACS); |
There was a problem hiding this comment.
Account extraction still truncates
When a provider message contains thirteen or more distinct MAC accounts, extractMacAddresses keeps only the first twelve before candidate assembly, causing every later account to disappear from the selectable import results.
Prompt To Fix With AI
This is a comment left during a code review.
Path: libs/shared/interfaces/src/lib/provider-import-scan.util.ts
Line: 404
Comment:
**Account extraction still truncates**
When a provider message contains thirteen or more distinct MAC accounts, `extractMacAddresses` keeps only the first twelve before candidate assembly, causing every later account to disappear from the selectable import results.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c0df0dfabd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : urls.filter((url) => url.role === 'generic'); | ||
| const portalAmbiguous = | ||
| macs.length > 1 && | ||
| new Set(portalPool.map((url) => url.parsed.hostname)).size > 1; |
There was a problem hiding this comment.
Treat distinct portal origins as ambiguous
When a multi-account paste contains two Stalker panels on the same DNS name but different ports, such as http://panel.example:8080/c/ and http://panel.example:9090/c/, both hostname values compare equal, so this guard treats the paste as unambiguous and assigns the first portal URL to every MAC. Fresh evidence in the current head is that the ambiguity check still ignores the port; compare origins or otherwise include the port so candidates are not prefilled with the wrong panel.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.
Suppressed comments (2)
libs/playlist/import/feature/src/lib/auto-import/auto-import.component.ts:134
- The auto-detect cards currently render
m3u-urlcandidates using the raw URL string. Forget.php?...&password=...links this exposes the password in cleartext on the candidate card, even though other password fields are masked. Mask query-string passwords before displaying the URL (keep the full URL value in the candidate object for prefilling).
switch (candidate.kind) {
case 'm3u-url':
push('HOME.URL_UPLOAD.PLAYLIST_URL', candidate.url);
break;
libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.ts:184
pendingPrefillis only cleared after a successful prefill. If the user selects an auto-detect candidate and then clicks a different method before the target child component is instantiated, the candidate stays pending and can later prefill unexpectedly when the user visits that method. Clear the pending candidate when the currentmethodno longer matches the candidate’s intended target form.
private applyPendingPrefill(): void {
const candidate = this.pendingPrefill();
if (!candidate) {
return;
}
switch (candidate.kind) {
…ortal ambiguity - The MAC and candidate caps are raised to 64/96: they exist to keep a pathological paste (a log full of MAC-shaped hex) from rendering thousands of cards, not to bound real handouts — scanner dumps top out around a couple dozen accounts, so no real multi-account message is truncated. - The multi-MAC portal ambiguity guard compares URL origins instead of hostnames, so two panels on one DNS name but different ports no longer cross-pair; URL normalization drops default ports, keeping the Real/Panel ':80'-vs-bare pairs of scanner dumps unambiguous. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c67f0f8eb4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export function extractUrls(text: string): DetectedUrl[] { | ||
| const detected: DetectedUrl[] = []; | ||
| const seen = new Set<string>(); | ||
| for (const match of allMatches(URL_PATTERN, text)) { |
There was a problem hiding this comment.
Stop URL scanning once the candidate cap is reached
When a large provider dump or log contains many URL-shaped strings, allMatches materializes every regex match before this loop begins, so the MAX_URLS break does not bound scanning or allocation. Because detection runs synchronously on every textarea edit, a sufficiently large paste can freeze the renderer or consume substantial memory despite the stated DOM-safety cap; iterate the matcher lazily so processing can stop after enough valid unique URLs are collected.
Useful? React with 👍 / 👎.
| .auto-import__candidate-head { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; |
There was a problem hiding this comment.
Allow candidate headers to wrap on phone widths
On narrow phone viewports, this single-line flex row cannot fit the fixed icon, non-wrapping confidence badge, candidate title, gaps, and Material action button inside the viewport-clamped dialog. At roughly 320–375 px wide, the header overflows horizontally and can clip or require horizontal scrolling to reach “Fill the form”; add a narrow-width wrapping or stacked layout for the badge and action.
Useful? React with 👍 / 👎.
- URL and MAC extraction drive their regex matchers lazily and stop at the caps instead of materializing every match of a pathological paste first — detection runs on each keystroke, so allocation must stop where the cap stops accepting. - The candidate card header wraps on narrow phone dialogs instead of overflowing horizontally past the Fill-the-form action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
…ect prefills, doc the assembled-host exception Addresses Copilot's suppressed review comments: - The m3u-url candidate card no longer shows a get.php password in clear — the display masks the query value while the untouched URL still prefills the form. - A picked candidate is dropped if the user switches to another method before its target form mounts, so it cannot lie in wait and prefill a later visit. - The parser contract now names its one assembled-value exception (labeledHostUrl completing a scheme-less host with http:// and a labeled port). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 526e8abc3e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const URL_PATTERN = /https?:\/\/[^\s<>"'`]+/gi; | ||
| const TRAILING_PUNCTUATION = /[),.;:!?\]»›]+$/; | ||
| const M3U_PATH_PATTERN = /\.m3u8?$/i; | ||
| const XTREAM_API_PATH_PATTERN = /\/(?:get|player_api|panel_api)\.php$/i; |
There was a problem hiding this comment.
Strip panel_api.php from detected Xtream server URLs
When a provider supplies http://host/panel_api.php with labeled credentials, this pattern classifies it as an Xtream API URL, but normalizeXtreamServerUrl() strips only get.php and player_api.php. The candidate therefore prefills http://host/panel_api.php, and the Xtream transport subsequently appends /player_api.php, requesting the invalid /panel_api.php/player_api.php path. Either normalize this recognized endpoint to its base URL or stop classifying it as supported.
Useful? React with 👍 / 👎.
| const portalAmbiguous = | ||
| macs.length > 1 && | ||
| new Set(portalPool.map((url) => url.parsed.origin)).size > 1; |
There was a problem hiding this comment.
Distinguish Stalker panels hosted under different paths
When two MAC blocks use distinct installations on one origin, such as https://panel.example/a/stalker_portal/c/ and https://panel.example/b/stalker_portal/c/, this origin-only set has size one, so both candidates receive the first portal and the second account cannot authenticate. Fresh evidence beyond the earlier host/port cases is that different installation paths are still collapsed; include the normalized portal installation path in the ambiguity key while preserving known alternate endpoints for the same installation.
Useful? React with 👍 / 👎.
…uity for tenant installs - normalizeXtreamServerUrl strips panel_api.php like the other API endpoints, so a panel_api.php handout (pasted or auto-detected) prefills the server base instead of a URL the transport would double-suffix. - The multi-MAC portal ambiguity key is origin plus the installation base path: tenant installs sharing one origin (/a/stalker_portal/c/ vs /b/...) no longer collapse, while alternate endpoints of one install (/c/, portal.php, server/load.php, Real/Panel pairs) still compare equal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e81d7034d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| compact({ | ||
| kind: 'xtream', | ||
| confidence: username && password ? 'high' : 'medium', | ||
| serverUrl: safeXtreamServerUrl(url.raw), |
There was a problem hiding this comment.
Honor separately labeled ports on Xtream API URLs
When a provider supplies Server: http://panel.example/player_api.php and Port: 8080 on separate lines, labeledHostUrl() correctly assembles the port, but this URL-shaped branch ignores that result and normalizes the raw URL instead. The candidate therefore prefills http://panel.example, and connection checks target the wrong origin; use the assembled labeled URL when the detected API URL is the labeled host.
Useful? React with 👍 / 👎.
| const shaped = urls.find((url) => url.role === 'stalker'); | ||
| if (shaped) { | ||
| return { url: shaped.raw, confidence: 'high' }; |
There was a problem hiding this comment.
Honor separately labeled ports on shaped Stalker URLs
When a handout contains Portal: http://panel.example/c/ plus a separate Port: 8080, finding the shaped URL here returns it before labeledHostUrl() can add the port. The prefilled portal consequently omits :8080 and cannot authenticate against panels listening there; prefer the assembled labeled URL when the shaped URL came from the portal label.
Useful? React with 👍 / 👎.
| shapedPortals.length > 0 | ||
| ? shapedPortals | ||
| : labeledHostUrl(labeled) !== undefined | ||
| ? [] | ||
| : urls.filter((url) => url.role === 'generic'); |
There was a problem hiding this comment.
Include generic panels in mixed portal ambiguity checks
When a multi-account paste contains one shaped portal such as http://panel-a.example/c/ and one root portal such as http://panel-b.example, this conditional excludes the generic URL because shapedPortals is nonempty, so ambiguity remains false and every MAC receives panel A. Fresh evidence beyond the earlier all-generic ambiguity report is that the new guard handles only homogeneous URL-role pools; include distinct generic portal installations when evaluating a mixed pool.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Declined: merging generic URLs into the pool once a shaped portal exists would misfire on the shapes the corpus actually contains. When a shaped portal is present, the generic URLs around it are, in observed handouts, either the same panel's "Real" line (same origin — already compares equal under the installation key) or decoration links (t.me channels, provider homepages) that are not portals at all; treating those as competing installations would strip the portal from legitimate single-panel MAC lists — the corpus #6 shape — far more often than it would catch the hypothesized two-panel paste, which is absent from the corpus. The cost stays bounded either way: multi-MAC candidates carry no identity fields, so a wrong portal is one failed login with a MAC, not a pinned device. Revisited if a mixed shaped+root two-panel handout shows up in the corpus.
Generated by Claude Code
…tal URLs 'Portal: http://host/…' plus 'Port: 8080' on its own line is a real handout shape; the labeled port now completes port-less Xtream API URLs, shaped Stalker portals, and the generic-URL fallback alike. A URL stating its own port explicitly is never overridden. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3723eb8e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| while ( | ||
| detected.length < MAX_URLS && | ||
| (match = matcher.exec(text)) !== null |
There was a problem hiding this comment.
Mask URLs beyond the candidate cap
When a paste contains more than 16 distinct URLs, this loop stops returning URLs, but extractLabeledFields() later masks only the returned set. Query parameters in every later URL remain visible to the label regexes; for example, an early credential-less player_api.php, 15 playlist links, and a 17th URL containing ?username=wrong&password=wrongpass produces a high-confidence candidate for the early panel using the later URL's credentials. Keep the result cap, but mask spans for all URL-shaped text or otherwise reject labeled matches inside capped-out URLs.
Useful? React with 👍 / 👎.
| const portalAmbiguous = | ||
| macs.length > 1 && | ||
| new Set(portalPool.map(portalInstallationKey)).size > 1; |
There was a problem hiding this comment.
Apply the labeled port before checking portal ambiguity
When a multi-MAC dump lists alternate URLs for one panel as http://panel:8080/c/ and http://panel/c/ plus Port: 8080, the second URL would be completed to the same origin by completeWithLabeledPort(), but this check compares the raw origins first and declares them different installations. Both Stalker candidates consequently lose the otherwise unambiguous portal and become low-confidence MAC-only entries; normalize each pool entry with the labeled port before deriving its installation key.
Useful? React with 👍 / 👎.
… extraction cap - The portal installation key is derived after labeled-port completion, so 'http://panel:8080/c/' next to 'http://panel/c/' plus a 'Port: 8080' line reads as one panel instead of stripping the portal from its MAC list. - Label matchers mask EVERY URL-shaped span, not just the capped extraction result: a URL past the candidate cap still carries a label-shaped query, and its credentials must not attach to an earlier credential-less panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
What changed
The Add playlist dialog gains a sixth method, Auto-detect: paste whatever the provider sent — links, Xtream credentials, a MAC address with device identity — and the app recognizes the source(s) and prefills the matching import form.
detectProviderImportCandidates(libs/shared/interfaces): extracts URLs, MAC addresses, and labeled fields (username/password/host+port/serial/device IDs/signatures), classifies each finding as Xtream, Stalker, or an M3U link/body, and returns ranked candidates. Pure, synchronous, runs on every keystroke.➤,►,=>), separator-less hex serials (SN 38415…), dual device IDs (DEVICE ID=> 1&2 <hex>,¹💥²), multi-MAC account lists (one candidate per MAC), bareURL + user + passthree-line handouts (low-confidence guess), a guard soADULT PASS(parental PIN) is never read as the account password, and TurkishMAC ADRESİlabels.auto-importsurface inlibs/playlist/import/feature: candidate cards with confidence badges and masked passwords; picking one switches the dialog to the matching form with fields prefilled. The pasted text survives switching methods and back.CLAUDE.mdupdated.Why
Providers routinely share credentials as free-form chat messages without saying whether it is Xtream, Stalker, or M3U. Users had to know which of five import forms to pick and copy fields one by one; now they paste the message and confirm a prefilled form.
Release note
.changes/(playlist-auto-detect-import.md)Checks
shared-interfaces, 78 inplaylist-import-feature)pnpm run lintand the affectedpnpm nx test <project>pass (shared-interfaces,playlist-import-feature,playlist-shared-ui;pnpm nx build webgreen)🤖 Generated with Claude Code
https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
Generated by Claude Code