Files
Bernd Steckmeister 25ed765a03 wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.)
als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen
Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
2026-07-03 05:47:18 +02:00

239 lines
5.7 KiB
Markdown

# 11 — Suche & Discovery
---
## Inhaltsverzeichnis
- [Nachrichten in einem Raum suchen](#nachrichten-in-einem-raum-suchen)
- [Globale Nachrichten-Suche](#globale-nachrichten-suche)
- [User-Suche](#user-suche)
- [Öffentliches Raumverzeichnis](#öffentliches-raumverzeichnis)
- [Matrix-URI auflösen](#matrix-uri-auflösen)
- [Raum per Alias finden](#raum-per-alias-finden)
---
## Nachrichten in einem Raum suchen
```dart
// In der lokalen Timeline suchen (gecacht)
final found = timeline.events
.where((e) =>
e.type == EventTypes.Message &&
e.body.toLowerCase().contains(query.toLowerCase()))
.toList();
// Auf dem Server suchen (Matrix-Suchendpoint)
final result = await client.searchEvents(
Categories(
roomEvents: RoomEventsCriteria(
searchTerm: 'suchbegriff',
roomId: room.id, // optional: nur in einem Raum
orderBy: SearchOrder.recent, // oder .rank
keys: [SearchKey.contentBody], // oder .contentName, .contentTopic
filter: RoomEventFilter(
limit: 20,
rooms: [room.id],
notRooms: [],
),
),
),
);
// Ergebnisse
for (final result in result.roomEvents?.results ?? []) {
print(result.result.body);
print(result.result.originServerTs);
print(result.result.senderId);
}
// Paginierung
final nextToken = result.roomEvents?.nextBatch;
if (nextToken != null) {
final more = await client.searchEvents(
/* gleiche Criteria */,
nextBatch: nextToken,
);
}
```
---
## Globale Nachrichten-Suche
```dart
// Über alle Räume suchen
final result = await client.searchEvents(
Categories(
roomEvents: RoomEventsCriteria(
searchTerm: 'flutter matrix',
orderBy: SearchOrder.recent,
includeState: false,
groupings: Groupings(
groupBy: [Group(key: GroupKey.roomId)], // Gruppiert nach Raum
),
),
),
);
// Gruppen-Ergebnisse
for (final entry in result.roomEvents?.groups?.entries ?? []) {
final groupKey = entry.key; // 'room_id'
for (final roomGroup in entry.value.entries) {
final roomId = roomGroup.key;
print('Treffer in $roomId: ${roomGroup.value.count}');
// roomGroup.value.results: List<String> (Event-IDs)
}
}
```
---
## User-Suche
```dart
// User-Verzeichnis durchsuchen
final response = await client.searchUserDirectory(
'alice', // Suchbegriff (Name oder MXID)
limit: 20,
);
for (final user in response.results) {
print(user.userId); // @alice:server
print(user.displayName); // Alice
print(user.avatarUrl); // mxc://...
// Avatar als HTTP-URL
final avatarHttp = user.avatarUrl?.getThumbnail(
client,
width: 56,
height: 56,
method: ThumbnailMethod.crop,
);
}
print(response.limited); // true wenn weitere Ergebnisse vorhanden
// MXID direkt laden (ohne Suche)
final profile = await client.getProfileFromUserId('@alice:server');
print(profile.displayname);
print(profile.avatarUrl);
```
---
## Öffentliches Raumverzeichnis
```dart
// Öffentliche Räume (des eigenen Servers)
final result = await client.queryPublicRooms(
limit: 50,
);
// Mit Suchbegriff
final searched = await client.queryPublicRooms(
limit: 20,
filter: PublicRoomQueryFilter(
genericSearchTerm: 'flutter',
),
);
// Anderen Server durchsuchen
final remote = await client.queryPublicRooms(
server: 'matrix.org',
limit: 20,
filter: PublicRoomQueryFilter(genericSearchTerm: 'linux'),
);
// Paginierung
var batch = await client.queryPublicRooms(limit: 50);
while (batch.nextBatch != null) {
batch = await client.queryPublicRooms(
limit: 50,
since: batch.nextBatch,
);
}
// Einzelnes Ergebnis
for (final room in result.chunk) {
print(room.roomId);
print(room.name);
print(room.topic);
print(room.numJoinedMembers);
print(room.worldReadable);
print(room.guestCanJoin);
print(room.avatarUrl);
print(room.canonicalAlias);
print(room.joinRule); // 'public' | 'knock' | etc.
print(room.roomType); // 'm.space' | null
}
```
---
## Matrix-URI auflösen
```dart
// Matrix-to URI parsen
// https://matrix.to/#/@alice:server
// https://matrix.to/#/!roomId:server
// https://matrix.to/#/#alias:server
// matrix:u/alice:server
// matrix:r/alias:server
// matrix:roomid/roomId:server
import 'package:matrix/matrix.dart';
final uri = MatrixUri.parse('https://matrix.to/#/#kanal:example.org');
if (uri != null) {
switch (uri.primaryType) {
case MatrixUriType.room: // Raum-Alias
case MatrixUriType.roomId: // Raum-ID
case MatrixUriType.user: // User-MXID
case MatrixUriType.event: // Event-Link
}
final identifier = uri.primaryIdentifier;
// z.B. '#kanal:example.org' oder '!roomId:server'
// Beitreten (bei Raum)
if (uri.primaryType == MatrixUriType.room ||
uri.primaryType == MatrixUriType.roomId) {
await client.joinRoomOrAlias(
identifier,
serverName: uri.viaServers, // föderierte Server
);
}
}
// Matrix-URI generieren
final roomUri = MatrixUri(
primaryType: MatrixUriType.roomId,
primaryIdentifier: room.id,
viaServers: ['example.org'],
).toUri().toString();
// → matrix:roomid/roomId:server?via=example.org
// Matrix.to Link generieren
final matrixToLink = 'https://matrix.to/#/${Uri.encodeComponent(room.canonicalAlias ?? room.id)}';
```
---
## Raum per Alias finden
```dart
// Alias → Raum-ID auflösen
final result = await client.getRoomIdByAlias('#alias:server');
print(result.roomId);
print(result.servers); // Via-Server für Beitritt
// Direkt beitreten via Alias
await client.joinRoomOrAlias('#alias:server');
// Lokal prüfen (gecacht)
final localRoom = client.rooms.firstWhere(
(r) => r.canonicalAlias == '#alias:server',
orElse: () => throw Exception('Nicht gefunden'),
);
```