Files
Class-Widgets/plugin_plaza.py
2025-05-29 22:29:58 +08:00

802 lines
34 KiB
Python
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.

import json
import sys
from datetime import datetime
from random import shuffle
from PyQt5 import uic
from PyQt5.QtCore import QSize, Qt, QTimer, QUrl, QStringListModel, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap, QDesktopServices
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QGridLayout, QSpacerItem, QSizePolicy, QWidget, \
QScroller, QCompleter
from loguru import logger
from qfluentwidgets import MSFluentWindow, FluentIcon as fIcon, NavigationItemPosition, TitleLabel, \
ImageLabel, StrongBodyLabel, HyperlinkLabel, CaptionLabel, PrimaryPushButton, HorizontalFlipView, \
InfoBar, InfoBarPosition, SplashScreen, MessageBoxBase, TransparentToolButton, BodyLabel, \
PrimarySplitPushButton, RoundMenu, Action, PipsPager, TextBrowser, CardWidget, \
IndeterminateProgressRing, ComboBox, ProgressBar, SmoothScrollArea, SearchLineEdit, HyperlinkButton, \
MessageBox, SwitchButton, SubtitleLabel
import conf
import list_ as l
import network_thread as nt
from conf import base_directory
from file import config_center
from plugin import p_loader
from utils import restart, calculate_size
import platform
from loguru import logger
# 适配高DPI缩放
if platform.system() == 'Windows' and platform.release() not in ['7', 'XP', 'Vista']:
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
else:
logger.warning('不兼容的系统,跳过高DPI标识')
CONF_PATH = f"{base_directory}/plugins/plugins_from_pp.json"
PLAZA_REPO_URL = "https://raw.githubusercontent.com/Class-Widgets/plugin-plaza/"
PLAZA_REPO_DIR = "https://api.github.com/repos/Class-Widgets/plugin-plaza/contents/Plugins"
TEST_DOWNLOAD_LINK = "https://dldir1.qq.com/qqfile/qq/PCQQ9.7.17/QQ9.7.17.29225.exe"
restart_tips_flag = False # 重启提示
plugins_data = {} # 仓库插件信息
local_plugins_version = {} # 本地插件版本
download_progress = [] # 下载线程
installed_plugins = [] # 已安装插件通过PluginPlaza获取
tags = ['示例', '信息展示', '学习', '测试', '工具', '自动化'] # 测试用TAG
recommend_plugins = ['cw-example-plugin'] # 推荐插件通过PluginPlaza获取
search_items = []
SELF_PLUGIN_VERSION = config_center.read_conf('Plugin', 'version') # 自身版本号
SEARCH_FIELDS = ["name", "description", "tag", "author"] # 搜索字段
class TagLink(HyperlinkButton): # 标签链接
def __init__(self, text, parent=None):
super().__init__(parent)
self.parent = parent
self.tag = text
self.setText(text)
self.setIcon(fIcon.SEARCH)
self.setFixedHeight(30)
self.clicked.connect(self.search_tag)
def search_tag(self):
self.parent.search_plugin.setText(self.tag)
self.parent.search_plugin.searchSignal.emit(self.tag) # 发射搜索信号
class downloadProgressBar(InfoBar): # 下载进度条(创建下载进程)
def __init__(self, url=TEST_DOWNLOAD_LINK, branch='main', name="Test", parent=None):
global download_progress
self.p_name = url.split('/')[4] # repo
# user = url.split('/')[3]
self.name = name
self.url = f'{url}/archive/refs/heads/{branch}.zip'
super().__init__(icon=fIcon.DOWNLOAD,
title='',
content=f"正在下载 {name} (~ ̄▽ ̄))",
orient=Qt.Horizontal,
isClosable=False,
position=InfoBarPosition.TOP,
duration=-1,
parent=parent
)
self.setCustomBackgroundColor('white', '#202020')
self.bar = ProgressBar()
self.bar.setFixedWidth(300)
self.cancelBtn = HyperlinkLabel()
self.cancelBtn.setText("取消")
self.cancelBtn.clicked.connect(self.cancelDownload)
self.addWidget(self.bar)
self.addWidget(self.cancelBtn)
# 开始下载
download_progress.append(self.p_name)
self.download(self.url)
def download(self, url): # 接受下载连接并开始任务
self.download_thread = nt.DownloadAndExtract(url, self.p_name)
# self.download_thread = nt.DownloadAndExtract(TEST_DOWNLOAD_LINK, self.p_name)
self.download_thread.progress_signal.connect(lambda progress: self.bar.setValue(int(progress))) # 下载进度
self.download_thread.status_signal.connect(self.detect_status) # 判断状态
self.download_thread.start()
def cancelDownload(self):
global download_progress
download_progress.remove(self.p_name)
self.download_thread.stop()
self.download_thread.deleteLater()
self.close()
def detect_status(self, status):
if status == "DOWNLOADING":
self.content = f"正在下载 {self.name} (~ ̄▽ ̄))"
elif status == "EXTRACTING":
self.content = f"正在解压 {self.name} ( •̀ ω •́ )✧)"
elif status == "DONE":
self.download_finished()
elif status.startswith("ERROR"):
self.download_error(status[6:])
else:
pass
def download_finished(self):
global download_progress
download_progress.remove(self.p_name)
add2save_plugin(self.p_name) # 保存到配置
self.download_thread.finished.emit()
self.download_thread.deleteLater()
InfoBar.success(
title='下载成功!',
content=f"下载 {self.name} 成功!",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=5000,
parent=self.parent()
)
if not restart_tips_flag: # 重启提示
self.parent().restart_tips()
self.close()
def download_error(self, error_info):
global download_progress
download_progress.remove(self.p_name)
InfoBar.error(
title='下载失败(っ °Д °;)っ',
content=f"{error_info}",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=5000,
parent=self.parent()
)
self.close()
def install_plugin(parent, p_name, data):
plugin_ver = str(data.get('plugin_ver'))
if plugin_ver != SELF_PLUGIN_VERSION: # 插件版本不匹配
if plugin_ver > SELF_PLUGIN_VERSION:
content = (f'此插件版本({plugin_ver})高于当前设备中 Class Widgets 兼容的插件版本({SELF_PLUGIN_VERSION}\n'
f'请更新 Class Widgets 后再尝试安装此插件。')
else:
content = (f'此插件版本({plugin_ver})低于当前设备中 Class Widgets 兼容的插件版本({SELF_PLUGIN_VERSION}\n'
f'可能是插件缺乏维护请联系插件作者更新插件或在社区GitHub、QQ群中提出问题。')
cc = MessageBox(
"本插件不兼容当前版本的 Class Widgets",
f"{content}\n\n不建议安装此插件,否则将出现不可预料(包括崩溃、闪退等故障)的问题。",
parent
) # 兼容性检查窗口
cc.yesButton.setText("取消安装")
cc.cancelButton.setText("强制安装(不建议)")
if cc.exec(): # 取消安装
return False
if p_name not in download_progress: # 如果正在下载
url = data.get("url")
branch = data.get("branch")
title = data.get("name")
di = downloadProgressBar(
url=f"{url}",
branch=branch,
name=title,
parent=parent
)
di.show()
return True
return False
class PluginDetailPage(MessageBoxBase): # 插件详情页面
def __init__(self, icon, title, content, tag, version, author, url, data=None, parent=None):
super().__init__(parent)
self.data = data
self.branch = data.get("branch")
self.title = title
self.parent = parent
self.url = url
self.p_name = url.split('/')[-1] # repo
author_url = '/'.join(url.rsplit('/', 2)[:-1])
self.init_ui()
self.download_readme()
scroll_area_widget = self.findChild(QVBoxLayout, 'verticalLayout_9')
self.iconWidget = self.findChild(ImageLabel, 'pluginIcon')
self.iconWidget.setImage(icon)
self.iconWidget.setFixedSize(100, 100)
self.iconWidget.setBorderRadius(8, 8, 8, 8)
self.titleLabel = self.findChild(TitleLabel, 'titleLabel') # 标题
self.titleLabel.setText(title)
self.contentLabel = self.findChild(CaptionLabel, 'descLabel') # 描述
self.contentLabel.setText(content)
self.tagLabel = self.findChild(HyperlinkLabel, 'tagButton') # tag
self.tagLabel.setText(tag)
self.versionLabel = self.findChild(BodyLabel, 'versionLabel') # 版本
self.versionLabel.setText(version)
self.authorLabel = self.findChild(HyperlinkLabel, 'authorButton') # 作者
self.authorLabel.setText(author)
self.authorLabel.setUrl(author_url)
self.openGitHub = self.findChild(TransparentToolButton, 'openGitHub') # 打开连接
self.openGitHub.setIcon(fIcon.LINK)
self.openGitHub.setIconSize(QSize(18, 18))
self.openGitHub.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url)))
self.installButton = self.findChild(PrimarySplitPushButton, 'installButton')
self.installButton.setText(" 安装 ")
self.installButton.setIcon(fIcon.DOWNLOAD)
self.installButton.clicked.connect(self.install)
if self.p_name in download_progress: # 如果正在下载
self.installButton.setText(" 安装中 ")
self.installButton.setEnabled(False)
if self.p_name in installed_plugins: # 如果已安装
self.installButton.setText(" 已安装 ")
self.installButton.setEnabled(False)
if self.p_name in local_plugins_version: # 如果本地版本低于仓库版本
print(local_plugins_version[self.p_name], version)
if local_plugins_version[self.p_name] < version:
self.installButton.setText("更新")
self.installButton.setIcon(fIcon.SYNC)
self.installButton.setEnabled(True)
menu = RoundMenu(parent=self.installButton)
menu.addActions([
Action(fIcon.DOWNLOAD, "为 Class Widgets 安装", triggered=self.install),
Action(fIcon.LINK, "下载到本地",
triggered=lambda: QDesktopServices.openUrl(QUrl(f"{url}/releases/latest")))
])
self.installButton.setFlyout(menu)
self.readmePage = TextBrowser(self)
self.readmePage.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.readmePage.setReadOnly(True)
scroll_area_widget.addWidget(self.readmePage)
def install(self):
if install_plugin(self.parent, self.p_name, self.data):
self.installButton.setText(" 安装中 ")
self.installButton.setEnabled(False)
def download_readme(self):
def display_readme(markdown_text):
self.readmePage.setMarkdown(markdown_text)
if self.data is None:
self.download_thread = nt.getReadme(f"{replace_to_file_server(self.url)}/README.md")
else:
self.download_thread = nt.getReadme(f"{replace_to_file_server(self.url, self.data['branch'])}/README.md")
self.download_thread.html_signal.connect(display_readme)
self.download_thread.start()
def init_ui(self):
# 加载ui文件
self.temp_widget = QWidget()
uic.loadUi(f'{base_directory}/view/pp/plugin_detail.ui', self.temp_widget)
self.viewLayout.addWidget(self.temp_widget)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
# 隐藏原有按钮
self.yesButton.hide()
self.cancelButton.hide()
self.buttonGroup.hide()
# 自定关闭按钮
self.closeButton = self.findChild(TransparentToolButton, 'closeButton')
self.closeButton.setIcon(fIcon.CLOSE)
self.closeButton.clicked.connect(self.close)
self.widget.setMinimumWidth(875)
self.widget.setMinimumHeight(625)
class PluginCard_Horizontal(CardWidget): # 插件卡片(横向)
def __init__(
self, icon='img/plaza/plugin_pre.png', title='Plugin Name', content='Description...', tag='Unknown',
version='1.0.0', author="CW Support",
url="https://github.com/RinLit-233-shiroko/cw-example-plugin", data=None, parent=None):
super().__init__(parent)
self.icon = icon
self.title = title
self.plugin_ver = data.get('plugin_ver')
self.parent = parent
self.tag = tag
self.branch = data.get("branch")
self.url = url
self.p_name = url.split('/')[-1] # repo
self.data = data
author_url = '/'.join(self.url.rsplit('/', 2)[:-1])
self.iconWidget = ImageLabel(icon) # 插件图标
self.titleLabel = StrongBodyLabel(title, self) # 插件名
self.versionLabel = CaptionLabel(version, self) # 插件版本
self.authorLabel = HyperlinkLabel() # 插件作者
self.contentLabel = CaptionLabel(content, self) # 插件描述
self.installButton = PrimaryPushButton()
# layout
self.hBoxLayout = QHBoxLayout()
self.hBoxLayout_Title = QHBoxLayout()
self.hBoxLayout_Author = QHBoxLayout()
self.vBoxLayout = QVBoxLayout()
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.setFixedHeight(110)
self.setMinimumWidth(250)
self.authorLabel.setText(author)
self.authorLabel.setUrl(author_url)
self.authorLabel.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed)
self.iconWidget.setFixedSize(84, 84)
self.iconWidget.setBorderRadius(5, 5, 5, 5) # 圆角
self.contentLabel.setTextColor("#606060", "#d2d2d2")
self.contentLabel.setWordWrap(True)
self.versionLabel.setTextColor("#999999", "#999999")
self.titleLabel.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed)
self.installButton.setText("安装")
self.installButton.setMaximumSize(100, 36)
self.installButton.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.installButton.setIcon(fIcon.DOWNLOAD)
self.installButton.clicked.connect(self.install)
if self.p_name in installed_plugins: # 如果已安装
self.installButton.setText("已安装")
self.installButton.setEnabled(False)
if self.p_name in local_plugins_version: # 如果本地版本低于仓库版本
print(local_plugins_version[self.p_name], version)
if local_plugins_version[self.p_name] < version:
self.installButton.setText("更新")
self.installButton.setIcon(fIcon.SYNC)
self.installButton.setEnabled(True)
self.hBoxLayout.setContentsMargins(20, 11, 11, 11)
self.hBoxLayout.setSpacing(15)
self.hBoxLayout.addWidget(self.iconWidget)
self.blank = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.vBoxLayout.setContentsMargins(0, 5, 0, 5)
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.addLayout(self.hBoxLayout_Title)
self.vBoxLayout.addLayout(self.hBoxLayout_Author)
self.vBoxLayout.addItem(self.blank)
self.vBoxLayout.addWidget(self.contentLabel, 0, Qt.AlignmentFlag.AlignTop)
self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
self.hBoxLayout.addLayout(self.vBoxLayout)
self.hBoxLayout.addWidget(self.installButton)
self.setLayout(self.hBoxLayout)
self.hBoxLayout_Title.setSpacing(12)
self.hBoxLayout_Title.addWidget(self.titleLabel, 0, Qt.AlignmentFlag.AlignVCenter)
self.hBoxLayout_Title.addWidget(self.versionLabel, 0, Qt.AlignmentFlag.AlignVCenter)
self.hBoxLayout_Author.addWidget(self.authorLabel, 0, Qt.AlignmentFlag.AlignLeft)
def install(self):
install_plugin(self.parent, self.p_name, self.data)
def set_img(self, img):
try:
self.icon = img
self.iconWidget.setImage(img)
self.iconWidget.setFixedSize(84, 84)
except Exception as e:
logger.error(f"设置插件图片失败: {e}")
def show_detail(self):
w = PluginDetailPage(
icon=self.icon, title=self.title, content=self.contentLabel.text(),
tag=self.tag, version=self.versionLabel.text(), author=self.authorLabel.text(),
url=self.url, data=self.data, parent=self.parent
)
w.exec()
class PluginPlaza(MSFluentWindow):
closed = pyqtSignal()
def __init__(self):
super().__init__()
self.splashScreen = None
global installed_plugins
try:
with open(CONF_PATH, 'r', encoding='utf-8') as file:
installed_plugins = json.load(file).get('plugins')
# 校验
for plugin in installed_plugins:
if plugin not in p_loader.plugins_name:
logger.warning(f"已在插件广场安装的插件 {plugin} 未找到,可能已遭删除")
installed_plugins.remove(plugin)
except Exception as e:
logger.error(f"读取已安装的插件失败: {e}")
try:
self.homeInterface = uic.loadUi(f'{base_directory}/view/pp/home.ui') # 首页
self.homeInterface.setObjectName("homeInterface")
self.latestsInterface = uic.loadUi(f'{base_directory}/view/pp/latests.ui') # 最新更新
self.latestsInterface.setObjectName("latestInterface")
self.settingsInterface = uic.loadUi(f'{base_directory}/view/pp/settings.ui') # 设置
self.settingsInterface.setObjectName("settingsInterface")
self.searchInterface = uic.loadUi(f'{base_directory}/view/pp/search.ui') # 搜索
self.searchInterface.setObjectName("searchInterface")
load_local_plugins_version() # 加载本地插件版本
self.init_nav()
self.init_window()
self.get_pp_data()
self.get_banner_img()
except Exception as e:
logger.error(f'初始化插件广场时发生错误:{e}')
def load_all_interface(self):
self.setup_homeInterface()
self.setup_latestInterface()
self.setup_settingsInterface()
self.setup_searchInterface()
def setup_latestInterface(self): # 初始化最新更新
latest_scroll = self.latestsInterface.findChild(SmoothScrollArea, 'latest_scroll')
QScroller.grabGesture(latest_scroll.viewport(), QScroller.LeftMouseButtonGesture)
def setup_searchInterface(self): # 初始化搜索
search_scroll = self.searchInterface.findChild(SmoothScrollArea, 'search_scroll')
def search(keyword): # 搜索
if keyword == '/all':
return plugins_data
result = {}
for key, value in plugins_data.items():
if any(keyword.lower() in str(value.get(field, "")).lower() for field in SEARCH_FIELDS):
result[key] = value
return result
def clear_results():
for i in reversed(range(self.search_plugin_grid.count())):
widget = self.search_plugin_grid.itemAt(i).widget()
if widget:
widget.setParent(None) # 移除控件
widget.destroy() # 销毁控件
def search_plugins(): # 搜索插件
if not plugins_data:
return
clear_results()
if self.search_plugin.text():
def set_plugin_image(plugin_card, data):
pixmap = QPixmap()
pixmap.loadFromData(data)
plugin_card.set_img(pixmap)
keyword = self.search_plugin.text()
print(f'结果:{search(keyword)}') # 结果
plugin_num = 0 # 计数
for key, data in search(keyword).items():
plugin_card = PluginCard_Horizontal(title=data['name'], content=data['description'],
tag=data['tag'], version=data['version'], url=data['url'],
author=data['author'], data=data, parent=self)
plugin_card.clicked.connect(plugin_card.show_detail) # 点击事件
# 启动线程加载图片
image_thread = nt.getImg(f"{replace_to_file_server(data['url'], data['branch'])}/icon.png")
image_thread.repo_signal.connect(
lambda img_data, card=plugin_card: set_plugin_image(card, img_data))
image_thread.start()
self.search_plugin_grid.addWidget(plugin_card, plugin_num // 2, plugin_num % 2) # 排列
plugin_num += 1
self.search_plugin_grid = self.searchInterface.findChild(QGridLayout, 'search_plugin_grid') # 插件表格
self.tags_layout = self.searchInterface.findChild(QGridLayout, 'tags_layout') # tag 布局
self.search_plugin = self.searchInterface.findChild(SearchLineEdit, 'search_plugin')
self.search_plugin.searchSignal.connect(search_plugins)
self.search_plugin.returnPressed.connect(search_plugins)
self.search_plugin.clearSignal.connect(clear_results)
self.search_completer = QCompleter(search_items, self.search_plugin)
# 设置显示的选项数
self.search_completer.setMaxVisibleItems(10)
self.search_completer.setFilterMode(Qt.MatchContains) # 内容匹配
self.search_completer.setCaseSensitivity(Qt.CaseInsensitive) # 不区分大小写
self.search_completer.activated.connect(search_plugins)
self.search_plugin.setCompleter(self.search_completer)
QScroller.grabGesture(search_scroll.viewport(), QScroller.LeftMouseButtonGesture)
def setup_settingsInterface(self): # 初始化设置
# 选择代理
select_mirror = self.settingsInterface.findChild(ComboBox, 'select_proxy')
select_mirror.addItems(nt.mirror_list)
select_mirror.setCurrentIndex(nt.mirror_list.index(config_center.read_conf('Plugin', 'mirror')))
select_mirror.currentIndexChanged.connect(
lambda: config_center.write_conf('Plugin', 'mirror', select_mirror.currentText()))
# 开关自动启用插件
auto_enable_plugin = self.settingsInterface.findChild(SwitchButton, 'auto_enable_plugin')
auto_enable_plugin.setChecked(int(config_center.read_conf('Plugin', 'auto_enable_plugin')))
auto_enable_plugin.checkedChanged.connect(
lambda: config_center.write_conf('Plugin', 'auto_enable_plugin', int(auto_enable_plugin.isChecked()))
)
def setup_homeInterface(self): # 初始化首页
# 标题和副标题
home_scroll = self.homeInterface.findChild(SmoothScrollArea, 'home_scroll')
time_today_label = self.homeInterface.findChild(TitleLabel, 'time_today_label')
time_today_label.setText(f"{datetime.now().month}{datetime.now().day}{l.week[datetime.now().weekday()]}")
# Banner
self.banner_view = self.homeInterface.findChild(HorizontalFlipView, 'banner_view')
self.banner_view.setAspectRatioMode(Qt.AspectRatioMode.KeepAspectRatio)
self.banner_view.setItemSize(QSize(900, 450)) # 设置图片大小banner图片尺寸比
self.banner_view.setBorderRadius(8)
self.banner_view.setSpacing(5)
self.banner_view.clicked.connect(self.open_banner_link)
self.auto_play_timer = QTimer(self) # 自动轮播
self.auto_play_timer.timeout.connect(lambda: self.switch_banners())
self.auto_play_timer.setInterval(2500)
# 翻页
self.banner_pager = self.homeInterface.findChild(PipsPager, 'banner_pager')
self.banner_pager.setVisibleNumber(5)
self.banner_pager.currentIndexChanged.connect(
lambda: (self.banner_view.scrollToIndex(self.banner_pager.currentIndex()),
self.auto_play_timer.stop(),
self.auto_play_timer.start(2500))
)
QScroller.grabGesture(home_scroll.viewport(), QScroller.LeftMouseButtonGesture)
def open_banner_link(self):
if not hasattr(self, 'img_list'):
QDesktopServices.openUrl(QUrl(
'https://www.yuque.com/rinlit/class-widgets_help/ez4vv7tv8wikxc0s#Se2Bb'
))
return False # 没有图片
if self.img_list[self.banner_view.currentIndex()] in self.banners_data:
if not self.banners_data[self.img_list[self.banner_view.currentIndex()]]['link']:
return False # 无链接
QDesktopServices.openUrl(QUrl(
self.banners_data[self.img_list[self.banner_view.currentIndex()]]['link']
))
def set_tags_data(self, data):
global tags, search_items, recommend_plugins
rec_data = {}
if data:
tags = data.get('tags')
recommend_plugins = data.get('recommend_plugin')
shuffle(tags) # 随机
for tag in tags:
search_items.append(tag)
self.search_completer.setModel(QStringListModel(search_items)) # 设置搜索提示
tag_num = 0 # 计数
for tag in tags[:6]:
tag_link = TagLink(tag, self)
self.tags_layout.addWidget(tag_link, tag_num // 3, tag_num % 3) # 排列
tag_num += 1
for key, data in plugins_data.items():
if key in recommend_plugins:
rec_data[key] = data
self.load_plugins(rec_data, 'home')
def load_plugins(self, p_data, page):
global search_items
for plugin in p_data.values(): # 遍历插件数据
search_items.append(plugin['name'])
if plugin['author'] not in search_items:
search_items.append(plugin['author'])
self.search_completer.setModel(QStringListModel(search_items)) # 设置搜索提示
def set_plugin_image(plugin_card, data):
pixmap = QPixmap()
pixmap.loadFromData(data)
plugin_card.set_img(pixmap)
if page == 'latest':
self.plugin_grid = self.latestsInterface.findChild(QGridLayout, 'all_plugin_grid') # 插件表格
elif page == 'home':
self.plugin_grid = self.homeInterface.findChild(QGridLayout, 'rec_plugin_grid') # 插件表格
else:
self.plugin_grid = self.latestsInterface.findChild(QGridLayout, 'all_plugin_grid') # 插件表格
plugin_num = 0 # 计数
for plugin, data in p_data.items(): # 遍历插件数据
plugin_card = PluginCard_Horizontal(title=data['name'], content=data['description'],
tag=data['tag'], version=data['version'], url=data['url'],
author=data['author'], data=data, parent=self)
plugin_card.clicked.connect(plugin_card.show_detail) # 点击事件
# 启动线程加载图片
image_thread = nt.getImg(f"{replace_to_file_server(data['url'], data['branch'])}/icon.png")
image_thread.repo_signal.connect(lambda img_data, card=plugin_card: set_plugin_image(card, img_data))
image_thread.start()
self.plugin_grid.addWidget(plugin_card, plugin_num // 2, plugin_num % 2) # 排列
plugin_num += 1
self.homeInterface.findChild(IndeterminateProgressRing, 'load_plugin_progress').hide()
def get_banner_img(self):
def display_banner(data, index=0):
if index == 0:
self.auto_play_timer.start()
if data:
pixmap = QPixmap()
pixmap.loadFromData(data)
self.banner_view.setItemImage(index, pixmap)
self.splashScreen.hide()
def get_banner(data=dict):
try:
if 'error' not in data:
self.banners_data = data
self.img_list = self.img_links = list(data.keys())
self.img_links = [f'https://raw.githubusercontent.com/Class-Widgets/plugin-plaza/main/Banner/'
f'{img}.png' for img in self.img_links]
self.banner_pager.setPageNumber(len(data))
banner_placeholders = ["img/plaza/banner_pre.png" for _ in range(len(data))]
self.banner_view.addImages(banner_placeholders)
else:
error_info = data.get("error", "未知错误")
logger.error(f'PluginPlaza 无法联网,错误:{error_info}')
self.findChild(BodyLabel, 'tips').setText(f'错误原因:{error_info}')
self.banner_view.addImage("img/plaza/banner_network-failed.png")
self.banner_view.addImage("img/plaza/banner_network-failed.png")
self.splashScreen.hide()
self.homeInterface.findChild(SubtitleLabel, 'SubtitleLabel_3').hide() # 隐藏副标题
return
# 定义一个内部函数来启动下一个线程
def start_next_banner(index):
if index < len(data):
self.banner_thread = nt.getImg(self.img_links[index])
self.banner_thread.repo_signal.connect(lambda data: display_banner(data, index))
self.banner_thread.repo_signal.connect(lambda: start_next_banner(index + 1)) # 连接完成信号
self.banner_thread.start()
start_next_banner(0) # 启动第一个线程
except Exception as e:
logger.error(f"获取Banner失败{e}")
self.banner_list_thread = nt.getRepoFileList()
self.banner_list_thread.repo_signal.connect(get_banner)
self.banner_list_thread.start()
def restart_tips(self):
global restart_tips_flag
restart_tips_flag = True
w = InfoBar.info(
title='需要重启',
content='若要应用插件配置,需重启 Class Widgets',
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=-1,
parent=self
)
restart_btn = HyperlinkLabel('现在重启')
restart_btn.clicked.connect(restart)
w.addWidget(restart_btn)
w.show()
def get_pp_data(self):
global plugins_data
def callback(data):
global plugins_data
plugins_data = data # 保存插件数据
self.load_plugins(data, 'latest')
self.get_tags_data()
self.get_plugin_list_thread = nt.getPluginInfo()
self.get_plugin_list_thread.repo_signal.connect(callback)
self.get_plugin_list_thread.start()
def get_tags_data(self):
self.get_tags_list_thread = nt.getTags()
self.get_tags_list_thread.repo_signal.connect(self.set_tags_data)
self.get_tags_list_thread.start()
def switch_banners(self): # 切换Banner
if self.banner_view.currentIndex() == len(self.img_list) - 1:
self.banner_view.scrollToIndex(0)
self.banner_pager.setCurrentIndex(0)
else:
self.banner_view.scrollNext()
self.banner_pager.setCurrentIndex(self.banner_view.currentIndex())
def init_nav(self):
self.addSubInterface(self.homeInterface, fIcon.HOME, '首页', fIcon.HOME_FILL)
self.addSubInterface(self.latestsInterface, fIcon.LIBRARY, '分类', fIcon.LIBRARY_FILL)
self.addSubInterface(
self.searchInterface, fIcon.SEARCH, '搜索', position=NavigationItemPosition.BOTTOM
)
self.addSubInterface(
self.settingsInterface, fIcon.SETTING, '设置', fIcon.SETTING, position=NavigationItemPosition.BOTTOM
)
def init_window(self):
self.load_all_interface()
self.init_font()
self.setMinimumWidth(850)
self.setMinimumHeight(500)
self.setWindowTitle('插件广场')
self.setWindowIcon(QIcon(f'{base_directory}/img/pp_favicon.png'))
# 设置窗口大小
size, pos = calculate_size()
self.move(pos[0], pos[1])
self.resize(size[0], size[1])
# 启动屏幕
self.splashScreen = SplashScreen(self.windowIcon(), self)
self.splashScreen.setIconSize(QSize(102, 102))
self.show()
def init_font(self): # 设置字体
self.setStyleSheet("""QLabel {
font-family: 'Microsoft YaHei';
}""")
def closeEvent(self, event):
self.closed.emit()
event.accept()
def add2save_plugin(p_name): # 保存已安装插件
global installed_plugins
installed_plugins.append(p_name)
try:
with open(CONF_PATH, 'r+', encoding='utf-8') as f:
if p_name not in json.load(f)['plugins']:
f.seek(0) # 指针指向开头
json.dump({"plugins": installed_plugins}, f, ensure_ascii=False, indent=4)
f.truncate() # 截断文件
except Exception as e:
logger.error(f"保存已安装插件失败:{e}")
def replace_to_file_server(url, branch='main'):
return (f'{url.replace("https://github.com/", "https://raw.githubusercontent.com/")}'
f'/{branch}')
def load_local_plugins_version():
global local_plugins_version
for plugin in installed_plugins:
try:
with open(f"plugins/{plugin}/plugin.json", 'r', encoding='utf-8') as f:
data = json.load(f)
local_plugins_version[plugin] = data['version']
except Exception as e:
logger.error(f"加载本地插件版本失败:{e}")
print(local_plugins_version)
if __name__ == '__main__':
app = QApplication(sys.argv)
pp = PluginPlaza()
pp.show()
sys.exit(app.exec())