Skip to content

Latest commit

 

History

123 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gundam DX Stream Suite

Gundam DX Stream Suite is an automation tool for live streaming "Mobile Suit Gundam: Federation vs. Zeon DX" arcade games. It's designed for a dual-perspective setup (Federation and Zeon), automating everything from stream creation to score tracking.

Watch our live stream: 荔枝角 GundamDX on YouTube

Disclaimer: This tool is built for a specific arcade streaming setup. Using it for other games or on different systems will require technical adjustments.

Key Features

  • Automated Dual Streaming: Automatically creates and manages two separate YouTube live streams (one for each faction) with a single click.
  • AI-Powered Scorekeeping: Uses screen recognition (OCR) to automatically detect the winner of each match, keeping track of scores, win rates, and streaks.
  • Unified Chat & TTS: Combines the live chat from both streams into one view. Viewers can use chat commands like !say to have their messages read out loud by a Text-to-Speech voice.
  • Simple Control Panel: A touch-friendly web interface to start/stop streams, view logs, and control features like TTS.

Getting Started

Prerequisites

  • Node.js
  • openclaw/wacli v0.12.0 or newer (install with brew install openclaw/tap/wacli on macOS/Linux, or put the Windows release binary on PATH)
  • FFmpeg (for video processing)
  • OBS Studio with the WebSocket and Aitum Vertical plugin enabled.
  • A Google Cloud Project with the YouTube Data API enabled to allow the tool to manage your streams.

Setup

  1. Download the code:
    git clone https://github.com/vkedwardli/GundamDXStreamSuite.git
    cd GundamDXStreamSuite
  2. Install dependencies:
    npm install
  3. Configure:
    • Create a .env file by copying from .env.example.
    • Fill in your details, such as Google API credentials and your OBS WebSocket password.
  4. Authorize and test WhatsApp:
    • Set WA_TEST_GROUP_ID, then run npm run test:whatsapp.
    • Scan the QR code from WhatsApp > Linked Devices when prompted. The test uses an isolated .wacli/session-diagnostic-test store and sends only to the testing group.
    • Outbound WhatsApp calls are allowlisted to WA_PUBLIC_GROUP_ID and WA_TEST_GROUP_ID; direct/new recipients and other groups are rejected.
  5. Authorize with YouTube:
    • Run the app for the first time: node index.js.
    • Follow the on-screen link to log in with your Google account and allow access. A token.json file will be saved for future use.

YouTube chat diagnostics and reauthorization

WhatsApp commands enter the bridge in index.js, are saved and dispatched by whatsappYouTubeBridge.js, and reach youtube.liveChatMessages.insert through youtubeService.js and youtubeChatDelivery.js. Sending uses OAuth, not an API key. CLIENT_ID and CLIENT_SECRET in .env identify the Google OAuth client; token.json holds the authorized account's access and refresh tokens.

On the streaming PC, from the application directory, run:

npm run diagnose:youtube -- PdYfjzdHR_g

Use the current target video ID. This checks token refresh, the authorized YouTube channel, and the video's active chat without posting or changing the saved credentials. Successful lookups do not prove permission to post. invalid_grant during refresh calls for reauthorization. Ordinary access-token expiry is handled automatically by the Google client while the refresh token remains valid. External OAuth apps in Google's Testing publishing status receive refresh tokens that expire after seven days for the YouTube scope.

To renew access, stop the Node app, move token.json to a private backup location outside the repository, and start the app again. Open the printed authorization link on the streaming PC, sign in to the intended YouTube channel, and grant access. The callback at http://localhost:3000/oauth2callback saves a new token.json; the authorization URL requests offline access and fresh consent. If replacing the OAuth client itself, update CLIENT_ID and CLIENT_SECRET in .env first and register that exact callback URL in Google Cloud Console. An API key cannot authorize chat posting.

Delivery diagnostics are always enabled. Failures log the operation (lookup or insert), HTTP status, API status, reason, and a single-line API message limited to 200 characters. Notification updates do not repeat the diagnostic summary. The outbox journal retains the full API error details, including chat ID and message length for insert failures. Request credentials are excluded. ERROR_UNSPECIFIED alone does not identify token expiry, moderation, or another cause; inspect these details before deciding how to recover.

An ERROR_UNSPECIFIED rejection sends a quoted Cantonese reply to the original WhatsApp command explaining that delivery failed for an unknown reason and the user can try later. An uncertain submission instead says delivery could not be confirmed, without encouraging a duplicate post. Retryable failures wait until safe retries are exhausted before notifying. Replies use the existing journal to prevent duplicates across restarts and redelivery, and expire after ten minutes. Previously rejected commands remain rejected after reauthorization.

References: YouTube authorization, OAuth token expiry, OAuth reauthorization.

Running the Application

node index.js

Once running, open the control panel in your browser to manage the stream.

On the touch panel (control.html), double-tap the Gundam icon to show the Wi-Fi QR code. Scan it to join the hidden LCKDX network, or connect manually using WPA/WPA2 and password clsclscls. The dialog hides automatically after 10 seconds. Close it sooner with 關閉, tap outside it, or press Escape. Keyboard users can focus the icon and press Enter or Space to open it.

The hotspot credentials are intentionally public. The QR image is bundled at images/lckdx-wifi.svg, so displaying it needs no external QR service. Its Wi-Fi payload is WIFI:T:WPA;S:LCKDX;P:clsclscls;H:true;; (H:true marks the SSID as hidden). If the network changes, replace the SVG with a QR code for the new credentials and update the network name shown in control.html.

Scores and winning streaks are saved to score.txt beside index.js after each change, together with the save date/time (UTC and local), draws, and total matches. Keep this runtime file when deploying; it is ignored by Git.

WhatsApp login and stream-link announcements do not gate stream startup. New stream links are sent in the background after OCR starts; if WhatsApp is not ready, the announcements are skipped. Hourly announcements are scheduled before optional desktop/speaker setup finishes.

On a Node startup from 06:00 inclusive to before 12:00 noon Hong Kong time, the app sends one morning weather brief to WA_PUBLIC_GROUP_ID after WhatsApp becomes ready, regardless of stream visibility or whether a stream has started. It uses the supplied HKO formatter: temperature versus yesterday, current Observatory temperature, today's forecast when available, active warnings, and high visibility at Central (falling back to Sai Wan Ho). Unavailable optional readings are omitted. HKO's nine-day feed can start at tomorrow after its late-morning update. The formatter therefore also fetches the local forecast (flw) for today's latest description, and saves any matching same-day nine-day entry under weather-announcements/forecasts/YYYYMMDD.json. Noon can reuse that entry for the temperature range and significant-rain probability, labelled 早前預報. If no same-day entry was saved (for example, the first startup is after the feed has rolled over), those fields say 暫未有資料; tomorrow's forecast is never shown as today's. The local description is used only for a fresh, same-day bulletin covering today, including 下午及今晚 after the noon update. Immediately below the current temperature, 體感溫度約 …°C shows the NWS heat index, calculated from fresh Observatory temperature and relative humidity and rounded to one decimal place. It uses the simple estimate averaged with temperature below 80°F, and the Rothfusz regression plus humidity adjustments otherwise. This estimates shaded heat conditions, not wind chill or direct sunlight; the line is omitted when either input is unavailable, stale, or invalid. Overnight restarts (00:00–05:59), including the previous operation day's 1am session, skip the morning edition and do not queue an announcement for 6am.

The app also schedules a 12:00 noon Hong Kong time announcement every day while Node is running, headed 香港中午天氣. This is independent of morning startup and stream visibility. If WhatsApp or HKO is unavailable, the noon edition can recover until 13:00; a restart during that hour catches up if it hasn't already been attempted. Later starts wait for the next day's noon run.

