mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-28 18:40:09 +00:00
* 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
267 lines
8.4 KiB
Haskell
267 lines
8.4 KiB
Haskell
{-# LANGUAGE ApplicativeDo #-}
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
module Directory.Options
|
|
( DirectoryOpts (..),
|
|
MigrateLog (..),
|
|
getDirectoryOpts,
|
|
directoryOpts,
|
|
mkChatOpts,
|
|
)
|
|
where
|
|
|
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
|
import qualified Data.Text as T
|
|
import Data.Text.Encoding (encodeUtf8)
|
|
import Options.Applicative
|
|
import Simplex.Chat.Bot.KnownContacts
|
|
import Simplex.Chat.Controller (updateStr, versionNumber, versionString)
|
|
import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, CreateBotOpts (..), coreChatOptsP)
|
|
import Simplex.Messaging.Parsers (parseAll)
|
|
|
|
data DirectoryOpts = DirectoryOpts
|
|
{ coreOptions :: CoreChatOpts,
|
|
adminUsers :: [KnownContact],
|
|
superUsers :: [KnownContact],
|
|
ownersGroup :: Maybe KnownGroup,
|
|
noAddress :: Bool, -- skip creating address
|
|
blockedWordsFile :: Maybe FilePath,
|
|
blockedFragmentsFile :: Maybe FilePath,
|
|
blockedExtensionRules :: Maybe FilePath,
|
|
nameSpellingFile :: Maybe FilePath,
|
|
profileNameLimit :: Int,
|
|
captchaGenerator :: Maybe FilePath,
|
|
voiceCaptchaGenerator :: Maybe FilePath,
|
|
directoryLog :: Maybe FilePath,
|
|
migrateDirectoryLog :: Maybe MigrateLog,
|
|
serviceName :: T.Text,
|
|
clientService :: Bool,
|
|
runCLI :: Bool,
|
|
searchResults :: Int,
|
|
webFolder :: Maybe FilePath,
|
|
linkCheckInterval :: Int,
|
|
prohibitedToObserver :: Bool,
|
|
alwaysCaptcha :: Bool,
|
|
knocking :: Bool,
|
|
testing :: Bool
|
|
}
|
|
|
|
data MigrateLog = MLCheck | MLImport | MLExport | MLListing
|
|
|
|
directoryOpts :: FilePath -> FilePath -> Parser DirectoryOpts
|
|
directoryOpts appDir defaultDbName = do
|
|
coreOptions <- coreChatOptsP appDir defaultDbName
|
|
adminUsers <-
|
|
option
|
|
parseKnownContacts
|
|
( long "admin-users"
|
|
<> metavar "ADMIN_USERS"
|
|
<> value []
|
|
<> help "Comma-separated list of admin-users in the format CONTACT_ID:DISPLAY_NAME who will be allowed to manage the directory"
|
|
)
|
|
superUsers <-
|
|
option
|
|
parseKnownContacts
|
|
( long "super-users"
|
|
<> metavar "SUPER_USERS"
|
|
<> help "Comma-separated list of super-users in the format CONTACT_ID:DISPLAY_NAME who will be allowed to manage the directory"
|
|
)
|
|
ownersGroup <-
|
|
optional $
|
|
option
|
|
parseKnownGroup
|
|
( long "owners-group"
|
|
<> metavar "OWNERS_GROUP"
|
|
<> help "The group of group owners in the format GROUP_ID:DISPLAY_NAME - owners of listed groups will be invited automatically"
|
|
)
|
|
noAddress <-
|
|
switch
|
|
( long "no-address"
|
|
<> help "skip checking and creating service address"
|
|
)
|
|
blockedWordsFile <-
|
|
optional $
|
|
strOption
|
|
( long "blocked-words-file"
|
|
<> metavar "BLOCKED_WORDS_FILE"
|
|
<> help "File with the basic forms of words not allowed in profiles"
|
|
)
|
|
blockedFragmentsFile <-
|
|
optional $
|
|
strOption
|
|
( long "blocked-fragments-file"
|
|
<> metavar "BLOCKED_WORDS_FILE"
|
|
<> help "File with the basic forms of word fragments not allowed in profiles"
|
|
)
|
|
blockedExtensionRules <-
|
|
optional $
|
|
strOption
|
|
( long "blocked-extenstion-rules"
|
|
<> metavar "BLOCKED_EXTENSION_RULES"
|
|
<> help "Substitions to extend the list of blocked words"
|
|
)
|
|
nameSpellingFile <-
|
|
optional $
|
|
strOption
|
|
( long "name-spelling-file"
|
|
<> metavar "NAME_SPELLING_FILE"
|
|
<> help "File with the character substitions to match in profile names"
|
|
)
|
|
profileNameLimit <-
|
|
option
|
|
auto
|
|
( long "profile-name-limit"
|
|
<> metavar "PROFILE_NAME_LIMIT"
|
|
<> help "Max length of profile name that will be allowed to connect and to join groups"
|
|
<> value maxBound
|
|
)
|
|
captchaGenerator <-
|
|
optional $
|
|
strOption
|
|
( long "captcha-generator"
|
|
<> metavar "CAPTCHA_GENERATOR"
|
|
<> help "Executable to generate captcha files, must accept text as parameter and save file to stdout as base64 up to 12500 bytes"
|
|
)
|
|
voiceCaptchaGenerator <-
|
|
optional $
|
|
strOption
|
|
( long "voice-captcha-generator"
|
|
<> metavar "VOICE_CAPTCHA_GENERATOR"
|
|
<> help "Executable to generate voice captcha, accepts text as parameter, writes audio file, outputs file_path and duration_seconds to stdout"
|
|
)
|
|
directoryLog <-
|
|
optional $
|
|
strOption
|
|
( long "directory-file"
|
|
<> metavar "DIRECTORY_FILE"
|
|
<> help "Append only log for directory state"
|
|
)
|
|
migrateDirectoryLog <-
|
|
optional $
|
|
option
|
|
parseMigrateLog
|
|
( long "migrate-directory-file"
|
|
<> metavar "MIGRATE_COMMAND"
|
|
<> help "Command to import/export directory log file"
|
|
)
|
|
serviceName <-
|
|
strOption
|
|
( long "service-name"
|
|
<> metavar "SERVICE_NAME"
|
|
<> help "The display name of the directory service bot, without *'s and spaces (SimpleX Directory)"
|
|
<> value "SimpleX Directory"
|
|
)
|
|
clientService <-
|
|
switch
|
|
( long "client-service"
|
|
<> help "Use client service certificate"
|
|
)
|
|
runCLI <-
|
|
switch
|
|
( long "run-cli"
|
|
<> help "Run directory service as CLI"
|
|
)
|
|
webFolder <-
|
|
optional $
|
|
strOption
|
|
( long "web-folder"
|
|
<> metavar "WEB_FOLDER"
|
|
<> help "Folder to store static web assets"
|
|
)
|
|
linkCheckInterval <-
|
|
option
|
|
auto
|
|
( long "link-check-interval"
|
|
<> metavar "SECONDS"
|
|
<> help "Interval in seconds to check public group link data (default: 1800)"
|
|
<> value 1800
|
|
)
|
|
prohibitedToObserver <-
|
|
switch
|
|
( long "prohibited-to-observer"
|
|
<> help "Set a member to observer (and delete the message) when they post content prohibited by the group's settings"
|
|
)
|
|
alwaysCaptcha <-
|
|
switch
|
|
( long "always-captcha"
|
|
<> help "Require a captcha from joining members in all groups, regardless of per-group filter settings"
|
|
)
|
|
knocking <-
|
|
switch
|
|
( long "knocking"
|
|
<> help "Require admin review (knocking) before joining members are admitted in all groups, regardless of group preference"
|
|
)
|
|
pure
|
|
DirectoryOpts
|
|
{ coreOptions,
|
|
adminUsers,
|
|
superUsers,
|
|
ownersGroup,
|
|
noAddress,
|
|
blockedWordsFile,
|
|
blockedFragmentsFile,
|
|
blockedExtensionRules,
|
|
nameSpellingFile,
|
|
profileNameLimit,
|
|
captchaGenerator,
|
|
voiceCaptchaGenerator,
|
|
directoryLog,
|
|
migrateDirectoryLog,
|
|
serviceName = T.pack serviceName,
|
|
clientService,
|
|
runCLI,
|
|
searchResults = 10,
|
|
webFolder,
|
|
linkCheckInterval,
|
|
prohibitedToObserver,
|
|
alwaysCaptcha,
|
|
knocking,
|
|
testing = False
|
|
}
|
|
|
|
getDirectoryOpts :: FilePath -> FilePath -> IO DirectoryOpts
|
|
getDirectoryOpts appDir defaultDbName =
|
|
execParser $
|
|
info
|
|
(helper <*> versionOption <*> directoryOpts appDir defaultDbName)
|
|
(header versionStr <> fullDesc <> progDesc "Start SimpleX Directory Service with DB_FILE, DIRECTORY_FILE and SUPER_USERS options")
|
|
where
|
|
versionStr = versionString versionNumber
|
|
versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version")
|
|
versionAndUpdate = versionStr <> "\n" <> updateStr
|
|
|
|
mkChatOpts :: DirectoryOpts -> ChatOpts
|
|
mkChatOpts DirectoryOpts {coreOptions, serviceName, clientService} =
|
|
ChatOpts
|
|
{ coreOptions,
|
|
chatCmd = "",
|
|
chatCmdDelay = 3,
|
|
chatCmdLog = CCLNone,
|
|
chatServerPort = Nothing,
|
|
optFilesFolder = Nothing,
|
|
optTempDirectory = Nothing,
|
|
showReactions = False,
|
|
allowInstantFiles = True,
|
|
autoAcceptFileSize = 0,
|
|
muteNotifications = True,
|
|
markRead = False,
|
|
createBot = Just CreateBotOpts {botDisplayName = serviceName, allowFiles = False, clientService},
|
|
userDisplayName = Nothing,
|
|
userImageFile = Nothing
|
|
}
|
|
|
|
parseMigrateLog :: ReadM MigrateLog
|
|
parseMigrateLog = eitherReader $ parseAll mlP . encodeUtf8 . T.pack
|
|
where
|
|
mlP =
|
|
A.takeTill (== ' ') >>= \case
|
|
"check" -> pure MLCheck
|
|
"import" -> pure MLImport
|
|
"export" -> pure MLExport
|
|
"listing" -> pure MLListing
|
|
_ -> fail "bad MigrateLog"
|