Compare commits

...

11 Commits

Author SHA1 Message Date
jxxghp
a8e161661c v2.2.2
- 分享的订阅支持删除(仅新分享的订阅有效)
- 媒体信息搜索支持系列合集
- 优化了实时日志的性能
- 优化了订阅识别词的生效优先级
- 优化了多处UI细节
2025-01-16 19:59:52 +08:00
jxxghp
2b07766f9a feat:支持搜索系列合集 2025-01-16 17:58:52 +08:00
jxxghp
adeb5361ab feat:支持搜索系列合集 2025-01-16 17:51:47 +08:00
jxxghp
bd6e43c41d Merge pull request #3737 from wikrin/event 2025-01-15 20:39:34 +08:00
Attente
450289c7b7 feat(event): 添加订阅调整事件
- `编辑`订阅信息后,发送订阅调整事件
- 新增 `EventType.SubscribeModified` 枚举值
- 事件数据包含`subscribe_id: int` 和更新后的订阅信息`subscribe_info: dict`
2025-01-15 20:16:32 +08:00
jxxghp
aa93c560e5 feat:分享订阅删除功能 2025-01-15 13:31:16 +08:00
jxxghp
22b1ebe1cf fix #3724 2025-01-15 08:14:39 +08:00
jxxghp
84bcf15e9b Merge pull request #3724 from wikrin/subscribe_words
fix: - 修复订阅识别词在下载阶段不生效的问题
2025-01-15 08:10:03 +08:00
Attente
5b66803f6d fix: 修复订阅识别词在下载阶段不生效的问题
- 将`季集匹配`从`优先级规则组匹配模块`移至`种子帮助类`
- - `FilterModule.__match_season_episodes()` ==> `TorrentHelper.match_season_episodes()`
- - 确保需要`订阅识别词` `偏移季集`的种子能够正确匹配
2025-01-15 03:43:50 +08:00
Attente
88cbde47da fix: 更新应用订阅识别词的种子元数据, 附加参数过滤空项 2025-01-15 03:23:05 +08:00
Attente
03b96fa88b fix: 类型注解 2025-01-15 02:54:22 +08:00
21 changed files with 272 additions and 75 deletions

View File

