Skip to content

docs: Starlight documentation site at ttlj.docs.isaackogan.com - #162

Open
isaackogan wants to merge 31 commits into
jwdeveloper:masterfrom
isaackogan:docs/starlight-site
Open

isaackogan wants to merge 31 commits into
jwdeveloper:masterfrom
isaackogan:docs/starlight-site

Conversation

@isaackogan

Copy link
Copy Markdown
Contributor

This adds a documentation site for TikTokLiveJava under docs/, live at https://ttlj.docs.isaackogan.com/. It is built with Astro and Starlight, generated from the library sources on every build, and checked so that it cannot drift from the code.

What is in it

  • Authored pages: Getting Started, configuration (client settings, HTTP settings, signing, authentication, proxies and host, logging), guides (gifts, listeners, custom events, recorder, collector, offline testing, debugging), a grouped reference for all 78 event classes, advanced pages (HTTP client, WebSocket, protocol), troubleshooting (all exception classes), contributing, and hosting.
  • Generated reference: API pages for the public classes of API, Client, extension-recorder, and extension-collector (JavaParser), and schema pages for every message and enum in the three .proto files. Generated pages are never committed; pnpm --dir docs build regenerates them.
  • Content check (docs/tools/): a small Maven tool that compiles every Java example on the pages against the built library and verifies that every event class, every LiveClientSettings field, every LiveClient method and EventsBuilder handler, every exception class, and both extensions are documented. The build fails if any of that drifts.
  • SEO gate: canonical URLs, sitemap, robots, Open Graph tags, and navigation on every built page.
  • Hosting: docs/wrangler.jsonc (Cloudflare Workers static assets) and .github/workflows/docs.yml.

How the workflow behaves here

On this repository the workflow validates every pull request and push that touches the library or the docs (library build, tool tests, content check, site build, SEO gate) and uploads the built site as an artifact. It deploys only when the repository has the CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID secrets, and the daily scheduled run skips itself while they are absent, so nothing changes for you unless you add them. Until then the site is deployed from my fork, which rebuilds daily from this repository's master once the docs are merged, so the hosted pages track your releases without further pushes.

Local development

npx -y pnpm@10.34.2 --dir docs install
npx -y pnpm@10.34.2 --dir docs build     # library, tool, check, generate, site
npx -y pnpm@10.34.2 --dir docs dev

Requires JDK 17 or newer, Maven, and Node 24. docs/src/content/docs/contributing.md has the authoring rules.

Changes outside docs/

  • README.md: a pointer to the hosted documentation, a · docs link on each event in the list, a Documentation: line under each event section, and the event section source links corrected to the api/ path that exists (they returned 404). Two links to the Node project now point at zerodytrash/TikTok-Live-Connector.
  • collaboration.md: a short Documentation section.
  • .github/workflows/docs.yml: new.
  • docs/tools/src/**/*.java: carry the repository's license header exactly as the root license-maven-plugin applies it on every build (so the build leaves the tree clean); the header text and attribution are the project's own.
  • docs/superpowers/: the design document and implementation plan the site was built from, kept for the record.

Library findings

Verified against 1.13.1-Release while writing the documentation; the site documents the current behaviour, so each of these is a candidate fix rather than a blocker for this PR. Paths are relative to the repository root; client/… stands for Client/src/main/java/io/github/jwdeveloper/tiktok/client/… and api/… for API/src/main/java/io/github/jwdeveloper/tiktok/api/….

Connection and sockets

  • Second connect() on a connected client flips state but keeps the socket — client/TikTokLiveClient.java:119-121 (and :108-110 on the Euler path) throws TikTokLiveException("Already connected"), which :87-90 handles like a connection failure: the state becomes DISCONNECTED and TikTokErrorEvent plus TikTokDisconnectedEvent are published, while webSocketClient.stop() is never called; a user sees a live client announce a disconnect although messages keep arriving. Fix: perform the state check before the try, outside the catch.
  • offline = true never connects — client/TikTokLiveHttpOfflineClient.java:42-44 returns a LiveUserData.Response whose room info is null; TikTokRoomInfo.copy() skips the null (client/TikTokRoomInfo.java:61), so client/TikTokLiveClient.java:136 dereferences it (userData.getRoomInfo().getRoomId()) and throws NullPointerException, which :100-104 logs as "Unhandled exception report this bug to github" and swallows; a user sees that message and a stack trace, and connect() returns without connecting or throwing. Fix: have the offline client return a TikTokRoomInfo with a placeholder id, or read the id from roomInfo after copy().
  • connectAsync(Consumer) drops failures — client/TikTokLiveClient.java:209-211 chains thenAccept(onConnection) and discards the future; when connect() throws, the consumer never runs and no code observes the exception, so a user gets neither a callback nor a handle to learn the outcome (only the TikTokErrorEvent that connect() publishes on its own). Fix: return the CompletableFuture from this overload, or attach .exceptionally(...).
  • WebSocket handshake timeout uses toMillisPart() — client/websocket/TikTokWebSocketClient.java:69, client/websocket/euler/TikTokWebSocketEulerClient.java:65 and client/http/HttpProxyClient.java:112-113 pass getTimeout().toMillisPart(), the sub-second remainder of the Duration (0 for Duration.ofSeconds(n)), as the connect (and SOCKS read) timeout in milliseconds; Java-WebSocket and HttpsURLConnection both treat 0 as "no timeout", so a user who sets a whole-second timeout gets none. Fix: (int) getTimeout().toMillis().
  • Euler stop() leaves a handshaking socket running — client/websocket/euler/TikTokWebSocketEulerClient.java:81-96 closes only when isConnected() (:98-100, which requires isOpen()) and then nulls the reference; connect() (:75) is non-blocking, so a disconnect() issued before the handshake completes neither closes the socket nor keeps a way to close it later, and a user ends up with a background connection they cannot stop. Fix: close any socket that is not yet isClosed(), as client/websocket/TikTokWebSocketClient.java:147-152 does.
  • Euler listener drops unrecognised frames silently — client/websocket/euler/TikTokWebSocketEulerListener.java:53-63 ignores a JSON frame without a messages array and any message whose type is neither workerInfo nor roomInfo (the switch has no default); a user sees nothing, not even TikTokWebsocketUnhandledMessageEvent, when the server sends an error object or a new frame type. Fix: log or publish an unhandled event from a default branch and for frames without messages.
  • Catch marked "This will never be thrown" discards the cause — client/websocket/TikTokWebSocketClient.java:115-118 wraps SSLContext.getInstance("SSL")/init() in catch (Exception e) and throws TikTokProxyRequestException("Unable to set Socks proxy SSL instance") without e; if it ever fires, a user sees the message with no underlying stack trace. Fix: catch NoSuchAlgorithmException | KeyManagementException and pass e as the cause.

HTTP, proxies and mapping

  • HTTP proxy rotation never ends on a non-200 response — client/http/HttpProxyClient.java:64-70 loops while (proxySettings.hasNext()) and continues on any status other than 200, but api/data/settings/ProxyClientSettings.java:80-82 defines hasNext() as !proxyList.isEmpty() and next() wraps around (:94-95); when every proxy connects but TikTok answers 403/429, a user sees connect() hang. Fix: bound the loop to one pass over the list (getProxyCount() attempts), or map non-200 to a failed ActionResult as client/http/HttpClient.java:55-69 does.
  • autoDiscard applies different rules per proxy type — the HTTP branch (client/http/HttpProxyClient.java:71-74) removes the endpoint only on HttpConnectTimeoutException/ConnectException, the SOCKS branch (:139-144) on any IOException; a user with the same list sees different proxies survive depending on type. Fix: apply one rule in both branches.
  • TikTokProxyRequestException never reaches the caller, and exhaustion falls back to a direct request — client/TikTokLiveHttpClient.java:98-109 (same shape at :73-83, :128-139, :185-196, :212-222) retries while (proxyClientSettings.hasNext()) and swallows the exception; with autoDiscard the list empties and the trailing call outside the loop is built as a plain HttpClient (client/http/HttpClientBuilder.java:99-101), so the request goes out unproxied from the user's own address; without autoDiscard a persistently failing proxy loops forever. A user can never catch TikTokProxyRequestException from connect(), fetch* or sendChat. Fix: count attempts, rethrow the last TikTokProxyRequestException, and do not fall through to a direct request while proxies are enabled.
  • roomId setting is never read — api/data/settings/LiveClientSettings.java:116-118 says the id "can be set manually", but nothing reads it: client/TikTokLiveClient.java:125-136 always resolves the room from the host name. A user who sets it sees no effect. Fix: honour it in tryConnect() or remove the field.
  • Euler room info puts totalViewers into viewersCount and never sets ageRestricted — client/http/mappers/LiveUserDataMapper.java:116-117 calls setViewersCount for totalViewers, overwriting the currentViewers value from :114-115, and mapEulerstream (:100-144) has no setAgeRestricted; on the Euler WebSocket path a user sees getViewersCount() equal to the total, getTotalViewersCount() 0 and isAgeRestricted() always false. Fix: setTotalViewersCount at :117, and set ageRestricted from the payload where available.
  • A malformed user-data body is reported as "user not found" — client/http/mappers/LiveUserDataMapper.java:94-97 (and :140-143) converts JsonSyntaxException, IllegalStateException and NullPointerException into UserStatus.NotFound, so client/TikTokLiveClient.java:131-132 throws TikTokLiveUnknownHostException("User not found: …"); a user whose request was blocked, rate-limited or served an HTML page is told the host does not exist. Fix: throw TikTokLiveRequestException from the catch instead.
  • HTTP status messages say "Sign server" for TikTok's own API — client/http/HttpClient.java:60-66 is the response mapper for every request, so a 429 or 5xx from www.tiktok.com/api-live/user/room or webcast.tiktok.com/webcast/room/info reads "Sign server rate limit reached" / "Sign server Error"; a user debugging a TikTok block is pointed at the wrong service. Fix: drop "Sign server" from the generic messages or include the request host.

Events and extensions

  • Sixteen event classes can never be produced — client/mappers/TikTokGenericEventMapper.java:62 (mapToEvent) has no caller (client/mappers/TikTokLiveMapperHelper.java:46 uses only getParsingMethod), and the registrations that would feed it are commented out at client/mappers/MessagesMapperFactory.java:132-149; TikTokChestEvent, TikTokDetectEvent, TikTokEnvelopeEvent, TikTokGoalUpdateEvent, TikTokIMDeleteEvent, TikTokInRoomBannerEvent, TikTokLinkApplyEvent, TikTokLinkLayerEvent, TikTokLinkMicFanTicketEvent, TikTokLinkMicMethodEvent, TikTokLinkReplyEvent, TikTokRankTextEvent, TikTokRankUpdateEvent, TikTokShopEvent, TikTokSubNotifyEvent and TikTokUnauthorizedMemberEvent are therefore never published by the default client, and a user subscribed to them receives the message only as TikTokWebsocketUnhandledMessageEvent. Fix: register each with forMessage (the shape at MessagesMapperFactory.java:143-146), or deprecate the classes.
  • Dispatch is exact-class and unordered — client/TikTokLiveEventHandler.java:39-42 looks up handlers for TikTokEvent.class and event.getClass() only, and :45 keeps them in a HashSet; a user registered for TikTokGiftEvent never sees TikTokGiftComboEvent (a subclass), likewise TikTokLinkEvent for its subclasses, and several handlers for one event run in no defined order. Fix: walk getSuperclass() up to TikTokEvent when publishing, and use a LinkedHashSet (or List) to keep registration order.
  • TikTokHttpResponseEvent has no publisher — api/live/builder/EventsBuilder.java:95-96 and api/listener/TikTokEventListenerBase.java:119 expose onHttpResponse, but nothing constructs the event, so a user's handler never runs. Fix: publish it from client/http/HttpClient.toHttpResponse() or remove the hook.
  • Five event classes lack @EventMeta — api/data/events/common/TikTokEvent.java, TikTokLiveClientEvent.java, TikTokEmptyEvent.java, TikTokHeaderEvent.java and TikTokUnhandledEvent.java carry no @EventMeta while the other 74 event classes do; a user (or tool) that reads the annotation to categorise events gets no EventType for them. Fix: add @EventMeta(eventType = …) or state that base classes are exempt.
  • Gift streaks: Finished is conditional and a late finishing frame still double-counts after fix(gift): stop streak gifts being dropped and double counted #160 — client/mappers/handlers/TikTokGiftEventHandler.java:121-124 publishes the Finished combo event only when an Active/Begin frame for the same key was seen, and once a streak is flushed by the 5-minute timeout (:143-149, :51) its key is remembered for only 60 s (FINALIZED_RETENTION_MS, :57, checked at :117-119), so a finishing frame arriving later reaches :125 with previous == null and raises a second TikTokGiftEvent; a user counting gifts sees a streak that began before they connected end without Finished (the TikTokGiftEvent still fires) and a slow streak counted twice. Fix: publish Finished for every finishing frame, and keep the finalized key at least as long as the combo timeout.
  • Recorder with a bare output filename records nothing — extension-recorder/src/main/java/io/github/jwdeveloper/tiktok/extension/recorder/impl/RecorderListener.java:98 calls file.getParentFile().mkdirs(); getParentFile() is null for a path with no directory (e.g. setOutputFile(new File("stream.flv"))), the NullPointerException is caught at :117-119 and only printed, and because it precedes the inner try/finally (:101-116) no TikTokLiveRecorderEndedEvent is published; a user sees TikTokLiveRecorderStartedEvent, a stack trace, and no file. Fix: var parent = file.getAbsoluteFile().getParentFile(); if (parent != null) parent.mkdirs();.

