================================================================================ TIVIMATE 5.3.3 — DISCORD + WEATHER + SPORTS ADDON FULL INSTALL & SETUP GUIDE (public release) ================================================================================ This guide explains how to set up the whole addon from scratch: - The Discord bridge bot (Python) + where to get every setting - The panel changes (what we changed from the original panel) - How to expose the bot to the internet (cloudflared tunnel) - The APK edits (how Discord, Weather and Sports work) - Full install steps — where to put files and what to add All real domain names, server URLs, folder paths and credentials have been replaced with PLACEHOLDERS. Replace every YOUR_... / PASTE_... value with your own. ================================================================================ ⚡ EASY SETUP — THE 5-MINUTE VERSION ================================================================================ If you just want it working fast, do these 5 things. Each is explained in full detail further down. 1. SET UP THE BOT (Part 1) - Create a Discord bot at https://discord.com/developers/applications - Copy its token, client ID, client secret, and your room + server IDs - Paste them into discord-bot/config.json (replace the PASTE_... values) - On your server run: bash install-and-run.sh 2. GET A PUBLIC URL FOR THE BOT (Part 3) - On your server run: bash install-cloudflared.sh - Copy the https://random-words.trycloudflare.com URL it prints - Put that URL in THREE places: a) discord-bot/config.json -> "public_base" b) panel config.ini -> [discord] bot_url c) Discord portal -> OAuth2 -> Redirects -> add https://.../callback 3. UPLOAD THE PANEL (Part 2 + 5.1) - Upload the whole panel folder to your web host - Edit config.ini: set your panel name + the bot_url from step 2 - Log in (admin/admin) and CHANGE THE PASSWORD 4. CHANGE THE PANEL URL IN THE APK (Part 4.5 — the ONLY APK edit) - Decompile the APK, open AppConfig.smali, and change the panel URL -1. `smali_classes21/com/PanelURL.smali` → https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/ ✅ -2. `smali_classes22/ca/krankhaus/tivi/config/AppConfig.smali` → https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/ ✅ -3. `smali_classes22/ca/krankhaus/tivi/panel/PanelAutoImport.smali` → https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/ ✅ (see Part 4.5 for the exact file + line) - Rebuild with the dex-swap method, zipalign, sign - Install the APK on the device 5. USE IT - Open the app -> Discord / Weather / Sports buttons appear - Discord: "Log in with Discord" -> Authorize -> chat room - Weather: clock + forecast appear automatically - Sports: categories | clock | listings THAT'S IT. The rest of this guide is the full detail for each step. ================================================================================ WHAT THIS ADDON DOES ================================================================================ - DISCORD: a button in the app opens a chat room. Users log in with their real Discord account (OAuth2 "Log in with Discord"). Only members of YOUR Discord server can get in (Discord checks membership automatically). - WEATHER: a weather page with a live clock, 3-day forecast, auto-detects the viewer's location. No API key needed. - SPORTS: a sports guide page with categories | clock | listings, D-pad navigation and auto-scroll. Two modes are included (see below). There are 4 parts: 1. BOT — Python bot (discord.py + aiohttp + faster-whisper) 2. TUNNEL — cloudflared, gives the bot a public HTTPS URL 3. PANEL — PHP panel (the web host) with the new pages 4. APK — the TiviMate app with the Discord/Weather/Sports buttons ================================================================================ PART 1 — THE DISCORD BOT (where to get every setting) ================================================================================ The bot runs on a server (a Raspberry Pi, a VPS, or any always-on Linux box). It connects to Discord, watches ONE room, and exposes an HTTP API the TV panel calls. FILES (in the discord-bot/ folder): DiscordBot.py <- the bot itself config.json <- ALL your settings go here requirements.txt <- Python dependencies install-and-run.sh <- one-shot install + run script install-cloudflared.sh <- starts the public tunnel discord-bot.service <- optional systemd auto-start (Linux) ------------------------------------------------------------------ 1.1 CREATE THE BOT (Discord Developer Portal) ------------------------------------------------------------------ Go to: https://discord.com/developers/applications Click "New Application", name it (e.g. "TiviMate Chat"). a) BOT TOKEN -> config.json "bot_token" Left menu -> Bot -> Reset Token -> Copy. This is the bot's login token. KEEP IT SECRET. b) PRIVILEGED INTENT Left menu -> Bot -> scroll to "Privileged Gateway Intents". Enable MESSAGE CONTENT INTENT (the bot needs it to read chat). c) OAUTH CLIENT ID -> config.json "client_id" Left menu -> OAuth2 -> General -> Copy "Client ID". d) OAUTH CLIENT SECRET -> config.json "client_secret" Left menu -> OAuth2 -> General -> "Reset Secret" -> Copy. KEEP IT SECRET. e) REDIRECT URI Left menu -> OAuth2 -> General -> "Redirects" -> Add: https://YOUR-PUBLIC-BOT-URL/callback (YOUR-PUBLIC-BOT-URL is the cloudflared URL from Part 3.) f) INVITE THE BOT TO YOUR SERVER Left menu -> OAuth2 -> URL Generator. Scopes: bot + applications.commands Bot permissions: Send Messages, Read Message History, View Channels Open the generated URL -> pick your server -> Authorize. ------------------------------------------------------------------ 1.2 GET YOUR ROOM + SERVER IDs ------------------------------------------------------------------ In Discord: Settings -> Advanced -> Developer Mode ON. - Right-click your ROOM (channel) -> Copy Channel ID -> config.json "room_id" - Right-click your SERVER name -> Copy Server ID -> config.json "guild_id" ------------------------------------------------------------------ 1.3 FILL IN config.json ------------------------------------------------------------------ { "bot_token": "PASTE_YOUR_BOT_TOKEN_HERE", "client_id": "PASTE_YOUR_OAUTH_CLIENT_ID_HERE", "client_secret": "PASTE_YOUR_OAUTH_CLIENT_SECRET_HERE", "room_id": "PASTE_YOUR_DISCORD_CHANNEL_ID_HERE", "guild_id": "PASTE_YOUR_DISCORD_SERVER_ID_HERE", "public_base": "https://YOUR-PUBLIC-BOT-URL.trycloudflare.com", "panel_base": "https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER", "http_port": 8090 } - public_base = the cloudflared URL (Part 3). The OAuth2 redirect must be reachable by Discord, so this MUST be a public HTTPS URL. - panel_base = the public URL of your panel folder (Part 2). The OAuth2 redirect points back to the panel's discord.php page. ------------------------------------------------------------------ 1.4 INSTALL + RUN THE BOT ------------------------------------------------------------------ On the server, in the discord-bot/ folder: bash install-and-run.sh This creates a Python venv, installs discord.py + aiohttp + faster-whisper, and starts the bot in the background on port 8090. It prints a health-check URL: http://127.0.0.1:8090/health To stop it later: kill $(cat bot.pid) To restart it: bash install-and-run.sh OPTIONAL — auto-start on boot (systemd): Edit discord-bot.service and set: User=YOUR_LINUX_USERNAME WorkingDirectory=/path/to/your/discord-bot ExecStart=/path/to/your/discord-bot/venv/bin/python DiscordBot.py Then: sudo cp discord-bot.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now discord-bot sudo systemctl status discord-bot ------------------------------------------------------------------ 1.5 VOICE INPUT (optional) ------------------------------------------------------------------ The bot has a /transcribe endpoint. The APK records audio from the device mic and POSTs it here; faster-whisper (runs locally on the server, no cloud, no API key) transcribes it and the text is auto-posted to the room. First voice use downloads the ~75MB "tiny" model (needs internet). ================================================================================ PART 2 — THE PANEL (what we changed from the original) ================================================================================ The panel is a PHP + SQLite admin panel for the TiviMate app. We took the original panel and ADDED the Discord, Weather and Sports pages, plus a few small changes. Here is exactly what changed. ------------------------------------------------------------------ 2.1 NEW FILES WE ADDED (at the panel root) ------------------------------------------------------------------ discord.php <- the Discord chat page (the app's Discord button opens it) weather.php <- the Weather page (live clock + 3-day forecast) sport.php <- the Sports page (categories | clock | listings) sports.php <- Sports admin page (pick which sports, colours, timezone) sports-fixtures.php <- Sports admin page (ESPN fixtures toggle) ------------------------------------------------------------------ 2.2 NEW FILES WE ADDED (in the api/ folder) ------------------------------------------------------------------ api/sports.php <- config endpoint the app calls for the Sports menu api/sports-fixtures.php <- ESPN fixtures proxy (UK-first league list) api/webview.php <- generic in-app WebView config endpoint api/dns.php <- lightweight plain-JSON portal list for the app's native bridge ------------------------------------------------------------------ 2.3 NEW FILES WE ADDED (in the includes/ folder) ------------------------------------------------------------------ includes/table.php <- MERGED. Added new DB tables/columns (see 2.5) ------------------------------------------------------------------ 2.4 WHAT WE EDITED IN THE ORIGINAL PANEL ------------------------------------------------------------------ a) main.php — SIMPLIFIED THE "ADD PLAYLIST" FORM. REMOVED: the Type dropdown (Xtream/Stalker/M3U), the Username, Password and MAC Address fields, and the output field. KEPT: only Name + Server URL. WHY: users log in with their OWN Xtream Codes credentials when they open a playlist. The panel just stores the base URL. b) api.php — handleGetDevices() SIMPLIFIED. REMOVED: the per-type URL building (embedding username/password into the URL for xtream, appending / for stalker, etc.). KEPT: passes the base server URL as-is, hardcodes type => 'xtream'. WHY: matches the simplified form — the app prompts for credentials. c) .htaccess — STRIPPED TO MINIMAL (CSP ONLY). REMOVED: the HTTPS force-redirect, the clean-URL rewrites, and the security blocks (deny .db / config.ini / includes/). KEPT: only the CSP header (needed for the sports Live Preview iframe) + X-Frame-Options SAMEORIGIN. WHY: the full .htaccess caused a 500 on the whole panel on an HTTP-only host. KEEP IT MINIMAL — do NOT re-add rewrites or the HTTPS redirect. d) config.ini — ADDED a [discord] section. ADDED: bot_url = "https://YOUR-PUBLIC-BOT-URL.trycloudflare.com" This is the public URL of the bot (Part 3). The discord.php page reads it so the TV's browser knows where to talk to the bot. ------------------------------------------------------------------ 2.5 DATABASE CHANGES (includes/table.php) ------------------------------------------------------------------ The original panel already had: user, dns, note, welcome, devices. We ADDED these tables (auto-created on first run): vpn_profiles - VPN server profiles sports_config - Sports menu config (enabled/title/url) webview_config - generic WebView config sports_fixtures_config- sports widget settings (token, sports, colours, timezone, time format) sports_fixtures_cache - cache for the ESPN proxy leagues - sports leagues sport_events - sports fixtures/events request_log - API request logging (analytics) ip_geo_cache - per-IP geolocation cache webauthn_credentials - optional passkey 2FA If your panel's table.php already has some of these, just make sure the columns exist. The sports_fixtures_config table needs these columns: widget_token, widget_sports, widget_bg, widget_bgs, widget_timezone, widget_time12 ------------------------------------------------------------------ 2.6 HOW MUCH CODE WE ADDED ------------------------------------------------------------------ The addon adds roughly 50% more code to the panel. Of the panel's PHP code, about half is the new Discord / Weather / Sports features, and about half is the original panel (with the small edits in 2.4). ------------------------------------------------------------------ 2.7 SECURITY — HOW THE CODE IS HARDENED ------------------------------------------------------------------ The panel is secured against common attacks. These protections are built into the original KRANKHAUS panel and kept in place: LOGIN & PASSWORDS - Passwords are stored as bcrypt hashes (configurable cost, default 12), never in plaintext. - Login is locked for 15 minutes after 5 failed attempts (brute-force protection). - Session timeout (default 15 minutes) — idle sessions expire. SESSIONS & CSRF - Session-based login with a single admin account. - CSRF tokens on every form (generated with random_bytes, verified with hash_equals) — blocks cross-site request forgery. INPUT HANDLING - All user input is sanitized. - Database queries use parameterized statements (SQLite wrapper) — blocks SQL injection. THE APP / API - The bot token and OAuth client secret live ONLY on the server (config.json) — never in the APK or the panel. - The TV only holds a short-lived session token issued after a successful Discord login. - Access to the Discord room is gated by Discord's own membership check — non-members get a 403 and can't get in. - The panel proxies external APIs (weather, sports) server-side, so the app never talks to third parties directly. ================================================================================ PART 3 — EXPOSING THE BOT (cloudflared tunnel) ================================================================================ The bot runs on your server behind your router, so it has no public URL. Discord's OAuth2 redirect and the TV's browser both need to reach it. The easiest way is a free cloudflared "quick tunnel" — no account needed. On the server, in the discord-bot/ folder: bash install-cloudflared.sh This installs cloudflared (if missing), checks the bot is up on port 8090, and starts a tunnel. It prints a public HTTPS URL like: https://random-words.trycloudflare.com THAT URL is your YOUR-PUBLIC-BOT-URL. You must set it in THREE places: 1. bot config.json -> "public_base" 2. panel config.ini -> [discord] bot_url 3. Discord Developer Portal -> OAuth2 Redirects -> https://.../callback IMPORTANT: the free trycloudflare.com URL is RANDOM and CHANGES every time the tunnel restarts. After each restart you must update all three places above. (For a permanent URL, use a named cloudflared tunnel or a reverse proxy like nginx/Caddy on a VPS — same idea, stable address.) The tunnel runs in the foreground. Leave it running (or run it under systemd/tmux so it survives logout). ================================================================================ PART 4 — THE APK (how Discord, Weather and Sports work) ================================================================================ The APK is the TiviMate app with three extra menu buttons. It has a NATIVE INTEGRITY CHECK (libcockpitdotlol.so -> isTampered) that crashes the app if protected dex files change. So we use the "dex-swap" method: ONLY classes22.dex is modified; classes.dex and classes2-21 stay byte-identical. ------------------------------------------------------------------ 4.1 THE MENU BUTTONS ------------------------------------------------------------------ The app fetches its menu config from the panel at launch. The panel's api.php / api/sports.php / api/webview.php return the URLs for the Discord, Weather and Sports pages. If the panel is down, no buttons show. ------------------------------------------------------------------ 4.2 HOW DISCORD WORKS ------------------------------------------------------------------ - The app's Discord button opens discord.php in a WebView. - discord.php talks to the bot's HTTP API, ALL from the browser (JS): GET /login -> OAuth2 "Log in with Discord" URL GET /callback -> OAuth2 exchange (Discord redirects back here) GET /me -> who is logged in GET /messages -> recent messages in the room POST /send -> post a message GET /logout -> revoke the session - The OAuth exchange is done CLIENT-SIDE in the browser, NOT server-side. Reason: the panel host can't reach the bot, but the TV's browser can (through the tunnel). The browser stores the session token in localStorage. - Only members of YOUR server get in. The bot checks Discord membership (scope "guilds.members.read") and returns 403 if the user isn't a member. - The bot token and OAuth secret live ONLY on the server (config.json) — never in the APK. The TV only holds a short-lived session token. VOICE: the on-screen 🎤 key in the keyboard records from the device's built-in mic, POSTs the audio to the bot's /transcribe endpoint, and the returned text is auto-posted. This bypasses the system recognizer (which on Fire TV routes to Alexa and talks back). ------------------------------------------------------------------ 4.3 HOW WEATHER WORKS ------------------------------------------------------------------ - The app's Weather menu is a WebView pointed at weather.php. - weather.php shows a live clock + 3-day forecast (open-meteo API). - It auto-detects the viewer's location: it tries GPS first (navigator.geolocation), and falls back to IP (ip-api.com) if GPS is denied or unavailable. - IMPORTANT: the WebView JS must NOT call external APIs directly (CORS is blocked). So weather.php PROXIES both calls server-side (same-origin): weather.php?action=loc -> returns the viewer's lat/lon weather.php?action=forecast -> returns the 3-day forecast - No API key required. Fully automatic. ------------------------------------------------------------------ 4.4 HOW SPORTS WORKS (two modes) ------------------------------------------------------------------ MODE A — sport-tv-guide widget (sport.php): The app's Sports menu loads sport.php, which fetches the sport-tv-guide.live widget server-side and renders a clean layout: [ category icons row ] [ clock ] [ sports listings ] - data-src -> src rewrite so icons show in the app WebView. - LEFT/RIGHT moves across category icons both ways (wraps). - DOWN/UP left to NATIVE WebView focus (listings scroll + auto-scroll). - Category selection uses location.replace() so the remote BACK goes straight to the main menu in one press. - Auto-scroll only runs in the app (window.self !== window.top disables it in the panel Live Preview iframe). - sports.php (admin) lets you pick which sports, colours, timezone and time format, with a Live Preview. SPORTS CATEGORIES AVAILABLE (pick any in the panel's Sports admin page): Football, Ice Hockey, Basketball, Handball, Motorsport, Tennis, Volleyball, Golf, Winter Sports, Combat Sports, Snooker, Cycling, Rugby Union, Extreme Sports, Athletics, Cricket, Baseball, Horse Racing, Darts, Am. Football, Rodeo, Bowling, Sailing / Boating, Water Sports, Aussie Rules, Rugby League, Table Tennis, Squash, Field Hockey, Gymnastics, Netball, Equestrian, GAA, Badminton, Lacrosse, Boxing, MMA, WWE, Futsal, Fencing, Beach Soccer, Kabaddi, Shooting, Floorball, Triathlon, Weightlifting, Esport, Alpine Ski, Ski Jumping, Biathlon MODE B — ESPN fixtures (webview.php?fetch=sport): The app's Sports menu can instead load webview.php?fetch=sport, an IBO-style 3-pane guide (Sports | Leagues | Events) that auto-imports today's fixtures from ESPN's mobile endpoint (UK first). Fully automatic — re-imports if stale (>6h) or empty. SPORTS COVERED (ESPN, UK first): Football (English Premier League, Championship, League One/Two, FA Cup, EFL Cup, Scottish Premiership, League of Ireland, German Bundesliga, Italian Serie A/B, Spanish La Liga, French Ligue 1, Dutch Eredivisie, Portuguese Primeira Liga, Belgian Pro League, Turkish Super Lig, Greek Super League, Danish Superliga, Austrian Bundesliga, Swiss Super League, Norwegian Eliteserien, Swedish Allsvenskan, Russian Premier League, UEFA Champions/Europa/Conference/Nations, Copa Libertadores, Copa Sudamericana, CONCACAF League, International + Club Friendlies, Argentine Primera, Brazilian Serie A, Mexican Liga MX, MLS, NWSL, Chinese Super League, Colombian Primera A, Uruguayan Primera, J1 League, A-League, Saudi Pro League) Motorsport (Formula 1, NASCAR Cup/Truck, IndyCar, NHRA) Tennis (ATP, WTA) Golf (PGA, LPGA, DP World Tour) Fighting (UFC, PFL, Bellator) Baseball (MLB) Basketball (NBA, WNBA) American Football (NFL, CFL, NCAA) Ice Hockey (NHL) The panel's api/sports.php returns the URL the app loads, so you choose which mode by editing that endpoint. ------------------------------------------------------------------ 4.5 THE ONLY APK EDIT — CHANGE THE PANEL URL (the "DNS") ------------------------------------------------------------------ The app finds your panel through ONE hardcoded URL baked into the APK. To point the app at YOUR panel, you change that one string. This is the only APK edit you need for a basic install. WHERE IT IS (FULL PATH): After you disassemble classes22.dex, the file is at: smali/ca/krankhaus/tivi/config/AppConfig.smali (i.e. the class ca.krankhaus.tivi.config.AppConfig inside classes22.dex) 1. `smali_classes21/com/PanelURL.smali` → https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/ ✅ 2. `smali_classes22/ca/krankhaus/tivi/config/AppConfig.smali` → https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/ ✅ 3. `smali_classes22/ca/krankhaus/tivi/panel/PanelAutoImport.smali` → https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/ ✅ Open that file and find the PANEL_BASE_URL field declaration near the top (around line 11): .field private static PANEL_BASE_URL:Ljava/lang/String; = null Then find the line that SETS it, inside the static initializer (around line 39): const-string v0, "https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/" sput-object v0, Lca/krankhaus/tivi/config/AppConfig;->PANEL_BASE_URL:Ljava/lang/String; CHANGE IT TO: const-string v0, "https://YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER/" ...where YOUR-PANEL-DOMAIN/YOUR-PANEL-FOLDER is the public URL of the panel folder you uploaded in step 3. KEEP the trailing slash. THIS IS THE ONLY URL YOU CHANGE IN THE APK. Everything else (the Discord page, Weather, Sports, the bot URL) is derived from this one base URL at runtime — the app appends "discord.php", "weather.php", "sport.php", etc. to it. So changing this one line repoints the whole app at your panel. HOW TO DECOMPILE + REBUILD (dex-swap method): The app has a NATIVE INTEGRITY CHECK (libcockpitdotlol.so -> isTampered) that crashes if protected dex files change. So you ONLY modify classes22.dex; classes.dex and classes2-21 must stay byte-identical. 1. Extract classes22.dex from the APK: unzip -o tivimate-5-3-3-discord-voice.apk classes22.dex 2. Disassemble it: baksmali d classes22.dex -o smali 3. Edit smali/ca/krankhaus/tivi/config/AppConfig.smali and change the panel URL (the const-string line above). 4. Reassemble: smali assemble smali -o classes22.dex 5. Copy the ORIGINAL APK and swap in the new dex: cp tivimate-5-3-3-discord-voice.apk base.apk zip -0 base.apk classes22.dex 6. Align: zipalign -f 4 base.apk aligned.apk 7. Sign: apksigner sign --ks YOUR_KEYSTORE.jks --out final.apk aligned.apk 8. VERIFY classes.dex and classes2-21 are byte-identical to the original (only classes22.dex should differ). If any protected dex changed, the app will crash on launch. IMPORTANT — the cloudflare bot URL is NOT in the APK: The bot's public URL (the cloudflared link) is NOT baked into the APK. It is passed at runtime from the PANEL's config.ini ([discord] bot_url) to the page via the JS bridge (window.TiviBridge.setBotUrl). So: - The APK only needs the PANEL URL (this section). - The cloudflare link goes in the PANEL config.ini, NOT the APK. If you change the cloudflare URL later, you only edit the panel config — you do NOT need to rebuild the APK. ================================================================================ PART 5 — FULL INSTALL (where to put files and what to add) ================================================================================ ------------------------------------------------------------------ 5.1 THE PANEL (web host) ------------------------------------------------------------------ 1. Upload the whole panel folder to your host (e.g. into a subfolder like /YOUR-PANEL-FOLDER/ under your web root). 2. Make sure the api/ folder is writable by the web server (SQLite DB). 3. Verify PHP 8.1+ with the SQLite3, OpenSSL and sodium extensions. 4. Edit config.ini: - set your panel name / brand / contact - set force_https = true if you use SSL - set [discord] bot_url to your cloudflared URL (Part 3) 5. Default login is admin / admin — CHANGE IT IMMEDIATELY. 6. KEEP .htaccess MINIMAL (CSP only). Do NOT re-add rewrites or the HTTPS redirect — it 500s the whole panel on an HTTP-only host. ------------------------------------------------------------------ 5.2 THE BOT (server) ------------------------------------------------------------------ 1. Copy the discord-bot/ folder to your server. 2. Fill in config.json (Part 1.3). 3. Run: bash install-and-run.sh 4. Run: bash install-cloudflared.sh -> copy the URL 5. Set that URL in: bot config.json, panel config.ini, Discord portal. 6. (Optional) systemd auto-start (Part 1.4). ------------------------------------------------------------------ 5.3 THE APK (device) ------------------------------------------------------------------ The working APK to install is: tivimate-5-3-3-discord-voice.apk (this is the last full working build — Discord + Weather + Sports + voice) 1. Uninstall any old TiviMate build from the device. 2. Install tivimate-5-3-3-discord-voice.apk 3. Open the app. The Discord / Weather / Sports buttons appear in the main menu (fetched from the panel at launch). 4. Discord: open it -> "Log in with Discord" -> Authorize -> chat room. 5. Weather: open it -> clock + forecast appear automatically. 6. Sports: open it -> categories | clock | listings. NOTE: if you change the panel URL (Part 4.5), rebuild from THIS APK (tivimate-5-3-3-discord-voice.apk) as the base — it is the known-good working build. ------------------------------------------------------------------ 5.4 REBUILDING THE APK (dex-swap method) ------------------------------------------------------------------ If you need to rebuild the APK (e.g. to change the panel URL), use the dex-swap method so the integrity check passes: 1. Start from the WORKING base APK (the one with the buttons). 2. Extract its classes22.dex: baksmali d classes22.dex -o smali 3. Edit the smali (e.g. the panel URL in the AppConfig class, or the WebViewActivity dispatchKeyEvent for the Discord scroll/click). 4. Reassemble: smali assemble smali -o classes22.dex 5. Copy the base APK, replace classes22.dex: cp base.apk out.apk zip -0 out.apk classes22.dex 6. zipalign: zipalign -f 4 out.apk aligned.apk 7. Sign: apksigner sign --ks YOUR_KEYSTORE.jks --out final.apk aligned.apk 8. VERIFY classes.dex and classes2-21 are byte-identical to the base (only classes22.dex should differ). If any protected dex changed, the app will crash on launch. KEY SMALI GOTCHAS (learned the hard way): - In a method with .registers N and P params, the top P registers alias the params (p0=this, p1=first param...). Using a param register for a new object clobbers `this` -> CRASH on WebView open. Use only dead registers that are re-set before use. - The FireCube WebView has NO pageDown()/pageUp() methods. Always scroll via JavaScript (evaluateJavascript), never pageDown/pageUp. - Discord's oauth2 page is a MODAL DIALOG — window.scrollBy does nothing. Scroll the actual scrollable element under the center of the screen. - Gate the scroll/click on "discord.com", NOT "oauth2" (the real oauth2 URL doesn't contain "oauth2"). - Do NOT hijack the D-pad center (OK) — it's needed for login/navigation. - Do NOT add a lockdown that throws when the JS bridge is absent — it breaks the oauth2 login. ================================================================================ TROUBLESHOOTING ================================================================================ - "No Discord/Weather/Sports buttons": the app fetches the menu config from the panel at launch. If the panel is down, no buttons show. Also check the APK's classes22.dex didn't get a VerifyError. - "Whole panel 500": suspect .htaccess first (HTTPS redirect or a bad directive). Keep .htaccess minimal (CSP only). - "Discord bounces back to login": a lockdown that throws when the JS bridge is absent, or the OAuth redirect URL is wrong. Check the redirect URI in the Discord portal matches your cloudflared URL. - "Voice talks back / launches Alexa": on Fire TV the system recognizer routes to Alexa. Use the on-screen 🎤 key (records the device mic and sends to the bot's /transcribe), NOT the remote mic button. - "403 on buttons": file permissions on the host — set .php files to 644. - "Weather unavailable": the WebView can't call external APIs (CORS). Make sure weather.php is proxying both calls server-side. ================================================================================ SECURITY NOTES ================================================================================ - The bot token and OAuth client secret live ONLY in config.json on the server. NEVER put them in the APK or on the panel. - The TV only holds a short-lived session token issued after a successful Discord login. - Access to the room is gated by Discord's own membership check. - Change the panel's default admin password immediately. - If you share this folder, make sure config.json and discord-bot.service use placeholders, not your real values. ================================================================================ END OF GUIDE ================================================================================