Skip to content
JawshTheDarkPublic

About

Android client for Lurker, an IRC client with a webui and bouncer.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

Spooky for Lurker

A native Android client for Lurker — Kotlin + Jetpack Compose, built to feel like a modern messaging app rather than a terminal. It talks to a Lurker bouncer server over its WebSocket + REST contract, and it also ships a standalone direct-IRC mode so it works even without a Lurker server at all.

Status: release-candidate. What began as a spike to prove Lurker's bearer-token contract could drive a native client has grown into a daily driver: signed-in persistence, resilient reconnect, formatted chat, member lists, uploads, search, DCC, notifications, and a full second backend that speaks raw IRC and bouncers. Actively used against production servers and the hosted app, and released often — see Releases for the APK and the running changelog.

Two ways to run it

Lurker mode — connect to a Lurker server, which stays connected to IRC for you (a true bouncer: the client never touches IRC; the server does parsing, TLS, SASL, reconnect, history, highlight matching, and ignore filtering). Works with:

  • Hosted lurker.chat — sign in with your email; the token is minted at the control plane and all traffic is proxied to your cell transparently.
  • Self-hosted Lurker — sign in with your username and password against your own server.

Direct mode — a standalone IRC client with no Lurker server required. Powered by KICL (Netty, IRCv3), it connects straight to IRC networks or to a bouncer (soju / ZNC). SASL, multi-network, cap negotiation, and auto-reconnect are all handled on-device; secrets are encrypted at rest. Connecting to a bouncer gets much of the always-on benefit back, since the bouncer stays connected upstream. First-class support for soju's bouncer-networks means one login can surface all your upstream networks as separate entries.

Pick the mode on first launch; it can be switched later from Settings.

Features

  • Stays signed in. The session is saved to app-private storage and reconnects silently on launch. Optional biometric / device-credential locks (see Security notes).
  • Resilient. Auto-reconnect with backoff; on return from background it resumes with ?since= so nothing is missed. Conversations closed on another client are reconciled away on reconnect, and an optional foreground service holds the connection open for reliable notifications.
  • Reads like a chat app. iMessage-style bubbles with sender grouping, mIRC color + formatting (bold / italic / underline / mono), tappable links, and inline system lines for joins, parts, quits, nick changes, kicks, modes, and topics. Or flip to compact mode — dense time · nick · message rows — globally or per channel. Layouts hold together at the system's largest font sizes.
  • Make it yours. Light / Dark / OLED themes, an accent colour (presets or a full HSV picker), chat typeface, message density, optional nick colouring, a 24-hour clock, and a chat background image — set globally or per channel.
  • Inline media. Images, GIFs, and video embed in the timeline with a built-in viewer.
  • Multi-channel. Opt any set of channels into one merged, pinned firehose — bubbles or compact, every line tagged network · channel · nick, and tapping one jumps straight to that message in its real channel.
  • Buffer list that scales. Grouped by network, DMs after channels, active buffers floated up, unread + highlight badges, pins, and mark-all-read. Unread queries get their own section at the top so a new DM can't hide down the list. Secure (E2E) buffers show a lock.
  • Mentions. Highlighted messages get a gold bubble (colour configurable), and tapping a notification opens the channel and jumps to the exact message. Notifications are actionable — reply, mark read, or mute — including from a paired Wear OS watch. Each channel or DM sits on the web's ladder — All messages, Highlights only, Nothing, or Muted (which counts highlights only). Caller ID (+g) offers to accept someone it turned away (/accept).
  • Members. Full member list with op/voice prefixes and per-nick actions (query, whois, op, kick, ban, notes…). Long-press any message to whois its sender; tap a #channel in any message to be offered the join. IRCv3 display names show in place of nicks, and a member's profile shows their avatar, pronouns, status and homepage (/displayname, /avatar, /metadata set your own).
  • Relay bots. Mark a bridge bot (Discord, Matrix, another network) and its [source] <nick> text lines read as the person who wrote them, source beside their name — bridges of bridges included. Custom formats and a line tester live in Relay bots (the ⋯ menu); /relay works too. On Lurker the marks are server-held, so they match the web client; in direct mode they stay on the phone.
  • Reply threads. With Lurker's Reply threads setting on, a channel's replies move into threads: a ╰─ 3 replies chip opens the thread with its own composer, and the title menu lists a channel's threads.
  • Composer. Formatting toolbar + colour picker, tab-completion for nicks and commands, typing indicators, swipe-to-reply, Quote (> <nick> text above your answer, and > lines drawn as quotes) and Copy quote, and image/file uploads (plus Android share-sheet targets). Oversized files are refused up front against the ceiling the server advertises, rather than after the upload; on a server with upload sessions, big files go up in resumable chunks.
  • Nothing sent is lost. A message that can't go out — offline, mid-reconnect, rejected, or unacknowledged — is kept in place with Resend / Edit / Discard instead of vanishing.
  • Slash-commands. /me, /msg, /query, /join, /part, /nick, /topic, /kick, /mode, /op, /ban, /whois, /away, /ctcp, /list, /raw, … (/help lists them). // sends a literal leading slash.
  • Channel browser. /list opens a searchable, sortable channel directory that streams results live as the server's LIST completes.
  • Search & contacts. Server-side message search, highlights, and bookmarks. Friends carry multiple per-network identities and show presence on each — green online, yellow away, red offline, grey unknown.
  • DCC. Receive and send file transfers, plus DCC chat, with live progress, speed and time left. On Lurker 2.5 it follows the account's per-feature grants; kept files can be saved to the phone, sent on, or deleted, and show their checksum result; XDCC packs queue per bot on the server (/xdcc add|queue|rmp|rmbot); and Secure DCC (KVIrc's SCHAT/SSEND) runs chats and sends over TLS. Offers pop up with Accept / Decline / Later. The full /dcc command set works from the composer.
  • Settings. A registry-driven editor over the server's settings surface — every advertised setting is editable by type — alongside device-local app preferences. Includes an in-app diagnostics log (connection + error history) to paste into a bug report.

