# 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'),
);
```
