Files
fuckKomet/lib/api/api_service_media.dart

402 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

part of 'api_service.dart';
extension ApiServiceMedia on ApiService {
void updateProfileText(
String firstName,
String lastName,
String description,
) {
final payload = {
"firstName": firstName,
"lastName": lastName,
"description": description,
};
_sendMessage(16, payload);
}
Future<void> updateProfilePhoto(String firstName, String lastName) async {
try {
final picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image == null) return;
print("Запрашиваем URL для загрузки фото...");
final int seq = _sendMessage(80, {"count": 1});
final response = await messages.firstWhere((msg) => msg['seq'] == seq);
final String uploadUrl = response['payload']['url'];
print("URL получен: $uploadUrl");
print("Загружаем фото на сервер...");
var request = http.MultipartRequest('POST', Uri.parse(uploadUrl));
request.files.add(await http.MultipartFile.fromPath('file', image.path));
var streamedResponse = await request.send();
var httpResponse = await http.Response.fromStream(streamedResponse);
if (httpResponse.statusCode != 200) {
throw Exception("Ошибка загрузки фото: ${httpResponse.body}");
}
final uploadResult = jsonDecode(httpResponse.body);
final String photoToken = uploadResult['photos'].values.first['token'];
print("Фото загружено, получен токен: $photoToken");
print("Привязываем фото к профилю...");
final payload = {
"firstName": firstName,
"lastName": lastName,
"photoToken": photoToken,
"avatarType": "USER_AVATAR",
};
_sendMessage(16, payload);
print("Запрос на смену аватара отправлен.");
} catch (e) {
print("!!! Ошибка в процессе смены аватара: $e");
}
}
Future<void> sendPhotoMessage(
int chatId, {
String? localPath,
String? caption,
int? cidOverride,
int? senderId,
}) async {
try {
XFile? image;
if (localPath != null) {
image = XFile(localPath);
} else {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.gallery);
if (image == null) return;
}
await waitUntilOnline();
final int seq80 = _sendMessage(80, {"count": 1});
final resp80 = await messages.firstWhere((m) => m['seq'] == seq80);
final String uploadUrl = resp80['payload']['url'];
var request = http.MultipartRequest('POST', Uri.parse(uploadUrl));
request.files.add(await http.MultipartFile.fromPath('file', image.path));
var streamed = await request.send();
var httpResp = await http.Response.fromStream(streamed);
if (httpResp.statusCode != 200) {
throw Exception(
'Ошибка загрузки фото: ${httpResp.statusCode} ${httpResp.body}',
);
}
final uploadJson = jsonDecode(httpResp.body) as Map<String, dynamic>;
final Map photos = uploadJson['photos'] as Map;
if (photos.isEmpty) throw Exception('Не получен токен фото');
final String photoToken = (photos.values.first as Map)['token'];
final int cid = cidOverride ?? DateTime.now().millisecondsSinceEpoch;
final payload = {
"chatId": chatId,
"message": {
"text": caption?.trim() ?? "",
"cid": cid,
"elements": [],
"attaches": [
{"_type": "PHOTO", "photoToken": photoToken},
],
},
"notify": true,
};
clearChatsCache();
if (localPath != null) {
_emitLocal({
'ver': 11,
'cmd': 1,
'seq': -1,
'opcode': 128,
'payload': {
'chatId': chatId,
'message': {
'id': 'local_$cid',
'sender': senderId ?? 0,
'time': DateTime.now().millisecondsSinceEpoch,
'text': caption?.trim() ?? '',
'type': 'USER',
'cid': cid,
'attaches': [
{'_type': 'PHOTO', 'url': 'file://$localPath'},
],
},
},
});
}
_sendMessage(64, payload);
} catch (e) {
print('Ошибка отправки фото-сообщения: $e');
}
}
Future<void> sendPhotoMessages(
int chatId, {
required List<String> localPaths,
String? caption,
int? senderId,
}) async {
if (localPaths.isEmpty) return;
try {
await waitUntilOnline();
final int cid = DateTime.now().millisecondsSinceEpoch;
_emitLocal({
'ver': 11,
'cmd': 1,
'seq': -1,
'opcode': 128,
'payload': {
'chatId': chatId,
'message': {
'id': 'local_$cid',
'sender': senderId ?? 0,
'time': DateTime.now().millisecondsSinceEpoch,
'text': caption?.trim() ?? '',
'type': 'USER',
'cid': cid,
'attaches': [
for (final p in localPaths)
{'_type': 'PHOTO', 'url': 'file://$p'},
],
},
},
});
final List<Map<String, String>> photoTokens = [];
for (final path in localPaths) {
final int seq80 = _sendMessage(80, {"count": 1});
final resp80 = await messages.firstWhere((m) => m['seq'] == seq80);
final String uploadUrl = resp80['payload']['url'];
var request = http.MultipartRequest('POST', Uri.parse(uploadUrl));
request.files.add(await http.MultipartFile.fromPath('file', path));
var streamed = await request.send();
var httpResp = await http.Response.fromStream(streamed);
if (httpResp.statusCode != 200) {
throw Exception(
'Ошибка загрузки фото: ${httpResp.statusCode} ${httpResp.body}',
);
}
final uploadJson = jsonDecode(httpResp.body) as Map<String, dynamic>;
final Map photos = uploadJson['photos'] as Map;
if (photos.isEmpty) throw Exception('Не получен токен фото');
final String photoToken = (photos.values.first as Map)['token'];
photoTokens.add({"token": photoToken});
}
final payload = {
"chatId": chatId,
"message": {
"text": caption?.trim() ?? "",
"cid": cid,
"elements": [],
"attaches": [
for (final t in photoTokens)
{"_type": "PHOTO", "photoToken": t["token"]},
],
},
"notify": true,
};
clearChatsCache();
_sendMessage(64, payload);
} catch (e) {
print('Ошибка отправки фото-сообщений: $e');
}
}
Future<void> sendFileMessage(
int chatId, {
String? caption,
int? senderId,
}) async {
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.any,
);
if (result == null || result.files.single.path == null) {
print("Выбор файла отменен");
return;
}
final String filePath = result.files.single.path!;
final String fileName = result.files.single.name;
final int fileSize = result.files.single.size;
await waitUntilOnline();
// Показываем локальное сообщение с файлом сразу, как отправляется
final int cid = DateTime.now().millisecondsSinceEpoch;
_emitLocal({
'ver': 11,
'cmd': 1,
'seq': -1,
'opcode': 128,
'payload': {
'chatId': chatId,
'message': {
'id': 'local_$cid',
'sender': senderId ?? 0,
'time': DateTime.now().millisecondsSinceEpoch,
'text': caption?.trim() ?? '',
'type': 'USER',
'cid': cid,
'attaches': [
{
'_type': 'FILE',
'name': fileName,
'size': fileSize,
'url': 'file://$filePath',
},
],
},
},
});
// Запрашиваем URL для загрузки файла
final int seq87 = _sendMessage(87, {"count": 1});
final resp87 = await messages.firstWhere((m) => m['seq'] == seq87);
if (resp87['payload'] == null ||
resp87['payload']['info'] == null ||
(resp87['payload']['info'] as List).isEmpty) {
throw Exception('Неверный ответ на Opcode 87: отсутствует "info"');
}
final uploadInfo = (resp87['payload']['info'] as List).first;
final String uploadUrl = uploadInfo['url'];
final int fileId = uploadInfo['fileId'];
final String token = uploadInfo['token'];
print('Получен fileId: $fileId, token: $token и URL: $uploadUrl');
// Начинаем heartbeat каждые 5 секунд
Timer? heartbeatTimer;
heartbeatTimer = Timer.periodic(const Duration(seconds: 5), (_) {
_sendMessage(65, {"chatId": chatId, "type": "FILE"});
print('Heartbeat отправлен для загрузки файла');
});
try {
// Загружаем файл
var request = http.MultipartRequest('POST', Uri.parse(uploadUrl));
request.files.add(await http.MultipartFile.fromPath('file', filePath));
var streamed = await request.send();
var httpResp = await http.Response.fromStream(streamed);
if (httpResp.statusCode != 200) {
throw Exception(
'Ошибка загрузки файла: ${httpResp.statusCode} ${httpResp.body}',
);
}
print('Файл успешно загружен на сервер. Ожидаем подтверждение...');
// Ждем уведомления о завершении загрузки (opcode 136)
final uploadCompleteMsg = await messages
.timeout(const Duration(seconds: 30))
.firstWhere(
(msg) =>
msg['opcode'] == 136 && msg['payload']['fileId'] == fileId,
);
print(
'Получено подтверждение загрузки файла: ${uploadCompleteMsg['payload']}',
);
// Останавливаем heartbeat
heartbeatTimer.cancel();
final payload = {
"chatId": chatId,
"message": {
"text": caption?.trim() ?? "",
"cid": cid,
"elements": [],
"attaches": [
{"_type": "FILE", "fileId": fileId},
],
},
"notify": true,
};
clearChatsCache();
_sendMessage(64, payload);
print('Сообщение о файле (Opcode 64) отправлено.');
} finally {
// Гарантированно останавливаем heartbeat в случае ошибки
heartbeatTimer.cancel();
}
} catch (e) {
print('Ошибка отправки файла: $e');
}
}
Future<String> getVideoUrl(int videoId, int chatId, String messageId) async {
await waitUntilOnline();
final payload = {
"videoId": videoId,
"chatId": chatId,
"messageId": messageId,
};
final int seq = _sendMessage(83, payload);
print('Запрашиваем URL для videoId: $videoId (seq: $seq)');
try {
final response = await messages
.firstWhere((msg) => msg['seq'] == seq && msg['opcode'] == 83)
.timeout(const Duration(seconds: 15));
if (response['cmd'] == 3) {
throw Exception(
'Ошибка получения URL видео: ${response['payload']?['message']}',
);
}
final videoPayload = response['payload'] as Map<String, dynamic>?;
if (videoPayload == null) {
throw Exception('Получен пустой payload для видео');
}
String? videoUrl =
videoPayload['MP4_720'] as String? ??
videoPayload['MP4_480'] as String? ??
videoPayload['MP4_1080'] as String? ??
videoPayload['MP4_360'] as String?;
if (videoUrl == null) {
final mp4Key = videoPayload.keys.firstWhere(
(k) => k.startsWith('MP4_'),
orElse: () => '',
);
if (mp4Key.isNotEmpty) {
videoUrl = videoPayload[mp4Key] as String?;
}
}
if (videoUrl != null) {
print('URL для videoId: $videoId успешно получен.');
return videoUrl;
} else {
throw Exception('Не найден ни один MP4 URL в ответе');
}
} on TimeoutException {
print('Таймаут ожидания URL для videoId: $videoId');
throw Exception('Сервер не ответил на запрос видео вовремя');
} catch (e) {
print('Ошибка в getVideoUrl: $e');
rethrow;
}
}
}