cli: add name, image and relay server address flags (#6944)

* cli: add --relay-address-server option for chat relay

New CLI flag --relay-address-server SERVER selects the SMP server used
for the chat relay address link created at startup. Only valid together
with --relay; errors out otherwise.

Threads Maybe SMPServerWithAuth through APICreateMyAddress to the new
agent createConnection parameter.

* cli: add --user-display-name option

Selects or creates the active user non-interactively:
- no active user: create one with the given display name
- active user with matching localDisplayName: continue
- active user with different name: exit with error

Mutually exclusive with --create-bot-display-name.

* cli: add --user-image-file option

Sets the active user's profile image from a .png/.jpg/.jpeg file at
startup. Reads file, base64-encodes as data URL, and updates the user
profile directly in the DB - no notification is sent to existing
contacts. Skips the update if the stored image already matches.

Requires --user-display-name.

* cli: address PR review comments

- rename APICreateMyAddress field srv_ to server_
- extract repeated `loop` and putStrLn from createActiveUser via
  prompt where-clause
- fuse u_ inspection: validate active user display name in the same
  case that creates the user when missing

* cli: enforce profile image size limit in --user-image-file

Reject the file if the encoded data URL exceeds 12500 bytes - matches
the cap mobile and desktop UIs pass to resizeImageToStrSize for profile
images. Without this, oversized images would be silently set on the
user profile.

* core: validate profile image size in chat commands

Enforced in CreateActiveUser, updateProfile_, newGroup, runUpdateGroupProfile
via checkProfileImageSize; max 12500 bytes (matches mobile UIs).

* fix: thread new ChatOpts/CoreChatOpts fields through bot/test constructors

* bots/docs: filter hidden params before type introspection

Hide APICreateMyAddress server_ field so the bot doc generator does not
try to introspect SMPServerWithAuth (an unregistered type).

* core: validate full encoded profile size

Add checkProfileSize / checkGroupProfileSize that encode the full
ChatMessage and check against maxEncodedInfoLength, so a long
displayName/bio combined with a near-max image is also caught at
command time instead of failing later at send time with CEException.

Run alongside the existing checkProfileImageSize (image-only cap of
12500 bytes, matching mobile UIs) in CreateActiveUser, updateProfile_,
newGroup, runUpdateGroupProfile. Update genProfileImg to fit the cap.

* cli: add --headless option for chat relay

Skips interactive prompts (relay address creation, display name) so the
chat relay can run non-interactively as a service. Requires --relay;
creating a new profile also requires --user-display-name.

* test: cover profile image size limit and address server

Adds tests for two new capabilities:
- profile image size validation rejects oversized images
- /_address with a server pins the address to the requested SMP server

* core: update simplexmq (pass optional SMP server to prepareConnectionLink)

* cli: add /set profile image file, fix image flag

Add "/set profile image file <path>" command to set the profile image
from a .png/.jpg/.jpeg file in a running session.

Make --user-image-file create-only: for an existing user it now no-ops
with a note instead of failing with "chat not started" (the update ran
before the chat controller was started).

* core: unify image loading and profile size checks

- loadImageFile (CLI) and readProfileImageFile (command) now share one
  loadImageData; removes the duplicated mime/base64 encoding and its
  decodeUtf8/safeDecodeUtf8 divergence. A missing --user-image-file no
  longer crashes with an uncaught IOException, and empty files are rejected.
- checkProfileSize/checkGroupProfileSize share checkInfoSize.
- --relay-address-server/--headless requires-relay checks use
  errorWithoutStackTrace, matching the adjacent validation (no callstack dump).

* bots/docs: document UpdateProfileImageFromFile as a CLI command
This commit is contained in:
sh
2026-07-15 09:31:19 +01:00
committed by GitHub
parent 937c79bc3c
commit 6e2995cab6
14 changed files with 265 additions and 49 deletions
+47 -4
View File
@@ -22,7 +22,7 @@ where
import Control.Logger.Simple (LogLevel (..))
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString.Char8 as B
import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe, isJust, isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
@@ -50,7 +50,9 @@ data ChatOpts = ChatOpts
autoAcceptFileSize :: Integer,
muteNotifications :: Bool,
markRead :: Bool,
createBot :: Maybe CreateBotOpts
createBot :: Maybe CreateBotOpts,
userDisplayName :: Maybe Text,
userImageFile :: Maybe FilePath
}
data CoreChatOpts = CoreChatOpts
@@ -67,6 +69,8 @@ data CoreChatOpts = CoreChatOpts
deviceName :: Maybe Text,
chatRelay :: Bool,
webPreviewConfig :: Maybe WebPreviewConfig,
chatRelayServer :: Maybe SMPServerWithAuth,
headless :: Bool,
highlyAvailable :: Bool,
yesToUpMigrations :: Bool,
migrationBackupPath :: Maybe FilePath,
@@ -281,6 +285,19 @@ coreChatOptsP appDir defaultDbName = do
(Just webDomain, Just webJsonDir) -> Just WebPreviewConfig {webDomain, webJsonDir, webCorsFile, webUpdateInterval, webPreviewItemCount}
(Nothing, Nothing) -> Nothing
_ -> errorWithoutStackTrace "--relay-web-domain and --relay-web-dir must both be provided"
chatRelayServer <-
optional $
option
strParse
( long "relay-address-server"
<> metavar "SERVER"
<> help "SMP server to use for chat relay address link (requires --relay)"
)
headless <-
switch
( long "headless"
<> help "Run chat relay without interactive prompts, e.g. as a service (requires --relay; on first run also --user-display-name to create the profile)"
)
highlyAvailable <-
switch
( long "ha"
@@ -325,6 +342,12 @@ coreChatOptsP appDir defaultDbName = do
deviceName,
chatRelay,
webPreviewConfig,
chatRelayServer = case chatRelayServer of
Just _ | not chatRelay -> errorWithoutStackTrace "--relay-address-server option requires --relay option"
_ -> chatRelayServer,
headless = case headless of
True | not chatRelay -> errorWithoutStackTrace "--headless option requires --relay option"
_ -> headless,
highlyAvailable,
yesToUpMigrations,
migrationBackupPath,
@@ -438,6 +461,20 @@ chatOptsP appDir defaultDbName = do
( long "create-bot-client-service"
<> help "Flag for created bot to use client service certificate"
)
userDisplayName <-
optional $
strOption
( long "user-display-name"
<> metavar "NAME"
<> help "Use existing active user with this display name, or create one on the first start (incompatible with --create-bot-display-name)"
)
userImageFile <-
optional $
strOption
( long "user-image-file"
<> metavar "FILE"
<> help "Set user profile image from .png/.jpg/.jpeg file when the profile is created (requires --user-display-name); ignored if the user already exists (use \"/set profile image file <path>\" to change it)"
)
pure
ChatOpts
{ coreOptions,
@@ -453,11 +490,17 @@ chatOptsP appDir defaultDbName = do
muteNotifications,
markRead,
createBot = case createBotDisplayName of
Just botDisplayName -> Just CreateBotOpts {botDisplayName, allowFiles = createBotAllowFiles, clientService = createBotClientService}
Just botDisplayName
| isJust userDisplayName -> error "--user-display-name and --create-bot-display-name are mutually exclusive"
| otherwise -> Just CreateBotOpts {botDisplayName, allowFiles = createBotAllowFiles, clientService = createBotClientService}
Nothing
| createBotAllowFiles -> error "--create-bot-allow-files option requires --create-bot-name option"
| createBotClientService -> error "--create-bot-client-service option requires --create-bot-name option"
| otherwise -> Nothing
| otherwise -> Nothing,
userDisplayName,
userImageFile = case userImageFile of
Just _ | isNothing userDisplayName -> error "--user-image-file option requires --user-display-name option"
_ -> userImageFile
}
parseProtocolServers :: ProtocolTypeI p => ReadM [ProtoServerWithAuth p]