Open
Conversation
warrentc3
added a commit
to warrentc3/go.xtream-codes
that referenced
this pull request
Apr 25, 2026
tellytv#8 — targeted omitzero on preempted big-3 fields (Tvdb*) and on ServerInfo.IE/IEAuth. With FlexInt and ConvertibleBoolean now being struct types, ,omitempty doesn't fire — every record was emitting tvdb: 0 (and tvdb_id: 0) on round-trip, plus ie/ie_auth on records from providers that don't send them. Fix: - IsZero() method on FlexInt and ConvertibleBoolean - ,omitzero (Go 1.24+) on Tvdb fields across Stream / SeriesInfo / VODInfo / EpisodeInfo / SeriesEpisode / Season - ,omitzero on ServerInfo.IE / ServerInfo.IEAuth Not applied to TmdbID with existing ,omitempty: data analysis from this cycle showed tmdb=0 carries semantic signal in some providers (ProviderA: 2,425/28,471 records explicitly tmdb=0 = "no TMDB ID"). Eliding it would lose the signal. tellytv#9 — VideoOnDemandInfo.UnmarshalJSON empty-array tolerance was too permissive: []struct{} happily decodes a non-empty array of objects (drops the contents). Switched the sentinel check to []json.RawMessage + len == 0; non-empty arrays now surface the original decode error rather than silently dropping metadata. tellytv#11 — sendRequest network/read errors now use %w (not %v) so callers can errors.Is/As-unwrap the underlying http or io error. Refs: PR #3 follow-up review by Copilot. Comments tellytv#7 (FlexInt struct-vs-numeric API break) and tellytv#10 (CI unit tests for skipped sample-data path) deferred — tellytv#7 is a pre-this-PR design choice (commit e39998b), tellytv#10 is a CI hygiene item to address separately.
warrentc3
added a commit
to warrentc3/go.xtream-codes
that referenced
this pull request
Apr 25, 2026
* Upgrade to Go 1.26; fix FlexInt/FlexFloat quoting round-trip; add missing ServerInfo fields
- Go 1.17 → 1.26; gomega v1.17.0 → v1.39.1
- FlexInt/FlexFloat converted from primitive aliases to structs with quoted bool — preserves original JSON quoting through marshal/unmarshal round-trip
- Remove ,string struct tags from all FlexInt fields (were stripping quotes before UnmarshalJSON, breaking quoting detection)
- Add NewFlexInt(v int64) constructor for external callers
- ServerInfo: add Version, Revision, XUI fields (empirically confirmed missing)
- Test: filepath.ToSlash for Windows path separator; os.ReadFile replaces ioutil.ReadFile
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Empirical struct validation against real provider data
- SeriesInfo: add TmdbID (tmdb), CategoryIDs, EpisodeRunTime (FlexInt); normalize
releaseDate/release_date via UnmarshalJSON
- Season: new named type replacing []interface{}; typed fields, Duration FlexInt
- EpisodeInfo: new named type; remove phantom ffprobe fields; add MovieImageTmdb, TmdbID
- SeriesEpisode: use EpisodeInfo; Added → Timestamp
- VODInfo/VODMovieData: new named types; remove ffprobe fields; add Name, OriginalName,
CoverBig, Actors, Description, Age, Country, Status
- Stream: add TmdbID, Trailer, IsAdult ConvertibleBoolean, CategoryIDs
- FlexInt: handle empty string (→0) and non-numeric strings (→0); add String() method
- FlexFloat: add String() method
- ConvertibleBoolean: add Bool() accessor, String() method; use tagged switch
Validated against 28K+ real provider records across series, VOD, and auth endpoints.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Add cross-provider validation harness
provider_validation_test.go runs structs against real provider sample
data (ProviderA/B/C). Three checks per (provider × endpoint): lenient
unmarshal + record count, strict per-record decode for unknown-field
reporting, semantic round-trip diff. Stdlib-only; deliberately avoids
gomega's MatchJSON formatter that hangs on large payloads (the reason
TestGetStreams was deferred).
Sample-data discovery: XTREAM_SAMPLE_DATA_DIR env var with
../../_sample_data/iptv-proxy workspace-relative fallback; t.Skip if
neither resolves so the test stays inert in foreign environments.
Runs in 6.5s on 90MB / 1165 lines. Surfaces ProviderC unquoted
SeriesEpisode.id as the only critical decode failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add warrentc3 copyright line; rewrite README for fork posture
Retain MIT with Telly's original copyright preserved; add warrentc3
contribution copyright line per standard named-fork convention.
README rewritten from 3-line stub to minimal fork identification:
upstream attribution, one-line change summary, license pointer.
Changelog: Items 2, 3 (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Remove upstream test file and gomega dependency
Delete xtream-codes_test.go (TestNewClient, TestGetCategories,
deferred TestGetStreams, plus TestMain / walkFunc / getClient
helpers) and its testData/ fixture directory. Drop gomega require
from go.mod; go mod tidy collapses 7 indirect deps
(google/go-cmp, kr/pretty, rogpeppe/go-internal, go.yaml.in/yaml/v3,
golang.org/x/net, golang.org/x/text, check.v1).
Rationale: gomega v1.39.1 MatchJSON formatter hangs on large payloads
(TestGetStreams was already deferred for this). The holistic test
suite gets designed later in iptv-proxy project scope; inherited
upstream tests aren't worth preserving as placeholders.
provider_validation_test.go remains as the sole test file, stdlib-only.
go.xtream-codes now has zero third-party dependencies.
Changelog: Item 9a (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Delete ffmpeg.go
Remove the ffprobe type hierarchy: Format, Disposition, SideData,
FFMPEGStreamInfo, ProbeInfo, Tags, the StreamType enum (VideoStream/
AudioStream), NewInfo, FilterStreams, IsRotated, Rotation, abs.
These types were designed to deserialize full ffprobe output, but
providers never populate the ffprobe fields. Empirical captures from
ProviderB and ProviderC show providers emit the audio/video keys as
empty scaffolding ({"disposition": {}, "tags": {}}) because the XC
library dictates the response shape, not because they carry real data.
Commit 38b945b already removed the fields from EpisodeInfo and VODInfo;
the ffmpeg.go types were internally orphaned. Public-API break is
theoretical — consumers of these types would have been interacting
with always-empty structs.
POSTERITY's deferred "absorb vs drop ffprobe blocks" decision resolves
to drop based on this evidence.
Changelog: Item 11 (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* flex_types: quote-detection consistency, Y2K38 fix, zero-length guard
Align ConvertibleBoolean and Timestamp quote-detection to the
leading-byte pattern used by FlexInt/FlexFloat (bytes.TrimSpace +
data[0] == '"'). Replaces any-position strings.Contains — more
principled check, consistent across the four flex types.
Timestamp.UnmarshalJSON: switch strconv.Atoi to ParseInt(s, 10, 64).
strconv.Atoi returns int; on 32-bit platforms Unix timestamps exceed
2^31-1 at 2038-01-19 03:14:07 UTC (Y2K38), silently truncating to
negative values. ParseInt guarantees int64 regardless of platform.
JSONStringSlice.UnmarshalJSON: guard against empty data. encoding/json
typically doesn't call UnmarshalJSON with an empty slice, so this is
latent rather than live — defensive two-liner removes a potential
panic path for zero behavioral cost.
Changelog: Items 13, 14 (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* SeriesInfo.Rating: FlexInt -> FlexFloat
Providers send rating values as quoted floats ("7.0", "6.0", "9.0"),
not integers. FlexInt.UnmarshalJSON couldn't parse quoted floats into
int64 — parse failure silently zeroed the field post-38b945b, or
propagated as an unmarshal error pre-38b945b. Either way, rating data
was lost.
Observed in container logs 2026-04-24:
json: cannot unmarshal number 7.0 into Go struct field .rating of type int64
FlexFloat handles quoted/unquoted, empty-string, and non-numeric with
graceful fallback. Consistent with the adjacent Rating5based FlexFloat.
Recorded in POSTERITY 2026-04-23 as a deferred struct fix; promoted
to Phase 2 because the fix is one line and the failure was actively
logging.
Changelog: Item 15 (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Client idiom cleanup
- defaultUserAgent: fix xstream/xtream typo in the default UA string
sent on every outbound request when callers don't override.
- sendRequest: rename local url variable to requestURL so it no
longer shadows the imported net/url package. Collapse two-step
http.NewRequest + request.WithContext(c.Context) into the idiomatic
http.NewRequestWithContext call (post-Go-1.13 form).
- GetCategories, GetSeriesInfo, GetVideoOnDemandInfo, getEPG: apply
err-before-mutate uniformly. GetCategories previously mutated the
decoded slice regardless of unmarshal error; the other three
returned (data, jsonErr) without checking, giving callers
partial-data-plus-error semantics. GetStreams and GetSeries
already follow the idiom and are untouched.
Pure idiom cleanup; no behavior change on the success path.
Changelog: Items 4, 5, 10, 12 (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Client resilience: HTTP timeout, AuthError, HTTPError, content heuristic
Introduce typed HTTPError and AuthError for structured error inspection.
HTTPError carries status/content-type/body from sendRequest when a
non-2xx response lands or when a 2xx body fails the non-JSON heuristic.
Callers can type-assert to get the captured response for diagnosis.
AuthError is returned by NewClient when authentication fails. Three
detection paths: HTTPError translated from sendRequest (auth call got
a non-2xx status), non-JSON 200 response (heuristic fires), or 2xx
with parsed AuthenticationResponse whose UserInfo is zero-valued
(null envelope or missing user_info — both leave Username empty).
sendRequest rewrite:
- Always read body before returning, via defer response.Body.Close()
and io.ReadAll. Fixes the pre-existing resource leak where non-2xx
early-returns skipped Body.Close().
- Capture StatusCode, Content-Type, and body into HTTPError on
status >= 400 (previously a plain fmt.Errorf with no body).
- Apply the isNonJSONErrorBody heuristic on 2xx responses: empty
body, leading '<' (HTML error page), or plain-text sentinels
(blocked / forbidden / access denied / unauthorized). Sentinel
list sourced secondhand from Dispatcharr's Python client and is
not empirically validated against our captures — comment in code
flags this.
NewClient: HTTP client now &http.Client{Timeout: 90 * time.Second}
instead of http.DefaultClient. 90s chosen to accommodate legitimate
large responses (XMLTV can exceed a minute in Warren's experience)
while still failing visibly on a hung upstream.
Auth failure shape design is empirically grounded: auth probes against
three providers (ProviderA 512/HTML, ProviderB 404/empty, ProviderC
404-HTML for unknown-user + 401-JSON for wrong-password) show status
codes span 401/404/512 and content-types span JSON/HTML/empty.
StatusCode >= 400 covers all three; the Username-empty check is
defense-in-depth for 200-with-error-envelope that Dispatcharr's client
guards against even though we haven't captured it ourselves.
Probe scripts: _helper_scripts/warrens_ps1s/xc/Get-XCAuthFailureRandom*.ps1
Captures: _sample_data/iptv-proxy/Provider{A,B,C}/auth_failure_*.json
Changelog: Items 6, 7, 8 (c:/git/_situational_awareness/iptv-proxy/GO_XTREAM_CODES_POLISH_CHANGELOG.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* SeriesEpisode.ID: string -> FlexInt
ProviderC (Dispatcharr) emits SeriesEpisode.id as an unquoted JSON number
(17008, 17009, ...) while A and B emit quoted strings ("91012"). The
previous string type failed decode on C with "cannot unmarshal number
into Go struct field SeriesEpisode.episodes.id of type string."
FlexInt accepts both forms. No other behavior change.
* VideoOnDemandInfo: tolerate info: [] as zero-value
ProviderA emits "info": [] (empty array) on 5 of 50 get_vod_info records
when the metadata block is absent. The current VODInfo value-type field
failed decode with "cannot unmarshal array into Go value of type
xtreamcodes.VODInfo" on those records.
Custom UnmarshalJSON on VideoOnDemandInfo attempts struct decode and,
if that fails specifically because the payload is an array, accepts it
as a zero-value VODInfo rather than rejecting the whole record. Any
other decode error still propagates.
* Stream: add live/series_no/type_name; TVArchive -> ConvertibleBoolean
Additive fields providers emit that the struct dropped:
- Live (ProviderB vod_streams + live_streams) — bool-as-int; "0"/"1"
- SeriesNo (ProviderB vod_streams, frequently null) — *FlexInt
- TypeName (ProviderB vod_streams) — descriptive category ("Movies" etc.)
TVArchive semantic is also bool-as-int (1 = timeshift / "go back in time"
capable, 0 = live-only). Promoting from FlexInt to ConvertibleBoolean
makes the boolean nature explicit and matches Live. Byte-level round-trip
is preserved since ConvertibleBoolean.MarshalJSON emits 0/1.
* VODInfo: add runtime, episode_run_time, kinopoisk_url, year, cover
Fields providers emit that the struct dropped:
- Runtime (A: 37/50, B: 39/44) — minutes as quoted string
- EpisodeRunTime (A: nullable/number) — *FlexInt
- KinopoiskURL (A: 9/50, sparse) — Russian movie DB link
- Year (C: 46/46) — unquoted number, separate from release_date
- Cover (C: 46/46) — alternate to cover_big
release_date alias coalesce (for ProviderC which sends release_date
instead of releasedate) is addressed in a later commit alongside the
big-3 content-ID alias coalesce work.
* EpisodeInfo: add per-episode metadata fields seen in captures
Fields providers emit that the struct dropped:
- AirDate (B: 35/37) — broadcast date; semantically distinct from
ReleaseDate (theatrical/premiere)
- BackdropPath (C) — *JSONStringSlice to tolerate single-string or array
- Bitrate (B) — FlexInt
- Crew (B) — string
- DirectedBy (C) — string
- Duration (B) — string (e.g. "45:00")
- DurationSecs (B) — FlexInt
- ID (B: string, C: number) — FlexInt handles both
- Name (C) — per-episode display name
- Overview (C) — per-episode synopsis
release_date alias (ProviderC sends release_date rather than releasedate)
is addressed in a later commit alongside the big-3 content-ID alias
coalesce work.
* ServerInfo/UserInfo/SeriesInfo: add missing additive fields
- ServerInfo.IE, ServerInfo.IEAuth (ProviderB) — native JSON booleans;
ConvertibleBoolean accepts both true/false and 0/1 shapes
- UserInfo.PlaylistName (ProviderB) — string; present on some providers
- SeriesInfo.Added (ProviderC series_info.info) — *Timestamp, optional
* Content-ID coalesce: tmdb/imdb/tvdb alt-key preempt + release_date alias
Big-3 content-ID aliases via alias-aware UnmarshalJSON on six content
structs. Per-struct canonical tag preserved (Stream/SeriesInfo/Season/
SeriesEpisode use short form tmdb/imdb/tvdb; VODInfo/EpisodeInfo use
long form tmdb_id/imdb_id/tvdb_id matching existing tmdb_id precedent).
All six structs accept both forms on input.
tvdb is preempted (no provider captures currently emit it) so that
future provider additions don't silently drop the key.
Bundled in the same UnmarshalJSON pass: release_date/releasedate alias
coalesce on VODInfo and EpisodeInfo (ProviderC emits snake_case form
where the struct uses the no-underscore form). Parallel to the existing
releaseDate/release_date handling on SeriesInfo and Season.
Types:
- Tmdb/Tvdb — FlexInt (numeric, may arrive quoted or unquoted)
- Imdb — string (alphanumeric "tt0111161" canonical form)
Structs in scope: Stream, SeriesInfo, VODInfo, EpisodeInfo,
SeriesEpisode, Season. Explicit exclusions: ServerInfo, UserInfo,
Category, VODMovieData, EPGInfo, wrapper types (Series,
VideoOnDemandInfo, AuthenticationResponse).
* Modernize CircleCI config
- Bump schema 2 -> 2.1
- Replace circleci/golang:1.10 with cimg/go:1.26 (matches go.mod)
- Drop go get for gomega (removed at 72d4794) and gometalinter
(deprecated, replaced industry-wide by golangci-lint)
- Replace gometalinter step with go build + go vet; keep go test
(provider_validation_test.go is the sole test file)
working_directory still reads tellytv module path; couples with the
publish-step module rename (GX06).
* Address Copilot review on PR #3
Three substantive fixes:
1. XMLTV URL no longer appends a redundant &action=xmltv.php query
parameter — the file path /xmltv.php encodes the endpoint, and the
server treats the duplicate as malformed (worked at upstream by server
tolerance; correctness fix).
2. isNonJSONErrorBody is no longer applied to /xmltv.php responses. The
heuristic flags bodies with a leading '<' as non-JSON errors, which
broke GetXMLTV() since XMLTV payloads start with the XML declaration.
The skip is gated on action == "xmltv.php"; JSON-expected endpoints
continue to receive the heuristic.
3. HTTPError doc tightened: "non-2xx" -> ">= 400". 3xx redirects are
followed by http.Client and never surface here.
Two comment tightenings (no behavior change):
4. isNonJSONErrorBody comment now states the sentinel match is exact
(bytes.Equal), not substring — matching what the code does and
explaining why (false-positive risk against legitimate JSON payloads
containing these words).
5. FlexInt.UnmarshalJSON now carries an explicit doc comment naming the
tolerance-by-design choice. Providers emit empty strings, nulls, and
non-numeric strings on int-typed fields; record-level decode failure
is worse than zero-coercion in this domain.
Two declined with inline comment (no behavior change):
6. Username/password URL interpolation stays unescaped — Xtream
credentials are uniformly [a-zA-Z0-9]{10} across providers, the
upstream and Dispatcharr clients use the same raw concatenation, and
no provider in the captures has issued a credential containing
reserved URL characters. A code comment names the call.
Refs: PR #3 review by Copilot (six points), all six addressed.
* Address Copilot follow-up review on PR #3
tellytv#8 — targeted omitzero on preempted big-3 fields (Tvdb*) and on
ServerInfo.IE/IEAuth. With FlexInt and ConvertibleBoolean now being
struct types, ,omitempty doesn't fire — every record was emitting
tvdb: 0 (and tvdb_id: 0) on round-trip, plus ie/ie_auth on records from
providers that don't send them. Fix:
- IsZero() method on FlexInt and ConvertibleBoolean
- ,omitzero (Go 1.24+) on Tvdb fields across Stream / SeriesInfo /
VODInfo / EpisodeInfo / SeriesEpisode / Season
- ,omitzero on ServerInfo.IE / ServerInfo.IEAuth
Not applied to TmdbID with existing ,omitempty: data analysis from
this cycle showed tmdb=0 carries semantic signal in some providers
(ProviderA: 2,425/28,471 records explicitly tmdb=0 = "no TMDB ID").
Eliding it would lose the signal.
tellytv#9 — VideoOnDemandInfo.UnmarshalJSON empty-array tolerance was too
permissive: []struct{} happily decodes a non-empty array of objects
(drops the contents). Switched the sentinel check to []json.RawMessage
+ len == 0; non-empty arrays now surface the original decode error
rather than silently dropping metadata.
tellytv#11 — sendRequest network/read errors now use %w (not %v) so callers
can errors.Is/As-unwrap the underlying http or io error.
Refs: PR #3 follow-up review by Copilot. Comments tellytv#7 (FlexInt
struct-vs-numeric API break) and tellytv#10 (CI unit tests for skipped
sample-data path) deferred — tellytv#7 is a pre-this-PR design choice
(commit e39998b), tellytv#10 is a CI hygiene item to address separately.
* gitignore provider_validation_test.go (CI hygiene per Copilot tellytv#10)
The test runs an empirical capture-driven sweep against
_sample_data/iptv-proxy/Provider*/ (which lives in the iptv-proxy
workspace, not the library repo). When the sample-data directory is
absent — including any CI environment that clones only this repo —
the test skips silently. CI was reporting "ok ... [tests pass]" while
asserting nothing.
gitignore + git rm --cached: the file persists locally for development
use (workspace overlay resolves the sample data); CI sees no test files
and reports the truthful "[no test files]" state. Eliminates the
false-positive coverage signal flagged by Copilot.
Refs: PR #3 follow-up review by Copilot, comment tellytv#10.
* gitignore .circleci/ — defer to publish step alongside go.mod rename
The CircleCI config was modernized in commit a2cf099 (Go 1.26, dropped
gomega/gometalinter), but working_directory still pins
github.com/tellytv/go.xtream-codes — the same upstream namespace
go.mod still carries (GX06, decided as Method B but gated on Layer 1
publish-readiness).
Same posture as provider_validation_test.go in the prior commit: keep
the file locally, untrack it from git so the public surface doesn't
ship in a half-renamed state. Restored at publish-step alongside the
module-path rename and any CircleCI/GitHub Actions integration the
public release needs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.