mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 17:13:10 +00:00
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:
14 files changed
+265
-49
No files matched your search
@@ -47,6 +47,8 @@ chatProfileTests = do
|
||||
describe "user profiles" $ do
|
||||
it "update user profile and notify contacts" testUpdateProfile
|
||||
it "update user profile with image" testUpdateProfileImage
|
||||
it "reject profile image that is too large" testSetProfileImageTooLarge
|
||||
it "set profile image from file" testSetProfileImageFromFile
|
||||
it "use multiword profile names" testMultiWordProfileNames
|
||||
it "present supporter badge to contacts" testUserBadgeBroadcast
|
||||
it "supporter badge sent to contact connecting after attach" testUserBadgeOnConnect
|
||||
@@ -57,6 +59,7 @@ chatProfileTests = do
|
||||
it "supporter badge sent to contact connecting via address" testUserBadgeContactAddress
|
||||
describe "user contact link" $ do
|
||||
it "create and connect via contact link" testUserContactLink
|
||||
it "create address on specified server" testCreateAddressOnServer
|
||||
it "retry connecting via contact link" testRetryConnectingViaContactLink
|
||||
it "add contact link to profile" testProfileLink
|
||||
it "auto accept contact requests" testUserContactLinkAutoAccept
|
||||
@@ -423,6 +426,52 @@ testUpdateProfileImage =
|
||||
bob <## "use @alice2 <message> to send messages"
|
||||
(bob </)
|
||||
|
||||
testSetProfileImageTooLarge :: HasCallStack => TestParams -> IO ()
|
||||
testSetProfileImageTooLarge =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
connectUsers alice bob
|
||||
-- image within the size limit is accepted
|
||||
alice ##> "/set profile image data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="
|
||||
alice <## "profile image updated"
|
||||
-- image over the size limit is rejected;
|
||||
-- the long command wraps in the virtual terminal, so drain echo lines until the error
|
||||
alice `send` ("/set profile image data:image/png;base64," <> replicate 12500 'A')
|
||||
let errPrefix = "bad chat command: Profile image is too large"
|
||||
expectError = do
|
||||
l <- getTermLine alice
|
||||
unless (take (length errPrefix) l == errPrefix) expectError
|
||||
expectError
|
||||
(bob </)
|
||||
|
||||
testSetProfileImageFromFile :: HasCallStack => TestParams -> IO ()
|
||||
testSetProfileImageFromFile ps = testChat aliceProfile test ps
|
||||
where
|
||||
tmp = tmpPath ps
|
||||
pngPath = tmp <> "/avatar.png"
|
||||
gifPath = tmp <> "/avatar.gif"
|
||||
missingPath = tmp <> "/missing.png"
|
||||
emptyPath = tmp <> "/empty.png"
|
||||
test alice = do
|
||||
B.writeFile pngPath "fake png bytes" -- content is not validated, only the extension
|
||||
-- set profile image from a .png file
|
||||
alice ##> ("/set profile image file " <> pngPath)
|
||||
alice <## "profile image updated"
|
||||
alice ##> "/show profile image"
|
||||
alice <## "Profile image:"
|
||||
alice <##. "data:image/png;base64,"
|
||||
-- unsupported extension is rejected
|
||||
B.writeFile gifPath "GIF89a"
|
||||
alice ##> ("/set profile image file " <> gifPath)
|
||||
alice <##. "bad chat command: unsupported image extension"
|
||||
-- missing file is rejected
|
||||
alice ##> ("/set profile image file " <> missingPath)
|
||||
alice <##. "bad chat command: image file not found"
|
||||
-- empty file is rejected
|
||||
B.writeFile emptyPath ""
|
||||
alice ##> ("/set profile image file " <> emptyPath)
|
||||
alice <##. "bad chat command: image file is empty"
|
||||
|
||||
testMultiWordProfileNames :: HasCallStack => TestParams -> IO ()
|
||||
testMultiWordProfileNames =
|
||||
testChat3 aliceProfile' bobProfile' cathProfile' $
|
||||
@@ -529,6 +578,32 @@ testUserContactLink =
|
||||
alice @@@ [("@cath", lastChatFeature), ("@bob", "hey")]
|
||||
alice <##> cath
|
||||
|
||||
testCreateAddressOnServer :: HasCallStack => TestParams -> IO ()
|
||||
testCreateAddressOnServer ps = testChat aliceProfile test ps
|
||||
where
|
||||
tmp = tmpPath ps
|
||||
-- second SMP server, distinct from alice's configured server (localhost:7001)
|
||||
altServer = "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"
|
||||
altServerCfg =
|
||||
smpServerCfg
|
||||
{ transports = [("7003", transport @TLS, False)],
|
||||
serverStoreCfg = persistentServerStoreCfg tmp
|
||||
}
|
||||
test alice = do
|
||||
withSmpServer' altServerCfg $ do
|
||||
-- without a server the address is created on the configured server (7001)
|
||||
alice ##> "/_address 1"
|
||||
(_, defaultLink) <- getContactLinks alice True
|
||||
defaultLink `shouldContain` "localhost%3A7001" -- server is URL-encoded in the link
|
||||
alice ##> "/_delete_address 1"
|
||||
alice <## "Your chat address is deleted - accepted contacts will remain connected."
|
||||
alice <## "To create a new chat address use /ad"
|
||||
-- with a server the address is pinned to the requested server (7003)
|
||||
alice ##> ("/_address 1 " <> altServer)
|
||||
(_, pinnedLink) <- getContactLinks alice True
|
||||
pinnedLink `shouldContain` "localhost%3A7003"
|
||||
alice <## "disconnected 1 connections on server localhost"
|
||||
|
||||
testRetryConnectingViaContactLink :: HasCallStack => TestParams -> IO ()
|
||||
testRetryConnectingViaContactLink ps = testChatCfgOpts2 cfg' opts' aliceProfile bobProfile test ps
|
||||
where
|
||||
|
||||
@@ -24,6 +24,7 @@ import Data.Maybe (fromMaybe)
|
||||
import Data.String
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), mkStoreCxt)
|
||||
import Simplex.Chat.Library.Commands (maxProfileImageSize)
|
||||
import Simplex.Chat.Markdown (viewName)
|
||||
import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText)
|
||||
import Simplex.Chat.Protocol
|
||||
@@ -241,7 +242,9 @@ genProfileImg = do
|
||||
g <- C.newRandom
|
||||
atomically $ B64.encode <$> C.randomBytes lrgLen g
|
||||
where
|
||||
lrgLen = maxEncodedInfoLength * 3 `div` 4 - 420
|
||||
-- raw bytes that base64-encode to fit maxProfileImageSize when prefixed with "data:image/png;base64,"
|
||||
lrgLen = (maxProfileImageSize - imagePrefixLen) * 3 `div` 4 - 1
|
||||
imagePrefixLen = 22
|
||||
|
||||
-- PQ combinators /
|
||||
|
||||
|
||||
Reference in new issue
Block a user