Skip to content

Some Changes; User-Agent#11

Open
ALiP61 wants to merge 2 commits intotellytv:masterfrom
ALiP61:master
Open

Some Changes; User-Agent#11
ALiP61 wants to merge 2 commits intotellytv:masterfrom
ALiP61:master

Conversation

@ALiP61
Copy link
Copy Markdown

@ALiP61 ALiP61 commented Jan 8, 2024

No description provided.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant