import 'dart:async'; import '../../core/cache/info_cache.dart'; import '../../core/protocol/opcode_map.dart'; import '../../core/storage/token_storage.dart'; import '../../models/admin_rights.dart'; import '../../models/chat_info.dart'; import '../../models/chat_reaction_settings.dart'; import '../api.dart'; import 'chats.dart'; class ChatAdminModule { final Api _api; (int?, int)? _reactionScope; ChatAdminModule(this._api); late final InfoCache _reactionCache = InfoCache( ttl: const Duration(minutes: 5), fetcher: _fetchReactionSettings, ); final StreamController<({int chatId, ChatReactionSettings settings})> _reactionUpdates = StreamController.broadcast(); Stream<({int chatId, ChatReactionSettings settings})> get reactionUpdates => _reactionUpdates.stream; Future reactionSettingsFor( int chatId, { bool forceRefresh = false, }) async { final scope = (await TokenStorage.getActiveAccountId(), _api.sessionEpoch); if (_reactionScope != scope) { _reactionCache.clear(); _reactionScope = scope; } final result = await _reactionCache.get(chatId, forceRefresh: forceRefresh); if ((await TokenStorage.getActiveAccountId(), _api.sessionEpoch) != scope) { return null; } return result; } Future setAdmin(int chatId, int userId, AdminRights rights) => _apply(chatId, Opcode.chatMembersUpdate, { 'userIds': [userId], 'type': 'ADMIN', 'operation': 'add', 'permissions': rights.bits, }); Future removeAdmin(int chatId, int userId) => _apply(chatId, Opcode.chatMembersUpdate, { 'userIds': [userId], 'type': 'ADMIN', 'operation': 'remove', }); Future removeMember(int chatId, int userId) => _apply(chatId, Opcode.chatMembersUpdate, { 'userIds': [userId], 'operation': 'remove', 'cleanMsgPeriod': 0, }); Future transferOwnership(int chatId, int userId) => _apply(chatId, Opcode.chatUpdate, {'changeOwnerId': userId}); Future revokeInviteLink(int chatId) => _apply(chatId, Opcode.chatUpdate, {'revokePrivateLink': true}); Future setOptions(int chatId, Map options) => _apply(chatId, Opcode.chatUpdate, {'options': options}); Future updateInfo( int chatId, { required String title, required String description, }) => _apply(chatId, Opcode.chatUpdate, { 'theme': title, 'description': description, }); Future setPhoto(int chatId, String photoToken) => _apply(chatId, Opcode.chatUpdate, {'photoToken': photoToken}); Future _fetchReactionSettings(int chatId) async { final packet = await _api.sendRequest(Opcode.reactionsSettingsGetByChatId, { 'chatIds': [chatId], }, silent: true); final payload = packet.payload; final list = payload is Map ? payload['chatReactionsSettings'] : null; if (list is! List) return null; for (final entry in list) { if (entry is Map && entry['chatId'] == chatId) { return ChatReactionSettings.fromMap(entry); } } return null; } Future disableReactions(int chatId) => _applyReactions(chatId, {'chatId': chatId, 'value': false}); Future setReactions( int chatId, { required int count, required List forbidden, }) => _applyReactions(chatId, { 'chatId': chatId, 'value': true, 'count': count, 'reactionIds': forbidden, 'included': false, }); Future _applyReactions( int chatId, Map payload, ) async { final epoch = _api.sessionEpoch; final accountId = await TokenStorage.getActiveAccountId(); final packet = await _api.sendRequest( Opcode.chatReactionsSettingsSet, payload, silent: true, ); if (_api.sessionEpoch != epoch || await TokenStorage.getActiveAccountId() != accountId) { throw StateError('Account changed during reaction settings update'); } final data = packet.payload; final settings = ChatReactionSettings.fromMap( data is Map ? data['chatReactionsSettings'] : null, ); if (settings == null) { throw StateError('reaction settings missing from the answer'); } _reactionCache.putValue(chatId, settings); _reactionUpdates.add((chatId: chatId, settings: settings)); return settings; } Future _apply( int chatId, int opcode, Map change, ) async { final epoch = _api.sessionEpoch; final accountId = await TokenStorage.getActiveAccountId(); final packet = await _api.sendRequest(opcode, { 'chatId': chatId, ...change, }, silent: true); if (_api.sessionEpoch != epoch || await TokenStorage.getActiveAccountId() != accountId) { throw StateError('Account changed during chat update'); } final payload = packet.payload; final chat = payload is Map ? payload['chat'] : null; if (chat is! Map) { final refreshed = await ChatInfoFetch.get(chatId, forceRefresh: true); if (refreshed == null) throw StateError('chat $chatId not refreshed'); return refreshed; } final info = ChatInfo.fromMap(Map.from(chat)); ChatInfoFetch.put(chatId, info); if (accountId != null) await chats.cacheServerChat(chat, accountId); return info; } }