derakkuma#
unofficial maimai DX NET client. built with compose multiplatform (android + ios).
all fetching and parsing is client-side, your SEGA cookies never leave your device.
building#
# android
./gradlew composeApp:assembleDebug
# ios (requires macOS + xcode)
./gradlew composeApp:compileKotlinIosArm64
tech stack#
- compose multiplatform: shared UI for Android + iOS
- code vendored from heron: atproto functionality
- ktor: HTTP client (okhttp on Android, darwin on iOS)
- sql_delight: local caching with per-section TTL + persistent play history
- coil: image loading
how it works#
derakkuma is a pure client-side scraper: it fetches HTML pages from maimaidx-eng.com using the user's session cookies, then parses them with custom Kotlin HTML parsers. there is no backend server.
scraping flow#
- auth: user logs in via SEGA's aime gateway. derakkuma stores the resulting session cookies (
_t,userId) plus aclalcookie for automatic session refresh - fetch: ktor HTTP client (with mobile chrome UA) requests pages like
/home/,/playLog/,/rating/etc. redirects are disabled so session expiry can be detected - parse: raw HTML is handed to parser functions in
Parsers.ktwhich extract structured data using regex + string operations - cache: parsed results are stored in sql_delight with per-section TTL (profile: 1hr, gameplay data: 1min). stale cache is served during maintenance mode
idx tokens#
SEGA's detail pages use opaque idx tokens to identify individual records:
- play log detail: each
RecentPlayentry has anidxlike5,1778896406. hitting/record/playlogDetail/?idx=...returns the full note breakdown for that play - song score detail: each song has a longer hash
idxlikef371feb917228aa2.... hitting/record/musicDetail/?idx=...returns scores across all difficulties
these idx values are extracted from the HTML during the initial list parse and stored on the data objects. tapping a record in the UI triggers a second fetch to the detail endpoint using that idx.
parser coverage#
| Page | Parser | Cache TTL |
|---|---|---|
| HOME | parseHomePage |
1 hr |
| PLAY DATA | parsePlayerDataPage |
1 min |
| PLAYER DATA (full) | parseFullPlayerDataPage |
1 min |
| GAME RECORD | parsePlayLogPage |
1 min |
| GAME RECORD (detail) | parsePlayLogDetailPage |
1 min |
| SONG SCORE (detail) | parseMusicDetailPage |
1 min |
| RATING TARGET MUSIC | parseRatingPage |
1 min |
| TOTAL RANKING | parseRankingsPage |
1 min |
| PLAYER NAME | parsePlayerNamePage |
uncached |
| GAME SETTINGS | parseUserOptionPage |
uncached |
| FAVORITES | parseFavoriteListPage / parseFavoriteUpdatePage |
uncached |
| MUSIC SORT / MYBEST | parseMusicSearchPage |
persistent (play_history table via PlayStore) |
| FRIEND CODE | parseFriendCodePage |
persistent (settings) |
| FRIENDS | parseFriendsPage / parseFriendDetailPage / parseFriendVsPage |
uncached |
| FRIEND SEARCH / REQUESTS / MATCHING | parseFriendSearchPage / parseFriendRequestsPage |
uncached |
| CIRCLE | parseCircleHomePage / parseCircleRewardsPage / parseCircleRankingPage / parseCircleMembersPage |
uncached |
| CIRCLE SEARCH / INVITES / FESTA | parseCircleSearchPage / parseCircleInvitesPage / parseCircleFestaPage |
uncached |
| SONG RANKING (index + search) | parseSongRankingIndex / parseSongRankingPage |
1 min |
| COURSE RANKING (index + search) | parseCourseRankingIndex / parseCourseRankingPage |
1 min |
| SEASON RANKING | parseSeasonRankingPage |
1 min |
| DX RATING RANKING | parseDeluxeRatingPage |
1 min |
| TOTAL ACHIEVEMENT RANKING | parseTotalAchievementPage |
1 min |
| PARTNER RANKING | parsePartnerRankingPage |
1 min |
| COLLECTION | parseCollectionPage |
uncached |
| STAMP CARD | parseStampCardPage |
uncached |
| PHOTO ALBUM | parsePhotoAlbumPage |
uncached |
| TOUR MEMBER FORMATION | parseTourMemberFormationPage / parseTourMemberSelectionPage / parseTourMemberChangePreview / parseTourMemberArrangementPage |
uncached |
| ARCADE LOCATIONS | parseArcadeLocations |
3 hr |
ATProto publishing#
ATProto support is opt-in and publishes custom com.derakkuma.* records to the user's PDS. User-owned records are intentionally compact: plays, best scores, and favorites strong-reference a canonical song/chart catalog instead of embedding repeated chart metadata.
Canonical catalog records are published by did:plc:4uoc2as443j2fg2f6xfsogqs from https://arcade-songs.zetaraku.dev/maimai/:
| Record | RKey strategy | Notes |
|---|---|---|
com.derakkuma.song |
deterministic song<sha256(songId)[0:16]> |
canonical song metadata; includes cover art blob plus stable upstream imageName |
com.derakkuma.chart |
deterministic chart<sha256(songId|version|type|difficulty)[0:16]> |
canonical chart/sheet metadata; strong-references its song |
User records:
| Record | RKey strategy | Notes |
|---|---|---|
com.derakkuma.profile |
literal self |
player profile; friend code is optional and separately confirmed |
com.derakkuma.play |
PDS-generated key via createRecord |
append-only play events; strong-references com.derakkuma.chart; includes a dedupeKey for client/AppView dedupe |
com.derakkuma.best |
deterministic opaque best<hash(chartUri)> |
one mutable best per canonical chart; strong-references com.derakkuma.chart |
com.derakkuma.friend |
deterministic opaque friend<sha256(displayName+friendIdx)> |
friend/detail idx is only used as private hash salt; never published raw |
com.derakkuma.favoriteSong |
deterministic opaque fav<hash(song identity)> |
current favorite list; strong-references com.derakkuma.song; unfavorited songs are deleted remotely |
com.derakkuma.circle |
deterministic opaque circle<hash(circleCode/name)> |
current circle snapshot; includes your circle rank/points; circle code is optional and separately confirmed |
com.derakkuma.circleMember |
self for your member record, deterministic opaque member key for others |
member records link back to their parent com.derakkuma.circle; self member also links to at://<did>/com.derakkuma.profile/self |
The catalog graph is:
com.derakkuma.song --charts[] URI array--> com.derakkuma.chart
com.derakkuma.chart --song strongRef-------> com.derakkuma.song
com.derakkuma.play --chart strongRef------> com.derakkuma.chart
com.derakkuma.best --chart strongRef------> com.derakkuma.chart
com.derakkuma.favoriteSong --song strongRef-> com.derakkuma.song
To avoid circular CID churn, chart.song is the canonical strong reference, while song.charts is just a convenience array of chart AT URIs. Consumers should trust chart.song if the two directions ever disagree.
Before publishing play or best, the app resolves DX NET scraped data to a catalog chart by normalized song title/songId, chart type (dx, std, or utage), difficulty, and level as a sanity check. If a chart cannot be resolved, the app skips that record and records an ATProto sync warning so the published graph stays clean.
HappyView joins user records through the catalog for social queries like friends' plays, the public record feed, and chart leaderboards.
feature parity progress#
- Maintenance Mode (countdown, cache fallback, UI disabling)
- Home (profile, play count, maimile)
- Parse
- UI
- Player Data (play stats, full player data)
- Parse
- UI
- ATProto
- Game Record (play log)
- Parse
- UI
- ATProto
- Historical Play Backfill (crawl song list for plays older than 90 days)
- Crawler
- Parser
- UI
- ATProto
- Game Record Detail (note breakdown, fast/late, rating delta)
- Parse
- UI
- ATProto
- Song Score Detail (per-difficulty scores)
- Parse
- UI
- Rating Target Music (
/home/ratingTargetMusic/, DX rating calculation songs)- Parse
- UI
- Total Ranking (national leaderboard)
- Parse
- UI
- Friend Code
- Parse
- UI
- ATProto
- Game Settings (note speed, timing, sound, display)
- Parse
- Save POST
- UI
- Player Name (display name change)
- Parse
- Save POST
- UI
- ATProto (profile sync)
- Favorites (manage favorite songs)
- Parse
- Save POST
- UI
- ATProto
- Friend Skip (skip friend registration)
- Parse
- Save POST
- UI
- Collection (icons, name plates, frames, titles, tour members, partners)
- Parse
- UI
- Tour Member Formation (preset, slot change, reorder, lock)
- Parse
- UI
- Record of maimai (legacy maimai FiNALE leaderboard/archive)
- Parse
- UI
- Stamp Card
- Parse
- UI
- ATProto
- Album
- Parse
- UI
- Friends
- Parse
- UI
- Favorite/unfavorite, rival register/deregister, friend detail, Friend VS
- Remove/drop friend
- ATProto
- Friend VS
- Parse
- UI
- Search Friends
- Parse
- UI
- Send friend request
- Match (friend matching)
- Parse
- UI
- Friend Request Sent
- Parse
- UI
- Cancel friend request
- Accepting Friend Requests
- Parse
- UI
- Accept/block friend request
- Circle (clan info)
- Parse
- UI
- ATProto (circle snapshot)
- Circle Point Rewards
- Parse
- UI
- Circle Ranking
- Parse
- UI
- ATProto (not a separate record; your circle's rank/points live on
com.derakkuma.circle)
- Circle Members
- Parse
- UI
- ATProto (members backlink to their circle;
selfmember backlinks to profile)
- Search Circles
- Parse
- UI
- Accepting Circle Invites
- Parse
- UI
- CiRCLE FESTA
- Parse
- UI (top, ranking, past FESTA)
- ATProto (draft/todo; wait for next active FESTA before freezing lexicon)
- maimile SHOP
- Parse
- UI
- ATProto
- Song Score List (genre-filtered, needs diff param)
- Parse
- UI
- World Stats (national clear rate)
- Parse
- UI
- Area (map/area progress)
- Parse
- UI
- ATProto
- Event Area
- Parse
- UI
- End Event Area
- Parse
- UI
- Season Info
- Parse
- UI
- Song Ranking (genre/title/level search, score type, ranking type, difficulty filters)
- Parse
- UI
- Course Ranking (dan/expert/master courses, score type, ranking type)
- Parse
- UI
- Season Ranking (class ranking, season filter)
- Parse
- UI
- DX Rating Ranking (rating leaderboard, ranking type filter)
- Parse
- UI
- Total Achievement (achievement leaderboard, ranking type + difficulty filters)
- Parse
- UI
- Partner Ranking (character intimate values, partner filter)
- Parse
- UI
- Store Locator (location.am-all.net)
- Parse
- UI
skipped (cannot access)#
- 段位認定 (course mode)
- オトモダチ対戦 RANKING (friend battle ranking)
maintenance window#
SEGA takes maimai DX NET offline daily from 19:00–22:00 UTC. during this window:
- a countdown banner shows time until service resumes
- all data loads from cache (no network fetches)
- interactive UI elements are disabled to prevent stale actions
- rankings are cached ahead of downtime so they're still available
analytics (optional)#
analytics are fully anonymous and private: no personal data is collected. they're enabled by default but can be toggled off in more → app settings → analytics.
to configure the posthog API key, add it to local.properties (see example)
license#
source code in this repository is licensed under the Mozilla Public License 2.0. see LICENSE for the full text.
third-party code and assets keep their original licenses and notices. the MPL-2.0 license grant does not cover third-party trademarks, service marks, logos, fonts, screenshots, artwork, game/source-site imagery, or other assets unless a file explicitly says otherwise.