Merge branch 'main' into huluwa-dev/fix-avatar-error-on-windows
This commit is contained in:
commit
d325e58a2c
|
|
@ -0,0 +1,156 @@
|
|||
## Available Chat Commands
|
||||
|
||||
The Matrix Dart SDK supports out of the box chat commands. Just use `Room.sendTextEvent("/command");`. If you do not desire to get chat commands parsed, you can disable them like this: `Room.sendTextEvent("/command", parseCommands: false);`
|
||||
|
||||
### Available Commands:
|
||||
|
||||
## Available Chat Commands
|
||||
|
||||
The Matrix Dart SDK supports out of the box chat commands. Just use `Room.sendTextEvent("/command");`. If you do not desire to get chat commands parsed, you can disable them like this: `Room.sendTextEvent("/command", parseCommands: false);`
|
||||
|
||||
### Available Commands:
|
||||
|
||||
```sh
|
||||
/send <message>
|
||||
```
|
||||
Sends a plain message to the current room (commands are not parsed).
|
||||
|
||||
```sh
|
||||
/me <action>
|
||||
```
|
||||
Sends an emote message (e.g., `/me is happy` will display as "YourName is happy").
|
||||
|
||||
```sh
|
||||
/dm <mxid> [--no-encryption]
|
||||
```
|
||||
Starts a direct chat with the given user. Optionally disables encryption.
|
||||
|
||||
```sh
|
||||
/create [<group name>] [--no-encryption]
|
||||
```
|
||||
Creates a new group chat with the given name. Optionally disables encryption.
|
||||
|
||||
```sh
|
||||
/plain <message>
|
||||
```
|
||||
Sends a plain text message (no markdown, no commands) to the current room.
|
||||
|
||||
```sh
|
||||
/html <html>
|
||||
```
|
||||
Sends a message as raw HTML to the current room.
|
||||
|
||||
```sh
|
||||
/react <emoji>
|
||||
```
|
||||
Reacts to the message you are replying to with the given emoji.
|
||||
|
||||
```sh
|
||||
/join <room>
|
||||
```
|
||||
Joins the specified room.
|
||||
|
||||
```sh
|
||||
/leave
|
||||
```
|
||||
Leaves the current room.
|
||||
|
||||
```sh
|
||||
/op <mxid> [<power level>]
|
||||
```
|
||||
Sets the power level of a user in the current room (default: 50).
|
||||
|
||||
```sh
|
||||
/kick <mxid>
|
||||
```
|
||||
Kicks a user from the current room.
|
||||
|
||||
```sh
|
||||
/ban <mxid>
|
||||
```
|
||||
Bans a user from the current room.
|
||||
|
||||
```sh
|
||||
/unban <mxid>
|
||||
```
|
||||
Unbans a user in the current room.
|
||||
|
||||
```sh
|
||||
/invite <mxid>
|
||||
```
|
||||
Invites a user to the current room.
|
||||
|
||||
```sh
|
||||
/myroomnick <displayname>
|
||||
```
|
||||
Sets your display name in the current room.
|
||||
|
||||
```sh
|
||||
/myroomavatar <mxc-url>
|
||||
```
|
||||
Sets your avatar in the current room.
|
||||
|
||||
```sh
|
||||
/discardsession
|
||||
```
|
||||
Discards the outbound group session for the current room (forces new encryption session).
|
||||
|
||||
```sh
|
||||
/clearcache
|
||||
```
|
||||
Clears the local cache.
|
||||
|
||||
```sh
|
||||
/markasdm <mxid>
|
||||
```
|
||||
Marks the current room as a direct chat with the given user.
|
||||
|
||||
```sh
|
||||
/markasgroup
|
||||
```
|
||||
Removes the direct chat status from the current room.
|
||||
|
||||
```sh
|
||||
/hug
|
||||
```
|
||||
Sends a "hug" event to the current room.
|
||||
|
||||
```sh
|
||||
/googly
|
||||
```
|
||||
Sends a "googly eyes" event to the current room.
|
||||
|
||||
```sh
|
||||
/cuddle
|
||||
```
|
||||
Sends a "cuddle" event to the current room.
|
||||
|
||||
```sh
|
||||
/sendRaw <json>
|
||||
```
|
||||
Sends a raw event (as JSON) to the current room.
|
||||
|
||||
```sh
|
||||
/ignore <mxid>
|
||||
```
|
||||
Ignores the given user (you will not see their messages).
|
||||
|
||||
```sh
|
||||
/unignore <mxid>
|
||||
```
|
||||
Stops ignoring the given user.
|
||||
|
||||
```sh
|
||||
/roomupgrade <version>
|
||||
```
|
||||
Upgrades the current room to a new version.
|
||||
|
||||
```sh
|
||||
/logout
|
||||
```
|
||||
Logs out the current session.
|
||||
|
||||
```sh
|
||||
/logoutAll
|
||||
```
|
||||
Logs out all sessions for the user.
|
||||
|
|
@ -181,23 +181,24 @@ class CrossSigning {
|
|||
}
|
||||
if (signedKeys.isNotEmpty) {
|
||||
// post our new keys!
|
||||
final payload = <String, Map<String, Map<String, dynamic>>>{};
|
||||
final payload = <String, Map<String, Map<String, Object?>>>{};
|
||||
for (final key in signedKeys) {
|
||||
if (key.identifier == null ||
|
||||
key.signatures == null ||
|
||||
key.signatures?.isEmpty != false) {
|
||||
final signatures = key.signatures;
|
||||
final identifier = key.identifier;
|
||||
if (identifier == null || signatures == null || signatures.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
if (!payload.containsKey(key.userId)) {
|
||||
payload[key.userId] = <String, Map<String, dynamic>>{};
|
||||
payload[key.userId] = <String, Map<String, Object?>>{};
|
||||
}
|
||||
if (payload[key.userId]?[key.identifier]?['signatures'] != null) {
|
||||
// we need to merge signature objects
|
||||
payload[key.userId]![key.identifier]!['signatures']
|
||||
.addAll(key.signatures);
|
||||
payload[key.userId]![key.identifier]!
|
||||
.tryGetMap<String, Map<String, String>>('signatures')!
|
||||
.addAll(signatures);
|
||||
} else {
|
||||
// we can just add signatures
|
||||
payload[key.userId]![key.identifier!] = key.toJson();
|
||||
payload[key.userId]![identifier] = key.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,28 +102,17 @@ class OlmManager {
|
|||
|
||||
/// Adds a signature to this json from this olm account and returns the signed
|
||||
/// json.
|
||||
Map<String, dynamic> signJson(Map<String, dynamic> payload) {
|
||||
Map<String, Object?> signJson(Map<String, Object?> payload) {
|
||||
if (!enabled) throw ('Encryption is disabled');
|
||||
final Map<String, dynamic>? unsigned = payload['unsigned'];
|
||||
final Map<String, dynamic>? signatures = payload['signatures'];
|
||||
payload.remove('unsigned');
|
||||
payload.remove('signatures');
|
||||
final canonical = canonicalJson.encode(payload);
|
||||
final signableJson = SignableJsonMap(payload);
|
||||
|
||||
final canonical = canonicalJson.encode(signableJson.jsonMap);
|
||||
final signature = _olmAccount!.sign(String.fromCharCodes(canonical));
|
||||
if (signatures != null) {
|
||||
payload['signatures'] = signatures;
|
||||
} else {
|
||||
payload['signatures'] = <String, dynamic>{};
|
||||
}
|
||||
if (!payload['signatures'].containsKey(client.userID)) {
|
||||
payload['signatures'][client.userID] = <String, dynamic>{};
|
||||
}
|
||||
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] =
|
||||
signature.toBase64();
|
||||
if (unsigned != null) {
|
||||
payload['unsigned'] = unsigned;
|
||||
}
|
||||
return payload;
|
||||
|
||||
final userSignatures = signableJson.signatures[client.userID!] ??= {};
|
||||
userSignatures['ed25519:$ourDeviceId'] = signature.toBase64();
|
||||
|
||||
return signableJson.toJson();
|
||||
}
|
||||
|
||||
String signString(String s) {
|
||||
|
|
@ -811,3 +800,24 @@ class NoOlmSessionFoundException implements Exception {
|
|||
String toString() =>
|
||||
'No olm session found for ${device.userId}:${device.deviceId}';
|
||||
}
|
||||
|
||||
class SignableJsonMap {
|
||||
final Map<String, Object?> jsonMap;
|
||||
final Map<String, Map<String, String>> signatures;
|
||||
final Map<String, Object?>? unsigned;
|
||||
|
||||
SignableJsonMap(Map<String, Object?> json)
|
||||
: jsonMap = json,
|
||||
signatures =
|
||||
json.tryGetMap<String, Map<String, String>>('signatures') ?? {},
|
||||
unsigned = json.tryGetMap<String, Object?>('unsigned') {
|
||||
jsonMap.remove('signatures');
|
||||
jsonMap.remove('unsigned');
|
||||
}
|
||||
|
||||
Map<String, Object?> toJson() => {
|
||||
...jsonMap,
|
||||
'signatures': signatures,
|
||||
if (unsigned != null) 'unsigned': unsigned,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,24 +41,25 @@ class OlmSession {
|
|||
required this.lastReceived,
|
||||
});
|
||||
|
||||
OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key)
|
||||
: identityKey = dbEntry['identity_key'] ?? '' {
|
||||
OlmSession.fromJson(Map<String, Object?> dbEntry, this.key)
|
||||
: identityKey = dbEntry.tryGet<String>('identity_key') ?? '' {
|
||||
try {
|
||||
try {
|
||||
session = vod.Session.fromPickleEncrypted(
|
||||
pickleKey: key.toPickleKey(),
|
||||
pickle: dbEntry['pickle'],
|
||||
pickle: dbEntry['pickle'] as String,
|
||||
);
|
||||
} catch (_) {
|
||||
Logs().d('Unable to unpickle Olm session. Try LibOlm format.');
|
||||
session = vod.Session.fromOlmPickleEncrypted(
|
||||
pickleKey: utf8.encode(key),
|
||||
pickle: dbEntry['pickle'],
|
||||
pickle: dbEntry['pickle'] as String,
|
||||
);
|
||||
}
|
||||
sessionId = dbEntry['session_id'];
|
||||
lastReceived =
|
||||
DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0);
|
||||
sessionId = dbEntry['session_id'] as String;
|
||||
lastReceived = DateTime.fromMillisecondsSinceEpoch(
|
||||
dbEntry.tryGet<int>('last_received') ?? 0,
|
||||
);
|
||||
assert(sessionId == session!.sessionId);
|
||||
} catch (e, s) {
|
||||
Logs().e('[Vodozemac] Could not unpickle olm session', e, s);
|
||||
|
|
|
|||
|
|
@ -1790,12 +1790,19 @@ class Client extends MatrixApi {
|
|||
if (eventId == null || roomId == null) return null;
|
||||
|
||||
// Create the room object:
|
||||
final room = getRoomById(roomId) ??
|
||||
await database.getSingleRoom(this, roomId) ??
|
||||
var room =
|
||||
getRoomById(roomId) ?? await database.getSingleRoom(this, roomId);
|
||||
if (room == null) {
|
||||
await oneShotSync()
|
||||
.timeout(timeoutForServerRequests)
|
||||
.catchError((_) => null);
|
||||
room = getRoomById(roomId) ??
|
||||
Room(
|
||||
id: roomId,
|
||||
client: this,
|
||||
);
|
||||
}
|
||||
|
||||
final roomName = notification.roomName;
|
||||
final roomAlias = notification.roomAlias;
|
||||
if (roomName != null) {
|
||||
|
|
@ -1840,9 +1847,7 @@ class Client extends MatrixApi {
|
|||
roomId: roomId,
|
||||
);
|
||||
}
|
||||
matrixEvent ??= await database
|
||||
.getEventById(eventId, room)
|
||||
.timeout(timeoutForServerRequests);
|
||||
matrixEvent ??= await database.getEventById(eventId, room);
|
||||
|
||||
try {
|
||||
matrixEvent ??= await getOneRoomEvent(roomId, eventId)
|
||||
|
|
@ -1869,9 +1874,8 @@ class Client extends MatrixApi {
|
|||
if (room.fullyRead == matrixEvent.eventId) {
|
||||
return null;
|
||||
}
|
||||
final readMarkerEvent = await database
|
||||
.getEventById(room.fullyRead, room)
|
||||
.timeout(timeoutForServerRequests);
|
||||
final readMarkerEvent = await database.getEventById(room.fullyRead, room);
|
||||
|
||||
if (readMarkerEvent != null &&
|
||||
readMarkerEvent.originServerTs.isAfter(
|
||||
matrixEvent.originServerTs
|
||||
|
|
@ -1910,7 +1914,10 @@ class Client extends MatrixApi {
|
|||
var decrypted = await encryption.decryptRoomEvent(event);
|
||||
if (decrypted.messageType == MessageTypes.BadEncrypted &&
|
||||
prevBatch != null) {
|
||||
await oneShotSync();
|
||||
await oneShotSync()
|
||||
.timeout(timeoutForServerRequests)
|
||||
.catchError((_) => null);
|
||||
|
||||
decrypted = await encryption.decryptRoomEvent(event);
|
||||
}
|
||||
event = decrypted;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ void main() {
|
|||
group('client path', () {
|
||||
late Client clientOnPath;
|
||||
|
||||
final dbPath = join(Directory.current.path, 'test.sqlite');
|
||||
final dbPath = join(Directory.current.path, 'client_path_test.sqlite');
|
||||
|
||||
setUp(() async {
|
||||
expect(
|
||||
|
|
|
|||
Loading…
Reference in New Issue