@@ -72,7 +72,7 @@ def search(title: str,
"""
模糊搜索媒体/人物信息列表 media媒体信息person人物信息
"""
def __get_source(obj: Union[dict, schemas.MediaPerson]):
def __get_source(obj: Union[schemas.MediaInfo, schemas.MediaPerson, dict]):
"""
获取对象属性
"""
@@ -85,6 +85,8 @@ def search(title: str,
_, medias = MediaChain().search(title=title)
if medias:
result = [media.to_dict() for media in medias]
elif type == "collection":
result = MediaChain().search_collections(name=title)
else:
result = MediaChain().search_persons(name=title)
if result:

View File

@@ -122,6 +122,11 @@ def update_subscribe(
if subscribe_in.total_episode != subscribe.total_episode:
subscribe_dict["manual_total_episode"] = 1
subscribe.update(db, subscribe_dict)
# 发送订阅调整事件
eventmanager.send_event(EventType.SubscribeModified, {
"subscribe_id": subscribe.id,
"subscribe_info": subscribe_dict,
})
return schemas.Response(success=True)
@@ -462,6 +467,17 @@ def subscribe_share(
return schemas.Response(success=state, message=errmsg)
@router.delete("/share/{share_id}", summary="删除分享", response_model=schemas.Response)
def subscribe_share_delete(
share_id: int,
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
删除分享
"""
state, errmsg = SubscribeHelper().share_delete(share_id=share_id)
return schemas.Response(success=state, message=errmsg)
@router.post("/fork", summary="复用订阅", response_model=schemas.Response)
def subscribe_fork(
sub: schemas.SubscribeShare,

View File

@@ -174,6 +174,10 @@ def get_global_setting():
exclude={"SECRET_KEY", "RESOURCE_SECRET_KEY", "API_TOKEN", "TMDB_API_KEY", "TVDB_API_KEY", "FANART_API_KEY",
"COOKIECLOUD_KEY", "COOKIECLOUD_PASSWORD", "GITHUB_TOKEN", "REPO_GITHUB_TOKEN"}
)
# 追加用户唯一ID
info.update({
"USER_UNIQUE_ID": SystemUtils.generate_user_unique_id()
})
return schemas.Response(success=True,
data=info)

View File

@@ -60,6 +60,20 @@ def tmdb_recommend(tmdbid: int,
return []
@router.get("/collection/{collection_id}", summary="系列合集详情", response_model=List[schemas.MediaInfo])
def tmdb_collection(collection_id: int,
page: int = 1,
count: int = 20,
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
根据合集ID查询合集详情
"""
medias = TmdbChain().tmdb_collection(collection_id=collection_id)
if medias:
return [media.to_dict() for media in medias][(page - 1) * count:page * count]
return []
@router.get("/credits/{tmdbid}/{type_name}", summary="演员阵容", response_model=List[schemas.MediaPerson])
def tmdb_credits(tmdbid: int,
type_name: str,

View File

@@ -301,6 +301,13 @@ class ChainBase(metaclass=ABCMeta):
"""
return self.run_module("search_persons", name=name)
def search_collections(self, name: str) -> Optional[List[MediaInfo]]:
"""
搜索集合信息
:param name: 集合名称
"""
return self.run_module("search_collections", name=name)
def search_torrents(self, site: CommentedMap,
keywords: List[str],
mtype: MediaType = None,
@@ -326,19 +333,16 @@ class ChainBase(metaclass=ABCMeta):
def filter_torrents(self, rule_groups: List[str],
torrent_list: List[TorrentInfo],
season_episodes: Dict[int, list] = None,
mediainfo: MediaInfo = None) -> List[TorrentInfo]:
"""
过滤种子资源
:param rule_groups: 过滤规则组名称列表
:param torrent_list: 资源列表
:param season_episodes: 季集数过滤 {season:[episodes]}
:param mediainfo: 识别的媒体信息
:return: 过滤后的资源列表,添加资源优先级
"""
return self.run_module("filter_torrents", rule_groups=rule_groups,
torrent_list=torrent_list, season_episodes=season_episodes,
mediainfo=mediainfo)
torrent_list=torrent_list, mediainfo=mediainfo)
def download(self, content: Union[Path, str], download_dir: Path, cookie: str,
episodes: Set[int] = None, category: str = None,

View File

@@ -125,7 +125,6 @@ class SearchChain(ChainBase):
"""
return self.filter_torrents(rule_groups=rule_groups,
torrent_list=torrent_list,
season_episodes=season_episodes,
mediainfo=mediainfo) or []
# 豆瓣标题处理
@@ -185,7 +184,10 @@ class SearchChain(ChainBase):
# 开始过滤
self.progress.update(value=0, text=f'开始过滤,总 {len(torrents)} 个资源,请稍候...',
key=ProgressKey.Search)
# 匹配订阅附加参数
if filter_params:
logger.info(f'开始附加参数过滤,附加参数:{filter_params} ...')
torrents = [torrent for torrent in torrents if self.torrenthelper.filter_torrent(torrent, filter_params)]
# 开始过滤规则过滤
if rule_groups is None:
# 取搜索过滤规则
@@ -222,16 +224,18 @@ class SearchChain(ChainBase):
if not torrent.title:
continue
# 匹配订阅附加参数
if filter_params and not self.torrenthelper.filter_torrent(torrent_info=torrent,
filter_params=filter_params):
continue
# 识别元数据
torrent_meta = MetaInfo(title=torrent.title, subtitle=torrent.description,
custom_words=custom_words)
if torrent.title != torrent_meta.org_string:
logger.info(f"种子名称应用识别词后发生改变:{torrent.title} => {torrent_meta.org_string}")
# 季集数过滤
if season_episodes \
and not self.torrenthelper.match_season_episodes(
torrent=torrent,
meta=torrent_meta,
season_episodes=season_episodes):
continue
# 比对IMDBID
if torrent.imdbid \
and mediainfo.imdb_id \

View File

@@ -384,7 +384,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
self.message.put('没有找到订阅!', title="订阅搜索", role="system")
logger.debug(f"search Lock released at {datetime.now()}")
def update_subscribe_priority(self, subscribe: Subscribe, meta: MetaInfo,
def update_subscribe_priority(self, subscribe: Subscribe, meta: MetaBase,
mediainfo: MediaInfo, downloads: List[Context]):
"""
更新订阅已下载资源的优先级
@@ -407,7 +407,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 正在洗版,更新资源优先级
logger.info(f'{mediainfo.title_year} 正在洗版,更新资源优先级为 {priority}')
def finish_subscribe_or_not(self, subscribe: Subscribe, meta: MetaInfo, mediainfo: MediaInfo,
def finish_subscribe_or_not(self, subscribe: Subscribe, meta: MetaBase, mediainfo: MediaInfo,
downloads: List[Context] = None,
lefts: Dict[Union[int | str], Dict[int, NotExistMediaInfo]] = None,
force: bool = False):
@@ -1145,16 +1145,17 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 默认过滤规则
default_rule = self.systemconfig.get(SystemConfigKey.SubscribeDefaultParams) or {}
return {
"include": subscribe.include or default_rule.get("include"),
"exclude": subscribe.exclude or default_rule.get("exclude"),
"quality": subscribe.quality or default_rule.get("quality"),
"resolution": subscribe.resolution or default_rule.get("resolution"),
"effect": subscribe.effect or default_rule.get("effect"),
"tv_size": default_rule.get("tv_size"),
"movie_size": default_rule.get("movie_size"),
"min_seeders": default_rule.get("min_seeders"),
"min_seeders_time": default_rule.get("min_seeders_time"),
}
key: value for key, value in {
"include": subscribe.include or default_rule.get("include"),
"exclude": subscribe.exclude or default_rule.get("exclude"),
"quality": subscribe.quality or default_rule.get("quality"),
"resolution": subscribe.resolution or default_rule.get("resolution"),
"effect": subscribe.effect or default_rule.get("effect"),
"tv_size": default_rule.get("tv_size"),
"movie_size": default_rule.get("movie_size"),
"min_seeders": default_rule.get("min_seeders"),
"min_seeders_time": default_rule.get("min_seeders_time"),
}.items() if value is not None}
def subscribe_files_info(self, subscribe: Subscribe) -> Optional[SubscrbieInfo]:
"""
@@ -1266,7 +1267,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
subscribe_info.episodes = episodes
return subscribe_info
def check_and_handle_existing_media(self, subscribe: Subscribe, meta: MetaInfo,
def check_and_handle_existing_media(self, subscribe: Subscribe, meta: MetaBase,
mediainfo: MediaInfo, mediakey: str):
"""
检查媒体是否已经存在,并根据情况执行相应的操作

View File

@@ -38,6 +38,13 @@ class TmdbChain(ChainBase, metaclass=Singleton):
"""
return self.run_module("tmdb_trending", page=page)
def tmdb_collection(self, collection_id: int) -> Optional[List[MediaInfo]]:
"""
根据合集ID查询集合
:param collection_id: 合集ID
"""
return self.run_module("tmdb_collection", collection_id=collection_id)
def tmdb_seasons(self, tmdbid: int) -> List[schemas.TmdbSeason]:
"""
根据TMDBID查询themoviedb所有季信息

View File

@@ -178,6 +178,8 @@ class MediaInfo:
douban_id: str = None
# Bangumi ID
bangumi_id: int = None
# 合集ID
collection_id: int = None
# 媒体原语种
original_language: str = None
# 媒体原发行标题
@@ -397,6 +399,8 @@ class MediaInfo:
if info.get("external_ids"):
self.tvdb_id = info.get("external_ids", {}).get("tvdb_id")
self.imdb_id = info.get("external_ids", {}).get("imdb_id")
# 合集ID
self.collection_id = info.get('collection_id')
# 评分
self.vote_average = round(float(info.get('vote_average')), 1) if info.get('vote_average') else 0
# 描述

View File

@@ -9,6 +9,7 @@ from app.db.systemconfig_oper import SystemConfigOper
from app.schemas.types import SystemConfigKey
from app.utils.http import RequestUtils
from app.utils.singleton import Singleton
from app.utils.system import SystemUtils
class SubscribeHelper(metaclass=Singleton):
@@ -34,6 +35,7 @@ class SubscribeHelper(metaclass=Singleton):
def __init__(self):
self.systemconfig = SystemConfigOper()
self.share_user_id = SystemUtils.generate_user_unique_id()
if settings.SUBSCRIBE_STATISTIC_SHARE:
if not self.systemconfig.get(SystemConfigKey.SubscribeReport):
if self.sub_report():
@@ -133,6 +135,7 @@ class SubscribeHelper(metaclass=Singleton):
"share_title": share_title,
"share_comment": share_comment,
"share_user": share_user,
"share_uid": self.share_user_id,
**subscribe_dict
})
if res is None:
@@ -144,6 +147,24 @@ class SubscribeHelper(metaclass=Singleton):
else:
return False, res.json().get("message")
def share_delete(self, share_id: int) -> Tuple[bool, str]:
"""
删除分享
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
return False, "当前没有开启订阅数据共享功能"
res = RequestUtils(proxies=settings.PROXY,
timeout=5).delete_res(f"{self._sub_share}/{share_id}",
params={"share_uid": self.share_user_id})
if res is None:
return False, "连接MoviePilot服务器失败"
if res.ok:
# 清除 get_shares 的缓存,以便实时看到结果
self._shares_cache.clear()
return True, ""
else:
return False, res.json().get("message")
def sub_fork(self, share_id: int) -> Tuple[bool, str]:
"""
复用分享的订阅

View File

@@ -9,6 +9,7 @@ from torrentool.api import Torrent
from app.core.config import settings
from app.core.context import Context, TorrentInfo, MediaInfo
from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo
from app.db.site_oper import SiteOper
from app.db.systemconfig_oper import SystemConfigOper
@@ -445,3 +446,38 @@ class TorrentHelper(metaclass=Singleton):
return False
return True
@staticmethod
def match_season_episodes(torrent: TorrentInfo, meta: MetaBase, season_episodes: Dict[int, list]) -> bool:
"""
判断种子是否匹配季集数
:param torrent: 种子信息
:param meta: 种子元数据
:param season_episodes: 季集数 {season:[episodes]}
"""
# 匹配季
seasons = season_episodes.keys()
# 种子季
torrent_seasons = meta.season_list
if not torrent_seasons:
# 按第一季处理
torrent_seasons = [1]
# 种子集
torrent_episodes = meta.episode_list
if not set(torrent_seasons).issubset(set(seasons)):
# 种子季不在过滤季中
logger.debug(
f"种子 {torrent.site_name} - {torrent.title} 包含季 {torrent_seasons} 不是需要的季 {list(seasons)}")
return False
if not torrent_episodes:
# 整季按匹配处理
return True
if len(torrent_seasons) == 1:
need_episodes = season_episodes.get(torrent_seasons[0])
if need_episodes \
and not set(torrent_episodes).intersection(set(need_episodes)):
# 单季集没有交集的不要
logger.debug(f"种子 {torrent.site_name} - {torrent.title} "
f"{torrent_episodes} 没有需要的集:{need_episodes}")
return False
return True

View File

@@ -192,13 +192,11 @@ class FilterModule(_ModuleBase):
def filter_torrents(self, rule_groups: List[str],
torrent_list: List[TorrentInfo],
season_episodes: Dict[int, list] = None,
mediainfo: MediaInfo = None) -> List[TorrentInfo]:
"""
过滤种子资源
:param rule_groups: 过滤规则组名称列表
:param torrent_list: 资源列表
:param season_episodes: 季集数过滤 {season:[episodes]}
:param mediainfo: 媒体信息
:return: 过滤后的资源列表,添加资源优先级
"""
@@ -215,24 +213,18 @@ class FilterModule(_ModuleBase):
torrent_list = self.__filter_torrents(
rule_string=group.rule_string,
rule_name=group.name,
torrent_list=torrent_list,
season_episodes=season_episodes
)
torrent_list=torrent_list
)
return torrent_list
def __filter_torrents(self, rule_string: str, rule_name: str,
torrent_list: List[TorrentInfo],
season_episodes: Dict[int, list]) -> List[TorrentInfo]:
torrent_list: List[TorrentInfo]) -> List[TorrentInfo]:
"""
过滤种子
"""
# 返回种子列表
ret_torrents = []
for torrent in torrent_list:
# 季集数过滤
if season_episodes \
and not self.__match_season_episodes(torrent, season_episodes):
continue
# 能命中优先级的才返回
if not self.__get_order(torrent, rule_string):
logger.debug(f"种子 {torrent.site_name} - {torrent.title} {torrent.description} "
@@ -242,39 +234,6 @@ class FilterModule(_ModuleBase):
return ret_torrents
@staticmethod
def __match_season_episodes(torrent: TorrentInfo, season_episodes: Dict[int, list]):
"""
判断种子是否匹配季集数
"""
# 匹配季
seasons = season_episodes.keys()
meta = MetaInfo(title=torrent.title, subtitle=torrent.description)
# 种子季
torrent_seasons = meta.season_list
if not torrent_seasons:
# 按第一季处理
torrent_seasons = [1]
# 种子集
torrent_episodes = meta.episode_list
if not set(torrent_seasons).issubset(set(seasons)):
# 种子季不在过滤季中
logger.debug(
f"种子 {torrent.site_name} - {torrent.title} 包含季 {torrent_seasons} 不是需要的季 {list(seasons)}")
return False
if not torrent_episodes:
# 整季按匹配处理
return True
if len(torrent_seasons) == 1:
need_episodes = season_episodes.get(torrent_seasons[0])
if need_episodes \
and not set(torrent_episodes).intersection(set(need_episodes)):
# 单季集没有交集的不要
logger.debug(f"种子 {torrent.site_name} - {torrent.title} "
f"{torrent_episodes} 没有需要的集:{need_episodes}")
return False
return True
def __get_order(self, torrent: TorrentInfo, rule_str: str) -> Optional[TorrentInfo]:
"""
获取种子匹配的规则优先级值越大越优先未匹配时返回None

View File

@@ -311,6 +311,27 @@ class TheMovieDbModule(_ModuleBase):
return [MediaPerson(source='themoviedb', **person) for person in results]
return []
def search_collections(self, name: str) -> Optional[List[MediaInfo]]:
"""
搜索集合信息
"""
if not name:
return []
results = self.tmdb.search_collections(name)
if results:
return [MediaInfo(tmdb_info=info) for info in results]
return []
def tmdb_collection(self, collection_id: int) -> Optional[List[MediaInfo]]:
"""
根据合集ID查询集合
:param collection_id: 合集ID
"""
results = self.tmdb.get_collection(collection_id)
if results:
return [MediaInfo(tmdb_info=info) for info in results]
return []
def metadata_nfo(self, meta: MetaBase, mediainfo: MediaInfo,
season: int = None, episode: int = None) -> Optional[str]:
"""

View File

@@ -11,7 +11,7 @@ from app.log import logger
from app.schemas.types import MediaType
from app.utils.http import RequestUtils
from app.utils.string import StringUtils
from .tmdbv3api import TMDb, Search, Movie, TV, Season, Episode, Discover, Trending, Person
from .tmdbv3api import TMDb, Search, Movie, TV, Season, Episode, Discover, Trending, Person, Collection
from .tmdbv3api.exceptions import TMDbException
@@ -44,6 +44,7 @@ class TmdbApi:
self.discover = Discover()
self.trending = Trending()
self.person = Person()
self.collection = Collection()
def search_multiis(self, title: str) -> List[dict]:
"""
@@ -101,6 +102,32 @@ class TmdbApi:
return []
return self.search.people(term=name) or []
def search_collections(self, name: str) -> List[dict]:
"""
查询模糊匹配的所有合集TMDB信息
"""
if not name:
return []
collections = self.search.collections(term=name) or []
for collection in collections:
collection['media_type'] = MediaType.COLLECTION
collection['collection_id'] = collection.get("id")
return collections
def get_collection(self, collection_id: int) -> List[dict]:
"""
根据合集ID查询合集详情
"""
if not collection_id:
return []
try:
return self.collection.details(collection_id=collection_id)
except TMDbException as err:
logger.error(f"连接TMDB出错{str(err)}")
except Exception as e:
logger.error(f"连接TMDB出错{str(e)}")
return []
@staticmethod
def __compare_names(file_name: str, tmdb_names: list) -> bool:
"""

View File

@@ -67,7 +67,7 @@ class MediaInfo(BaseModel):
"""
# 来源themoviedb、douban、bangumi
source: Optional[str] = None
# 类型 电影、电视剧
# 类型 电影、电视剧、合集
type: Optional[str] = None
# 媒体标题
title: Optional[str] = None
@@ -79,6 +79,8 @@ class MediaInfo(BaseModel):
title_year: Optional[str] = None
# 当前指定季,如有
season: Optional[int] = None
# 合集等id
collection_id: Optional[int] = None
# TMDB ID
tmdb_id: Optional[int] = None
# IMDB ID

View File

@@ -88,6 +88,8 @@ class SubscribeShare(BaseModel):
share_comment: Optional[str] = None
# 分享人
share_user: Optional[str] = None
# 分享人唯一ID
share_uid: Optional[str] = None
# 订阅名称
name: Optional[str] = None
# 订阅年份

View File

@@ -48,8 +48,8 @@ class TransferTask(BaseModel):
"""
文件整理任务
"""
fileitem: FileItem = None
meta: Any = None
fileitem: FileItem
meta: Optional[Any] = None
mediainfo: Optional[Any] = None
target_directory: Optional[TransferDirectoryConf] = None
target_storage: Optional[str] = None

View File

@@ -5,6 +5,7 @@ from enum import Enum
class MediaType(Enum):
MOVIE = '电影'
TV = '电视剧'
COLLECTION = '系列'
UNKNOWN = '未知'
@@ -48,6 +49,8 @@ class EventType(Enum):
NoticeMessage = "notice.message"
# 订阅已添加
SubscribeAdded = "subscribe.added"
# 订阅已调整
SubscribeModified = "subscribe.modified"
# 订阅已删除
SubscribeDeleted = "subscribe.deleted"
# 订阅已完成

View File

@@ -207,6 +207,32 @@ class RequestUtils:
raise_exception=raise_exception,
**kwargs)
def delete_res(self,
url: str,
data: Any = None,
params: dict = None,
allow_redirects: bool = True,
raise_exception: bool = False,
**kwargs) -> Optional[Response]:
"""
发送DELETE请求并返回响应对象
:param url: 请求的URL
:param data: 请求的数据
:param params: 请求的参数
:param allow_redirects: 是否允许重定向
:param raise_exception: 是否在发生异常时抛出异常否则默认拦截异常返回None
:param kwargs: 其他请求参数如headers, cookies, proxies等
:return: HTTP响应对象若发生RequestException则返回None
:raises: requests.exceptions.RequestException 仅raise_exception为True时会抛出
"""
return self.request(method="delete",
url=url,
data=data,
params=params,
allow_redirects=allow_redirects,
raise_exception=raise_exception,
**kwargs)
@staticmethod
def cookie_parse(cookies_str: str, array: bool = False) -> Union[list, dict]:
"""

View File

@@ -1,10 +1,12 @@
import datetime
import hashlib
import os
import platform
import re
import shutil
import subprocess
import sys
import uuid
from glob import glob
from pathlib import Path
from typing import List, Optional, Tuple, Union
@@ -556,3 +558,45 @@ class SystemUtils:
# 确保是空文件夹
if folder.is_dir() and not any(folder.iterdir()):
folder.rmdir()
@staticmethod
def generate_user_unique_id():
"""
根据优先级依次尝试生成稳定唯一ID
1. 文件系统唯一标识符。
2. MAC 地址。
3. 主机名。
"""
def get_filesystem_unique_id():
"""
获取文件系统的唯一标识符。
使用根目录的设备号和 inode。
"""
try:
stat_info = os.stat("/")
fs_id = f"{stat_info.st_dev}-{stat_info.st_ino}"
return hashlib.sha256(fs_id.encode("utf-8")).hexdigest()
except Exception as e:
print(str(e))
return None
def get_mac_address_id():
"""
获取设备的 MAC 地址并生成唯一标识符。
"""
try:
mac_address = uuid.getnode()
if (mac_address >> 40) % 2: # 检查是否是虚拟MAC地址
raise ValueError("MAC地址可能是虚拟地址")
mac_str = f"{mac_address:012x}"
return hashlib.sha256(mac_str.encode("utf-8")).hexdigest()
except Exception as e:
print(str(e))
return None
for method in [get_filesystem_unique_id, get_mac_address_id]:
unique_id = method()
if unique_id:
return unique_id
return None

View File

@@ -1,2 +1,2 @@
APP_VERSION = 'v2.2.1'
FRONTEND_VERSION = 'v2.2.1'
APP_VERSION = 'v2.2.2'
FRONTEND_VERSION = 'v2.2.2'