-
Notifications
You must be signed in to change notification settings - Fork 296
Expand file tree
/
Copy pathEmby[优].js
More file actions
303 lines (269 loc) · 11.5 KB
/
Emby[优].js
File metadata and controls
303 lines (269 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/*
@header({
searchable: 2,
filterable: 0,
quickSearch: 1,
title: 'Emby',
'类型': '影视',
lang: 'ds'
})
*/
const config = {
host: '',
userId: '',
token: '',
deviceId: '',
clientVersion: '',
pageSize: 30
};
// 设备配置:核心属性单独换行,短数组合并,格式清晰
const deviceProfile = {
DeviceProfile: {
MaxStaticBitrate: 140000000,
MaxStreamingBitrate: 140000000,
DirectPlayProfiles: [
{
Container: 'mp4,mkv,webm',
Type: 'Video',
VideoCodec: 'h264,h265,av1,vp9',
AudioCodec: 'aac,mp3,opus,flac'
},
{Container: 'mp3,aac,flac,opus', Type: 'Audio'}
],
TranscodingProfiles: [
{
Container: 'mp4',
Type: 'Video',
VideoCodec: 'h264',
AudioCodec: 'aac',
Context: 'Streaming',
Protocol: 'http'
},
{Container: 'aac', Type: 'Audio', Context: 'Streaming', Protocol: 'http'}
],
SubtitleProfiles: [{Format: 'srt,ass,vtt', Method: 'External'}],
CodecProfiles: [{
Type: 'Video',
Codec: 'h264',
ApplyConditions: [{Condition: 'LessThanEqual', Property: 'VideoLevel', Value: '62'}]
}],
BreakOnNonKeyFrames: true
}
};
// 提取重复常量:解决硬编码冗余,修改仅需改1处
const BASE_FIELDS = 'BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,Status,EndDate';
const IMAGE_TYPES = 'Primary,Backdrop,Thumb,Banner';
// 请求头生成:属性分行,结构清晰
function getHeaders(extra = {}) {
return {
'X-Emby-Client': 'Emby Web',
'X-Emby-Device-Name': 'Android WebView Android',
'X-Emby-Device-Id': config.deviceId,
'X-Emby-Client-Version': config.clientVersion,
'X-Emby-Token': config.token,
...extra
};
}
// 图片URL生成:三元运算符分行,可读性强
function getImageUrl(itemId, imageTag) {
return imageTag
? `${config.host}/emby/Items/${itemId}/Images/Primary?maxWidth=400&tag=${imageTag}&quality=90`
: '';
}
// 视频数据提取:逻辑块分行,变量单独声明
function extractVideos(jsonData) {
return (jsonData?.Items || []).map(function (item) {
const isFolder = ['Folder', 'BoxSet', 'CollectionFolder'].includes(item.Type);
return {
title: item.Name || '',
img: getImageUrl(item.Id, item.ImageTags?.Primary),
desc: item.ProductionYear?.toString() || '',
url: item.Id,
vod_tag: isFolder ? 'folder' : 'video'
};
});
}
// API请求:参数分行,逻辑清晰
async function fetchApi(url, options = {}) {
return await request(url, {
...options,
headers: getHeaders(options.headers || {})
});
}
var rule = {
title: 'Emby',
host: config.host,
headers: getHeaders(),
searchable: 2,
quickSearch: 1,
timeout: 15000,
play_parse: true,
searchUrl: `/emby/Users/${config.userId}/Items?` +
`SortBy=SortName&SortOrder=Ascending&Fields=${BASE_FIELDS}&` +
`StartIndex=0&EnableImageTypes=${IMAGE_TYPES}&ImageTypeLimit=1&` +
`Recursive=true&SearchTerm=**&GroupProgramsBySeries=true&Limit=50&X-Emby-Token=${config.token}`,
预处理: async function () {
// log('rule.params:', rule.params);
let parts = JSON.parse(rule.params);
if (parts) {
if (parts.host) config.host = parts.host;
if (parts.userId) config.userId = parts.userId;
if (parts.token) config.token = parts.token;
if (parts.deviceId) config.deviceId = parts.deviceId;
if (parts.clientVersion) config.clientVersion = parts.clientVersion;
// pageSize 保留原默认值,不覆盖
}
},
// 播放解析:按步骤拆分,每个逻辑块空行分隔
lazy: async function () {
// 提取视频ID
const videoId = typeof this.input === "string"
? (this.input.match(/\/Items\/(\w+)/)?.[1] || this.input)
: this.input;
// 请求播放信息
const json = JSON.parse(await fetchApi(
`${config.host}/emby/Items/${videoId}/PlaybackInfo`,
{
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(deviceProfile)
}
));
// 媒体源判断:短路求值简化逻辑,避免嵌套
const mediaSource = json?.MediaSources?.[0];
if (!mediaSource) {
return {
parse: 1,
url: `${config.host}/emby/Items/${videoId}/PlaybackInfo`,
header: getHeaders({"Content-Type": "application/json"}),
msg: "没有可用的媒体源"
};
}
// 构建播放URL
const playUrl = `${config.host}/emby/videos/${videoId}/stream?` +
`Static=true&MediaSourceId=${mediaSource.Id}&DeviceId=${config.deviceId}&` +
`api_key=${config.token}&PlaySessionId=${json.PlaySessionId || ''}`;
return {parse: 0, jx: 0, url: playUrl, header: getHeaders()};
},
// 分类解析:逻辑块分行,过滤/映射单独换行
class_parse: async function () {
const json = JSON.parse(await fetchApi(`${config.host}/emby/Users/${config.userId}/Views`));
const classList = (json?.Items || []).filter(function (it) {
return !it.Name.includes("播放列表") && !it.Name.includes("相机");
}).map(function (it) {
return {type_id: it.Id, type_name: it.Name};
});
return {class: classList, filters: {}, list: []};
},
// 推荐:URL拆分,请求-解析-返回逻辑清晰
推荐: async function (tid, pg, filter, extend) {
const url = `${config.host}/emby/Users/${config.userId}/Items?` +
`SortBy=DateCreated&SortOrder=Descending&IncludeItemTypes=Movie,Series,Folder&` +
`Recursive=true&Limit=40&Fields=${BASE_FIELDS},CommunityRating,CriticRating,Path,Overview,IsFolder&` +
`EnableImageTypes=${IMAGE_TYPES}&ImageTypeLimit=1`;
const json = JSON.parse(await fetchApi(url));
return setResult(extractVideos(json));
},
// 一级分类:关键变量单独声明,URL按参数组拆分
一级: async function (tid, pg, filter, extend) {
const startIndex = (pg - 1) * config.pageSize;
const url = `${config.host}/emby/Users/${config.userId}/Items?` +
`SortBy=DateLastContentAdded,SortName&SortOrder=Descending&` +
`IncludeItemTypes=Movie,Series,Folder&Recursive=true&` +
`Fields=${BASE_FIELDS},CommunityRating,CriticRating,Path,Overview,IsFolder&` +
`StartIndex=${startIndex}&ParentId=${tid}&EnableImageTypes=${IMAGE_TYPES}&` +
`ImageTypeLimit=1&Limit=${config.pageSize}&EnableUserData=true`;
const json = JSON.parse(await fetchApi(url));
return setResult(extractVideos(json));
},
// 二级详情:按功能模块拆分,每个模块空行分隔
二级: async function (ids) {
// 1. 获取基础信息
const detailUrl = `${config.host}/emby/Users/${config.userId}/Items/${ids}?` +
`Fields=${BASE_FIELDS},CommunityRating,CriticRating,Path,Overview,People,Studios,RunTimeTicks,MediaStreams`;
const info = JSON.parse(await fetchApi(detailUrl));
// 2. 解构提取核心数据:简化变量声明,避免多层if
const {People = [], Studios = [], MediaStreams = [], RunTimeTicks} = info;
const director = People.filter(function (p) {
return p.Type === "Director" || (p.Role && p.Role.includes("Director"));
}).map(function (p) {
return p.Name;
}).join(" / ");
const actor = People.filter(function (p) {
return p.Type === "Actor" || (p.Role && p.Role.includes("Actor"));
}).map(function (p) {
return p.Name;
}).join(" / ");
const area = Studios.map(function (s) {
return s.Name;
}).join(" / ");
const language = Array.from(new Set(
MediaStreams.filter(function (s) {
return s.Type === "Audio" && s.Language;
})
.map(function (s) {
return s.Language;
})
)).join(" / ");
// 3. 时长计算:逻辑单独成块,变量命名清晰
let duration = "";
if (RunTimeTicks) {
const mins = Math.floor(RunTimeTicks / 600000000);
const hours = Math.floor(mins / 60);
duration = hours > 0 ? `${hours}小时${mins}分钟` : `${mins}分钟`;
}
// 4. 构建基础VOD信息:属性分行,结构清晰
const VOD = {
type_name: (info.Genres || []).join(" "),
vod_name: info.Name || "",
vod_pic: getImageUrl(info.Id, info.ImageTags?.Primary),
vod_remarks: `评分:${info.CommunityRating || "N/A"}`,
vod_content: info.Overview || "",
vod_year: info.ProductionYear?.toString() || "",
vod_director: director,
vod_actor: actor,
vod_area: area,
vod_lang: language,
vod_time: duration
};
// 5. 剧集特殊处理:循环内逻辑分行,避免堆砌
if (info.Type === "Series") {
const seasonsUrl = `${config.host}/emby/Shows/${ids}/Seasons?` +
`UserId=${config.userId}&Fields=${BASE_FIELDS},Path,Overview&EnableTotalRecordCount=false`;
const seasons = (JSON.parse(await fetchApi(seasonsUrl))).Items || [];
const from = [], result = [];
for (let i = 0; i < seasons.length; i++) {
const season = seasons[i];
from.push(season.Name);
const episodesUrl = `${config.host}/emby/Shows/${ids}/Episodes?` +
`SeasonId=${season.Id}&ImageTypeLimit=1&UserId=${config.userId}&` +
`Fields=Overview,PrimaryImageAspectRatio&Limit=1000`;
const episodes = (JSON.parse(await fetchApi(episodesUrl))).Items || [];
// 仅新增:添加第几集的序号显示
result.push(episodes.map(function (item) {
// 获取集序号,兜底处理
const episodeNum = item.IndexNumber ? `第${item.IndexNumber}集` : "未知集数";
// 拼接序号和原名称
const fullName = item.Name ? `${episodeNum} ${item.Name}` : episodeNum;
return `${fullName}$${item.Id}`;
}).join("#"));
}
VOD.vod_play_from = from.join("$$$");
VOD.vod_play_url = result.join("$$$");
} else {
VOD.vod_play_from = "EMBY";
VOD.vod_play_url = `${info.Name || "播放"}$${ids}`;
}
return VOD;
},
// 搜索:URL拆分,逻辑简洁清晰
搜索: async function (wd, quick, pg = 1) {
const url = `${config.host}/emby/Users/${config.userId}/Items?` +
`SortBy=SortName&SortOrder=Ascending&Fields=${BASE_FIELDS}&` +
`StartIndex=0&EnableImageTypes=${IMAGE_TYPES}&ImageTypeLimit=1&` +
`Recursive=true&SearchTerm=${encodeURIComponent(wd)}&` +
`GroupProgramsBySeries=true&Limit=50`;
const json = JSON.parse(await fetchApi(url));
return setResult(extractVideos(json));
}
};