mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-10 17:18:31 +00:00
Merge branch 'master' into ab/async-subs
This commit is contained in:
@@ -19,6 +19,9 @@ struct ChatItemForwardingView: View {
|
||||
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocused
|
||||
@State private var alert: SomeAlert?
|
||||
@State private var hasSimplexLink_: Bool?
|
||||
private let chatsToForwardTo = filterChatsToForwardTo()
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -35,47 +38,29 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
@ViewBuilder private func forwardListView() -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
let chatsToForwardTo = filterChatsToForwardTo()
|
||||
if !chatsToForwardTo.isEmpty {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 8) {
|
||||
searchFieldView(text: $searchText, focussed: $searchFocused)
|
||||
.padding(.leading, 2)
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { filterChatSearched($0, s) }
|
||||
ForEach(chats) { chat in
|
||||
Divider()
|
||||
forwardListNavLinkView(chat)
|
||||
.disabled(chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
}
|
||||
List {
|
||||
searchFieldView(text: $searchText, focussed: $searchFocused)
|
||||
.padding(.leading, 2)
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) }
|
||||
ForEach(chats) { chat in
|
||||
forwardListChatView(chat)
|
||||
.disabled(chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func filterChatsToForwardTo() -> [Chat] {
|
||||
var filteredChats = chatModel.chats.filter({ canForwardToChat($0) })
|
||||
if let index = filteredChats.firstIndex(where: { $0.chatInfo.chatType == .local }) {
|
||||
let privateNotes = filteredChats.remove(at: index)
|
||||
filteredChats.insert(privateNotes, at: 0)
|
||||
}
|
||||
return filteredChats
|
||||
}
|
||||
|
||||
private func filterChatSearched(_ chat: Chat, _ searchStr: String) -> Bool {
|
||||
private func foundChat(_ chat: Chat, _ searchStr: String) -> Bool {
|
||||
let cInfo = chat.chatInfo
|
||||
return switch cInfo {
|
||||
case let .direct(contact):
|
||||
@@ -91,42 +76,70 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func canForwardToChat(_ chat: Chat) -> Bool {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
|
||||
case let .group(groupInfo): groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): noteFolder.sendMsgEnabled
|
||||
private func prohibitedByPref(_ chat: Chat) -> Bool {
|
||||
// preference checks should match checks in compose view
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = (ci.content.msgContent?.isMediaOrFileAttachment ?? false) && !chat.groupFeatureEnabled(.files)
|
||||
let voiceProhibited = (ci.content.msgContent?.isVoice ?? false) && !chat.chatInfo.featureEnabled(.voice)
|
||||
return switch chat.chatInfo {
|
||||
case .direct: voiceProhibited
|
||||
case .group: simplexLinkProhibited || fileProhibited || voiceProhibited
|
||||
case .local: false
|
||||
case .contactRequest: false
|
||||
case .contactConnection: false
|
||||
case .invalidJSON: false
|
||||
}
|
||||
}
|
||||
|
||||
private var hasSimplexLink: Bool {
|
||||
if let hasSimplexLink_ { return hasSimplexLink_ }
|
||||
let r =
|
||||
if let mcText = ci.content.msgContent?.text,
|
||||
let parsedMsg = parseSimpleXMarkdown(mcText) {
|
||||
parsedMsgHasSimplexLink(parsedMsg)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
hasSimplexLink_ = r
|
||||
return r
|
||||
}
|
||||
|
||||
private func emptyList() -> some View {
|
||||
Text("No filtered chats")
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
@ViewBuilder private func forwardListNavLinkView(_ chat: Chat) -> some View {
|
||||
|
||||
@ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View {
|
||||
let prohibited = prohibitedByPref(chat)
|
||||
Button {
|
||||
dismiss()
|
||||
if chat.id == fromChatInfo.id {
|
||||
composeState = ComposeState(
|
||||
message: composeState.message,
|
||||
preview: composeState.linkPreview != nil ? composeState.preview : .noPreview,
|
||||
contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo)
|
||||
)
|
||||
if prohibited {
|
||||
alert = SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Cannot forward message",
|
||||
message: "Selected chat preferences prohibit this message."
|
||||
),
|
||||
id: "forward prohibited by preferences"
|
||||
)
|
||||
} else {
|
||||
composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo)
|
||||
chatModel.chatId = chat.id
|
||||
dismiss()
|
||||
if chat.id == fromChatInfo.id {
|
||||
composeState = ComposeState(
|
||||
message: composeState.message,
|
||||
preview: composeState.linkPreview != nil ? composeState.preview : .noPreview,
|
||||
contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo)
|
||||
)
|
||||
} else {
|
||||
composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo)
|
||||
chatModel.chatId = chat.id
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
ChatInfoImage(chat: chat, size: 30)
|
||||
.padding(.trailing, 2)
|
||||
Text(chat.chatInfo.chatViewName)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(prohibited ? .secondary : .primary)
|
||||
.lineLimit(1)
|
||||
if chat.chatInfo.incognito {
|
||||
Spacer()
|
||||
@@ -142,6 +155,27 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func filterChatsToForwardTo() -> [Chat] {
|
||||
var filteredChats = ChatModel.shared.chats.filter { c in
|
||||
c.chatInfo.chatType != .local && canForwardToChat(c)
|
||||
}
|
||||
if let privateNotes = ChatModel.shared.chats.first(where: { $0.chatInfo.chatType == .local }) {
|
||||
filteredChats.insert(privateNotes, at: 0)
|
||||
}
|
||||
return filteredChats
|
||||
}
|
||||
|
||||
private func canForwardToChat(_ chat: Chat) -> Bool {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
|
||||
case let .group(groupInfo): groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): noteFolder.sendMsgEnabled
|
||||
case .contactRequest: false
|
||||
case .contactConnection: false
|
||||
case .invalidJSON: false
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ChatItemForwardingView(
|
||||
ci: ChatItem.getSample(1, .directSnd, .now, "hello"),
|
||||
|
||||
@@ -286,6 +286,7 @@ struct ComposeView: View {
|
||||
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
|
||||
ContextInvitingContactMemberView()
|
||||
}
|
||||
// preference checks should match checks in forwarding list
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
|
||||
let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice)
|
||||
@@ -1065,7 +1066,7 @@ struct ComposeView: View {
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
let simplexLink = parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
|
||||
let simplexLink = parsedMsgHasSimplexLink(parsedMsg)
|
||||
return (url, simplexLink)
|
||||
}
|
||||
|
||||
@@ -1105,6 +1106,10 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool {
|
||||
parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
|
||||
}
|
||||
|
||||
struct ComposeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
|
||||
@@ -11,14 +11,9 @@ import SimpleXChat
|
||||
import CodeScanner
|
||||
import AVFoundation
|
||||
|
||||
enum SomeAlert: Identifiable {
|
||||
case someAlert(alert: Alert, id: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .someAlert(_, id): return id
|
||||
}
|
||||
}
|
||||
struct SomeAlert: Identifiable {
|
||||
var alert: Alert
|
||||
var id: String
|
||||
}
|
||||
|
||||
private enum NewChatViewAlert: Identifiable {
|
||||
@@ -142,8 +137,8 @@ struct NewChatView: View {
|
||||
switch(a) {
|
||||
case let .planAndConnectAlert(alert):
|
||||
return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" })
|
||||
case let .newChatSomeAlert(.someAlert(alert, _)):
|
||||
return alert
|
||||
case let .newChatSomeAlert(a):
|
||||
return a.alert
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +176,7 @@ struct NewChatView: View {
|
||||
await MainActor.run {
|
||||
creatingConnReq = false
|
||||
if let apiAlert = apiAlert {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(alert: apiAlert, id: "createInvitation error"))
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(alert: apiAlert, id: "createInvitation error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,7 +310,7 @@ private struct ConnectView: View {
|
||||
// showQRCodeScanner = false
|
||||
connect(pastedLink)
|
||||
} else {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."),
|
||||
id: "pasteLinkView: code is not a SimpleX link"
|
||||
))
|
||||
@@ -338,14 +333,14 @@ private struct ConnectView: View {
|
||||
if strIsSimplexLink(r.string) {
|
||||
connect(link)
|
||||
} else {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."),
|
||||
id: "processQRCode: code is not a SimpleX link"
|
||||
))
|
||||
}
|
||||
case let .failure(e):
|
||||
logger.error("processQRCode QR code error: \(e.localizedDescription)")
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"),
|
||||
id: "processQRCode: failure"
|
||||
))
|
||||
|
||||
@@ -24,11 +24,6 @@
|
||||
5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; };
|
||||
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; };
|
||||
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
|
||||
5C0EA13B2C0B176B00AD2E5E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1362C0B176B00AD2E5E /* libgmp.a */; };
|
||||
5C0EA13C2C0B176B00AD2E5E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */; };
|
||||
5C0EA13D2C0B176B00AD2E5E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1382C0B176B00AD2E5E /* libffi.a */; };
|
||||
5C0EA13E2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */; };
|
||||
5C0EA13F2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */; };
|
||||
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */; };
|
||||
5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */; };
|
||||
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
|
||||
@@ -197,6 +192,11 @@
|
||||
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
|
||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
|
||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
||||
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */; };
|
||||
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3B2C22D78C00CBA347 /* libffi.a */; };
|
||||
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */; };
|
||||
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3D2C22D78C00CBA347 /* libgmp.a */; };
|
||||
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -273,11 +273,6 @@
|
||||
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
|
||||
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
|
||||
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
|
||||
5C0EA1362C0B176B00AD2E5E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C0EA1382C0B176B00AD2E5E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a"; sourceTree = "<group>"; };
|
||||
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = "<group>"; };
|
||||
5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMediaView.swift; sourceTree = "<group>"; };
|
||||
5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; };
|
||||
@@ -492,6 +487,11 @@
|
||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = "<group>"; };
|
||||
E5D68D3B2C22D78C00CBA347 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
E5D68D3D2C22D78C00CBA347 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -529,13 +529,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C0EA13C2C0B176B00AD2E5E /* libgmpxx.a in Frameworks */,
|
||||
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C0EA13B2C0B176B00AD2E5E /* libgmp.a in Frameworks */,
|
||||
5C0EA13D2C0B176B00AD2E5E /* libffi.a in Frameworks */,
|
||||
5C0EA13F2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C0EA13E2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a in Frameworks */,
|
||||
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */,
|
||||
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */,
|
||||
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */,
|
||||
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -601,11 +601,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C0EA1382C0B176B00AD2E5E /* libffi.a */,
|
||||
5C0EA1362C0B176B00AD2E5E /* libgmp.a */,
|
||||
5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */,
|
||||
5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */,
|
||||
5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */,
|
||||
E5D68D3B2C22D78C00CBA347 /* libffi.a */,
|
||||
E5D68D3D2C22D78C00CBA347 /* libgmp.a */,
|
||||
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */,
|
||||
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */,
|
||||
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1552,7 +1552,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1577,7 +1577,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1601,7 +1601,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1626,7 +1626,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1687,7 +1687,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -1702,7 +1702,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1724,7 +1724,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -1739,7 +1739,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1761,7 +1761,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1787,7 +1787,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1812,7 +1812,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1838,7 +1838,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -3438,6 +3438,15 @@ public enum MsgContent: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public var isMediaOrFileAttachment: Bool {
|
||||
switch self {
|
||||
case .image: true
|
||||
case .video: true
|
||||
case .file: true
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
var cmdString: String {
|
||||
"json \(encodeJSON(self))"
|
||||
}
|
||||
|
||||
+14
@@ -2925,6 +2925,20 @@ sealed class MsgContent {
|
||||
@Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent()
|
||||
|
||||
val isVoice: Boolean get() =
|
||||
when (this) {
|
||||
is MCVoice -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val isMediaOrFileAttachment: Boolean get() =
|
||||
when (this) {
|
||||
is MCImage -> true
|
||||
is MCVideo -> true
|
||||
is MCFile -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val cmdString: String get() =
|
||||
if (this is MCUnknown) "json $json" else "json ${json.encodeToString(this)}"
|
||||
}
|
||||
|
||||
+43
-11
@@ -9,30 +9,55 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.views.helpers.ProfileImage
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
fun ShareListNavLinkView(
|
||||
chat: Chat,
|
||||
chatModel: ChatModel,
|
||||
isMediaOrFileAttachment: Boolean,
|
||||
isVoice: Boolean,
|
||||
hasSimplexLink: Boolean
|
||||
) {
|
||||
val stopped = chatModel.chatRunning.value == false
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct ->
|
||||
is ChatInfo.Direct -> {
|
||||
val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
|
||||
ShareListNavLinkLayout(
|
||||
chatLinkPreview = { SharePreviewView(chat) },
|
||||
click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) },
|
||||
chatLinkPreview = { SharePreviewView(chat, disabled = voiceProhibited) },
|
||||
click = {
|
||||
if (voiceProhibited) {
|
||||
showForwardProhibitedByPrefAlert()
|
||||
} else {
|
||||
directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel)
|
||||
}
|
||||
},
|
||||
stopped
|
||||
)
|
||||
is ChatInfo.Group ->
|
||||
}
|
||||
is ChatInfo.Group -> {
|
||||
val simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
|
||||
val fileProhibited = isMediaOrFileAttachment && !chat.groupFeatureEnabled(GroupFeature.Files)
|
||||
val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
|
||||
val prohibitedByPref = simplexLinkProhibited || fileProhibited || voiceProhibited
|
||||
ShareListNavLinkLayout(
|
||||
chatLinkPreview = { SharePreviewView(chat) },
|
||||
click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) },
|
||||
chatLinkPreview = { SharePreviewView(chat, disabled = prohibitedByPref) },
|
||||
click = {
|
||||
if (prohibitedByPref) {
|
||||
showForwardProhibitedByPrefAlert()
|
||||
} else {
|
||||
groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel)
|
||||
}
|
||||
},
|
||||
stopped
|
||||
)
|
||||
}
|
||||
is ChatInfo.Local ->
|
||||
ShareListNavLinkLayout(
|
||||
chatLinkPreview = { SharePreviewView(chat) },
|
||||
chatLinkPreview = { SharePreviewView(chat, disabled = false) },
|
||||
click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) },
|
||||
stopped
|
||||
)
|
||||
@@ -40,6 +65,13 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showForwardProhibitedByPrefAlert() {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.cannot_share_message_alert_title),
|
||||
text = generalGetString(MR.strings.cannot_share_message_alert_text),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShareListNavLinkLayout(
|
||||
chatLinkPreview: @Composable () -> Unit,
|
||||
@@ -53,7 +85,7 @@ private fun ShareListNavLinkLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SharePreviewView(chat: Chat) {
|
||||
private fun SharePreviewView(chat: Chat, disabled: Boolean) {
|
||||
Row(
|
||||
Modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -70,7 +102,7 @@ private fun SharePreviewView(chat: Chat) {
|
||||
}
|
||||
Text(
|
||||
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified
|
||||
color = if (disabled) MaterialTheme.colors.secondary else if (chat.chatInfo.incognito) Indigo else Color.Unspecified
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+51
-3
@@ -31,13 +31,44 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
|
||||
scaffoldState = scaffoldState,
|
||||
topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } },
|
||||
) {
|
||||
val sharedContent = chatModel.sharedContent.value
|
||||
var isMediaOrFileAttachment = false
|
||||
var isVoice = false
|
||||
var hasSimplexLink = false
|
||||
when (sharedContent) {
|
||||
is SharedContent.Text ->
|
||||
hasSimplexLink = hasSimplexLink(sharedContent.text)
|
||||
is SharedContent.Media -> {
|
||||
isMediaOrFileAttachment = true
|
||||
hasSimplexLink = hasSimplexLink(sharedContent.text)
|
||||
}
|
||||
is SharedContent.File -> {
|
||||
isMediaOrFileAttachment = true
|
||||
hasSimplexLink = hasSimplexLink(sharedContent.text)
|
||||
}
|
||||
is SharedContent.Forward -> {
|
||||
val mc = sharedContent.chatItem.content.msgContent
|
||||
if (mc != null) {
|
||||
isMediaOrFileAttachment = mc.isMediaOrFileAttachment
|
||||
isVoice = mc.isVoice
|
||||
hasSimplexLink = hasSimplexLink(mc.text)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
Box(Modifier.padding(it)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
if (chatModel.chats.isNotEmpty()) {
|
||||
ShareList(chatModel, search = searchInList)
|
||||
ShareList(
|
||||
chatModel,
|
||||
search = searchInList,
|
||||
isMediaOrFileAttachment = isMediaOrFileAttachment,
|
||||
isVoice = isVoice,
|
||||
hasSimplexLink = hasSimplexLink
|
||||
)
|
||||
} else {
|
||||
EmptyList()
|
||||
}
|
||||
@@ -54,6 +85,11 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasSimplexLink(msg: String): Boolean {
|
||||
val parsedMsg = parseToMarkdown(msg) ?: return false
|
||||
return parsedMsg.any { ft -> ft.format is Format.SimplexLink }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyList() {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
@@ -141,7 +177,13 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShareList(chatModel: ChatModel, search: String) {
|
||||
private fun ShareList(
|
||||
chatModel: ChatModel,
|
||||
search: String,
|
||||
isMediaOrFileAttachment: Boolean,
|
||||
isVoice: Boolean,
|
||||
hasSimplexLink: Boolean
|
||||
) {
|
||||
val chats by remember(search) {
|
||||
derivedStateOf {
|
||||
val sorted = chatModel.chats.toList().sortedByDescending { it.chatInfo is ChatInfo.Local }
|
||||
@@ -156,7 +198,13 @@ private fun ShareList(chatModel: ChatModel, search: String) {
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
items(chats) { chat ->
|
||||
ShareListNavLinkView(chat, chatModel)
|
||||
ShareListNavLinkView(
|
||||
chat,
|
||||
chatModel,
|
||||
isMediaOrFileAttachment = isMediaOrFileAttachment,
|
||||
isVoice = isVoice,
|
||||
hasSimplexLink = hasSimplexLink
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,8 @@
|
||||
<string name="share_image">Share media…</string>
|
||||
<string name="share_file">Share file…</string>
|
||||
<string name="forward_message">Forward message…</string>
|
||||
<string name="cannot_share_message_alert_title">Cannot send message</string>
|
||||
<string name="cannot_share_message_alert_text">Selected chat preferences prohibit this message.</string>
|
||||
|
||||
<!-- ComposeView.kt, helpers -->
|
||||
<string name="attach">Attach</string>
|
||||
|
||||
@@ -26,11 +26,11 @@ android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
android.version_name=5.8
|
||||
android.version_code=219
|
||||
android.version_name=5.8.1
|
||||
android.version_code=221
|
||||
|
||||
desktop.version_name=5.8
|
||||
desktop.version_code=53
|
||||
desktop.version_name=5.8.1
|
||||
desktop.version_code=54
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: bb1d31e459337f5d2de05f4495ff50d0a8788dff
|
||||
tag: 8a3b72458f917e9867f4e3640dda0fa1827ff6cf
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -17,18 +17,23 @@ SimpleX Chat Protocol is a protocol used by SimpleX Chat clients to exchange mes
|
||||
The scope of SimpleX Chat Protocol is application level messages, both for chat functionality, related to the conversations between the clients, and extensible for any other application functions. Currently supported chat functions:
|
||||
|
||||
- direct and group messages,
|
||||
- message replies (quoting), forwarded messages and message deletions,
|
||||
- message attachments: images and files,
|
||||
- message replies (quoting), message editing, forwarded messages and message deletions,
|
||||
- message attachments: images, videos, voice messages and files,
|
||||
- creating and managing chat groups,
|
||||
- invitation and signalling for audio/video WebRTC calls.
|
||||
|
||||
## General message format
|
||||
|
||||
SimpleX Chat protocol supports two message formats:
|
||||
SimpleX Chat protocol supports these message formats:
|
||||
|
||||
- JSON-based format for chat and application messages.
|
||||
- compressed format for adapting larger messages to reduced size of message envelope, caused by addition of PQ encryption keys to SMP agent message envelope.
|
||||
- binary format for sending files or any other binary data.
|
||||
|
||||
JSON-based message format supports batching inside a single container message, by encoding list of messages as JSON array.
|
||||
|
||||
Current implementation of chat protocol in SimpleX Chat uses SimpleX File Transfer Protocol (XFTP) for file transfer, with passing file description as chat protocol messages, instead passing files in binary format via SMP connections.
|
||||
|
||||
### JSON format for chat and application messages
|
||||
|
||||
This document uses JTD schemas [RFC 8927](https://www.rfc-editor.org/rfc/rfc8927.html) to define the properties of chat messages, with some additional restrictions on message properties included in metadata member of JTD schemas. In case of any contradiction between JSON examples and JTD schema the latter MUST be considered correct.
|
||||
@@ -77,8 +82,22 @@ For example, this message defines a simple text message `"hello!"`:
|
||||
|
||||
`params` property includes message data, depending on `event`, as defined below and in [JTD schema](./simplex-chat.schema.json).
|
||||
|
||||
### Compressed format
|
||||
|
||||
The syntax of compressed message is defined by the following ABNF notation:
|
||||
|
||||
```abnf
|
||||
compressedMessage = %s"X" 1*15780 OCTET; compressed message data
|
||||
```
|
||||
|
||||
Compressed message is required to fit into 13388 bytes, accounting for agent overhead (see Protocol's maxCompressedMsgLength).
|
||||
|
||||
The actual JSON message is required to fit into 15610 bytes, accounting for group message forwarding (x.grp.msg.forward) overhead (see Protocol's maxEncodedMsgLength).
|
||||
|
||||
### Binary format for sending files
|
||||
|
||||
> Note: Planned to be deprecated. No longer used for file transfer in SimpleX Chat implementation of chat protocol.
|
||||
|
||||
SimpleX Chat clients use separate connections to send files using a binary format. File chunk size send in each message MUST NOT be bigger than 15,780 bytes to fit into 16kb (16384 bytes) transport block.
|
||||
|
||||
The syntax of each message used to send files is defined by the following ABNF notation:
|
||||
@@ -117,7 +136,9 @@ SimpleX Chat Protocol supports the following message types passed in `event` pro
|
||||
- `x.contact` - contact profile and additional data sent as part of contact request to a long-term contact address.
|
||||
- `x.info*` - messages to send, update and de-duplicate contact profiles.
|
||||
- `x.msg.*` - messages to create, update and delete content chat items.
|
||||
- `x.msg.file.descr` - message to transfer XFTP file description.
|
||||
- `x.file.*` - messages to accept and cancel sending files (see files sub-protocol).
|
||||
- `x.direct.del` - message to notify about contact deletion.
|
||||
- `x.grp.*` - messages used to manage groups and group members (see group sub-protocol).
|
||||
- `x.call.*` - messages to invite to WebRTC calls and send signalling messages.
|
||||
- `x.ok` - message sent during connection handshake.
|
||||
@@ -155,6 +176,8 @@ Message content can be one of four types:
|
||||
- `text` - no file attachment is expected for this format, `text` property MUST be non-empty.
|
||||
- `file` - attached file is required, `text` property MAY be empty.
|
||||
- `image` - attached file is required, `text` property MAY be empty.
|
||||
- `video` - attached file is required, `text` property MAY be empty.
|
||||
- `voice` - attached file is required, `text` property MAY be empty.
|
||||
- `link` - no file attachment is expected, `text` property MUST be non-empty. `preview` property contains information about link preview.
|
||||
|
||||
See `/definition/msgContent` in [JTD schema](./simplex-chat.schema.json) for message container format.
|
||||
@@ -181,6 +204,8 @@ File attachment can optionally include connection address to receive the file -
|
||||
|
||||
`x.file.cancel` message is sent to notify the recipient that sending of the file was cancelled. It is sent in response to accepting the file with `x.file.acpt.inv` message. It is sent in the same connection where the file was offered.
|
||||
|
||||
`x.msg.file.descr` message is used to send XFTP file description. File descriptions that don't fit into a single chat protocol message are sent in parts, with messages including part number (`fileDescrPartNo`) and description completion marker (`fileDescrComplete`). Recipient client accumulates description parts and starts file download upon completing file description.
|
||||
|
||||
## Sub-protocol for chat groups
|
||||
|
||||
### Decentralized design for chat groups
|
||||
@@ -197,6 +222,8 @@ The diagram below shows the sequence of messages sent between the users' clients
|
||||
|
||||

|
||||
|
||||
While introduced members establish connection inside group, inviting member forwards messages between them by sending `x.grp.msg.forward` messages. When introduced members finalize connection, they notify inviting member to stop forwarding via `x.grp.mem.con` message.
|
||||
|
||||
### Member roles
|
||||
|
||||
Currently members can have one of three roles - `owner`, `admin` and `member`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role.
|
||||
@@ -207,6 +234,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
||||
|
||||
`x.grp.acpt` message is sent as part of group member connection handshake, only to the inviting user.
|
||||
|
||||
`x.grp.link.inv` message is sent as part of connection handshake to member joining via group link, and contains group profile and initial information about inviting and joining member.
|
||||
|
||||
`x.grp.link.mem` message is sent as part of connection handshake to member joining via group link, and contains remaining information about inviting member.
|
||||
|
||||
`x.grp.mem.new` message is sent by the inviting user to all connected members (and scheduled as pending to all announced but not yet connected members) to announce a new member to the existing members. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with `member` role.
|
||||
|
||||
`x.grp.mem.intro` messages are sent by the inviting user to the invited member, via their group member connection, one message for each existing member. When this message is sent by any other member than the one who invited the recipient it MUST be ignored.
|
||||
@@ -219,6 +250,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
||||
|
||||
`x.grp.mem.role` message is sent to update group member role - it is sent to all members by the member who updated the role of the member referenced in this message. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with role less than `admin`.
|
||||
|
||||
`x.grp.mem.restrict` message is sent to group members to communicate group member restrictions, such as member being blocked for sending messages.
|
||||
|
||||
`x.grp.mem.con` message is sent by members connecting inside group to inviting member, to notify the inviting member they have completed the connection and no longer require forwarding messages between them.
|
||||
|
||||
`x.grp.mem.del` message is sent to delete a member - it is sent to all members by the member who deletes the member referenced in this message. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with `member` role.
|
||||
|
||||
`x.grp.leave` message is sent to all members by the member leaving the group. If the only group `owner` leaves the group, it will not be possible to delete it with `x.grp.del` message - but all members can still leave the group with `x.grp.leave` message and then delete a local copy of the group.
|
||||
@@ -227,6 +262,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
||||
|
||||
`x.grp.info` message is sent to all members by the member who updated group profile. Only group owners can update group profiles. Clients MAY implement some conflict resolution strategy - it is currently not implemented by SimpleX Chat client. This message MUST only be sent by members with `owner` role. Receiving clients MUST ignore this message if it is received from member other than with `owner` role.
|
||||
|
||||
`x.grp.direct.inv` message is sent to a group member to propose establishing a direct connection between members, thus creating a contact with another member.
|
||||
|
||||
`x.grp.msg.forward` message is sent by inviting member to forward messages between introduced members, while they are connecting.
|
||||
|
||||
## Sub-protocol for WebRTC audio/video calls
|
||||
|
||||
This sub-protocol is used to send call invitations and to negotiate end-to-end encryption keys and pass WebRTC signalling information.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.8.0.5
|
||||
version: 5.8.1.0
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
||||
@@ -47,7 +47,7 @@ for ORIG_NAME in "${ORIG_NAMES[@]}"; do
|
||||
#(cd apk && 7z a -r -mx=0 -tzip ../$ORIG_NAME resources.arsc)
|
||||
|
||||
ALL_TOOLS=("$sdk_dir"/build-tools/*/)
|
||||
BIN_DIR="${ALL_TOOLS[1]}"
|
||||
BIN_DIR="${ALL_TOOLS[${#ALL_TOOLS[@]}-1]}"
|
||||
|
||||
"$BIN_DIR"/zipalign -p -f 4 "$ORIG_NAME" "$ORIG_NAME"-2
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."bb1d31e459337f5d2de05f4495ff50d0a8788dff" = "1nbd80anzh2k5hnf330vlcda0zdn2lzlcnjmi3qz2jdkmmzcc3b6";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."8a3b72458f917e9867f4e3640dda0fa1827ff6cf" = "1mmxdaj563kjmlkacxdnq62n6mzw9khampzaqghnk6iiwzdig0qy";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.8.0.5
|
||||
version: 5.8.1.0
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
||||
+58
-39
@@ -104,7 +104,7 @@ import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), Migrati
|
||||
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
|
||||
import Simplex.Messaging.Client (ProxyClientError (..), NetworkConfig (..), defaultNetworkConfig)
|
||||
import Simplex.Messaging.Client (NetworkConfig (..), ProxyClientError (..), defaultNetworkConfig)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
@@ -218,11 +218,12 @@ newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Boo
|
||||
newChatController
|
||||
ChatDatabase {chatStore, agentStore}
|
||||
user
|
||||
cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote}
|
||||
ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, simpleNetCfg, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize}
|
||||
cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote, confirmMigrations}
|
||||
ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, simpleNetCfg, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable, yesToUpMigrations}, deviceName, optFilesFolder, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize}
|
||||
backgroundMode = do
|
||||
let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
|
||||
config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable}
|
||||
confirmMigrations' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations
|
||||
config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable, confirmMigrations = confirmMigrations'}
|
||||
firstTime = dbNew chatStore
|
||||
currentUser <- newTVarIO user
|
||||
currentRemoteHost <- newTVarIO Nothing
|
||||
@@ -762,28 +763,31 @@ processChatCommand' vr = \case
|
||||
_ -> throwChatError CEInvalidChatItemUpdate
|
||||
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
|
||||
CTGroup -> withGroupLock "updateChatItem" chatId $ do
|
||||
Group gInfo@GroupInfo {groupId} ms <- withStore $ \db -> getGroup db vr user chatId
|
||||
Group gInfo@GroupInfo {groupId, membership} ms <- withStore $ \db -> getGroup db vr user chatId
|
||||
assertUserGroupRole gInfo GRAuthor
|
||||
cci <- withStore $ \db -> getGroupCIWithReactions db user gInfo itemId
|
||||
case cci of
|
||||
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do
|
||||
case (ciContent, itemSharedMsgId, editable) of
|
||||
(CISndMsgContent oldMC, Just itemSharedMId, True) -> do
|
||||
let changed = mc /= oldMC
|
||||
if changed || fromMaybe False itemLive
|
||||
then do
|
||||
(SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive))
|
||||
ci' <- withStore' $ \db -> do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
when changed $
|
||||
addInitialAndNewCIVersions db itemId (chatItemTs' ci, oldMC) (currentTs, mc)
|
||||
let edited = itemLive /= Just True
|
||||
updateGroupChatItem db user groupId ci (CISndMsgContent mc) edited live $ Just msgId
|
||||
startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci'
|
||||
pure $ CRChatItemUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci')
|
||||
else pure $ CRChatItemNotChanged user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci)
|
||||
_ -> throwChatError CEInvalidChatItemUpdate
|
||||
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
|
||||
if prohibitedSimplexLinks gInfo membership mc
|
||||
then pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText GFSimplexLinks))
|
||||
else do
|
||||
cci <- withStore $ \db -> getGroupCIWithReactions db user gInfo itemId
|
||||
case cci of
|
||||
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do
|
||||
case (ciContent, itemSharedMsgId, editable) of
|
||||
(CISndMsgContent oldMC, Just itemSharedMId, True) -> do
|
||||
let changed = mc /= oldMC
|
||||
if changed || fromMaybe False itemLive
|
||||
then do
|
||||
(SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive))
|
||||
ci' <- withStore' $ \db -> do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
when changed $
|
||||
addInitialAndNewCIVersions db itemId (chatItemTs' ci, oldMC) (currentTs, mc)
|
||||
let edited = itemLive /= Just True
|
||||
updateGroupChatItem db user groupId ci (CISndMsgContent mc) edited live $ Just msgId
|
||||
startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci'
|
||||
pure $ CRChatItemUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci')
|
||||
else pure $ CRChatItemNotChanged user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci)
|
||||
_ -> throwChatError CEInvalidChatItemUpdate
|
||||
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
|
||||
CTLocal -> do
|
||||
(nf@NoteFolder {noteFolderId}, cci) <- withStore $ \db -> (,) <$> getNoteFolder db user chatId <*> getLocalChatItem db user chatId itemId
|
||||
case cci of
|
||||
@@ -1357,6 +1361,9 @@ processChatCommand' vr = \case
|
||||
pure $ CRNetworkConfig cfg
|
||||
APISetNetworkInfo info -> lift (withAgent' (`setUserNetworkInfo` info)) >> ok_
|
||||
ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_
|
||||
ReconnectServer userId srv -> withUserId userId $ \user -> do
|
||||
lift (withAgent' $ \a -> reconnectSMPServer a (aUserId user) srv)
|
||||
ok_
|
||||
APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of
|
||||
CTDirect -> do
|
||||
ct <- withStore $ \db -> do
|
||||
@@ -2918,9 +2925,17 @@ prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe f -> M
|
||||
prohibitedGroupContent gInfo m mc file_
|
||||
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice
|
||||
| not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles
|
||||
| not (groupFeatureMemberAllowed SGFSimplexLinks m gInfo) && containsFormat isSimplexLink (parseMarkdown $ msgContentText mc) = Just GFSimplexLinks
|
||||
| prohibitedSimplexLinks gInfo m mc = Just GFSimplexLinks
|
||||
| otherwise = Nothing
|
||||
|
||||
prohibitedSimplexLinks :: GroupInfo -> GroupMember -> MsgContent -> Bool
|
||||
prohibitedSimplexLinks gInfo m mc =
|
||||
not (groupFeatureMemberAllowed SGFSimplexLinks m gInfo)
|
||||
&& maybe False (any ftIsSimplexLink) (parseMaybeMarkdownList $ msgContentText mc)
|
||||
where
|
||||
ftIsSimplexLink :: FormattedText -> Bool
|
||||
ftIsSimplexLink FormattedText {format} = maybe False isSimplexLink format
|
||||
|
||||
roundedFDCount :: Int -> Int
|
||||
roundedFDCount n
|
||||
| n <= 0 = 4
|
||||
@@ -3214,7 +3229,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete}
|
||||
forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci
|
||||
throwChatError $ CEFileNotApproved fileId unknownSrvs
|
||||
|
||||
getNetworkConfig :: CM' NetworkConfig
|
||||
getNetworkConfig :: CM' NetworkConfig
|
||||
getNetworkConfig = withAgent' $ liftIO . getNetworkConfig'
|
||||
|
||||
resetRcvCIFileStatus :: User -> FileTransferId -> CIFileStatus 'MDRcv -> CM (Maybe AChatItem)
|
||||
@@ -5223,18 +5238,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
groupMsgToView gInfo ci' {reactions}
|
||||
|
||||
groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM ()
|
||||
groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} brokerTs ttl_ live_ =
|
||||
updateRcvChatItem `catchCINotFound` \_ -> do
|
||||
-- This patches initial sharedMsgId into chat item when locally deleted chat item
|
||||
-- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete).
|
||||
-- Chat item and update message which created it will have different sharedMsgId in this case...
|
||||
let timed_ = rcvGroupCITimed gInfo ttl_
|
||||
ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg (Just sharedMsgId) brokerTs content Nothing timed_ live
|
||||
ci' <- withStore' $ \db -> do
|
||||
createChatItemVersion db (chatItemId' ci) brokerTs mc
|
||||
ci' <- updateGroupChatItem db user groupId ci content True live Nothing
|
||||
blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci'
|
||||
toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
|
||||
groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} brokerTs ttl_ live_
|
||||
| prohibitedSimplexLinks gInfo m mc =
|
||||
messageWarning $ "x.msg.update ignored: feature not allowed " <> groupFeatureNameText GFSimplexLinks
|
||||
| otherwise = do
|
||||
updateRcvChatItem `catchCINotFound` \_ -> do
|
||||
-- This patches initial sharedMsgId into chat item when locally deleted chat item
|
||||
-- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete).
|
||||
-- Chat item and update message which created it will have different sharedMsgId in this case...
|
||||
let timed_ = rcvGroupCITimed gInfo ttl_
|
||||
ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg (Just sharedMsgId) brokerTs content Nothing timed_ live
|
||||
ci' <- withStore' $ \db -> do
|
||||
createChatItemVersion db (chatItemId' ci) brokerTs mc
|
||||
ci' <- updateGroupChatItem db user groupId ci content True live Nothing
|
||||
blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci'
|
||||
toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
|
||||
where
|
||||
content = CIRcvMsgContent mc
|
||||
live = fromMaybe False live_
|
||||
@@ -7383,6 +7401,7 @@ chatCommandP =
|
||||
"/_network " *> (APISetNetworkConfig <$> jsonP),
|
||||
("/network " <|> "/net ") *> (SetNetworkConfig <$> netCfgP),
|
||||
("/network" <|> "/net") $> APIGetNetworkConfig,
|
||||
"/reconnect " *> (ReconnectServer <$> A.decimal <* A.space <*> strP),
|
||||
"/reconnect" $> ReconnectAllServers,
|
||||
"/_settings " *> (APISetChatSettings <$> chatRefP <* A.space <*> jsonP),
|
||||
"/_member settings #" *> (APISetMemberSettings <$> A.decimal <* A.space <*> A.decimal <* A.space <*> jsonP),
|
||||
|
||||
@@ -355,6 +355,7 @@ data ChatCommand
|
||||
| SetNetworkConfig SimpleNetCfg
|
||||
| APISetNetworkInfo UserNetworkInfo
|
||||
| ReconnectAllServers
|
||||
| ReconnectServer UserId SMPServer
|
||||
| APISetChatSettings ChatRef ChatSettings
|
||||
| APISetMemberSettings GroupId GroupMemberId GroupMemberSettings
|
||||
| APIContactInfo ContactId
|
||||
|
||||
@@ -144,10 +144,6 @@ markdownToList (m1 :|: m2) = markdownToList m1 <> markdownToList m2
|
||||
parseMarkdown :: Text -> Markdown
|
||||
parseMarkdown s = fromRight (unmarked s) $ A.parseOnly (markdownP <* A.endOfInput) s
|
||||
|
||||
containsFormat :: (Format -> Bool) -> Markdown -> Bool
|
||||
containsFormat p (Markdown f _) = maybe False p f
|
||||
containsFormat p (m1 :|: m2) = containsFormat p m1 || containsFormat p m2
|
||||
|
||||
isSimplexLink :: Format -> Bool
|
||||
isSimplexLink = \case
|
||||
SimplexLink {} -> True;
|
||||
|
||||
@@ -198,7 +198,8 @@ mobileChatOpts dbFilePrefix =
|
||||
logAgent = Nothing,
|
||||
logFile = Nothing,
|
||||
tbqSize = 1024,
|
||||
highlyAvailable = False
|
||||
highlyAvailable = False,
|
||||
yesToUpMigrations = False
|
||||
},
|
||||
deviceName = Nothing,
|
||||
chatCmd = "",
|
||||
|
||||
@@ -62,7 +62,8 @@ data CoreChatOpts = CoreChatOpts
|
||||
logAgent :: Maybe LogLevel,
|
||||
logFile :: Maybe FilePath,
|
||||
tbqSize :: Natural,
|
||||
highlyAvailable :: Bool
|
||||
highlyAvailable :: Bool,
|
||||
yesToUpMigrations :: Bool
|
||||
}
|
||||
|
||||
data ChatCmdLog = CCLAll | CCLMessages | CCLNone
|
||||
@@ -204,6 +205,12 @@ coreChatOptsP appDir defaultDbFileName = do
|
||||
( long "ha"
|
||||
<> help "Run as a highly available client (this may increase traffic in groups)"
|
||||
)
|
||||
yesToUpMigrations <-
|
||||
switch
|
||||
( long "--yes-migrate"
|
||||
<> short 'y'
|
||||
<> help "Automatically confirm \"up\" database migrations"
|
||||
)
|
||||
pure
|
||||
CoreChatOpts
|
||||
{ dbFilePrefix,
|
||||
@@ -217,7 +224,8 @@ coreChatOptsP appDir defaultDbFileName = do
|
||||
logAgent = if logAgent || logLevel == CLLDebug then Just $ agentLogLevel logLevel else Nothing,
|
||||
logFile,
|
||||
tbqSize,
|
||||
highlyAvailable
|
||||
highlyAvailable,
|
||||
yesToUpMigrations
|
||||
}
|
||||
where
|
||||
useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p
|
||||
|
||||
+2
-1
@@ -101,7 +101,8 @@ testCoreOpts =
|
||||
logAgent = Nothing,
|
||||
logFile = Nothing,
|
||||
tbqSize = 16,
|
||||
highlyAvailable = False
|
||||
highlyAvailable = False,
|
||||
yesToUpMigrations = False
|
||||
}
|
||||
|
||||
getTestOpts :: Bool -> ScrubbedBytes -> ChatOpts
|
||||
|
||||
@@ -2032,10 +2032,19 @@ testGroupPrefsSimplexLinksForRole = testChat3 aliceProfile bobProfile cathProfil
|
||||
threadDelay 1000000
|
||||
bob ##> "/c"
|
||||
inv <- getInvitation bob
|
||||
bob ##> ("#team " <> inv)
|
||||
bob ##> ("#team \"" <> inv <> "\\ntest\"")
|
||||
bob <## "bad chat command: feature not allowed SimpleX links"
|
||||
bob ##> ("/_send #1 json {\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}")
|
||||
bob <## "bad chat command: feature not allowed SimpleX links"
|
||||
(alice </)
|
||||
(cath </)
|
||||
bob `send` ("@alice \"" <> inv <> "\\ntest\"")
|
||||
bob <# ("@alice " <> inv)
|
||||
bob <## "test"
|
||||
alice <# ("bob> " <> inv)
|
||||
alice <## "test"
|
||||
bob ##> "#team <- @alice https://simplex.chat"
|
||||
bob <## "bad chat command: feature not allowed SimpleX links"
|
||||
alice #> ("#team " <> inv)
|
||||
bob <# ("#team alice> " <> inv)
|
||||
cath <# ("#team alice> " <> inv)
|
||||
|
||||
@@ -210,3 +210,10 @@ multilineMarkdownList = describe "multiline markdown" do
|
||||
parseMaybeMarkdownList "http://simplex.chat\ntext 1\ntext 2\nhttp://app.simplex.chat" `shouldBe` Just [uri' "http://simplex.chat", "\ntext 1\ntext 2\n", uri' "http://app.simplex.chat"]
|
||||
it "no markdown" do
|
||||
parseMaybeMarkdownList "not a\nmarkdown" `shouldBe` Nothing
|
||||
let inv = "/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
it "multiline with simplex link" do
|
||||
parseMaybeMarkdownList ("https://simplex.chat" <> inv <> "\ntext")
|
||||
`shouldBe` Just
|
||||
[ FormattedText (Just $ SimplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"]) ("https://simplex.chat" <> inv),
|
||||
"\ntext"
|
||||
]
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"simplex-explained-tab-2-p-1": "لكل اتصال، تستخدم قائمتي انتظار منفصلتين للمُراسلة لإرسال واستلام الرسائل عبر خوادم مختلفة.",
|
||||
"simplex-explained-tab-2-p-2": "تقوم الخوادم بتمرير الرسائل في اتجاه واحد فقط، دون الحصول على الصورة الكاملة لمُحادثات المستخدم أو اتصالاته.",
|
||||
"simplex-explained-tab-3-p-1": "تحتوي الخوادم على بيانات اعتماد مجهولة منفصلة لكل قائمة انتظار، ولا تعرف المستخدمين الذين ينتمون إليهم.",
|
||||
"copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2023",
|
||||
"copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2024",
|
||||
"simplex-chat-protocol": "بروتوكول دردشة SimpleX",
|
||||
"developers": "المطورين",
|
||||
"hero-subheader": "أول نظام مُراسلة<br> دون معرّفات مُستخدم",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"smp-protocol": "СМП Протокол",
|
||||
"chat-protocol": "Чат протокол",
|
||||
"donate": "Дарете",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Проект с отворен код",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Проект с отворен код",
|
||||
"simplex-chat-protocol": "SimpleX Чат протокол",
|
||||
"terminal-cli": "Системна конзола",
|
||||
"terms-and-privacy-policy": "Условия и политика за поверителност",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"smp-protocol": "SMP protokol",
|
||||
"chat-protocol": "Chat protokol",
|
||||
"donate": "Darovat",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Projekt s otevřeným zdrojovým kódem",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Projekt s otevřeným zdrojovým kódem",
|
||||
"simplex-chat-protocol": "SimpleX Chat protokol",
|
||||
"terminal-cli": "Terminálové rozhraní příkazového řádku",
|
||||
"terms-and-privacy-policy": "Podmínky a zásady ochrany osobních údajů",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"smp-protocol": "SMP Protokoll",
|
||||
"chat-bot-example": "Beispiel für einen Chatbot",
|
||||
"donate": "Spenden",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Open-Source Projekt",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Open-Source Projekt",
|
||||
"chat-protocol": "Chat Protokoll",
|
||||
"simplex-chat-protocol": "SimpleX Chat Protokoll",
|
||||
"terminal-cli": "Terminal Kommandozeilen-Schnittstelle",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"smp-protocol": "SMP protocol",
|
||||
"chat-protocol": "Chat protocol",
|
||||
"donate": "Donate",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Open-Source Project",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Open-Source Project",
|
||||
"simplex-chat-protocol": "SimpleX Chat protocol",
|
||||
"terminal-cli": "Terminal CLI",
|
||||
"terms-and-privacy-policy": "Privacy Policy",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"simplex-explained-tab-3-p-2": "El usuario puede mejorar aún más la privacidad de sus metadatos haciendo uso de la red Tor para acceder a los servidores, evitando así la correlación por dirección IP.",
|
||||
"smp-protocol": "Protocolo SMP",
|
||||
"donate": "Donación",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Proyecto de Código Abierto",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Proyecto de Código Abierto",
|
||||
"simplex-chat-protocol": "Protocolo de SimpleX Chat",
|
||||
"terms-and-privacy-policy": "Términos y Política de Privacidad",
|
||||
"hero-header": "Privacidad redefinida",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"smp-protocol": "Protocole SMP",
|
||||
"chat-protocol": "Protocole de chat",
|
||||
"donate": "Faire un don",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Projet Open-Source",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Projet Open-Source",
|
||||
"simplex-chat-protocol": "Protocole SimpleX Chat",
|
||||
"terminal-cli": "Terminal CLI",
|
||||
"terms-and-privacy-policy": "Politique de confidentialité",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"smp-protocol": "SMP protokoll",
|
||||
"chat-protocol": "Csevegés protokoll",
|
||||
"donate": "Támogatás",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Nyílt forráskódú projekt",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Nyílt forráskódú projekt",
|
||||
"simplex-chat-protocol": "SimpleX Chat protokoll",
|
||||
"terminal-cli": "Terminál CLI",
|
||||
"terms-and-privacy-policy": "Adatvédelmi irányelvek",
|
||||
@@ -256,4 +256,4 @@
|
||||
"simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül",
|
||||
"simplex-chat-repo": "SimpleX Chat tároló",
|
||||
"stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"simplex-explained-tab-3-p-1": "I server hanno credenziali anonime separate per ogni coda e non sanno a quali utenti appartengano.",
|
||||
"chat-protocol": "Protocollo di chat",
|
||||
"donate": "Dona",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Progetto Open-Source",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Progetto Open-Source",
|
||||
"simplex-chat-protocol": "Protocollo di SimpleX Chat",
|
||||
"terminal-cli": "Terminale CLI",
|
||||
"terms-and-privacy-policy": "Informativa sulla privacy",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"chat-protocol": "チャットプロトコル",
|
||||
"chat-bot-example": "チャットボットの例",
|
||||
"donate": "寄付",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Open-Source Project",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Open-Source Project",
|
||||
"hero-p-1": "他のアプリにはユーザー ID があります: Signal、Matrix、Session、Briar、Jami、Cwtch など。<br> SimpleX にはありません。<strong>乱数さえもありません</strong>。<br> これにより、プライバシーが大幅に向上します。",
|
||||
"copy-the-command-below-text": "以下のコマンドをコピーしてチャットで使用します:",
|
||||
"simplex-private-card-9-point-1": "各メッセージ キューは、異なる送信アドレスと受信アドレスを使用してメッセージを一方向に渡します。",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"chat-bot-example": "Chatbot voorbeeld",
|
||||
"smp-protocol": "SMP protocol",
|
||||
"donate": "Doneer",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Open-sourceproject",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Open-sourceproject",
|
||||
"simplex-chat-protocol": "SimpleX Chat protocol",
|
||||
"terminal-cli": "Terminal CLI",
|
||||
"terms-and-privacy-policy": "Privacybeleid",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"smp-protocol": "Protokół SMP",
|
||||
"chat-protocol": "Protokół czatu",
|
||||
"donate": "Darowizna",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Projekt Open-Source",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Projekt Open-Source",
|
||||
"simplex-chat-protocol": "Protokół SimpleX Chat",
|
||||
"terminal-cli": "Terminal CLI",
|
||||
"terms-and-privacy-policy": "Polityka prywatności",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"smp-protocol": "Protocolo SMP",
|
||||
"chat-protocol": "Protocolo de bate-papo",
|
||||
"donate": "Doar",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Projeto de Código Livre",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Projeto de Código Livre",
|
||||
"simplex-chat-protocol": "Protocolo Chat SimpleX",
|
||||
"terminal-cli": "CLI Terminal",
|
||||
"hero-header": "Privacidade redefinida",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"copy-the-command-below-text": "скопируйте приведенную ниже команду и используйте ее в чате:",
|
||||
"copyright-label": "© 2020-2023 SimpleX | Проект с открытым исходным кодом",
|
||||
"copyright-label": "© 2020-2024 SimpleX | Проект с открытым исходным кодом",
|
||||
"chat-bot-example": "Пример Чат бота",
|
||||
"simplex-private-card-9-point-1": "Каждая очередь сообщений передает сообщения в одном направлении с разными адресами отправки и получения.",
|
||||
"simplex-private-card-1-point-2": "Криптобокс NaCL в каждой очереди для предотвращения корреляции трафика между очередями сообщений, в случае компрометации TLS.",
|
||||
|
||||
@@ -58,7 +58,7 @@ active_blog: true
|
||||
</div>
|
||||
<div class="p-6 md:py-8 flex-[2.5] flex flex-col">
|
||||
<div>
|
||||
<h1 class="text-grey-black dark:text-white text-lg md:text-xl font-bold ">
|
||||
<h1 class="text-grey-black dark:text-white !text-lg md:!text-xl font-bold ">
|
||||
<a href="{{ blog.url }}">{{ blog.data.title | safe }}</a>
|
||||
</h1>
|
||||
<p class="text-sm text-[#A8B0B4] font-medium mt-2 mb-4 tracking-[0.03em]">
|
||||
|
||||
@@ -11,28 +11,31 @@ metadata:
|
||||
email: chat@simplex.chat
|
||||
---
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{ metadata.url }}">
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ metadata.language }}">
|
||||
<id>{{ metadata.url }}</id>
|
||||
<link type="text/html" rel="alternate" href="{{ metadata.url }}"/>
|
||||
<link type="application/atom+xml" rel="self" href="{{ permalink | absoluteUrl(metadata.url) }}"/>
|
||||
<title>{{ metadata.title }}</title>
|
||||
<subtitle>{{ metadata.subtitle }}</subtitle>
|
||||
<link href="{{ permalink | absoluteUrl(metadata.url) }}" rel="self"/>
|
||||
<link href="{{ metadata.url }}"/>
|
||||
<updated>{{ collections.blogs | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
|
||||
<id>{{ metadata.url }}</id>
|
||||
<author>
|
||||
<name>{{ metadata.author.name }}</name>
|
||||
<email>{{ metadata.author.email }}</email>
|
||||
</author>
|
||||
{%- for blog in collections.blogs | reverse %}
|
||||
{%- if not blog.data.draft %}
|
||||
{%- set absolutePostUrl = blog.url | absoluteUrl(metadata.url) %}
|
||||
{%- set absolutePostUrl = blog.data.permalink | absoluteUrl(metadata.url) %}
|
||||
<entry>
|
||||
<id>{{ blog.data.permalink | absoluteUrl(metadata.url) }}</id>
|
||||
<!-- <updated>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</updated> -->
|
||||
<updated>{{ blog.data.date | dateToRfc3339 }}</updated>
|
||||
<link rel="alternate" type="text/html" href="{{ absolutePostUrl }}"/>
|
||||
<title>{{ blog.data.title }}</title>
|
||||
<link href="{{ absolutePostUrl }}"/>
|
||||
{# <updated>{{ blog.date | dateToRfc3339 }}</updated> #}
|
||||
<updated>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</updated>
|
||||
<id>{{ absolutePostUrl }}</id>
|
||||
<content xml:lang="{{ metadata.language }}" type="html">{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
|
||||
{# <content xml:lang="{{ metadata.language }}" type="html">{{ blog.templateContent | striptags | truncate(200) }}</content> #}
|
||||
<content type="html">{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
|
||||
<author>
|
||||
<name>{{ metadata.author.name }}</name>
|
||||
<email>{{ metadata.author.email }}</email>
|
||||
</author>
|
||||
</entry>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
|
||||
@@ -26,8 +26,8 @@ metadata:
|
||||
<link>{{ absolutePostUrl }}</link>
|
||||
<description>{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description>
|
||||
{# <description>{{ blog.templateContent | striptags | truncate(200) }}</description> #}
|
||||
{# <pubDate>{{ blog.data.date | dateToRfc822 }}</pubDate> #}
|
||||
<pubDate>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</pubDate>
|
||||
<pubDate>{{ blog.data.date | dateToRfc822 }}</pubDate>
|
||||
{# <pubDate>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</pubDate> #}
|
||||
<dc:creator>{{ metadata.author.name }}</dc:creator>
|
||||
<guid>{{ absolutePostUrl }}</guid>
|
||||
</item>
|
||||
|
||||
@@ -46,6 +46,10 @@ img{
|
||||
-ms-user-select: none; /* For Internet Explorer and Edge */
|
||||
}
|
||||
|
||||
a{
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* #comparison::before {
|
||||
display: block;
|
||||
content: " ";
|
||||
|
||||
Reference in New Issue
Block a user