Files
IKAR/lib/backend/modules/chat_admin.dart
T

173 lines
5.4 KiB
Dart

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<ChatReactionSettings> _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<ChatReactionSettings?> 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<ChatInfo> setAdmin(int chatId, int userId, AdminRights rights) =>
_apply(chatId, Opcode.chatMembersUpdate, {
'userIds': [userId],
'type': 'ADMIN',
'operation': 'add',
'permissions': rights.bits,
});
Future<ChatInfo> removeAdmin(int chatId, int userId) =>
_apply(chatId, Opcode.chatMembersUpdate, {
'userIds': [userId],
'type': 'ADMIN',
'operation': 'remove',
});
Future<ChatInfo> removeMember(int chatId, int userId) =>
_apply(chatId, Opcode.chatMembersUpdate, {
'userIds': [userId],
'operation': 'remove',
'cleanMsgPeriod': 0,
});
Future<ChatInfo> transferOwnership(int chatId, int userId) =>
_apply(chatId, Opcode.chatUpdate, {'changeOwnerId': userId});
Future<ChatInfo> revokeInviteLink(int chatId) =>
_apply(chatId, Opcode.chatUpdate, {'revokePrivateLink': true});
Future<ChatInfo> setOptions(int chatId, Map<String, bool> options) =>
_apply(chatId, Opcode.chatUpdate, {'options': options});
Future<ChatInfo> updateInfo(
int chatId, {
required String title,
required String description,
}) => _apply(chatId, Opcode.chatUpdate, {
'theme': title,
'description': description,
});
Future<ChatInfo> setPhoto(int chatId, String photoToken) =>
_apply(chatId, Opcode.chatUpdate, {'photoToken': photoToken});
Future<ChatReactionSettings?> _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<ChatReactionSettings> disableReactions(int chatId) =>
_applyReactions(chatId, {'chatId': chatId, 'value': false});
Future<ChatReactionSettings> setReactions(
int chatId, {
required int count,
required List<String> forbidden,
}) => _applyReactions(chatId, {
'chatId': chatId,
'value': true,
'count': count,
'reactionIds': forbidden,
'included': false,
});
Future<ChatReactionSettings> _applyReactions(
int chatId,
Map<String, dynamic> 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<ChatInfo> _apply(
int chatId,
int opcode,
Map<String, dynamic> 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<String, dynamic>.from(chat));
ChatInfoFetch.put(chatId, info);
if (accountId != null) await chats.cacheServerChat(chat, accountId);
return info;
}
}