Implement qBittorrent download management via WebUI API
This commit is contained in:
parent
1b62a7c6e8
commit
26bbacffd2
@ -1,8 +1,151 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/qbittorrent_client.dart';
|
||||
|
||||
class DownloadPage extends StatelessWidget {
|
||||
class DownloadPage extends StatefulWidget {
|
||||
const DownloadPage({super.key});
|
||||
|
||||
@override
|
||||
State<DownloadPage> createState() => _DownloadPageState();
|
||||
}
|
||||
|
||||
class _DownloadPageState extends State<DownloadPage> {
|
||||
List<Map<String, dynamic>> _torrents = [];
|
||||
List<Map<String, dynamic>> _filteredTorrents = [];
|
||||
bool _isLoading = false;
|
||||
String _selectedCategory = '全部';
|
||||
String _downloadSpeed = '0.0 MB/s';
|
||||
String _uploadSpeed = '0.0 MB/s';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadTorrents();
|
||||
}
|
||||
|
||||
Future<void> _loadTorrents() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
final torrents = await QBittorrentClient.getTorrents();
|
||||
|
||||
setState(() {
|
||||
_torrents = torrents;
|
||||
_filterTorrents();
|
||||
_calculateStats();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _filterTorrents() {
|
||||
if (_selectedCategory == '全部') {
|
||||
_filteredTorrents = _torrents;
|
||||
} else if (_selectedCategory == '下载中') {
|
||||
_filteredTorrents =
|
||||
_torrents.where((torrent) {
|
||||
final status = torrent['state'] as String;
|
||||
return status.contains('downloading') ||
|
||||
status.contains('queuedDL') ||
|
||||
status.contains('checkingDL');
|
||||
}).toList();
|
||||
} else if (_selectedCategory == '已完成') {
|
||||
_filteredTorrents =
|
||||
_torrents.where((torrent) {
|
||||
final status = torrent['state'] as String;
|
||||
return status == 'pausedUP' ||
|
||||
status == 'stalledUP' ||
|
||||
status == 'uploading' ||
|
||||
status == 'queuedUP';
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
void _calculateStats() {
|
||||
double totalDownloadSpeed = 0;
|
||||
double totalUploadSpeed = 0;
|
||||
|
||||
for (var torrent in _torrents) {
|
||||
totalDownloadSpeed += (torrent['dlspeed'] as num).toDouble();
|
||||
totalUploadSpeed += (torrent['upspeed'] as num).toDouble();
|
||||
}
|
||||
|
||||
_downloadSpeed = _formatSpeed(totalDownloadSpeed);
|
||||
_uploadSpeed = _formatSpeed(totalUploadSpeed);
|
||||
}
|
||||
|
||||
String _formatSpeed(double speed) {
|
||||
if (speed < 1024) {
|
||||
return '${speed.toStringAsFixed(1)} B/s';
|
||||
} else if (speed < 1024 * 1024) {
|
||||
return '${(speed / 1024).toStringAsFixed(1)} KB/s';
|
||||
} else {
|
||||
return '${(speed / (1024 * 1024)).toStringAsFixed(1)} MB/s';
|
||||
}
|
||||
}
|
||||
|
||||
String _formatSize(int size) {
|
||||
if (size < 1024) {
|
||||
return '$size B';
|
||||
} else if (size < 1024 * 1024) {
|
||||
return '${(size / 1024).toStringAsFixed(1)} KB';
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return '${(size / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||
} else {
|
||||
return '${(size / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||||
}
|
||||
}
|
||||
|
||||
String _getStatusText(String status) {
|
||||
switch (status) {
|
||||
case 'downloading':
|
||||
return '下载中';
|
||||
case 'queuedDL':
|
||||
return '等待下载';
|
||||
case 'checkingDL':
|
||||
return '检查文件';
|
||||
case 'pausedDL':
|
||||
return '已暂停';
|
||||
case 'stalledDL':
|
||||
return '下载停滞';
|
||||
case 'uploading':
|
||||
return '上传中';
|
||||
case 'queuedUP':
|
||||
return '等待上传';
|
||||
case 'checkingUP':
|
||||
return '检查上传';
|
||||
case 'pausedUP':
|
||||
return '已完成';
|
||||
case 'stalledUP':
|
||||
return '上传停滞';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleTorrent(String hash, bool isPaused) async {
|
||||
bool success;
|
||||
if (isPaused) {
|
||||
success = await QBittorrentClient.resumeTorrent(hash);
|
||||
} else {
|
||||
success = await QBittorrentClient.pauseTorrent(hash);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
await _loadTorrents();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteTorrent(String hash, bool deleteFiles) async {
|
||||
final success = await QBittorrentClient.deleteTorrent(
|
||||
hash,
|
||||
deleteFiles: deleteFiles,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
await _loadTorrents();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -13,7 +156,7 @@ class DownloadPage extends StatelessWidget {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh_outlined, size: 24),
|
||||
onPressed: () {},
|
||||
onPressed: _loadTorrents,
|
||||
color: const Color(0xFF1F2937),
|
||||
),
|
||||
],
|
||||
@ -29,7 +172,7 @@ class DownloadPage extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'下载速度',
|
||||
'0.0 MB/s',
|
||||
_downloadSpeed,
|
||||
Icons.arrow_downward,
|
||||
const Color(0xFF3B82F6),
|
||||
),
|
||||
@ -38,7 +181,7 @@ class DownloadPage extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'上传速度',
|
||||
'0.0 MB/s',
|
||||
_uploadSpeed,
|
||||
Icons.arrow_upward,
|
||||
const Color(0xFF22C55E),
|
||||
),
|
||||
@ -47,7 +190,7 @@ class DownloadPage extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'任务数',
|
||||
'0',
|
||||
_torrents.length.toString(),
|
||||
Icons.task_outlined,
|
||||
const Color(0xFF8B5CF6),
|
||||
),
|
||||
@ -61,88 +204,28 @@ class DownloadPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildCategoryTab('全部', true),
|
||||
_buildCategoryTab('下载中', false),
|
||||
_buildCategoryTab('已完成', false),
|
||||
_buildCategoryTab('全部', _selectedCategory == '全部'),
|
||||
_buildCategoryTab('下载中', _selectedCategory == '下载中'),
|
||||
_buildCategoryTab('已完成', _selectedCategory == '已完成'),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 空状态
|
||||
// 下载列表
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 120,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF3F4F6),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.download_for_offline_outlined,
|
||||
size: 64,
|
||||
color: Color(0xFF9CA3AF),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'暂无下载任务',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'从首页推送资源到这里',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF6B7280),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
SizedBox(
|
||||
height: 44,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// 切换到首页
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => const MainPage(),
|
||||
// ),
|
||||
// );
|
||||
child:
|
||||
_isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _filteredTorrents.isEmpty
|
||||
? _buildEmptyState()
|
||||
: ListView.builder(
|
||||
itemCount: _filteredTorrents.length,
|
||||
itemBuilder: (context, index) {
|
||||
final torrent = _filteredTorrents[index];
|
||||
return _buildTorrentItem(torrent);
|
||||
},
|
||||
icon: const Icon(Icons.home, size: 16),
|
||||
label: const Text('去首页推送'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF22C55E),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
vertical: 12,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 0,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -150,6 +233,138 @@ class DownloadPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 120,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF3F4F6),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.download_for_offline_outlined,
|
||||
size: 64,
|
||||
color: Color(0xFF9CA3AF),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
_selectedCategory == '全部' ? '暂无下载任务' : '该分类下暂无任务',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_selectedCategory == '全部' ? '从首页推送资源到这里' : '',
|
||||
style: const TextStyle(fontSize: 14, color: Color(0xFF6B7280)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTorrentItem(Map<String, dynamic> torrent) {
|
||||
final name = torrent['name'] as String;
|
||||
final size = (torrent['size'] as num).toInt();
|
||||
final progress = (torrent['progress'] as num).toDouble() * 100;
|
||||
final state = torrent['state'] as String;
|
||||
final isPaused = state.startsWith('paused') || state.contains('queued');
|
||||
final dlspeed = (torrent['dlspeed'] as num).toDouble();
|
||||
final upspeed = (torrent['upspeed'] as num).toDouble();
|
||||
|
||||
return Card(
|
||||
elevation: 0,
|
||||
margin: const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: const BorderSide(color: Color(0xFFE5E7EB), width: 1),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'${_getStatusText(state)} · ${_formatSize(size)} · ${progress.toStringAsFixed(1)}%',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF6B7280),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (dlspeed > 0 || upspeed > 0) ...[
|
||||
Text(
|
||||
'${_formatSpeed(dlspeed)} / ${_formatSpeed(upspeed)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF6B7280),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
LinearProgressIndicator(
|
||||
value: progress / 100,
|
||||
backgroundColor: const Color(0xFFE5E7EB),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Color(0xFF22C55E),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
minHeight: 8,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isPaused ? Icons.play_arrow : Icons.pause,
|
||||
size: 20,
|
||||
color: const Color(0xFF6B7280),
|
||||
),
|
||||
onPressed:
|
||||
() => _toggleTorrent(torrent['hash'] as String, isPaused),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete_outline,
|
||||
size: 20,
|
||||
color: const Color(0xFFEF4444),
|
||||
),
|
||||
onPressed:
|
||||
() => _deleteTorrent(torrent['hash'] as String, false),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(
|
||||
String title,
|
||||
String value,
|
||||
@ -197,24 +412,33 @@ class DownloadPage extends StatelessWidget {
|
||||
|
||||
Widget _buildCategoryTab(String title, bool isActive) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 8.0),
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? const Color(0xFF22C55E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isActive ? const Color(0xFF22C55E) : const Color(0xFFE5E7EB),
|
||||
width: 1,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedCategory = title;
|
||||
_filterTorrents();
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 8.0),
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? const Color(0xFF22C55E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color:
|
||||
isActive ? const Color(0xFF22C55E) : const Color(0xFFE5E7EB),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isActive ? Colors.white : Color(0xFF1F2937),
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isActive ? Colors.white : Color(0xFF1F2937),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
208
lib/services/qbittorrent_client.dart
Normal file
208
lib/services/qbittorrent_client.dart
Normal file
@ -0,0 +1,208 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class QBittorrentClient {
|
||||
static Future<Map<String, String>?> _login() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final host = prefs.getString('qbittorrent_host') ?? '';
|
||||
final port = prefs.getString('qbittorrent_port') ?? '';
|
||||
final username = prefs.getString('qbittorrent_username') ?? '';
|
||||
final password = prefs.getString('qbittorrent_password') ?? '';
|
||||
final useHttps = prefs.getBool('qbittorrent_use_https') ?? false;
|
||||
|
||||
print(
|
||||
'qBittorrent配置: host=$host, port=$port, username=$username, useHttps=$useHttps',
|
||||
);
|
||||
|
||||
if (host.isEmpty || port.isEmpty) {
|
||||
print('qBittorrent配置不完整,缺少host或port');
|
||||
return null;
|
||||
}
|
||||
|
||||
final scheme = useHttps ? 'https' : 'http';
|
||||
final baseUrl = '$scheme://$host:$port';
|
||||
final client = http.Client();
|
||||
|
||||
try {
|
||||
// 获取CSRF Token
|
||||
print('尝试获取CSRF Token: $baseUrl/api/v2/app/version');
|
||||
final response = await client.get(
|
||||
Uri.parse('$baseUrl/api/v2/app/version'),
|
||||
headers: {'Referer': baseUrl},
|
||||
);
|
||||
|
||||
print(
|
||||
'获取CSRF Token响应: statusCode=${response.statusCode}, headers=${response.headers}',
|
||||
);
|
||||
|
||||
final cookies = response.headers['set-cookie'] ?? '';
|
||||
print('获取到的cookies: $cookies');
|
||||
|
||||
final csrfToken =
|
||||
cookies
|
||||
.split(';')
|
||||
.firstWhere(
|
||||
(cookie) => cookie.startsWith('SID='),
|
||||
orElse: () => '',
|
||||
)
|
||||
.split('=')
|
||||
.last;
|
||||
|
||||
print('获取到的CSRF Token: $csrfToken');
|
||||
|
||||
if (csrfToken.isEmpty) {
|
||||
print('CSRF Token获取失败,为空');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 登录
|
||||
print('尝试登录qBittorrent');
|
||||
final loginResponse = await client.post(
|
||||
Uri.parse('$baseUrl/api/v2/auth/login'),
|
||||
headers: {'Referer': baseUrl, 'X-CSRF-Token': csrfToken},
|
||||
body: {'username': username, 'password': password},
|
||||
);
|
||||
|
||||
print(
|
||||
'登录响应: statusCode=${loginResponse.statusCode}, body=${loginResponse.body}',
|
||||
);
|
||||
|
||||
if (loginResponse.statusCode != 200 || loginResponse.body != 'Ok.') {
|
||||
print(
|
||||
'登录失败,状态码: ${loginResponse.statusCode}, 响应体: ${loginResponse.body}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
print('登录成功');
|
||||
return {'baseUrl': baseUrl, 'cookie': cookies, 'csrfToken': csrfToken};
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('登录异常: $e');
|
||||
print('异常堆栈: $stackTrace');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getTorrents() async {
|
||||
final session = await _login();
|
||||
if (session == null) {
|
||||
print('登录失败,无法获取下载列表');
|
||||
return [];
|
||||
}
|
||||
|
||||
final client = http.Client();
|
||||
final baseUrl = session['baseUrl']!;
|
||||
final cookie = session['cookie']!;
|
||||
final csrfToken = session['csrfToken']!;
|
||||
|
||||
try {
|
||||
print('尝试获取下载列表: $baseUrl/api/v2/torrents/info');
|
||||
final response = await client.get(
|
||||
Uri.parse('$baseUrl/api/v2/torrents/info'),
|
||||
headers: {
|
||||
'Referer': baseUrl,
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Cookie': cookie,
|
||||
},
|
||||
);
|
||||
|
||||
print(
|
||||
'获取下载列表响应: statusCode=${response.statusCode}, body=${response.body}',
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body) as List;
|
||||
print('获取到的下载列表数量: ${data.length}');
|
||||
return data.cast<Map<String, dynamic>>();
|
||||
}
|
||||
print('获取下载列表失败,状态码: ${response.statusCode}');
|
||||
return [];
|
||||
} catch (e, stackTrace) {
|
||||
print('获取下载列表异常: $e');
|
||||
print('异常堆栈: $stackTrace');
|
||||
return [];
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> pauseTorrent(String hash) async {
|
||||
return _torrentAction(hash, 'pause');
|
||||
}
|
||||
|
||||
static Future<bool> resumeTorrent(String hash) async {
|
||||
return _torrentAction(hash, 'resume');
|
||||
}
|
||||
|
||||
static Future<bool> deleteTorrent(
|
||||
String hash, {
|
||||
bool deleteFiles = false,
|
||||
}) async {
|
||||
final session = await _login();
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final client = http.Client();
|
||||
final baseUrl = session['baseUrl']!;
|
||||
final cookie = session['cookie']!;
|
||||
final csrfToken = session['csrfToken']!;
|
||||
|
||||
try {
|
||||
final response = await client.post(
|
||||
Uri.parse('$baseUrl/api/v2/torrents/delete'),
|
||||
headers: {
|
||||
'Referer': baseUrl,
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Cookie': cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {'hashes': hash, 'deleteFiles': deleteFiles ? 'true' : 'false'},
|
||||
);
|
||||
|
||||
return response.statusCode == 200;
|
||||
} catch (e) {
|
||||
print('删除任务失败: $e');
|
||||
return false;
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> _torrentAction(String hash, String action) async {
|
||||
final session = await _login();
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final client = http.Client();
|
||||
final baseUrl = session['baseUrl']!;
|
||||
final cookie = session['cookie']!;
|
||||
final csrfToken = session['csrfToken']!;
|
||||
|
||||
try {
|
||||
final response = await client.post(
|
||||
Uri.parse('$baseUrl/api/v2/torrents/$action'),
|
||||
headers: {
|
||||
'Referer': baseUrl,
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Cookie': cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {'hashes': hash},
|
||||
);
|
||||
|
||||
return response.statusCode == 200;
|
||||
} catch (e) {
|
||||
print('$action任务失败: $e');
|
||||
return false;
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user