minimost.chat

minimost.chat

Core messaging routes: sending, receiving, editing, deleting, reacting, and serving uploaded files.

This is the largest module in MiniMost and contains all business logic for the chat interface, including:

  • Channel management helpers — resolving participants, validating access for public channels, DMs, and private channels.

  • Message CRUD — sending, fetching (with polling support), editing, and soft-deleting messages across every recipient’s database simultaneously.

  • Reactions — toggle emoji reactions stored atomically in the shared presence.db.

  • Read receipts — mark messages as read and query who has read them.

  • File serving — authenticated delivery of image attachments.

  • Search — full-text LIKE search across a user’s message history.

  • Link previews — delegate to minimost.preview.

  • Private channels — create invite-only channels, manage membership, and rename channels. Private channel state (membership, channel metadata) is stored in the shared presence.db so every member sees the same view. Private channels are identified throughout the app as "private:<id>" where <id> is the auto-increment primary key from the private_channels table.

Module-level attributes

chat_bpflask.Blueprint

The Flask Blueprint that groups all chat-related routes. Registered in minimost.create_app().

ALLOWED_EXTENSIONSset

File extensions accepted for image uploads: {".jpg", ".jpeg", ".png", ".gif", ".webp"}.

UPLOAD_DIRpathlib.Path

Absolute path to the uploads/ directory where image attachments are stored. Created at import time if it does not exist.

CHANNELSlist of str

Public channel names loaded from channels.json at startup. Defaults to ["general"] if the file is absent or malformed.

VALID_REACTIONSset of str

Set of valid reaction emoji names, derived from the SVG filenames in static/reactions/. Only names in this set are accepted by the /react/<msg_id> endpoint.

minimost.chat._max_upload_size_bytes() int[source]

Return the configured max upload size in bytes (default 25 MiB).

minimost.chat._check_upload_sizes(files, max_bytes: int)[source]

Return an error tuple if any file exceeds max_bytes, else None.

minimost.chat._max_avatar_size_bytes() int[source]

Return the configured max avatar size in bytes (default 5 MiB).

minimost.chat._load_channels() List[str][source]

Load the list of public channels from settings.json.

Reads and parses settings.json from the project root and returns the value of the "channels" key. Falls back to a single "general" channel if the file is absent, malformed, or missing the key.

Returns:

Ordered list of public channel name strings.

Return type:

list of str

minimost.chat.extract_mentions(text: str, channel: str) List[str][source]

Return the channel members that are @-mentioned in text.

Parses every @name token out of text and keeps only those that resolve (case-insensitively) to an actual member of channel. The returned usernames use their canonical (stored) casing, in channel membership order. Returns [] when text is empty or contains no valid mentions.

The reserved keyword @everyone mentions the whole channel; when it is present the single-element list [MENTION_EVERYONE] is returned regardless of any other tokens, since it already covers every member.

Parameters:
  • text (str) – The raw message body.

  • channel (str) – The channel the message is being posted to.

Returns:

Canonical usernames of mentioned channel members, or [MENTION_EVERYONE] for a channel-wide mention.

Return type:

list of str

minimost.chat._mentions_json(text: str, channel: str)[source]

Return a JSON-encoded mention list for text, or None if empty.

minimost.chat._opt_float(value)[source]

Parse value as a float, returning None for empty/invalid input.

Used to coerce optional numeric query-string parameters (e.g. the search date bounds) without raising on absent or malformed values.

Parameters:

value – The raw query-string value, or None.

Returns:

The parsed float, or None if value is missing or invalid.

Return type:

float or None

minimost.chat._get_private_db()[source]

Return an open WAL connection to the shared presence.db.

Configures the connection with WAL journal mode and sqlite3.Row row factory so columns are accessible by name. The caller is responsible for closing the returned connection.

Returns:

An open SQLite connection to presence.db.

Return type:

sqlite3.Connection

minimost.chat.get_private_channel_members(channel_id: int) List[str][source]

Return the list of usernames that are members of a private channel.

Queries the private_channel_members table in presence.db for all rows matching channel_id and returns the corresponding usernames. Returns an empty list if the channel does not exist or has no members.

Parameters:

channel_id (int) – The integer primary key of the private channel.

Returns:

Usernames of all current members, in insertion order.

Return type:

list of str

minimost.chat.get_db(username: str)[source]

Open and return a SQLite connection to a user’s message database.

The connection is configured with:

  • WAL journal mode — allows concurrent reads during writes, which is important when multiple Gunicorn workers serve different users simultaneously.

  • ``row_factory = sqlite3.Row`` — rows can be accessed by column name (e.g. row["sender"]) in addition to integer index.

The caller is responsible for closing the returned connection.

Parameters:

username (str) – The account username whose database should be opened.

Returns:

An open SQLite database connection.

Return type:

sqlite3.Connection

Example:

db = get_db("alice")
rows = db.execute("SELECT * FROM messages WHERE channel = ?", ("general",)).fetchall()
db.close()
minimost.chat.all_users() List[str][source]

Return a list of every registered username.

Queries the shared auth.db database and returns every username in the users table. The order is undefined (SQLite row insertion order).

This is used to determine the recipient list when sending a message to a public channel — every registered user receives a copy of every public channel message.

Returns:

List of usernames for all registered accounts.

Return type:

list of str

minimost.chat.normalize_dm(users: List[str]) str[source]

Return the canonical DM channel identifier for a set of participants.

DM channels are identified by the string "dm:" followed by the sorted, colon-separated participant usernames. Sorting ensures that normalize_dm(["bob", "alice"]) and normalize_dm(["alice", "bob"]) both return the same string, preventing duplicate conversations from being created with different orderings.

Parameters:

users (list of str) – List of usernames that are participants in the DM. Duplicates are removed before sorting.

Returns:

Canonical DM channel string, e.g. "dm:alice:bob".

Return type:

str

Example:

normalize_dm(["charlie", "alice"])
# returns "dm:alice:charlie"
minimost.chat.channel_users(channel: str) List[str][source]

Return the list of users who should receive messages on a channel.

  • For DM channels ("dm:user1:user2:..."): the participants are parsed directly from the channel string.

  • For private channels ("private:<id>"): the member list is fetched from the private_channel_members table in presence.db via get_private_channel_members(). Returns [] if the channel ID is invalid.

  • For public channels: every registered user is a recipient, so all_users() is called.

This list is used by the send, edit, delete, and react routes to determine which per-user databases must be updated.

Parameters:

channel (str) – The channel name, DM identifier, or private channel identifier ("private:<id>").

Returns:

List of usernames that are members of channel.

Return type:

list of str

Example:

channel_users("dm:alice:bob")     # ["alice", "bob"]
channel_users("private:3")        # members of private channel 3
channel_users("general")          # all registered users
minimost.chat.is_valid_channel(channel: str, user: str) bool[source]

Return True only if user is permitted to access channel.

Three access rules apply:

  • DM channels — the channel string must have at least two participants (len(parts) >= 3) and user must be one of them.

  • Private channelsuser must appear in the private_channel_members table for the given channel ID (looked up via get_private_channel_members()). Returns False if the ID is malformed or the channel does not exist.

  • Public channelschannel must appear in the CHANNELS list loaded from channels.json.

This function is called by the send() route before writing any data, and is the primary authorization check for channel access.

Parameters:
  • channel (str) – The channel name, DM identifier, or private channel identifier ("private:<id>").

  • user (str) – The username attempting to access the channel.

Returns:

True if access is permitted, False otherwise.

Return type:

bool

Example:

is_valid_channel("dm:alice:bob", "alice")    # True
is_valid_channel("dm:alice:bob", "charlie")  # False
is_valid_channel("private:3", "alice")       # True if alice is a member
is_valid_channel("general", "alice")         # True (if "general" in CHANNELS)
is_valid_channel("secret", "alice")          # False (not in CHANNELS)
minimost.chat.channels()[source]

Return the list of public channel names.

Route: GET /channels

Requires authentication. Returns the CHANNELS list that was loaded from channels.json at application startup. The client uses this list to build the channel sidebar.

Returns:

JSON array of channel name strings, e.g. ["general", "software", "off-topic"].

Return type:

flask.Response (application/json)

minimost.chat.channel_unreads()[source]

Return unread message counts for every public channel.

Route: GET /channel_unreads

Requires authentication. Queries the current user’s database and counts messages that are unread (read = 0), not sent by the user (sender != user), and not deleted (deleted = 0), grouped by public channel.

All channels in CHANNELS are included in the response — those with no unread messages are returned with a count of 0.

Returns:

JSON object mapping each public channel name to its unread count, e.g. {"general": 3, "software": 0, "off-topic": 1}.

Return type:

flask.Response (application/json)

minimost.chat.unread_count()[source]

Return the total number of unread direct messages for the current user.

Route: GET /unread_count

Requires authentication. Counts all unread (read = 0) messages in DM channels (channel LIKE 'dm:%') where the current user is a participant and the message was sent by someone else.

The three LIKE patterns handle the three positions a username can occupy in a DM channel string:

  • dm:<user>:<others> — user is the first participant.

  • dm:<others>:<user> — user is the last participant.

  • dm:<others>:<user>:<more> — user is a middle participant (group DM).

This count is displayed as a badge on the “DMs” section of the sidebar and drives the browser tab title badge.

Returns:

JSON object {"count": N} where N is the total unread DM message count.

Return type:

flask.Response (application/json)

minimost.chat.dms()[source]

Return a summary of all DM conversations involving the current user.

Route: GET /dms

Requires authentication. Queries the current user’s database for all DM channels, returning them sorted by most-recent message timestamp (last_ts DESC) so the sidebar list reflects activity order.

For each conversation, the response includes:

  • channel — the canonical DM channel identifier (e.g. "dm:alice:bob").

  • users — list of the other participants (the current user is excluded so the client can display their names).

  • unread — count of unread messages from other users in this conversation.

Returns:

JSON array of conversation objects, each with keys channel, users (list of str), and unread (int), ordered by most recent activity descending.

Return type:

flask.Response (application/json)

minimost.chat.close_dm()[source]

Hide a DM conversation from the current user’s sidebar.

Route: POST /dms/close

JSON body: {"channel": str}. Records a hidden_ts timestamp for the channel; the /dms endpoint will exclude this conversation until a new message arrives after that timestamp.

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.user_colors()[source]

Return custom name colors for all users who have set one.

Route: GET /user_colors

Returns:

JSON object mapping username to hex color string.

Return type:

flask.Response (application/json)

minimost.chat.get_settings()[source]

Return the current user’s settings.

Route: GET /settings

Returns:

JSON object with user settings keys.

Return type:

flask.Response (application/json)

minimost.chat.save_settings()[source]

Save the current user’s settings.

Route: POST /settings

JSON body: {"name_color": str | null}. Pass null to reset to the default hash-derived color.

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.get_profile(username)[source]
minimost.chat.user_avatars()[source]

Return usernames of all users who have a custom avatar.

Route: GET /user_avatars

Returns:

JSON array of usernames.

Return type:

flask.Response (application/json)

minimost.chat.get_avatar(username)[source]

Serve a user’s avatar image.

Route: GET /avatar/<username>

Returns 404 if the user has no custom avatar.

Returns:

Image file response or 404.

Return type:

flask.Response

minimost.chat.upload_avatar()[source]

Upload and store the current user’s avatar.

Route: POST /avatar

Expects a multipart file named avatar (pre-resized client-side). Deletes the previous avatar file if one existed.

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.delete_avatar()[source]

Remove the current user’s custom avatar.

Route: DELETE /avatar

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.delete_account()[source]

Soft or hard delete the current user’s account.

Route: POST /delete_account

JSON body: {"type": "soft"|"hard", "password": "<current password>"}

Soft delete — removes the user’s credentials and settings from auth.db, renames their messages to "Deleted User" in every recipient’s database, and deletes their avatar file. Message history remains visible.

Hard delete — performs the soft delete steps and additionally removes every message they sent from all recipient databases, deletes their own database file, and purges their records from presence.db.

In both cases the session is cleared and the client should redirect to the login page.

Returns:

JSON {"status": "ok"} on success, or an error response.

Return type:

flask.Response (application/json)

minimost.chat.online_users()[source]

Return presence states for all recently active users.

Route: GET /online_users

Requires authentication. Queries presence.db for any user whose last_seen timestamp is within the last 3600 seconds (one hour). Users who have not reported any presence update in that window are considered offline and excluded from the response.

Possible state values returned: "active", "idle", "hidden", "offline". States are lowercased before being returned.

The client polls this endpoint once per second to refresh the colored presence indicator (dot) displayed next to each username in the sidebar.

Returns:

JSON object mapping username strings to their current presence state, e.g. {"alice": "active", "bob": "idle"}.

Return type:

flask.Response (application/json)

minimost.chat.messages(channel)[source]

Fetch messages for a channel since a given timestamp.

Route: GET /messages/<channel>?after=<timestamp>

Requires authentication. This is the core polling endpoint: the JavaScript client calls it every 500 ms, passing the timestamp of the last message it received as the after query parameter. The server returns only rows that have changed since that point, keeping each response small.

What is returned:

A message row is included in the response if any of the following conditions are true since after:

  • It is a new, non-deleted message (ts > after).

  • It has been edited since after (edited_ts > after).

  • Its reactions have been updated since after (reactions_ts > after).

  • It was deleted after after (deleted = 1 AND deleted_ts > after). Deleted tombstones are returned so the client can remove the message from view.

Reactions enrichment:

After querying the user’s database, the function fetches the current reactions for all returned messages from presence.db::message_reactions and merges them into the response as a JSON string under the reactions key. This overwrites the stale reactions column from the user DB.

``after`` parameter handling:

  • The string "NaN" (case-insensitive) is treated as 0.0 to guard against clients passing NaN on first load.

  • Non-numeric values are silently converted to 0.0.

Parameters:

channel (str) – The channel name or DM identifier.

Query parameters:

after (float, optional): Unix timestamp. Only messages modified after this point are returned. Defaults to 0 (return all messages).

Returns:

JSON array of message objects. Each object has keys: id, channel, sender, content, filename, ts, edited, edited_ts, deleted, deleted_ts, reply_to_id, reactions (JSON string or null), reactions_ts.

Return type:

flask.Response (application/json)

minimost.chat._try_append_message(channel, sender, text, ts, recipients, filenames, reply_to_id)[source]

Append text to the sender’s previous message if within the 300 s window.

Returns True and commits if appended; returns False if a new message is needed.

minimost.chat.post_welcome_message(new_user: str) None[source]

Post a system welcome message greeting a new user.

Called from the signup flow when a new account is created. Inserts a content_type = "system" message — rendered under the “MiniMost” identity, like other system notices — into the first public channel for every recipient (including the newcomer), so the whole community sees the new arrival being greeted.

No-op if no public channels are configured.

Parameters:

new_user (str) – The username of the newly registered account.

Returns:

None

minimost.chat.send(channel)[source]

Send a message (and/or image attachments) to a channel.

Route: POST /send/<channel>

Requires authentication. Validates the channel, processes any uploaded images, then inserts the message into every recipient’s database.

Form fields:

  • text (str, optional) — The message body. Trailing whitespace is stripped.

  • reply_to_id (int, optional) — The id of the message being replied to. Stored as a foreign key in the reply_to_id column.

  • files (multipart file list, optional) — One or more image files. Only files with extensions in ALLOWED_EXTENSIONS are saved; others are silently skipped.

At least one of text or files must be non-empty; a request with neither returns 400 empty.

Message propagation:

MiniMost does not use a shared messages table. Instead, each message is written into every recipient’s individual database by iterating over channel_users(). This means:

  • For a public channel with N users, N separate INSERT statements are executed.

  • For each uploaded image, an additional INSERT per recipient is executed (one row per file, with content = '' and filename set to the UUID-based filename).

  • All inserts for a given recipient are committed in a single transaction.

The sender is always added to the recipient list so their own message appears in their database (marked read = 0 like everyone else’s).

File naming:

Uploaded images are saved as <uuid4hex><original_ext> in UPLOAD_DIR to prevent collisions and avoid directory traversal via crafted filenames.

Parameters:

channel (str) – Target channel name or DM identifier.

Returns:

The string "ok" on success.

Return type:

flask.Response

Raises:

403 forbidden — if the user is not permitted to post to the channel.

Raises:

400 empty — if neither text nor valid files were provided.

Raises:

400 no recipients — if the recipient list is empty (should not happen in normal operation).

minimost.chat.get_message(msg_id)[source]

Fetch a single message by its database ID.

Route: GET /message/<msg_id>

Requires authentication. Used by the client to load the quoted parent message when rendering a reply thread, since the reply_to_id column stores only the ID rather than the full message content.

The lookup is performed against the current user’s database, which means the message must exist in that user’s records.

Parameters:

msg_id (int) – The integer primary key of the message to retrieve.

Returns:

JSON object with keys id, sender, content, filename, and deleted.

Return type:

flask.Response (application/json)

Raises:

404 not found — if no message with msg_id exists in the current user’s database.

minimost.chat.files(filename)[source]

Serve an uploaded file.

Route: GET /files/<filename>

Images are served inline; all other file types are served as attachments so the browser downloads rather than attempts to render them.

minimost.chat.file_preview(filename)[source]

Return a code preview for an uploaded text file.

Route: GET /file_preview/<filename>

Reads the file from UPLOAD_DIR, checks its extension against the known text-file extension set, and returns the same code-preview dict shape used by the Bitbucket preview routes. Returns {} for unrecognised extensions or unreadable files.

minimost.chat.search_messages()[source]

Search the current user’s message history by keyword.

Route: GET /search_messages?q=<query>

Requires authentication. Performs a case-insensitive substring search (SQLite LIKE %query%) across the content column of the current user’s messages table, excluding deleted messages.

Results are returned in descending timestamp order (newest first) and limited to 50 rows to keep responses fast. The search scope is the current user’s database only, which means only messages that user has access to (including all public channels and their DMs) are searched.

Query parameters (all optional, combined with AND):

q (str): Keyword matched as a case-insensitive LIKE substring of the message content.

from (str): Restrict to messages from this sender (matched case-insensitively).

channel (str): Restrict to a single channel identifier.

start / end (float): Inclusive lower / exclusive upper bound on the message timestamp, as epoch seconds. The client computes these from the picked dates using the viewer’s local timezone.

At least one filter must be supplied; a request with no filters returns [] without hitting the database, so an empty search box never dumps the whole history.

Returns:

JSON array of matching message objects, each with keys id, channel, sender, content, and ts.

Return type:

flask.Response (application/json)

minimost.chat.edit(msg_id)[source]

Edit the text content of the current user’s message.

Route: POST /edit/<msg_id>

Requires authentication. Only the original sender may edit a message; any other user’s attempt returns 403 forbidden.

Only text messages can be edited — rows with a non-null filename (image attachments) are excluded from the UPDATE by the AND filename IS NULL clause.

Propagation:

The edit is applied to every recipient’s copy of the message, matched by the combination of (channel, sender, ts) rather than by id because row IDs differ between per-user databases. The edited flag is set to 1 and edited_ts records when the edit occurred so the polling query (messages()) picks it up for other users.

Form fields:

text (str): The new message content. Stripped of leading/trailing whitespace.

Parameters:

msg_id (int) – The integer id of the message to edit (in the current user’s database).

Returns:

The string "ok" on success.

Return type:

flask.Response

Raises:

403 forbidden — if the message does not belong to the current user or does not exist.

minimost.chat.delete_message(msg_id)[source]

Soft-delete the current user’s message.

Route: POST /delete/<msg_id>

Requires authentication. Only the original sender may delete a message; other users receive 403 forbidden.

Soft delete, not hard delete:

The message row is not removed from the database. Instead, deleted is set to 1 and deleted_ts records when the deletion occurred. This allows the polling query (messages()) to return a tombstone to any client that has already cached the message, so they can remove it from their view. The actual row is preserved for audit/admin purposes.

Propagation:

Like edits, the soft-delete is applied to every recipient’s copy of the message, matched by (channel, sender, ts).

Parameters:

msg_id (int) – The integer id of the message to delete (in the current user’s database).

Returns:

The string "ok" on success.

Return type:

flask.Response

Raises:

403 forbidden — if the message was not sent by the current user or does not exist.

minimost.chat._save_uploaded_files(files) List[str][source]

Save uploaded files of any type, returning their stored filenames.

Images are stored as <uuid32hex><ext>. All other files are stored as <uuid32hex>_<sanitized_original_name> so the original name can be recovered by slicing off the first 33 chars.

minimost.chat.react(msg_id)[source]

Toggle an emoji reaction on a message.

Route: POST /react/<msg_id>

Requires authentication. The reaction is toggled: if the current user has already reacted with this emoji, the reaction is removed; otherwise it is added.

Why the shared ``presence.db`` is used:

Reactions must be visible to all users instantly and without race conditions. If reactions were stored in per-user databases, a read-modify-write cycle would be needed on each user’s file — which creates TOCTOU races under concurrent requests. Instead, the message_reactions table in presence.db is used with a single atomic INSERT OR DELETE operation.

Propagation to user databases:

After updating presence.db, the function bumps the reactions_ts column in every recipient’s copy of the message. This causes the polling query (messages()) to return the message row again for all users, and the client re-fetches the current reactions from the response.

Form fields:

reaction (str): The emoji name (SVG filename without extension) to toggle. Must be a member of VALID_REACTIONS.

Parameters:

msg_id (int) – The integer id of the message to react to (in the current user’s database).

Returns:

JSON object mapping emoji names to lists of reactors, e.g. {"thumbs_up": ["alice", "bob"], "heart": ["charlie"]}.

Return type:

flask.Response (application/json)

Raises:

400 invalid reaction — if reaction is not in VALID_REACTIONS.

Raises:

404 not found — if msg_id does not exist in the current user’s database.

minimost.chat.mark_read(channel)[source]

Mark all messages in a channel as read for the current user.

Route: POST /mark_read/<channel>

Requires authentication. Called by the client whenever the user switches to a channel or scrolls to the bottom of the message list.

Two-step operation:

  1. Collects the timestamps of every currently-unread message in the channel (sent by other users) so they can be recorded as read receipts.

  2. Sets read = 1 for all messages in the channel that were not sent by the current user.

Read receipts:

After marking messages as read in the user’s database, a row is inserted into presence.db::read_receipts for each previously-unread message timestamp. INSERT OR IGNORE is used to avoid duplicate entries when the same message is read multiple times (e.g. the user switches to the channel, back, then returns).

The client polls read_receipts() to display checkmarks and tooltips showing who has read each message.

Parameters:

channel (str) – The channel name or DM identifier.

Returns:

Empty body with HTTP 204 No Content.

Return type:

flask.Response

minimost.chat.read_receipts(channel)[source]

Return read receipts for all messages in a channel.

Route: GET /read_receipts/<channel>

Requires authentication. Queries presence.db::read_receipts and returns a mapping of message timestamps to the list of users who have read each message.

The message timestamp (msg_ts) is used as the key (as a string) rather than the message id because IDs differ across per-user databases while timestamps are shared.

The client polls this endpoint every 3 seconds when viewing a channel and renders indicators with a tooltip listing the readers.

Parameters:

channel (str) – The channel name or DM identifier.

Returns:

JSON object mapping message timestamp strings to lists of reader usernames, e.g. {"1716000000.123": ["alice", "bob"], "1716000001.456": ["alice"]}.

Return type:

flask.Response (application/json)

minimost.chat.users()[source]

Return a list of all registered users except the current user.

Route: GET /users

Requires authentication. Used by the client to populate the “New DM” autocomplete modal — the list shows all other accounts the user can start a conversation with.

Returns:

JSON array of username strings, excluding the currently logged-in user.

Return type:

flask.Response (application/json)

minimost.chat.channel_members(channel)[source]

Return the mentionable members of a channel (excluding the caller).

Route: GET /channel_members/<channel>

Requires authentication and that the caller is permitted to access the channel. Used by the client to populate the @-mention autocomplete dropdown: for public channels this is every other registered user, for private channels the other members, and for DMs the other participants.

Parameters:

channel (str) – The channel name, DM identifier, or "private:<id>".

Returns:

