Add MineGeo gameplay systems and interface
This commit is contained in:
@@ -40,7 +40,7 @@ The follow-up migration `20261008075322_profile_identity.sql` adds a permanent
|
||||
**Bug finder** badge when a glow is granted. Revoking the glow preserves the badge.
|
||||
Guest reports retain confirmed-bug recognition when linked after a glow revoke.
|
||||
Players with an active glow can choose any hex color in **Settings → Username glow**.
|
||||
Moderators display a shield badge from their account role and can find an account
|
||||
Moderators display a shield badge, and Admins display a crown. Admins can find an account
|
||||
in **Moderation → Player prefixes** to assign, change, or remove a custom text/icon
|
||||
prefix. Account IDs disambiguate players with identical names. Prefixes do not
|
||||
change permissions. These decorations appear in account views, leaderboards,
|
||||
|
||||
135
docs/discord-integration.md
Normal file
135
docs/discord-integration.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Discord announcements
|
||||
|
||||
MineGeo uses your Discord bot to send messages through Discord's REST API.
|
||||
It runs on Vercel and does not need a continuously running bot process, Gateway,
|
||||
Message Content intent, slash commands, an interactions URL, or a Discord webhook.
|
||||
The bot can appear offline while announcements work.
|
||||
|
||||
New maps are announced when a Moderator or Admin approves their first public
|
||||
version. Drafts, imported ZIP copies, player contributions, subsequent map updates,
|
||||
and re-publication do not create new-map announcements. Existing maps from before
|
||||
this integration are not backfilled.
|
||||
|
||||
In an open multiplayer lobby, the host can select **Publish party to Discord**.
|
||||
All published parties go to the single server-wide `DISCORD_PARTIES_CHANNEL_ID`.
|
||||
Players cannot choose a destination. Discord readers can open the invite and join
|
||||
without a MineGeo account while the lobby is open and has space. Messages show
|
||||
the map, host, difficulty, player count, and link. Publishing is optional: ordinary
|
||||
private invite links still work. Parties on private or imported maps cannot be
|
||||
announced publicly. A room gets one announcement, including repeated clicks or
|
||||
a transfer of lobby ownership. The counts are a snapshot when posted.
|
||||
|
||||
## Create and invite your bot
|
||||
|
||||
1. Open the [Discord Developer Portal](https://discord.com/developers/applications)
|
||||
and select **New Application**. Name it **MineGeo**, or your preferred bot name.
|
||||
2. Open **Bot**. Create the bot if prompted, then use **Reset Token** to obtain
|
||||
its bot token. Keep it private; save it directly into Vercel as described below.
|
||||
3. Under **Installation**, enable **Guild Install**. For the guild's install
|
||||
settings, choose the **bot** scope and these permissions: **View Channels**,
|
||||
**Send Messages**, **Embed Links**. You do not need Administrator permission.
|
||||
Use the installation link to add the bot to your Discord server. Alternatively,
|
||||
**OAuth2 → URL Generator** with the `bot` scope generates an invite link.
|
||||
4. Create or choose a text channel for map announcements and another for parties.
|
||||
You can use the same channel for both. Ensure the bot has the three permissions
|
||||
above in each channel, including any category or channel permission overrides.
|
||||
Ensure community members can view the party channel.
|
||||
5. In Discord's **User Settings → Advanced**, enable **Developer Mode**. Right-click
|
||||
each channel and choose **Copy Channel ID**. On mobile, long-press the channel
|
||||
or open its channel menu, then choose **Copy Channel ID**. Copy the numeric
|
||||
channel IDs; an invite link or server ID is not a channel ID.
|
||||
|
||||
Discord setup references: [application setup](https://docs.discord.com/developers/quick-start/getting-started),
|
||||
[bot permissions](https://docs.discord.com/developers/topics/permissions).
|
||||
|
||||
## Add the Vercel environment variables
|
||||
|
||||
Open [MineGeo's environment settings](https://vercel.com/pavel-melnikovs-projects-74f979c2/minegeo/settings/environment-variables).
|
||||
Add these for **Production**:
|
||||
|
||||
| Name | Value | Sensitive? |
|
||||
| --- | --- | --- |
|
||||
| `DISCORD_BOT_TOKEN` | Bot token from the Developer Portal, without `Bot ` in front | Yes |
|
||||
| `DISCORD_MAPS_CHANNEL_ID` | Numeric channel ID for new approved maps | No |
|
||||
| `DISCORD_PARTIES_CHANNEL_ID` | Numeric channel ID where **all published parties** appear | No |
|
||||
| `MINEGEO_URL` | `https://minegeo.vercel.app` (already configured) | No |
|
||||
|
||||
Use **Sensitive** for the token, and never prefix these names with `NEXT_PUBLIC_`.
|
||||
Leave a channel variable blank to disable that announcement type. A Discord
|
||||
invite such as `MINEGEO_DISCORD_URL` controls the footer button only; it cannot
|
||||
send announcements.
|
||||
|
||||
After saving, open **Deployments**, choose the newest Production deployment,
|
||||
select **Redeploy**, and use the current environment variables. Vercel applies
|
||||
environment changes to new deployments. See [Vercel's environment-variable guide](https://vercel.com/docs/environment-variables).
|
||||
|
||||
For local use, put the same variables in ignored `.env.local` (ordinary `.env`
|
||||
also works) and restart the server. Use a publicly reachable HTTPS `MINEGEO_URL`
|
||||
for links that Discord members can open. Do not paste bot tokens into chat,
|
||||
source control, Linear issues, or public map metadata.
|
||||
|
||||
## Check it
|
||||
|
||||
Sign in as **PerfectPython500**, open **Moderation → Discord integration**, and
|
||||
select **Check Discord setup**. This reads the bot and channels without sending
|
||||
a test message. It confirms the bot token, channel visibility, and channel type;
|
||||
the bot must also have **Send Messages** and **Embed Links**.
|
||||
|
||||
Approve a genuinely new map to test a map announcement. Create a multiplayer room
|
||||
on a published map, choose **Publish party to Discord**, and open the resulting
|
||||
link from Discord. The lobby shows queued, sent, expired, or failed delivery.
|
||||
|
||||
## Saved delivery and retries
|
||||
|
||||
Announcements are stored in a private Supabase queue. Delivery runs after map
|
||||
approval or party publication, leaving gameplay and approval independent of
|
||||
Discord availability. Active workers hold leases, repeated clicks share one event,
|
||||
and Discord receives a stable nonce with `enforce_nonce`. Rate limits and temporary
|
||||
errors retry with a delay, up to eight attempts. Invalid tokens and missing channel
|
||||
permissions require fixing the settings and selecting **Retry saved announcements**
|
||||
as an Admin. Delivery resumes while the host polls their lobby or an Admin opens
|
||||
the integration panel; other announcements also wake pending jobs.
|
||||
|
||||
Started, closed, full, removed, private, or stale parties are skipped. Pending
|
||||
party announcements expire one hour after sharing; lobbies themselves last up to
|
||||
24 hours. A map removed before delivery is skipped. Messages already posted are
|
||||
kept when the match starts; the linked lobby shows its live state. When a party
|
||||
closes (including its last player leaving), its posted announcement turns red,
|
||||
says **Party closed**, and removes the join prompt. Five minutes after Discord
|
||||
acknowledges that notice, the bot deletes the message. Vercel Workflow stores the
|
||||
timer and resumes deletion even if every player closes the page. Retries preserve
|
||||
the original deadline and tolerate messages already removed by a moderator.
|
||||
Previously posted announcements for closed or deleted rooms receive the same
|
||||
cleanup when the queue next runs. The original message's channel is used even
|
||||
if `DISCORD_PARTIES_CHANNEL_ID` has since changed. Cleanup failures appear in the
|
||||
Admin integration panel and can be retried after fixing bot access.
|
||||
Discord only deduplicates nonces for a few minutes. A long outage after an
|
||||
unconfirmed send can therefore result in a duplicate; inspect the channel before
|
||||
manually retrying uncertain deliveries.
|
||||
|
||||
For optional scheduled retries, configure a private `CRON_SECRET` and call
|
||||
`GET /api/integrations/discord/dispatch` with `Authorization: Bearer <CRON_SECRET>`.
|
||||
This endpoint claims at most five due announcements. A Vercel Cron entry can use
|
||||
that path; scheduling limits depend on your plan. No schedule is installed by
|
||||
default, and the secret must never be placed in a URL.
|
||||
|
||||
## Database and verification
|
||||
|
||||
Apply `20261009133431_discord_announcements.sql` and
|
||||
`20261009153708_discord_party_cleanup.sql` after existing migrations.
|
||||
The queue and functions are service-role-only, use RLS and invoker permissions,
|
||||
and verify the host from the room's server-verified access hash. No Discord
|
||||
credentials are stored in Supabase or sent to a browser.
|
||||
|
||||
Vercel Workflow uses the project's managed backend automatically. Keep **Settings
|
||||
→ Environment Variables → Enable access to System Environment Variables** enabled
|
||||
and redeploy after changing it. No additional secret or scheduled cron is needed
|
||||
for the five-minute timer. The bot edits and deletes its own announcements, so
|
||||
**Manage Messages** is not required.
|
||||
|
||||
Run `npm test`, `npm run typecheck -- --incremental false`, `npm run build`, and
|
||||
`node scripts/test-discord-sql.mjs`. `node scripts/test-discord-http.mjs` verifies
|
||||
the live role gates, destination restrictions, optional configuration, and
|
||||
host-only room action with temporary accounts and a room, which it removes.
|
||||
Discord transport tests use a mocked API; real delivery requires your bot token
|
||||
and channel permissions.
|
||||
@@ -1,11 +1,13 @@
|
||||
# Moderator access
|
||||
# Admin and Moderator access
|
||||
|
||||
MineGeo checks `public.profiles.role` on the server for every moderation request. A contributor cannot change their own role through the website or Supabase's public API. No Discord role, JWT metadata, or additional plugin is required.
|
||||
|
||||
1. Have the moderator sign in to MineGeo and open their dashboard once.
|
||||
2. In the **MineGeo** Supabase project, open **Authentication → Users** and copy that person's user UUID.
|
||||
3. Open **Table Editor → public.profiles**, find the matching `id`, and set `role` to `moderator`. Keep the existing ID and display name.
|
||||
4. The moderator can open `/moderation`, inspect each location in 360°, approve a submitted map, return it with feedback, unpublish a map, and resolve community reports. The role takes effect on the next request.
|
||||
3. Open **Table Editor → public.profiles**, find the matching `id`, and set `role` to `moderator` or `admin`. Keep the existing ID and display name.
|
||||
4. Moderators can open `/moderation`, inspect pending maps and updates in 360°, approve them, or return them with feedback. Admins also manage the beta badge, player prefixes, community reports, ban appeals, and unpublishing. The role takes effect on the next request.
|
||||
|
||||
Apply `20261009123642_admin_permissions.sql` before deploying this role split. The account **PerfectPython500** has Admin permission. The username shows an Admin crown; Moderators retain their shield. Neither role can be assigned by editing a player profile through the website or a public Supabase client. Admin assignment remains a project-owner operation in Supabase.
|
||||
|
||||
Alternatively, the project owner can run this in Supabase's SQL Editor, replacing the email with the intended account:
|
||||
|
||||
@@ -26,9 +28,9 @@ Treat the website role separately from Supabase organization membership: moderat
|
||||
|
||||
Signed-in players can use **Report content** on a published map page to report the entire map, or during a game to report the current panorama. Reports include a category, a 10–2000 character explanation, and an optional HTTP/HTTPS evidence link. The website derives panorama identity from the player's accessible game round; it retains the reported immutable revision for moderator inspection. Owners cannot report their own maps. Each person can have only one open or confirmed report per map until its ban is appealed successfully; dismissed reports can be corrected and resubmitted.
|
||||
|
||||
In `/moderation`, inspect the reported surface map and 360° locations, then **Confirm report · add strike** or **Dismiss report**, with written feedback. Pending and dismissed reports never count toward bans. Each distinct reporter can contribute one confirmed strike. Confirming the third report atomically unpublishes the map and blocks publication. The ban applies to the map, not the owner's account. Existing games retain their original revisions; the map disappears from the public catalog and cannot start new games.
|
||||
In `/moderation`, Admins inspect the reported surface map and 360° locations, then **Confirm report · add strike** or **Dismiss report**, with written feedback. Pending and dismissed reports never count toward bans. Each distinct reporter can contribute one confirmed strike. Confirming the third report atomically unpublishes the map and blocks publication. The ban applies to the map, not the owner's account. Existing games retain their original revisions; the map disappears from the public catalog and cannot start new games.
|
||||
|
||||
The owner opens the banned map from `/dashboard` and uses **Appeal map ban**. One appeal can await review at a time. Moderators can inspect the map and approve or reject the appeal with feedback. Rejection keeps the ban and permits another appeal with new information. Approval restores the last published revision, resets the three-strike counter, closes pending reports from the old cycle, and preserves all report and appeal history. A moderator cannot confirm their own report, confirm reports on their own map, or approve their own map's appeal.
|
||||
The owner opens the banned map from `/dashboard` and uses **Appeal map ban**. One appeal can await review at a time. Admins can inspect the map and approve or reject the appeal with feedback. Rejection keeps the ban and permits another appeal with new information. Approval restores the last published revision, resets the three-strike counter, closes pending reports from the old cycle, and preserves all report and appeal history. An Admin cannot confirm their own report, confirm reports on their own map, or approve their own map's appeal.
|
||||
|
||||
Contribution uploads older than five days are hidden from the dashboard and the owner's **Player submissions** list. Nothing is deleted: direct contribution links, accepted panoramas, and quota accounting still work. The cutoff uses the contribution's creation time, including pending submissions.
|
||||
|
||||
@@ -37,8 +39,13 @@ Apply `supabase/migrations/20261006074343_content_reports_and_appeals.sql` after
|
||||
```sh
|
||||
npm install --prefix .data/sql-verification --no-save --package-lock=false @electric-sql/pglite@0.3.14
|
||||
node scripts/test-moderation-sql.mjs
|
||||
node scripts/test-admin-permissions-sql.mjs
|
||||
```
|
||||
|
||||
After deployment, `node scripts/test-admin-permissions-production.mjs` verifies the
|
||||
live API using disposable player, Moderator, and Admin accounts. It removes its
|
||||
own maps and accounts afterward. It does not require a browser.
|
||||
|
||||
The reporting migration was applied to the configured **MineGeo** Supabase project on October 6, 2026 after `/api/v1/moderation` returned `PGRST205` for the missing `map_appeals` table. The live Data API now successfully reads moderation maps, open reports, and the appeal queue. Appeal RLS is enabled, and public roles cannot read appeals or execute moderation decisions. The earlier `map_refresh_fixes` migration remains unapplied; applying it was rejected by automatic approval review as outside this reporting fix.
|
||||
|
||||
# First beta map
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# Deploy MineGeo
|
||||
|
||||
Optional Discord map and multiplayer-party announcements use `DISCORD_BOT_TOKEN`,
|
||||
`DISCORD_MAPS_CHANNEL_ID`, and `DISCORD_PARTIES_CHANNEL_ID`. All published parties
|
||||
use the same configured channel. Follow [Discord bot setup](discord-integration.md)
|
||||
for creation, permissions, Vercel variables, and retries. Apply the Discord queue
|
||||
migration before deploying this feature; the bot token stays server-only.
|
||||
|
||||
Optional community buttons appear in the footer when `MINEGEO_DISCORD_URL` or
|
||||
`MINEGEO_TELEGRAM_URL` is configured. Set these public links in `.env.local` for
|
||||
local development (ordinary `.env` also works). Restart the local server after
|
||||
changing a link. For the hosted site, add the same names as Production Config
|
||||
variables in the MineGeo Vercel project, then redeploy. These values are public
|
||||
destinations; no Discord bot token or Telegram API key is needed.
|
||||
|
||||
Discord accepts HTTPS links on `discord.gg`, `discord.com`, `www.discord.com`,
|
||||
and `discordapp.com`. Telegram accepts `t.me` and `telegram.me`, including group
|
||||
invite links. Blank, malformed, or unsupported URLs hide the corresponding
|
||||
button. Each button opens its configured destination in a new tab.
|
||||
|
||||
Run `npm run deploy` from the project directory. The script collects all web
|
||||
source files and public assets, uploads them in one request, waits for the build,
|
||||
and prints the deployment URL. It uses the [Vercel deployments API](https://vercel.com/docs/rest-api/deployments/create-a-new-deployment). The database credentials already configured in
|
||||
|
||||
Reference in New Issue
Block a user