Skip to content

Commit

Permalink
feat: 增加Emby媒体服务器支持(Beta)
Browse files Browse the repository at this point in the history
主要功能:在参数配置页添加媒体服务器(Emby)之后,可以在搜索页面的电影卡片下方显示服务器中已存在的媒体信息。

PS:给2024年画上圆满的句号,2025诸事顺意!
  • Loading branch information
ronggang committed Dec 31, 2024
1 parent 4c2806e commit cf69b46
Show file tree
Hide file tree
Showing 18 changed files with 1,091 additions and 26 deletions.
1 change: 1 addition & 0 deletions public/assets/media-server/emby.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 33 additions & 1 deletion resource/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@
"downloadPaths": "Download Paths",
"searchSolution": "Search Solution",
"backup": "Backup & Restore",
"permissions": "Permissions"
"permissions": "Permissions",
"mediaServers": "Media Servers"
},
"thanks": {
"title": "Thanks",
Expand Down Expand Up @@ -849,6 +850,37 @@
"action": "Action"
}
}
},
"mediaServers": {
"index": {
"title": "Media Servers",
"subTitle": "Media servers are currently only used to determine if the searched resource already exists.",
"add": "Add",
"remove": "Remove",
"clear": "Clear",
"itemDuplicate": "The name already exists.",
"removeConfirm": "Are you sure you want to remove this media server?",
"removeConfirmTitle": "Remove Confirmation",
"clearConfirm": "Are you sure you want to remove all media servers?",
"removeSelectedConfirm": "Are you sure you want to remove the selected media servers?",
"yes": "Yes",
"no": "No",
"headers": {
"name": "Name",
"enabled": "Enabled",
"type": "Type",
"address": "Server Address",
"action": "Action"
}
},
"editor": {
"type": "Server Type",
"name": "Service Name",
"address": "Server Address",
"addressTip": "Complete server address (including port), e.g.: http://192.168.1.1:5000/",
"apiKey": "API KEY",
"test": "Test if the server is connectable"
}
}
},
"statistic": {
Expand Down
34 changes: 33 additions & 1 deletion resource/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
"downloadPaths": "下载目录设置",
"searchSolution": "搜索方案",
"backup": "参数备份与恢复",
"permissions": "权限设置"
"permissions": "权限设置",
"mediaServers": "媒体服务器"
},
"thanks": {
"title": "鸣谢",
Expand Down Expand Up @@ -844,6 +845,37 @@
"action": "操作"
}
}
},
"mediaServers": {
"index": {
"title": "媒体服务器",
"subTitle": "媒体服务器当前仅用于判断搜索资源是否已存在。",
"add": "新增",
"remove": "删除",
"clear": "清除",
"itemDuplicate": "该名称已存在",
"removeConfirm": "确认要删除这个媒体服务器吗?",
"removeConfirmTitle": "删除确认",
"clearConfirm": "确认要删除所有媒体服务器吗?",
"removeSelectedConfirm": "确认要删除已选中的媒体服务器吗?",
"yes": "",
"no": "",
"headers": {
"name": "名称",
"enabled": "是否启用",
"type": "类型",
"address": "服务器地址",
"action": "操作"
}
},
"editor": {
"type": "服务器类型",
"name": "服务名称",
"address": "服务器地址",
"addressTip": "完整的服务器地址(含端口),如:http://192.168.1.1:5000/",
"apiKey": "API KEY",
"test": "测试服务器是否可连接"
}
}
},
"statistic": {
Expand Down
10 changes: 10 additions & 0 deletions src/background/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { User } from "./user";
import { MovieInfoService } from "@/service/movieInfoService";
import { remote as parseTorrentRemote } from "parse-torrent";
import { PPF } from "@/service/public";
import { MediaServerManager } from "./mediaServerManager";

type Service = PTPlugin;
export default class Controller {
Expand All @@ -46,6 +47,7 @@ export default class Controller {
public searcher: Searcher = new Searcher(this.service);
public userService: User = new User(this.service);
public movieInfoService = new MovieInfoService();
public mediaServerManager = new MediaServerManager();

public clientController: ClientController = new ClientController();
public isInitialized: boolean = false;
Expand Down Expand Up @@ -1367,6 +1369,14 @@ export default class Controller {
return this.service.config.testBackupServerConnectivity(options);
}

public testMediaServerConnectivity(options: any): Promise<any> {
return this.mediaServerManager.ping(options);
}

public getMediaFromMediaServer(options: any): Promise<any> {
return this.mediaServerManager.getMediaFromMediaServer(options.server, options.imdbId);
}

public createSearchResultSnapshot(options: any): Promise<any> {
return this.service.searchResultSnapshot.add(options);
}
Expand Down
58 changes: 58 additions & 0 deletions src/background/mediaServerManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { EMediaServerType, IMediaServer } from "@/interface/common";
import { Emby } from "./plugins/Emby";

export class MediaServerManager {
private servers: any = {};


private getServer(options: IMediaServer) {
let server = this.servers[options.id];

if (server) {
return server;
}

switch (options.type) {
case EMediaServerType.Emby:
server = new Emby(options);
break;

default:
break;
}

if (server) {
this.servers[options.id] = server;
}

return server;
}

public reset() {
for (const item of this.servers) {
this.servers[item] = undefined;
delete this.servers[item];
}
this.servers = {};
}

public async ping(options: IMediaServer) {
let server = this.getServer(options);

if (server) {
return server.ping();
}

return false;
}

public async getMediaFromMediaServer(options: IMediaServer, imdbId: string) {
let server = this.getServer(options);

if (server) {
return server.getMediaFromMediaServer(imdbId);
}

return false;
}
}
127 changes: 127 additions & 0 deletions src/background/plugins/Emby.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { IMediaServer } from "@/interface/common";
export type Dictionary<T> = { [key: string]: T };

export class Emby {
public serverURL: string = "";

private API: any = {
methods: {
findFromIMDb: 'Items?Recursive=true&Fields=Path,Size,OfficialRating,MediaSources&AnyProviderIdEquals=imdb.$imdbId$',
getSystemInfo: 'System/Info'
}
}

constructor(public options: IMediaServer) {
this.serverURL = this.options.address;
if (this.serverURL.substr(-1) !== "/") {
this.serverURL += "/";
}
}

/**
* 替换指定的字符串列表
* @param source
* @param maps
*/
public replaceKeys(
source: string,
maps: Dictionary<any>,
prefix: string = ""
): string {
if (!source) {
return source;
}
let result: string = source;

for (const key in maps) {
if (maps.hasOwnProperty(key)) {
const value = maps[key];
let search = "$" + key + "$";
if (prefix) {
search = `$${prefix}.${key}$`;
}
result = result.replace(search, value);
}
}
return result;
}

/**
* 指定指定的API
* @param method
* @param data
* @returns
*/
public async execAPI(method = '', data: any = {}) {
let m: any;
let methods = method.split(".");

if (methods.length == 1) {
m = this.API.methods[method]
} else {
m = this.API.methods[methods[0]][methods[1]]
}

let url = '';
let mode = 'GET';
if (typeof (m) == 'string') {
url = m;
} else {
url = m.url;
mode = m.mode || 'GET';
}

url = this.serverURL + this.replaceKeys(url, data);

const options = {
method: mode,
headers: {
accept: 'application/json',
'X-Emby-Token': `${this.options.apiKey}`
}
};


try {
const response = await fetch(url, options);
if (response.ok) {
const result = await response.json();
return result;
} else {
throw new Error(`HTTP 错误!状态码:${response.status}`);
}
} catch (error) {

}

return false;
}

/**
* 验证服务器可用性
*/
public async ping() {
const result = await this.execAPI('getSystemInfo');
if (result && result.Id) {
return true;
}

return false;
}

/**
* 根据imdbId 获取媒体信息
* @param imdbId
* @returns
*/
public async getMediaFromMediaServer(imdbId: string) {
const result = await this.execAPI('findFromIMDb', {
imdbId
});
if (result && result.Items) {
return result;
}

return false;
}
}
18 changes: 15 additions & 3 deletions src/interface/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
EWorkingStatus,
EEncryptMode,
ETorrentStatus,
ERequestType
ERequestType,
EMediaServerType
} from "./enum";

/**
Expand All @@ -27,7 +28,7 @@ export interface ContextMenuRules {

export interface DownloadClient {
id?: string;
enabled?:boolean;
enabled?: boolean;
name?: string;
// oldName?: string;
address?: string;
Expand Down Expand Up @@ -55,6 +56,16 @@ export interface QbCategory {
path: string;
}

export interface IMediaServer {
id: string;
enabled: boolean;
name: string;
address: string;
type: EMediaServerType;
apiKey?: string;
desc?: string;
}

/**
* 助手按钮
*/
Expand Down Expand Up @@ -119,6 +130,7 @@ export interface Options {
exceedSizeUnit?: ESizeUnit;
sites: any[];
clients: any[];
mediaServers?: IMediaServer[];
pluginIconShowPages?: string[];
contextMenuRules?: ContextMenuRules;
allowSelectionTextSearch?: boolean;
Expand Down Expand Up @@ -384,7 +396,7 @@ export interface Request {
data?: any;
}

export interface IRequest extends Request {}
export interface IRequest extends Request { }

export interface NoticeOptions {
msg?: string;
Expand Down
14 changes: 13 additions & 1 deletion src/interface/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,12 @@ export enum EAction {
pushDebugMsg = "pushDebugMsg",
updateDebuggerTabId = "updateDebuggerTabId",
// 获取热门搜索
getTopSearches = "getTopSearches"
getTopSearches = "getTopSearches",

// 测试媒体服务器是否可连接
testMediaServerConnectivity = "testMediaServerConnectivity",
// 从媒体服务器中获取信息
getMediaFromMediaServer = "getMediaFromMediaServer"
}

/**
Expand Down Expand Up @@ -388,6 +393,13 @@ export enum EBackupServerType {
WebDAV = "WebDAV"
}

/**
* 媒体服务器类型
*/
export enum EMediaServerType {
Emby = "Emby"
}

/**
* 插件显示位置
*/
Expand Down
Loading

0 comments on commit cf69b46

Please sign in to comment.