mirror of
https://github.com/jxxghp/MoviePilot-Plugins.git
synced 2026-03-27 10:05:57 +00:00
Merge pull request #977 from wumode/imdbsource
This commit is contained in:
@@ -472,11 +472,12 @@
|
||||
"name": "IMDb源",
|
||||
"description": "让探索,推荐和媒体识别支持IMDb数据源。",
|
||||
"labels": "探索",
|
||||
"version": "1.6.6",
|
||||
"version": "1.6.7",
|
||||
"icon": "IMDb_IOS-OSX_App.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.6.7": "优化界面显示; 增加榜单排名显示; 添加制作公司过滤项",
|
||||
"v1.6.6": "优化主页组件链接跳转",
|
||||
"v1.6.5": "仪表盘组件支持图片缓存",
|
||||
"v1.6.4": "为元数据增加背景图",
|
||||
@@ -496,7 +497,7 @@
|
||||
"v1.4.3": "为仪表盘组件添加缓存",
|
||||
"v1.4.2": "优化小屏幕组件显示",
|
||||
"v1.4.1": "优化亮色主题显示",
|
||||
"v1.4.0":"添加仪表盘组件: IMDb 编辑精选",
|
||||
"v1.4.0": "添加仪表盘组件: IMDb 编辑精选",
|
||||
"v1.3.3": "修复依赖问题",
|
||||
"v1.3.2": "更新 API query hash",
|
||||
"v1.3.1": "修复按日期排序错误",
|
||||
@@ -554,11 +555,12 @@
|
||||
"name": "美剧生词标注",
|
||||
"description": "根据CEFR等级,为英语影视剧标注高级词汇。",
|
||||
"labels": "英语",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.4",
|
||||
"icon": "LexiAnnot.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.2.4": "增强数据校验",
|
||||
"v1.2.3": "优化提示词",
|
||||
"v1.2.1": "改进字幕样式获取方法",
|
||||
"v1.2.0": "引入大模型候选词决策和词义丰富处理链; 支持读取系统智能体配置; 添加智能体工具; 优化通知样式; 改进 UI",
|
||||
|
||||
@@ -6007,13 +6007,28 @@ const _sfc_main$q = /* @__PURE__ */ _defineComponent$q({
|
||||
}, {
|
||||
default: _withCtx$q(() => [
|
||||
_createVNode$q(_component_v_btn_group, {
|
||||
class: "d-sm-none",
|
||||
variant: "outlined",
|
||||
rounded: "",
|
||||
divided: ""
|
||||
}, {
|
||||
default: _withCtx$q(() => [
|
||||
_createVNode$q(_component_v_btn, {
|
||||
icon: "mdi-plus",
|
||||
disabled: loading.value,
|
||||
onClick: openAddRuleDialog
|
||||
}, null, 8, ["disabled"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode$q(_component_v_btn_group, {
|
||||
class: "d-none d-sm-flex",
|
||||
variant: "outlined",
|
||||
rounded: "",
|
||||
divided: ""
|
||||
}, {
|
||||
default: _withCtx$q(() => [
|
||||
_createVNode$q(_component_v_btn, {
|
||||
class: "d-none d-sm-flex",
|
||||
icon: group.value ? "mdi-format-list-bulleted" : "mdi-format-list-group",
|
||||
disabled: loading.value,
|
||||
onClick: _cache[2] || (_cache[2] = ($event) => group.value = !group.value)
|
||||
@@ -3,7 +3,7 @@ const currentImports = {};
|
||||
let moduleMap = {
|
||||
"./Page":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Page-CJILOVp4.css"], false, './Page');
|
||||
return __federation_import('./__federation_expose_Page-DeAFYy3o.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
return __federation_import('./__federation_expose_Page-DhQfGEOD.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
"./Config":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Config-CwbjkOP2.css"], false, './Config');
|
||||
return __federation_import('./__federation_expose_Config-CY46uj5g.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
|
||||
@@ -191,7 +191,7 @@ class ClashRuleProviderService:
|
||||
except ValueError:
|
||||
final_action = action
|
||||
rules = self.state.ruleset_rules_manager.filter_rules_by_action(final_action)
|
||||
return [rule.rule.condition_string() for rule in rules]
|
||||
return [rule.rule.condition_string() for rule in rules if rule.meta.available()]
|
||||
|
||||
def sync_ruleset(self):
|
||||
outbounds = set()
|
||||
@@ -240,12 +240,12 @@ class ClashRuleProviderService:
|
||||
self.state.save_data(DataKey.TOP_RULES, self.state.top_rules_manager.export_rules())
|
||||
|
||||
def clash_outbound(self) -> list[str]:
|
||||
outbound = [pg_data.data.name for pg_data in self.state.proxy_groups_from_subs()]
|
||||
outbound = [pg.data.name for pg in self.state.proxy_groups]
|
||||
if self.state.clash_template:
|
||||
outbound.extend(pg.name for pg in self.state.clash_template.proxy_groups)
|
||||
if self.state.config.group_by_region or self.state.config.group_by_country:
|
||||
outbound.extend(pg.name for pg in self.proxy_groups_by_region())
|
||||
outbound.extend(pg.data.name for pg in self.state.proxy_groups)
|
||||
outbound.extend(pg_data.data.name for pg_data in self.state.proxy_groups_from_subs())
|
||||
outbound.extend(pg.data.name for pg in self.get_proxies())
|
||||
return outbound
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class ImdbSource(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "IMDb_IOS-OSX_App.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.6.6"
|
||||
plugin_version = "1.6.7"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
@@ -223,7 +223,7 @@ class ImdbSource(_PluginBase):
|
||||
height = 335
|
||||
is_mobile = ImdbSource.is_mobile(kwargs.get('user_agent'))
|
||||
if is_mobile:
|
||||
height *= 1.75
|
||||
height *= 1.80
|
||||
# 全局配置
|
||||
attrs = {
|
||||
"border": False
|
||||
@@ -320,7 +320,7 @@ class ImdbSource(_PluginBase):
|
||||
'href': f"https://www.imdb.com/name/{cs.id}",
|
||||
'target': '_blank',
|
||||
'rel': 'noopener noreferrer',
|
||||
'class': 'text-h4 font-weight-bold mb-2 d-flex align-center',
|
||||
'class': 'text-h4 font-weight-bold mb-1 d-flex align-center',
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
@@ -344,7 +344,6 @@ class ImdbSource(_PluginBase):
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
'component': 'span',
|
||||
'props': {
|
||||
@@ -361,6 +360,7 @@ class ImdbSource(_PluginBase):
|
||||
poster_url = next((f"{title.primary_image.url if title.primary_image else ''}" for title in titles if
|
||||
title.id == entry.ttconst), None)
|
||||
poster_url = f"{self._img_proxy_prefix}{quote(poster_url or '')}"
|
||||
meter_ranking_url = imdb_title.meter_ranking.url if imdb_title.meter_ranking else None
|
||||
poster_com = {
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
@@ -375,9 +375,9 @@ class ImdbSource(_PluginBase):
|
||||
}
|
||||
|
||||
poster_ui = {
|
||||
'component': 'div',
|
||||
'component': 'VRow',
|
||||
'props': {
|
||||
'class': 'd-flex justify-center mt-2'
|
||||
'align': 'center'
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
@@ -394,11 +394,39 @@ class ImdbSource(_PluginBase):
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
meta_chips = [
|
||||
{
|
||||
"component": "VChip",
|
||||
"props": {
|
||||
"append-icon": "mdi-trending-up",
|
||||
"size": "small",
|
||||
"href": meter_ranking_url,
|
||||
"target": "_blank"
|
||||
},
|
||||
"text": imdb_title.meter_ranking_text
|
||||
},
|
||||
{
|
||||
"component": "VChip",
|
||||
"props": {
|
||||
"size": "small",
|
||||
},
|
||||
"text": imdb_title.title_type.text
|
||||
},
|
||||
]
|
||||
if imdb_title.certificate_text:
|
||||
meta_chips.append(
|
||||
{
|
||||
"component": "VChip",
|
||||
"props": {
|
||||
"size": "small"
|
||||
},
|
||||
"text": imdb_title.certificate_text
|
||||
}
|
||||
)
|
||||
rating_ui = {
|
||||
'component': 'div',
|
||||
'props': {
|
||||
'class': 'mb-2 d-flex align-center',
|
||||
'class': 'd-flex align-center mb-1',
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
@@ -418,21 +446,21 @@ class ImdbSource(_PluginBase):
|
||||
{
|
||||
'component': 'span',
|
||||
'props': {
|
||||
'class': 'text-body-2 ml-1',
|
||||
'class': 'text-truncate text-body-2 ml-1',
|
||||
'style': 'color: rgba(231, 227, 252, 0.8);'
|
||||
},
|
||||
'text': f"{imdb_title.rating_text}/10",
|
||||
'text': f"{imdb_title.rating_text}",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'span',
|
||||
'props': {
|
||||
'class': 'text-warning font-weight-bold ml-4',
|
||||
'class': 'text-truncate text-warning font-weight-bold ml-4',
|
||||
'color': 'warning'
|
||||
},
|
||||
'text': entry.detail,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -455,7 +483,7 @@ class ImdbSource(_PluginBase):
|
||||
'component': 'span',
|
||||
'html': f"{entry.name}",
|
||||
'props': {
|
||||
'class': 'line-clamp-2 overflow-hidden',
|
||||
'class': 'text-truncate overflow-hidden',
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -468,6 +496,13 @@ class ImdbSource(_PluginBase):
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"component": 'div',
|
||||
"props": {
|
||||
"class": "d-flex align-center gap-1 mb-2",
|
||||
},
|
||||
"content": meta_chips
|
||||
},
|
||||
rating_ui,
|
||||
{
|
||||
'component': 'span',
|
||||
@@ -499,14 +534,16 @@ class ImdbSource(_PluginBase):
|
||||
{
|
||||
'component': 'VCardText',
|
||||
'props': {
|
||||
'class': 'd-flex flex-row absolute pa-4 text-white',
|
||||
'class': 'd-flex flex-row absolute pa-4 text-white h-100',
|
||||
'style': 'z-index: 2; bottom: 0; max-width: 100%;',
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'props': {
|
||||
'class': 'w-100'
|
||||
'class': 'w-100',
|
||||
'align': "end",
|
||||
'align-md': "center"
|
||||
},
|
||||
'content': [
|
||||
# 左图:海报
|
||||
@@ -514,7 +551,8 @@ class ImdbSource(_PluginBase):
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
'md': 3,
|
||||
'class': 'd-flex justify-center align-center'
|
||||
},
|
||||
'content': [
|
||||
poster_ui
|
||||
@@ -1057,16 +1095,17 @@ class ImdbSource(_PluginBase):
|
||||
return res
|
||||
|
||||
async def imdb_discover(self, mtype: str = "series",
|
||||
country: str = None,
|
||||
lang: str = None,
|
||||
genre: str = None,
|
||||
country: str | None = None,
|
||||
lang: str | None = None,
|
||||
genre: str | None = None,
|
||||
company: str | None = None,
|
||||
sort_by: str = 'POPULARITY',
|
||||
sort_order: str = 'DESC',
|
||||
using_rating: bool = False,
|
||||
user_rating: list[int] = Query(None, alias="user_rating[]"),
|
||||
year: str = None,
|
||||
award: str = None,
|
||||
ranked_list: str = None,
|
||||
year: str | None = None,
|
||||
award: str | None = None,
|
||||
ranked_list: str | None = None,
|
||||
page: int = 1) -> List[schemas.MediaInfo]:
|
||||
|
||||
if not self._imdb_helper:
|
||||
@@ -1086,41 +1125,16 @@ class ImdbSource(_PluginBase):
|
||||
release_date_start = None
|
||||
release_date_end = None
|
||||
if year:
|
||||
if year == "2025":
|
||||
release_date_start = "2025-01-01"
|
||||
elif year == "2024":
|
||||
release_date_start = "2024-01-01"
|
||||
release_date_end = "2024-12-31"
|
||||
elif year == "2023":
|
||||
release_date_start = "2023-01-01"
|
||||
release_date_end = "2023-12-31"
|
||||
elif year == "2022":
|
||||
release_date_start = "2022-01-01"
|
||||
release_date_end = "2022-12-31"
|
||||
elif year == "2021":
|
||||
release_date_start = "2021-01-01"
|
||||
release_date_end = "2021-12-31"
|
||||
elif year == "2020":
|
||||
release_date_start = "2020-01-01"
|
||||
release_date_end = "2020-12-31"
|
||||
elif year == "2020s":
|
||||
release_date_start = "2020-01-01"
|
||||
release_date_end = "2029-12-31"
|
||||
elif year == "2010s":
|
||||
release_date_start = "2010-01-01"
|
||||
release_date_end = "2019-12-31"
|
||||
elif year == "2000s":
|
||||
release_date_start = "2000-01-01"
|
||||
release_date_end = "2009-12-31"
|
||||
elif year == "1990s":
|
||||
release_date_start = "1990-01-01"
|
||||
release_date_end = "1999-12-31"
|
||||
elif year == "1980s":
|
||||
release_date_start = "1980-01-01"
|
||||
release_date_end = "1989-12-31"
|
||||
elif year == "1970s":
|
||||
release_date_start = "1970-01-01"
|
||||
release_date_end = "1979-12-31"
|
||||
if year == f"{datetime.now().year}":
|
||||
release_date_start = f"{datetime.now().year}-01-01"
|
||||
elif year.endswith("s"):
|
||||
decade = int(year[:-2])
|
||||
release_date_start = f"{decade}0-01-01"
|
||||
release_date_end = f"{decade}9-12-31"
|
||||
else:
|
||||
release_date_start = f"{year}-01-01"
|
||||
release_date_end = f"{year}-12-31"
|
||||
|
||||
if not release_date_end:
|
||||
release_date_end = datetime.now().date().strftime("%Y-%m-%d")
|
||||
if sort_by == 'POPULARITY':
|
||||
@@ -1142,7 +1156,8 @@ class ImdbSource(_PluginBase):
|
||||
release_date_end=release_date_end,
|
||||
release_date_start=release_date_start,
|
||||
award_constraint=awards,
|
||||
ranked=ranked_lists
|
||||
ranked=ranked_lists,
|
||||
company=company
|
||||
)
|
||||
results = await self._imdb_helper.async_advanced_title_search(search_params, first_page=first_page)
|
||||
res: List[schemas.MediaInfo] = []
|
||||
@@ -1339,21 +1354,15 @@ class ImdbSource(_PluginBase):
|
||||
} for key, value in sort_order_dict.items()
|
||||
]
|
||||
|
||||
year_dict = {
|
||||
"2025": "2025",
|
||||
"2024": "2024",
|
||||
"2023": "2023",
|
||||
"2022": "2022",
|
||||
"2021": "2021",
|
||||
"2020": "2020",
|
||||
year_dict = {str(year): str(year) for year in range(datetime.now().year, 2019, -1)}
|
||||
year_dict.update({
|
||||
"2020s": "2020s",
|
||||
"2010s": "2010s",
|
||||
"2000s": "2000s",
|
||||
"1990s": "1990s",
|
||||
"1980s": "1980s",
|
||||
"1970s": "1970s",
|
||||
}
|
||||
|
||||
})
|
||||
year_ui = [
|
||||
{
|
||||
"component": "VChip",
|
||||
@@ -1394,12 +1403,12 @@ class ImdbSource(_PluginBase):
|
||||
]
|
||||
|
||||
ranked_list_dict = {
|
||||
"TOP_RATED_MOVIES-100": "IMDb Top 100",
|
||||
"TOP_RATED_MOVIES-250": "IMDb Top 250",
|
||||
"TOP_RATED_MOVIES-1000": "IMDb Top 1000",
|
||||
"LOWEST_RATED_MOVIES-100": "IMDb Bottom 100",
|
||||
"LOWEST_RATED_MOVIES-250": "IMDb Bottom 250",
|
||||
"LOWEST_RATED_MOVIES-1000": "IMDb Bottom 1000",
|
||||
"TOP_RATED_MOVIES-100": "Top 100",
|
||||
"TOP_RATED_MOVIES-250": "Top 250",
|
||||
"TOP_RATED_MOVIES-1000": "Top 1000",
|
||||
"LOWEST_RATED_MOVIES-100": "Bottom 100",
|
||||
"LOWEST_RATED_MOVIES-250": "Bottom 250",
|
||||
"LOWEST_RATED_MOVIES-1000": "Bottom 1000",
|
||||
}
|
||||
|
||||
ranked_list_ui = [
|
||||
@@ -1414,6 +1423,41 @@ class ImdbSource(_PluginBase):
|
||||
} for key, value in ranked_list_dict.items()
|
||||
]
|
||||
|
||||
companies = {
|
||||
"20th Century Fox": "20世纪福克斯",
|
||||
"DreamWorks": "梦工厂",
|
||||
"MGM": "米高梅",
|
||||
"Paramount": "派拉蒙",
|
||||
"Sony": "索尼",
|
||||
"Universal": "环球",
|
||||
"Walt Disney": "迪士尼",
|
||||
"Warner Bros.": "华纳兄弟",
|
||||
"HBO": "HBO",
|
||||
"Netflix": "Netflix",
|
||||
"Hulu": "Hulu",
|
||||
"Amazon Prime Video": "Amazon Prime",
|
||||
"Apple TV": "Apple TV",
|
||||
"British Broadcasting Corporation (BBC)": "BBC",
|
||||
"Tencent Video": "腾讯视频",
|
||||
"Youku": "优酷",
|
||||
"iQIYI": "爱奇艺",
|
||||
"China Central Television (CCTV)": "CCTV",
|
||||
"Huayi Brothers Media": "华谊兄弟",
|
||||
"Beijing Enlight Pictures": "光线传媒",
|
||||
"Bona Film Group": "博纳影业",
|
||||
}
|
||||
companies_ui = [
|
||||
{
|
||||
"component": "VChip",
|
||||
"props": {
|
||||
"filter": True,
|
||||
"tile": True,
|
||||
"value": key
|
||||
},
|
||||
"text": value
|
||||
} for key, value in companies.items()
|
||||
]
|
||||
|
||||
return [
|
||||
{
|
||||
"component": "div",
|
||||
@@ -1596,6 +1640,33 @@ class ImdbSource(_PluginBase):
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"component": "div",
|
||||
"props": {
|
||||
"class": "flex justify-start items-center",
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"component": "div",
|
||||
"props": {
|
||||
"class": "mr-5"
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"component": "VLabel",
|
||||
"text": "出品方"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"component": "VChipGroup",
|
||||
"props": {
|
||||
"model": "company"
|
||||
},
|
||||
"content": companies_ui
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"component": "div",
|
||||
"props": {
|
||||
@@ -1750,7 +1821,7 @@ class ImdbSource(_PluginBase):
|
||||
"user_rating": [1, 10],
|
||||
"using_rating": False,
|
||||
"award": None,
|
||||
"ranked_list": None
|
||||
"ranked_list": None,
|
||||
},
|
||||
depends={
|
||||
"ranked_list": ["mtype"]
|
||||
|
||||
@@ -11,7 +11,7 @@ from app.utils.http import RequestUtils, AsyncRequestUtils
|
||||
from .schema.imdbapi import ImdbApiTitle, ImdbApiEpisode, ImdbApiCredit, ImdbapiImage
|
||||
from .schema.imdbapi import (ImdbApiSearchTitlesResponse, ImdbApiListTitlesResponse, ImdbApiListTitleEpisodesResponse,
|
||||
ImdbApiListTitleSeasonsResponse, ImdbApiListTitleCreditsResponse,
|
||||
ImdbapiListTitleAKAsResponse, ImdbApiTitleImagesResponse)
|
||||
ImdbapiListTitleAKAsResponse, ImdbApiTitleImagesResponse, ImdbapiCompanyCreditResponse)
|
||||
from .schema.imdbtypes import ImdbType
|
||||
|
||||
|
||||
@@ -769,3 +769,24 @@ class ImdbApiClient:
|
||||
page_token = response.next_page_token
|
||||
if not page_token:
|
||||
break
|
||||
|
||||
async def company_credits(self, title_id: str, categories: list[str] | None = None
|
||||
) -> Optional[ImdbapiCompanyCreditResponse]:
|
||||
"""
|
||||
Retrieve the company credits associated with a specific title.
|
||||
|
||||
:param title_id: Required. IMDb title ID in the format "tt1234567".
|
||||
:param categories: Optional. The categories of company credit to filter by.
|
||||
:return: Company Credits.
|
||||
"""
|
||||
path = "/titles/%s/companyCredits"
|
||||
param: dict[str, Any] = {}
|
||||
if categories:
|
||||
param['categories'] = categories
|
||||
try:
|
||||
r = await self._async_free_imdb_api(path=path % title_id, params=param)
|
||||
ret = ImdbapiCompanyCreditResponse.model_validate(r)
|
||||
except Exception as e:
|
||||
logger.debug(f"An error occurred while retrieving company credits: {e}")
|
||||
return None
|
||||
return ret
|
||||
|
||||
@@ -113,7 +113,7 @@ class ImdbHelper:
|
||||
logger.error("Error getting staff picks")
|
||||
return None
|
||||
try:
|
||||
data = StaffPickApiResponse.model_validate_json(res)
|
||||
data = StaffPickApiResponse.model_validate_json(res, by_name=True)
|
||||
except (JSONDecodeError, ValidationError):
|
||||
return None
|
||||
return data
|
||||
@@ -210,7 +210,8 @@ class ImdbHelper:
|
||||
return key
|
||||
return ""
|
||||
|
||||
async def advanced_title_search_generator(self, params: SearchParams, first_page: bool = True) -> AsyncGenerator[TitleEdge, None]:
|
||||
async def advanced_title_search_generator(self, params: SearchParams, first_page: bool = True
|
||||
) -> AsyncGenerator[TitleEdge, None]:
|
||||
await self._async_update_hash()
|
||||
sha256 = self._imdb_api_hash.advanced_title_search
|
||||
if not first_page and params in self._title_generators:
|
||||
@@ -253,7 +254,7 @@ class ImdbHelper:
|
||||
seasons_dict[s] = episode.release_date
|
||||
return seasons_dict
|
||||
|
||||
def match_by(self, name: str, mtype: Optional[MediaType] = None, year: Optional[str] = None) -> Optional[ImdbMediaInfo]:
|
||||
def match_by(self, name: str, mtype: MediaType | None = None, year: str | None = None) -> ImdbMediaInfo | None:
|
||||
"""
|
||||
根据名称同时查询电影和电视剧,没有类型也没有年份时使用
|
||||
|
||||
|
||||
@@ -275,10 +275,35 @@ INTERESTS_ID: Final[Dict[str, Dict[str, str]]] = {
|
||||
"Western Epic": "in0000189"
|
||||
}
|
||||
}
|
||||
|
||||
COMPANY_ID = {
|
||||
"20th Century Fox": ["co0000756", "co0176225", "co0201557", "co0017497"],
|
||||
"DreamWorks": ["co0067641", "co0040938", "co0252576", "co0003158"],
|
||||
"MGM": ["co0007143", "co0026841"],
|
||||
"Paramount": ["co0023400"],
|
||||
"Sony": ["co0050868", "co0026545", "co0121181"],
|
||||
"Universal": ["co0005073", "co0055277", "co0042399"],
|
||||
"Walt Disney": ["co0008970", "co0017902", "co0098836", "co0059516", "co0092035", "co0049348"],
|
||||
"Warner Bros.": ["co0002663", "co0005035", "co0863266", "co0072876", "co0080422", "co0046718"],
|
||||
"HBO": ["co0008693", "co0754095", "co0306346", "co0148466", "co0909975", "co0638197", "co0391378"],
|
||||
"Netflix": ["co0144901", "co0805756"],
|
||||
"Hulu": ["co0218858", "co0381648"],
|
||||
"Amazon Prime Video": ["co0476953", "co1160313", "co0939864", "co0931938"],
|
||||
"Apple TV": ["co0931939", "co0546168"],
|
||||
"British Broadcasting Corporation (BBC)": ['co0043107'],
|
||||
"Tencent Video": ["co0487058"],
|
||||
"Youku": ["co0264223"],
|
||||
"iQIYI": ["co0493506", "co0691262"],
|
||||
"China Central Television (CCTV)": ['co0001524'],
|
||||
"Huayi Brothers Media": ["co0099734"],
|
||||
"Beijing Enlight Pictures": ["co0208796"],
|
||||
"Bona Film Group": ["co0452101"],
|
||||
}
|
||||
|
||||
CACHE_LIFETIME: Final[int] = 86400
|
||||
IMDB_GRAPHQL_QUERY: Final[str] = dedent("""
|
||||
query VerticalListPageItems( $titles: [ID!]! $names: [ID!]! $images: [ID!]! $videos: [ID!]!) {
|
||||
titles(ids: $titles) { ...TitleParts meterRanking { currentRank meterType rankChange {changeDirection difference} } ratingsSummary { aggregateRating } }
|
||||
titles(ids: $titles) { ...TitleParts meterRanking { currentRank meterType rankChange {changeDirection difference} } ratingsSummary { aggregateRating voteCount} }
|
||||
names(ids: $names) { ...NameParts }
|
||||
videos(ids: $videos) { ...VideoParts }
|
||||
images(ids: $images) { ...ImageParts }
|
||||
@@ -500,6 +525,12 @@ class OfficialApiClient:
|
||||
if in_id:
|
||||
constraints.append(in_id)
|
||||
variables["interestConstraint"] = {"allInterestIds": constraints, "excludeInterestIds": []}
|
||||
|
||||
if params.company:
|
||||
company_ids = COMPANY_ID.get(params.company)
|
||||
if company_ids:
|
||||
variables["creditedCompanyConstraint"] = {"anyCompanyIds": company_ids, "excludeCompanyIds": []}
|
||||
|
||||
if last_cursor:
|
||||
variables["after"] = last_cursor
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ class ErrorType(Enum):
|
||||
|
||||
class StaffPickEntry(BaseModel):
|
||||
name: str
|
||||
ttconst: str
|
||||
ttconst: str = Field(..., alias='id')
|
||||
rmconst: str
|
||||
detail: Optional[str] = ""
|
||||
description: Optional[str] = ""
|
||||
relatedconst: List[str] = Field(default_factory=list)
|
||||
relatedconst: List[str] = Field(default_factory=list, alias='relatedConst')
|
||||
viconst: Optional[str] = None
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ class SearchParams(BaseModel):
|
||||
award_constraint: Optional[Tuple[str, ...]] = None
|
||||
ranked: Optional[Tuple[str, ...]] = None
|
||||
interests: Optional[Tuple[str, ...]] = None
|
||||
company: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(
|
||||
frozen=True
|
||||
|
||||
@@ -154,3 +154,20 @@ class ImdbapiListTitleAKAsResponse(BaseModel):
|
||||
|
||||
class ImdbApiTitleImagesResponse(PagedResponse):
|
||||
images: List[ImdbapiImage] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ImdbapiCompany(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class ImdbapiCompanyCredit(BaseModel):
|
||||
company: ImdbapiCompany
|
||||
category: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Category of the company credit, such as production, sales, distribution, etc."
|
||||
)
|
||||
|
||||
|
||||
class ImdbapiCompanyCreditResponse(PagedResponse):
|
||||
company_credits: List[ImdbapiCompanyCredit] = Field(default_factory=list, alias='companyCredits')
|
||||
|
||||
@@ -4,6 +4,15 @@ from typing import Optional, List
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def format_number(n: int) -> str:
|
||||
units = ["", "K", "M", "B", "T"]
|
||||
idx = 0
|
||||
while n >= 1000 and idx < len(units) - 1:
|
||||
n //= 1000
|
||||
idx += 1
|
||||
return f"{n}{units[idx]}"
|
||||
|
||||
|
||||
class ImdbType(Enum):
|
||||
TV_SERIES = "tvSeries"
|
||||
TV_MINI_SERIES = "tvMiniSeries"
|
||||
@@ -23,6 +32,25 @@ class ImdbType(Enum):
|
||||
class TitleType(BaseModel):
|
||||
id: ImdbType
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
type_mapping = {
|
||||
ImdbType.TV_SERIES: "TV Series",
|
||||
ImdbType.TV_MINI_SERIES: "TV Mini Series",
|
||||
ImdbType.MOVIE: "Movie",
|
||||
ImdbType.TV_MOVIE: "TV Movie",
|
||||
ImdbType.MUSIC_VIDEO: "Music Video",
|
||||
ImdbType.TV_SHORT: "TV Short",
|
||||
ImdbType.SHORT: "Short",
|
||||
ImdbType.TV_EPISODE: "TV Episode",
|
||||
ImdbType.TV_SPECIAL: "TV Special",
|
||||
ImdbType.VIDEO_GAME: "Video Game",
|
||||
ImdbType.VIDEO: "Video",
|
||||
ImdbType.PODCAST_SERIES: "Podcast Series",
|
||||
ImdbType.PODCAST_EPISODE: "Podcast Episode",
|
||||
}
|
||||
return type_mapping.get(self.id, "Unknown")
|
||||
|
||||
|
||||
class ReleaseYear(BaseModel):
|
||||
year: Optional[int] = None
|
||||
@@ -89,6 +117,23 @@ class MeterRanking(BaseModel):
|
||||
meter_type: Optional[str] = Field(default=None, alias='meterType')
|
||||
rank_change: Optional[RankChange] = Field(default=None, alias='rankChange')
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
if self.current_rank:
|
||||
rank = self.current_rank
|
||||
meter_rank = ""
|
||||
if self.meter_type:
|
||||
meter_rank = self.meter_type.replace("_", "").replace("METER", "Meter")
|
||||
meter_rank = f" {meter_rank}"
|
||||
return f"#{rank}{meter_rank}"
|
||||
return ""
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
if self.current_rank and self.meter_type:
|
||||
return f"https://www.imdb.com/chart/{self.meter_type.replace("_", "").lower()}/"
|
||||
return ""
|
||||
|
||||
|
||||
class RatingsSummary(BaseModel):
|
||||
aggregate_rating: Optional[float] = Field(default=None, alias='aggregateRating')
|
||||
@@ -154,8 +199,24 @@ class ImdbTitle(BaseModel):
|
||||
@property
|
||||
def rating_text(self) -> str:
|
||||
if self.ratings_summary and self.ratings_summary.aggregate_rating:
|
||||
return f"{self.ratings_summary.aggregate_rating:.1f}"
|
||||
return "-"
|
||||
votes = ""
|
||||
if self.ratings_summary.vote_count:
|
||||
votes = f" ({format_number(self.ratings_summary.vote_count)})"
|
||||
return f"{self.ratings_summary.aggregate_rating:.1f}{votes}"
|
||||
return "-/10"
|
||||
|
||||
@property
|
||||
def meter_ranking_text(self) -> str:
|
||||
if self.meter_ranking and self.meter_ranking.current_rank:
|
||||
return self.meter_ranking.text
|
||||
return ""
|
||||
|
||||
@property
|
||||
def certificate_text(self) -> str:
|
||||
if self.certificate and self.certificate.rating:
|
||||
return self.certificate.rating
|
||||
return ""
|
||||
|
||||
|
||||
class Thumbnail(BaseModel):
|
||||
url: str
|
||||
|
||||
@@ -60,7 +60,7 @@ class LexiAnnot(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "LexiAnnot.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.2.3"
|
||||
plugin_version = "1.2.4"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
|
||||
@@ -509,10 +509,6 @@ Your goal is two-fold:
|
||||
* **Do NOT include** simple high-frequency words, common fillers ('gonna', 'gotta'), onomatopoeia, or basic swear words.
|
||||
|
||||
-------------------------
|
||||
You MUST return output strictly matching the provided Pydantic schema.
|
||||
Return ONLY valid JSON.
|
||||
|
||||
**Here are the output format instructions you MUST follow strictly:**
|
||||
{format_instructions}
|
||||
""",
|
||||
),
|
||||
@@ -556,10 +552,6 @@ For each word (identified by `WORD_ID`), provide:
|
||||
**Your judgment should be based strictly on the provided subtitle context. DO NOT fabricate context or forced explanation.**
|
||||
|
||||
-------------------------
|
||||
You MUST return output strictly matching the provided Pydantic schema.
|
||||
Return ONLY valid JSON.
|
||||
|
||||
**Here are the output format instructions you MUST follow strictly:**
|
||||
{format_instructions}
|
||||
""",
|
||||
),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import re
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from enum import Enum
|
||||
from enum import Enum, StrEnum
|
||||
from typing import Literal, Generator, Iterator
|
||||
|
||||
from pydantic import BaseModel, Field, RootModel, model_validator
|
||||
from pydantic import BaseModel, Field, RootModel, model_validator, field_validator
|
||||
|
||||
from app.utils.singleton import Singleton
|
||||
|
||||
@@ -12,9 +12,8 @@ from app.utils.singleton import Singleton
|
||||
Cefr = Literal["C2", "C1", "B2", "B1", "A2", "A1"]
|
||||
|
||||
|
||||
class UniversalPos(str, Enum):
|
||||
class UniversalPos(StrEnum):
|
||||
"""Universal Part-of-Speech tags"""
|
||||
|
||||
ADJ = "ADJ" # Adjective
|
||||
ADV = "ADV" # Adverb
|
||||
INTJ = "INTJ" # Interjection
|
||||
@@ -34,9 +33,8 @@ class UniversalPos(str, Enum):
|
||||
X = "X" # Other/unknown
|
||||
|
||||
|
||||
class LexicalFeatures(str, Enum):
|
||||
class LexicalFeatures(StrEnum):
|
||||
"""Lexical features for words."""
|
||||
|
||||
FORMAL = "formal"
|
||||
INFORMAL = "informal"
|
||||
SLANG = "slang"
|
||||
@@ -333,6 +331,14 @@ class LlmWordEnrichment(BaseModel):
|
||||
usage_context: str | None = Field(default=None, description="Usage or Cultural Context")
|
||||
lexical_features: list[LexicalFeatures] = Field(default_factory=list, description="Lexical features")
|
||||
|
||||
@field_validator("lexical_features", mode="before")
|
||||
@classmethod
|
||||
def filter_invalid_lexical_features(cls, v):
|
||||
if isinstance(v, list):
|
||||
valid_values = {f.value for f in LexicalFeatures}
|
||||
return [item for item in v if item in valid_values]
|
||||
return v
|
||||
|
||||
|
||||
class LlmEnrichmentResult(BaseModel):
|
||||
enriched_words: list[LlmWordEnrichment] = Field(default_factory=list, description="List of enriched word data")
|
||||
|
||||
Reference in New Issue
Block a user