JSON array of usernames the caller may mention, excluding themselves.

Return type:

flask.Response (application/json)

Raises:

403 forbidden — if the caller may not access the channel.

Fetch a link preview card for a URL.

Route: GET /link_preview?url=<url>

Requires authentication. Delegates to minimost.preview.fetch_preview(), which supports Bitbucket Cloud, Bitbucket Server, and generic OpenGraph previews.

An empty JSON object {} is returned if:

  • The url query parameter is missing or blank.

  • The URL is a private/internal IP address (SSRF protection).

  • The URL scheme is not http or https.

  • The request fails or times out.

  • No usable preview data can be extracted.

Query parameters:

url (str): The fully-qualified URL to preview.

Returns:

A JSON object describing the preview. Shape depends on the preview type:

  • Code preview (Bitbucket): {"type": "code", "filename": ..., "filepath": ..., "language": ..., "first_line_num": ..., "highlight_start": ..., "highlight_end": ..., "code": ..., "total_lines": ..., "url": ...}

  • OpenGraph preview: {"type": "og", "title": ..., "description": ..., "image": ..., "domain": ..., "url": ...}

  • No preview: {}

Return type:

flask.Response (application/json)

minimost.chat.create_private_channel()[source]

Create a new private channel.

Route: POST /private_channels/create

JSON body: {"name": str, "members": [str]}. The creator is always included as a member regardless of the list provided.

Returns:

JSON {"id": int, "channel": str, "name": str} on success.

Return type:

flask.Response (application/json)

minimost.chat.list_private_channels()[source]

List private channels the current user is a member of.

Route: GET /private_channels

Returns:

JSON array of {"id", "channel", "name", "unread"} objects.

Return type:

flask.Response (application/json)

minimost.chat.rename_private_channel(channel_id)[source]

Rename a private channel. Any member may rename.

Route: POST /private_channels/<channel_id>/rename

JSON body: {"name": str}.

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.add_private_channel_member(channel_id)[source]

Add a user to a private channel. Any existing member may add.

Route: POST /private_channels/<channel_id>/add_member

JSON body: {"username": str}. The new member starts fresh — no prior message history is shared. A system message is inserted into every member’s database announcing the addition.

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.leave_private_channel(channel_id)[source]

Remove the current user from a private channel.

Route: POST /private_channels/<channel_id>/leave

A system message announcing the departure is inserted into every remaining member’s database.

Returns:

"ok" on success.

Return type:

flask.Response

minimost.chat.private_channel_members_route(channel_id)[source]

List members of a private channel.

Route: GET /private_channels/<channel_id>/members

Returns:

JSON array of {"username": str} objects.

Return type:

flask.Response (application/json)

minimost.chat.index()[source]

Serve the main chat single-page application.

Route: GET /

Requires authentication (redirects to /login otherwise). Renders the chat.html Jinja2 template, which contains the full client-side chat interface. All subsequent data is loaded by the JavaScript polling loop via the JSON API endpoints in this module.

Returns:

A rendered HTML response.

Return type:

flask.Response (text/html)

Route Summary

Method

Path

Handler

GET

/

minimost.chat.index()

GET

/channels

minimost.chat.channels()

GET

/channel_unreads

minimost.chat.channel_unreads()

GET

/messages/<channel>

minimost.chat.messages()

POST

/send/<channel>

minimost.chat.send()

GET

/message/<msg_id>

minimost.chat.get_message()

POST

/edit/<msg_id>

minimost.chat.edit()

POST

/delete/<msg_id>

minimost.chat.delete_message()

POST

/react/<msg_id>

minimost.chat.react()

POST

/mark_read/<channel>

minimost.chat.mark_read()

GET

/read_receipts/<channel>

minimost.chat.read_receipts()

GET

/dms

minimost.chat.dms()

GET

/unread_count

minimost.chat.unread_count()

GET

/online_users

minimost.chat.online_users()

GET

/users

minimost.chat.users()

GET

/channel_members/<channel>

minimost.chat.channel_members()

GET

/search_messages

minimost.chat.search_messages()

GET

/files/<filename>

minimost.chat.files()

GET

/link_preview

minimost.chat.link_preview()