feat(app): 添加 ASMR 和磁力链接功能模块
- 新增 asmr_bp 蓝图模块和 AsmrService 服务 - 新增 magnet_bp 蓝图模块和 MagnetService 服务 - 在应用初始化时注册新的蓝图模块 - 将 switch 语句替换为 if-elif 语句进行配置管理 - 在前端界面添加 ASMR 功能入口 - 增加 libtorrent 依赖包 - 修复字符串引号使用问题 - 增加文件重命名异常处理 - 扩展类型注解支持 Union 类型 - 增加请求超时时间到 30 秒 - 优化前端错误处理逻辑
This commit is contained in:
+10
-8
@@ -2,7 +2,7 @@ from flask import Flask
|
|||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from .extensions import db, migrate
|
from .extensions import db, migrate
|
||||||
from .config import DevelopmentConfig, ProductionConfig, TestingConfig
|
from .config import DevelopmentConfig, ProductionConfig, TestingConfig
|
||||||
from .blueprints import torrent_bp, book_bp, music_bp, file_bp, video_bp, image_bp
|
from .blueprints import torrent_bp, book_bp, music_bp, file_bp, video_bp, image_bp, asmr_bp, magnet_bp
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_name="development"):
|
def create_app(config_name="development"):
|
||||||
@@ -10,13 +10,13 @@ def create_app(config_name="development"):
|
|||||||
CORS(app)
|
CORS(app)
|
||||||
app.config.from_object(DevelopmentConfig)
|
app.config.from_object(DevelopmentConfig)
|
||||||
|
|
||||||
match config_name:
|
if config_name == 'production':
|
||||||
case 'production':
|
app.config.from_object(ProductionConfig)
|
||||||
app.config.from_object(ProductionConfig)
|
elif config_name == 'testing':
|
||||||
case 'testing':
|
app.config.from_object(TestingConfig)
|
||||||
app.config.from_object(TestingConfig)
|
else:
|
||||||
case _:
|
app.config.from_object(DevelopmentConfig)
|
||||||
app.config.from_object(DevelopmentConfig)
|
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
@@ -27,5 +27,7 @@ def create_app(config_name="development"):
|
|||||||
app.register_blueprint(file_bp.bp, url_prefix='/api/file')
|
app.register_blueprint(file_bp.bp, url_prefix='/api/file')
|
||||||
app.register_blueprint(video_bp.bp, url_prefix='/api/video')
|
app.register_blueprint(video_bp.bp, url_prefix='/api/video')
|
||||||
app.register_blueprint(image_bp.bp, url_prefix='/api/image')
|
app.register_blueprint(image_bp.bp, url_prefix='/api/image')
|
||||||
|
app.register_blueprint(asmr_bp.bp, url_prefix='/api/asmr')
|
||||||
|
app.register_blueprint(magnet_bp.bp, url_prefix='/api/magnet')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from flask import Blueprint, Response, jsonify
|
||||||
|
|
||||||
|
from app.services.asmr_service import AsmrService
|
||||||
|
|
||||||
|
bp = Blueprint("asmr", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/")
|
||||||
|
def index():
|
||||||
|
asmr_object = AsmrService()
|
||||||
|
return jsonify({"code": 200, "message": asmr_object.get_all()})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from flask import Blueprint, Response, jsonify
|
||||||
|
|
||||||
|
from app.services.magnet_service import MagnetService
|
||||||
|
|
||||||
|
bp = Blueprint("magnet", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/")
|
||||||
|
def index():
|
||||||
|
magnet_object = MagnetService()
|
||||||
|
return jsonify({"code": 200, "message": magnet_object.to_torrent()})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
class AsmrService:
|
||||||
|
API_URL = "https://www.asmrgay.com/api/fs/list"
|
||||||
|
base_params = {
|
||||||
|
"path": "/asmr/中文音声",
|
||||||
|
"password": "",
|
||||||
|
"per_page": 30,
|
||||||
|
"refresh": False
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
return [self.API_URL]
|
||||||
@@ -87,8 +87,7 @@ class BookService:
|
|||||||
if privilege_level > self.setting["privilegeLevel"]:
|
if privilege_level > self.setting["privilegeLevel"]:
|
||||||
print("下载失败,权限等级不够")
|
print("下载失败,权限等级不够")
|
||||||
return
|
return
|
||||||
yield from self.download_file(f"{self.setting["baseUrl"]}{i.select_one(
|
yield from self.download_file(f"{self.setting['baseUrl']}{i.select_one('a')['href']}", name, i.select_one('a').string)
|
||||||
'a')['href']}", name, i.select_one('a').string)
|
|
||||||
# yield from self.download_file(f"{self.setting["baseUrl"]}{i.select_one(
|
# yield from self.download_file(f"{self.setting["baseUrl"]}{i.select_one(
|
||||||
# 'a')['href']}", name, self.legitimate_naming(i.select_one('a').string))
|
# 'a')['href']}", name, self.legitimate_naming(i.select_one('a').string))
|
||||||
except RetryError as e:
|
except RetryError as e:
|
||||||
@@ -106,7 +105,7 @@ class BookService:
|
|||||||
print(dir_name + "/" + file_name)
|
print(dir_name + "/" + file_name)
|
||||||
content_type = response.headers.get('Content-Type', '')
|
content_type = response.headers.get('Content-Type', '')
|
||||||
if content_type not in self.ALLOWED_CONTENT_TYPES:
|
if content_type not in self.ALLOWED_CONTENT_TYPES:
|
||||||
yield f"event: mes_error\ndata: {self.setting["targetDate"]} {file_name}\n\n"
|
yield f"event: mes_error\ndata: {self.setting['targetDate']} {file_name}\n\n"
|
||||||
print("不是可下载文件")
|
print("不是可下载文件")
|
||||||
return
|
return
|
||||||
target_dir = self.download_path / self.setting["targetDate"] / dir_name / file_name
|
target_dir = self.download_path / self.setting["targetDate"] / dir_name / file_name
|
||||||
@@ -115,7 +114,7 @@ class BookService:
|
|||||||
for chunk in response.iter_content(chunk_size=1024):
|
for chunk in response.iter_content(chunk_size=1024):
|
||||||
if chunk:
|
if chunk:
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
yield f"data: {self.setting["targetDate"]} {file_name}\n\n"
|
yield f"data: {self.setting['targetDate']} {file_name}\n\n"
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
yield f"event: error\ndata: {self.setting['targetDate']} {file_name} 网络错误\n\n"
|
yield f"event: error\ndata: {self.setting['targetDate']} {file_name} 网络错误\n\n"
|
||||||
raise # 触发 tenacity 重试
|
raise # 触发 tenacity 重试
|
||||||
@@ -128,7 +127,7 @@ class BookService:
|
|||||||
yield f"data: 不允许下载当天的\n\n"
|
yield f"data: 不允许下载当天的\n\n"
|
||||||
return None
|
return None
|
||||||
# yield f"data: 当前页面 {page}\n\n"
|
# yield f"data: 当前页面 {page}\n\n"
|
||||||
url = f"{self.setting["baseUrl"]}forum.php?mod={self.setting["mod"]}&fid={self.setting["fid"]}&page={page}" # 版块页面地址
|
url = f"{self.setting['baseUrl']}forum.php?mod={self.setting['mod']}&fid={self.setting['fid']}&page={page}" # 版块页面地址
|
||||||
response = self.session.get(url) # 请求
|
response = self.session.get(url) # 请求
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
soup = BeautifulSoup(response.text, 'lxml') # 解析
|
soup = BeautifulSoup(response.text, 'lxml') # 解析
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ class FileService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
new_path = os.path.join(os.path.dirname(entry.path), new_name)
|
new_path = os.path.join(os.path.dirname(entry.path), new_name)
|
||||||
os.rename(entry.path, new_path)
|
try:
|
||||||
|
os.rename(entry.path, new_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
self.result_list.insert(0, new_path)
|
self.result_list.insert(0, new_path)
|
||||||
# return new_path
|
# return new_path
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
from typing import Optional, Callable, List
|
from typing import Optional, Callable, List, Union
|
||||||
|
|
||||||
|
|
||||||
class FolderService:
|
class FolderService:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
paths: List[str] | str,
|
paths: Union[List[str], str],
|
||||||
ignore_dirs: Optional[set] = None,
|
ignore_dirs: Optional[set] = None,
|
||||||
folder_callback: Optional[Callable] = None,
|
folder_callback: Optional[Callable] = None,
|
||||||
file_callback: Optional[Callable] = None,
|
file_callback: Optional[Callable] = None,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class MagnetService:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def to_torrent(self):
|
||||||
|
pass
|
||||||
@@ -2,25 +2,25 @@ import request from "../utils/request.ts"
|
|||||||
|
|
||||||
type ResponseData = Promise<{ code: number, message: Array<never> }>
|
type ResponseData = Promise<{ code: number, message: Array<never> }>
|
||||||
|
|
||||||
export function book_download():ResponseData {
|
export function book_download(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/book/download",
|
url: "/book/download",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function video_findDeduplication():ResponseData {
|
export function video_findDeduplication(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/video/findDeduplication",
|
url: "/video/findDeduplication",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function video_deduplication():ResponseData {
|
export function video_deduplication(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/video/deduplication",
|
url: "/video/deduplication",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function video_hasTorrent():ResponseData {
|
export function video_hasTorrent(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/video/hasTorrent",
|
url: "/video/hasTorrent",
|
||||||
})
|
})
|
||||||
@@ -32,31 +32,31 @@ export function file_rename(): ResponseData {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function torrent_deduplication():ResponseData {
|
export function torrent_deduplication(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/torrent/deduplication",
|
url: "/torrent/deduplication",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function image_export():ResponseData {
|
export function image_export(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/image/export",
|
url: "/image/export",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function image_hasTorrent():ResponseData {
|
export function image_hasTorrent(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/image/hasTorrent",
|
url: "/image/hasTorrent",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function music_rename():ResponseData {
|
export function music_rename(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/music/rename",
|
url: "/music/rename",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test():ResponseData {
|
export function test(): ResponseData {
|
||||||
return request({
|
return request({
|
||||||
url: "/test",
|
url: "/test",
|
||||||
})
|
})
|
||||||
@@ -67,3 +67,9 @@ export function torrent_statistics(): ResponseData {
|
|||||||
url: "/torrent/statistics",
|
url: "/torrent/statistics",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function asmr(): ResponseData {
|
||||||
|
return request({
|
||||||
|
url: "/asmr/",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ axios.defaults.headers["Content-Type"] = "application/json; charset=UTF-8"
|
|||||||
const service: AxiosInstance = axios.create({
|
const service: AxiosInstance = axios.create({
|
||||||
baseURL: "http://192.168.1.32:5000/api/",
|
baseURL: "http://192.168.1.32:5000/api/",
|
||||||
// baseURL: "/api",
|
// baseURL: "/api",
|
||||||
timeout: 10000,
|
timeout: 30000,
|
||||||
})
|
})
|
||||||
|
|
||||||
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, onUnmounted} from "vue";
|
import {ref, onUnmounted} from "vue";
|
||||||
import {
|
import {
|
||||||
|
asmr,
|
||||||
file_rename,
|
file_rename,
|
||||||
music_rename,
|
music_rename,
|
||||||
image_hasTorrent,
|
image_hasTorrent,
|
||||||
@@ -38,7 +39,8 @@ const buttons = [
|
|||||||
{id: 5, title: "判断视频是否有torrent", handle: video_hasTorrent},
|
{id: 5, title: "判断视频是否有torrent", handle: video_hasTorrent},
|
||||||
{id: 6, title: "判断图片是否有torrent", handle: image_hasTorrent},
|
{id: 6, title: "判断图片是否有torrent", handle: image_hasTorrent},
|
||||||
{id: 7, title: "音乐文件重命名", handle: music_rename},
|
{id: 7, title: "音乐文件重命名", handle: music_rename},
|
||||||
{id: 8, title: "种子统计", handle: torrent_statistics}
|
{id: 8, title: "种子统计", handle: torrent_statistics},
|
||||||
|
{id: 9, title: "asmr", handle: asmr}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +66,10 @@ const handleClick = async (btnObj: BtnItem) => {
|
|||||||
}))
|
}))
|
||||||
messages.value.push({id: Date.now() + messages.value.length, text: '已完成'})
|
messages.value.push({id: Date.now() + messages.value.length, text: '已完成'})
|
||||||
messages.value.reverse()
|
messages.value.reverse()
|
||||||
} finally {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -7,3 +7,4 @@ Flask-Migrate
|
|||||||
Flask-SocketIO
|
Flask-SocketIO
|
||||||
Flask-SQLAlchemy
|
Flask-SQLAlchemy
|
||||||
Flask-CORS
|
Flask-CORS
|
||||||
|
libtorrent
|
||||||
Reference in New Issue
Block a user