Merge branch 'refs/heads/qb' into electron

# Conflicts:
#	openRenamerBackend/index.ts
#	openRenamerBackend/package.json
#	openRenamerBackend/pnpm-lock.yaml
#	openRenamerFront/pnpm-lock.yaml
#	openRenamerFront/src/App.vue
This commit is contained in:
fanxb 2024-07-01 21:34:38 +08:00
commit f3bcacfc4a
9 changed files with 206 additions and 143 deletions

View File

@ -1,4 +1,4 @@
import { Context } from "koa";
import {Context} from "koa";
import service from "../service/QbService";
const router = {};
@ -7,7 +7,21 @@ const router = {};
*
*/
router["POST /qb/saveQbInfo"] = async function (ctx: Context) {
ctx.body = await service.saveAddress(ctx.request.body);
ctx.body = await service.saveAddress(ctx.request.body);
};
/**
* qb配置
*/
router["GET /qb/config"] = async function (ctx: Context) {
ctx.body = await service.getConfig();
};
/**
* qb配置
*/
router["GET /qb/bt/list"] = async function (ctx: Context) {
ctx.body = await service.getBtList();
};

View File

@ -0,0 +1,28 @@
export default interface BtListItemDto {
hash: string;
/**
*
*/
added_on: number;
/**
* left bytes num
*/
amount_left: number;
/**
* Percentage of file pieces currently available
*/
availability: number;
category: string;
/**
* Amount of transfer data completed (bytes)
*/
completed: number;
/**
* Time (Unix Epoch) when the torrent completed
*/
completion_on: number;
/**
* Absolute path of torrent content (root path for multifile torrents, absolute file path for singlefile torrents)
*/
content_path: string;
}

View File

@ -1,5 +0,0 @@
export default interface QbAddressDto {
address: string;
username: string;
password: string;
}

View File

@ -0,0 +1,22 @@
export default interface QbConfigDto {
address: string;
username: string;
password: string;
valid: boolean;
/**
* qb version,null if config is error
*/
version: string;
/**
* Qbittorrent's download
*/
qbDownloadPath: string;
/**
* Qbittorrent's download path corresponds to current system path
*/
renameQbDownloadPath: string;
/**
* config path to select convenient
*/
configPaths: Array<string>;
}

View File

@ -9,9 +9,8 @@ import handleError from "./middleware/handleError";
import init from "./middleware/init";
import SqliteUtil from './util/SqliteHelper';
import log from './util/LogUtil';
import {updateQbInfo} from './util/QbApiUtil';
import TimeUtil from "./util/TimeUtil";
import QbService from './service/QbService';
import qbService from "./service/QbService";
console.log(config);
@ -33,7 +32,7 @@ app.use(handleError);
app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
(async () => {
await SqliteUtil.createPool();
await updateQbInfo(null, null);
await qbService.init();
app.listen(config.port);
log.info(`server listened `, config.port);
})();

View File

@ -1,17 +1,52 @@
import QbAddressDto from "../entity/dto/QbAddressDto";
import { tryLogin, updateQbInfo } from '../util/QbApiUtil';
import QbConfigDto from "../entity/dto/QbConfigDto";
import {tryLogin, get, post, updateQbInfo, getQbInfo} from '../util/QbApiUtil';
import GlobalConfigService from "./GlobalConfigService";
import GlobalConfig from "../entity/po/GlobalConfig";
import BtListItemDto from "../entity/dto/BtListItemDto";
class QbService {
static async saveAddress(body: QbAddressDto) {
await tryLogin(body.address, body.username, body.password, false);
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbAddress", body.address, "qbAdress"));
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbUsername", body.username, ""));
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbPassword", body.password, ""));
await updateQbInfo(body, true);
}
/**
*
* @param body
*/
static async saveAddress(body: QbConfigDto): Promise<QbConfigDto> {
if (body.address.endsWith("/")) {
body.address = body.address.substring(0, body.address.length - 1);
}
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbConfig", JSON.stringify(body), "qb config"));
updateQbInfo(body);
body.valid = await tryLogin();
body.version = body ? (await get("/app/version", null)) : null;
return body;
}
a
/**
*
*/
static async getConfig(): Promise<QbConfigDto> {
return getQbInfo();
}
/**
* get torrents list from qb
*/
static async getBtList(): Promise<Array<BtListItemDto>> {
let res = await get("/api/v2/torrents/info?category=&sort=added_on", null);
return res;
}
/**
*
*/
static async init() {
let config = await GlobalConfigService.getVal("qbConfig");
let qbInfo: QbConfigDto = config == null ? {} : JSON.parse(config);
updateQbInfo(qbInfo);
qbInfo.valid = await tryLogin();
qbInfo.version = qbInfo.valid ? (await get("/app/version", null)) : null;
return qbInfo;
}
}
export default QbService;

View File

@ -1,102 +1,83 @@
import { Method } from "axios";
import {Method} from "axios";
import axios from "axios";
import QbAddressDto from "../entity/dto/QbAddressDto";
import querystring from "querystring";
import QbConfigDto from "../entity/dto/QbConfigDto";
import GlobalService from '../service/GlobalConfigService';
import { setUncaughtExceptionCaptureCallback } from "process";
//qb状态true正常false:无法访问
let qbStatus = true;
let qbInfo: QbAddressDto = null;
let cookie: string = null;
let qbInfo: QbConfigDto = null;
let cookie: any = null;
export function getQbStatus() {
return qbStatus;
export function updateQbInfo(info: QbConfigDto) {
qbInfo = info;
}
export async function updateQbInfo(info: QbAddressDto, status: boolean) {
if (!info) {
let obj = await GlobalService.getMultVal(["qbAddress", "qbUsername", "qbPassword"]);
if (!obj.qbAddress) {
qbStatus = false;
return;
}
qbInfo.address = obj.qbAddress;
qbInfo.username = obj.qbUsername;
qbInfo.password = obj.qbPassword;
} else {
qbInfo = info;
}
if (status) {
qbStatus = status;
}
axios.defaults.baseURL = qbInfo.address;
export function getQbInfo() {
return qbInfo;
}
export function get() {
export async function get(url: string, data: object) {
return await request("get", url, data, null, false);
}
export function post() {
export async function post(url: string, data: object, isForm = false) {
return await request("post", url, null, data, isForm);
}
async function request(method: Method, url: string, query: any, body: any, isForm = false) {
if (!qbStatus) {
throw new Error("qbittorrent无法连接请检查配置");
}
let isTryLogin = false;
while (true) {
let headers = { "Cookie": cookie };
if (isForm) {
headers['content-type'] = "multipart/form-data";
} else if (method == "post") {
headers['content-type'] = "application/json";
}
let res = await axios.request({
baseURL: qbInfo.address,
url: url,
method,
params: query,
data: body,
headers,
});
if (res.status == 200) {
return res.data;
} if (res.status == 403) {
if (isTryLogin) {
throw new Error("qb用户名密码设置有误");
} else {
await tryLogin(qbInfo.address, qbInfo.username, qbInfo.password, true);
isTryLogin = true;
}
} else {
throw new Error("请求报错:" + res.data);
}
}
if (!qbInfo.valid) {
throw new Error("qbittorrent无法连接请检查配置");
}
let isTryLogin = false;
while (true) {
let headers = {"Cookie": cookie};
if (isForm) {
headers['content-type'] = "multipart/form-data";
} else if (method == "post") {
headers['content-type'] = "application/json";
}
let res = await axios.request({
baseURL: qbInfo.address,
url: "/api/v2" + url,
method,
params: query,
data: body,
headers,
});
if (res.status == 200) {
return res.data;
}
if (res.status == 403) {
if (isTryLogin) {
throw new Error("qb用户名密码设置有误");
} else {
await tryLogin();
isTryLogin = true;
}
} else {
throw new Error("请求报错:" + res.data);
}
}
}
export async function tryLogin(address: string, username: string, password: string, updateStatus: boolean): Promise<void> {
let body = { username, password };
try {
let res = await axios.post(address + "/api/v2/auth/login", body, {
headers: { "Content-Type": "multipart/form-data;boundary=--------------------------125002698093981740970152" }
});
let success = res.data.toLocaleLowerCase().contains('ok');
if (updateStatus) {
qbStatus = success;
}
if (!success) {
throw new Error("登录失败");
} else {
cookie = res.headers['Cookie'];
}
} catch (error) {
console.error("登录报错:", error);
if (updateStatus) {
qbStatus = false;
}
throw new Error("登录出错");
}
export async function tryLogin(): Promise<boolean> {
if (qbInfo == null || qbInfo.address == null || qbInfo.address == "") {
return false;
}
let body = {username: qbInfo.username, password: qbInfo.password};
try {
let res = await axios.post(qbInfo.address + `/api/v2/auth/login`, querystring.stringify(body), {
headers: {"Content-Type": "application/x-www-form-urlencoded"}
});
let success = res.data.toLocaleLowerCase().indexOf('ok') > -1;
if (success) {
cookie = res.headers['set-cookie'];
}
qbInfo.valid = success;
return success;
} catch (error) {
console.error("登录报错:", error);
return false;
}
}

View File

@ -49,17 +49,6 @@ export default {
};
},
async beforeCreate() {
console.log("beforeCreate");
let queryMap = {};
location.search.substring(1).split("&").forEach(item => {
let arr = item.split("=");
queryMap[arr[0]] = arr[1];
})
if (queryMap.port) {
window.baseUrl = "http://localhost:" + queryMap.port;
} else {
window.baseUrl = "";
}
window.token = localStorage.getItem("token");
window.isWindows = await httpUtil.get("/file/isWindows");
},

View File

@ -1,13 +1,24 @@
<template>
<div>配置qb</div>
<div class="item">
<div class="left">qb信息</div>
<div class="right">{{ qbInfo }}<el-button @click="editInfo = true">编辑</el-button></div>
</div>
<el-form v-if="editInfo" :model="qbBody" label-width="4em">
<el-form-item label="qb地址"><el-input type="text" v-model="qbBody.address" placeholder="例如:http://192.168.1.4:8080" /></el-form-item>
<el-form-item label="用户名"><el-input type="text" v-model="qbBody.username" placeholder="qb访问用户名" /></el-form-item>
<el-form-item label="密码"><el-input type="password" v-model="qbBody.password" placeholder="qb访问密码" /></el-form-item>
<el-form :model="data.qbConfig" label-width="8em">
<el-form-item label="qb版本">
{{ data.qbConfig.version ? data.qbConfig.version : "配置错误,无法访问" }}
</el-form-item>
<el-form-item label="访问地址">
<el-input type="text" v-model="data.qbConfig.address" placeholder="例如:http://localhost:8080(仅支持v4.1及以上)"/>
</el-form-item>
<el-form-item label="用户名">
<el-input type="text" v-model="data.qbConfig.username" placeholder="qb访问用户名"/>
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="data.qbConfig.password" placeholder="qb访问密码"/>
</el-form-item>
<el-form-item label="qb下载路径">
<el-input type="text" v-model="data.qbConfig.qbDownloadPath" placeholder="qb下载路径(qb中选择的下载路径)"/>
</el-form-item>
<el-form-item label="对应本系统路径">
<el-input type="text" v-model="data.qbConfig.renameQbDownloadPath" placeholder="qb下载路径对应到本软件中的路径"/>
</el-form-item>
<div style="text-align: center">
<el-button type="" @click="editInfo = false">取消</el-button>
<el-button type="primary" @click="submitQb">提交</el-button>
@ -16,34 +27,21 @@
</template>
<script setup>
import { ref, reactive, onMounted, computed } from "vue";
import {ref, reactive, onMounted, computed} from "vue";
import http from "@/utils/HttpUtil";
//
const qbBody = reactive({
address: "",
username: "",
password: "",
const data = reactive({
qbConfig: {},
});
//
let downloadConfig = reactive({});
//qb访
let qbReach = ref(true);
let editInfo = ref(false);
const qbInfo = computed(() => {
if (downloadConfig.qbAddress) {
return downloadConfig.qbAddress + " 用户名:" + downloadConfig.qbUsername;
} else {
return "尚未配置";
}
});
onMounted(async () => {
downloadConfig = reactive(await http.post("/config/multCode", null, ["qbAddress", "qbUsername", "qbPassword"]));
data.qbConfig = await http.get("/qb/config");
});
async function submitQb() {
let res = await http.post("");
data.qbConfig = await http.post("/qb/saveQbInfo", null, data.qbConfig);
}
</script>
@ -52,12 +50,14 @@ async function submitQb() {
display: flex;
text-align: left;
padding-bottom: 0.5em;
.left {
width: 6em;
font-weight: 600;
}
.right {
flex: 1;
}
}
</style>
</style>