Weather work runs in the background. Logs prefixed WhatsApp morning weather YYYYMMDD: or WhatsApp noon weather YYYYMMDD: report waiting, fetching, sending, sent (with message ID), skipped, or failure. HKO fetch failures retry every five minutes within the edition's window. The morning edition expires at noon, and the noon edition expires at 13:00. Shutdown cancels the daily job and pending weather work.

Daily send records live in weather-announcements/ within the configured wacli session directory and survive Node restarts. Keep this folder during deployment. Morning uses YYYYMMDD.json; noon uses YYYYMMDD-noon.json, allowing both editions on the same day. A send attempt is recorded before dispatch; failed or uncertain sends are logged and that edition is not automatically repeated that day, avoiding duplicate public messages after a crash or lost acknowledgement. A returned message ID confirms WhatsApp accepted the send, not that every group member received or read it. This announcement checks outbound WhatsApp delivery; it does not verify the incoming YouTube bridge.

While WhatsApp sync is running, the adapter checks local authentication every 15 seconds using a read-only command. If wacli reports authenticated: false, the adapter stops its own sync process to release the store and starts QR login without restarting Node or OBS. Failed QR attempts retry. A failed status read is logged and does not itself trigger reauthentication. Detection depends on wacli recording the remote logout in its local session store; an old device row that still reports authenticated cannot prove the phone still lists the device.

Use Ctrl+C for a deployment restart: one shutdown handler stops OCR and waits for the app's WhatsApp child processes and outbox cleanup, leaving OBS running. Force-closing the console can bypass this cleanup. Do not delete the WhatsApp session database or lock files to work around a running process.

On Windows, start.bat disables QuickEdit for its console using disableQuickEdit.ps1, without changing registry settings. This reduces the risk of pausing the app while selecting console text. If all features freeze, press Escape in that console first. If it resumes immediately, console selection was involved. This is a diagnostic check, not proof that all hangs have that cause. Direct node index.js launches do not apply the console setting.

Relevant references: wacli sync, read-only auth status, Windows console modes.

For a deployment restart, leave OBS streaming, close and restart Node, then click Live for the current stream visibility. When OBS is already streaming, the app restores score.txt before OCR resumes. Starting Node or opening the control page does not overwrite the saved file. The restore happens once per Node process; starting a fresh OBS stream does not load old scores. A missing or invalid file is logged and the current scores are used instead. The streak inactivity window starts again on resume; the last real result timestamp is retained to prevent a quick restart from bypassing the existing duplicate-result cooldown. Matches played while Node is stopped cannot be counted. On the first deployment of this feature, use the manual options below if there is no saved file yet.

To manually override the saved scores, pass the previous scores and current winning streak. For example, Federation 25, Zeon 77, with Zeon on six consecutive wins:

node index.js --fed-score 25 --zeon-score 77 --zeon-streak 6

On Windows, the launcher accepts the same options:

start.bat --fed-score 25 --zeon-score 77 --zeon-streak 6

The four options are --fed-score, --zeon-score, --fed-streak, and --zeon-streak. Omitted values default to zero. Values must be non-negative whole numbers; only one faction can have a positive streak, and its streak cannot exceed its score. Both --fed-score 25 and --fed-score=25 work.

These options restore the scoreboard and the state used by score announcements. The six-minute streak inactivity window starts again when Node starts. Historical draws are not restored by these manual options; the initial match count is the sum of both scores. Explicit launch options take priority over score.txt. With no options, a fresh Node process starts at zero unless it resumes an ongoing OBS stream. The next detected result continues from the selected values.

For Developers

This project is highly customizable but contains hardcoded values specific to the original setup (e.g., screen coordinates for OCR in score.js, Windows paths in systemUtils.js). If you plan to adapt this project, you will need to modify these values to fit your environment.

License

This project is licensed under the GNU General Public License v3.0 (GPL-3.0).