feat: гост мод и фиксы разные всякие я линус торвальдс

This commit is contained in:
Jganenokk
2026-06-13 22:19:45 +07:00
parent e84cdf15e3
commit 814e969c4a
17 changed files with 1341 additions and 412 deletions
+2 -1
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import '../api.dart';
import '../../core/config/komet_settings.dart';
import '../../core/protocol/chat_cache_fingerprint.dart';
import '../../core/protocol/opcode_map.dart';
import '../../core/protocol/packet.dart';
@@ -1146,7 +1147,7 @@ class AccountModule {
) {
final payload = <dynamic, dynamic>{
'token': token,
'interactive': true,
'interactive': !KometSettings.ghostMode.value,
'exp': {
'chatsCountGroups': Uint8List.fromList([0x0b, 0x32]),
},
+96 -9
View File
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../../core/config/komet_settings.dart';
import '../../core/protocol/opcode_map.dart';
import '../../core/protocol/packet.dart';
import '../../core/cache/info_cache.dart';
@@ -180,6 +181,11 @@ class MessageRemovedEvent extends MessageEvent {
const MessageRemovedEvent(super.chatId, this.messageId);
}
class MessageMarkedDeletedEvent extends MessageEvent {
final String messageId;
const MessageMarkedDeletedEvent(super.chatId, this.messageId);
}
class MessageReactionsChangedEvent extends MessageEvent {
final String messageId;
final Map<String, dynamic>? reactionInfo;
@@ -267,6 +273,11 @@ class ChatsModule {
String messageId,
int mark,
) async {
final rows = await AppDatabase.loadChat(accountId, chatId);
if (rows.isEmpty) return;
final row = Map<String, dynamic>.from(rows.first);
if ((row['unread_count'] as int? ?? 0) == 0) return;
final msgIdNum = int.tryParse(messageId);
if (msgIdNum != null) {
try {
@@ -279,10 +290,6 @@ class ChatsModule {
} catch (_) {}
}
final rows = await AppDatabase.loadChat(accountId, chatId);
if (rows.isEmpty) return;
final row = Map<String, dynamic>.from(rows.first);
if ((row['unread_count'] as int? ?? 0) == 0) return;
row['unread_count'] = 0;
await AppDatabase.saveChats([row]);
_bump();
@@ -367,9 +374,21 @@ class ChatsModule {
await _handleNotifMsgReactionsChanged(packet);
case Opcode.notifMsgDelete:
await _handleNotifMsgDelete(packet);
case Opcode.notifPresence:
_handlePresence(packet);
}
}
static void _handlePresence(Packet packet) {
final payload = packet.payload;
if (payload is! Map) return;
final userId = payload['userId'];
if (userId is! int) return;
final presence = payload['presence'];
if (presence is! Map) return;
PresenceFetch.apply(userId, Map<String, dynamic>.from(presence));
}
static Future<void> _handleNotifMsgDelete(Packet packet) async {
final payload = packet.payload;
if (payload is! Map) return;
@@ -386,13 +405,19 @@ class ChatsModule {
}
if (chatId == null) return;
final keepDeleted = KometSettings.viewDeleted.value;
final ids = payload['messageIds'];
if (ids is List) {
for (final raw in ids) {
final id = raw?.toString();
if (id == null || id.isEmpty) continue;
await AppDatabase.deleteMessage(accountId, chatId, id);
_messageEventsController.add(MessageRemovedEvent(chatId, id));
if (keepDeleted) {
await AppDatabase.markMessageDeleted(accountId, chatId, id);
_messageEventsController.add(MessageMarkedDeletedEvent(chatId, id));
} else {
await AppDatabase.deleteMessage(accountId, chatId, id);
_messageEventsController.add(MessageRemovedEvent(chatId, id));
}
}
}
_bump();
@@ -435,7 +460,12 @@ class ChatsModule {
}
if (status == 'REMOVED' && msgIdStr != null) {
await AppDatabase.deleteMessage(accountId, chatId, msgIdStr);
final keepDeleted = KometSettings.viewDeleted.value;
if (keepDeleted) {
await AppDatabase.markMessageDeleted(accountId, chatId, msgIdStr);
} else {
await AppDatabase.deleteMessage(accountId, chatId, msgIdStr);
}
final cachedChat = CachedChat.fromDbRow(rows.first);
if (cachedChat.lastMsgId == msgIdInt) {
await _reconcileLastMessage(accountId, chatId, rows.first, unread: unread);
@@ -444,7 +474,11 @@ class ChatsModule {
newRow['unread_count'] = unread;
await AppDatabase.saveChats([newRow]);
}
_messageEventsController.add(MessageRemovedEvent(chatId, msgIdStr));
_messageEventsController.add(
keepDeleted
? MessageMarkedDeletedEvent(chatId, msgIdStr)
: MessageRemovedEvent(chatId, msgIdStr),
);
_bump();
return;
}
@@ -523,7 +557,12 @@ class ChatsModule {
Map<String, dynamic> chatRow, {
int? unread,
}) async {
final latest = await AppDatabase.loadMessages(accountId, chatId, limit: 1);
final latest = await AppDatabase.loadMessages(
accountId,
chatId,
limit: 1,
onlyVisible: true,
);
final newRow = Map<String, dynamic>.from(chatRow);
if (latest.isNotEmpty) {
final m = latest.first;
@@ -576,6 +615,54 @@ class ChatsModule {
_bump();
}
static Future<List<String>> reconcileDeletedFromFetch(
int accountId,
int chatId,
List<CachedMessage> serverMessages,
) async {
if (serverMessages.isEmpty) return const [];
final serverIds = <String>{};
var minTime = serverMessages.first.time;
var maxTime = serverMessages.first.time;
for (final m in serverMessages) {
serverIds.add(m.id);
if (m.time < minTime) minTime = m.time;
if (m.time > maxTime) maxTime = m.time;
}
final cached = await AppDatabase.loadMessages(
accountId,
chatId,
limit: 300,
onlyVisible: true,
);
final newlyDeleted = <String>[];
for (final row in cached) {
final id = row['id']?.toString();
if (id == null || id.isEmpty || id.startsWith('temp_')) continue;
if (serverIds.contains(id)) continue;
final status = row['status']?.toString();
if (status == 'pending' || status == 'sending' || status == 'error') {
continue;
}
final time = row['time'] is int
? row['time'] as int
: int.tryParse(row['time']?.toString() ?? '') ?? 0;
if (time < minTime || time > maxTime) continue;
newlyDeleted.add(id);
}
if (newlyDeleted.isNotEmpty) {
await AppDatabase.markMessagesDeleted(accountId, chatId, newlyDeleted);
}
return newlyDeleted;
}
static Future<void> _handleNotifMsgReactionsChanged(Packet packet) async {
final payload = packet.payload;
if (payload is! Map) return;
+98 -4
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -14,13 +15,48 @@ class ContactCache {
static final Map<int, String> _avatarCache = {};
static final Map<int, Set<String>> _optionsCache = {};
static void put(int id, String name) => _nameCache[id] = name;
static const _prefsKey = 'contact_cache_v1';
static Timer? _saveTimer;
static bool _loaded = false;
static void putAvatar(int id, String? baseUrl) {
if (baseUrl != null) _avatarCache[id] = baseUrl;
static Future<void> load() async {
if (_loaded) return;
_loaded = true;
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_prefsKey);
if (raw == null) return;
try {
final decoded = jsonDecode(raw);
if (decoded is! Map) return;
decoded.forEach((key, value) {
final id = int.tryParse(key.toString());
if (id == null || value is! Map) return;
final name = value['n'];
final avatar = value['a'];
final opts = value['o'];
if (name is String) _nameCache[id] = name;
if (avatar is String) _avatarCache[id] = avatar;
if (opts is List) _optionsCache[id] = opts.whereType<String>().toSet();
});
} catch (_) {}
}
static void putOptions(int id, Set<String> opts) => _optionsCache[id] = opts;
static void put(int id, String name) {
_nameCache[id] = name;
_scheduleSave();
}
static void putAvatar(int id, String? baseUrl) {
if (baseUrl != null) {
_avatarCache[id] = baseUrl;
_scheduleSave();
}
}
static void putOptions(int id, Set<String> opts) {
_optionsCache[id] = opts;
_scheduleSave();
}
static String? get(int id) => _nameCache[id];
static String? getAvatar(int id) => _avatarCache[id];
@@ -32,6 +68,40 @@ class ContactCache {
_nameCache.clear();
_avatarCache.clear();
_optionsCache.clear();
_saveTimer?.cancel();
_saveTimer = null;
unawaited(_wipePersisted());
}
static void _scheduleSave() {
_saveTimer?.cancel();
_saveTimer = Timer(const Duration(seconds: 3), () => unawaited(_save()));
}
static Future<void> _save() async {
final ids = <int>{
..._nameCache.keys,
..._avatarCache.keys,
..._optionsCache.keys,
};
final map = <String, dynamic>{};
for (final id in ids) {
final entry = <String, dynamic>{};
final name = _nameCache[id];
final avatar = _avatarCache[id];
final opts = _optionsCache[id];
if (name != null) entry['n'] = name;
if (avatar != null) entry['a'] = avatar;
if (opts != null && opts.isNotEmpty) entry['o'] = opts.toList();
if (entry.isNotEmpty) map['$id'] = entry;
}
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_prefsKey, jsonEncode(map));
}
static Future<void> _wipePersisted() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_prefsKey);
}
}
@@ -197,6 +267,7 @@ class CachedMessage {
final Map<String, dynamic>? payload;
final List<MessageAttachment>? attachments;
final bool isControl;
final bool deleted;
const CachedMessage({
required this.id,
@@ -209,8 +280,27 @@ class CachedMessage {
this.payload,
this.attachments,
this.isControl = false,
this.deleted = false,
});
CachedMessage copyWith({
String? status,
bool? deleted,
List<MessageAttachment>? attachments,
}) => CachedMessage(
id: id,
accountId: accountId,
chatId: chatId,
senderId: senderId,
text: text,
time: time,
status: status ?? this.status,
payload: payload,
attachments: attachments ?? this.attachments,
isControl: isControl,
deleted: deleted ?? this.deleted,
);
factory CachedMessage.fromDbRow(Map<String, dynamic> row) {
Map<String, dynamic>? payload;
final payloadRaw = row['payload'];
@@ -259,6 +349,9 @@ class CachedMessage {
attachments: attachments,
isControl:
attachments?.any((a) => a.type == AttachmentType.control) ?? false,
deleted: row['deleted'] is int
? row['deleted'] == 1
: row['deleted']?.toString() == '1',
);
}
@@ -295,6 +388,7 @@ class CachedMessage {
'time': time,
'status': status,
'payload': payload != null ? jsonEncode(payload) : null,
'deleted': deleted ? 1 : 0,
};
static CachedMessage fromPushPayload(int accountId, int chatId, Map msg) {
+41
View File
@@ -0,0 +1,41 @@
import 'dart:async';
import '../../core/cache/info_cache.dart';
import '../../core/cache/self_presence.dart';
import '../../core/config/komet_settings.dart';
import '../../core/storage/token_storage.dart';
import '../../core/utils/logger.dart';
import '../api.dart';
class SelfCheckService {
SelfCheckService._();
static final SelfCheckService instance = SelfCheckService._();
static const Duration interval = Duration(seconds: 10);
Api? _api;
Timer? _timer;
void init(Api api) {
if (_api != null) return;
_api = api;
_timer?.cancel();
_timer = Timer.periodic(interval, (_) => unawaited(_check()));
}
Future<void> _check() async {
final api = _api;
if (api == null || api.state != SessionState.online) return;
if (!KometSettings.selfOnlineCheck.value) return;
final accountId = await TokenStorage.getActiveAccountId();
if (accountId == null) return;
final presence = await PresenceFetch.get(accountId, forceRefresh: true);
logger.i('[SELF CHECK] id=$accountId presence=$presence');
if (presence == null) return;
SelfPresence.applySelfCheck(
online: presence['status'] == 1,
seenSeconds: presence['seen'] is int ? presence['seen'] as int : null,
);
}
}