Merge pull request #472 from EstrellaXD/3.1-dev

3.1.2
This commit is contained in:
Estrella Pan
2023-09-26 16:14:27 +08:00
committed by GitHub
12 changed files with 269 additions and 211 deletions

View File

@@ -75,6 +75,13 @@ async def enable_rule(bangumi_id: str):
return u_response(resp)
@router.get(path="/refresh/poster/all", response_model=APIResponse, dependencies=[Depends(get_current_user)])
async def refresh_poster():
with TorrentManager() as manager:
resp = manager.refresh_poster()
return u_response(resp)
@router.get("/reset/all", response_model=APIResponse, dependencies=[Depends(get_current_user)])
async def reset_all():
with TorrentManager() as manager:

View File

@@ -9,13 +9,13 @@ logger = logging.getLogger(__name__)
class SeasonCollector(DownloadClient):
def collect_season(self, bangumi: Bangumi, link: str = None):
def collect_season(self, bangumi: Bangumi, link: str = None, limit: int = None):
logger.info(
f"Start collecting {bangumi.official_title} Season {bangumi.season}..."
)
with SearchTorrent() as st:
if not link:
torrents = st.search_season(bangumi)
torrents = st.search_season(bangumi, limit=limit)
else:
torrents = st.get_torrents(link, bangumi.filter.replace(",", "|"))
if self.add_torrent(torrents, bangumi):
@@ -23,6 +23,7 @@ class SeasonCollector(DownloadClient):
bangumi.eps_collect = True
with RSSEngine() as engine:
engine.bangumi.update(bangumi)
engine.torrent.add_all(torrents)
return ResponseModel(
status=True,
status_code=200,
@@ -57,7 +58,7 @@ def eps_complete():
logger.info("Start collecting full season...")
for data in datas:
if not data.eps_collect:
with SeasonCollector() as sc:
sc.collect_season(data)
with SeasonCollector() as collector:
collector.collect_season(data)
data.eps_collect = True
engine.bangumi.update_all(datas)

View File

@@ -1,10 +1,10 @@
import logging
from fastapi.responses import JSONResponse
from module.database import Database
from module.downloader import DownloadClient
from module.models import Bangumi, BangumiUpdate, ResponseModel
from module.parser import TitleParser
logger = logging.getLogger(__name__)
@@ -133,6 +133,18 @@ class TorrentManager(Database):
msg_zh=f"无法找到 id {bangumi_id} 的数据",
)
def refresh_poster(self):
bangumis = self.bangumi.search_all()
for bangumi in bangumis:
if not bangumi.poster_link:
TitleParser().tmdb_poster_parser(bangumi)
self.bangumi.update_all(bangumis)
return ResponseModel(
status_code=200,
status=True,
msg_en=f"Refresh poster link successfully.",
msg_zh=f"刷新海报链接成功。",
)
def search_all_bangumi(self):
datas = self.bangumi.search_all()
@@ -152,3 +164,7 @@ class TorrentManager(Database):
)
else:
return data
if __name__ == '__main__':
with TorrentManager() as manager:
manager.refresh_poster()

View File

@@ -30,8 +30,9 @@ class RequestContent(RequestURL):
torrents.append(
Torrent(name=_title, url=torrent_url, homepage=homepage)
)
if len(torrents) >= limit:
break
if isinstance(limit, int):
if len(torrents) >= limit:
break
return torrents
else:
logger.warning(f"[Network] Failed to get torrents: {_url}")

View File

@@ -36,6 +36,16 @@ class TitleParser:
logger.warning("Please change bangumi info manually.")
return title, season, None, None
@staticmethod
def tmdb_poster_parser(bangumi: Bangumi):
tmdb_info = tmdb_parser(bangumi.official_title, settings.rss_parser.language)
if tmdb_info:
logger.debug(f"TMDB Matched, official title is {tmdb_info.title}")
bangumi.poster_link = tmdb_info.poster_link
else:
logger.warning(f"Cannot match {bangumi.official_title} in TMDB. Use raw title instead.")
logger.warning("Please change bangumi info manually.")
@staticmethod
def raw_parser(raw: str) -> Bangumi | None:
language = settings.rss_parser.language

View File

@@ -44,7 +44,7 @@ class SearchTorrent(RequestContent, RSSAnalyser):
url = search_url(site, keywords)
return url
def search_season(self, data: Bangumi, site: str = "mikan") -> list[Torrent]:
def search_season(self, data: Bangumi, site: str = "mikan", limit: int = None) -> list[Torrent]:
rss_item = self.special_url(data, site)
torrents = self.search_torrents(rss_item)
torrents = self.search_torrents(rss_item, limit=limit)
return [torrent for torrent in torrents if data.title_raw in torrent.name]

View File

@@ -1,129 +1,137 @@
import type { BangumiAPI, BangumiRule } from '#/bangumi';
import type { ApiSuccess } from '#/api';
import type {BangumiAPI, BangumiRule} from '#/bangumi';
import type {ApiSuccess} from '#/api';
export const apiBangumi = {
/**
* 获取所有 bangumi 数据
* @returns 所有 bangumi 数据
*/
async getAll() {
const { data } = await axios.get<BangumiAPI[]>('api/v1/bangumi/get/all');
const result: BangumiRule[] = data.map((bangumi) => (
{
...bangumi,
filter: bangumi.filter.split(','),
rss_link: bangumi.rss_link.split(','),
/**
* 获取所有 bangumi 数据
* @returns 所有 bangumi 数据
*/
async getAll() {
const {data} = await axios.get<BangumiAPI[]>('api/v1/bangumi/get/all');
const result: BangumiRule[] = data.map((bangumi) => (
{
...bangumi,
filter: bangumi.filter.split(','),
rss_link: bangumi.rss_link.split(','),
}
));
return result;
},
/**
* 获取指定 bangumiId 的规则
* @param bangumiId bangumi id
* @returns 指定 bangumi 的规则
*/
async getRule(bangumiId: number) {
const {data} = await axios.get<BangumiAPI>(
`api/v1/bangumi/get/${bangumiId}`
);
const result: BangumiRule = {
...data,
filter: data.filter.split(','),
rss_link: data.rss_link.split(','),
}
));
return result;
},
return result;
},
/**
* 获取指定 bangumiId 的规则
* @param bangumiId bangumi id
* @returns 指定 bangumi 的规则
*/
async getRule(bangumiId: number) {
const { data } = await axios.get<BangumiAPI>(
`api/v1/bangumi/get/${bangumiId}`
);
const result: BangumiRule = {
...data,
filter: data.filter.split(','),
rss_link: data.rss_link.split(','),
/**
* 更新指定 bangumiId 的规则
* @param bangumiId - 需要更新的 bangumi id
* @param bangumiRule
* @returns axios 请求返回的数据
*/
async updateRule(bangumiId: number, bangumiRule: BangumiRule) {
const rule: BangumiAPI = {
...bangumiRule,
filter: bangumiRule.filter.join(','),
rss_link: bangumiRule.rss_link.join(','),
}
const post = omit(rule, ['id'])
const {data} = await axios.patch<ApiSuccess>(
`api/v1/bangumi/update/${bangumiId}`,
post
);
return data;
},
/**
* 删除指定 bangumiId 的数据库规则,会在重新匹配到后重建
* @param bangumiId - 需要删除的 bangumi 的 id
* @param file - 是否同时删除关联文件。
* @returns axios 请求返回的数据
*/
async deleteRule(bangumiId: number | number[], file: boolean) {
let url = 'api/v1/bangumi/delete';
let ids: undefined | number[];
if (typeof bangumiId === 'number') {
url = `${url}/${bangumiId}`;
} else {
url = `${url}/many`;
ids = bangumiId;
}
const {data} = await axios.delete<ApiSuccess>(url, {
data: ids,
params: {
file,
},
});
return data;
},
/**
* 删除指定 bangumiId 的规则。如果 file 为 true则同时删除关联文件。
* @param bangumiId - 需要删除规则的 bangumi 的 id。
* @param file - 是否同时删除关联文件。
* @returns axios 请求返回的数据
*/
async disableRule(bangumiId: number | number[], file: boolean) {
let url = 'api/v1/bangumi/disable';
let ids: undefined | number[];
if (typeof bangumiId === 'number') {
url = `${url}/${bangumiId}`;
} else {
url = `${url}/many`;
ids = bangumiId;
}
const {data} = await axios.delete<ApiSuccess>(url, {
data: ids,
params: {
file,
},
});
return data;
},
/**
* 启用指定 bangumiId 的规则
* @param bangumiId - 需要启用的 bangumi 的 id
*/
async enableRule(bangumiId: number) {
const {data} = await axios.get<ApiSuccess>(
`api/v1/bangumi/enable/${bangumiId}`
);
return data;
},
/**
* 重置所有 bangumi 数据
*/
async resetAll() {
const {data} = await axios.get<ApiSuccess>('api/v1/bangumi/reset/all');
return data;
},
/**
* 刷新所有没有海报的 bangumi 海报
*/
async refreshPoster() {
const {data} = await axios.get<ApiSuccess>('api/v1/bangumi/refresh/poster/all');
return data;
}
return result;
},
/**
* 更新指定 bangumiId 的规则
* @param bangumiId - 需要更新的 bangumi 的 id
* @param bangumiRule
* @returns axios 请求返回的数据
*/
async updateRule(bangumiId: number, bangumiRule: BangumiRule) {
const rule: BangumiAPI = {
...bangumiRule,
filter: bangumiRule.filter.join(','),
rss_link: bangumiRule.rss_link.join(','),
}
const post = omit(rule, ['id'])
const { data } = await axios.patch< ApiSuccess >(
`api/v1/bangumi/update/${bangumiId}`,
post
);
return data;
},
/**
* 删除指定 bangumiId 的数据库规则,会在重新匹配到后重建
* @param bangumiId - 需要删除的 bangumi 的 id
* @param file - 是否同时删除关联文件。
* @returns axios 请求返回的数据
*/
async deleteRule(bangumiId: number | number[], file: boolean) {
let url = 'api/v1/bangumi/delete';
let ids: undefined | number[];
if (typeof bangumiId === 'number') {
url = `${url}/${bangumiId}`;
} else {
url = `${url}/many`;
ids = bangumiId;
}
const { data } = await axios.delete< ApiSuccess >(url, {
data: ids,
params: {
file,
},
});
return data;
},
/**
* 删除指定 bangumiId 的规则。如果 file 为 true则同时删除关联文件。
* @param bangumiId - 需要删除规则的 bangumi 的 id。
* @param file - 是否同时删除关联文件。
* @returns axios 请求返回的数据
*/
async disableRule(bangumiId: number | number[], file: boolean) {
let url = 'api/v1/bangumi/disable';
let ids: undefined | number[];
if (typeof bangumiId === 'number') {
url = `${url}/${bangumiId}`;
} else {
url = `${url}/many`;
ids = bangumiId;
}
const { data } = await axios.delete< ApiSuccess >(url, {
data: ids,
params: {
file,
},
});
return data;
},
/**
* 启用指定 bangumiId 的规则
* @param bangumiId - 需要启用的 bangumi 的 id
*/
async enableRule(bangumiId: number) {
const { data } = await axios.get< ApiSuccess >(
`api/v1/bangumi/enable/${bangumiId}`
);
return data;
},
/**
* 重置所有 bangumi 数据
*/
async resetAll() {
const { data } = await axios.get< ApiSuccess >('api/v1/bangumi/reset/all');
return data;
},
};

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import {Menu, MenuButton, MenuItem, MenuItems} from '@headlessui/vue';
import {AddOne, International, More} from '@icon-park/vue-next';
import {AddOne, International, More, System} from '@icon-park/vue-next';
withDefaults(
defineProps<{
@@ -54,7 +54,7 @@ function abLabel(label: string | (() => string)) {
/>
<MenuButton bg-transparent is-btn btn-click>
<More theme="outline" size="24" fill="#fff"/>
<System theme="outline" size="24" fill="#fff"/>
</MenuButton>
<ab-status :running="running"/>

View File

@@ -7,7 +7,7 @@ import {
Power,
Refresh,
} from '@icon-park/vue-next';
import { ruleTemplate } from "#/bangumi";
import {ruleTemplate} from "#/bangumi";
import type {BangumiRule} from "#/bangumi";
const {t, changeLocale} = useMyI18n();
@@ -18,6 +18,7 @@ const showAddRSS = ref(false);
const searchRule = ref<BangumiRule>()
const {start, pause, shutdown, restart, resetRule} = useProgramStore();
const {refreshPoster} = useBangumiStore();
const items = [
{
@@ -46,12 +47,18 @@ const items = [
},
{
id: 5,
label: () => t('topbar.refresh_poster'),
icon: Refresh,
handle: refreshPoster
},
{
id: 6,
label: () => t('topbar.reset_rule'),
icon: Format,
handle: resetRule,
},
{
id: 6,
id: 7,
label: () => t('topbar.profile.title'),
icon: Me,
handle: () => {

View File

@@ -23,6 +23,7 @@
"restart": "Restart",
"shutdown": "Shutdown",
"reset_rule": "Reset Rule",
"refresh_poster": "Refresh Poster",
"search": {
"placeholder": "Type to search"
},

View File

@@ -23,6 +23,7 @@
"restart": "重启",
"shutdown": "关闭",
"reset_rule": "重置规则",
"refresh_poster": "刷新海报",
"search": {
"placeholder": "输入关键字搜索"
},

View File

@@ -1,88 +1,94 @@
import type { BangumiRule } from '#/bangumi';
import { ruleTemplate } from '#/bangumi';
import type { ApiSuccess } from '#/api';
import type {BangumiRule} from '#/bangumi';
import {ruleTemplate} from '#/bangumi';
import type {ApiSuccess} from '#/api';
export const useBangumiStore = defineStore('bangumi', () => {
const message = useMessage();
const message = useMessage();
const bangumi = ref<BangumiRule[]>();
const editRule = reactive<{
show: boolean;
item: BangumiRule;
}>({
show: false,
item: ruleTemplate,
});
const bangumi = ref<BangumiRule[]>();
const editRule = reactive<{
show: boolean;
item: BangumiRule;
}>({
show: false,
item: ruleTemplate,
});
const { execute: getAll, onResult: onBangumiResult } = useApi(
apiBangumi.getAll
);
const { execute: updateRule, onResult: onUpdateRuleResult } = useApi(
apiBangumi.updateRule
);
const { execute: enableRule, onResult: onEnableRuleResult } = useApi(
apiBangumi.enableRule
);
const { execute: disableRule, onResult: onDisableRuleResult } = useApi(
apiBangumi.disableRule
);
const { execute: deleteRule, onResult: onDeleteRuleResult } = useApi(
apiBangumi.deleteRule
);
const {execute: getAll, onResult: onBangumiResult} = useApi(
apiBangumi.getAll
);
const {execute: updateRule, onResult: onUpdateRuleResult} = useApi(
apiBangumi.updateRule
);
const {execute: enableRule, onResult: onEnableRuleResult} = useApi(
apiBangumi.enableRule
);
const {execute: disableRule, onResult: onDisableRuleResult} = useApi(
apiBangumi.disableRule
);
const {execute: deleteRule, onResult: onDeleteRuleResult} = useApi(
apiBangumi.deleteRule
);
const {execute: refreshPoster, onResult: onRefreshPosterResult} = useApi(
apiBangumi.refreshPoster
);
onBangumiResult((res) => {
function sort(arr: BangumiRule[]) {
return arr.sort((a, b) => b.id - a.id);
onBangumiResult((res) => {
function sort(arr: BangumiRule[]) {
return arr.sort((a, b) => b.id - a.id);
}
const enabled = sort(res.filter((e) => !e.deleted));
const disabled = sort(res.filter((e) => e.deleted));
bangumi.value = [...enabled, ...disabled];
});
function refresh() {
editRule.show = false;
getAll();
}
const enabled = sort(res.filter((e) => !e.deleted));
const disabled = sort(res.filter((e) => e.deleted));
bangumi.value = [...enabled, ...disabled];
});
function refresh() {
editRule.show = false;
getAll();
}
function actionSuccess(apiRes: ApiSuccess) {
message.success(apiRes.msg_en);
refresh();
}
onUpdateRuleResult(actionSuccess);
onDisableRuleResult(actionSuccess);
onEnableRuleResult(actionSuccess);
onDeleteRuleResult(actionSuccess);
function openEditPopup(data: BangumiRule) {
editRule.show = true;
editRule.item = data;
}
function ruleManage(
type: 'disable' | 'delete',
id: number,
deleteFile: boolean
) {
if (type === 'disable') {
disableRule(id, deleteFile);
function actionSuccess(apiRes: ApiSuccess) {
message.success(apiRes.msg_en);
refresh();
}
if (type === 'delete') {
deleteRule(id, deleteFile);
onUpdateRuleResult(actionSuccess);
onDisableRuleResult(actionSuccess);
onEnableRuleResult(actionSuccess);
onDeleteRuleResult(actionSuccess);
onRefreshPosterResult(actionSuccess);
function openEditPopup(data: BangumiRule) {
editRule.show = true;
editRule.item = data;
}
}
return {
bangumi,
editRule,
function ruleManage(
type: 'disable' | 'delete',
id: number,
deleteFile: boolean
) {
if (type === 'disable') {
disableRule(id, deleteFile);
}
if (type === 'delete') {
deleteRule(id, deleteFile);
}
}
getAll,
updateRule,
enableRule,
disableRule,
deleteRule,
openEditPopup,
ruleManage,
};
return {
bangumi,
editRule,
getAll,
updateRule,
enableRule,
disableRule,
deleteRule,
refreshPoster,
openEditPopup,
ruleManage,
};
});