<?xml version="1.0"?>
<Container version="2">
  <Name>Unified-Chat</Name>
  <!-- No Docker Hub account needed. This runs the official node:20 image and
       downloads the app from streamhelpers.com into the appdata folder on start. -->
  <Repository>node:20</Repository>
  <Registry>https://hub.docker.com/_/node</Registry>
  <Network>bridge</Network>
  <MyIP/>
  <Shell>bash</Shell>
  <Privileged>false</Privileged>
  <Support>https://streamhelpers.com/unified-chat/</Support>
  <Project>https://streamhelpers.com/unified-chat/</Project>
  <Overview>Merges live chat from YouTube, Twitch, Kick, Discord, Rumble, Trovo, DLive and Facebook into one dark web dashboard, streamed to the browser over SSE. Also includes a YouTube live-title editor. Every platform is optional - fill in only the credentials for the services you use and the rest are skipped; check the log for the "Platforms enabled" line. Adapters stay idle until you press Start in the dashboard, so the container does not burn YouTube API quota while you are offline. On each start the container downloads the latest app files from streamhelpers.com into your appdata folder and runs npm install, so the first boot takes a minute or two - watch the container log. IMPORTANT: set Auth Username and Auth Password, otherwise there is no login and anyone who can reach the port can read your chat and rename your live broadcast. X/Twitter is not supported. Provided as-is with no warranty; see https://streamhelpers.com/legal.html</Overview>
  <Category>MediaApp:Video Tools:</Category>
  <WebUI>http://[IP]:[PORT:8088]/</WebUI>
  <TemplateURL/>
  <Icon>https://streamhelpers.com/unified-chat/icon.png</Icon>
  <ExtraParams>-w /app</ExtraParams>
  <PostArgs>bash -c "cd /app &amp;&amp; curl -fsSL -O https://streamhelpers.com/unified-chat/unified-chat.js -O https://streamhelpers.com/unified-chat/package.json &amp;&amp; npm install --omit=dev &amp;&amp; node unified-chat.js"</PostArgs>
  <CPUset/>
  <DateInstalled/>
  <DonateText/>
  <DonateLink/>
  <Description>Unified live chat dashboard for multi-platform streamers. Another fine product of Blue Soup Enterprises.</Description>
  <Networking>
    <Mode>bridge</Mode>
    <Publish>
      <Port>
        <HostPort>8088</HostPort>
        <ContainerPort>8088</ContainerPort>
        <Protocol>tcp</Protocol>
      </Port>
    </Publish>
  </Networking>
  <Data>
    <Volume>
      <HostDir>/mnt/user/appdata/unified-chat</HostDir>
      <ContainerDir>/app</ContainerDir>
      <Mode>rw</Mode>
    </Volume>
  </Data>

  <Config Name="WebUI Port" Target="8088" Default="8088" Mode="tcp" Description="Port for the chat dashboard." Type="Port" Display="always" Required="true" Mask="false">8088</Config>
  <Config Name="App Data (app files + YouTube token)" Target="/app" Default="/mnt/user/appdata/unified-chat" Mode="rw" Description="Holds the downloaded app files and tokens/youtube.json, so your YouTube authorization survives restarts." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/unified-chat</Config>

  <!-- ============ Security ============ -->
  <Config Name="Auth Username" Target="AUTH_USER" Default="" Mode="" Description="HTTP Basic auth username. SET THIS. Leaving both auth fields blank means no login at all - anyone who can reach the port can read your chat and rename your live YouTube broadcast." Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="Auth Password" Target="AUTH_PASS" Default="" Mode="" Description="HTTP Basic auth password. Use something long and random. Basic auth is sent base64-encoded, so put the container behind a TLS reverse proxy if it is reachable from an untrusted network." Type="Variable" Display="always" Required="false" Mask="true"></Config>

  <!-- ============ YouTube ============ -->
  <Config Name="YouTube API Keys" Target="YT_API_KEYS" Default="" Mode="" Description="YouTube Data API v3 keys, comma-separated. Live chat polling burns 10,000 units/day per Google Cloud project, so create keys in SEPARATE projects and the app rotates when one hits quota. Required (with Channel ID) to enable YouTube." Type="Variable" Display="always" Required="false" Mask="true"></Config>
  <Config Name="YouTube Channel ID" Target="YT_CHANNEL_ID" Default="" Mode="" Description="The UC... string from your channel URL (24 chars), NOT your handle. Required (with API Keys) to enable YouTube." Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="YouTube Min Poll (ms)" Target="MIN_YT_POLL_MS" Default="10000" Mode="" Description="Minimum YouTube poll interval. Do not go below 10000 or you will exhaust your daily quota in a couple of hours." Type="Variable" Display="advanced" Required="false" Mask="false">10000</Config>
  <Config Name="YouTube Key Rotate Cooldown (ms)" Target="YT_KEY_ROTATE_COOLDOWN_MS" Default="900000" Mode="" Description="How long a quota-exhausted API key is benched before being retried." Type="Variable" Display="advanced" Required="false" Mask="false">900000</Config>
  <Config Name="YouTube OAuth Client ID" Target="YT_OAUTH_CLIENT_ID" Default="" Mode="" Description="Optional. Only needed for the /yt live-title editor. Chat relay works without it." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="YouTube OAuth Client Secret" Target="YT_OAUTH_CLIENT_SECRET" Default="" Mode="" Description="Optional. Pairs with the OAuth Client ID above." Type="Variable" Display="advanced" Required="false" Mask="true"></Config>
  <Config Name="YouTube OAuth Redirect" Target="YT_OAUTH_REDIRECT" Default="" Mode="" Description="Optional. Must match the redirect URI registered in Google Cloud EXACTLY (same host and port) and be reachable from your browser. Example: http://your-host:8088/auth/youtube/callback" Type="Variable" Display="advanced" Required="false" Mask="false"></Config>

  <!-- ============ Twitch ============ -->
  <Config Name="Twitch Nick" Target="TWITCH_NICK" Default="" Mode="" Description="Your Twitch login name (used for IRC auth)." Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="Twitch Channel" Target="TWITCH_CHANNEL" Default="" Mode="" Description="The channel whose chat you want to read." Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="Twitch Token URL" Target="TWITCH_TOKEN_URL" Default="" Mode="" Description="A URL you host that returns a fresh Twitch IRC oauth token. Preferred over a static token, because IRC tokens expire and this lets the app re-fetch on reconnect." Type="Variable" Display="always" Required="false" Mask="true"></Config>
  <Config Name="Twitch IRC OAuth" Target="TWITCH_IRC_OAUTH" Default="" Mode="" Description="Alternative to Token URL: a static 'oauth:xxxx' token. Simpler, but you must replace it by hand when it expires." Type="Variable" Display="advanced" Required="false" Mask="true"></Config>
  <Config Name="Twitch Client ID" Target="TWITCH_CLIENT_ID" Default="" Mode="" Description="Optional Helix app credentials. Used only as a polling fallback if the IRC settings above are not filled in." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="Twitch Client Secret" Target="TWITCH_CLIENT_SECRET" Default="" Mode="" Description="Pairs with the Twitch Client ID above." Type="Variable" Display="advanced" Required="false" Mask="true"></Config>

  <!-- ============ Kick ============ -->
  <Config Name="Kick Client ID" Target="KICK_CLIENT_ID" Default="" Mode="" Description="From https://kick.com/settings/developer. All three Kick fields are required to enable Kick." Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="Kick Client Secret" Target="KICK_CLIENT_SECRET" Default="" Mode="" Description="From https://kick.com/settings/developer." Type="Variable" Display="always" Required="false" Mask="true"></Config>
  <Config Name="Kick Channel Slug" Target="KICK_CHANNEL_SLUG" Default="" Mode="" Description="The slug from your channel URL: https://kick.com/&lt;slug&gt;" Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="Kick Enable Webhooks" Target="KICK_ENABLE_WEBHOOKS" Default="false" Mode="" Description="Set true only if the container is reachable from the public internet. Leave false for LAN-only setups." Type="Variable" Display="advanced" Required="false" Mask="false">false</Config>
  <Config Name="Kick Webhook URL" Target="KICK_WEBHOOK_EXTERNAL_URL" Default="" Mode="" Description="Public URL Kick's servers will POST to. Must end in /kick/webhook. This route is exempt from the login and verifies Kick's RSA signature instead." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>

  <!-- ============ Discord ============ -->
  <Config Name="Discord Bot Token" Target="DISCORD_BOT_TOKEN" Default="" Mode="" Description="From the Discord Developer Portal. You MUST also enable the MESSAGE CONTENT INTENT under Bot - Privileged Gateway Intents, or every message arrives empty." Type="Variable" Display="always" Required="false" Mask="true"></Config>
  <Config Name="Discord Channel IDs" Target="DISCORD_CHANNEL_IDS" Default="" Mode="" Description="One or more channel IDs, comma-separated. Enable Developer Mode in Discord, then right-click a channel and Copy Channel ID." Type="Variable" Display="always" Required="false" Mask="false"></Config>
  <Config Name="Discord Ignore Bots" Target="DISCORD_IGNORE_BOTS" Default="true" Mode="" Description="Skip messages posted by other bots." Type="Variable" Display="advanced" Required="false" Mask="false">true</Config>

  <!-- ============ Rumble ============ -->
  <Config Name="Rumble API URL" Target="RUMBLE_API_URL" Default="" Mode="" Description="Rumble Studio - Account - Live Stream API - Generate API Key/URL. Paste the full URL including the ?key= part." Type="Variable" Display="always" Required="false" Mask="true"></Config>
  <Config Name="Rumble Channel URL" Target="RUMBLE_CHANNEL_URL" Default="" Mode="" Description="Fallback if you have no Rumble API access: the public channel page URL." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>

  <!-- ============ Trovo ============ -->
  <Config Name="Trovo Client ID" Target="TROVO_CLIENT_ID" Default="" Mode="" Description="Apply at https://developer.trovo.live/. Required (with Channel ID or Access Token) to enable Trovo." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="Trovo Channel ID" Target="TROVO_CHANNEL_ID" Default="" Mode="" Description="Numeric channel ID (not your username). See .env.example for the curl command that looks it up." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="Trovo Access Token" Target="TROVO_ACCESS_TOKEN" Default="" Mode="" Description="Only needed to read your own channel's chat as an authenticated user." Type="Variable" Display="advanced" Required="false" Mask="true"></Config>

  <!-- ============ DLive ============ -->
  <Config Name="DLive Username" Target="DLIVE_USERNAME" Default="" Mode="" Description="The account username from your channel URL, not the display name. Required (with Access Token) to enable DLive." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="DLive Access Token" Target="DLIVE_ACCESS_TOKEN" Default="" Mode="" Description="App access token from https://dev.dlive.tv/." Type="Variable" Display="advanced" Required="false" Mask="true"></Config>

  <!-- ============ Facebook ============ -->
  <Config Name="Facebook Page ID" Target="FB_PAGE_ID" Default="" Mode="" Description="Numeric Page ID. Required (with Page Token) to enable Facebook." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="Facebook Page Token" Target="FB_PAGE_TOKEN" Default="" Mode="" Description="System User token with pages_read_engagement and pages_read_user_content. Does not expire unless revoked. Facebook polling is floored at 25s internally." Type="Variable" Display="advanced" Required="false" Mask="true"></Config>

  <!-- ============ General ============ -->
  <Config Name="Poll Interval (ms)" Target="POLL_MS" Default="2500" Mode="" Description="Default polling interval for platforms that do not push events. YouTube and Facebook apply their own higher floors regardless." Type="Variable" Display="advanced" Required="false" Mask="false">2500</Config>
  <Config Name="SSE Buffer Size" Target="SSE_BUFFER_MAX" Default="400" Mode="" Description="How many recent messages are replayed to a newly opened browser tab." Type="Variable" Display="advanced" Required="false" Mask="false">400</Config>
  <Config Name="Theme Park Destinations" Target="TP_ALLOWED_DESTINATIONS" Default="" Mode="" Description="Optional, niche. Restricts the destination dropdown in the YouTube title editor to these comma-separated names. Full list at https://api.themeparks.wiki/v1/destinations. Leave blank to allow all." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
  <Config Name="Theme Park Default" Target="TP_DEFAULT_DESTINATION" Default="" Mode="" Description="Optional, niche. Preselects a destination in the title editor on first load. Leave blank for none." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
</Container>