README

  • Duplicated sections and dead intro anchors (pre-existing) — README.md:169 and :173 are both ## Events, :188-189 list onEvent twice, and :297 and :318 are two ## onEvent sections; the intro at :30 links #member, #gift, #subscribe, #roomuser, #social, #questionnew, #like and #linkmicbattle, none of which matches a heading (GitHub generates e.g. #ongift-tiktokgiftevent), so a user clicking them goes nowhere. On 1.13.1-Release the same items are at :167/:171, :184-185, :285/:304 and :30. Fix: remove the duplicates and point the intro at the generated anchors.

https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ

Social scrapers need a raster og:image; the raw GitHub SVG the config
pointed at is not one. Render the project logo to a 1200x630 PNG with a
white background (logo contained and centred) under docs/public and point
og:image at it on the site's own origin.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
Port the Python sibling's post-build checks to Node (ESM, node: built-ins
only): homepage title, per-page canonical equal to the page's own URL,
Open Graph and meta description, sitemap index and pages, robots.txt,
dofollow Euler Stream links on every page with varied homepage anchor
text, and the sidebar navigation marker. The CLI prints one PASS/FAIL
line per check and exits 1 on any failure; CI runs it as a separate step
after the build via `pnpm --dir docs run seo`.

The test script uses Node's own glob (node --test "scripts/**/*.test.mjs")
because node --test treats a bare directory argument as a file to run.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
Add a documentation pointer after the introduction and a Documentation
entry at the top of the events list, both to https://ttlj.docs.isaackogan.com/.
Each events-list item keeps its in-page anchor and gains a docs link to
the event's section on the hosted event pages, and each event section
carries a Documentation line with the same link. Each section heading's
source link gains the api/ path segment the files moved under, so the
links resolve again. The two links to the Node original now point at
zerodytrash/TikTok-Live-Connector, where that repository lives.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
…d mapping behaviour

Keep the connection, WebSocket, and troubleshooting pages consistent on
the daemon flag the socket's read thread inherits; say "every exception
the library defines"; tag the collaboration fence and list the dev
prerequisites; and apply the deferred wording minors (room-info
defaults and update paths, Euler roomInfo fields, offline + Euler path,
Euler text frames, connectAsync(Consumer) failures, unhandled-exception
order, autoDiscard split, SOCKS propagation and read timeout, catch-all
dispatch order, forAnyMessage hook order, mapping-exception causes,
mapping fragment imports, user attributes, Gradle url assignment,
ffmpeg path, README blank line, .gitignore separator).

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
…, queue master deploys

The upstream job now sets has_docs=false with a notice when a scheduled
run has no Cloudflare secrets, treats only a 404 from the GitHub API as
"no docs", and fails the job on any other status or a failed request.
Pull-request runs still cancel each other; master pushes and scheduled
runs queue in the shared group instead of cancelling a deploy. The
hosting page, docs README, and contributing page describe the new step,
both notices, the failure mode, and that a fork-only change is
overwritten by the 06:17 UTC upstream deploy until merged upstream.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
…ks raw

summary() no longer un-escapes "\<"; the type page's description
frontmatter, which is JSON-quoted, gets the plain form through
Markdown.plain(). <pre> blocks are cut out of the description before
the HTML conversion, escaping and tidying, and emitted as a fenced block
with entities decoded, {@code} content verbatim (JavaParser's cut at the
first brace is repaired) and common indentation removed; single-line
contexts (field bullets, enum values, @param/@return) render them as a
code span instead of a broken fence. MarkdownTest pins the listing
line, the description frontmatter, the fenced block, the bullets with
pipes, the link and the {@link} on a synthetic compilation unit.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
…ted links in the check

The events section now also requires a "## <EventClassName>" heading on
a grouped page under events/ other than the catalog, so the row alone
no longer passes; the readme section resolves every hosted link in the
README to an authored page and, with a fragment, to a heading whose
slug equals it. Fence languages are compared case-insensitively. Tests
cover a passing and a failing case for each rule and pin the real
README against the real pages.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
Schema pages take their directory from the .proto file's name, threaded
through ProtoFile, with the package-derived stem as the fallback. The
content check scans README handler items only within the README's
events list, reports duplicated catalog rows, and fails the examples
section on an unclosed fence instead of dropping it. The example
compiler maps javac errors on hoisted imports back to the line they
were written on, batches full units by every type they declare so
same-named helper classes do not collide, and lifts javac's error cap.
The SEO gate's tests assert the exact check count, drop two unused
exports and fix the run comment; the Euler host regex escapes every dot.

Claude-Session: https://claude.ai/code/session_014bbgdemLuSLpxB3kaK7zfJ
@isaackogan

Copy link
Copy Markdown
Contributor Author

LOL. Claude, my god. What an insane PR.

@kohlerpop1

Just merge it, it's only docs, lmfao, then DM me on discord i need to give you GitHub actions secrets

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