бля а как мне тестить у меня темпблок

This commit is contained in:
Jganenokk
2026-04-06 22:04:22 +07:00
parent 57fbaece03
commit f9be6abd4a
6 changed files with 2843 additions and 100 deletions
+476
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import '../api.dart';
import '../../core/protocol/opcode_map.dart';
import '../../core/protocol/packet.dart';
@@ -9,6 +10,184 @@ import 'chats.dart';
import 'contacts.dart';
import 'folders.dart';
class PrivacyConfig {
final String searchByPhone;
final String incomingCall;
final bool doubleTapReactionDisabled;
final bool safeModeNoPin;
final String? doubleTapReactionValue;
final String familyProtection;
final bool pushDetails;
final bool hidden;
final String chatsInvite;
final bool pushNewContacts;
final bool unsafeFiles;
final String inactiveTtl;
final bool showReadMark;
final bool altKeyboard;
final bool contentLevelAccess;
final String stickersSuggest;
final bool safeMode;
final bool audioTranscriptionEnabled;
final String hash;
const PrivacyConfig({
required this.searchByPhone,
required this.incomingCall,
required this.doubleTapReactionDisabled,
required this.safeModeNoPin,
this.doubleTapReactionValue,
required this.familyProtection,
required this.pushDetails,
required this.hidden,
required this.chatsInvite,
required this.pushNewContacts,
required this.unsafeFiles,
required this.inactiveTtl,
required this.showReadMark,
required this.altKeyboard,
required this.contentLevelAccess,
required this.stickersSuggest,
required this.safeMode,
required this.audioTranscriptionEnabled,
required this.hash,
});
factory PrivacyConfig.fromMap(Map<dynamic, dynamic> map) {
return PrivacyConfig(
searchByPhone: map['SEARCH_BY_PHONE']?.toString() ?? 'ALL',
incomingCall: map['INCOMING_CALL']?.toString() ?? 'CONTACTS',
doubleTapReactionDisabled: map['DOUBLE_TAP_REACTION_DISABLED'] ?? false,
safeModeNoPin: map['SAFE_MODE_NO_PIN'] ?? false,
doubleTapReactionValue: map['DOUBLE_TAP_REACTION_VALUE']?.toString(),
familyProtection: map['FAMILY_PROTECTION']?.toString() ?? 'OFF',
pushDetails: map['PUSH_DETAILS'] ?? false,
hidden: map['HIDDEN'] ?? true,
chatsInvite: map['CHATS_INVITE']?.toString() ?? 'CONTACTS',
pushNewContacts: map['PUSH_NEW_CONTACTS'] ?? false,
unsafeFiles: map['UNSAFE_FILES'] ?? true,
inactiveTtl: map['INACTIVE_TTL']?.toString() ?? '6M',
showReadMark: map['SHOW_READ_MARK'] ?? true,
altKeyboard: map['ALT_KEYBOARD'] ?? false,
contentLevelAccess: map['CONTENT_LEVEL_ACCESS'] ?? false,
stickersSuggest: map['STICKERS_SUGGEST']?.toString() ?? 'ON',
safeMode: map['SAFE_MODE'] ?? false,
audioTranscriptionEnabled: map['AUDIO_TRANSCRIPTION_ENABLED'] ?? true,
hash: map['hash']?.toString() ?? '',
);
}
String toJson() => jsonEncode({
'SEARCH_BY_PHONE': searchByPhone,
'INCOMING_CALL': incomingCall,
'DOUBLE_TAP_REACTION_DISABLED': doubleTapReactionDisabled,
'SAFE_MODE_NO_PIN': safeModeNoPin,
'DOUBLE_TAP_REACTION_VALUE': doubleTapReactionValue,
'FAMILY_PROTECTION': familyProtection,
'PUSH_DETAILS': pushDetails,
'HIDDEN': hidden,
'CHATS_INVITE': chatsInvite,
'PUSH_NEW_CONTACTS': pushNewContacts,
'UNSAFE_FILES': unsafeFiles,
'INACTIVE_TTL': inactiveTtl,
'SHOW_READ_MARK': showReadMark,
'ALT_KEYBOARD': altKeyboard,
'CONTENT_LEVEL_ACCESS': contentLevelAccess,
'STICKERS_SUGGEST': stickersSuggest,
'SAFE_MODE': safeMode,
'AUDIO_TRANSCRIPTION_ENABLED': audioTranscriptionEnabled,
'hash': hash,
});
factory PrivacyConfig.fromJson(String json) {
try {
final map = jsonDecode(json) as Map<String, dynamic>;
return PrivacyConfig.fromMap(map);
} catch (_) {
return PrivacyConfig.empty();
}
}
static PrivacyConfig empty() {
return const PrivacyConfig(
searchByPhone: 'ALL',
incomingCall: 'CONTACTS',
doubleTapReactionDisabled: false,
safeModeNoPin: false,
familyProtection: 'OFF',
pushDetails: false,
hidden: true,
chatsInvite: 'CONTACTS',
pushNewContacts: false,
unsafeFiles: true,
inactiveTtl: '6M',
showReadMark: true,
altKeyboard: false,
contentLevelAccess: false,
stickersSuggest: 'ON',
safeMode: false,
audioTranscriptionEnabled: true,
hash: '',
);
}
}
class BlockedContact {
final int id;
final String? firstName;
final String? lastName;
final String? baseUrl;
final int? photoId;
final String status;
final int registrationTime;
final int updateTime;
const BlockedContact({
required this.id,
this.firstName,
this.lastName,
this.baseUrl,
this.photoId,
required this.status,
required this.registrationTime,
required this.updateTime,
});
factory BlockedContact.fromMap(Map<dynamic, dynamic> map) {
String? firstName;
String? lastName;
final names = map['names'] as List?;
if (names != null && names.isNotEmpty) {
for (final n in names) {
if (n is Map) {
firstName = n['firstName'] as String?;
lastName = n['lastName'] as String?;
if (n['type'] == 'ONEME') break;
}
}
}
return BlockedContact(
id: map['id'] as int? ?? 0,
firstName: firstName,
lastName: lastName,
baseUrl: map['baseUrl'] as String?,
photoId: map['photoId'] as int?,
status: map['status']?.toString() ?? 'BLOCKED',
registrationTime: map['registrationTime'] as int? ?? 0,
updateTime: map['updateTime'] as int? ?? 0,
);
}
}
class TwoFactorDetails {
final bool enabled;
final String? email;
final String? hint;
const TwoFactorDetails({required this.enabled, this.email, this.hint});
}
enum AuthRequestType {
startAuth('START_AUTH'),
resend('RESEND'),
@@ -182,6 +361,303 @@ class AccountModule {
Stream<LoginStatus> get loginStatusStream => _loginStatusController.stream;
Future<PrivacyConfig> getPrivacyConfig() async {
final accountId = await TokenStorage.getActiveAccountId();
if (accountId != null) {
final saved = await AppDatabase.getPrivacyConfig(accountId);
if (saved != null) {
return PrivacyConfig.fromJson(saved);
}
}
return PrivacyConfig.empty();
}
Future<List<BlockedContact>> getBlockedContacts() async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.contactList, {
'status': 'BLOCKED',
'count': 100,
'from': 0,
});
_checkPacketError(packet, 'getBlockedContacts');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'getBlockedContacts: неожиданный тип payload: ${data.runtimeType}',
);
}
final contacts = data['contacts'] as List?;
if (contacts == null) return [];
return contacts
.whereType<Map>()
.map((c) => BlockedContact.fromMap(c.cast<dynamic, dynamic>()))
.toList();
}
Future<PrivacyConfig> updatePrivacyConfig(
Map<String, dynamic> settings,
) async {
_ensureOnline();
final payload = <dynamic, dynamic>{
'settings': {'user': settings},
};
final packet = await _api.sendRequest(Opcode.config, payload);
_checkPacketError(packet, 'updatePrivacyConfig');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'updatePrivacyConfig: неожиданный тип payload: ${data.runtimeType}',
);
}
final user = data['user'];
if (user is! Map) {
throw Exception('updatePrivacyConfig: отсутствует user в payload');
}
final config = PrivacyConfig.fromMap(user.cast<dynamic, dynamic>());
final accountId = await TokenStorage.getActiveAccountId();
if (accountId != null) {
await AppDatabase.savePrivacyConfig(accountId, config.toJson());
}
return config;
}
// 2FA Creation (when not set)
Future<String> create2faTrack() async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authCreateTrack, {'type': 0});
_checkPacketError(packet, 'create2faTrack');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'create2faTrack: неожиданный тип payload: ${data.runtimeType}',
);
}
final trackId = data['trackId'] as String?;
if (trackId == null) {
throw Exception('create2faTrack: отсутствует trackId');
}
return trackId;
}
Future<void> set2faPassword(String trackId, String password) async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authValidatePassword, {
'trackId': trackId,
'password': password,
});
_checkPacketError(packet, 'set2faPassword');
if (packet.payload != null && packet.payload is! Map) {
throw Exception('set2faPassword: неожиданный ответ');
}
}
Future<void> set2faHint(String trackId, String hint) async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authValidateHint, {
'trackId': trackId,
'hint': hint,
});
_checkPacketError(packet, 'set2faHint');
if (packet.payload != null && packet.payload is! Map) {
throw Exception('set2faHint: неожиданный ответ');
}
}
Future<int> verify2faEmail(String trackId, String email) async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authVerifyEmail, {
'trackId': trackId,
'email': email,
});
_checkPacketError(packet, 'verify2faEmail');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'verify2faEmail: неожиданный тип payload: ${data.runtimeType}',
);
}
final blockingDuration = data['blockingDuration'] as int? ?? 60;
return blockingDuration;
}
Future<String> verify2faCode(String trackId, String code) async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authCheckEmail, {
'trackId': trackId,
'verifyCode': code,
});
_checkPacketError(packet, 'verify2faCode');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'verify2faCode: неожиданный тип payload: ${data.runtimeType}',
);
}
final email = data['email'] as String? ?? '';
return email;
}
Future<ProfileData> confirm2fa({
required String trackId,
required String password,
String? hint,
}) async {
_ensureOnline();
final payload = <dynamic, dynamic>{
'expectedCapabilities': [0, 3, 4],
'trackId': trackId,
'password': password,
};
if (hint != null) payload['hint'] = hint;
final packet = await _api.sendRequest(Opcode.authSet2fa, payload);
_checkPacketError(packet, 'confirm2fa');
return _processProfileUpdate(packet);
}
// 2FA Management (when already set)
Future<String> enter2faPanel() async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authCreateTrack, {'type': 0});
_checkPacketError(packet, 'enter2faPanel');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'enter2faPanel: неожиданный тип payload: ${data.runtimeType}',
);
}
final trackId = data['trackId'] as String?;
if (trackId == null) {
throw Exception('enter2faPanel: отсутствует trackId');
}
return trackId;
}
Future<TwoFactorDetails> get2faDetails(String trackId) async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.auth2faDetails, {
'trackId': trackId,
});
_checkPacketError(packet, 'get2faDetails');
final data = packet.payload;
if (data is! Map) {
throw Exception(
'get2faDetails: неожиданный тип payload: ${data.runtimeType}',
);
}
final password = data['password'] as Map?;
return TwoFactorDetails(
enabled: password?['enabled'] ?? false,
email: password?['email'] as String?,
hint: password?['hint'] as String?,
);
}
Future<void> check2faPassword(String trackId, String password) async {
_ensureOnline();
final packet = await _api.sendRequest(Opcode.authLoginCheckPassword, {
'trackId': trackId,
'password': password,
});
_checkPacketError(packet, 'check2faPassword');
final data = packet.payload;
if (data is Map && data['error'] != null) {
throw Exception('Неверный пароль');
}
}
Future<ProfileData> update2faPassword({
required String trackId,
required String newPassword,
String? hint,
}) async {
_ensureOnline();
final validatePacket = await _api.sendRequest(Opcode.authValidatePassword, {
'trackId': trackId,
'password': newPassword,
});
_checkPacketError(validatePacket, 'update2faPassword: validate');
if (validatePacket.payload != null && validatePacket.payload is! Map) {
throw Exception('update2faPassword: неожиданный ответ при валидации');
}
if (hint != null) {
final hintPacket = await _api.sendRequest(Opcode.authValidateHint, {
'trackId': trackId,
'hint': hint,
});
_checkPacketError(hintPacket, 'update2faPassword: hint');
}
final payload = <dynamic, dynamic>{
'expectedCapabilities': [1, 3],
'trackId': trackId,
'password': newPassword,
};
if (hint != null) payload['hint'] = hint;
final packet = await _api.sendRequest(Opcode.authSet2fa, payload);
_checkPacketError(packet, 'update2faPassword');
return _processProfileUpdate(packet);
}
Future<ProfileData> update2faEmail({
required String trackId,
required String email,
required String code,
}) async {
_ensureOnline();
final verifyPacket = await _api.sendRequest(Opcode.authVerifyEmail, {
'trackId': trackId,
'email': email,
});
_checkPacketError(verifyPacket, 'update2faEmail: verify');
final codePacket = await _api.sendRequest(Opcode.authCheckEmail, {
'trackId': trackId,
'verifyCode': code,
});
_checkPacketError(codePacket, 'update2faEmail: code');
final payload = <dynamic, dynamic>{
'expectedCapabilities': [4],
'trackId': trackId,
};
final packet = await _api.sendRequest(Opcode.authSet2fa, payload);
_checkPacketError(packet, 'update2faEmail');
return _processProfileUpdate(packet);
}
Future<ProfileData> remove2fa(String trackId) async {
_ensureOnline();
final payload = <dynamic, dynamic>{
'expectedCapabilities': [5],
'trackId': trackId,
'remove2fa': true,
};
final packet = await _api.sendRequest(Opcode.authSet2fa, payload);
_checkPacketError(packet, 'remove2fa');
return _processProfileUpdate(packet);
}
Future<ProfileData> _processProfileUpdate(Packet packet) async {
_api.registerPushHandler(Opcode.notifProfile, (p) {});
await for (final push in _api.pushStream.where(
(p) => p.opcode == Opcode.notifProfile,
)) {
final payload = push.payload;
if (payload is Map) {
final profile = payload['profile'];
if (profile is Map) {
final contact = profile['contact'];
if (contact is Map) {
return ProfileData.fromServerMap(contact.cast<dynamic, dynamic>());
}
}
}
}
throw Exception('Не удалось получить обновлённый профиль');
}
Future<RequestCodeResult> requestCode(
String phone, {
String language = 'ru',
+56 -7
View File
@@ -14,6 +14,7 @@ class ProfileData {
final String country;
final int accountStatus;
final int updateTime;
final List<int>? profileOptions;
ProfileData({
required this.id,
@@ -26,6 +27,7 @@ class ProfileData {
required this.country,
required this.accountStatus,
required this.updateTime,
this.profileOptions,
});
factory ProfileData.fromServerMap(Map<dynamic, dynamic> contact) {
@@ -44,6 +46,12 @@ class ProfileData {
lastName = name['lastName'] as String?;
}
final profileOptionsRaw = contact['profileOptions'];
List<int>? profileOptions;
if (profileOptionsRaw is List) {
profileOptions = profileOptionsRaw.map((e) => e as int).toList();
}
return ProfileData(
id: contact['id'] as int,
firstName: firstName,
@@ -55,21 +63,31 @@ class ProfileData {
country: (contact['country'] as String?) ?? '',
accountStatus: (contact['accountStatus'] as int?) ?? 0,
updateTime: (contact['updateTime'] as int?) ?? 0,
profileOptions: profileOptions,
);
}
factory ProfileData.fromDbRow(Map<String, dynamic> row) {
final profileOptionsStr = row['profile_options'] as String?;
List<int>? profileOptions;
if (profileOptionsStr != null && profileOptionsStr.isNotEmpty) {
profileOptions = profileOptionsStr
.split(',')
.map((e) => int.parse(e.trim()))
.toList();
}
return ProfileData(
id: row['id'] as int,
firstName: row['first_name'] as String,
firstName: (row['first_name'] as String?) ?? '',
lastName: row['last_name'] as String?,
phone: row['phone'] as int,
phone: (row['phone'] as int?) ?? 0,
photoId: row['photo_id'] as int?,
baseUrl: row['base_url'] as String?,
baseRawUrl: row['base_raw_url'] as String?,
country: row['country'] as String,
accountStatus: row['account_status'] as int,
updateTime: row['update_time'] as int,
country: (row['country'] as String?) ?? '',
accountStatus: (row['account_status'] as int?) ?? 0,
updateTime: (row['update_time'] as int?) ?? 0,
profileOptions: profileOptions,
);
}
@@ -84,6 +102,7 @@ class ProfileData {
'country': country,
'account_status': accountStatus,
'update_time': updateTime,
'profile_options': profileOptions?.join(','),
};
}
@@ -119,7 +138,7 @@ class AppDatabase {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'komet.db'),
version: 6,
version: 7,
onOpen: (db) => db.execute('PRAGMA foreign_keys = ON'),
onCreate: (db, _) => _createTables(db),
onUpgrade: (db, oldVersion, newVersion) async {
@@ -143,6 +162,11 @@ class AppDatabase {
if (oldVersion < 6) {
await db.execute(_messagesSchema);
}
if (oldVersion < 7) {
await db.execute(
'ALTER TABLE profile ADD COLUMN profile_options TEXT',
);
}
},
);
}
@@ -160,7 +184,8 @@ class AppDatabase {
country TEXT NOT NULL DEFAULT '',
account_status INTEGER NOT NULL DEFAULT 0,
update_time INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 0
is_active INTEGER NOT NULL DEFAULT 0,
profile_options TEXT
)
''');
await db.execute(_syncStateSchema);
@@ -318,6 +343,30 @@ class AppDatabase {
};
}
static Future<void> savePrivacyConfig(
int accountId,
String jsonConfig,
) async {
final db = await _instance;
await db.insert('sync_state', {
'account_id': accountId,
'key': 'privacy_config',
'value': jsonConfig,
}, conflictAlgorithm: ConflictAlgorithm.replace);
}
static Future<String?> getPrivacyConfig(int accountId) async {
final db = await _instance;
final rows = await db.query(
'sync_state',
where: 'account_id = ? AND key = ?',
whereArgs: [accountId, 'privacy_config'],
limit: 1,
);
if (rows.isEmpty) return null;
return rows.first['value'] as String;
}
static Future<void> close() async {
await _db?.close();
_db = null;
-2
View File
@@ -2,7 +2,6 @@ import '../protocol/packet.dart';
import '../utils/logger.dart';
import 'connection.dart';
/// Упаковывает и отправляет пакеты, ведёт счётчик seq.
class PacketSender {
int _seq = 0;
@@ -13,7 +12,6 @@ class PacketSender {
return _seq;
}
/// Отправляет пакет, возвращает присвоенный seq.
int send(Connection connection, int opcode, Map<dynamic, dynamic> payload) {
final seq = _nextSeq();
final data = packPacket(opcode, payload, seq: seq);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
clock:
dependency: transitive
description:
@@ -313,18 +313,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
material_symbols_icons:
dependency: "direct main"
description:
@@ -638,10 +638,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.10"
timezone:
dependency: "direct main"
description: