core: add peer type "agent" (#7738)

* add agent peer type

* bot types

* agent app icons

* refactor

* smaller image

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
EvgenyandEvgeny @ SimpleX Chat authored and GitHub committed 2026-10-17 19:23:53 +01:00
1 parent 9a90d8e230
commit 77f9f4a464
29 files changed
+101 -45

No files matched your search

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "agent.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="124.5 -878.5 711 797" width="21.41px" fill="#000000"><path d="M564.12,-698.86 C564.12,-652.47 526.51,-614.86 480.12,-614.86 C433.73,-614.86 396.12,-652.47 396.12,-698.86 C396.12,-745.25 433.73,-782.86 480.12,-782.86 C526.51,-782.86 564.12,-745.25 564.12,-698.86 Z M373.01,-371.05 C373.01,-324.66 335.4,-287.05 289.01,-287.05 C242.62,-287.05 205.01,-324.66 205.01,-371.05 C205.01,-417.44 242.62,-455.05 289.01,-455.05 C335.4,-455.05 373.01,-417.44 373.01,-371.05 Z M755.03,-371.07 C755.03,-324.68 717.42,-287.07 671.03,-287.07 C624.63,-287.07 587.03,-324.68 587.03,-371.07 C587.03,-417.46 624.63,-455.07 671.03,-455.07 C717.42,-455.07 755.03,-417.46 755.03,-371.07 Z"/></svg>

After

Width:  |  Height:  |  Size: 737 B

@@ -50,7 +50,7 @@ struct CIChatLinkHeader: View {
HStack(alignment: .top) {
ProfileImage(
imageStr: chatLink.image,
iconName: chatLink.iconName,
icon: Image(systemName: chatLink.iconName),
size: 44,
color: Color(uiColor: .tertiaryLabel)
)
@@ -115,7 +115,7 @@ struct CIGroupInvitationView: View {
return HStack(alignment: .top) {
ProfileImage(
imageStr: groupInvitation.groupProfile.image,
iconName: "person.2.circle.fill",
icon: Image(systemName: "person.2.circle.fill"),
size: 44,
color: color
)
+2 -2
View File
@@ -1968,7 +1968,7 @@ struct ChatView: View {
.padding(.trailing, 12)
}
HStack(alignment: .top, spacing: 10) {
ProfileImage(imageStr: groupInfo.image, iconName: groupInfo.chatIconName, size: memberImageSize, backgroundColor: theme.colors.background)
ProfileImage(imageStr: groupInfo.image, icon: Image(systemName: groupInfo.chatIconName), size: memberImageSize, backgroundColor: theme.colors.background)
.simultaneousGesture(TapGesture().onEnded {
showChatInfoSheet = true
})
@@ -2052,7 +2052,7 @@ struct ChatView: View {
}
HStack(alignment: .top, spacing: 10) {
if ci.meta.showGroupAsSender {
ProfileImage(imageStr: groupInfo.image, iconName: groupInfo.chatIconName, size: memberImageSize, backgroundColor: theme.colors.background)
ProfileImage(imageStr: groupInfo.image, icon: Image(systemName: groupInfo.chatIconName), size: memberImageSize, backgroundColor: theme.colors.background)
.simultaneousGesture(TapGesture().onEnded {
showChatInfoSheet = true
})
@@ -11,7 +11,7 @@ struct ComposeChatLinkView: View {
HStack(alignment: .center, spacing: 8) {
ProfileImage(
imageStr: chatLink.image,
iconName: chatLink.iconName,
icon: Image(systemName: chatLink.iconName),
size: 44
)
.padding(.leading, 12)
@@ -37,7 +37,7 @@ struct GroupProfileView: View {
var body: some View {
List {
EditProfileImage(profileImage: $groupProfile.image, iconName: groupInfo.chatIconName, showChooseSource: $showChooseSource)
EditProfileImage(profileImage: $groupProfile.image, icon: Image(systemName: groupInfo.chatIconName), showChooseSource: $showChooseSource)
.if(!focusDisplayName) { $0.padding(.top) }
Section {
@@ -444,7 +444,7 @@ struct ChatPreviewView: View {
smallContentPreview(size: dynamicMediaSize, borderColor: chatLink.image != nil ? .secondary : .clear) {
ProfileImage(
imageStr: chatLink.image,
iconName: chatLink.iconName,
icon: Image(systemName: chatLink.iconName),
size: dynamicMediaSize,
color: Color(uiColor: .tertiaryLabel)
)
@@ -19,7 +19,7 @@ struct ChatInfoImage: View {
let iconColor = if case .local = chat.chatInfo { theme.appColors.primaryVariant2 } else { color }
return ProfileImage(
imageStr: chat.chatInfo.image,
iconName: chatIconName(chat.chatInfo),
icon: chatIcon(chat.chatInfo),
size: size,
color: iconColor
)
@@ -12,7 +12,7 @@ import SimpleXChat
struct ProfileImage: View {
@EnvironmentObject var theme: AppTheme
var imageStr: String? = nil
var iconName: String = "person.crop.circle.fill"
var icon: Image = Image(systemName: "person.crop.circle.fill")
var size: CGFloat
var color = Color(uiColor: .tertiarySystemGroupedBackground)
var backgroundColor: Color? = nil
@@ -24,7 +24,7 @@ struct ProfileImage: View {
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred)
} else {
let c = color.asAnotherColorFromSecondaryVariant(theme)
Image(systemName: iconName)
icon
.resizable()
.foregroundColor(c)
.scaledToFit()
@@ -50,7 +50,7 @@ struct AddChannelView: View {
Spacer(minLength: 0)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128)
ProfileImage(imageStr: profile.image, icon: Image(systemName: "antenna.radiowaves.left.and.right.circle.fill"), size: 128)
if profile.image != nil {
Button {
profile.image = nil
@@ -285,7 +285,7 @@ struct AddChannelView: View {
let total = groupRelays.count
return List {
Group {
ProfileImage(imageStr: gInfo.groupProfile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128)
ProfileImage(imageStr: gInfo.groupProfile.image, icon: Image(systemName: "antenna.radiowaves.left.and.right.circle.fill"), size: 128)
.frame(maxWidth: .infinity, alignment: .center)
Text(gInfo.groupProfile.displayName)
@@ -71,7 +71,7 @@ struct AddGroupView: View {
Spacer(minLength: 0)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: profile.image, iconName: "person.2.circle.fill", size: 128)
ProfileImage(imageStr: profile.image, icon: Image(systemName: "person.2.circle.fill"), size: 128)
if profile.image != nil {
Button {
profile.image = nil
@@ -1129,11 +1129,9 @@ private func showPrepareContactAlert(
profileImage:
ProfileImage(
imageStr: contactShortLinkData.profile.image,
iconName: contactShortLinkData.business
? "briefcase.circle.fill"
: contactShortLinkData.profile.peerType == .bot
? "cube.fill"
: "person.crop.circle.fill",
icon: contactShortLinkData.business
? Image(systemName: "briefcase.circle.fill")
: peerTypeIcon(contactShortLinkData.profile.peerType),
size: alertProfileImageSize
),
profileBadge: contactShortLinkData.localBadge,
@@ -1186,9 +1184,9 @@ private func showPrepareGroupAlert(
profileImage:
ProfileImage(
imageStr: groupShortLinkData.groupProfile.image,
iconName: isChannel
icon: Image(systemName: isChannel
? "antenna.radiowaves.left.and.right.circle.fill"
: "person.2.circle.fill",
: "person.2.circle.fill"),
size: alertProfileImageSize
),
theme: theme,
@@ -1240,7 +1238,7 @@ private func showOpenKnownContactAlert(
profileImage:
ProfileImage(
imageStr: contact.profile.image,
iconName: contact.chatIconName,
icon: contact.chatIcon,
size: alertProfileImageSize
),
profileBadge: contact.active ? contact.profile.localBadge : nil,
@@ -1288,7 +1286,7 @@ private func showOpenKnownGroupAlert(
profileImage:
ProfileImage(
imageStr: groupInfo.groupProfile.image,
iconName: groupInfo.chatIconName,
icon: Image(systemName: groupInfo.chatIconName),
size: alertProfileImageSize
),
theme: theme,
@@ -1605,7 +1603,7 @@ func planAndConnect(
profileImage:
ProfileImage(
imageStr: groupSLinkData.groupProfile.image,
iconName: "antenna.radiowaves.left.and.right.circle.fill",
icon: Image(systemName: "antenna.radiowaves.left.and.right.circle.fill"),
size: alertProfileImageSize
),
theme: theme,
@@ -1632,7 +1630,7 @@ func planAndConnect(
profileImage:
ProfileImage(
imageStr: groupSLinkData.groupProfile.image,
iconName: "person.2.circle.fill",
icon: Image(systemName: "person.2.circle.fill"),
size: alertProfileImageSize
),
theme: theme,
@@ -29,7 +29,7 @@ struct UserProfile: View {
var body: some View {
List {
EditProfileImage(profileImage: $profile.image, iconName: "person.crop.circle.fill", showChooseSource: $showChooseSource)
EditProfileImage(profileImage: $profile.image, icon: Image(systemName: "person.crop.circle.fill"), showChooseSource: $showChooseSource)
.padding(.top)
Section {
@@ -221,7 +221,7 @@ struct EditProfileImage: View {
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
@Binding var profileImage: String?
var iconName: String
var icon: Image
@Binding var showChooseSource: Bool
var body: some View {
@@ -237,7 +237,7 @@ struct EditProfileImage: View {
}
} else {
ZStack(alignment: .center) {
ProfileImage(imageStr: profileImage, iconName: iconName, size: 160)
ProfileImage(imageStr: profileImage, icon: icon, size: 160)
editImageButton { showChooseSource = true }
}
}
+3 -3
View File
@@ -25,7 +25,7 @@ struct ShareView: View {
HStack {
profileImage(
chatInfoId: chat.chatInfo.id,
iconName: chatIconName(chat.chatInfo),
icon: chatIcon(chat.chatInfo),
size: 30
)
Text(chat.chatInfo.displayName).foregroundStyle(
@@ -209,11 +209,11 @@ struct ShareView: View {
.background(Material.ultraThin)
}
@ViewBuilder private func profileImage(chatInfoId: ChatInfo.ID, iconName: String, size: Double) -> some View {
@ViewBuilder private func profileImage(chatInfoId: ChatInfo.ID, icon: Image, size: Double) -> some View {
if let uiImage = model.profileImages[chatInfoId] {
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius)
} else {
Image(systemName: iconName)
icon
.resizable()
.foregroundColor(Color(uiColor: .tertiaryLabel))
.frame(width: size, height: size)
@@ -271,6 +271,8 @@
E5AEC0AF2F91A73500270665 /* ComposeChatLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */; };
E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; };
E5C0BBE92F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; };
E5C0BBEB2F82B45500EA7527 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */; };
E5C0BBEC2F82B45500EA7527 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */; };
E5DBF1932F88169800E1D7FD /* ConnectBannerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */; };
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
@@ -662,6 +664,7 @@
E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatLinkHeader.swift; sourceTree = "<group>"; };
E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeChatLinkView.swift; sourceTree = "<group>"; };
E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SimpleXAssets.xcassets; sourceTree = "<group>"; };
E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SharedAssets.xcassets; sourceTree = "<group>"; };
E5C0BBFD2F82BBC000EA7527 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
E5C0BBFE2F82BBC900EA7527 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectBannerCard.swift; sourceTree = "<group>"; };
@@ -953,6 +956,7 @@
5C2E260D27A30E2400F70299 /* Views */,
5CA059C5279559F40002BEB4 /* Assets.xcassets */,
E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */,
E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */,
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */,
5C13730C2815740A00F43030 /* DebugJSON.playground */,
);
@@ -1506,6 +1510,7 @@
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */,
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */,
E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */,
E5C0BBEB2F82B45500EA7527 /* SharedAssets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1538,6 +1543,7 @@
files = (
E5DCF9712C590272007928CC /* Localizable.strings in Resources */,
E5C0BBE92F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */,
E5C0BBEC2F82B45500EA7527 /* SharedAssets.xcassets in Resources */,
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
+13 -2
View File
@@ -228,6 +228,7 @@ public enum ChatPeerType: Hashable {
case human
case bot
case business
case agent
case unknown(String)
public var text: String {
@@ -235,6 +236,7 @@ public enum ChatPeerType: Hashable {
case .human: "human"
case .bot: "bot"
case .business: "business"
case .agent: "agent"
case let .unknown(s): s
}
}
@@ -246,6 +248,7 @@ extension ChatPeerType: Codable {
case "human": self = .human
case "bot": self = .bot
case "business": self = .business
case "agent": self = .agent
case let s: self = .unknown(s)
}
}
@@ -256,6 +259,14 @@ extension ChatPeerType: Codable {
}
}
public func peerTypeIcon(_ peerType: ChatPeerType?) -> Image {
switch peerType {
case .bot: Image(systemName: "cube.fill")
case .agent: Image("agent")
default: Image(systemName: "person.crop.circle.fill")
}
}
// Supporter badge. The credential/proof bytes stay core-side; the UI only sees the disclosed type + status.
// Unknown types keep their string so a verified badge's real name can be shown, while the icon falls back to supporter.
public enum BadgeType: Hashable {
@@ -2532,8 +2543,8 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
activeConn?.customUserProfileId != nil
}
public var chatIconName: String {
isBot ? "cube.fill" : "person.crop.circle.fill"
public var chatIcon: Image {
peerTypeIcon(profile.peerType)
}
public func allowsFeature(_ feature: ChatFeature) -> Bool {
+7 -6
View File
@@ -7,6 +7,7 @@
//
import Foundation
import SwiftUI
public protocol ChatLike {
var chatInfo: ChatInfo { get }
@@ -91,13 +92,13 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
}
}
public func chatIconName(_ cInfo: ChatInfo) -> String {
public func chatIcon(_ cInfo: ChatInfo) -> Image {
switch cInfo {
case let .direct(contact): contact.chatIconName
case let .group(groupInfo, _): groupInfo.chatIconName
case .local: "folder.circle.fill"
case .contactRequest: "person.crop.circle.fill"
default: "circle.fill"
case let .direct(contact): contact.chatIcon
case let .group(groupInfo, _): Image(systemName: groupInfo.chatIconName)
case .local: Image(systemName: "folder.circle.fill")
case .contactRequest: Image(systemName: "person.crop.circle.fill")
default: Image(systemName: "circle.fill")
}
}
@@ -1954,7 +1954,7 @@ data class Contact(
activeConn?.customUserProfileId != null
val chatIconName: ImageResource get() =
if (isBot) MR.images.ic_cube else MR.images.ic_account_circle_filled
peerTypeIcon(profile.peerType)
fun allowsFeature(feature: ChatFeature): Boolean = when (feature) {
ChatFeature.TimedMessages -> mergedPreferences.timedMessages.contactPreference.allow != FeatureAllowed.NO
@@ -2165,6 +2165,7 @@ sealed class ChatPeerType {
@Serializable @SerialName("human") object Human: ChatPeerType()
@Serializable @SerialName("bot") object Bot: ChatPeerType()
@Serializable @SerialName("business") object Business: ChatPeerType()
@Serializable @SerialName("agent") object Agent: ChatPeerType()
@Serializable @SerialName("unknown") data class Unknown(val type: String): ChatPeerType()
val text: String
@@ -2172,6 +2173,7 @@ sealed class ChatPeerType {
is Human -> "human"
is Bot -> "bot"
is Business -> "business"
is Agent -> "agent"
is Unknown -> type
}
}
@@ -2183,11 +2185,19 @@ object ChatPeerTypeSerializer : KSerializer<ChatPeerType> {
"human" -> ChatPeerType.Human
"bot" -> ChatPeerType.Bot
"business" -> ChatPeerType.Business
"agent" -> ChatPeerType.Agent
else -> ChatPeerType.Unknown(v)
}
override fun serialize(encoder: Encoder, value: ChatPeerType) = encoder.encodeString(value.text)
}
fun peerTypeIcon(peerType: ChatPeerType?): ImageResource =
when (peerType) {
ChatPeerType.Bot -> MR.images.ic_cube
ChatPeerType.Agent -> MR.images.ic_agent
else -> MR.images.ic_account_circle_filled
}
// Supporter badge. The credential/proof bytes stay core-side; the UI only sees the disclosed type + status.
// Unknown types keep their string so a verified badge's real name can be shown, while the icon falls back to supporter.
@Serializable(with = BadgeTypeSerializer::class)
@@ -8139,7 +8139,7 @@ sealed class StoreError {
is UserChatRelayNotFound -> "userChatRelayNotFound $chatRelayId"
is GroupRelayNotFound -> "groupRelayNotFound $groupRelayId"
is GroupRelayNotFoundByMemberId -> "groupRelayNotFoundByMemberId $groupMemberId"
is DuplicateGroupMessage -> "duplicateGroupMessage $groupId $sharedMsgId $authorGroupMemberId $authorGroupMemberId"
is DuplicateGroupMessage -> "duplicateGroupMessage $groupId $sharedMsgId $authorGroupMemberId $forwardedByGroupMemberId"
is RemoteHostNotFound -> "remoteHostNotFound $remoteHostId"
is RemoteHostUnknown -> "remoteHostUnknown"
is RemoteHostDuplicateCA -> "remoteHostDuplicateCA"
@@ -717,8 +717,7 @@ fun showPrepareContactAlert(
image = contactShortLinkData.profile.image,
icon =
if (contactShortLinkData.business) MR.images.ic_work_filled_padded
else if (contactShortLinkData.profile.peerType == ChatPeerType.Bot) MR.images.ic_cube
else MR.images.ic_account_circle_filled
else peerTypeIcon(contactShortLinkData.profile.peerType)
)
},
profileBadge = if (contactShortLinkData.localBadge?.status == BadgeStatus.ExpiredOld) null else contactShortLinkData.localBadge,
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="22px" viewBox="0 -960 960 960" width="22px" fill="#000000"><path d="M564.12,-698.86 C564.12,-652.47 526.51,-614.86 480.12,-614.86 C433.73,-614.86 396.12,-652.47 396.12,-698.86 C396.12,-745.25 433.73,-782.86 480.12,-782.86 C526.51,-782.86 564.12,-745.25 564.12,-698.86 Z M373.01,-371.05 C373.01,-324.66 335.4,-287.05 289.01,-287.05 C242.62,-287.05 205.01,-324.66 205.01,-371.05 C205.01,-417.44 242.62,-455.05 289.01,-455.05 C335.4,-455.05 373.01,-417.44 373.01,-371.05 Z M755.03,-371.07 C755.03,-324.68 717.42,-287.07 671.03,-287.07 C624.63,-287.07 587.03,-324.68 587.03,-371.07 C587.03,-417.46 624.63,-455.07 671.03,-455.07 C717.42,-455.07 755.03,-417.46 755.03,-371.07 Z"/></svg>

After

Width:  |  Height:  |  Size: 728 B

+1
View File
@@ -1545,6 +1545,7 @@ Search:
- "human"
- "bot"
- "business"
- "agent"
---
+1 -1
View File
@@ -25,7 +25,7 @@
"metadata": {"comment": "profile bio (160 characters)"}
},
"contactLink": {"ref": "connReqUri"},
"peerType": {"enum": ["human", "bot"]},
"peerType": {"enum": ["human", "bot", "agent"]},
"preferences": {
"optionalProperties": {
"commands": {
@@ -1796,6 +1796,7 @@ export enum ChatPeerType {
Human = "human",
Bot = "bot",
Business = "business",
Agent = "agent",
}
// Used in API commands. Chat scope can only be passed with groups.
@@ -1247,7 +1247,7 @@ ChatListQuery = ChatListQuery_filters | ChatListQuery_search
ChatListQuery_Tag = Literal["filters", "search"]
ChatPeerType = Literal["human", "bot", "business"]
ChatPeerType = Literal["human", "bot", "business", "agent"]
# Used in API commands. Chat scope can only be passed with groups.
+3 -1
View File
@@ -749,7 +749,7 @@ data Profile = Profile
}
deriving (Eq, Show)
data ChatPeerType = CPTHuman | CPTBot | CPTBusiness | CPTUnknown Text
data ChatPeerType = CPTHuman | CPTBot | CPTBusiness | CPTAgent | CPTUnknown Text
deriving (Eq, Show)
instance FromJSON ChatPeerType where
@@ -768,11 +768,13 @@ instance TextEncoding ChatPeerType where
"human" -> CPTHuman
"bot" -> CPTBot
"business" -> CPTBusiness
"agent" -> CPTAgent
tag -> CPTUnknown tag
textEncode = \case
CPTHuman -> "human"
CPTBot -> "bot"
CPTBusiness -> "business"
CPTAgent -> "agent"
CPTUnknown tag -> tag
profileFromName :: ContactName -> Profile
+3
View File
@@ -332,6 +332,9 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
it "x.info with empty full name" $
"{\"v\":\"9\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
#==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing} Nothing
it "x.info with agent peer type" $
"{\"v\":\"9\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"peerType\":\"agent\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
#==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTAgent, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing} Nothing
it "x.contact with xContactId" $
"{\"v\":\"9\",\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
#==# XContact testProfile Nothing (Just $ XContactId "\1\2\3\4") Nothing Nothing