Flavors

One codebase, two identities (Gradle product flavors):

  • full — chat.irc.lurker.full, "Spooky (GitHub)" — the sideload build attached to GitHub releases. (It was net.amiantos.lurker / "Lurker" until 0.11.1; that package is now the official Lurker app, so it moved.)
  • spooky — chat.irc.lurker, "Spooky for Lurker" — the public Play Store build.

Fork-only surfaces (custom aliases, DCC send/chat, fserve) are gated at runtime on the connected server's capabilities, so both flavors carry all the code and simply light those up on a capable server — there's no stripped branch to maintain.

Building

# Debug (full flavor)
./gradlew assembleFullDebug
adb install -r app/build/outputs/apk/full/debug/app-full-debug.apk

# Release APK (sideload) / Play bundle
./gradlew assembleFullRelease
./gradlew bundleSpookyRelease

Release signing for the Play build reads a gitignored keystore.properties; absent it (fresh clone), release falls back to debug signing so the build still succeeds.

To sign in, point it at your server: the hosted app is lurker.chat, a self-hosted server is its address (use the API port, 8010 by default, not the web dev port). On Lurker 2.3+ Sign in with browser is the default: the app registers itself with the server as an OAuth client (PKCE, no secret), the server's own page authenticates you — password or passkey — and the app only ever holds the resulting token. The redirect comes back on the application id as a custom scheme (chat.irc.lurker:/oauth), so the two flavors don't intercept each other. Older servers get the username/password form (hosted: email + control-plane mint). From the emulator the host machine is 10.0.2.2, not localhost. Cleartext HTTP is permitted for local dev servers.

Security notes

  • Sessions live in app-private storage, unreadable by other apps on a non-rooted device.
  • Direct-mode IRC/SASL secrets are encrypted at rest via EncryptedSharedPreferences — direct mode stores real IRC account passwords, so these are kept out of cleartext.
  • Locks. Optional biometric / device-credential gates on opening the app and on opening an encrypted (E2E) channel. Changing either one requires an unlock in both directions, so a lock can't be switched off by a stray tap; the confirmation fails closed.
  • Encrypted channels set FLAG_SECURE while on screen — no screenshots, no screen recording, and no content in the recents thumbnail.

Architecture

The UI talks to one observable backend. The base LurkerClient drives the WebSocket + REST transport to a Lurker server; DirectIrcBackend subclasses it and overrides the transport with a KICL-backed raw-IRC / bouncer engine, reusing the same Compose state and rendering. Pure, unit-tested logic — mIRC parsing, command parsing, completion — is split into Mirc.kt, Commands.kt, and friends; the screens live in MainActivity.kt.

License

MPL-2.0, same as Lurker.

About

Android client for Lurker, an IRC client with a webui and bouncer.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages