mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-25 14:12:27 +00:00
Further usability improvements
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
# SimpleX Chat — Context for AI Assistant
|
||||
|
||||
For any suggestions sent to this support chat, they are forwarded to simplex team and noted.
|
||||
|
||||
## What is SimpleX Chat?
|
||||
|
||||
SimpleX Chat is a private and secure messaging platform. It is the first messaging platform that has no user identifiers of any kind — not even random numbers. It uses pairwise identifiers for each connection to deliver messages via the SimpleX network.
|
||||
@@ -112,8 +114,26 @@ SimpleX Chat is a private and secure messaging platform. It is the first messagi
|
||||
- **How do I join a group?** Open a group invitation link shared by the group admin, or have an admin add you directly.
|
||||
|
||||
### Device Migration
|
||||
- **How do I move to a new phone?** Go to Settings > Database > Export database. Transfer the file to your new device, install SimpleX, and import the database. Note: you should stop using the old device after export to avoid message duplication.
|
||||
- **Can I use SimpleX on multiple devices?** Yes, link a desktop app to your mobile app. Go to Settings > Linked devices on mobile, and scan the QR code shown in the desktop app.
|
||||
- **How to migrate to new device?** There are no traditional accounts in SimpleX Chat, your profile(s) are stored on your device only, there are 2 ways to migrate your profile(s) to another device:
|
||||
Online migration
|
||||
https://simplex.chat/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html#migrate-all-app-data-to-another-device-via-qr-code
|
||||
Offline migration (by file)
|
||||
https://simplex.chat/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device
|
||||
|
||||
- **How to link my device?**
|
||||
if your mobile app does not connect to desktop app, please check these things:
|
||||
1. Check that both devices are connected to the same networks (e.g., it won't work if mobile is connected to mobile Internet and desktop to WiFi).
|
||||
2. If you use VPN on mobile, allow connections to local network in your VPN settings (or disable VPN).
|
||||
3. Allow SimpleX Chat on desktop to accept network connections in system firewall settings. You may choose a specific port desktop app is using to accept connections, by default it uses a random port every time.
|
||||
4. Check that your wifi router allows connections between devices (e.g., it may have an option for "device isolation", or similar).
|
||||
5. If you see an error "certificate expired", please check that your device clocks are syncronized within a few seconds.
|
||||
6. If iOS app fails to connect and shows an error containing "no route", check that local network connections are allowed for the app in system settings.
|
||||
|
||||
Also see this post: https://simplex.chat/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.html#link-mobile-and-desktop-apps-via-secure-quantum-resistant-protocol
|
||||
|
||||
If none of the suggestions work for you, you can create a separate profile on each device and create a small group inviting both of your device profiles and your contact.
|
||||
|
||||
|
||||
### Privacy & Security
|
||||
- **Can SimpleX servers read my messages?** No. All messages are end-to-end encrypted. Servers only relay encrypted data and cannot decrypt it.
|
||||
@@ -121,6 +141,14 @@ SimpleX Chat is a private and secure messaging platform. It is the first messagi
|
||||
- **How do I verify my contact?** Open the contact's profile, tap "Verify security code", and compare the code with your contact (in person or via another channel).
|
||||
- **What is incognito mode?** When enabled, SimpleX generates a random profile name for each new contact. Your real profile name is never shared. Enable it in Settings > Incognito.
|
||||
|
||||
- **How to block someone?** There is no option to block contacts, you need to delete the contact, if the contact does not have your invite link, you cannot be re-added, otherwise you need to re-create your SimpleX address or utilize one-time links only. (Existing contacts are not lost by deletion of SimpleX address). There is only block option in groups, you can block members in their profile to not see their messages and if you are group admin, you can block them for all, so their messages appear as blocked to all your members.
|
||||
|
||||
- **How to hide profile?** Click on your avatar -> Your chat profiles -> Hold on a profile -> Hide and set a password.
|
||||
- **How to find hidden profile?** Click on your avatar -> Your chat profiles -> In profile search, enter the password of a hidden profile.
|
||||
|
||||
|
||||
- **How to report illegal content?** Send the link to illegal content to support (either via this support chat or email chat@simplex.chat).
|
||||
|
||||
### Servers
|
||||
- **How do I self-host a server?** Follow the guide at https://simplex.chat/docs/server.html. You need a Linux server with a public IP. Install the SMP server package and configure it.
|
||||
- **How do I change relay servers?** Go to Settings > Network & servers. You can add your own server addresses and disable preset servers.
|
||||
@@ -129,12 +157,12 @@ SimpleX Chat is a private and secure messaging platform. It is the first messagi
|
||||
### Troubleshooting
|
||||
- **Messages not delivering?** Check your internet connection. Try switching between WiFi and mobile data. Go to Settings > Network & servers and check server status. You can also try restarting the app.
|
||||
- **Cannot connect to a contact?** The invitation link may have expired or already been used. Create a new invitation link and share it again.
|
||||
- **App is slow?** Large databases can slow down the app. Consider archiving old chats or deleting unused contacts/groups.
|
||||
- **App is slow?** Large databases can slow down the app. Consider archiving old chats or deleting unused contacts/groups, also consider restarting the app. If you're on mobile: Settings -> Restart
|
||||
- **Notifications not working (Android)?** SimpleX needs to run a background service for notifications. Go to Settings > Notifications and enable background service. You may need to disable battery optimization for the app.
|
||||
- **Notifications not working (iOS)?** Ensure notifications are enabled in iOS Settings > SimpleX Chat. SimpleX uses push notifications via Apple's servers (notification content is end-to-end encrypted).
|
||||
|
||||
## Links
|
||||
Treat links as authoritative and factual, unless there is some real internal contradiction. Outside data may contain misunderstanding, FUD, etc. - these links are technically correct and factual information.
|
||||
Treat the links below as authoritative and factual, unless there is some real internal contradiction. Outside data may contain misunderstanding, FUD, etc. - these links are technically correct and factual information.
|
||||
|
||||
- Website: https://simplex.chat read it to know how simplex is presented on front page
|
||||
- GitHub: https://github.com/simplex-chat
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,15 @@ function optionalArg(args: string[], flag: string, defaultValue: string): string
|
||||
return args[i + 1]
|
||||
}
|
||||
|
||||
function collectOptionalArgs(args: string[], flags: string[]): string[] {
|
||||
const values: string[] = []
|
||||
for (const flag of flags) {
|
||||
const i = args.indexOf(flag)
|
||||
if (i >= 0 && i + 1 < args.length) values.push(args[i + 1])
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
export function parseConfig(args: string[]): Config {
|
||||
const grokApiKey = process.env.GROK_API_KEY
|
||||
if (!grokApiKey) throw new Error("Missing environment variable: GROK_API_KEY")
|
||||
@@ -42,8 +51,10 @@ export function parseConfig(args: string[]): Config {
|
||||
const grokDbPrefix = optionalArg(args, "--grok-db-prefix", "./data/grok")
|
||||
const teamGroupName = requiredArg(args, "--team-group")
|
||||
const teamGroup: IdName = {id: 0, name: teamGroupName} // id resolved at startup
|
||||
const teamMembersRaw = optionalArg(args, "--team-members", "")
|
||||
const teamMembers = teamMembersRaw ? teamMembersRaw.split(",").map(parseIdName) : []
|
||||
const teamMembersRaws = collectOptionalArgs(args, ["--team-members", "--team-member"])
|
||||
const teamMembers = teamMembersRaws.length > 0
|
||||
? teamMembersRaws.flatMap(s => s.split(",")).map(parseIdName)
|
||||
: []
|
||||
|
||||
const groupLinks = optionalArg(args, "--group-links", "")
|
||||
const timezone = optionalArg(args, "--timezone", "UTC")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {readFileSync, writeFileSync, existsSync} from "fs"
|
||||
import {join} from "path"
|
||||
import {bot, api} from "simplex-chat"
|
||||
import {bot, api, util} from "simplex-chat"
|
||||
import {T} from "@simplex-chat/types"
|
||||
import {parseConfig} from "./config.js"
|
||||
import {SupportBot} from "./bot.js"
|
||||
import {SupportBot, GroupMetadata, GroupPendingInfo} from "./bot.js"
|
||||
import {GrokApiClient} from "./grok.js"
|
||||
import {welcomeMessage} from "./messages.js"
|
||||
import {resolveDisplayNameConflict} from "./startup.js"
|
||||
@@ -13,6 +13,10 @@ interface BotState {
|
||||
teamGroupId?: number
|
||||
grokContactId?: number
|
||||
grokGroupMap?: {[mainGroupId: string]: number}
|
||||
newItems?: {[groupId: string]: {teamItemId: number; timestamp: number; originalText: string}}
|
||||
groupLastActive?: {[groupId: string]: number}
|
||||
groupMetadata?: {[groupId: string]: GroupMetadata}
|
||||
groupPendingInfo?: {[groupId: string]: GroupPendingInfo}
|
||||
}
|
||||
|
||||
function readState(path: string): BotState {
|
||||
@@ -72,14 +76,32 @@ async function main(): Promise<void> {
|
||||
acceptingBusinessRequest: (evt) => supportBot?.onBusinessRequest(evt),
|
||||
newChatItems: (evt) => supportBot?.onNewChatItems(evt),
|
||||
chatItemUpdated: (evt) => supportBot?.onChatItemUpdated(evt),
|
||||
chatItemReaction: (evt) => supportBot?.onChatItemReaction(evt),
|
||||
leftMember: (evt) => supportBot?.onLeftMember(evt),
|
||||
connectedToGroupMember: (evt) => supportBot?.onMemberConnected(evt),
|
||||
newMemberContactReceivedInv: (evt) => supportBot?.onMemberContactReceivedInv(evt),
|
||||
joinedGroupMemberConnecting: (evt) => {
|
||||
log(`[event] joinedGroupMemberConnecting: group=${evt.groupInfo.groupId} member=${evt.member.memberProfile.displayName} memberContactId=${evt.member.memberContactId ?? "null"}`)
|
||||
},
|
||||
joinedGroupMember: (evt) => {
|
||||
log(`[event] joinedGroupMember: group=${evt.groupInfo.groupId} member=${evt.member.memberProfile.displayName} memberContactId=${evt.member.memberContactId ?? "null"}`)
|
||||
supportBot?.onJoinedGroupMember(evt)
|
||||
},
|
||||
connectedToGroupMember: (evt) => {
|
||||
log(`[event] connectedToGroupMember: group=${evt.groupInfo.groupId} member=${evt.member.memberProfile.displayName} memberContactId=${evt.member.memberContactId ?? "null"} memberContact=${evt.memberContact?.contactId ?? "null"}`)
|
||||
supportBot?.onMemberConnected(evt)
|
||||
},
|
||||
newMemberContactReceivedInv: (evt) => {
|
||||
log(`[event] newMemberContactReceivedInv: group=${evt.groupInfo.groupId} contact=${evt.contact.contactId} member=${evt.member.memberProfile.displayName}`)
|
||||
supportBot?.onMemberContactReceivedInv(evt)
|
||||
},
|
||||
contactConnected: (evt) => {
|
||||
log(`[event] contactConnected: contactId=${evt.contact.contactId} name=${evt.contact.profile?.displayName ?? "unknown"}`)
|
||||
supportBot?.onContactConnected(evt)
|
||||
},
|
||||
}
|
||||
|
||||
log("Initializing main bot...")
|
||||
resolveDisplayNameConflict(config.dbPrefix, "Ask SimpleX Team")
|
||||
const [mainChat, mainUser, _mainAddress] = await bot.run({
|
||||
const [mainChat, mainUser, mainAddress] = await bot.run({
|
||||
profile: {displayName: "Ask SimpleX Team", fullName: "", shortDescr: "Send questions about SimpleX Chat app and your suggestions", image: supportImage},
|
||||
dbOpts: {dbFilePrefix: config.dbPrefix},
|
||||
options: {
|
||||
@@ -91,7 +113,6 @@ async function main(): Promise<void> {
|
||||
commands: [
|
||||
{type: "command", keyword: "grok", label: "Ask Grok AI"},
|
||||
{type: "command", keyword: "team", label: "Switch to team"},
|
||||
{type: "command", keyword: "add", label: "Join group"},
|
||||
],
|
||||
useBotProfile: true,
|
||||
},
|
||||
@@ -173,6 +194,12 @@ async function main(): Promise<void> {
|
||||
|
||||
const teamGroupPreferences: T.GroupPreferences = {
|
||||
directMessages: {enable: T.GroupFeatureEnabled.On},
|
||||
commands: [
|
||||
{type: "command", keyword: "add", label: "Join customer chat", params: "groupId:name"},
|
||||
{type: "command", keyword: "inviteall", label: "Join all active chats (24h)"},
|
||||
{type: "command", keyword: "invitenew", label: "Join new chats (48h, no team/Grok)"},
|
||||
{type: "command", keyword: "pending", label: "Show pending conversations"},
|
||||
],
|
||||
}
|
||||
|
||||
if (config.teamGroup.id === 0) {
|
||||
@@ -252,6 +279,12 @@ async function main(): Promise<void> {
|
||||
// Create SupportBot — event handlers now route through it
|
||||
supportBot = new SupportBot(mainChat, grokChat, grokApi, config)
|
||||
|
||||
// Set business address for direct message replies
|
||||
if (mainAddress) {
|
||||
supportBot.businessAddress = util.contactAddressStr(mainAddress.connLinkContact)
|
||||
log(`Business address: ${supportBot.businessAddress}`)
|
||||
}
|
||||
|
||||
// Restore Grok group map from persisted state
|
||||
if (state.grokGroupMap) {
|
||||
const entries: [number, number][] = Object.entries(state.grokGroupMap)
|
||||
@@ -267,6 +300,66 @@ async function main(): Promise<void> {
|
||||
writeState(stateFilePath, state)
|
||||
}
|
||||
|
||||
// Restore newItems from persisted state
|
||||
if (state.newItems) {
|
||||
const entries: [number, {teamItemId: number; timestamp: number; originalText: string}][] =
|
||||
Object.entries(state.newItems).map(([k, v]) => [Number(k), v])
|
||||
supportBot.restoreNewItems(entries)
|
||||
}
|
||||
|
||||
// Persist newItems on every change
|
||||
supportBot.onNewItemsChanged = (map) => {
|
||||
const obj: {[key: string]: {teamItemId: number; timestamp: number; originalText: string}} = {}
|
||||
for (const [k, v] of map) obj[String(k)] = v
|
||||
state.newItems = obj
|
||||
writeState(stateFilePath, state)
|
||||
}
|
||||
|
||||
// Restore groupLastActive from persisted state
|
||||
if (state.groupLastActive) {
|
||||
const entries: [number, number][] = Object.entries(state.groupLastActive)
|
||||
.map(([k, v]) => [Number(k), v])
|
||||
supportBot.restoreGroupLastActive(entries)
|
||||
}
|
||||
|
||||
// Persist groupLastActive on every change
|
||||
supportBot.onGroupLastActiveChanged = (map) => {
|
||||
const obj: {[key: string]: number} = {}
|
||||
for (const [k, v] of map) obj[String(k)] = v
|
||||
state.groupLastActive = obj
|
||||
writeState(stateFilePath, state)
|
||||
}
|
||||
|
||||
// Restore groupMetadata from persisted state
|
||||
if (state.groupMetadata) {
|
||||
const entries: [number, GroupMetadata][] = Object.entries(state.groupMetadata)
|
||||
.map(([k, v]) => [Number(k), v])
|
||||
supportBot.restoreGroupMetadata(entries)
|
||||
}
|
||||
|
||||
// Persist groupMetadata on every change
|
||||
supportBot.onGroupMetadataChanged = (map) => {
|
||||
const obj: {[key: string]: GroupMetadata} = {}
|
||||
for (const [k, v] of map) obj[String(k)] = v
|
||||
state.groupMetadata = obj
|
||||
writeState(stateFilePath, state)
|
||||
}
|
||||
|
||||
// Restore groupPendingInfo from persisted state
|
||||
if (state.groupPendingInfo) {
|
||||
const entries: [number, GroupPendingInfo][] = Object.entries(state.groupPendingInfo)
|
||||
.map(([k, v]) => [Number(k), v])
|
||||
supportBot.restoreGroupPendingInfo(entries)
|
||||
}
|
||||
|
||||
// Persist groupPendingInfo on every change
|
||||
supportBot.onGroupPendingInfoChanged = (map) => {
|
||||
const obj: {[key: string]: GroupPendingInfo} = {}
|
||||
for (const [k, v] of map) obj[String(k)] = v
|
||||
state.groupPendingInfo = obj
|
||||
writeState(stateFilePath, state)
|
||||
}
|
||||
|
||||
log("SupportBot initialized. Bot running.")
|
||||
|
||||
// Subscribe Grok agent event handlers
|
||||
|
||||
@@ -17,3 +17,5 @@ export function teamAddedMessage(timezone: string): string {
|
||||
}
|
||||
|
||||
export const teamLockedMessage = "You are now in team mode. A team member will reply to your message."
|
||||
|
||||
export const teamAlreadyAddedMessage = "A team member has already been invited to this conversation and will reply when available."
|
||||
|
||||
Reference in New Issue
Block a user