# Bringing a Discord community into Orb

A community that is leaving Discord should not have to leave its history behind. The owner exports each channel
with [DiscordChatExporter](https://github.com/Tyrrrz/DiscordChatExporter) as JSON and drops the files into Orb. The
history becomes read-only channels, with the right author, the right time and replies that still point at what they
answered. From there the [Open Ledger](orb-ledger.md) can save it on Monad, so the history outlives both platforms.

Only the owner can import, and only into their own community.

To try it without anybody's real server, press **Use a sample (3 invented channels)** next to the file chooser: the three invented
channels (28 messages) that ship in `public/samples/discord/` go through exactly the same path as files, and need the same
confirmation first. `npm run import:sample` writes the same files anywhere you like, in the format DiscordChatExporter writes.

## How to do it

1. Export each channel you want with DiscordChatExporter, format **JSON**, one file per channel.
2. Open the community's profile settings in Orb. Under the portability panel, find **Bring your Discord history**.
3. Tick the box that says you run the community and its members know their history is being brought here.
4. Choose the files. Orb reads each one in your browser, sends only the messages it can keep, a few hundred at a
   time, and shows the progress per file.
5. To keep the history on Monad, turn on the Open Ledger below and choose the new channels.

Each exported channel becomes a read-only channel with the same name. When a channel with that name already exists (nearly
every community has a `general`), `-imported` is added, so the archive is never mistaken for the live channel.

Sending a file again is safe. Each Discord message id is remembered, so a second upload adds only what is new,
which also means a longer export of the same channel (made later, to catch up) can be sent on top of the first.

## What comes across

| Kept | Left behind |
| --- | --- |
| The text of each message, with Discord's markup read as plain words: `<@123>` becomes `@Name`, a custom emoji becomes `:name:`, a channel link becomes `#channel`, a timestamp becomes a date | Avatars, roles, colours, reactions, embeds and pins |
| Who said it: the server nickname, or the account name when there is none. Bots are marked `(bot)` | Account ids, discriminators and any contact details of the people who spoke |
| When it was said, to the second | Join, leave, call and pin events |
| Which message it answered, when that message is in the same export | Voice, video and stage content |
| Attachments and stickers by name (`[file] plan.pdf`), never the bytes | File and image contents |

Messages with nothing to keep (an embed with no text, a system event) and repeated ids are counted and shown as
"left out", so the number in Orb is never silently smaller than the file's.

## The people who spoke

Each person who appears gets a profile in Orb so that their messages carry their name. Nobody can sign in to these
profiles: the sign-in token is random and never shown, as for an application's profile. They are not members of the
community and do not appear in its roster. A person who spoke in two channels is one profile, keyed by their Discord
id inside that community. If a real member later wants to be linked to their old name, that is a manual step the
owner does, not something Orb guesses.

## Before you use a real server

Orb never talks to Discord and never asks for a Discord token: the exporter is a separate program that you run yourself, and Orb only
reads the file it writes. Two things stay with you.

- **The people in the history did not all agree to a new home.** Tell the members first (the confirmation box is the reminder), leave out
  channels that were private or sensitive, and remember that a **public** Open Ledger makes the text permanent and readable by anyone.
- **Discord's terms and the exporter's own documentation** decide how you may export, including which kind of token to use. Check them
  before running it on a server that is not a throwaway.

## Limits

| Limit | Value |
| --- | --- |
| One exported channel | 64 MB and 100,000 messages. A longer one can be exported in parts, with a date range |
| One community | 200,000 imported messages and 30 imported channels, within the 40 channels a community can have |
| One person | 200,000 imported messages across all their communities (`ORB_IMPORT_MAX_PER_OWNER`) |
| The whole Orb | 300,000 imported messages (`ORB_IMPORT_MAX_TOTAL`), because anyone can own a community; imports also stop when the disk is below `ORB_MIN_FREE_BYTES` |
| One request | 250 messages |
| One message | 4,000 characters (longer text is cut on a character edge) |

These keep an import from becoming a way to fill the database. Saving the history on Monad is paced separately by
the Open Ledger's daily allowance, and the Ledger panel shows how many messages are waiting and how long that takes.

## Saving it on Monad: what to know first

- **A public ledger is forever.** Everyone in the history is named on chain. Import only a community you run, and
  tell its members. The Open Ledger removes common secret formats from every message and, in public mode, e-mail
  addresses and phone numbers too, so something pasted years ago on Discord does not reach the chain. That is a
  safety net with known limits, not a promise.
- **The chain proves what was saved, not what was said.** A ledger shows that this text was written to Monad at a
  block, signed off by the community's owner. It cannot show that the text was ever said on Discord: the file is
  the owner's word. Times are as the file states them.
- **Private is available too.** A private ledger seals every entry to a key only the owner holds.

## Taking it out again

**Remove imported history** deletes the imported channels, their messages and the profiles made for the people who
spoke, unless one of those profiles has said something somewhere else. If the Open Ledger had already saved those
channels, the keeper writes a removal marker for each message, so a reader shows them as removed. The earlier
entries stay on chain: a tombstone does not erase what was written, as the [Open Ledger](orb-ledger.md) says about
every deletion.

## What this does not do

- It reads DiscordChatExporter's JSON only. Other platforms and Discord's own data package are not read.
- It does not sync: it is a move, not a bridge. New messages on Discord are not mirrored.
- It does not verify the export. A fabricated file imports as easily as a real one, in the owner's name.
- It does not carry threads' structure, edit history, or anything from voice.

## Tests

`npm run import:test` parses a realistic export (system events, a bot, custom emoji, replies, a repeated id, a message
at a character boundary), imports it in pieces over a real database, checks that only the owner can, that nothing is
written twice and replies hold across requests, that the caps hold, writes the community to a local EVM with the real
keeper, reads it back with a reader that has only an RPC, and removes it again. A browser test drives the importer itself:
owner-only, consent first, a file that is not an export refused before anything is sent, 600 messages sent oldest first
in pieces of 250, and removal behind a second confirmation.
