feat: реакции, так еще ебать и анимированные!!!!!! правда лагает пизда я это потом пофиксю мб как в тг сделаю

This commit is contained in:
Jganenokk
2026-07-10 00:38:03 +07:00
parent 94ca2016ba
commit c81cd91ecd
13 changed files with 602 additions and 176 deletions
+128
View File
@@ -0,0 +1,128 @@
import '../api.dart';
import '../../core/protocol/opcode_map.dart';
import '../../core/utils/logger.dart';
import '../../models/animoji.dart';
class AnimojiModule {
final Api _api;
AnimojiModule(this._api);
static const List<String> fallbackReactions = [
'👍',
'❤️',
'🔥',
'🤣',
'😭',
'😍',
];
final Map<int, Animoji> _byId = {};
List<int> _orderedIds = [];
Future<void>? _loading;
bool get isLoaded => _orderedIds.isNotEmpty;
List<Animoji> get animojis =>
_orderedIds.map((id) => _byId[id]).whereType<Animoji>().toList();
List<String> get emojis => animojis.map((a) => a.emoji).toList();
List<Animoji> get quickAnimojis {
final list = animojis;
return list.length <= 6 ? list : list.sublist(0, 6);
}
Future<void> ensureLoaded() {
return _loading ??= _load().catchError((Object e) {
_loading = null;
throw e;
});
}
Future<void> _load() async {
final setIds = <int>[];
final fallbackIds = <int>[];
final sync = await _api.sendRequestMap(Opcode.assetsUpdate, {
'type': 'ANIMOJI_SET',
'sync': 0,
});
if (sync != null) {
final sections = sync['sections'];
if (sections is List) {
for (final s in sections) {
if (s is Map) _appendIntList(setIds, s['animojiSetIds']);
}
}
final updates = sync['animojiUpdates'];
if (updates is Map) {
for (final key in updates.keys) {
final id = key is int ? key : int.tryParse(key.toString());
if (id != null) fallbackIds.add(id);
}
}
}
final orderedIds = <int>[];
if (setIds.isNotEmpty) {
final setMap = await _api.sendRequestMap(Opcode.assetsGetByIds, {
'type': 'ANIMOJI_SET',
'ids': setIds,
});
if (setMap != null) {
final sets = setMap['animojiSets'];
if (sets is List) {
for (final set in sets) {
if (set is! Map) continue;
_appendIntList(orderedIds, set['animojis']);
_appendIntList(orderedIds, set['animojiIds']);
}
}
}
}
final ids = _dedup(orderedIds.isNotEmpty ? orderedIds : fallbackIds);
if (ids.isEmpty) return;
for (final batch in _chunk(ids, 100)) {
final map = await _api.sendRequestMap(Opcode.assetsGetByIds, {
'type': 'ANIMOJI',
'ids': batch,
});
if (map == null) continue;
final list = map['animojis'];
if (list is! List) continue;
for (final e in list) {
if (e is! Map) continue;
final animoji = Animoji.fromMap(e);
if (animoji != null) _byId[animoji.id] = animoji;
}
}
_orderedIds = ids.where(_byId.containsKey).toList();
logger.i('Анимодзи: ${_orderedIds.length} доступно для реакций');
}
List<int> _dedup(List<int> ids) {
final seen = <int>{};
final result = <int>[];
for (final id in ids) {
if (seen.add(id)) result.add(id);
}
return result;
}
void _appendIntList(List<int> target, dynamic raw) {
if (raw is! List) return;
for (final e in raw) {
if (e is int) target.add(e);
}
}
Iterable<List<T>> _chunk<T>(List<T> list, int size) sync* {
for (var i = 0; i < list.length; i += size) {
yield list.sublist(i, i + size > list.length ? list.length : i + size);
}
}
}
+104 -1
View File
@@ -7,6 +7,7 @@ import '../../core/config/komet_settings.dart';
import '../../core/protocol/opcode_map.dart';
import '../../core/protocol/packet.dart';
import '../../core/storage/app_database.dart';
import '../../core/storage/token_storage.dart';
import '../../core/utils/logger.dart';
import '../../core/utils/text_format.dart';
import '../../models/attachment.dart';
@@ -393,6 +394,7 @@ class CachedMessage {
bool? deleted,
List<MessageAttachment>? attachments,
List<Map<String, dynamic>>? editHistory,
Map<String, dynamic>? payload,
}) => CachedMessage(
id: id,
accountId: accountId,
@@ -401,7 +403,7 @@ class CachedMessage {
text: text,
time: time,
status: status ?? this.status,
payload: payload,
payload: payload ?? this.payload,
attachments: attachments ?? this.attachments,
isControl: isControl,
deleted: deleted ?? this.deleted,
@@ -1055,6 +1057,107 @@ class MessagesModule {
return _api.sendRequestOk(Opcode.msgDelete, payload);
}
Future<({bool ok, Map<String, dynamic>? info})> setReaction(
int chatId,
String messageId,
String emoji,
) async {
final id = int.tryParse(messageId);
if (id == null) return (ok: false, info: null);
final response = await _api.sendRequest(Opcode.msgReaction, {
'chatId': chatId,
'messageId': id,
'reaction': {'reactionType': 'EMOJI', 'id': emoji},
});
return _applyReactionResponse(chatId, messageId, response);
}
Future<({bool ok, Map<String, dynamic>? info})> cancelReaction(
int chatId,
String messageId,
) async {
final id = int.tryParse(messageId);
if (id == null) return (ok: false, info: null);
final response = await _api.sendRequest(Opcode.msgCancelReaction, {
'chatId': chatId,
'messageId': id,
});
return _applyReactionResponse(chatId, messageId, response);
}
Future<({bool ok, Map<String, dynamic>? info})> _applyReactionResponse(
int chatId,
String messageId,
Packet response,
) async {
if (!response.isOk) return (ok: false, info: null);
final payload = response.payload;
final info = payload is Map
? _normalizeReactionInfo(payload['reactionInfo'])
: null;
try {
await _persistReaction(chatId, messageId, info);
} catch (_) {}
return (ok: true, info: info);
}
static Map<String, dynamic>? _normalizeReactionInfo(dynamic raw) {
if (raw is! Map) return null;
final rawCounters = raw['counters'];
if (rawCounters is! List) return null;
final counters = <Map<String, dynamic>>[];
for (final c in rawCounters) {
if (c is! Map) continue;
final reaction = c['reaction']?.toString();
if (reaction == null || reaction.isEmpty) continue;
final count = c['count'];
counters.add({'reaction': reaction, 'count': count is int ? count : 0});
}
if (counters.isEmpty) return null;
final your = raw['yourReaction']?.toString();
final total = raw['totalCount'];
return {
'counters': counters,
if (your != null && your.isNotEmpty) 'yourReaction': your,
'totalCount': total is int
? total
: counters.fold<int>(0, (a, b) => a + (b['count'] as int)),
};
}
Future<void> _persistReaction(
int chatId,
String messageId,
Map<String, dynamic>? info,
) async {
final accountId = await TokenStorage.getActiveAccountId();
if (accountId == null) return;
final existing = await AppDatabase.loadMessage(accountId, chatId, messageId);
if (existing == null) return;
Map<String, dynamic> payloadMap;
final raw = existing['payload'];
if (raw is String && raw.isNotEmpty) {
try {
payloadMap = Map<String, dynamic>.from(jsonDecode(raw) as Map);
} catch (_) {
payloadMap = <String, dynamic>{};
}
} else {
payloadMap = <String, dynamic>{};
}
if (info == null) {
payloadMap.remove('reactionInfo');
} else {
payloadMap['reactionInfo'] = info;
}
final newRow = Map<String, dynamic>.from(existing);
newRow['payload'] = jsonEncode(payloadMap);
await AppDatabase.saveMessages([newRow]);
}
Future<Map<String, dynamic>?> sendButtonCallback({
required int chatId,
required String messageId,