Files
2026-08-14 11:22:01 +02:00

152 lines
8.1 KiB
JavaScript

let host = 'https://emby.bangumi.ca';/*"Emby服务器URL (例如 http://localhost:8096): "*/
let Token = "8b0b16aae7e8403cb3d19969b82c3902";
let Users = "80e861cbff1343bfa0bedcea78895b91";
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": host + "/",
"Accept-Language": "zh-CN,zh;q=0.9"
};
const init = async () => {};
const extractVideos = jsonData => {
if (!jsonData || !jsonData.Items) return [];
return jsonData.Items.map(it => {
return {
vod_id: it.Id,
vod_name: it.Name || "",
vod_pic: it.ImageTags?.Primary ? `${host}/emby/Items/${it.Id}/Images/Primary?maxWidth=400&tag=${it.ImageTags.Primary}&quality=90` : "",
vod_remarks: it.ProductionYear ? it.ProductionYear.toString() : ""
};
});
};
const homeVod = async () => {
const url = `${host}/emby/Users/${Users}/Views?X-Emby-Client=Emby+Web&X-Emby-Device-Name=Android+WebView+Android&X-Emby-Device-Id=ea27caf7-9a51-4209-b1a5-374bf30c2ffd&X-Emby-Client-Version=4.9.0.31&X-Emby-Token=${Token}&X-Emby-Language=zh-cn`;
const resp = await req(url, { headers });
if (!resp?.content) return JSON.stringify({ list: [] });
const json = JSON.parse(resp.content);
return JSON.stringify({ list: extractVideos(json) });
};
const home = async () => {
const url = `${host}/emby/Users/${Users}/Views?X-Emby-Client=Emby+Web&X-Emby-Device-Name=Android+WebView+Android&X-Emby-Device-Id=ea27caf7-9a51-4209-b1a5-374bf30c2ffd&X-Emby-Client-Version=4.9.0.31&X-Emby-Token=${Token}&X-Emby-Language=zh-cn`;
const resp = await req(url, { headers });
if (!resp?.content) return JSON.stringify({ class: [], filters: {}, list: [] });
const json = JSON.parse(resp.content);
const classList = json.Items
.filter(it => !it.Name.includes("播放列表") && !it.Name.includes("相机"))
.map(it => ({
type_id: it.Id,
type_name: it.Name
}));
const list = [];
return JSON.stringify({
class: classList,
filters: {},
list
});
};
const category = async (tid, pg, _, extend) => {
const startIndex = (pg - 1) * 30;
const url = `${host}/emby/Users/${Users}/Items?SortBy=DateLastContentAdded%2CSortName&SortOrder=Descending&IncludeItemTypes=Movie%2CSeries&Recursive=true&Fields=BasicSyncInfo%2CCanDelete%2CContainer%2CPrimaryImageAspectRatio%2CProductionYear%2CCommunityRating%2CStatus%2CCriticRating%2CEndDate%2CPath&StartIndex=${startIndex}&ParentId=${tid}&EnableImageTypes=Primary%2CBackdrop%2CThumb%2CBanner&ImageTypeLimit=1&Limit=30&EnableUserData=true&X-Emby-Token=${Token}`;
const resp = await req(url, { headers });
if (!resp?.content) return JSON.stringify({ list: [], page: +pg, pagecount: 1, limit: 30 });
const json = JSON.parse(resp.content);
const list = extractVideos(json);
const total = json.TotalRecordCount || 0;
const pagecount = pg * 30 < total ? +pg + 1 : +pg;
return JSON.stringify({ list, page: +pg, pagecount, limit: 30, total });
};
const detail = async id => {
const url = `${host}/emby/Users/${Users}/Items/${id}?X-Emby-Token=${Token}`;
const resp = await req(url, { headers });
if (!resp?.content) return JSON.stringify({ list: [] });
const info = JSON.parse(resp.content);
let playFrom = "EMBY";
let playUrl = "";
if (!info.IsFolder) {
playUrl += `${info.Name.trim()}$${info.Id}#`;
} else {
if (info.Type === "Series") {
const seasonsUrl = `${host}/emby/Shows/${id}/Seasons?UserId=${Users}&Fields=BasicSyncInfo%2CCanDelete%2CContainer%2CPrimaryImageAspectRatio%2CProductionYear%2CCommunityRating&EnableImages=true&EnableUserData=true&X-Emby-Token=${Token}`;
const seasonsResp = await req(seasonsUrl, { headers });
if (seasonsResp?.content) {
const seasons = JSON.parse(seasonsResp.content);
for (const season of seasons.Items) {
const episodesUrl = `${host}/emby/Shows/${id}/Episodes?SeasonId=${season.Id}&Fields=BasicSyncInfo%2CCanDelete%2CCommunityRating%2CPrimaryImageAspectRatio%2CProductionYear%2COverview&UserId=${Users}&Limit=1000&X-Emby-Token=${Token}`;
const episodesResp = await req(episodesUrl, { headers });
if (episodesResp?.content) {
const episodes = JSON.parse(episodesResp.content);
for (const episode of episodes.Items) {
const seasonName = season.Name.replace('#', '-').replace('$', '|').trim();
const episodeName = episode.Name.trim();
playUrl += `${seasonName}|${episodeName}$${episode.Id}#`;
}
}
}
}
} else {
const itemsUrl = `${host}/emby/Users/${Users}/Items?ParentId=${id}&Fields=BasicSyncInfo%2CCanDelete%2CContainer%2CPrimaryImageAspectRatio%2CProductionYear%2CCommunityRating%2CCriticRating&ImageTypeLimit=1&StartIndex=0&EnableUserData=true&X-Emby-Token=${Token}`;
const itemsResp = await req(itemsUrl, { headers });
if (itemsResp?.content) {
const items = JSON.parse(itemsResp.content);
for (const item of items.Items) {
playUrl += `${item.Name.replace('#', '-').replace('$', '|').trim()}$${item.Id}#`;
}
}
}
}
playUrl = playUrl.slice(0, -1);
return JSON.stringify({
list: [{
vod_id: id,
vod_name: info.Name || "",
vod_pic: info.ImageTags?.Primary ? `${host}/emby/Items/${id}/Images/Primary?maxWidth=400&tag=${info.ImageTags.Primary}&quality=90` : "",
vod_content: info.Overview ? info.Overview.replace(/\xa0/g, ' ').replace(/\n\n/g, '\n').trim() : "暂无简介",
vod_year: info.ProductionYear ? info.ProductionYear.toString() : "",
vod_director: "",
vod_actor: "",
vod_type: info.Genres ? info.Genres.join(" / ") : "",
vod_play_from: playFrom,
vod_play_url: playUrl
}]
});
};
const search = async (wd, _, pg = 1) => {
const url = `${host}/emby/Users/${Users}/Items?SortBy=SortName&SortOrder=Ascending&Fields=BasicSyncInfo%2CCanDelete%2CContainer%2CPrimaryImageAspectRatio%2CProductionYear%2CStatus%2CEndDate&StartIndex=0&EnableImageTypes=Primary%2CBackdrop%2CThumb&ImageTypeLimit=1&Recursive=true&SearchTerm=${encodeURIComponent(wd)}&GroupProgramsBySeries=true&Limit=50&X-Emby-Token=${Token}`;
const resp = await req(url, { headers });
if (!resp?.content) return JSON.stringify({ list: [] });
const json = JSON.parse(resp.content);
return JSON.stringify({ list: extractVideos(json) });
};
const play = async (_, id) => {
const playbackUrl = `${host}/emby/Items/${id}/PlaybackInfo?UserId=${Users}&IsPlayback=false&AutoOpenLiveStream=false&StartTimeTicks=0&MaxStreamingBitrate=7000000&X-Emby-Token=${Token}`;
const resp = await req(playbackUrl, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json"
},
body: JSON.stringify({
"DeviceProfile": {
"DirectPlayProfiles": [{"Container": "mp4,m4v,mkv,hls,webm", "Type": "Video"}],
"TranscodingProfiles": [{"Container": "ts", "Type": "Video", "Protocol": "hls"}]
}
})
});
if (!resp?.content) return JSON.stringify({ parse: 1, url: playbackUrl, header: headers });
const json = JSON.parse(resp.content);
const mediaSources = json.MediaSources;
if (!mediaSources || mediaSources.length === 0) {
return JSON.stringify({ parse: 1, url: playbackUrl, header: headers });
}
const mediaSource = mediaSources[0];
if (mediaSource.DirectStreamUrl) {
return JSON.stringify({ parse: 0, url: host + mediaSource.DirectStreamUrl, header: headers });
} else if (mediaSource.Protocol === "Http") {
return JSON.stringify({ parse: 0, url: mediaSource.Path, header: headers });
}
return JSON.stringify({ parse: 1, url: playbackUrl, header: headers });
};
export default { init, home, homeVod, category, detail, search, play };