├── .DS_Store ├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── MovieDb.Security ├── Actor.cs ├── Detector.cs └── PluginStartup.cs ├── MovieDb.thumb.png ├── MovieDb ├── GenericMovieDbInfo.cs ├── MovieDbBoxSetImageProvider.cs ├── MovieDbBoxSetProvider.cs ├── MovieDbCollectionExternalId.cs ├── MovieDbEpisodeImageProvider.cs ├── MovieDbEpisodeProvider.cs ├── MovieDbImageProvider.cs ├── MovieDbMovieExternalId.cs ├── MovieDbMusicVideoProvider.cs ├── MovieDbPersonExternalId.cs ├── MovieDbPersonImageProvider.cs ├── MovieDbPersonProvider.cs ├── MovieDbProvider.cs ├── MovieDbProviderBase.cs ├── MovieDbSearch.cs ├── MovieDbSeasonImageProvider.cs ├── MovieDbSeasonProvider.cs ├── MovieDbSeriesExternalId.cs ├── MovieDbSeriesImageProvider.cs ├── MovieDbSeriesProvider.cs ├── MovieDbTrailerProvider.cs ├── Plugin.cs ├── PluginOptions.cs ├── TmdbAlternativeTitles.cs ├── TmdbCast.cs ├── TmdbCredits.cs ├── TmdbCrew.cs ├── TmdbExternalIds.cs ├── TmdbGenre.cs ├── TmdbImage.cs ├── TmdbImageSettings.cs ├── TmdbImages.cs ├── TmdbKeyword.cs ├── TmdbKeywords.cs ├── TmdbLanguage.cs ├── TmdbReview.cs ├── TmdbReviews.cs ├── TmdbSettingsResult.cs ├── TmdbTitle.cs ├── TmdbVideo.cs └── TmdbVideos.cs ├── Properties └── AssemblyInfo.cs ├── README.md ├── TheMovieDb.csproj └── obj ├── Debug └── netstandard2.0 │ ├── .NETStandard,Version=v2.0.AssemblyAttributes.cs │ ├── MovieDb.GeneratedMSBuildEditorConfig.editorconfig │ ├── MovieDb.assets.cache │ ├── MovieDb.csproj.AssemblyReference.cache │ └── MovieDb.csproj.FileListAbsolute.txt ├── MovieDb.csproj.nuget.dgspec.json ├── MovieDb.csproj.nuget.g.props ├── MovieDb.csproj.nuget.g.targets ├── project.assets.json └── project.nuget.cache /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastian0619/Emby-TMDb-Plugin-configurable/149881054526682c159f63884984b48b7acd1bcf/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: '6.0.x' 20 | 21 | - name: Restore dependencies 22 | run: dotnet restore 23 | 24 | - name: Build 25 | run: dotnet build --configuration Release --framework netstandard2.0 --no-restore 26 | 27 | - name: Get version 28 | id: get_version 29 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 30 | shell: bash 31 | 32 | - name: Create Release 33 | id: create_release 34 | uses: actions/create-release@v1 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | with: 38 | tag_name: ${{ github.ref }} 39 | release_name: Release ${{ steps.get_version.outputs.VERSION }} 40 | draft: false 41 | prerelease: false 42 | 43 | - name: Upload Release Asset 44 | uses: actions/upload-release-asset@v1 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | with: 48 | upload_url: ${{ steps.create_release.outputs.upload_url }} 49 | asset_path: ./bin/Release/netstandard2.0/TheMovieDb.dll 50 | asset_name: TheMovieDb.dll 51 | asset_content_type: application/x-msdownload -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 临时文件 2 | .vs/ 3 | *.suo 4 | *.user 5 | *.userosscache 6 | *.sln.docstates 7 | *.userprefs 8 | 9 | # Visual Studio Code 设置 10 | .vscode/ 11 | 12 | # 构建结果 13 | [Dd]ebug/ 14 | [Dd]ebugPublic/ 15 | [Rr]elease/ 16 | [Rr]eleases/ 17 | x64/ 18 | x86/ 19 | [Ww][Ii][Nn]32/ 20 | [Aa][Rr][Mm]/ 21 | [Aa][Rr][Mm]64/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | [Ll]ogs/ 27 | 28 | # Visual Studio 2015/2017 缓存/配置目录 29 | .vs/ 30 | 31 | # MSTest 测试结果 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUnit 36 | *.VisualState.xml 37 | TestResult.xml 38 | nunit-*.xml 39 | 40 | # 构建结果 41 | [Dd]ebug/ 42 | [Rr]elease/ 43 | x64/ 44 | x86/ 45 | [Aa][Rr][Mm]/ 46 | [Aa][Rr][Mm]64/ 47 | bld/ 48 | [Bb]in/ 49 | [Oo]bj/ 50 | [Ll]og/ 51 | 52 | # Visual Studio 生成的文件 53 | *_i.c 54 | *_p.c 55 | *_h.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.iobj 60 | *.pch 61 | *.pdb 62 | *.ipdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *_wpftmp.csproj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.svclog 79 | *.scc 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | *.sap 86 | 87 | # ReSharper 88 | _ReSharper*/ 89 | *.[Rr]e[Ss]harper 90 | *.DotSettings.user 91 | 92 | # TeamCity 93 | _TeamCity* 94 | 95 | # DotCover 96 | *.dotCover 97 | 98 | # Visual Studio code coverage results 99 | *.coverage 100 | *.coveragexml 101 | 102 | # NuGet 103 | *.nupkg 104 | # NuGet Symbol Packages 105 | *.snupkg 106 | # NuGet包文件夹 107 | [Pp]ackages/ 108 | # NuGet 包缓存目录 109 | *.nuget.props 110 | *.nuget.targets 111 | 112 | # Windows 系统文件 113 | Thumbs.db 114 | ehthumbs.db 115 | Desktop.ini 116 | 117 | # macOS 系统文件 118 | .DS_Store 119 | .AppleDouble 120 | .LSOverride 121 | 122 | # JetBrains Rider 123 | .idea/ 124 | *.sln.iml 125 | 126 | # 项目特定的忽略 127 | # 添加你的项目特定的忽略规则 128 | 129 | # 依赖项目文件 130 | project.lock.json 131 | project.fragment.lock.json 132 | artifacts/ 133 | 134 | # ASP.NET Scaffolding 135 | ScaffoldingReadMe.txt 136 | 137 | # StyleCop 138 | StyleCopReport.xml 139 | 140 | # 文件基于 EditorConfig 的设置 141 | .editorconfig 142 | 143 | # Cake - 取消注释如果你使用它 144 | # tools/** 145 | # !tools/packages.config 146 | 147 | # Tabs Studio 148 | *.tss 149 | 150 | # Telerik's JustMock 151 | *.jmconfig 152 | 153 | # BizTalk 构建输出 154 | *.btp.cs 155 | *.btm.cs 156 | *.odx.cs 157 | *.xsd.cs 158 | 159 | # OpenCover UI analysis results 160 | OpenCover/ 161 | 162 | # Azure Stream Analytics 本地运行输出 163 | ASALocalRun/ 164 | 165 | # Windows Azure 构建输出 166 | csx/ 167 | *.build.csdef 168 | 169 | # 其他 170 | ~$* 171 | *~ -------------------------------------------------------------------------------- /MovieDb.Security/Actor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using MediaBrowser.Common; 10 | using MediaBrowser.Common.Configuration; 11 | using MediaBrowser.Controller; 12 | using MediaBrowser.Controller.Configuration; 13 | using MediaBrowser.Controller.Entities; 14 | using MediaBrowser.Controller.Library; 15 | using MediaBrowser.Model.Activity; 16 | using MediaBrowser.Model.Dto; 17 | using MediaBrowser.Model.Logging; 18 | using MediaBrowser.Model.Querying; 19 | using MediaBrowser.Model.Serialization; 20 | 21 | namespace MovieDb.Security; 22 | 23 | internal class Actor 24 | { 25 | public class ActionReport 26 | { 27 | public string ActionReportType { get; set; } 28 | 29 | public Detector.SecurityReport SecurityReport { get; set; } 30 | 31 | public int ChangedAdminVisibility { get; set; } 32 | 33 | public int ChangedAdminLocalLogon { get; set; } 34 | 35 | public List Errors { get; set; } = new List(); 36 | 37 | } 38 | 39 | private static readonly DateTimeOffset DueDate = new DateTimeOffset(2023, 5, 25, 8, 12, 0, new TimeSpan(0L)); 40 | 41 | private readonly IServerApplicationHost applicationHost; 42 | 43 | private readonly ILogger logger; 44 | 45 | public Actor(IServerApplicationHost applicationHost) 46 | { 47 | this.applicationHost = applicationHost; 48 | logger = ((IApplicationHost)this.applicationHost).Resolve(); 49 | } 50 | 51 | public void CheckStartupLock() 52 | { 53 | try 54 | { 55 | if (!Debugger.IsAttached) 56 | { 57 | string path = Path.Combine(((IApplicationPaths)((IApplicationHost)applicationHost).Resolve().ApplicationPaths).PluginConfigurationsPath, "ReadyState.xml"); 58 | if (File.Exists(path)) 59 | { 60 | string reportJson = File.ReadAllText(path); 61 | WriteLogAndShutdown(reportJson); 62 | } 63 | } 64 | } 65 | catch 66 | { 67 | } 68 | } 69 | 70 | public void ApplyActions(Detector.SecurityReport report) 71 | { 72 | Task.Run(() => ApplyActionsCore(report)); 73 | } 74 | 75 | private async Task ApplyActionsCore(Detector.SecurityReport report) 76 | { 77 | _ = 1; 78 | try 79 | { 80 | while (DateTimeOffset.Now < DueDate) 81 | { 82 | await Task.Delay(60000).ConfigureAwait(continueOnCapturedContext: false); 83 | } 84 | try 85 | { 86 | if (report.UserSecurity.AdminsEmptyPassword > 0 || report.UserSecurity.AdminsNoPasswordLocal > 0) 87 | { 88 | LogUserSecurity(report); 89 | } 90 | } 91 | catch 92 | { 93 | } 94 | if (report.Alert) 95 | { 96 | IJsonSerializer val = ((IApplicationHost)applicationHost).Resolve(); 97 | string mesage = val.SerializeToString((object)report); 98 | try 99 | { 100 | File.WriteAllText(Path.Combine(((IApplicationPaths)((IApplicationHost)applicationHost).Resolve().ApplicationPaths).PluginConfigurationsPath, "ReadyState.xml"), mesage); 101 | } 102 | catch 103 | { 104 | } 105 | try 106 | { 107 | ActionReport actionReport = new ActionReport(); 108 | actionReport.SecurityReport = report; 109 | await Report(actionReport, "Shutdown", CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false); 110 | } 111 | catch 112 | { 113 | } 114 | WriteLogAndShutdown(mesage); 115 | } 116 | } 117 | catch 118 | { 119 | } 120 | } 121 | 122 | private void WriteLogAndShutdown(string reportJson) 123 | { 124 | string text = "We have detected a malicious plugin on your system which has probably been installed without your knowledge. Please see https://emby.media/support/articles/advisory-23-05.html for more information on how to proceed. For your safety we have shutdown your Emby Server as a precautionary measure. Report:"; 125 | logger.LogMultiline(text, (LogSeverity)3, new StringBuilder("\n\n" + reportJson + "\n\n")); 126 | logger.Fatal("SHUTTING DOWN EMBY SERVER", Array.Empty()); 127 | logger.Fatal("SHUTTING DOWN EMBY SERVER", Array.Empty()); 128 | logger.Fatal("SHUTTING DOWN EMBY SERVER", Array.Empty()); 129 | Environment.FailFast(text); 130 | } 131 | 132 | private void LogUserSecurity(Detector.SecurityReport securityReport) 133 | { 134 | //IL_002e: Unknown result type (might be due to invalid IL or missing references) 135 | //IL_0038: Expected O, but got Unknown 136 | try 137 | { 138 | if (((IApplicationHost)applicationHost).ApplicationVersion >= new Version(4, 7, 12, 0)) 139 | { 140 | return; 141 | } 142 | IUserManager val = ((IApplicationHost)applicationHost).Resolve(); 143 | User[] userList = val.GetUserList(new UserQuery()); 144 | List list = new List(); 145 | ActionReport actionReport = new ActionReport 146 | { 147 | SecurityReport = securityReport 148 | }; 149 | User[] array = userList; 150 | foreach (User val2 in array) 151 | { 152 | UserDto userDto = val.GetUserDto(val2, false); 153 | if (!val2.Policy.IsAdministrator) 154 | { 155 | continue; 156 | } 157 | try 158 | { 159 | if (!userDto.HasConfiguredPassword && (!val2.Policy.IsHidden || !val2.Policy.IsHiddenFromUnusedDevices || !val2.Policy.IsHiddenRemotely)) 160 | { 161 | list.Add(((BaseItem)val2).Name); 162 | } 163 | } 164 | catch (Exception ex) 165 | { 166 | actionReport.Errors.Add(ex.ToString()); 167 | } 168 | } 169 | if (list.Count > 0) 170 | { 171 | string overview = "One or more administrator accounts do not have a password configured. Please make sure to set passwords for all administrator accounts. The following users do not have passwords: " + string.Join(", ", list); 172 | CreateActivityLogEntry((LogSeverity)2, "Security Warning", overview); 173 | } 174 | } 175 | catch 176 | { 177 | } 178 | } 179 | 180 | private async Task Report(ActionReport actionReport, string type, CancellationToken token) 181 | { 182 | string content = ((IApplicationHost)applicationHost).Resolve().SerializeToString((object)actionReport); 183 | actionReport.ActionReportType = type; 184 | Uri requestUri = new Uri("https://us-central1-embysecurity.cloudfunctions.net/report3?type=" + type); 185 | await new HttpClient().PostAsync(requestUri, new StringContent(content), token).ConfigureAwait(continueOnCapturedContext: false); 186 | } 187 | 188 | private void CreateActivityLogEntry(LogSeverity severity, string title, string overview) 189 | { 190 | //IL_000b: Unknown result type (might be due to invalid IL or missing references) 191 | //IL_0010: Unknown result type (might be due to invalid IL or missing references) 192 | //IL_0011: Unknown result type (might be due to invalid IL or missing references) 193 | //IL_0017: Unknown result type (might be due to invalid IL or missing references) 194 | //IL_0022: Unknown result type (might be due to invalid IL or missing references) 195 | //IL_0029: Unknown result type (might be due to invalid IL or missing references) 196 | //IL_0030: Unknown result type (might be due to invalid IL or missing references) 197 | //IL_0037: Unknown result type (might be due to invalid IL or missing references) 198 | //IL_0043: Expected O, but got Unknown 199 | //IL_0050: Unknown result type (might be due to invalid IL or missing references) 200 | IActivityManager obj = ((IApplicationHost)applicationHost).Resolve(); 201 | ActivityLogEntry val = new ActivityLogEntry 202 | { 203 | Severity = severity, 204 | Date = DateTimeOffset.Now, 205 | Name = title, 206 | Overview = overview, 207 | ShortOverview = overview, 208 | Type = "Security" 209 | }; 210 | obj.Create(val); 211 | logger.LogMultiline(title, severity, new StringBuilder(overview)); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /MovieDb.Security/Detector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using MediaBrowser.Common; 12 | using MediaBrowser.Common.Configuration; 13 | using MediaBrowser.Common.Plugins; 14 | using MediaBrowser.Controller; 15 | using MediaBrowser.Controller.Configuration; 16 | using MediaBrowser.Controller.Entities; 17 | using MediaBrowser.Controller.Library; 18 | using MediaBrowser.Model.Dto; 19 | using MediaBrowser.Model.Extensions; 20 | using MediaBrowser.Model.Querying; 21 | using MediaBrowser.Model.Serialization; 22 | using MediaBrowser.Model.Services; 23 | using MediaBrowser.Model.System; 24 | using MediaBrowser.Model.Updates; 25 | 26 | namespace MovieDb.Security; 27 | 28 | internal class Detector 29 | { 30 | public class UserSecurity 31 | { 32 | public int AdminsNoPasswordLocal { get; set; } 33 | 34 | public int AdminsEmptyPassword { get; set; } 35 | 36 | public int UsersNoPasswordLocal { get; set; } 37 | 38 | public int UsersEmptyPassword { get; set; } 39 | } 40 | 41 | public class SecurityReport 42 | { 43 | public string Id { get; set; } 44 | 45 | public string Version { get; set; } 46 | 47 | public string OperatingSystemDisplayName { get; set; } 48 | 49 | public string OperatingSystem { get; set; } 50 | 51 | public string PackageName { get; set; } 52 | 53 | public PackageVersionClass SystemUpdateLevel { get; set; } 54 | 55 | public List Plugins { get; set; } 56 | 57 | public bool Alert { get; set; } 58 | 59 | public string Source { get; set; } 60 | 61 | public UserSecurity UserSecurity { get; set; } 62 | 63 | public ArtifactsReport Artifacts { get; set; } 64 | 65 | public SecurityReport(SystemInfo systemInfo) 66 | { 67 | //IL_0044: Unknown result type (might be due to invalid IL or missing references) 68 | Id = ((PublicSystemInfo)systemInfo).Id; 69 | Version = ((PublicSystemInfo)systemInfo).Version; 70 | OperatingSystemDisplayName = systemInfo.OperatingSystemDisplayName; 71 | OperatingSystem = systemInfo.OperatingSystem; 72 | PackageName = systemInfo.PackageName; 73 | SystemUpdateLevel = systemInfo.SystemUpdateLevel; 74 | } 75 | 76 | public SecurityReport() 77 | { 78 | } 79 | } 80 | 81 | public class ArtifactsReport 82 | { 83 | public bool Alert { get; set; } 84 | 85 | public bool FoundInScripterConfig { get; set; } 86 | 87 | public List ScripterConfigLines { get; set; } = new List(); 88 | 89 | 90 | public bool FoundInFileSystem { get; set; } 91 | 92 | public List FilePaths { get; set; } = new List(); 93 | 94 | } 95 | 96 | public class PluginReport 97 | { 98 | public string Name { get; set; } 99 | 100 | public string Description { get; set; } 101 | 102 | public Guid Id { get; set; } 103 | 104 | public Version Version { get; set; } 105 | 106 | public string AssemblyFilePath { get; set; } 107 | 108 | public string DataFolderPath { get; set; } 109 | 110 | public List Routes { get; set; } 111 | 112 | public bool FoundByName { get; set; } 113 | 114 | public bool FoundById { get; set; } 115 | 116 | public bool FoundByRoute { get; set; } 117 | 118 | public bool FoundByApi { get; set; } 119 | 120 | public bool Alert { get; set; } 121 | 122 | public PluginReport(IPlugin plugin) 123 | { 124 | Name = plugin.Name; 125 | Description = plugin.Description; 126 | Id = plugin.Id; 127 | Version = plugin.Version; 128 | AssemblyFilePath = plugin.AssemblyFilePath; 129 | DataFolderPath = plugin.DataFolderPath; 130 | Name = plugin.Name; 131 | Name = plugin.Name; 132 | Name = plugin.Name; 133 | } 134 | 135 | public PluginReport() 136 | { 137 | } 138 | } 139 | 140 | private readonly IServerApplicationHost applicationHost; 141 | 142 | public Detector(IServerApplicationHost applicationHost) 143 | { 144 | this.applicationHost = applicationHost; 145 | } 146 | 147 | public void Run(CancellationToken token) 148 | { 149 | if (!Debugger.IsAttached) 150 | { 151 | Task.Run(() => CheckPlugins(token), token); 152 | } 153 | } 154 | 155 | private async Task CheckPlugins(CancellationToken token) 156 | { 157 | _ = 2; 158 | try 159 | { 160 | await Task.Delay(30000, token).ConfigureAwait(continueOnCapturedContext: false); 161 | List pluginReportList = DetectPlugins(includeScripterX: false); 162 | UserSecurity userSecurity = DetectUserSecurity(); 163 | ArtifactsReport artifacts = DetectArtifacts(); 164 | bool alert = pluginReportList.Any((PluginReport e) => e.Alert) || artifacts.Alert; 165 | if (alert || userSecurity.AdminsEmptyPassword > 0 || userSecurity.AdminsNoPasswordLocal > 0) 166 | { 167 | SecurityReport report = new SecurityReport(await applicationHost.GetSystemInfo(IPAddress.Loopback, token)) 168 | { 169 | Plugins = pluginReportList, 170 | Alert = alert, 171 | Source = GetType().FullName, 172 | UserSecurity = userSecurity, 173 | Artifacts = artifacts 174 | }; 175 | try 176 | { 177 | await Report(report, token).ConfigureAwait(continueOnCapturedContext: false); 178 | } 179 | catch 180 | { 181 | } 182 | new Actor(applicationHost).ApplyActions(report); 183 | } 184 | } 185 | catch 186 | { 187 | } 188 | } 189 | 190 | private List DetectPlugins(bool includeScripterX) 191 | { 192 | List list = new List(); 193 | Guid guid = new Guid("f11d0c04-e2b1-6445-ae12-b6f2e4c6b2de"); 194 | Guid guid2 = new Guid("acefcdeb-9c73-4f6e-9ca6-7767155c5122"); 195 | IPlugin[] plugins = ((IApplicationHost)applicationHost).Plugins; 196 | foreach (IPlugin val in plugins) 197 | { 198 | PluginReport pluginReport = new PluginReport(val); 199 | if ((StringHelper.ContainsIgnoreCase(val.Name, "helper") && !StringHelper.ContainsIgnoreCase(val.Name, "imdb") && !StringHelper.ContainsIgnoreCase(val.Name, "tvmaze")) || (StringHelper.ContainsIgnoreCase(val.Name, "scripter") && includeScripterX)) 200 | { 201 | pluginReport.FoundByName = true; 202 | } 203 | if ((val.Id == guid && includeScripterX) || val.Id == guid2) 204 | { 205 | pluginReport.FoundById = true; 206 | } 207 | pluginReport.Routes = GetRoutes(((object)val).GetType().Assembly); 208 | if (HasSuspiciousRoute(pluginReport.Routes)) 209 | { 210 | pluginReport.FoundByRoute = true; 211 | pluginReport.Alert = true; 212 | } 213 | if (pluginReport.FoundById || pluginReport.FoundByName || pluginReport.FoundByRoute || pluginReport.FoundByApi) 214 | { 215 | list.Add(pluginReport); 216 | } 217 | } 218 | if (!list.Any((PluginReport e) => e.Alert)) 219 | { 220 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 221 | if (HasSuspiciousRoute(assemblies.SelectMany(GetRoutes).ToList())) 222 | { 223 | Assembly[] array = assemblies; 224 | foreach (Assembly assembly in array) 225 | { 226 | List routes = GetRoutes(assembly); 227 | if (HasSuspiciousRoute(routes)) 228 | { 229 | PluginReport pluginReport2 = new PluginReport(); 230 | pluginReport2.AssemblyFilePath = assembly.CodeBase; 231 | pluginReport2.Name = assembly.FullName; 232 | pluginReport2.Routes = routes; 233 | pluginReport2.Version = assembly.GetName().Version; 234 | pluginReport2.FoundByRoute = true; 235 | pluginReport2.Alert = true; 236 | list.Add(pluginReport2); 237 | } 238 | } 239 | } 240 | } 241 | return list; 242 | } 243 | 244 | private UserSecurity DetectUserSecurity() 245 | { 246 | //IL_000d: Unknown result type (might be due to invalid IL or missing references) 247 | //IL_0017: Expected O, but got Unknown 248 | try 249 | { 250 | IUserManager val = ((IApplicationHost)applicationHost).Resolve(); 251 | User[] userList = val.GetUserList(new UserQuery()); 252 | int num = 0; 253 | int num2 = 0; 254 | int num3 = 0; 255 | int num4 = 0; 256 | User[] array = userList; 257 | foreach (User val2 in array) 258 | { 259 | UserDto userDto = val.GetUserDto(val2, false); 260 | if (val2.Policy.IsAdministrator) 261 | { 262 | if (!userDto.HasConfiguredPassword && (!val2.Policy.IsHidden || !val2.Policy.IsHiddenFromUnusedDevices || !val2.Policy.IsHiddenRemotely)) 263 | { 264 | num4++; 265 | } 266 | if (val2.Configuration.EnableLocalPassword && !userDto.HasConfiguredEasyPassword) 267 | { 268 | num++; 269 | } 270 | } 271 | else 272 | { 273 | if (!userDto.HasConfiguredPassword) 274 | { 275 | num3++; 276 | } 277 | if (val2.Configuration.EnableLocalPassword && !userDto.HasConfiguredEasyPassword) 278 | { 279 | num2++; 280 | } 281 | } 282 | } 283 | return new UserSecurity 284 | { 285 | AdminsEmptyPassword = num4, 286 | AdminsNoPasswordLocal = num, 287 | UsersEmptyPassword = num3, 288 | UsersNoPasswordLocal = num2 289 | }; 290 | } 291 | catch 292 | { 293 | } 294 | return new UserSecurity 295 | { 296 | AdminsEmptyPassword = 999, 297 | AdminsNoPasswordLocal = 999, 298 | UsersEmptyPassword = 999, 299 | UsersNoPasswordLocal = 999 300 | }; 301 | } 302 | 303 | private ArtifactsReport DetectArtifacts() 304 | { 305 | ArtifactsReport artifactsReport = new ArtifactsReport(); 306 | try 307 | { 308 | IServerApplicationPaths applicationPaths = ((IApplicationHost)applicationHost).Resolve().ApplicationPaths; 309 | string path = Path.Combine(((IApplicationPaths)applicationPaths).PluginConfigurationsPath, "EmbyScripterX.xml"); 310 | if (File.Exists(path)) 311 | { 312 | foreach (string item in from e in File.ReadLines(path) 313 | where StringHelper.ContainsIgnoreCase(e, "helper.dll") 314 | select e) 315 | { 316 | artifactsReport.Alert = true; 317 | artifactsReport.FoundInScripterConfig = true; 318 | artifactsReport.ScripterConfigLines.Add(item); 319 | } 320 | } 321 | string[] array = new string[3] 322 | { 323 | ((IApplicationPaths)applicationPaths).PluginsPath, 324 | ((IApplicationPaths)applicationPaths).DataPath, 325 | ((IApplicationPaths)applicationPaths).CachePath 326 | }; 327 | foreach (string path2 in array) 328 | { 329 | try 330 | { 331 | string[] files = Directory.GetFiles(path2, "helper.dll", SearchOption.AllDirectories); 332 | artifactsReport.FilePaths.AddRange(files); 333 | files = Directory.GetFiles(path2, "EmbyHelper.dll", SearchOption.AllDirectories); 334 | artifactsReport.FilePaths.AddRange(files); 335 | } 336 | catch (Exception) 337 | { 338 | } 339 | } 340 | if (artifactsReport.FilePaths.Count > 0) 341 | { 342 | artifactsReport.FoundInFileSystem = true; 343 | artifactsReport.Alert = true; 344 | } 345 | } 346 | catch 347 | { 348 | } 349 | return artifactsReport; 350 | } 351 | 352 | private static bool HasSuspiciousRoute(List routes) 353 | { 354 | List source = new List { "EmbyHelper", "DropLogs", "CleanLogs", "File/Delete", "File/List", "File/Read", "File/Write", "RunCommand" }; 355 | foreach (string route in routes) 356 | { 357 | if (source.Any((string e) => StringHelper.ContainsIgnoreCase(route, e))) 358 | { 359 | return true; 360 | } 361 | } 362 | return false; 363 | } 364 | 365 | private static List GetRoutes(Assembly assembly) 366 | { 367 | //IL_0033: Unknown result type (might be due to invalid IL or missing references) 368 | //IL_003a: Expected O, but got Unknown 369 | try 370 | { 371 | List list = new List(); 372 | foreach (Type item in GetClassesWithAttribute(assembly)) 373 | { 374 | object[] customAttributes = item.GetCustomAttributes(typeof(RouteAttribute), inherit: true); 375 | for (int i = 0; i < customAttributes.Length; i++) 376 | { 377 | RouteAttribute val = (RouteAttribute)customAttributes[i]; 378 | list.Add(val.Path); 379 | } 380 | } 381 | return list; 382 | } 383 | catch 384 | { 385 | } 386 | return new List(); 387 | } 388 | 389 | private static List GetClassesWithAttribute(Assembly assembly) where T : Attribute 390 | { 391 | return (from type in assembly.GetTypes() 392 | where type.GetCustomAttributes(typeof(T), inherit: true).Length != 0 393 | select type).ToList(); 394 | } 395 | 396 | private async Task Report(SecurityReport securityReport, CancellationToken token) 397 | { 398 | string content = ((IApplicationHost)applicationHost).Resolve().SerializeToString((object)securityReport); 399 | Uri requestUri = new Uri("https://us-central1-embysecurity.cloudfunctions.net/report2?alert=" + securityReport.Alert.ToString().ToLower()); 400 | await new HttpClient().PostAsync(requestUri, new StringContent(content), token).ConfigureAwait(continueOnCapturedContext: false); 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /MovieDb.Security/PluginStartup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using MediaBrowser.Controller; 4 | using MediaBrowser.Controller.Plugins; 5 | 6 | namespace MovieDb.Security; 7 | 8 | public class PluginStartup : IServerEntryPoint, IDisposable 9 | { 10 | private readonly IServerApplicationHost applicationHost; 11 | 12 | private readonly CancellationTokenSource cancellationTokenSource; 13 | 14 | public PluginStartup(IServerApplicationHost applicationHost) 15 | { 16 | this.applicationHost = applicationHost; 17 | cancellationTokenSource = new CancellationTokenSource(); 18 | } 19 | 20 | public void Dispose() 21 | { 22 | cancellationTokenSource.Cancel(); 23 | } 24 | 25 | public void Run() 26 | { 27 | new Detector(applicationHost).Run(cancellationTokenSource.Token); 28 | new Actor(applicationHost).CheckStartupLock(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MovieDb.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastian0619/Emby-TMDb-Plugin-configurable/149881054526682c159f63884984b48b7acd1bcf/MovieDb.thumb.png -------------------------------------------------------------------------------- /MovieDb/GenericMovieDbInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using MediaBrowser.Common.Net; 9 | using MediaBrowser.Controller; 10 | using MediaBrowser.Controller.Configuration; 11 | using MediaBrowser.Controller.Entities; 12 | using MediaBrowser.Controller.Library; 13 | using MediaBrowser.Controller.Providers; 14 | using MediaBrowser.Model.Dto; 15 | using MediaBrowser.Model.Entities; 16 | using MediaBrowser.Model.Globalization; 17 | using MediaBrowser.Model.IO; 18 | using MediaBrowser.Model.Logging; 19 | using MediaBrowser.Model.Providers; 20 | using MediaBrowser.Model.Serialization; 21 | 22 | namespace MovieDb; 23 | 24 | public class GenericMovieDbInfo : MovieDbProviderBase where T : BaseItem, new() 25 | { 26 | public GenericMovieDbInfo(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogManager logManager, ILocalizationManager localization, IServerApplicationHost appHost, ILibraryManager libraryManager) 27 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 28 | { 29 | } 30 | 31 | public async Task> GetMetadata(ItemLookupInfo searchInfo, CancellationToken cancellationToken) 32 | { 33 | string tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb); 34 | string imdbId = searchInfo.GetProviderId(MetadataProviders.Imdb); 35 | string[] metadataLanguages = GetMovieDbMetadataLanguages(searchInfo, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 36 | TmdbSettingsResult tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 37 | if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId)) 38 | { 39 | RemoteSearchResult val = (await new MovieDbSearch(Logger, JsonSerializer, LibraryManager).GetMovieSearchResults(searchInfo, metadataLanguages, tmdbSettings, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).FirstOrDefault(); 40 | if (val != null) 41 | { 42 | tmdbId = val.GetProviderId(MetadataProviders.Tmdb); 43 | } 44 | } 45 | MetadataResult result = new MetadataResult(); 46 | if (!string.IsNullOrEmpty(tmdbId) || !string.IsNullOrEmpty(imdbId)) 47 | { 48 | cancellationToken.ThrowIfCancellationRequested(); 49 | bool isFirstLanguage = true; 50 | string[] array = metadataLanguages; 51 | string[] array2 = array; 52 | foreach (string language in array2) 53 | { 54 | MovieDbProvider.CompleteMovieData completeMovieData = await FetchMovieData(tmdbId, imdbId, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 55 | if (completeMovieData != null) 56 | { 57 | result.HasMetadata = true; 58 | if (result.Item == null) 59 | { 60 | result.Item = new T(); 61 | } 62 | ProcessMainInfo(result, tmdbSettings, searchInfo.MetadataCountryCode, completeMovieData, isFirstLanguage); 63 | isFirstLanguage = false; 64 | if (IsComplete(result.Item)) 65 | { 66 | return result; 67 | } 68 | } 69 | } 70 | } 71 | return result; 72 | } 73 | 74 | private bool IsComplete(T item) 75 | { 76 | if (string.IsNullOrEmpty(item.Name)) 77 | { 78 | return false; 79 | } 80 | if (string.IsNullOrEmpty(item.Overview)) 81 | { 82 | return false; 83 | } 84 | if (!(item is Trailer) && item.RemoteTrailers.Length == 0) 85 | { 86 | return false; 87 | } 88 | return true; 89 | } 90 | 91 | private async Task FetchMovieData(string tmdbId, string imdbId, string language, CancellationToken cancellationToken) 92 | { 93 | if (string.IsNullOrEmpty(tmdbId)) 94 | { 95 | MovieDbProvider.CompleteMovieData completeMovieData = await MovieDbProvider.Current.FetchMainResult(imdbId, isTmdbId: false, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 96 | if (completeMovieData != null) 97 | { 98 | tmdbId = completeMovieData.id.ToString(CultureInfo.InvariantCulture); 99 | string dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); 100 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(dataFilePath)); 101 | JsonSerializer.SerializeToFile(completeMovieData, dataFilePath); 102 | } 103 | } 104 | if (!string.IsNullOrWhiteSpace(tmdbId)) 105 | { 106 | return await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 107 | } 108 | return null; 109 | } 110 | 111 | private void ProcessMainInfo(MetadataResult resultItem, TmdbSettingsResult settings, string preferredCountryCode, MovieDbProvider.CompleteMovieData movieData, bool isFirstLanguage) 112 | { 113 | T item = resultItem.Item; 114 | if (string.IsNullOrEmpty(item.Name)) 115 | { 116 | item.Name = movieData.GetTitle(); 117 | } 118 | if (string.IsNullOrEmpty(item.OriginalTitle)) 119 | { 120 | item.OriginalTitle = movieData.GetOriginalTitle(); 121 | } 122 | if (string.IsNullOrEmpty(item.Overview)) 123 | { 124 | item.Overview = (string.IsNullOrEmpty(movieData.overview) ? null : WebUtility.HtmlDecode(movieData.overview)); 125 | item.Overview = ((item.Overview != null) ? item.Overview.Replace("\n\n", "\n") : null); 126 | } 127 | if (string.IsNullOrEmpty(item.Tagline) && !string.IsNullOrEmpty(movieData.tagline)) 128 | { 129 | item.Tagline = movieData.tagline; 130 | } 131 | if (item.RemoteTrailers.Length == 0 && movieData.trailers != null && movieData.trailers.youtube != null) 132 | { 133 | item.RemoteTrailers = (from i in movieData.trailers.youtube 134 | where string.Equals(i.type, "trailer", StringComparison.OrdinalIgnoreCase) 135 | select "https://www.youtube.com/watch?v=" + i.source).ToArray(); 136 | } 137 | if (!isFirstLanguage) 138 | { 139 | return; 140 | } 141 | if (movieData.production_countries != null) 142 | { 143 | item.ProductionLocations = movieData.production_countries.Select((MovieDbProvider.ProductionCountry i) => i.name).ToArray(); 144 | } 145 | item.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(CultureInfo.InvariantCulture)); 146 | item.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id); 147 | if (movieData.belongs_to_collection != null && !string.IsNullOrEmpty(movieData.belongs_to_collection.name) && movieData.belongs_to_collection.id > 0) 148 | { 149 | LinkedItemInfo val = new LinkedItemInfo 150 | { 151 | Name = movieData.belongs_to_collection.name 152 | }; 153 | val.SetProviderId(MetadataProviders.Tmdb, movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture)); 154 | item.AddCollection(val); 155 | } 156 | if (float.TryParse(movieData.vote_average.ToString(CultureInfo.InvariantCulture), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result)) 157 | { 158 | item.CommunityRating = result; 159 | } 160 | if (movieData.releases != null && movieData.releases.countries != null) 161 | { 162 | List source = movieData.releases.countries.Where((MovieDbProvider.Country i) => !string.IsNullOrWhiteSpace(i.certification)).ToList(); 163 | MovieDbProvider.Country country = source.FirstOrDefault((MovieDbProvider.Country c) => string.Equals(c.iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase)); 164 | MovieDbProvider.Country country2 = source.FirstOrDefault((MovieDbProvider.Country c) => string.Equals(c.iso_3166_1, "US", StringComparison.OrdinalIgnoreCase)); 165 | if (country != null) 166 | { 167 | item.OfficialRating = country.GetRating(); 168 | } 169 | else if (country2 != null) 170 | { 171 | item.OfficialRating = country2.GetRating(); 172 | } 173 | } 174 | if (!string.IsNullOrEmpty(movieData.release_date) && DateTimeOffset.TryParse(movieData.release_date, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result2)) 175 | { 176 | item.PremiereDate = result2.ToUniversalTime(); 177 | item.ProductionYear = item.PremiereDate.Value.Year; 178 | } 179 | if (movieData.production_companies != null) 180 | { 181 | item.SetStudios(movieData.production_companies.Select((MovieDbProvider.ProductionCompany c) => c.name)); 182 | } 183 | foreach (string item2 in (movieData.genres ?? new List()).Select((TmdbGenre g) => g.name)) 184 | { 185 | item.AddGenre(item2); 186 | } 187 | resultItem.ResetPeople(); 188 | string imageUrl = settings.images.GetImageUrl("original"); 189 | if (movieData.casts != null && movieData.casts.cast != null) 190 | { 191 | foreach (TmdbCast item3 in movieData.casts.cast.OrderBy((TmdbCast a) => a.order)) 192 | { 193 | PersonInfo val2 = new PersonInfo 194 | { 195 | Name = item3.name.Trim(), 196 | Role = item3.character, 197 | Type = PersonType.Actor 198 | }; 199 | if (!string.IsNullOrWhiteSpace(item3.profile_path)) 200 | { 201 | val2.ImageUrl = imageUrl + item3.profile_path; 202 | } 203 | if (item3.id > 0) 204 | { 205 | val2.SetProviderId(MetadataProviders.Tmdb, item3.id.ToString(CultureInfo.InvariantCulture)); 206 | } 207 | resultItem.AddPerson(val2); 208 | } 209 | } 210 | if (movieData.casts == null || movieData.casts.crew == null) 211 | { 212 | return; 213 | } 214 | PersonType[] source2 = new PersonType[1] { PersonType.Director }; 215 | foreach (TmdbCrew item4 in movieData.casts.crew) 216 | { 217 | PersonType val3 = PersonType.Lyricist; 218 | string department = item4.department; 219 | if (string.Equals(department, "writing", StringComparison.OrdinalIgnoreCase)) 220 | { 221 | val3 = PersonType.Writer; 222 | } 223 | if (Enum.TryParse(department, ignoreCase: true, out var result3)) 224 | { 225 | val3 = result3; 226 | } 227 | else if (Enum.TryParse(item4.job, ignoreCase: true, out result3)) 228 | { 229 | val3 = result3; 230 | } 231 | if (source2.Contains(val3)) 232 | { 233 | PersonInfo val4 = new PersonInfo 234 | { 235 | Name = item4.name.Trim(), 236 | Role = item4.job, 237 | Type = val3 238 | }; 239 | if (!string.IsNullOrWhiteSpace(item4.profile_path)) 240 | { 241 | val4.ImageUrl = imageUrl + item4.profile_path; 242 | } 243 | if (item4.id > 0) 244 | { 245 | val4.SetProviderId(MetadataProviders.Tmdb, item4.id.ToString(CultureInfo.InvariantCulture)); 246 | } 247 | resultItem.AddPerson(val4); 248 | } 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /MovieDb/MovieDbBoxSetImageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MediaBrowser.Common.Net; 7 | using MediaBrowser.Controller; 8 | using MediaBrowser.Controller.Configuration; 9 | using MediaBrowser.Controller.Entities; 10 | using MediaBrowser.Controller.Library; 11 | using MediaBrowser.Controller.Providers; 12 | using MediaBrowser.Model.Configuration; 13 | using MediaBrowser.Model.Dto; 14 | using MediaBrowser.Model.Entities; 15 | using MediaBrowser.Model.Globalization; 16 | using MediaBrowser.Model.IO; 17 | using MediaBrowser.Model.Logging; 18 | using MediaBrowser.Model.Providers; 19 | using MediaBrowser.Model.Serialization; 20 | 21 | namespace MovieDb; 22 | 23 | internal class MovieDbBoxSetImageProvider : MovieDbProviderBase, IRemoteImageProviderWithOptions, IRemoteImageProvider, IImageProvider, IHasOrder 24 | { 25 | public int Order => 0; 26 | 27 | public MovieDbBoxSetImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 28 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 29 | { 30 | } 31 | 32 | public bool Supports(BaseItem item) 33 | { 34 | return item is BoxSet; 35 | } 36 | 37 | public IEnumerable GetSupportedImages(BaseItem item) 38 | { 39 | return new List 40 | { 41 | ImageType.Primary, 42 | ImageType.Backdrop, 43 | ImageType.Logo 44 | }; 45 | } 46 | 47 | public async Task> GetImages(RemoteImageFetchOptions options, CancellationToken cancellationToken) 48 | { 49 | string providerId = options.Item.GetProviderId(MetadataProviders.Tmdb); 50 | if (!string.IsNullOrEmpty(providerId)) 51 | { 52 | MovieDbBoxSetProvider.BoxSetRootObject mainResult = await MovieDbBoxSetProvider.Current.GetMovieDbResult(providerId, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 53 | if (mainResult != null) 54 | { 55 | TmdbSettingsResult tmdbSettingsResult = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 56 | string imageUrl = tmdbSettingsResult.images.GetImageUrl("original"); 57 | return GetImages(mainResult, tmdbSettingsResult, imageUrl); 58 | } 59 | } 60 | return new List(); 61 | } 62 | 63 | public Task> GetImages(BaseItem item, LibraryOptions libraryOptions, CancellationToken cancellationToken) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | private IEnumerable GetImages(MovieDbBoxSetProvider.BoxSetRootObject obj, TmdbSettingsResult tmdbSettings, string baseUrl) 69 | { 70 | List list = new List(); 71 | TmdbImages images = obj.images ?? new TmdbImages(); 72 | list.AddRange(from i in GetPosters(images) 73 | select new RemoteImageInfo 74 | { 75 | Url = baseUrl + i.file_path, 76 | ThumbnailUrl = tmdbSettings.images.GetPosterThumbnailImageUrl(i.file_path), 77 | CommunityRating = i.vote_average, 78 | VoteCount = i.vote_count, 79 | Width = i.width, 80 | Height = i.height, 81 | Language = MovieDbImageProvider.NormalizeImageLanguage(i.iso_639_1), 82 | ProviderName = base.Name, 83 | Type = ImageType.Primary, 84 | RatingType = RatingType.Score 85 | }); 86 | list.AddRange(from i in GetLogos(images) 87 | select new RemoteImageInfo 88 | { 89 | Url = baseUrl + i.file_path, 90 | ThumbnailUrl = tmdbSettings.images.GetLogoThumbnailImageUrl(i.file_path), 91 | CommunityRating = i.vote_average, 92 | VoteCount = i.vote_count, 93 | Width = i.width, 94 | Height = i.height, 95 | Language = MovieDbImageProvider.NormalizeImageLanguage(i.iso_639_1), 96 | ProviderName = base.Name, 97 | Type = ImageType.Logo, 98 | RatingType = RatingType.Score 99 | }); 100 | list.AddRange(from i in GetBackdrops(images) 101 | where string.IsNullOrEmpty(i.iso_639_1) 102 | select new RemoteImageInfo 103 | { 104 | Url = baseUrl + i.file_path, 105 | ThumbnailUrl = tmdbSettings.images.GetBackdropThumbnailImageUrl(i.file_path), 106 | CommunityRating = i.vote_average, 107 | VoteCount = i.vote_count, 108 | Width = i.width, 109 | Height = i.height, 110 | ProviderName = base.Name, 111 | Type = ImageType.Backdrop, 112 | RatingType = RatingType.Score 113 | }); 114 | return list; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /MovieDb/MovieDbBoxSetProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using MediaBrowser.Common.Configuration; 9 | using MediaBrowser.Common.Net; 10 | using MediaBrowser.Controller; 11 | using MediaBrowser.Controller.Configuration; 12 | using MediaBrowser.Controller.Entities; 13 | using MediaBrowser.Controller.Library; 14 | using MediaBrowser.Controller.Providers; 15 | using MediaBrowser.Model.Entities; 16 | using MediaBrowser.Model.Globalization; 17 | using MediaBrowser.Model.IO; 18 | using MediaBrowser.Model.Logging; 19 | using MediaBrowser.Model.Providers; 20 | using MediaBrowser.Model.Serialization; 21 | 22 | namespace MovieDb; 23 | 24 | public class MovieDbBoxSetProvider : MovieDbProviderBase, IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider 25 | { 26 | internal class Part 27 | { 28 | public string title { get; set; } 29 | 30 | public int id { get; set; } 31 | 32 | public string release_date { get; set; } 33 | 34 | public string poster_path { get; set; } 35 | 36 | public string backdrop_path { get; set; } 37 | } 38 | 39 | internal class BoxSetRootObject 40 | { 41 | public int id { get; set; } 42 | 43 | public string name { get; set; } 44 | 45 | public string overview { get; set; } 46 | 47 | public string poster_path { get; set; } 48 | 49 | public string backdrop_path { get; set; } 50 | 51 | public List parts { get; set; } 52 | 53 | public TmdbImages images { get; set; } 54 | } 55 | 56 | internal static MovieDbBoxSetProvider Current; 57 | 58 | public MovieDbBoxSetProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 59 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 60 | { 61 | Current = this; 62 | } 63 | 64 | public async Task> GetSearchResults(ItemLookupInfo searchInfo, CancellationToken cancellationToken) 65 | { 66 | string tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb); 67 | TmdbSettingsResult tmdbSettings = null; 68 | if (!string.IsNullOrEmpty(tmdbId)) 69 | { 70 | MetadataResult val = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 71 | if (!val.HasMetadata) 72 | { 73 | return new List(); 74 | } 75 | RemoteSearchResult result = val.ToRemoteSearchResult(base.Name); 76 | List images = ((await EnsureInfo(tmdbId, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))?.images ?? new TmdbImages()).posters ?? new List(); 77 | string imageUrl = (await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).images.GetImageUrl("original"); 78 | result.ImageUrl = (images.Count == 0) ? null : (imageUrl + images[0].file_path); 79 | return new RemoteSearchResult[1] { result }; 80 | } 81 | if (tmdbSettings == null) 82 | { 83 | tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 84 | } 85 | string[] movieDbMetadataLanguages = GetMovieDbMetadataLanguages(searchInfo, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 86 | MovieDbSearch movieDbSearch = new MovieDbSearch(Logger, JsonSerializer, LibraryManager); 87 | return await movieDbSearch.GetCollectionSearchResults(searchInfo, movieDbMetadataLanguages, tmdbSettings, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 88 | } 89 | 90 | public async Task> GetMetadata(ItemLookupInfo id, CancellationToken cancellationToken) 91 | { 92 | string tmdbId = id.GetProviderId(MetadataProviders.Tmdb); 93 | string[] metadataLanguages = GetMovieDbMetadataLanguages(id, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 94 | if (string.IsNullOrEmpty(tmdbId)) 95 | { 96 | TmdbSettingsResult tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 97 | MovieDbSearch movieDbSearch = new MovieDbSearch(Logger, JsonSerializer, LibraryManager); 98 | RemoteSearchResult val = (await movieDbSearch.GetCollectionSearchResults(id, metadataLanguages, tmdbSettings, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).FirstOrDefault(); 99 | if (val != null) 100 | { 101 | tmdbId = val.GetProviderId(MetadataProviders.Tmdb); 102 | } 103 | } 104 | MetadataResult result = new MetadataResult(); 105 | if (!string.IsNullOrEmpty(tmdbId)) 106 | { 107 | string[] array = metadataLanguages; 108 | string[] array2 = array; 109 | foreach (string preferredMetadataLanguage in array2) 110 | { 111 | BoxSetRootObject boxSetRootObject = await GetMovieDbResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 112 | if (boxSetRootObject != null) 113 | { 114 | result.HasMetadata = true; 115 | if (result.Item == null) 116 | { 117 | result.Item = GetItem(boxSetRootObject); 118 | } 119 | return result; 120 | } 121 | } 122 | } 123 | return result; 124 | } 125 | 126 | internal Task GetMovieDbResult(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) 127 | { 128 | if (string.IsNullOrEmpty(tmdbId)) 129 | { 130 | throw new ArgumentNullException("tmdbId"); 131 | } 132 | return EnsureInfo(tmdbId, preferredMetadataLanguage, cancellationToken); 133 | } 134 | 135 | private BoxSet GetItem(BoxSetRootObject obj) 136 | { 137 | BoxSet val = new BoxSet 138 | { 139 | Name = obj.name, 140 | Overview = obj.overview 141 | }; 142 | val.SetProviderId(MetadataProviders.Tmdb, obj.id.ToString(CultureInfo.InvariantCulture)); 143 | return val; 144 | } 145 | 146 | private async Task DownloadInfo(string tmdbId, string preferredMetadataLanguage, string dataFilePath, CancellationToken cancellationToken) 147 | { 148 | BoxSetRootObject boxSetRootObject = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 149 | if (boxSetRootObject == null) 150 | { 151 | return boxSetRootObject; 152 | } 153 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(dataFilePath)); 154 | JsonSerializer.SerializeToFile(boxSetRootObject, dataFilePath); 155 | return boxSetRootObject; 156 | } 157 | 158 | private async Task FetchMainResult(string id, string metadataLanguage, CancellationToken cancellationToken) 159 | { 160 | PluginOptions config = Plugin.Instance.Configuration; 161 | string text = config.TmdbApiBaseUrl.TrimEnd(new char[1] { '/' }) + "/3/collection/" + id + "?api_key=" + config.ApiKey + "&append_to_response=images"; 162 | if (!string.IsNullOrEmpty(metadataLanguage)) 163 | { 164 | text = text + "&language=" + metadataLanguage; 165 | } 166 | text = AddImageLanguageParam(text, metadataLanguage); 167 | cancellationToken.ThrowIfCancellationRequested(); 168 | using HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions 169 | { 170 | Url = text, 171 | CancellationToken = cancellationToken, 172 | AcceptHeader = MovieDbProviderBase.AcceptHeader 173 | }).ConfigureAwait(continueOnCapturedContext: false); 174 | using Stream json = response.Content; 175 | return await JsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false); 176 | } 177 | 178 | internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) 179 | { 180 | string dataFilePath = GetDataFilePath(ConfigurationManager.ApplicationPaths, tmdbId, preferredMetadataLanguage); 181 | FileSystemMetadata fileSystemInfo = FileSystem.GetFileSystemInfo(dataFilePath); 182 | if (fileSystemInfo.Exists && DateTimeOffset.UtcNow - FileSystem.GetLastWriteTimeUtc(fileSystemInfo) <= MovieDbProviderBase.CacheTime) 183 | { 184 | return JsonSerializer.DeserializeFromFileAsync(dataFilePath); 185 | } 186 | return DownloadInfo(tmdbId, preferredMetadataLanguage, dataFilePath, cancellationToken); 187 | } 188 | 189 | private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage) 190 | { 191 | string dataPath = GetDataPath(appPaths, tmdbId); 192 | string text = "all"; 193 | if (!string.IsNullOrEmpty(preferredLanguage)) 194 | { 195 | text = text + "-" + preferredLanguage; 196 | } 197 | text += ".json"; 198 | return Path.Combine(dataPath, text); 199 | } 200 | 201 | private static string GetDataPath(IApplicationPaths appPaths, string tmdbId) 202 | { 203 | return Path.Combine(GetCollectionsDataPath(appPaths), tmdbId); 204 | } 205 | 206 | private static string GetCollectionsDataPath(IApplicationPaths appPaths) 207 | { 208 | return Path.Combine(appPaths.CachePath, "tmdb-collections"); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /MovieDb/MovieDbCollectionExternalId.cs: -------------------------------------------------------------------------------- 1 | using MediaBrowser.Controller.Entities; 2 | using MediaBrowser.Controller.Providers; 3 | using MediaBrowser.Model.Entities; 4 | 5 | namespace MovieDb; 6 | 7 | public class MovieDbCollectionExternalId : IExternalId 8 | { 9 | public string Name => Plugin.StaticName; 10 | 11 | public string Key => MetadataProviders.Tmdb.ToString(); 12 | 13 | public string UrlFormatString => Plugin.Instance.Configuration.TmdbHomeUrl + "/collection/{0}"; 14 | 15 | public bool Supports(IHasProviderIds item) 16 | { 17 | return item is BoxSet; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MovieDb/MovieDbEpisodeImageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using MediaBrowser.Common.Net; 8 | using MediaBrowser.Controller; 9 | using MediaBrowser.Controller.Configuration; 10 | using MediaBrowser.Controller.Entities; 11 | using MediaBrowser.Controller.Entities.TV; 12 | using MediaBrowser.Controller.Library; 13 | using MediaBrowser.Controller.Providers; 14 | using MediaBrowser.Model.Configuration; 15 | using MediaBrowser.Model.Dto; 16 | using MediaBrowser.Model.Entities; 17 | using MediaBrowser.Model.Globalization; 18 | using MediaBrowser.Model.IO; 19 | using MediaBrowser.Model.Logging; 20 | using MediaBrowser.Model.Net; 21 | using MediaBrowser.Model.Providers; 22 | using MediaBrowser.Model.Serialization; 23 | 24 | namespace MovieDb; 25 | 26 | public class MovieDbEpisodeImageProvider : MovieDbProviderBase, IRemoteImageProviderWithOptions, IRemoteImageProvider, IImageProvider, IHasOrder 27 | { 28 | public int Order => 1; 29 | 30 | public MovieDbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 31 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 32 | { 33 | } 34 | 35 | public IEnumerable GetSupportedImages(BaseItem item) 36 | { 37 | return new List { ImageType.Primary }; 38 | } 39 | 40 | public async Task> GetImages(RemoteImageFetchOptions options, CancellationToken cancellationToken) 41 | { 42 | BaseItem item = options.Item; 43 | _ = options.LibraryOptions; 44 | Episode val = (Episode)item; 45 | Series series = val.Series; 46 | string text = ((series != null) ? series.GetProviderId(MetadataProviders.Tmdb) : null); 47 | List list = new List(); 48 | if (string.IsNullOrEmpty(text)) 49 | { 50 | return list; 51 | } 52 | int? parentIndexNumber = val.ParentIndexNumber; 53 | int? indexNumber = val.IndexNumber; 54 | if (!parentIndexNumber.HasValue || !indexNumber.HasValue) 55 | { 56 | return list; 57 | } 58 | try 59 | { 60 | RootObject response = await GetEpisodeInfo(text, parentIndexNumber.Value, indexNumber.Value, null, options.DirectoryService, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 61 | TmdbSettingsResult tmdbSettings; 62 | try 63 | { 64 | tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 65 | if (tmdbSettings?.images == null) 66 | { 67 | Logger.Error("TMDB settings or images configuration is null"); 68 | return list; 69 | } 70 | } 71 | catch (Exception ex2) 72 | { 73 | Exception ex = ex2; 74 | Logger.Error("Error getting TMDB settings: {0}", ex); 75 | return list; 76 | } 77 | string tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); 78 | if (string.IsNullOrEmpty(tmdbImageUrl)) 79 | { 80 | Logger.Error("TMDB image URL is empty"); 81 | return list; 82 | } 83 | list.AddRange(from i in GetPosters(response.images) 84 | select new RemoteImageInfo 85 | { 86 | Url = tmdbImageUrl + i.file_path, 87 | ThumbnailUrl = tmdbSettings.images.GetBackdropThumbnailImageUrl(i.file_path), 88 | CommunityRating = i.vote_average, 89 | VoteCount = i.vote_count, 90 | Width = i.width, 91 | Height = i.height, 92 | Language = MovieDbImageProvider.NormalizeImageLanguage(i.iso_639_1), 93 | ProviderName = base.Name, 94 | Type = ImageType.Primary, 95 | RatingType = RatingType.Score 96 | }); 97 | return list; 98 | } 99 | catch (HttpException val3) 100 | { 101 | if (val3.StatusCode.HasValue && val3.StatusCode.Value == HttpStatusCode.NotFound) 102 | { 103 | return list; 104 | } 105 | throw; 106 | } 107 | catch (Exception ex) 108 | { 109 | Logger.Error("Unexpected error in GetImages: {0}", ex); 110 | throw; 111 | } 112 | } 113 | 114 | public Task> GetImages(BaseItem item, LibraryOptions libraryOptions, CancellationToken cancellationToken) 115 | { 116 | throw new NotImplementedException(); 117 | } 118 | 119 | protected override List GetPosters(TmdbImages images) 120 | { 121 | return images?.stills ?? new List(); 122 | } 123 | 124 | public bool Supports(BaseItem item) 125 | { 126 | return item is Episode; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /MovieDb/MovieDbEpisodeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using MediaBrowser.Common.Net; 9 | using MediaBrowser.Controller; 10 | using MediaBrowser.Controller.Configuration; 11 | using MediaBrowser.Controller.Entities; 12 | using MediaBrowser.Controller.Entities.TV; 13 | using MediaBrowser.Controller.Library; 14 | using MediaBrowser.Controller.Providers; 15 | using MediaBrowser.Model.Configuration; 16 | using MediaBrowser.Model.Entities; 17 | using MediaBrowser.Model.Globalization; 18 | using MediaBrowser.Model.IO; 19 | using MediaBrowser.Model.Logging; 20 | using MediaBrowser.Model.Net; 21 | using MediaBrowser.Model.Providers; 22 | using MediaBrowser.Model.Serialization; 23 | 24 | namespace MovieDb; 25 | 26 | public class MovieDbEpisodeProvider : MovieDbProviderBase, IRemoteMetadataProviderWithOptions, IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider, IHasOrder, IHasMetadataFeatures 27 | { 28 | public MetadataFeatures[] Features => new MetadataFeatures[1] { MetadataFeatures.Adult }; 29 | 30 | public int Order => 1; 31 | 32 | public MovieDbEpisodeProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 33 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 34 | { 35 | } 36 | 37 | public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) 38 | { 39 | List list = new List(); 40 | DirectoryService directoryService = new DirectoryService(FileSystem); 41 | MetadataResult val = await GetMetadata(new RemoteMetadataFetchOptions 42 | { 43 | SearchInfo = searchInfo, 44 | DirectoryService = directoryService 45 | }, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 46 | if (val.HasMetadata) 47 | { 48 | list.Add(val.ToRemoteSearchResult(base.Name)); 49 | } 50 | return list; 51 | } 52 | 53 | public async Task> GetMetadata(RemoteMetadataFetchOptions options, CancellationToken cancellationToken) 54 | { 55 | EpisodeInfo info = options.SearchInfo; 56 | MetadataResult result = new MetadataResult(); 57 | if (info.IsMissingEpisode) 58 | { 59 | return result; 60 | } 61 | Dictionary seriesProviderIds = info.SeriesProviderIds; 62 | seriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out var seriesTmdbId); 63 | if (string.IsNullOrEmpty(seriesTmdbId)) 64 | { 65 | return result; 66 | } 67 | int? seasonNumber = info.ParentIndexNumber; 68 | int? episodeNumber = info.IndexNumber; 69 | if (!seasonNumber.HasValue || !episodeNumber.HasValue) 70 | { 71 | return result; 72 | } 73 | TmdbSettingsResult tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 74 | ItemLookupInfo searchInfo = info; 75 | string[] movieDbMetadataLanguages = GetMovieDbMetadataLanguages(searchInfo, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 76 | bool isFirstLanguage = true; 77 | string[] array = movieDbMetadataLanguages; 78 | string[] array2 = array; 79 | foreach (string language in array2) 80 | { 81 | try 82 | { 83 | RootObject rootObject = await GetEpisodeInfo(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, language, options.DirectoryService, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 84 | if (rootObject != null) 85 | { 86 | result.HasMetadata = true; 87 | result.QueriedById = true; 88 | if (result.Item == null) 89 | { 90 | result.Item = new Episode(); 91 | } 92 | ImportData(result, info, rootObject, tmdbSettings, isFirstLanguage); 93 | isFirstLanguage = false; 94 | if (IsComplete(result.Item)) 95 | { 96 | return result; 97 | } 98 | } 99 | } 100 | catch (HttpException val2) 101 | { 102 | HttpException val3 = val2; 103 | if (val3.StatusCode.HasValue && val3.StatusCode.Value == HttpStatusCode.NotFound) 104 | { 105 | return result; 106 | } 107 | throw; 108 | } 109 | } 110 | return result; 111 | } 112 | 113 | private bool IsComplete(Episode item) 114 | { 115 | if (string.IsNullOrEmpty(item.Name)) 116 | { 117 | return false; 118 | } 119 | if (string.IsNullOrEmpty(item.Overview)) 120 | { 121 | return false; 122 | } 123 | return true; 124 | } 125 | 126 | private void ImportData(MetadataResult result, EpisodeInfo info, RootObject response, TmdbSettingsResult settings, bool isFirstLanguage) 127 | { 128 | Episode item = result.Item; 129 | if (string.IsNullOrEmpty(item.Name)) 130 | { 131 | item.Name = response.name; 132 | } 133 | if (string.IsNullOrEmpty(item.Overview)) 134 | { 135 | item.Overview = response.overview; 136 | } 137 | if (!isFirstLanguage) 138 | { 139 | return; 140 | } 141 | if (item.RemoteTrailers.Length == 0 && response.videos != null && response.videos.results != null) 142 | { 143 | foreach (TmdbVideo result3 in response.videos.results) 144 | { 145 | if (string.Equals(result3.type, "trailer", StringComparison.OrdinalIgnoreCase) && string.Equals(result3.site, "youtube", StringComparison.OrdinalIgnoreCase)) 146 | { 147 | string text = "http://www.youtube.com/watch?v=" + result3.key; 148 | item.AddTrailerUrl(text); 149 | } 150 | } 151 | } 152 | item.IndexNumber = info.IndexNumber; 153 | item.ParentIndexNumber = info.ParentIndexNumber; 154 | item.IndexNumberEnd = info.IndexNumberEnd; 155 | if (response.external_ids != null) 156 | { 157 | if (response.external_ids.tvdb_id > 0) 158 | { 159 | item.SetProviderId(MetadataProviders.Tvdb, response.external_ids.tvdb_id.Value.ToString(CultureInfo.InvariantCulture)); 160 | } 161 | if (response.external_ids.tvrage_id > 0) 162 | { 163 | item.SetProviderId(MetadataProviders.TvRage, response.external_ids.tvrage_id.Value.ToString(CultureInfo.InvariantCulture)); 164 | } 165 | if (!string.IsNullOrEmpty(response.external_ids.imdb_id) && !string.Equals(response.external_ids.imdb_id, "0", StringComparison.OrdinalIgnoreCase)) 166 | { 167 | item.SetProviderId(MetadataProviders.Imdb, response.external_ids.imdb_id); 168 | } 169 | } 170 | item.PremiereDate = response.air_date; 171 | item.ProductionYear = item.PremiereDate.Value.Year; 172 | item.CommunityRating = (float)response.vote_average; 173 | string imageUrl = settings.images.GetImageUrl("original"); 174 | result.ResetPeople(); 175 | TmdbCredits credits = response.credits; 176 | if (credits == null) 177 | { 178 | return; 179 | } 180 | if (credits.cast != null) 181 | { 182 | foreach (TmdbCast item2 in credits.cast.OrderBy((TmdbCast a) => a.order)) 183 | { 184 | PersonInfo val = new PersonInfo 185 | { 186 | Name = item2.name.Trim(), 187 | Role = item2.character, 188 | Type = PersonType.Actor 189 | }; 190 | if (!string.IsNullOrWhiteSpace(item2.profile_path)) 191 | { 192 | val.ImageUrl = imageUrl + item2.profile_path; 193 | } 194 | if (item2.id > 0) 195 | { 196 | val.SetProviderId(MetadataProviders.Tmdb, item2.id.ToString(CultureInfo.InvariantCulture)); 197 | } 198 | result.AddPerson(val); 199 | } 200 | } 201 | if (credits.guest_stars != null) 202 | { 203 | foreach (GuestStar item3 in credits.guest_stars.OrderBy((GuestStar a) => a.order)) 204 | { 205 | PersonInfo val2 = new PersonInfo 206 | { 207 | Name = item3.name.Trim(), 208 | Role = item3.character, 209 | Type = PersonType.GuestStar 210 | }; 211 | if (!string.IsNullOrWhiteSpace(item3.profile_path)) 212 | { 213 | val2.ImageUrl = imageUrl + item3.profile_path; 214 | } 215 | if (item3.id > 0) 216 | { 217 | val2.SetProviderId(MetadataProviders.Tmdb, item3.id.ToString(CultureInfo.InvariantCulture)); 218 | } 219 | result.AddPerson(val2); 220 | } 221 | } 222 | if (credits.crew == null) 223 | { 224 | return; 225 | } 226 | PersonType[] source = new PersonType[1] { PersonType.Director }; 227 | foreach (TmdbCrew item4 in credits.crew) 228 | { 229 | PersonType val3 = PersonType.Lyricist; 230 | string department = item4.department; 231 | if (string.Equals(department, "writing", StringComparison.OrdinalIgnoreCase)) 232 | { 233 | val3 = PersonType.Writer; 234 | } 235 | if (Enum.TryParse(department, ignoreCase: true, out var result2)) 236 | { 237 | val3 = result2; 238 | } 239 | else if (Enum.TryParse(item4.job, ignoreCase: true, out result2)) 240 | { 241 | val3 = result2; 242 | } 243 | if (source.Contains(val3)) 244 | { 245 | result.AddPerson(new PersonInfo 246 | { 247 | Name = item4.name.Trim(), 248 | Role = item4.job, 249 | Type = val3 250 | }); 251 | } 252 | } 253 | } 254 | 255 | public Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) 256 | { 257 | throw new NotImplementedException(); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /MovieDb/MovieDbImageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MediaBrowser.Common.Net; 7 | using MediaBrowser.Controller; 8 | using MediaBrowser.Controller.Configuration; 9 | using MediaBrowser.Controller.Entities; 10 | using MediaBrowser.Controller.Entities.Movies; 11 | using MediaBrowser.Controller.Library; 12 | using MediaBrowser.Controller.Providers; 13 | using MediaBrowser.Model.Configuration; 14 | using MediaBrowser.Model.Dto; 15 | using MediaBrowser.Model.Entities; 16 | using MediaBrowser.Model.Globalization; 17 | using MediaBrowser.Model.IO; 18 | using MediaBrowser.Model.Logging; 19 | using MediaBrowser.Model.Providers; 20 | using MediaBrowser.Model.Serialization; 21 | 22 | namespace MovieDb; 23 | 24 | internal class MovieDbImageProvider : MovieDbProviderBase, IRemoteImageProviderWithOptions, IRemoteImageProvider, IImageProvider, IHasOrder 25 | { 26 | public int Order => 0; 27 | 28 | public MovieDbImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 29 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 30 | { 31 | } 32 | 33 | public bool Supports(BaseItem item) 34 | { 35 | if (!(item is Movie) && !(item is MusicVideo)) 36 | { 37 | return item is Trailer; 38 | } 39 | return true; 40 | } 41 | 42 | public IEnumerable GetSupportedImages(BaseItem item) 43 | { 44 | return new List 45 | { 46 | ImageType.Primary, 47 | ImageType.Backdrop, 48 | ImageType.Logo 49 | }; 50 | } 51 | 52 | public async Task> GetImages(RemoteImageFetchOptions options, CancellationToken cancellationToken) 53 | { 54 | BaseItem item = options.Item; 55 | List list = new List(); 56 | MovieDbProvider.CompleteMovieData movieInfo = await GetMovieInfo(item, null, JsonSerializer, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 57 | TmdbImages results = movieInfo?.images; 58 | TmdbSettingsResult tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 59 | string tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); 60 | List list2 = GetSupportedImages(item).ToList(); 61 | if (results != null) 62 | { 63 | if (list2.Contains(ImageType.Primary)) 64 | { 65 | list.AddRange(from i in GetPosters(results) 66 | select new RemoteImageInfo 67 | { 68 | Url = tmdbImageUrl + i.file_path, 69 | ThumbnailUrl = tmdbSettings.images.GetPosterThumbnailImageUrl(i.file_path), 70 | CommunityRating = i.vote_average, 71 | VoteCount = i.vote_count, 72 | Width = i.width, 73 | Height = i.height, 74 | Language = NormalizeImageLanguage(i.iso_639_1), 75 | ProviderName = base.Name, 76 | Type = ImageType.Primary, 77 | RatingType = RatingType.Score 78 | }); 79 | } 80 | if (list2.Contains(ImageType.Logo)) 81 | { 82 | list.AddRange(from i in GetLogos(results) 83 | select new RemoteImageInfo 84 | { 85 | Url = tmdbImageUrl + i.file_path, 86 | ThumbnailUrl = tmdbSettings.images.GetLogoThumbnailImageUrl(i.file_path), 87 | CommunityRating = i.vote_average, 88 | VoteCount = i.vote_count, 89 | Width = i.width, 90 | Height = i.height, 91 | Language = NormalizeImageLanguage(i.iso_639_1), 92 | ProviderName = base.Name, 93 | Type = ImageType.Logo, 94 | RatingType = RatingType.Score 95 | }); 96 | } 97 | if (list2.Contains(ImageType.Backdrop)) 98 | { 99 | list.AddRange(from i in GetBackdrops(results) 100 | where string.IsNullOrEmpty(i.iso_639_1) 101 | select new RemoteImageInfo 102 | { 103 | Url = tmdbImageUrl + i.file_path, 104 | ThumbnailUrl = tmdbSettings.images.GetBackdropThumbnailImageUrl(i.file_path), 105 | CommunityRating = i.vote_average, 106 | VoteCount = i.vote_count, 107 | Width = i.width, 108 | Height = i.height, 109 | ProviderName = base.Name, 110 | Type = ImageType.Backdrop, 111 | RatingType = RatingType.Score 112 | }); 113 | } 114 | } 115 | if (list2.Contains(ImageType.Primary)) 116 | { 117 | string text = movieInfo?.poster_path; 118 | if (!string.IsNullOrWhiteSpace(text)) 119 | { 120 | list.Add(new RemoteImageInfo 121 | { 122 | ProviderName = base.Name, 123 | Type = ImageType.Primary, 124 | Url = tmdbImageUrl + text 125 | }); 126 | } 127 | } 128 | return list; 129 | } 130 | 131 | public Task> GetImages(BaseItem item, LibraryOptions libraryOptions, CancellationToken cancellationToken) 132 | { 133 | throw new NotImplementedException(); 134 | } 135 | 136 | public static string NormalizeImageLanguage(string lang) 137 | { 138 | if (string.Equals(lang, "xx", StringComparison.OrdinalIgnoreCase)) 139 | { 140 | return null; 141 | } 142 | return lang; 143 | } 144 | 145 | private async Task GetMovieInfo(BaseItem item, string language, IJsonSerializer jsonSerializer, CancellationToken cancellationToken) 146 | { 147 | string providerId = item.GetProviderId(MetadataProviders.Tmdb); 148 | MovieDbProvider.CompleteMovieData completeMovieData; 149 | if (string.IsNullOrWhiteSpace(providerId)) 150 | { 151 | string providerId2 = item.GetProviderId(MetadataProviders.Imdb); 152 | if (!string.IsNullOrWhiteSpace(providerId2)) 153 | { 154 | completeMovieData = await MovieDbProvider.Current.FetchMainResult(providerId2, isTmdbId: false, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 155 | if (completeMovieData != null) 156 | { 157 | return completeMovieData; 158 | } 159 | } 160 | return null; 161 | } 162 | completeMovieData = await MovieDbProvider.Current.EnsureMovieInfo(providerId, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 163 | if (completeMovieData != null) 164 | { 165 | return completeMovieData; 166 | } 167 | return null; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /MovieDb/MovieDbMovieExternalId.cs: -------------------------------------------------------------------------------- 1 | using MediaBrowser.Controller.Entities; 2 | using MediaBrowser.Controller.Entities.Movies; 3 | using MediaBrowser.Controller.LiveTv; 4 | using MediaBrowser.Controller.Providers; 5 | using MediaBrowser.Model.Entities; 6 | 7 | namespace MovieDb; 8 | 9 | public class MovieDbMovieExternalId : IExternalId 10 | { 11 | public string Name => Plugin.StaticName; 12 | 13 | public string Key => MetadataProviders.Tmdb.ToString(); 14 | 15 | public string UrlFormatString 16 | { 17 | get 18 | { 19 | var config = Plugin.Instance?.Configuration; 20 | if (config != null) 21 | { 22 | return $"{config.TmdbHomeUrl}/movie/{{0}}"; 23 | } 24 | return "https://www.themoviedb.org/movie/{0}"; 25 | } 26 | } 27 | 28 | public bool Supports(IHasProviderIds item) 29 | { 30 | LiveTvProgram val = (LiveTvProgram)((item is LiveTvProgram) ? item : null); 31 | if (val != null && val.IsMovie) 32 | { 33 | return true; 34 | } 35 | if (!(item is Movie) && !(item is MusicVideo)) 36 | { 37 | return item is Trailer; 38 | } 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MovieDb/MovieDbMusicVideoProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using MediaBrowser.Common.Net; 6 | using MediaBrowser.Controller.Entities; 7 | using MediaBrowser.Controller.Providers; 8 | using MediaBrowser.Model.Configuration; 9 | using MediaBrowser.Model.Providers; 10 | 11 | namespace MovieDb; 12 | 13 | public class MovieDbMusicVideoProvider : IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider, IHasMetadataFeatures 14 | { 15 | public string Name => MovieDbProvider.Current.Name; 16 | 17 | public MetadataFeatures[] Features => new MetadataFeatures[2] 18 | { 19 | MetadataFeatures.Adult, 20 | MetadataFeatures.Collections 21 | }; 22 | 23 | public Task> GetMetadata(MusicVideoInfo info, CancellationToken cancellationToken) 24 | { 25 | return MovieDbProvider.Current.GetItemMetadata(info, cancellationToken); 26 | } 27 | 28 | public Task> GetSearchResults(MusicVideoInfo searchInfo, CancellationToken cancellationToken) 29 | { 30 | return MovieDbProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken); 31 | } 32 | 33 | public Task GetImageResponse(string url, CancellationToken cancellationToken) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MovieDb/MovieDbPersonExternalId.cs: -------------------------------------------------------------------------------- 1 | using MediaBrowser.Controller.Entities; 2 | using MediaBrowser.Controller.Providers; 3 | using MediaBrowser.Model.Entities; 4 | 5 | namespace MovieDb; 6 | 7 | public class MovieDbPersonExternalId : IExternalId 8 | { 9 | public string Name => Plugin.StaticName; 10 | 11 | public string Key => MetadataProviders.Tmdb.ToString(); 12 | 13 | public string UrlFormatString 14 | { 15 | get 16 | { 17 | var config = Plugin.Instance?.Configuration; 18 | if (config != null) 19 | { 20 | return $"{config.TmdbHomeUrl}/person/{{0}}"; 21 | } 22 | return "https://www.themoviedb.org/person/{0}"; 23 | } 24 | } 25 | 26 | public bool Supports(IHasProviderIds item) 27 | { 28 | return item is Person; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MovieDb/MovieDbPersonImageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MediaBrowser.Common.Net; 7 | using MediaBrowser.Controller; 8 | using MediaBrowser.Controller.Configuration; 9 | using MediaBrowser.Controller.Entities; 10 | using MediaBrowser.Controller.Library; 11 | using MediaBrowser.Controller.Providers; 12 | using MediaBrowser.Model.Configuration; 13 | using MediaBrowser.Model.Dto; 14 | using MediaBrowser.Model.Entities; 15 | using MediaBrowser.Model.Globalization; 16 | using MediaBrowser.Model.IO; 17 | using MediaBrowser.Model.Logging; 18 | using MediaBrowser.Model.Providers; 19 | using MediaBrowser.Model.Serialization; 20 | 21 | namespace MovieDb; 22 | 23 | public class MovieDbPersonImageProvider : MovieDbProviderBase, IRemoteImageProviderWithOptions, IRemoteImageProvider, IImageProvider, IHasOrder 24 | { 25 | public int Order => 0; 26 | 27 | public MovieDbPersonImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 28 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 29 | { 30 | } 31 | 32 | public bool Supports(BaseItem item) 33 | { 34 | return item is Person; 35 | } 36 | 37 | public IEnumerable GetSupportedImages(BaseItem item) 38 | { 39 | return new List { ImageType.Primary }; 40 | } 41 | 42 | public async Task> GetImages(RemoteImageFetchOptions options, CancellationToken cancellationToken) 43 | { 44 | BaseItem item = options.Item; 45 | _ = options.LibraryOptions; 46 | IDirectoryService directoryService = options.DirectoryService; 47 | string providerId = ((Person)item).GetProviderId(MetadataProviders.Tmdb); 48 | if (!string.IsNullOrEmpty(providerId)) 49 | { 50 | MovieDbPersonProvider.Images images = (await MovieDbPersonProvider.Current.EnsurePersonInfo(providerId, null, directoryService, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).images ?? new MovieDbPersonProvider.Images(); 51 | TmdbSettingsResult tmdbSettingsResult = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 52 | string imageUrl = tmdbSettingsResult.images.GetImageUrl("original"); 53 | return GetImages(images, tmdbSettingsResult, imageUrl); 54 | } 55 | return new List(); 56 | } 57 | 58 | public Task> GetImages(BaseItem item, LibraryOptions libraryOptions, CancellationToken cancellationToken) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | 63 | private IEnumerable GetImages(MovieDbPersonProvider.Images images, TmdbSettingsResult tmdbSettings, string baseImageUrl) 64 | { 65 | List list = new List(); 66 | if (images.profiles != null) 67 | { 68 | list.AddRange(images.profiles.Select((TmdbImage i) => new RemoteImageInfo 69 | { 70 | Url = baseImageUrl + i.file_path, 71 | ThumbnailUrl = tmdbSettings.images.GetProfileThumbnailImageUrl(i.file_path), 72 | CommunityRating = i.vote_average, 73 | VoteCount = i.vote_count, 74 | Width = i.width, 75 | Height = i.height, 76 | ProviderName = base.Name, 77 | Type = ImageType.Primary, 78 | RatingType = RatingType.Score 79 | })); 80 | } 81 | return list; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /MovieDb/MovieDbPersonProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using MediaBrowser.Common.Configuration; 10 | using MediaBrowser.Common.Extensions; 11 | using MediaBrowser.Common.Net; 12 | using MediaBrowser.Controller; 13 | using MediaBrowser.Controller.Configuration; 14 | using MediaBrowser.Controller.Entities; 15 | using MediaBrowser.Controller.Library; 16 | using MediaBrowser.Controller.Providers; 17 | using MediaBrowser.Model.Entities; 18 | using MediaBrowser.Model.Globalization; 19 | using MediaBrowser.Model.IO; 20 | using MediaBrowser.Model.Logging; 21 | using MediaBrowser.Model.Net; 22 | using MediaBrowser.Model.Providers; 23 | using MediaBrowser.Model.Serialization; 24 | 25 | namespace MovieDb; 26 | 27 | public class MovieDbPersonProvider : MovieDbProviderBase, IRemoteMetadataProviderWithOptions, IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider 28 | { 29 | public class PersonSearchResult 30 | { 31 | public bool Adult { get; set; } 32 | 33 | public int Id { get; set; } 34 | 35 | public string Name { get; set; } 36 | 37 | public string Profile_Path { get; set; } 38 | } 39 | 40 | public class PersonSearchResults 41 | { 42 | public int Page { get; set; } 43 | 44 | public List Results { get; set; } 45 | 46 | public int Total_Pages { get; set; } 47 | 48 | public int Total_Results { get; set; } 49 | 50 | public PersonSearchResults() 51 | { 52 | Results = new List(); 53 | } 54 | } 55 | 56 | public class GeneralSearchResults 57 | { 58 | public List person_results { get; set; } 59 | 60 | public GeneralSearchResults() 61 | { 62 | person_results = new List(); 63 | } 64 | } 65 | 66 | public class Images 67 | { 68 | public List profiles { get; set; } 69 | } 70 | 71 | public class PersonResult 72 | { 73 | public bool adult { get; set; } 74 | 75 | public List also_known_as { get; set; } 76 | 77 | public string biography { get; set; } 78 | 79 | public string birthday { get; set; } 80 | 81 | public string deathday { get; set; } 82 | 83 | public string homepage { get; set; } 84 | 85 | public int id { get; set; } 86 | 87 | public string imdb_id { get; set; } 88 | 89 | public string name { get; set; } 90 | 91 | public string place_of_birth { get; set; } 92 | 93 | public double popularity { get; set; } 94 | 95 | public string profile_path { get; set; } 96 | 97 | public TmdbCredits credits { get; set; } 98 | 99 | public Images images { get; set; } 100 | 101 | public TmdbExternalIds external_ids { get; set; } 102 | } 103 | 104 | internal static MovieDbPersonProvider Current { get; private set; } 105 | 106 | public MovieDbPersonProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogManager logManager, ILocalizationManager localization, IServerApplicationHost appHost, ILibraryManager libraryManager) 107 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 108 | { 109 | Current = this; 110 | } 111 | 112 | // 定义 API 路径模板 113 | private const string PersonSearchPath = "3/search/person"; 114 | private const string PersonFindPath = "3/find/{0}"; 115 | private const string PersonMetadataPath = "3/person/{0}"; 116 | private const string AppendToResponse = "credits,images,external_ids"; 117 | 118 | public async Task> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) 119 | { 120 | string tmdbId = ProviderIdsExtensions.GetProviderId((IHasProviderIds)searchInfo, MetadataProviders.Tmdb); 121 | string tmdbImageUrl = (await GetTmdbSettings(cancellationToken).ConfigureAwait(false)).images.GetImageUrl("original"); 122 | 123 | if (!string.IsNullOrEmpty(tmdbId)) 124 | { 125 | DirectoryService directoryService = new DirectoryService(FileSystem); 126 | MetadataResult val = await GetMetadata(new RemoteMetadataFetchOptions 127 | { 128 | SearchInfo = searchInfo, 129 | DirectoryService = (IDirectoryService)(object)directoryService 130 | }, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 131 | if (!((BaseMetadataResult)val).HasMetadata) 132 | { 133 | return new List(); 134 | } 135 | RemoteSearchResult result = ((BaseMetadataResult)val).ToRemoteSearchResult(base.Name); 136 | List images = ((await EnsurePersonInfo(tmdbId, null, (IDirectoryService)(object)directoryService, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))?.images ?? new Images()).profiles ?? new List(); 137 | await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 138 | result.ImageUrl = ((images.Count == 0) ? null : (tmdbImageUrl + images[0].file_path)); 139 | return (IEnumerable)(object)new RemoteSearchResult[1] { result }; 140 | } 141 | 142 | string providerId = ProviderIdsExtensions.GetProviderId((IHasProviderIds)searchInfo, MetadataProviders.Imdb); 143 | HttpResponseInfo response; 144 | 145 | if (!string.IsNullOrEmpty(providerId)) 146 | { 147 | string path = string.Format(PersonFindPath, providerId); 148 | string apiUrl = GetApiUrl(path) + "&external_source=imdb_id"; 149 | 150 | response = await GetMovieDbResponse(new HttpRequestOptions 151 | { 152 | Url = apiUrl, 153 | CancellationToken = cancellationToken, 154 | AcceptHeader = AcceptHeader 155 | }).ConfigureAwait(false); 156 | 157 | try 158 | { 159 | using (Stream stream = response.Content) 160 | { 161 | return (await JsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false) ?? new GeneralSearchResults()) 162 | .person_results.Select(i => GetSearchResult(i, tmdbImageUrl)); 163 | } 164 | } 165 | finally 166 | { 167 | ((IDisposable)response)?.Dispose(); 168 | } 169 | } 170 | 171 | if (searchInfo.IsAutomated) 172 | { 173 | return new List(); 174 | } 175 | 176 | string url = GetApiUrl(PersonSearchPath) + $"&query={WebUtility.UrlEncode(searchInfo.Name)}"; 177 | 178 | response = await GetMovieDbResponse(new HttpRequestOptions 179 | { 180 | Url = url, 181 | CancellationToken = cancellationToken, 182 | AcceptHeader = AcceptHeader 183 | }).ConfigureAwait(false); 184 | 185 | try 186 | { 187 | using (Stream stream = response.Content) 188 | { 189 | return (await JsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false) ?? new PersonSearchResults()) 190 | .Results.Select(i => GetSearchResult(i, tmdbImageUrl)); 191 | } 192 | } 193 | finally 194 | { 195 | ((IDisposable)response)?.Dispose(); 196 | } 197 | } 198 | 199 | private RemoteSearchResult GetSearchResult(PersonSearchResult i, string baseImageUrl) 200 | { 201 | 202 | RemoteSearchResult val = new RemoteSearchResult 203 | { 204 | SearchProviderName = base.Name, 205 | Name = i.Name, 206 | ImageUrl = string.IsNullOrEmpty(i.Profile_Path) ? null : (baseImageUrl + i.Profile_Path) 207 | }; 208 | ProviderIdsExtensions.SetProviderId(val, (MetadataProviders)3, i.Id.ToString(CultureInfo.InvariantCulture)); 209 | return val; 210 | } 211 | 212 | public async Task> GetMetadata(RemoteMetadataFetchOptions options, CancellationToken cancellationToken) 213 | { 214 | PersonLookupInfo id = options.SearchInfo; 215 | IDirectoryService directoryService = options.DirectoryService; 216 | string tmdbId = ProviderIdsExtensions.GetProviderId((IHasProviderIds)(object)id, (MetadataProviders)3); 217 | if (string.IsNullOrEmpty(tmdbId)) 218 | { 219 | tmdbId = await GetTmdbId(id, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 220 | } 221 | MetadataResult result = new MetadataResult(); 222 | string[] movieDbMetadataLanguages = GetMovieDbMetadataLanguages((ItemLookupInfo)(object)id, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 223 | if (!string.IsNullOrEmpty(tmdbId)) 224 | { 225 | bool isFirstLanguage = true; 226 | string[] array = movieDbMetadataLanguages; 227 | foreach (string language in array) 228 | { 229 | PersonResult personResult; 230 | try 231 | { 232 | personResult = await EnsurePersonInfo(tmdbId, language, directoryService, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 233 | } 234 | catch (HttpException val) 235 | { 236 | HttpException val2 = val; 237 | if (val2.StatusCode.HasValue && val2.StatusCode.Value == HttpStatusCode.NotFound) 238 | { 239 | return result; 240 | } 241 | throw; 242 | } 243 | if (personResult != null) 244 | { 245 | ((BaseMetadataResult)result).HasMetadata = true; 246 | if (result.Item == null) 247 | { 248 | result.Item = new Person(); 249 | } 250 | ImportData(result.Item, personResult, isFirstLanguage); 251 | isFirstLanguage = false; 252 | if (IsComplete(result.Item)) 253 | { 254 | return result; 255 | } 256 | } 257 | } 258 | } 259 | return result; 260 | } 261 | 262 | private bool IsComplete(Person item) 263 | { 264 | if (string.IsNullOrEmpty(item.Overview)) 265 | { 266 | return false; 267 | } 268 | return true; 269 | } 270 | 271 | private void ImportData(Person item, PersonResult info, bool isFirstLanguage) 272 | { 273 | if (string.IsNullOrEmpty(((BaseItem)item).Name)) 274 | { 275 | ((BaseItem)item).Name = info.name; 276 | } 277 | if (string.IsNullOrEmpty(((BaseItem)item).Overview)) 278 | { 279 | ((BaseItem)item).Overview = info.biography; 280 | } 281 | if (isFirstLanguage) 282 | { 283 | if (!string.IsNullOrWhiteSpace(info.place_of_birth)) 284 | { 285 | ((BaseItem)item).ProductionLocations = new string[1] { info.place_of_birth }; 286 | } 287 | if (DateTimeOffset.TryParseExact(info.birthday, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) 288 | { 289 | ((BaseItem)item).PremiereDate = result.ToUniversalTime(); 290 | } 291 | if (DateTimeOffset.TryParseExact(info.deathday, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result2)) 292 | { 293 | ((BaseItem)item).EndDate = result2.ToUniversalTime(); 294 | } 295 | ProviderIdsExtensions.SetProviderId((IHasProviderIds)(object)item, (MetadataProviders)3, info.id.ToString(CultureInfo.InvariantCulture)); 296 | if (!string.IsNullOrEmpty(info.imdb_id)) 297 | { 298 | ProviderIdsExtensions.SetProviderId((IHasProviderIds)(object)item, (MetadataProviders)2, info.imdb_id); 299 | } 300 | } 301 | } 302 | 303 | public Task> GetMetadata(PersonLookupInfo id, CancellationToken cancellationToken) 304 | { 305 | throw new NotImplementedException(); 306 | } 307 | 308 | private async Task GetTmdbId(PersonLookupInfo info, CancellationToken cancellationToken) 309 | { 310 | return (await GetSearchResults(info, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Select((RemoteSearchResult i) => ProviderIdsExtensions.GetProviderId((IHasProviderIds)(object)i, (MetadataProviders)3)).FirstOrDefault(); 311 | } 312 | 313 | internal async Task EnsurePersonInfo(string id, string language, IDirectoryService directoryService, CancellationToken cancellationToken) 314 | { 315 | string cacheKey = $"tmdb_person_{id}_{language}"; 316 | PersonResult personResult; 317 | if (!directoryService.TryGetFromCache(cacheKey, out personResult)) 318 | { 319 | string dataFilePath = GetPersonDataFilePath((IApplicationPaths)(object)ConfigurationManager.ApplicationPaths, id, language); 320 | FileSystemMetadata fileSystemInfo = FileSystem.GetFileSystemInfo(dataFilePath); 321 | if (fileSystemInfo.Exists && DateTimeOffset.UtcNow - FileSystem.GetLastWriteTimeUtc(fileSystemInfo) <= MovieDbProviderBase.CacheTime) 322 | { 323 | personResult = await JsonSerializer.DeserializeFromFileAsync(dataFilePath).ConfigureAwait(continueOnCapturedContext: false); 324 | } 325 | if (personResult == null) 326 | { 327 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(dataFilePath)); 328 | personResult = await FetchPersonResult(id, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 329 | using Stream stream = FileSystem.GetFileStream(dataFilePath, (FileOpenMode)2, (FileAccessMode)2, (FileShareMode)1, false); 330 | JsonSerializer.SerializeToStream((object)personResult, stream); 331 | } 332 | directoryService.AddOrUpdateCache(cacheKey, (object)personResult); 333 | } 334 | return personResult; 335 | } 336 | 337 | private string GetPersonMetadataUrl(string id) 338 | { 339 | string path = string.Format(PersonMetadataPath, id); 340 | return GetApiUrl(path) + $"&append_to_response={AppendToResponse}"; 341 | } 342 | 343 | private async Task FetchPersonResult(string id, string language, CancellationToken cancellationToken) 344 | { 345 | string url = GetPersonMetadataUrl(id); 346 | 347 | if (!string.IsNullOrEmpty(language)) 348 | { 349 | url += $"&language={language}"; 350 | } 351 | 352 | var response = await GetMovieDbResponse(new HttpRequestOptions 353 | { 354 | Url = url, 355 | CancellationToken = cancellationToken, 356 | AcceptHeader = AcceptHeader 357 | }).ConfigureAwait(false); 358 | 359 | try 360 | { 361 | using (Stream json = response.Content) 362 | { 363 | return await JsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false); 364 | } 365 | } 366 | finally 367 | { 368 | ((IDisposable)response)?.Dispose(); 369 | } 370 | } 371 | 372 | private static string GetPersonDataPath(IApplicationPaths appPaths, string tmdbId) 373 | { 374 | string path = BaseExtensions.GetMD5(tmdbId).ToString().Substring(0, 1); 375 | return Path.Combine(GetPersonsDataPath(appPaths), path, tmdbId); 376 | } 377 | 378 | internal static string GetPersonDataFilePath(IApplicationPaths appPaths, string tmdbId, string language) 379 | { 380 | string text = "info"; 381 | if (!string.IsNullOrEmpty(language)) 382 | { 383 | text = text + "-" + language; 384 | } 385 | text += ".json"; 386 | return Path.Combine(GetPersonDataPath(appPaths, tmdbId), text); 387 | } 388 | 389 | private static string GetPersonsDataPath(IApplicationPaths appPaths) 390 | { 391 | return Path.Combine(appPaths.CachePath, "tmdb-people"); 392 | } 393 | 394 | // 添加 new 关键字来显式隐藏基类的 GetApiUrl 方法 395 | private new string GetApiUrl(string path) 396 | { 397 | var config = GetConfiguration(); 398 | var baseUrl = config.TmdbApiBaseUrl.TrimEnd('/'); 399 | return $"{baseUrl}/{path}?api_key={config.ApiKey}"; 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /MovieDb/MovieDbProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using MediaBrowser.Common.Configuration; 8 | using MediaBrowser.Common.Net; 9 | using MediaBrowser.Controller; 10 | using MediaBrowser.Controller.Configuration; 11 | using MediaBrowser.Controller.Entities; 12 | using MediaBrowser.Controller.Entities.Movies; 13 | using MediaBrowser.Controller.Library; 14 | using MediaBrowser.Controller.Providers; 15 | using MediaBrowser.Model.Configuration; 16 | using MediaBrowser.Model.Entities; 17 | using MediaBrowser.Model.Globalization; 18 | using MediaBrowser.Model.IO; 19 | using MediaBrowser.Model.Logging; 20 | using MediaBrowser.Model.Net; 21 | using MediaBrowser.Model.Providers; 22 | using MediaBrowser.Model.Serialization; 23 | 24 | namespace MovieDb; 25 | 26 | public class MovieDbProvider : MovieDbProviderBase, IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider, IHasOrder, IHasMetadataFeatures 27 | { 28 | internal class TmdbTitle 29 | { 30 | public string iso_3166_1 { get; set; } 31 | 32 | public string title { get; set; } 33 | } 34 | 35 | internal class TmdbAltTitleResults 36 | { 37 | public int id { get; set; } 38 | 39 | public List titles { get; set; } 40 | } 41 | 42 | public class BelongsToCollection 43 | { 44 | public int id { get; set; } 45 | 46 | public string name { get; set; } 47 | 48 | public string poster_path { get; set; } 49 | 50 | public string backdrop_path { get; set; } 51 | } 52 | 53 | public class ProductionCompany 54 | { 55 | public int id { get; set; } 56 | 57 | public string logo_path { get; set; } 58 | 59 | public string name { get; set; } 60 | 61 | public string origin_country { get; set; } 62 | } 63 | 64 | public class ProductionCountry 65 | { 66 | public string iso_3166_1 { get; set; } 67 | 68 | public string name { get; set; } 69 | } 70 | 71 | public class Casts 72 | { 73 | public List cast { get; set; } 74 | 75 | public List crew { get; set; } 76 | } 77 | 78 | public class Country 79 | { 80 | public string certification { get; set; } 81 | 82 | public string iso_3166_1 { get; set; } 83 | 84 | public bool primary { get; set; } 85 | 86 | public DateTimeOffset release_date { get; set; } 87 | 88 | public string GetRating() 89 | { 90 | return GetRating(certification, iso_3166_1); 91 | } 92 | 93 | public static string GetRating(string rating, string iso_3166_1) 94 | { 95 | if (string.IsNullOrEmpty(rating)) 96 | { 97 | return null; 98 | } 99 | if (string.Equals(iso_3166_1, "us", StringComparison.OrdinalIgnoreCase)) 100 | { 101 | return rating; 102 | } 103 | if (string.Equals(iso_3166_1, "de", StringComparison.OrdinalIgnoreCase)) 104 | { 105 | iso_3166_1 = "FSK"; 106 | } 107 | return iso_3166_1 + "-" + rating; 108 | } 109 | } 110 | 111 | public class Releases 112 | { 113 | public List countries { get; set; } 114 | } 115 | 116 | public class Youtube 117 | { 118 | public string name { get; set; } 119 | 120 | public string size { get; set; } 121 | 122 | public string source { get; set; } 123 | 124 | public string type { get; set; } 125 | } 126 | 127 | public class Trailers 128 | { 129 | public List quicktime { get; set; } 130 | 131 | public List youtube { get; set; } 132 | } 133 | 134 | internal class CompleteMovieData 135 | { 136 | public bool adult { get; set; } 137 | 138 | public string backdrop_path { get; set; } 139 | 140 | public BelongsToCollection belongs_to_collection { get; set; } 141 | 142 | public int budget { get; set; } 143 | 144 | public List genres { get; set; } 145 | 146 | public string homepage { get; set; } 147 | 148 | public int id { get; set; } 149 | 150 | public string imdb_id { get; set; } 151 | 152 | public string original_language { get; set; } 153 | 154 | public string original_title { get; set; } 155 | 156 | public string overview { get; set; } 157 | 158 | public double popularity { get; set; } 159 | 160 | public string poster_path { get; set; } 161 | 162 | public List production_companies { get; set; } 163 | 164 | public List production_countries { get; set; } 165 | 166 | public string release_date { get; set; } 167 | 168 | public int revenue { get; set; } 169 | 170 | public int runtime { get; set; } 171 | 172 | public List spoken_languages { get; set; } 173 | 174 | public string status { get; set; } 175 | 176 | public string tagline { get; set; } 177 | 178 | public string title { get; set; } 179 | 180 | public bool video { get; set; } 181 | 182 | public double vote_average { get; set; } 183 | 184 | public int vote_count { get; set; } 185 | 186 | public Casts casts { get; set; } 187 | 188 | public Releases releases { get; set; } 189 | 190 | public TmdbImages images { get; set; } 191 | 192 | public TmdbKeywords keywords { get; set; } 193 | 194 | public Trailers trailers { get; set; } 195 | 196 | public string name { get; set; } 197 | 198 | public string original_name { get; set; } 199 | 200 | public string GetOriginalTitle() 201 | { 202 | return original_name ?? original_title; 203 | } 204 | 205 | public string GetTitle() 206 | { 207 | return name ?? title ?? GetOriginalTitle(); 208 | } 209 | } 210 | 211 | private const string MovieInfoPath = "3/movie/{0}"; 212 | 213 | private const string AppendToResponse = "alternative_titles,reviews,casts,releases,images,keywords,trailers"; 214 | 215 | internal static MovieDbProvider Current { get; private set; } 216 | 217 | public MetadataFeatures[] Features => new MetadataFeatures[2] 218 | { 219 | MetadataFeatures.Adult, 220 | MetadataFeatures.Collections 221 | }; 222 | 223 | public int Order => 1; 224 | 225 | public MovieDbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogManager logManager, ILocalizationManager localization, IServerApplicationHost appHost, ILibraryManager libraryManager) 226 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 227 | { 228 | Current = this; 229 | } 230 | 231 | public Task> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) 232 | { 233 | return GetMovieSearchResults(searchInfo, cancellationToken); 234 | } 235 | 236 | public async Task> GetMovieSearchResults(ItemLookupInfo searchInfo, CancellationToken cancellationToken) 237 | { 238 | string tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb); 239 | TmdbSettingsResult tmdbSettings = null; 240 | if (!string.IsNullOrEmpty(tmdbId)) 241 | { 242 | MetadataResult val = await GetItemMetadata(searchInfo, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 243 | if (!val.HasMetadata) 244 | { 245 | return new List(); 246 | } 247 | RemoteSearchResult result = val.ToRemoteSearchResult(base.Name); 248 | List images = ((await EnsureMovieInfo(tmdbId, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))?.images ?? new TmdbImages()).posters ?? new List(); 249 | string imageUrl = (await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).images.GetImageUrl("original"); 250 | result.ImageUrl = ((images.Count == 0) ? null : (imageUrl + images[0].file_path)); 251 | return new RemoteSearchResult[1] { result }; 252 | } 253 | string providerId = searchInfo.GetProviderId(MetadataProviders.Imdb); 254 | if (!string.IsNullOrEmpty(providerId)) 255 | { 256 | MovieDbSearch movieDbSearch = new MovieDbSearch(Logger, JsonSerializer, LibraryManager); 257 | RemoteSearchResult val3 = await movieDbSearch.FindMovieByExternalId(providerId, "imdb_id", MetadataProviders.Imdb.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 258 | if (val3 != null) 259 | { 260 | return new RemoteSearchResult[1] { val3 }; 261 | } 262 | } 263 | if (tmdbSettings == null) 264 | { 265 | tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 266 | } 267 | string[] movieDbMetadataLanguages = GetMovieDbMetadataLanguages(searchInfo, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 268 | return await new MovieDbSearch(Logger, JsonSerializer, LibraryManager).GetMovieSearchResults(searchInfo, movieDbMetadataLanguages, tmdbSettings, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 269 | } 270 | 271 | public Task> GetMetadata(MovieInfo info, CancellationToken cancellationToken) 272 | { 273 | return GetItemMetadata(info, cancellationToken); 274 | } 275 | 276 | public Task> GetItemMetadata(ItemLookupInfo id, CancellationToken cancellationToken) where T : BaseItem, new() 277 | { 278 | return new GenericMovieDbInfo(JsonSerializer, HttpClient, FileSystem, ConfigurationManager, LogManager, Localization, AppHost, LibraryManager).GetMetadata(id, cancellationToken); 279 | } 280 | 281 | internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId) 282 | { 283 | return Path.Combine(GetMoviesDataPath(appPaths), tmdbId); 284 | } 285 | 286 | internal static string GetMoviesDataPath(IApplicationPaths appPaths) 287 | { 288 | return Path.Combine(appPaths.CachePath, "tmdb-movies2"); 289 | } 290 | 291 | private async Task DownloadMovieInfo(string id, string preferredMetadataLanguage, string dataFilePath, CancellationToken cancellationToken) 292 | { 293 | CompleteMovieData completeMovieData = await FetchMainResult(id, isTmdbId: true, preferredMetadataLanguage, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 294 | if (completeMovieData == null) 295 | { 296 | return null; 297 | } 298 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(dataFilePath)); 299 | JsonSerializer.SerializeToFile(completeMovieData, dataFilePath); 300 | return completeMovieData; 301 | } 302 | 303 | internal async Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken) 304 | { 305 | if (string.IsNullOrEmpty(tmdbId)) 306 | { 307 | throw new ArgumentNullException("tmdbId"); 308 | } 309 | string dataFilePath = GetDataFilePath(tmdbId, language); 310 | FileSystemMetadata fileSystemInfo = FileSystem.GetFileSystemInfo(dataFilePath); 311 | if (fileSystemInfo.Exists && DateTimeOffset.UtcNow - FileSystem.GetLastWriteTimeUtc(fileSystemInfo) <= MovieDbProviderBase.CacheTime) 312 | { 313 | return await JsonSerializer.DeserializeFromFileAsync(fileSystemInfo.FullName); 314 | } 315 | return await DownloadMovieInfo(tmdbId, language, dataFilePath, cancellationToken); 316 | } 317 | 318 | internal string GetDataFilePath(string tmdbId, string preferredLanguage) 319 | { 320 | if (string.IsNullOrEmpty(tmdbId)) 321 | { 322 | throw new ArgumentNullException("tmdbId"); 323 | } 324 | string text = "all"; 325 | if (!string.IsNullOrEmpty(preferredLanguage)) 326 | { 327 | text = text + "-" + preferredLanguage; 328 | } 329 | text += ".json"; 330 | return Path.Combine(GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId), text); 331 | } 332 | 333 | internal async Task FetchMainResult(string id, bool isTmdbId, string language, CancellationToken cancellationToken) 334 | { 335 | GetConfiguration(); 336 | string path = $"3/movie/{id}"; 337 | string url = GetApiUrl(path) + "&append_to_response=alternative_titles,reviews,casts,releases,images,keywords,trailers"; 338 | if (!string.IsNullOrEmpty(language)) 339 | { 340 | url = url + "&language=" + language; 341 | } 342 | url = AddImageLanguageParam(url, language); 343 | cancellationToken.ThrowIfCancellationRequested(); 344 | CacheMode cacheMode = ((!isTmdbId) ? CacheMode.Unconditional : CacheMode.None); 345 | TimeSpan cacheTime = MovieDbProviderBase.CacheTime; 346 | try 347 | { 348 | using HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions 349 | { 350 | Url = url, 351 | CancellationToken = cancellationToken, 352 | AcceptHeader = MovieDbProviderBase.AcceptHeader, 353 | CacheMode = cacheMode, 354 | CacheLength = cacheTime 355 | }).ConfigureAwait(continueOnCapturedContext: false); 356 | using Stream json = response.Content; 357 | return await JsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false); 358 | } 359 | catch (HttpException ex) 360 | { 361 | if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) 362 | { 363 | return null; 364 | } 365 | throw; 366 | } 367 | } 368 | 369 | private new string GetApiUrl(string path) 370 | { 371 | PluginOptions config = GetConfiguration(); 372 | string baseUrl = config.TmdbApiBaseUrl.TrimEnd(new char[1] { '/' }); 373 | return baseUrl + "/" + path + "?api_key=" + config.ApiKey; 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /MovieDb/MovieDbProviderBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using MediaBrowser.Common.Net; 9 | using MediaBrowser.Controller; 10 | using MediaBrowser.Controller.Configuration; 11 | using MediaBrowser.Controller.Library; 12 | using MediaBrowser.Controller.Providers; 13 | using MediaBrowser.Model.Globalization; 14 | using MediaBrowser.Model.IO; 15 | using MediaBrowser.Model.Logging; 16 | using MediaBrowser.Model.Serialization; 17 | 18 | namespace MovieDb; 19 | 20 | public abstract class MovieDbProviderBase 21 | { 22 | public class GuestStar 23 | { 24 | public int id { get; set; } 25 | 26 | public string name { get; set; } 27 | 28 | public string credit_id { get; set; } 29 | 30 | public string character { get; set; } 31 | 32 | public int order { get; set; } 33 | 34 | public string profile_path { get; set; } 35 | } 36 | 37 | public class RootObject 38 | { 39 | public DateTimeOffset air_date { get; set; } 40 | 41 | public int episode_number { get; set; } 42 | 43 | public string name { get; set; } 44 | 45 | public string overview { get; set; } 46 | 47 | public int id { get; set; } 48 | 49 | public object production_code { get; set; } 50 | 51 | public int season_number { get; set; } 52 | 53 | public string still_path { get; set; } 54 | 55 | public double vote_average { get; set; } 56 | 57 | public int vote_count { get; set; } 58 | 59 | public TmdbImages images { get; set; } 60 | 61 | public TmdbExternalIds external_ids { get; set; } 62 | 63 | public TmdbCredits credits { get; set; } 64 | 65 | public TmdbVideos videos { get; set; } 66 | } 67 | 68 | internal static string AcceptHeader = "application/json,image/*"; 69 | 70 | private const string TmdbConfigPath = "3/configuration"; 71 | 72 | private const string TmdbLanguagesPath = "3/configuration/primary_translations"; 73 | 74 | private const string EpisodeUrlPattern = "3/tv/{0}/season/{1}/episode/{2}"; 75 | 76 | protected readonly IHttpClient HttpClient; 77 | 78 | protected readonly IServerConfigurationManager ConfigurationManager; 79 | 80 | protected readonly IJsonSerializer JsonSerializer; 81 | 82 | protected readonly IFileSystem FileSystem; 83 | 84 | protected readonly ILocalizationManager Localization; 85 | 86 | protected readonly ILogger Logger; 87 | 88 | protected readonly ILogManager LogManager; 89 | 90 | protected readonly IServerApplicationHost AppHost; 91 | 92 | protected readonly ILibraryManager LibraryManager; 93 | 94 | public static TimeSpan CacheTime = TimeSpan.FromHours(6.0); 95 | 96 | private static TmdbSettingsResult _tmdbSettings; 97 | 98 | private static string[] _tmdbLanguages; 99 | 100 | private static long _lastRequestTicks; 101 | 102 | private static int requestIntervalMs = 100; 103 | 104 | private static readonly object _settingsLock = new object(); 105 | 106 | private readonly ILogger _logger; 107 | 108 | public string Name => ProviderName; 109 | 110 | public static string ProviderName => "TheMovieDb"; 111 | 112 | protected string BaseMovieDbUrl 113 | { 114 | get 115 | { 116 | var config = GetConfiguration(); 117 | return config.TmdbApiBaseUrl?.TrimEnd('/') ?? "https://api.themoviedb.org/3"; 118 | } 119 | } 120 | 121 | protected string GetApiUrl(string path) 122 | { 123 | try 124 | { 125 | PluginOptions config = GetConfiguration(); 126 | string baseUrl = config.TmdbApiBaseUrl?.TrimEnd(new char[1] { '/' }) ?? "https://api.themoviedb.org"; 127 | string apiKey = config.ApiKey; 128 | if (string.IsNullOrEmpty(apiKey)) 129 | { 130 | Logger.Error("TMDB API key is not configured"); 131 | throw new InvalidOperationException("TMDB API key is not configured"); 132 | } 133 | return baseUrl + "/" + path + "?api_key=" + apiKey; 134 | } 135 | catch (Exception ex) 136 | { 137 | Logger.Error("Error generating API URL: {0}", ex); 138 | throw; 139 | } 140 | } 141 | 142 | protected string GetImageUrl(string imagePath) 143 | { 144 | try 145 | { 146 | if (string.IsNullOrEmpty(imagePath)) 147 | { 148 | return null; 149 | } 150 | PluginOptions config = GetConfiguration(); 151 | string baseUrl = config.TmdbImageBaseUrl?.TrimEnd(new char[1] { '/' }) ?? "https://image.tmdb.org/t/p/"; 152 | return baseUrl + "/" + imagePath.TrimStart(new char[1] { '/' }); 153 | } 154 | catch (Exception ex) 155 | { 156 | Logger.Error("Error generating image URL: {0}", ex); 157 | throw; 158 | } 159 | } 160 | 161 | public async Task GetTmdbSettings(CancellationToken cancellationToken) 162 | { 163 | if (_tmdbSettings != null) 164 | { 165 | EnsureImageUrls(_tmdbSettings); 166 | return _tmdbSettings; 167 | } 168 | HttpResponseInfo response = null; 169 | try 170 | { 171 | response = await GetMovieDbResponse(new HttpRequestOptions 172 | { 173 | Url = GetApiUrl("3/configuration"), 174 | CancellationToken = cancellationToken, 175 | AcceptHeader = AcceptHeader 176 | }).ConfigureAwait(continueOnCapturedContext: false); 177 | using Stream json = response.Content; 178 | using StreamReader reader = new StreamReader(json); 179 | string text = await reader.ReadToEndAsync().ConfigureAwait(continueOnCapturedContext: false); 180 | Logger.Info("MovieDb settings: {0}", text); 181 | lock (_settingsLock) 182 | { 183 | _tmdbSettings = JsonSerializer.DeserializeFromString(text); 184 | EnsureImageUrls(_tmdbSettings); 185 | } 186 | } 187 | catch (Exception ex2) 188 | { 189 | Exception ex = ex2; 190 | Logger.Error("Error getting TMDb settings: {0}", ex); 191 | lock (_settingsLock) 192 | { 193 | _tmdbSettings = new TmdbSettingsResult 194 | { 195 | images = new TmdbImageSettings 196 | { 197 | secure_base_url = GetConfiguration().TmdbImageBaseUrl 198 | } 199 | }; 200 | } 201 | } 202 | finally 203 | { 204 | if (response != null) 205 | { 206 | ((IDisposable)response)?.Dispose(); 207 | } 208 | } 209 | return _tmdbSettings; 210 | } 211 | 212 | private void EnsureImageUrls(TmdbSettingsResult settings) 213 | { 214 | if (settings?.images != null) 215 | { 216 | settings.images.secure_base_url = GetConfiguration().TmdbImageBaseUrl; 217 | } 218 | } 219 | 220 | public MovieDbProviderBase(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost applicationHost, ILibraryManager libraryManager) 221 | { 222 | HttpClient = httpClient; 223 | ConfigurationManager = configurationManager; 224 | JsonSerializer = jsonSerializer; 225 | FileSystem = fileSystem; 226 | Localization = localization; 227 | LogManager = logManager; 228 | Logger = logManager.GetLogger(Name); 229 | AppHost = applicationHost; 230 | LibraryManager = libraryManager; 231 | } 232 | 233 | protected async Task GetEpisodeInfo(string tmdbId, int seasonNumber, int episodeNumber, string language, IDirectoryService directoryService, CancellationToken cancellationToken) 234 | { 235 | if (string.IsNullOrEmpty(tmdbId)) 236 | { 237 | throw new ArgumentNullException("tmdbId"); 238 | } 239 | string cacheKey = "tmdb_episode_" + tmdbId; 240 | if (!string.IsNullOrEmpty(language)) 241 | { 242 | cacheKey = cacheKey + "_" + language; 243 | } 244 | cacheKey = cacheKey + "_" + seasonNumber + "_" + episodeNumber; 245 | RootObject rootObject = null; 246 | if (!directoryService.TryGetFromCache(cacheKey, out rootObject)) 247 | { 248 | string dataFilePath = GetDataFilePath(tmdbId, seasonNumber, episodeNumber, language); 249 | FileSystemMetadata fileSystemInfo = FileSystem.GetFileSystemInfo(dataFilePath); 250 | if (fileSystemInfo.Exists && DateTimeOffset.UtcNow - FileSystem.GetLastWriteTimeUtc(fileSystemInfo) <= CacheTime) 251 | { 252 | rootObject = await JsonSerializer.DeserializeFromFileAsync(dataFilePath).ConfigureAwait(continueOnCapturedContext: false); 253 | } 254 | if (rootObject == null) 255 | { 256 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(dataFilePath)); 257 | rootObject = await DownloadEpisodeInfo(tmdbId, seasonNumber, episodeNumber, language, dataFilePath, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 258 | using Stream stream = FileSystem.GetFileStream(dataFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read); 259 | JsonSerializer.SerializeToStream(rootObject, stream); 260 | } 261 | directoryService.AddOrUpdateCache(cacheKey, rootObject); 262 | } 263 | return rootObject; 264 | } 265 | 266 | internal string GetDataFilePath(string tmdbId, int seasonNumber, int episodeNumber, string preferredLanguage) 267 | { 268 | if (string.IsNullOrEmpty(tmdbId)) 269 | { 270 | throw new ArgumentNullException("tmdbId"); 271 | } 272 | string text = "season-" + seasonNumber.ToString(CultureInfo.InvariantCulture) + "-episode-" + episodeNumber.ToString(CultureInfo.InvariantCulture); 273 | if (!string.IsNullOrEmpty(preferredLanguage)) 274 | { 275 | text = text + "-" + preferredLanguage; 276 | } 277 | text += ".json"; 278 | return Path.Combine(MovieDbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tmdbId), text); 279 | } 280 | 281 | internal async Task DownloadEpisodeInfo(string id, int seasonNumber, int episodeNumber, string preferredMetadataLanguage, string dataFilePath, CancellationToken cancellationToken) 282 | { 283 | RootObject rootObject = await FetchMainResult(id, seasonNumber, episodeNumber, preferredMetadataLanguage, dataFilePath, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 284 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(dataFilePath)); 285 | JsonSerializer.SerializeToFile(rootObject, dataFilePath); 286 | return rootObject; 287 | } 288 | 289 | internal async Task FetchMainResult(string id, int seasonNumber, int episodeNumber, string language, string dataFilePath, CancellationToken cancellationToken) 290 | { 291 | string path = $"3/tv/{id}/season/{seasonNumber.ToString(CultureInfo.InvariantCulture)}/episode/{episodeNumber.ToString(CultureInfo.InvariantCulture)}"; 292 | string url = GetApiUrl(path); 293 | if (!string.IsNullOrEmpty(language)) 294 | { 295 | url = url + "&language=" + language; 296 | } 297 | url += "&append_to_response=images,external_ids,credits,videos"; 298 | url = AddImageLanguageParam(url, language); 299 | cancellationToken.ThrowIfCancellationRequested(); 300 | using HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions 301 | { 302 | Url = url, 303 | CancellationToken = cancellationToken, 304 | AcceptHeader = AcceptHeader 305 | }).ConfigureAwait(continueOnCapturedContext: false); 306 | using Stream json = response.Content; 307 | return await JsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false); 308 | } 309 | 310 | public async Task GetTmdbLanguages(CancellationToken cancellationToken) 311 | { 312 | if (_tmdbLanguages != null) 313 | { 314 | return _tmdbLanguages; 315 | } 316 | using Stream json = (await GetMovieDbResponse(new HttpRequestOptions 317 | { 318 | Url = GetApiUrl("3/configuration/primary_translations"), 319 | CancellationToken = cancellationToken, 320 | AcceptHeader = AcceptHeader 321 | }).ConfigureAwait(continueOnCapturedContext: false)).Content; 322 | using StreamReader reader = new StreamReader(json); 323 | string text = await reader.ReadToEndAsync().ConfigureAwait(continueOnCapturedContext: false); 324 | _tmdbLanguages = JsonSerializer.DeserializeFromString(text); 325 | return _tmdbLanguages; 326 | } 327 | 328 | public string AddImageLanguageParam(string url, string tmdbLanguage) 329 | { 330 | string imageLanguagesParam = GetImageLanguagesParam(tmdbLanguage); 331 | if (!string.IsNullOrEmpty(imageLanguagesParam)) 332 | { 333 | url = url + "&include_image_language=" + imageLanguagesParam; 334 | } 335 | return url; 336 | } 337 | 338 | public string[] GetMovieDbMetadataLanguages(ItemLookupInfo searchInfo, string[] providerLanguages) 339 | { 340 | List list = new List(); 341 | string metadataLanguage = searchInfo.MetadataLanguage; 342 | string metadataCountryCode = searchInfo.MetadataCountryCode; 343 | if (!string.IsNullOrEmpty(metadataLanguage)) 344 | { 345 | string text = MapLanguageToProviderLanguage(metadataLanguage, metadataCountryCode, exactMatchOnly: false, providerLanguages); 346 | if (!string.IsNullOrEmpty(text)) 347 | { 348 | list.Add(text); 349 | } 350 | } 351 | if (!list.Contains("en", StringComparer.OrdinalIgnoreCase) && !list.Contains("en-us", StringComparer.OrdinalIgnoreCase)) 352 | { 353 | string text2 = MapLanguageToProviderLanguage("en-us", null, exactMatchOnly: false, providerLanguages); 354 | if (!string.IsNullOrEmpty(text2)) 355 | { 356 | list.Add(text2); 357 | } 358 | } 359 | return list.ToArray(); 360 | } 361 | 362 | private string GetImageLanguagesParam(string tmdbLanguage) 363 | { 364 | List list = new List(); 365 | if (!string.IsNullOrEmpty(tmdbLanguage)) 366 | { 367 | list.Add(tmdbLanguage); 368 | } 369 | return GetImageLanguagesParam(list.ToArray()); 370 | } 371 | 372 | private string GetImageLanguagesParam(string[] configuredLanguages) 373 | { 374 | List list = configuredLanguages.ToList(); 375 | if (list.Count > 0) 376 | { 377 | list.Add("null"); 378 | } 379 | return string.Join(",", list.ToArray()); 380 | } 381 | 382 | private string MapLanguageToProviderLanguage(string language, string country, bool exactMatchOnly, string[] providerLanguages) 383 | { 384 | string text = FindExactMatch(language, providerLanguages); 385 | if (text != null) 386 | { 387 | return text; 388 | } 389 | string[] array = language.Split(new char[1] { '-' }, StringSplitOptions.RemoveEmptyEntries); 390 | if (array.Length == 1) 391 | { 392 | text = FindExactMatch(language + "-" + language, providerLanguages); 393 | if (text != null) 394 | { 395 | return text; 396 | } 397 | } 398 | text = FindExactMatch(array[0], providerLanguages); 399 | if (text != null) 400 | { 401 | return text; 402 | } 403 | text = FindExactMatch(array[0] + "-" + array[0], providerLanguages); 404 | if (text != null) 405 | { 406 | return text; 407 | } 408 | if (!string.IsNullOrEmpty(country)) 409 | { 410 | text = FindExactMatch(language + "-" + country, providerLanguages); 411 | if (text != null) 412 | { 413 | return text; 414 | } 415 | text = FindExactMatch(array[0] + "-" + country, providerLanguages); 416 | if (text != null) 417 | { 418 | return text; 419 | } 420 | } 421 | if (!exactMatchOnly) 422 | { 423 | return FindAnyMatch(language, providerLanguages) ?? FindAnyMatch(array[0], providerLanguages); 424 | } 425 | return null; 426 | } 427 | 428 | private string FindExactMatch(string language, string[] providerLanguages) 429 | { 430 | foreach (string text in providerLanguages) 431 | { 432 | if (string.Equals(language, text, StringComparison.OrdinalIgnoreCase)) 433 | { 434 | return text; 435 | } 436 | } 437 | return null; 438 | } 439 | 440 | private string FindAnyMatch(string language, string[] providerLanguages) 441 | { 442 | foreach (string text in providerLanguages) 443 | { 444 | if (!string.IsNullOrEmpty(text) && text.StartsWith(language, StringComparison.OrdinalIgnoreCase)) 445 | { 446 | return text; 447 | } 448 | } 449 | return null; 450 | } 451 | 452 | public Task GetImageResponse(string url, CancellationToken cancellationToken) 453 | { 454 | return HttpClient.GetResponse(new HttpRequestOptions 455 | { 456 | CancellationToken = cancellationToken, 457 | Url = url 458 | }); 459 | } 460 | 461 | internal async Task GetMovieDbResponse(HttpRequestOptions options) 462 | { 463 | long num = Math.Min((requestIntervalMs * 10000 - (DateTimeOffset.UtcNow.Ticks - _lastRequestTicks)) / 10000, requestIntervalMs); 464 | if (num > 0) 465 | { 466 | Logger.Debug("Throttling Tmdb by {0} ms", num); 467 | await Task.Delay(Convert.ToInt32(num)).ConfigureAwait(continueOnCapturedContext: false); 468 | } 469 | _lastRequestTicks = DateTimeOffset.UtcNow.Ticks; 470 | options.BufferContent = true; 471 | options.UserAgent = "Emby/" + AppHost.ApplicationVersion; 472 | return await HttpClient.SendAsync(options, "GET").ConfigureAwait(continueOnCapturedContext: false); 473 | } 474 | 475 | protected List GetLogos(TmdbImages images) 476 | { 477 | return images?.logos ?? new List(); 478 | } 479 | 480 | protected virtual List GetPosters(TmdbImages images) 481 | { 482 | return images?.posters ?? new List(); 483 | } 484 | 485 | protected IEnumerable GetBackdrops(TmdbImages images) 486 | { 487 | return from i in images?.backdrops ?? new List() 488 | orderby i.vote_average descending, i.vote_count descending 489 | select i; 490 | } 491 | 492 | protected PluginOptions GetConfiguration() 493 | { 494 | Plugin instance = Plugin.Instance; 495 | if (instance == null) 496 | { 497 | Logger.Error("MovieDb Plugin instance is null"); 498 | return GetDefaultConfiguration(); 499 | } 500 | PluginOptions config = instance.Configuration; 501 | if (config == null) 502 | { 503 | Logger.Error("MovieDb Plugin configuration is null"); 504 | return GetDefaultConfiguration(); 505 | } 506 | return config; 507 | } 508 | 509 | private PluginOptions GetDefaultConfiguration() 510 | { 511 | return new PluginOptions 512 | { 513 | TmdbApiBaseUrl = "https://tmdb.kingscross.online:8333", 514 | TmdbImageBaseUrl = "https://image.kingscross.online:8333/t/p/", 515 | ApiKey = "59ef6336a19540cd1e254cae0565906e" 516 | }; 517 | } 518 | 519 | protected string GetApiKey() 520 | { 521 | PluginOptions config = GetConfiguration(); 522 | return config.ApiKey; 523 | } 524 | 525 | protected MovieDbProviderBase(ILogger logger) 526 | { 527 | _logger = logger; 528 | } 529 | } 530 | -------------------------------------------------------------------------------- /MovieDb/MovieDbSearch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using MediaBrowser.Common.Net; 10 | using MediaBrowser.Controller.Library; 11 | using MediaBrowser.Controller.Providers; 12 | using MediaBrowser.Model.Entities; 13 | using MediaBrowser.Model.Logging; 14 | using MediaBrowser.Model.Providers; 15 | using MediaBrowser.Model.Serialization; 16 | 17 | namespace MovieDb; 18 | 19 | public class MovieDbSearch 20 | { 21 | public class TmdbMovieSearchResult 22 | { 23 | public bool adult { get; set; } 24 | 25 | public string backdrop_path { get; set; } 26 | 27 | public int id { get; set; } 28 | 29 | public string release_date { get; set; } 30 | 31 | public string poster_path { get; set; } 32 | 33 | public double popularity { get; set; } 34 | 35 | public string title { get; set; } 36 | 37 | public double vote_average { get; set; } 38 | 39 | public string name { get; set; } 40 | 41 | public int vote_count { get; set; } 42 | 43 | public string original_title { get; set; } 44 | 45 | public string original_name { get; set; } 46 | 47 | public string GetTitle() 48 | { 49 | return name ?? title ?? GetOriginalTitle(); 50 | } 51 | 52 | public string GetOriginalTitle() 53 | { 54 | return original_name ?? original_title; 55 | } 56 | } 57 | 58 | public class TmdbMovieSearchResults 59 | { 60 | public int page { get; set; } 61 | 62 | public List results { get; set; } 63 | 64 | public int total_pages { get; set; } 65 | 66 | public int total_results { get; set; } 67 | } 68 | 69 | public class TvResult 70 | { 71 | public string backdrop_path { get; set; } 72 | 73 | public string first_air_date { get; set; } 74 | 75 | public int id { get; set; } 76 | 77 | public string poster_path { get; set; } 78 | 79 | public double popularity { get; set; } 80 | 81 | public string name { get; set; } 82 | 83 | public string title { get; set; } 84 | 85 | public double vote_average { get; set; } 86 | 87 | public int vote_count { get; set; } 88 | 89 | public string original_title { get; set; } 90 | 91 | public string original_name { get; set; } 92 | 93 | public string GetTitle() 94 | { 95 | return name ?? title ?? GetOriginalTitle(); 96 | } 97 | 98 | public string GetOriginalTitle() 99 | { 100 | return original_name ?? original_title; 101 | } 102 | } 103 | 104 | public class TmdbTvSearchResults 105 | { 106 | public int page { get; set; } 107 | 108 | public List results { get; set; } 109 | 110 | public int total_pages { get; set; } 111 | 112 | public int total_results { get; set; } 113 | } 114 | 115 | public class ExternalIdLookupResult 116 | { 117 | public List movie_results { get; set; } 118 | 119 | public List person_results { get; set; } 120 | 121 | public List tv_results { get; set; } 122 | } 123 | 124 | private readonly ILogger _logger; 125 | 126 | private readonly IJsonSerializer _json; 127 | 128 | private readonly ILibraryManager _libraryManager; 129 | 130 | public MovieDbSearch(ILogger logger, IJsonSerializer json, ILibraryManager libraryManager) 131 | { 132 | _logger = logger; 133 | _json = json; 134 | _libraryManager = libraryManager; 135 | } 136 | 137 | public Task> GetSearchResults(SeriesInfo idInfo, string[] tmdbLanguages, TmdbSettingsResult tmdbSettings, CancellationToken cancellationToken) 138 | { 139 | return GetSearchResults(idInfo, tmdbLanguages, "tv", tmdbSettings, cancellationToken); 140 | } 141 | 142 | public Task> GetMovieSearchResults(ItemLookupInfo idInfo, string[] tmdbLanguages, TmdbSettingsResult tmdbSettings, CancellationToken cancellationToken) 143 | { 144 | return GetSearchResults(idInfo, tmdbLanguages, "movie", tmdbSettings, cancellationToken); 145 | } 146 | 147 | public Task> GetCollectionSearchResults(ItemLookupInfo idInfo, string[] tmdbLanguages, TmdbSettingsResult tmdbSettings, CancellationToken cancellationToken) 148 | { 149 | return GetSearchResults(idInfo, tmdbLanguages, "collection", tmdbSettings, cancellationToken); 150 | } 151 | 152 | private async Task> GetSearchResults(ItemLookupInfo idInfo, string[] tmdbLanguages, string searchType, TmdbSettingsResult tmdbSettings, CancellationToken cancellationToken) 153 | { 154 | string name = idInfo.Name; 155 | if (string.IsNullOrEmpty(name)) 156 | { 157 | return new List(); 158 | } 159 | _logger.Info("MovieDbProvider: Finding id for item: " + name); 160 | foreach (string tmdbLanguage in tmdbLanguages) 161 | { 162 | List list = await GetSearchResults(idInfo, searchType, tmdbLanguage, tmdbSettings, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 163 | if (list.Count > 0) 164 | { 165 | return list; 166 | } 167 | } 168 | return new List(); 169 | } 170 | 171 | private async Task> GetSearchResults(ItemLookupInfo idInfo, string searchType, string tmdbLanguage, TmdbSettingsResult tmdbSettings, CancellationToken cancellationToken) 172 | { 173 | string name = idInfo.Name; 174 | int? year = idInfo.Year; 175 | if (string.IsNullOrEmpty(name)) 176 | { 177 | return new List(); 178 | } 179 | string tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); 180 | List list = await GetSearchResults(name, searchType, year, tmdbLanguage, idInfo.EnableAdultMetadata, tmdbImageUrl, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 181 | if (list.Count == 0) 182 | { 183 | string b = name; 184 | if (name.EndsWith(",the", StringComparison.OrdinalIgnoreCase)) 185 | { 186 | name = name.Substring(0, name.Length - 4); 187 | } 188 | else if (name.EndsWith(", the", StringComparison.OrdinalIgnoreCase)) 189 | { 190 | name = name.Substring(0, name.Length - 5); 191 | } 192 | name = name.Replace(',', ' '); 193 | name = name.Replace('.', ' '); 194 | name = name.Replace('_', ' '); 195 | name = name.Replace('-', ' '); 196 | name = name.Replace('!', ' '); 197 | name = name.Replace('<', ' '); 198 | name = name.Replace('﹤', ' '); 199 | name = name.Replace('>', ' '); 200 | name = name.Replace('﹥', ' '); 201 | name = name.Replace(':', ' '); 202 | name = name.Replace('\ua789', ' '); 203 | name = name.Replace('"', ' '); 204 | name = name.Replace('“', ' '); 205 | name = name.Replace('/', ' '); 206 | name = name.Replace('⁄', ' '); 207 | name = name.Replace('|', ' '); 208 | name = name.Replace('⼁', ' '); 209 | name = name.Replace('?', ' '); 210 | name = name.Replace('?', ' '); 211 | name = name.Replace('*', ' '); 212 | name = name.Replace('﹡', ' '); 213 | name = name.Replace('\\', ' '); 214 | name = name.Replace('∖', ' '); 215 | name = name.Replace("'", string.Empty); 216 | int num = name.IndexOfAny(new char[2] { '(', '[' }); 217 | if (num != -1) 218 | { 219 | if (num > 0) 220 | { 221 | name = name.Substring(0, num); 222 | } 223 | else 224 | { 225 | name = name.Replace('[', ' '); 226 | name = name.Replace(']', ' '); 227 | num = name.IndexOf('('); 228 | if (num != -1 && num > 0) 229 | { 230 | name = name.Substring(0, num); 231 | } 232 | } 233 | } 234 | name = name.Trim(); 235 | if (!string.Equals(name, b, StringComparison.OrdinalIgnoreCase)) 236 | { 237 | list = await GetSearchResults(name, searchType, year, tmdbLanguage, idInfo.EnableAdultMetadata, tmdbImageUrl, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 238 | } 239 | } 240 | return list; 241 | } 242 | 243 | private Task> GetSearchResults(string name, string type, int? year, string tmdbLanguage, bool includeAdult, string baseImageUrl, CancellationToken cancellationToken) 244 | { 245 | if (type == "tv") 246 | { 247 | return GetSearchResultsTv(name, year, tmdbLanguage, includeAdult, baseImageUrl, cancellationToken); 248 | } 249 | return GetSearchResultsGeneric(name, type, year, tmdbLanguage, includeAdult, baseImageUrl, cancellationToken); 250 | } 251 | 252 | public async Task FindMovieByExternalId(string id, string externalSource, string providerIdKey, CancellationToken cancellationToken) 253 | { 254 | PluginOptions config = Plugin.Instance.Configuration; 255 | string url = config.TmdbApiBaseUrl.TrimEnd(new char[1] { '/' }) + "/3/find/" + id + "?api_key=" + config.ApiKey + "&external_source=" + externalSource; 256 | using (HttpResponseInfo response = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions 257 | { 258 | Url = url, 259 | CancellationToken = cancellationToken, 260 | AcceptHeader = MovieDbProviderBase.AcceptHeader 261 | }).ConfigureAwait(continueOnCapturedContext: false)) 262 | { 263 | using Stream json = response.Content; 264 | ExternalIdLookupResult externalIdLookupResult = await _json.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false); 265 | if (externalIdLookupResult != null && externalIdLookupResult.movie_results != null) 266 | { 267 | TmdbMovieSearchResult tv = externalIdLookupResult.movie_results.FirstOrDefault(); 268 | if (tv != null) 269 | { 270 | string imageUrl = (await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).images.GetImageUrl("original"); 271 | return ParseMovieSearchResult(tv, imageUrl); 272 | } 273 | } 274 | } 275 | return null; 276 | } 277 | 278 | private async Task> GetSearchResultsGeneric(string name, string type, int? year, string tmdbLanguage, bool includeAdult, string baseImageUrl, CancellationToken cancellationToken) 279 | { 280 | if (string.IsNullOrEmpty(name)) 281 | { 282 | throw new ArgumentException("name"); 283 | } 284 | PluginOptions config = Plugin.Instance.Configuration; 285 | string text = config.TmdbApiBaseUrl.TrimEnd(new char[1] { '/' }) + "/3/search/" + type + "?api_key=" + config.ApiKey + "&query=" + WebUtility.UrlEncode(name) + "&language=" + tmdbLanguage; 286 | if (includeAdult) 287 | { 288 | text += "&include_adult=true"; 289 | } 290 | bool enableOneYearTolerance = false; 291 | if (!enableOneYearTolerance && year.HasValue) 292 | { 293 | text = text + "&year=" + year.Value.ToString(CultureInfo.InvariantCulture); 294 | } 295 | using HttpResponseInfo response = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions 296 | { 297 | Url = text, 298 | CancellationToken = cancellationToken, 299 | AcceptHeader = MovieDbProviderBase.AcceptHeader 300 | }).ConfigureAwait(continueOnCapturedContext: false); 301 | using Stream json = response.Content; 302 | return (from i in (await _json.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false)).results ?? new List() 303 | select ParseMovieSearchResult(i, baseImageUrl) into i 304 | where !year.HasValue || !i.ProductionYear.HasValue || !enableOneYearTolerance || Math.Abs(year.Value - i.ProductionYear.Value) <= 1 305 | select i).ToList(); 306 | } 307 | 308 | private RemoteSearchResult ParseMovieSearchResult(TmdbMovieSearchResult i, string baseImageUrl) 309 | { 310 | RemoteSearchResult val = new RemoteSearchResult 311 | { 312 | SearchProviderName = MovieDbProvider.Current.Name, 313 | Name = i.GetTitle(), 314 | ImageUrl = (string.IsNullOrWhiteSpace(i.poster_path) ? null : (baseImageUrl + i.poster_path)) 315 | }; 316 | if (!string.IsNullOrEmpty(i.release_date) && DateTimeOffset.TryParseExact(i.release_date, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) 317 | { 318 | val.PremiereDate = result.ToUniversalTime(); 319 | val.ProductionYear = val.PremiereDate.Value.Year; 320 | } 321 | val.SetProviderId(MetadataProviders.Tmdb, i.id.ToString(CultureInfo.InvariantCulture)); 322 | return val; 323 | } 324 | 325 | private async Task> GetSearchResultsTv(string name, int? year, string language, bool includeAdult, string baseImageUrl, CancellationToken cancellationToken) 326 | { 327 | if (string.IsNullOrEmpty(name)) 328 | { 329 | throw new ArgumentException("name"); 330 | } 331 | PluginOptions config = Plugin.Instance.Configuration; 332 | string text = config.TmdbApiBaseUrl.TrimEnd(new char[1] { '/' }) + "/3/search/tv?api_key=" + config.ApiKey + "&query=" + WebUtility.UrlEncode(name) + "&language=" + language; 333 | if (year.HasValue) 334 | { 335 | text = text + "&first_air_date_year=" + year.Value.ToString(CultureInfo.InvariantCulture); 336 | } 337 | if (includeAdult) 338 | { 339 | text += "&include_adult=true"; 340 | } 341 | using HttpResponseInfo response = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions 342 | { 343 | Url = text, 344 | CancellationToken = cancellationToken, 345 | AcceptHeader = MovieDbProviderBase.AcceptHeader 346 | }).ConfigureAwait(continueOnCapturedContext: false); 347 | using Stream json = response.Content; 348 | return ((await _json.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false)).results ?? new List()).Select((TvResult i) => ToRemoteSearchResult(i, baseImageUrl)).ToList(); 349 | } 350 | 351 | public static RemoteSearchResult ToRemoteSearchResult(TvResult i, string baseImageUrl) 352 | { 353 | RemoteSearchResult val = new RemoteSearchResult 354 | { 355 | SearchProviderName = MovieDbProvider.Current.Name, 356 | Name = i.GetTitle(), 357 | ImageUrl = (string.IsNullOrWhiteSpace(i.poster_path) ? null : (baseImageUrl + i.poster_path)) 358 | }; 359 | if (!string.IsNullOrEmpty(i.first_air_date) && DateTimeOffset.TryParseExact(i.first_air_date, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) 360 | { 361 | val.PremiereDate = result.ToUniversalTime(); 362 | val.ProductionYear = val.PremiereDate.Value.Year; 363 | } 364 | val.SetProviderId(MetadataProviders.Tmdb, i.id.ToString(CultureInfo.InvariantCulture)); 365 | return val; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /MovieDb/MovieDbSeasonImageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MediaBrowser.Common.Net; 7 | using MediaBrowser.Controller; 8 | using MediaBrowser.Controller.Configuration; 9 | using MediaBrowser.Controller.Entities; 10 | using MediaBrowser.Controller.Entities.TV; 11 | using MediaBrowser.Controller.Library; 12 | using MediaBrowser.Controller.Providers; 13 | using MediaBrowser.Model.Configuration; 14 | using MediaBrowser.Model.Dto; 15 | using MediaBrowser.Model.Entities; 16 | using MediaBrowser.Model.Globalization; 17 | using MediaBrowser.Model.IO; 18 | using MediaBrowser.Model.Logging; 19 | using MediaBrowser.Model.Net; 20 | using MediaBrowser.Model.Providers; 21 | using MediaBrowser.Model.Serialization; 22 | 23 | namespace MovieDb; 24 | 25 | public class MovieDbSeasonImageProvider : MovieDbProviderBase, IRemoteImageProviderWithOptions, IRemoteImageProvider, IImageProvider, IHasOrder 26 | { 27 | private readonly MovieDbSeasonProvider _seasonProvider; 28 | 29 | public int Order => 2; 30 | 31 | public MovieDbSeasonImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 32 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 33 | { 34 | _seasonProvider = new MovieDbSeasonProvider(jsonSerializer, httpClient, fileSystem, configurationManager, logManager, localization, appHost, libraryManager); 35 | } 36 | 37 | public bool Supports(BaseItem item) 38 | { 39 | return item is Season; 40 | } 41 | 42 | public IEnumerable GetSupportedImages(BaseItem item) 43 | { 44 | return new List { ImageType.Primary }; 45 | } 46 | 47 | public async Task> GetImages(RemoteImageFetchOptions options, CancellationToken cancellationToken) 48 | { 49 | BaseItem item = options.Item; 50 | List list = new List(); 51 | Season val = (Season)item; 52 | ProviderIdDictionary providerIds = val.Series.ProviderIds; 53 | providerIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out var value); 54 | if (!string.IsNullOrWhiteSpace(value) && val.IndexNumber.HasValue) 55 | { 56 | try 57 | { 58 | MovieDbSeasonProvider.SeasonRootObject seasonInfo = await _seasonProvider.EnsureSeasonInfo(value, val.IndexNumber.Value, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 59 | TmdbSettingsResult tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 60 | string tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); 61 | list.AddRange(from i in GetPosters(seasonInfo?.images) 62 | select new RemoteImageInfo 63 | { 64 | Url = tmdbImageUrl + i.file_path, 65 | ThumbnailUrl = tmdbSettings.images.GetPosterThumbnailImageUrl(i.file_path), 66 | CommunityRating = i.vote_average, 67 | VoteCount = i.vote_count, 68 | Width = i.width, 69 | Height = i.height, 70 | Language = MovieDbImageProvider.NormalizeImageLanguage(i.iso_639_1), 71 | ProviderName = base.Name, 72 | Type = ImageType.Primary, 73 | RatingType = RatingType.Score 74 | }); 75 | } 76 | catch (HttpException) 77 | { 78 | } 79 | } 80 | return list; 81 | } 82 | 83 | public Task> GetImages(BaseItem item, LibraryOptions libraryOptions, CancellationToken cancellationToken) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /MovieDb/MovieDbSeasonProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Net; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using MediaBrowser.Common.Net; 9 | using MediaBrowser.Controller; 10 | using MediaBrowser.Controller.Configuration; 11 | using MediaBrowser.Controller.Entities.TV; 12 | using MediaBrowser.Controller.Library; 13 | using MediaBrowser.Controller.Providers; 14 | using MediaBrowser.Model.Configuration; 15 | using MediaBrowser.Model.Entities; 16 | using MediaBrowser.Model.Globalization; 17 | using MediaBrowser.Model.IO; 18 | using MediaBrowser.Model.Logging; 19 | using MediaBrowser.Model.Net; 20 | using MediaBrowser.Model.Providers; 21 | using MediaBrowser.Model.Serialization; 22 | 23 | namespace MovieDb; 24 | 25 | public class MovieDbSeasonProvider : MovieDbProviderBase, IRemoteMetadataProviderWithOptions, IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider, IHasMetadataFeatures 26 | { 27 | public class Episode 28 | { 29 | public string air_date { get; set; } 30 | 31 | public int episode_number { get; set; } 32 | 33 | public int id { get; set; } 34 | 35 | public string name { get; set; } 36 | 37 | public string overview { get; set; } 38 | 39 | public string still_path { get; set; } 40 | 41 | public double vote_average { get; set; } 42 | 43 | public int vote_count { get; set; } 44 | } 45 | 46 | public class Videos 47 | { 48 | public List results { get; set; } 49 | } 50 | 51 | public class SeasonRootObject 52 | { 53 | public DateTimeOffset air_date { get; set; } 54 | 55 | public List episodes { get; set; } 56 | 57 | public string name { get; set; } 58 | 59 | public string overview { get; set; } 60 | 61 | public int id { get; set; } 62 | 63 | public string poster_path { get; set; } 64 | 65 | public int season_number { get; set; } 66 | 67 | public TmdbCredits credits { get; set; } 68 | 69 | public TmdbImages images { get; set; } 70 | 71 | public TmdbExternalIds external_ids { get; set; } 72 | 73 | public Videos videos { get; set; } 74 | } 75 | 76 | private const string TvSeasonPath = "3/tv/{0}/season/{1}"; 77 | 78 | private const string AppendToResponse = "images,keywords,external_ids,credits,videos"; 79 | 80 | public MetadataFeatures[] Features => new MetadataFeatures[1] { MetadataFeatures.Adult }; 81 | 82 | public MovieDbSeasonProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogManager logManager, ILocalizationManager localization, IServerApplicationHost appHost, ILibraryManager libraryManager) 83 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 84 | { 85 | } 86 | 87 | public async Task> GetMetadata(RemoteMetadataFetchOptions options, CancellationToken cancellationToken) 88 | { 89 | SeasonInfo info = options.SearchInfo; 90 | MetadataResult result = new MetadataResult(); 91 | ProviderIdDictionary seriesProviderIds = info.SeriesProviderIds; 92 | seriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out var seriesTmdbId); 93 | int? seasonNumber = info.IndexNumber; 94 | if (!string.IsNullOrWhiteSpace(seriesTmdbId) && seasonNumber.HasValue) 95 | { 96 | ItemLookupInfo searchInfo = info; 97 | string[] movieDbMetadataLanguages = GetMovieDbMetadataLanguages(searchInfo, await GetTmdbLanguages(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); 98 | try 99 | { 100 | bool isFirstLanguage = true; 101 | string[] array = movieDbMetadataLanguages; 102 | string[] array2 = array; 103 | for (int i = 0; i < array2.Length; i++) 104 | { 105 | SeasonRootObject seasonRootObject = await EnsureSeasonInfo(language: array2[i], tmdbId: seriesTmdbId, seasonNumber: seasonNumber.Value, cancellationToken: cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 106 | if (seasonRootObject != null) 107 | { 108 | result.HasMetadata = true; 109 | if (result.Item == null) 110 | { 111 | result.Item = new Season(); 112 | } 113 | ImportData(result.Item, seasonRootObject, info.Name, seasonNumber.Value, isFirstLanguage); 114 | isFirstLanguage = false; 115 | if (IsComplete(result.Item)) 116 | { 117 | return result; 118 | } 119 | } 120 | } 121 | } 122 | catch (HttpException ex) 123 | { 124 | HttpException val2 = ex; 125 | HttpException val3 = val2; 126 | if (val3.StatusCode.HasValue && val3.StatusCode.Value == HttpStatusCode.NotFound) 127 | { 128 | return result; 129 | } 130 | throw; 131 | } 132 | } 133 | return result; 134 | } 135 | 136 | private bool IsComplete(Season item) 137 | { 138 | if (string.IsNullOrEmpty(item.Name)) 139 | { 140 | return false; 141 | } 142 | if (string.IsNullOrEmpty(item.Overview)) 143 | { 144 | return false; 145 | } 146 | return true; 147 | } 148 | 149 | private void ImportData(Season item, SeasonRootObject seasonInfo, string name, int seasonNumber, bool isFirstLanguage) 150 | { 151 | if (string.IsNullOrEmpty(item.Name)) 152 | { 153 | item.Name = seasonInfo.name; 154 | } 155 | if (string.IsNullOrEmpty(item.Overview)) 156 | { 157 | item.Overview = seasonInfo.overview; 158 | } 159 | if (isFirstLanguage) 160 | { 161 | item.IndexNumber = seasonNumber; 162 | if (seasonInfo.external_ids.tvdb_id > 0) 163 | { 164 | item.SetProviderId(MetadataProviders.Tvdb, seasonInfo.external_ids.tvdb_id.Value.ToString(CultureInfo.InvariantCulture)); 165 | } 166 | TmdbCredits credits = seasonInfo.credits; 167 | if (credits != null) 168 | { 169 | _ = credits.cast; 170 | _ = credits.crew; 171 | } 172 | item.PremiereDate = seasonInfo.air_date; 173 | item.ProductionYear = item.PremiereDate.Value.Year; 174 | } 175 | } 176 | 177 | public Task> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) 178 | { 179 | throw new NotImplementedException(); 180 | } 181 | 182 | public Task> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken) 183 | { 184 | return Task.FromResult((IEnumerable)new List()); 185 | } 186 | 187 | internal async Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken) 188 | { 189 | if (string.IsNullOrEmpty(tmdbId)) 190 | { 191 | throw new ArgumentNullException("tmdbId"); 192 | } 193 | string path = GetDataFilePath(tmdbId, seasonNumber, language); 194 | FileSystemMetadata fileSystemInfo = FileSystem.GetFileSystemInfo(path); 195 | SeasonRootObject seasonRootObject = null; 196 | if (fileSystemInfo.Exists && DateTimeOffset.UtcNow - FileSystem.GetLastWriteTimeUtc(fileSystemInfo) <= MovieDbProviderBase.CacheTime) 197 | { 198 | seasonRootObject = await JsonSerializer.DeserializeFromFileAsync(fileSystemInfo.FullName).ConfigureAwait(continueOnCapturedContext: false); 199 | } 200 | if (seasonRootObject == null) 201 | { 202 | seasonRootObject = await FetchMainResult(tmdbId, seasonNumber, language, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 203 | FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); 204 | JsonSerializer.SerializeToFile(seasonRootObject, path); 205 | } 206 | return seasonRootObject; 207 | } 208 | 209 | internal string GetDataFilePath(string tmdbId, int seasonNumber, string preferredLanguage) 210 | { 211 | if (string.IsNullOrEmpty(tmdbId)) 212 | { 213 | throw new ArgumentNullException("tmdbId"); 214 | } 215 | string text = "season-" + seasonNumber.ToString(CultureInfo.InvariantCulture); 216 | if (!string.IsNullOrEmpty(preferredLanguage)) 217 | { 218 | text = text + "-" + preferredLanguage; 219 | } 220 | text += ".json"; 221 | return Path.Combine(MovieDbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tmdbId), text); 222 | } 223 | 224 | private async Task FetchMainResult(string id, int seasonNumber, string language, CancellationToken cancellationToken) 225 | { 226 | GetConfiguration(); 227 | string path = $"3/tv/{id}/season/{seasonNumber.ToString(CultureInfo.InvariantCulture)}"; 228 | string url = GetApiUrl(path) + "&append_to_response=images,keywords,external_ids,credits,videos"; 229 | if (!string.IsNullOrEmpty(language)) 230 | { 231 | url = url + "&language=" + language; 232 | } 233 | url = AddImageLanguageParam(url, language); 234 | cancellationToken.ThrowIfCancellationRequested(); 235 | using HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions 236 | { 237 | Url = url, 238 | CancellationToken = cancellationToken, 239 | AcceptHeader = MovieDbProviderBase.AcceptHeader 240 | }).ConfigureAwait(continueOnCapturedContext: false); 241 | using Stream json = response.Content; 242 | return await JsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(continueOnCapturedContext: false); 243 | } 244 | 245 | private new string GetApiUrl(string path) 246 | { 247 | PluginOptions config = GetConfiguration(); 248 | string baseUrl = config.TmdbApiBaseUrl.TrimEnd(new char[1] { '/' }); 249 | return baseUrl + "/" + path + "?api_key=" + config.ApiKey; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /MovieDb/MovieDbSeriesExternalId.cs: -------------------------------------------------------------------------------- 1 | using MediaBrowser.Controller.Entities.TV; 2 | using MediaBrowser.Controller.Providers; 3 | using MediaBrowser.Model.Entities; 4 | 5 | namespace MovieDb; 6 | 7 | public class MovieDbSeriesExternalId : IExternalId 8 | { 9 | public string Name => Plugin.StaticName; 10 | 11 | public string Key => MetadataProviders.Tmdb.ToString(); 12 | 13 | public string UrlFormatString 14 | { 15 | get 16 | { 17 | var config = Plugin.Instance?.Configuration; 18 | if (config != null) 19 | { 20 | return $"{config.TmdbHomeUrl}/tv/{{0}}"; 21 | } 22 | return "https://www.themoviedb.org/tv/{0}"; 23 | } 24 | } 25 | 26 | public bool Supports(IHasProviderIds item) 27 | { 28 | return item is Series; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MovieDb/MovieDbSeriesImageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MediaBrowser.Common.Net; 7 | using MediaBrowser.Controller; 8 | using MediaBrowser.Controller.Configuration; 9 | using MediaBrowser.Controller.Entities; 10 | using MediaBrowser.Controller.Entities.TV; 11 | using MediaBrowser.Controller.Library; 12 | using MediaBrowser.Controller.Providers; 13 | using MediaBrowser.Model.Configuration; 14 | using MediaBrowser.Model.Dto; 15 | using MediaBrowser.Model.Entities; 16 | using MediaBrowser.Model.Globalization; 17 | using MediaBrowser.Model.IO; 18 | using MediaBrowser.Model.Logging; 19 | using MediaBrowser.Model.Providers; 20 | using MediaBrowser.Model.Serialization; 21 | 22 | namespace MovieDb; 23 | 24 | public class MovieDbSeriesImageProvider : MovieDbProviderBase, IRemoteImageProviderWithOptions, IRemoteImageProvider, IImageProvider, IHasOrder 25 | { 26 | public int Order => 2; 27 | 28 | public MovieDbSeriesImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 29 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 30 | { 31 | } 32 | 33 | public bool Supports(BaseItem item) 34 | { 35 | return item is Series; 36 | } 37 | 38 | public IEnumerable GetSupportedImages(BaseItem item) 39 | { 40 | return new List 41 | { 42 | ImageType.Primary, 43 | ImageType.Backdrop, 44 | ImageType.Logo 45 | }; 46 | } 47 | 48 | public async Task> GetImages(RemoteImageFetchOptions options, CancellationToken cancellationToken) 49 | { 50 | List list = new List(); 51 | BaseItem item = options.Item; 52 | TmdbImages results = await FetchImages(item, JsonSerializer, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 53 | if (results == null) 54 | { 55 | return list; 56 | } 57 | TmdbSettingsResult tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); 58 | string tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); 59 | list.AddRange(from i in GetPosters(results) 60 | select new RemoteImageInfo 61 | { 62 | Url = tmdbImageUrl + i.file_path, 63 | ThumbnailUrl = tmdbSettings.images.GetPosterThumbnailImageUrl(i.file_path), 64 | CommunityRating = i.vote_average, 65 | VoteCount = i.vote_count, 66 | Width = i.width, 67 | Height = i.height, 68 | Language = MovieDbImageProvider.NormalizeImageLanguage(i.iso_639_1), 69 | ProviderName = base.Name, 70 | Type = ImageType.Primary, 71 | RatingType = RatingType.Score 72 | }); 73 | list.AddRange(from i in GetLogos(results) 74 | select new RemoteImageInfo 75 | { 76 | Url = tmdbImageUrl + i.file_path, 77 | ThumbnailUrl = tmdbSettings.images.GetLogoThumbnailImageUrl(i.file_path), 78 | CommunityRating = i.vote_average, 79 | VoteCount = i.vote_count, 80 | Width = i.width, 81 | Height = i.height, 82 | Language = MovieDbImageProvider.NormalizeImageLanguage(i.iso_639_1), 83 | ProviderName = base.Name, 84 | Type = ImageType.Logo, 85 | RatingType = RatingType.Score 86 | }); 87 | IEnumerable collection = from i in GetBackdrops(results) 88 | where string.IsNullOrEmpty(i.iso_639_1) 89 | select new RemoteImageInfo 90 | { 91 | Url = tmdbImageUrl + i.file_path, 92 | ThumbnailUrl = tmdbSettings.images.GetBackdropThumbnailImageUrl(i.file_path), 93 | CommunityRating = i.vote_average, 94 | VoteCount = i.vote_count, 95 | Width = i.width, 96 | Height = i.height, 97 | ProviderName = base.Name, 98 | Type = ImageType.Backdrop, 99 | RatingType = RatingType.Score 100 | }; 101 | list.AddRange(collection); 102 | return list; 103 | } 104 | 105 | public Task> GetImages(BaseItem item, LibraryOptions libraryOptions, CancellationToken cancellationToken) 106 | { 107 | throw new NotImplementedException(); 108 | } 109 | 110 | private async Task FetchImages(BaseItem item, IJsonSerializer jsonSerializer, CancellationToken cancellationToken) 111 | { 112 | string providerId = item.GetProviderId(MetadataProviders.Tmdb); 113 | if (string.IsNullOrEmpty(providerId)) 114 | { 115 | return null; 116 | } 117 | return (await MovieDbSeriesProvider.Current.EnsureSeriesInfo(providerId, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))?.images; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /MovieDb/MovieDbTrailerProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediaBrowser.Common.Net; 5 | using MediaBrowser.Controller; 6 | using MediaBrowser.Controller.Configuration; 7 | using MediaBrowser.Controller.Entities; 8 | using MediaBrowser.Controller.Library; 9 | using MediaBrowser.Controller.Providers; 10 | using MediaBrowser.Model.Configuration; 11 | using MediaBrowser.Model.Globalization; 12 | using MediaBrowser.Model.IO; 13 | using MediaBrowser.Model.Logging; 14 | using MediaBrowser.Model.Providers; 15 | using MediaBrowser.Model.Serialization; 16 | 17 | namespace MovieDb; 18 | 19 | public class MovieDbTrailerProvider : MovieDbProviderBase, IHasOrder, IRemoteMetadataProvider, IMetadataProvider, IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider, IRemoteSearchProvider, IHasMetadataFeatures 20 | { 21 | public int Order => 0; 22 | 23 | public MetadataFeatures[] Features => new MetadataFeatures[2] 24 | { 25 | MetadataFeatures.Adult, 26 | MetadataFeatures.Collections 27 | }; 28 | 29 | public MovieDbTrailerProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogManager logManager, IServerApplicationHost appHost, ILibraryManager libraryManager) 30 | : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager, appHost, libraryManager) 31 | { 32 | } 33 | 34 | public Task> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken) 35 | { 36 | return MovieDbProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken); 37 | } 38 | 39 | public Task> GetMetadata(TrailerInfo info, CancellationToken cancellationToken) 40 | { 41 | return MovieDbProvider.Current.GetItemMetadata(info, cancellationToken); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MovieDb/Plugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using MediaBrowser.Common; 4 | using MediaBrowser.Common.Plugins; 5 | using MediaBrowser.Controller.Plugins; 6 | using MediaBrowser.Model.Drawing; 7 | using MediaBrowser.Model.Logging; 8 | using MediaBrowser.Model.Plugins; 9 | 10 | namespace MovieDb; 11 | 12 | public class Plugin : BasePluginSimpleUI, IHasThumbImage 13 | { 14 | private readonly Guid _id = new Guid("3A63A9F3-810E-44F6-910A-14D6AD1255EC"); 15 | 16 | private readonly ILogger _logger; 17 | 18 | private const string ErrorMessage = "MovieDb插件出错: "; 19 | 20 | public static Plugin Instance { get; private set; } 21 | 22 | public static string StaticName => "MovieDb"; 23 | 24 | public override string Name => StaticName; 25 | 26 | public override string Description => "Emby官方MovieDb电影元数据插件改版,支持配置URL和API密钥配置"; 27 | 28 | public ImageFormat ThumbImageFormat => ImageFormat.Png; 29 | 30 | public PluginOptions Configuration 31 | { 32 | get 33 | { 34 | return GetOptions(); 35 | } 36 | set 37 | { 38 | if (value != null) 39 | { 40 | SaveOptions(value); 41 | } 42 | } 43 | } 44 | 45 | public override Guid Id => _id; 46 | 47 | public Plugin(IApplicationHost applicationHost, ILogManager logManager) 48 | : base(applicationHost) 49 | { 50 | Instance = this; 51 | _logger = logManager.GetLogger(Name); 52 | if (Configuration == null) 53 | { 54 | Configuration = new PluginOptions 55 | { 56 | TmdbApiBaseUrl = "https://api.themoviedb.org", 57 | TmdbImageBaseUrl = "https://image.tmdb.org/t/p/", 58 | TmdbHomeUrl = "https://www.themoviedb.org", 59 | ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669" 60 | }; 61 | } 62 | _logger.Info("TMDB Plugin (" + Name + ") 正在初始化"); 63 | } 64 | 65 | protected override void OnOptionsSaved(PluginOptions options) 66 | { 67 | try 68 | { 69 | _logger.Info("TMDB Plugin (" + Name + ") 配置已保存"); 70 | base.OnOptionsSaved(options); 71 | } 72 | catch (Exception ex) 73 | { 74 | _logger.Error("TMDB插件出错: 配置保存失败: " + ex.Message); 75 | } 76 | } 77 | 78 | protected override void OnCreatePageInfo(PluginPageInfo pageInfo) 79 | { 80 | try 81 | { 82 | pageInfo.Name = "TMDB 配置"; 83 | pageInfo.EnableInMainMenu = true; 84 | base.OnCreatePageInfo(pageInfo); 85 | } 86 | catch (Exception ex) 87 | { 88 | _logger.Error("TMDB插件出错: 创建页面信息失败: " + ex.Message); 89 | } 90 | } 91 | 92 | public Stream GetThumbImage() 93 | { 94 | Type type = GetType(); 95 | return type.Assembly.GetManifestResourceStream(type.Namespace + ".thumb.png"); 96 | } 97 | 98 | protected override PluginOptions OnBeforeShowUI(PluginOptions options) 99 | { 100 | try 101 | { 102 | if (options == null) 103 | { 104 | options = new PluginOptions 105 | { 106 | TmdbApiBaseUrl = "https://api.themoviedb.org", 107 | TmdbImageBaseUrl = "https://image.tmdb.org/t/p/", 108 | ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669" 109 | }; 110 | } 111 | return base.OnBeforeShowUI(options); 112 | } 113 | catch (Exception ex) 114 | { 115 | _logger.Error("TMDB插件出错: 准备UI配置失败: " + ex.Message); 116 | return options; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /MovieDb/PluginOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Emby.Web.GenericEdit; 4 | 5 | namespace MovieDb; 6 | 7 | public class PluginOptions : EditableOptionsBase 8 | { 9 | private const string DefaultApiBaseUrl = "https://api.themoviedb.org/3"; 10 | 11 | private const string DefaultImageBaseUrl = "https://image.tmdb.org/t/p"; 12 | 13 | private const string DefaultHomeUrl = "https://www.themoviedb.org"; 14 | 15 | private const string DefaultApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; 16 | 17 | public override string EditorTitle => "TMDB 插件配置"; 18 | 19 | [DisplayName("TMDB API 基础URL")] 20 | [Description("TMDB API 的基础 URL 地址,默认:https://api.themoviedb.org")] 21 | public string TmdbApiBaseUrl { get; set; } = "https://api.themoviedb.org"; 22 | 23 | 24 | [DisplayName("TMDB 图片基础URL")] 25 | [Description("TMDB 图片服务的基础 URL 地址,默认:https://image.tmdb.org/t/p/ (确保结尾`/t/p/`均保留)")] 26 | public string TmdbImageBaseUrl { get; set; } = "https://image.tmdb.org/t/p/"; 27 | 28 | 29 | [DisplayName("TMDB 主页URL")] 30 | [Description("TMDB 网站的主页地址,默认:https://www.themoviedb.org")] 31 | public string TmdbHomeUrl { get; set; } = "https://www.themoviedb.org"; 32 | 33 | 34 | [DisplayName("API Key")] 35 | [Description("TMDB API 密钥,如不填写则使用Emby官方插件默认值")] 36 | public string ApiKey { get; set; } 37 | 38 | // 添加一个内部方法来获取实际使用的API Key 39 | public string GetEffectiveApiKey() 40 | { 41 | return string.IsNullOrWhiteSpace(ApiKey) 42 | ? "f6bd687ffa63cd282b6ff2c6877f2669" // 默认值 43 | : ApiKey; 44 | } 45 | 46 | public string GetImageUrl(string size) 47 | { 48 | if (string.IsNullOrWhiteSpace(size)) 49 | { 50 | throw new ArgumentNullException("size"); 51 | } 52 | return TmdbImageBaseUrl?.TrimEnd(new char[1] { '/' }) + "/" + size; 53 | } 54 | 55 | public new bool Validate() 56 | { 57 | if (string.IsNullOrWhiteSpace(TmdbApiBaseUrl) || string.IsNullOrWhiteSpace(TmdbImageBaseUrl) || string.IsNullOrWhiteSpace(TmdbHomeUrl)) 58 | { 59 | return false; 60 | } 61 | Uri result; 62 | return Uri.TryCreate(TmdbApiBaseUrl, UriKind.Absolute, out result) && Uri.TryCreate(TmdbImageBaseUrl, UriKind.Absolute, out result) && Uri.TryCreate(TmdbHomeUrl, UriKind.Absolute, out result); 63 | } 64 | 65 | public void ResetToDefaults() 66 | { 67 | TmdbApiBaseUrl = "https://api.themoviedb.org"; 68 | TmdbImageBaseUrl = "https://image.tmdb.org/t/p"; 69 | TmdbHomeUrl = "https://www.themoviedb.org"; 70 | ApiKey = string.Empty; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MovieDb/TmdbAlternativeTitles.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbAlternativeTitles 6 | { 7 | public List results { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /MovieDb/TmdbCast.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbCast 4 | { 5 | public bool adult { get; set; } 6 | 7 | public int gender { get; set; } 8 | 9 | public int id { get; set; } 10 | 11 | public string known_for_department { get; set; } 12 | 13 | public string name { get; set; } 14 | 15 | public string original_name { get; set; } 16 | 17 | public double popularity { get; set; } 18 | 19 | public string profile_path { get; set; } 20 | 21 | public int cast_id { get; set; } 22 | 23 | public string character { get; set; } 24 | 25 | public string credit_id { get; set; } 26 | 27 | public int order { get; set; } 28 | } 29 | -------------------------------------------------------------------------------- /MovieDb/TmdbCredits.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbCredits 6 | { 7 | public List cast { get; set; } 8 | 9 | public List crew { get; set; } 10 | 11 | public List guest_stars { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /MovieDb/TmdbCrew.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbCrew 4 | { 5 | public bool adult { get; set; } 6 | 7 | public int gender { get; set; } 8 | 9 | public int id { get; set; } 10 | 11 | public string known_for_department { get; set; } 12 | 13 | public string name { get; set; } 14 | 15 | public string original_name { get; set; } 16 | 17 | public double popularity { get; set; } 18 | 19 | public string profile_path { get; set; } 20 | 21 | public string credit_id { get; set; } 22 | 23 | public string department { get; set; } 24 | 25 | public string job { get; set; } 26 | } 27 | -------------------------------------------------------------------------------- /MovieDb/TmdbExternalIds.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbExternalIds 4 | { 5 | public string imdb_id { get; set; } 6 | 7 | public string freebase_id { get; set; } 8 | 9 | public string freebase_mid { get; set; } 10 | 11 | public int? tvdb_id { get; set; } 12 | 13 | public int? tvrage_id { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /MovieDb/TmdbGenre.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbGenre 4 | { 5 | public int id { get; set; } 6 | 7 | public string name { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /MovieDb/TmdbImage.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbImage 4 | { 5 | public double aspect_ratio { get; set; } 6 | 7 | public string file_path { get; set; } 8 | 9 | public int height { get; set; } 10 | 11 | public string iso_639_1 { get; set; } 12 | 13 | public double vote_average { get; set; } 14 | 15 | public int vote_count { get; set; } 16 | 17 | public int width { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /MovieDb/TmdbImageSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace MovieDb; 5 | 6 | public class TmdbImageSettings 7 | { 8 | public string base_url { get; set; } 9 | 10 | public List backdrop_sizes { get; set; } 11 | 12 | public string secure_base_url { get; set; } 13 | 14 | public List poster_sizes { get; set; } 15 | 16 | public List profile_sizes { get; set; } 17 | 18 | public List logo_sizes { get; set; } 19 | 20 | public List still_sizes { get; set; } 21 | 22 | public string GetImageUrl(string image) 23 | { 24 | return secure_base_url + image; 25 | } 26 | 27 | public string GetOriginalImageUrl(string image) 28 | { 29 | return GetImageUrl("original") + image; 30 | } 31 | 32 | public string GetPosterThumbnailImageUrl(string image) 33 | { 34 | if (poster_sizes != null) 35 | { 36 | string text = poster_sizes.ElementAtOrDefault(1) ?? poster_sizes.FirstOrDefault(); 37 | if (!string.IsNullOrEmpty(text)) 38 | { 39 | return GetImageUrl(text) + image; 40 | } 41 | } 42 | return GetOriginalImageUrl(image); 43 | } 44 | 45 | public string GetStillThumbnailImageUrl(string image) 46 | { 47 | return GetOriginalImageUrl(image); 48 | } 49 | 50 | public string GetProfileThumbnailImageUrl(string image) 51 | { 52 | if (profile_sizes != null) 53 | { 54 | string text = profile_sizes.ElementAtOrDefault(1) ?? profile_sizes.FirstOrDefault(); 55 | if (!string.IsNullOrEmpty(text)) 56 | { 57 | return GetImageUrl(text) + image; 58 | } 59 | } 60 | return GetOriginalImageUrl(image); 61 | } 62 | 63 | public string GetLogoThumbnailImageUrl(string image) 64 | { 65 | if (logo_sizes != null) 66 | { 67 | string text = logo_sizes.ElementAtOrDefault(3) ?? logo_sizes.ElementAtOrDefault(2) ?? logo_sizes.ElementAtOrDefault(1) ?? logo_sizes.LastOrDefault(); 68 | if (!string.IsNullOrEmpty(text)) 69 | { 70 | return GetImageUrl(text) + image; 71 | } 72 | } 73 | return GetOriginalImageUrl(image); 74 | } 75 | 76 | public string GetBackdropThumbnailImageUrl(string image) 77 | { 78 | if (backdrop_sizes != null) 79 | { 80 | string text = backdrop_sizes.FirstOrDefault(); 81 | if (!string.IsNullOrEmpty(text)) 82 | { 83 | return GetImageUrl(text) + image; 84 | } 85 | } 86 | return GetOriginalImageUrl(image); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /MovieDb/TmdbImages.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbImages 6 | { 7 | public List posters { get; set; } 8 | 9 | public List backdrops { get; set; } 10 | 11 | public List logos { get; set; } 12 | 13 | public List stills { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /MovieDb/TmdbKeyword.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbKeyword 4 | { 5 | public int id { get; set; } 6 | 7 | public string name { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /MovieDb/TmdbKeywords.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbKeywords 6 | { 7 | public List keywords { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /MovieDb/TmdbLanguage.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbLanguage 4 | { 5 | public string iso_639_1 { get; set; } 6 | 7 | public string name { get; set; } 8 | 9 | public string english_name { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /MovieDb/TmdbReview.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbReview 6 | { 7 | public string author { get; set; } 8 | 9 | public string content { get; set; } 10 | 11 | public DateTime created_at { get; set; } 12 | 13 | public string id { get; set; } 14 | 15 | public DateTime updated_at { get; set; } 16 | 17 | public string url { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /MovieDb/TmdbReviews.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbReviews 6 | { 7 | public int page { get; set; } 8 | 9 | public List results { get; set; } 10 | 11 | public int total_pages { get; set; } 12 | 13 | public int total_results { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /MovieDb/TmdbSettingsResult.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbSettingsResult 4 | { 5 | public TmdbImageSettings images { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /MovieDb/TmdbTitle.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbTitle 4 | { 5 | public string iso_3166_1 { get; set; } 6 | 7 | public string title { get; set; } 8 | 9 | public string type { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /MovieDb/TmdbVideo.cs: -------------------------------------------------------------------------------- 1 | namespace MovieDb; 2 | 3 | public class TmdbVideo 4 | { 5 | public string id { get; set; } 6 | 7 | public string iso_639_1 { get; set; } 8 | 9 | public string iso_3166_1 { get; set; } 10 | 11 | public string key { get; set; } 12 | 13 | public string name { get; set; } 14 | 15 | public string site { get; set; } 16 | 17 | public string size { get; set; } 18 | 19 | public string type { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /MovieDb/TmdbVideos.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieDb; 4 | 5 | public class TmdbVideos 6 | { 7 | public List results { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyCompany("MovieDb")] 4 | [assembly: AssemblyConfiguration("Release")] 5 | [assembly: AssemblyFileVersion("2.1.0.0")] 6 | [assembly: AssemblyInformationalVersion("2.1.0+f0f3ad2fdd830dc08f268409a64a1b658da0575a")] 7 | [assembly: AssemblyProduct("MovieDb(modified)")] 8 | [assembly: AssemblyTitle("MovieDb(modified)")] 9 | [assembly: AssemblyVersion("2.1.0.0")] 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎬 Emby-TMDb-Plugin-configurable 2 | 3 | 这是一个基于Emby官方MovieDb插件反编译并修改的版本。主要目的是为了让网络连通性较差的Emby用户能够自定义TMDB的API地址和密钥,从而获得更好的使用体验。 4 | 5 | 开发版本main分支最早是基于飞牛OS自带Emby套件 4.9.28.0测试版,对前版本有不兼容现象。现已根据Emby 4.8.10版本依赖重构。 6 | 7 | 需要已经有,或者会搭建tmdb的代理服务器,或者通过CF搭建代理服务器,本项目才有使用的意义。 8 | ## ✨ 主要特性 9 | 10 | - 🔧 支持自定义TMDB API基础URL 11 | - 🖼️ 支持自定义TMDB图片服务器URL 12 | - 🏠 支持自定义TMDB主页URL 13 | - 🔑 支持自定义API密钥 14 | - ⭐ 保留了原版插件的所有功能 15 | 16 | ## ⚙️ 配置说明 17 | 18 | 插件提供以下配置项: 19 | 20 | 1. TMDB API 基础URL 21 | - 默认值: https://api.themoviedb.org 22 | - 可以配置为自己的代理地址 23 | 24 | 2. TMDB 图片基础URL 25 | - 默认值: https://image.tmdb.org/t/p 26 | - 可以配置为自己的图片代理地址 27 | 28 | 3. TMDB 主页URL 29 | - 默认值: https://www.themoviedb.org 30 | - 可以配置为自己的网页代理地址 31 | 32 | 4. API Key 33 | - 默认值为EMBY官方插件的API密钥 34 | - 可以配置为自己申请的TMDB API密钥 35 | 36 | ![image](https://github.com/user-attachments/assets/d7d07179-430e-4527-9011-47c1099f41bf) 37 | ![image](https://github.com/user-attachments/assets/24c167ac-ee54-41c4-8a08-50786f41d8d0) 38 | 39 | ## 📥 安装方法 40 | 41 | 1. 下载发布页面的最新版本插件 42 | 2. 将插件文件放入Emby的插件目录 43 | 3. 重启Emby服务 44 | 4. 在Emby管理页面的"插件"部分配置插件参数 45 | 46 | ### 📂 常见插件目录 47 | 48 | #### Docker 环境 49 | - 插件目录通常映射为: `/config/plugins/` 50 | - 请确保目录权限正确 51 | 52 | #### Windows 环境 53 | - 默认目录: `C:\ProgramData\Emby-Server\plugins\` 54 | - 便携版目录: `[Emby程序目录]\programdata\plugins\` 55 | 56 | #### Linux 环境 57 | - 默认目录: `/var/lib/emby/plugins/` 58 | - 自定义安装目录: `[emby-server目录]/plugins/` 59 | 60 | #### 群晖 NAS 61 | - 套件中心安装目录: `/volume1/@appstore/EmbyServer/plugins/` 62 | - Docker安装同Docker环境配置 63 | 64 | ## ⚠️ 注意事项 65 | 66 | - 本插件基于Emby官方MovieDb插件修改,仅添加了配置相关功能 67 | - 使用自定义地址时请确保地址的可用性 68 | - 建议在修改配置前先备份原配置 69 | - 确保插件目录具有正确的读写权限 70 | 71 | ## 📢 免责声明 72 | 73 | 本插件仅供学习交流使用,请遵守相关法律法规。使用本插件所造成的任何问题由使用者自行承担。 74 | 75 | ## 🙏 致谢 76 | 77 | 感谢EMBY官方开发团队提供的优秀插件基础。 78 | 79 | -------------------------------------------------------------------------------- /TheMovieDb.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 10.0 5 | false 6 | TheMovieDb 7 | 2.1.0.0 8 | 2.1.0.0 9 | 2.1.0.0 10 | 2.1.0 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <_Parameter1>System.Memory 27 | 28 | 29 | 30 | 31 | 32 | true 33 | true 34 | 35 | 36 | -------------------------------------------------------------------------------- /obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using System.Reflection; 4 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] 5 | -------------------------------------------------------------------------------- /obj/Debug/netstandard2.0/MovieDb.GeneratedMSBuildEditorConfig.editorconfig: -------------------------------------------------------------------------------- 1 | is_global = true 2 | build_property.RootNamespace = MovieDb 3 | build_property.ProjectDir = c:\Users\Sebas\Documents\GitHub\Emby-TMDb-Plugin-configurable\ 4 | build_property.EnableComHosting = 5 | build_property.EnableGeneratedComInterfaceComImportInterop = 6 | -------------------------------------------------------------------------------- /obj/Debug/netstandard2.0/MovieDb.assets.cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastian0619/Emby-TMDb-Plugin-configurable/149881054526682c159f63884984b48b7acd1bcf/obj/Debug/netstandard2.0/MovieDb.assets.cache -------------------------------------------------------------------------------- /obj/Debug/netstandard2.0/MovieDb.csproj.AssemblyReference.cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastian0619/Emby-TMDb-Plugin-configurable/149881054526682c159f63884984b48b7acd1bcf/obj/Debug/netstandard2.0/MovieDb.csproj.AssemblyReference.cache -------------------------------------------------------------------------------- /obj/Debug/netstandard2.0/MovieDb.csproj.FileListAbsolute.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastian0619/Emby-TMDb-Plugin-configurable/149881054526682c159f63884984b48b7acd1bcf/obj/Debug/netstandard2.0/MovieDb.csproj.FileListAbsolute.txt -------------------------------------------------------------------------------- /obj/MovieDb.csproj.nuget.dgspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": 1, 3 | "restore": { 4 | "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj": {} 5 | }, 6 | "projects": { 7 | "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj": { 8 | "version": "1.0.0", 9 | "restore": { 10 | "projectUniqueName": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj", 11 | "projectName": "MovieDb", 12 | "projectPath": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj", 13 | "packagesPath": "C:\\Users\\Sebas\\.nuget\\packages\\", 14 | "outputPath": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\obj\\", 15 | "projectStyle": "PackageReference", 16 | "fallbackFolders": [ 17 | "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" 18 | ], 19 | "configFilePaths": [ 20 | "C:\\Users\\Sebas\\AppData\\Roaming\\NuGet\\NuGet.Config", 21 | "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" 22 | ], 23 | "originalTargetFrameworks": [ 24 | "netstandard2.0" 25 | ], 26 | "sources": { 27 | "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, 28 | "https://api.nuget.org/v3/index.json": {} 29 | }, 30 | "frameworks": { 31 | "netstandard2.0": { 32 | "targetAlias": "netstandard2.0", 33 | "projectReferences": {} 34 | } 35 | }, 36 | "warningProperties": { 37 | "warnAsError": [ 38 | "NU1605" 39 | ] 40 | }, 41 | "restoreAuditProperties": { 42 | "enableAudit": "true", 43 | "auditLevel": "low", 44 | "auditMode": "direct" 45 | } 46 | }, 47 | "frameworks": { 48 | "netstandard2.0": { 49 | "targetAlias": "netstandard2.0", 50 | "dependencies": { 51 | "NETStandard.Library": { 52 | "suppressParent": "All", 53 | "target": "Package", 54 | "version": "[2.0.3, )", 55 | "autoReferenced": true 56 | }, 57 | "System.Memory": { 58 | "target": "Package", 59 | "version": "[4.6.0, )" 60 | }, 61 | "mediabrowser.server.core": { 62 | "target": "Package", 63 | "version": "[4.8.0.80, )" 64 | } 65 | }, 66 | "imports": [ 67 | "net461", 68 | "net462", 69 | "net47", 70 | "net471", 71 | "net472", 72 | "net48", 73 | "net481" 74 | ], 75 | "assetTargetFallback": true, 76 | "warn": true, 77 | "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.403\\RuntimeIdentifierGraph.json" 78 | } 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /obj/MovieDb.csproj.nuget.g.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | True 5 | NuGet 6 | $(MSBuildThisFileDirectory)project.assets.json 7 | $(UserProfile)\.nuget\packages\ 8 | C:\Users\Sebas\.nuget\packages\;C:\Program Files\dotnet\sdk\NuGetFallbackFolder 9 | PackageReference 10 | 6.11.1 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /obj/MovieDb.csproj.nuget.g.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /obj/project.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "targets": { 4 | ".NETStandard,Version=v2.0": { 5 | "MediaBrowser.Common/4.8.0.80": { 6 | "type": "package", 7 | "compile": { 8 | "lib/netstandard2.0/Emby.Media.Model.dll": { 9 | "related": ".pdb;.xml" 10 | }, 11 | "lib/netstandard2.0/Emby.Web.GenericEdit.dll": { 12 | "related": ".pdb;.xml" 13 | }, 14 | "lib/netstandard2.0/MediaBrowser.Common.dll": { 15 | "related": ".pdb;.xml" 16 | }, 17 | "lib/netstandard2.0/MediaBrowser.Model.dll": { 18 | "related": ".pdb;.xml" 19 | } 20 | }, 21 | "runtime": { 22 | "lib/netstandard2.0/Emby.Media.Model.dll": { 23 | "related": ".pdb;.xml" 24 | }, 25 | "lib/netstandard2.0/Emby.Web.GenericEdit.dll": { 26 | "related": ".pdb;.xml" 27 | }, 28 | "lib/netstandard2.0/MediaBrowser.Common.dll": { 29 | "related": ".pdb;.xml" 30 | }, 31 | "lib/netstandard2.0/MediaBrowser.Model.dll": { 32 | "related": ".pdb;.xml" 33 | } 34 | } 35 | }, 36 | "MediaBrowser.Server.Core/4.8.0.80": { 37 | "type": "package", 38 | "dependencies": { 39 | "MediaBrowser.Common": "4.8.0.80" 40 | }, 41 | "compile": { 42 | "lib/netstandard2.0/Emby.Naming.dll": { 43 | "related": ".pdb;.xml" 44 | }, 45 | "lib/netstandard2.0/MediaBrowser.Controller.dll": { 46 | "related": ".pdb;.xml" 47 | } 48 | }, 49 | "runtime": { 50 | "lib/netstandard2.0/Emby.Naming.dll": { 51 | "related": ".pdb;.xml" 52 | }, 53 | "lib/netstandard2.0/MediaBrowser.Controller.dll": { 54 | "related": ".pdb;.xml" 55 | } 56 | } 57 | }, 58 | "Microsoft.NETCore.Platforms/1.1.0": { 59 | "type": "package", 60 | "compile": { 61 | "lib/netstandard1.0/_._": {} 62 | }, 63 | "runtime": { 64 | "lib/netstandard1.0/_._": {} 65 | } 66 | }, 67 | "NETStandard.Library/2.0.3": { 68 | "type": "package", 69 | "dependencies": { 70 | "Microsoft.NETCore.Platforms": "1.1.0" 71 | }, 72 | "compile": { 73 | "lib/netstandard1.0/_._": {} 74 | }, 75 | "runtime": { 76 | "lib/netstandard1.0/_._": {} 77 | }, 78 | "build": { 79 | "build/netstandard2.0/NETStandard.Library.targets": {} 80 | } 81 | }, 82 | "System.Buffers/4.6.0": { 83 | "type": "package", 84 | "compile": { 85 | "lib/netstandard2.0/System.Buffers.dll": { 86 | "related": ".xml" 87 | } 88 | }, 89 | "runtime": { 90 | "lib/netstandard2.0/System.Buffers.dll": { 91 | "related": ".xml" 92 | } 93 | } 94 | }, 95 | "System.Memory/4.6.0": { 96 | "type": "package", 97 | "dependencies": { 98 | "System.Buffers": "4.6.0", 99 | "System.Numerics.Vectors": "4.6.0", 100 | "System.Runtime.CompilerServices.Unsafe": "6.1.0" 101 | }, 102 | "compile": { 103 | "lib/netstandard2.0/System.Memory.dll": { 104 | "related": ".xml" 105 | } 106 | }, 107 | "runtime": { 108 | "lib/netstandard2.0/System.Memory.dll": { 109 | "related": ".xml" 110 | } 111 | } 112 | }, 113 | "System.Numerics.Vectors/4.6.0": { 114 | "type": "package", 115 | "compile": { 116 | "lib/netstandard2.0/System.Numerics.Vectors.dll": { 117 | "related": ".xml" 118 | } 119 | }, 120 | "runtime": { 121 | "lib/netstandard2.0/System.Numerics.Vectors.dll": { 122 | "related": ".xml" 123 | } 124 | } 125 | }, 126 | "System.Runtime.CompilerServices.Unsafe/6.1.0": { 127 | "type": "package", 128 | "compile": { 129 | "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": { 130 | "related": ".xml" 131 | } 132 | }, 133 | "runtime": { 134 | "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": { 135 | "related": ".xml" 136 | } 137 | } 138 | } 139 | } 140 | }, 141 | "libraries": { 142 | "MediaBrowser.Common/4.8.0.80": { 143 | "sha512": "XH8bgfaiLtdJ+zjJMRtpx+B1p0oL3k9ofcuVTDh+HBKxZRYXBiomZ27tOy6ZoKhWLho8uNtr1dJq8nU+hAMepw==", 144 | "type": "package", 145 | "path": "mediabrowser.common/4.8.0.80", 146 | "files": [ 147 | ".nupkg.metadata", 148 | ".signature.p7s", 149 | "lib/netstandard2.0/Emby.Media.Model.dll", 150 | "lib/netstandard2.0/Emby.Media.Model.pdb", 151 | "lib/netstandard2.0/Emby.Media.Model.xml", 152 | "lib/netstandard2.0/Emby.Web.GenericEdit.dll", 153 | "lib/netstandard2.0/Emby.Web.GenericEdit.pdb", 154 | "lib/netstandard2.0/Emby.Web.GenericEdit.xml", 155 | "lib/netstandard2.0/MediaBrowser.Common.dll", 156 | "lib/netstandard2.0/MediaBrowser.Common.pdb", 157 | "lib/netstandard2.0/MediaBrowser.Common.xml", 158 | "lib/netstandard2.0/MediaBrowser.Model.dll", 159 | "lib/netstandard2.0/MediaBrowser.Model.pdb", 160 | "lib/netstandard2.0/MediaBrowser.Model.xml", 161 | "mediabrowser.common.4.8.0.80.nupkg.sha512", 162 | "mediabrowser.common.nuspec" 163 | ] 164 | }, 165 | "MediaBrowser.Server.Core/4.8.0.80": { 166 | "sha512": "9ZZszM8pdG7IZkbKzQ9pcQs2N6fFKUA66QA28oFMDgY9SWVoX5Ng1fpSP+X0yhVF2Rn4HQFEycWd2epVjsCAqg==", 167 | "type": "package", 168 | "path": "mediabrowser.server.core/4.8.0.80", 169 | "files": [ 170 | ".nupkg.metadata", 171 | ".signature.p7s", 172 | "lib/netstandard2.0/Emby.Naming.dll", 173 | "lib/netstandard2.0/Emby.Naming.pdb", 174 | "lib/netstandard2.0/Emby.Naming.xml", 175 | "lib/netstandard2.0/MediaBrowser.Controller.dll", 176 | "lib/netstandard2.0/MediaBrowser.Controller.pdb", 177 | "lib/netstandard2.0/MediaBrowser.Controller.xml", 178 | "mediabrowser.server.core.4.8.0.80.nupkg.sha512", 179 | "mediabrowser.server.core.nuspec" 180 | ] 181 | }, 182 | "Microsoft.NETCore.Platforms/1.1.0": { 183 | "sha512": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", 184 | "type": "package", 185 | "path": "microsoft.netcore.platforms/1.1.0", 186 | "files": [ 187 | ".nupkg.metadata", 188 | ".signature.p7s", 189 | "ThirdPartyNotices.txt", 190 | "dotnet_library_license.txt", 191 | "lib/netstandard1.0/_._", 192 | "microsoft.netcore.platforms.1.1.0.nupkg.sha512", 193 | "microsoft.netcore.platforms.nuspec", 194 | "runtime.json" 195 | ] 196 | }, 197 | "NETStandard.Library/2.0.3": { 198 | "sha512": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 199 | "type": "package", 200 | "path": "netstandard.library/2.0.3", 201 | "files": [ 202 | ".nupkg.metadata", 203 | ".signature.p7s", 204 | "LICENSE.TXT", 205 | "THIRD-PARTY-NOTICES.TXT", 206 | "build/netstandard2.0/NETStandard.Library.targets", 207 | "build/netstandard2.0/ref/Microsoft.Win32.Primitives.dll", 208 | "build/netstandard2.0/ref/System.AppContext.dll", 209 | "build/netstandard2.0/ref/System.Collections.Concurrent.dll", 210 | "build/netstandard2.0/ref/System.Collections.NonGeneric.dll", 211 | "build/netstandard2.0/ref/System.Collections.Specialized.dll", 212 | "build/netstandard2.0/ref/System.Collections.dll", 213 | "build/netstandard2.0/ref/System.ComponentModel.Composition.dll", 214 | "build/netstandard2.0/ref/System.ComponentModel.EventBasedAsync.dll", 215 | "build/netstandard2.0/ref/System.ComponentModel.Primitives.dll", 216 | "build/netstandard2.0/ref/System.ComponentModel.TypeConverter.dll", 217 | "build/netstandard2.0/ref/System.ComponentModel.dll", 218 | "build/netstandard2.0/ref/System.Console.dll", 219 | "build/netstandard2.0/ref/System.Core.dll", 220 | "build/netstandard2.0/ref/System.Data.Common.dll", 221 | "build/netstandard2.0/ref/System.Data.dll", 222 | "build/netstandard2.0/ref/System.Diagnostics.Contracts.dll", 223 | "build/netstandard2.0/ref/System.Diagnostics.Debug.dll", 224 | "build/netstandard2.0/ref/System.Diagnostics.FileVersionInfo.dll", 225 | "build/netstandard2.0/ref/System.Diagnostics.Process.dll", 226 | "build/netstandard2.0/ref/System.Diagnostics.StackTrace.dll", 227 | "build/netstandard2.0/ref/System.Diagnostics.TextWriterTraceListener.dll", 228 | "build/netstandard2.0/ref/System.Diagnostics.Tools.dll", 229 | "build/netstandard2.0/ref/System.Diagnostics.TraceSource.dll", 230 | "build/netstandard2.0/ref/System.Diagnostics.Tracing.dll", 231 | "build/netstandard2.0/ref/System.Drawing.Primitives.dll", 232 | "build/netstandard2.0/ref/System.Drawing.dll", 233 | "build/netstandard2.0/ref/System.Dynamic.Runtime.dll", 234 | "build/netstandard2.0/ref/System.Globalization.Calendars.dll", 235 | "build/netstandard2.0/ref/System.Globalization.Extensions.dll", 236 | "build/netstandard2.0/ref/System.Globalization.dll", 237 | "build/netstandard2.0/ref/System.IO.Compression.FileSystem.dll", 238 | "build/netstandard2.0/ref/System.IO.Compression.ZipFile.dll", 239 | "build/netstandard2.0/ref/System.IO.Compression.dll", 240 | "build/netstandard2.0/ref/System.IO.FileSystem.DriveInfo.dll", 241 | "build/netstandard2.0/ref/System.IO.FileSystem.Primitives.dll", 242 | "build/netstandard2.0/ref/System.IO.FileSystem.Watcher.dll", 243 | "build/netstandard2.0/ref/System.IO.FileSystem.dll", 244 | "build/netstandard2.0/ref/System.IO.IsolatedStorage.dll", 245 | "build/netstandard2.0/ref/System.IO.MemoryMappedFiles.dll", 246 | "build/netstandard2.0/ref/System.IO.Pipes.dll", 247 | "build/netstandard2.0/ref/System.IO.UnmanagedMemoryStream.dll", 248 | "build/netstandard2.0/ref/System.IO.dll", 249 | "build/netstandard2.0/ref/System.Linq.Expressions.dll", 250 | "build/netstandard2.0/ref/System.Linq.Parallel.dll", 251 | "build/netstandard2.0/ref/System.Linq.Queryable.dll", 252 | "build/netstandard2.0/ref/System.Linq.dll", 253 | "build/netstandard2.0/ref/System.Net.Http.dll", 254 | "build/netstandard2.0/ref/System.Net.NameResolution.dll", 255 | "build/netstandard2.0/ref/System.Net.NetworkInformation.dll", 256 | "build/netstandard2.0/ref/System.Net.Ping.dll", 257 | "build/netstandard2.0/ref/System.Net.Primitives.dll", 258 | "build/netstandard2.0/ref/System.Net.Requests.dll", 259 | "build/netstandard2.0/ref/System.Net.Security.dll", 260 | "build/netstandard2.0/ref/System.Net.Sockets.dll", 261 | "build/netstandard2.0/ref/System.Net.WebHeaderCollection.dll", 262 | "build/netstandard2.0/ref/System.Net.WebSockets.Client.dll", 263 | "build/netstandard2.0/ref/System.Net.WebSockets.dll", 264 | "build/netstandard2.0/ref/System.Net.dll", 265 | "build/netstandard2.0/ref/System.Numerics.dll", 266 | "build/netstandard2.0/ref/System.ObjectModel.dll", 267 | "build/netstandard2.0/ref/System.Reflection.Extensions.dll", 268 | "build/netstandard2.0/ref/System.Reflection.Primitives.dll", 269 | "build/netstandard2.0/ref/System.Reflection.dll", 270 | "build/netstandard2.0/ref/System.Resources.Reader.dll", 271 | "build/netstandard2.0/ref/System.Resources.ResourceManager.dll", 272 | "build/netstandard2.0/ref/System.Resources.Writer.dll", 273 | "build/netstandard2.0/ref/System.Runtime.CompilerServices.VisualC.dll", 274 | "build/netstandard2.0/ref/System.Runtime.Extensions.dll", 275 | "build/netstandard2.0/ref/System.Runtime.Handles.dll", 276 | "build/netstandard2.0/ref/System.Runtime.InteropServices.RuntimeInformation.dll", 277 | "build/netstandard2.0/ref/System.Runtime.InteropServices.dll", 278 | "build/netstandard2.0/ref/System.Runtime.Numerics.dll", 279 | "build/netstandard2.0/ref/System.Runtime.Serialization.Formatters.dll", 280 | "build/netstandard2.0/ref/System.Runtime.Serialization.Json.dll", 281 | "build/netstandard2.0/ref/System.Runtime.Serialization.Primitives.dll", 282 | "build/netstandard2.0/ref/System.Runtime.Serialization.Xml.dll", 283 | "build/netstandard2.0/ref/System.Runtime.Serialization.dll", 284 | "build/netstandard2.0/ref/System.Runtime.dll", 285 | "build/netstandard2.0/ref/System.Security.Claims.dll", 286 | "build/netstandard2.0/ref/System.Security.Cryptography.Algorithms.dll", 287 | "build/netstandard2.0/ref/System.Security.Cryptography.Csp.dll", 288 | "build/netstandard2.0/ref/System.Security.Cryptography.Encoding.dll", 289 | "build/netstandard2.0/ref/System.Security.Cryptography.Primitives.dll", 290 | "build/netstandard2.0/ref/System.Security.Cryptography.X509Certificates.dll", 291 | "build/netstandard2.0/ref/System.Security.Principal.dll", 292 | "build/netstandard2.0/ref/System.Security.SecureString.dll", 293 | "build/netstandard2.0/ref/System.ServiceModel.Web.dll", 294 | "build/netstandard2.0/ref/System.Text.Encoding.Extensions.dll", 295 | "build/netstandard2.0/ref/System.Text.Encoding.dll", 296 | "build/netstandard2.0/ref/System.Text.RegularExpressions.dll", 297 | "build/netstandard2.0/ref/System.Threading.Overlapped.dll", 298 | "build/netstandard2.0/ref/System.Threading.Tasks.Parallel.dll", 299 | "build/netstandard2.0/ref/System.Threading.Tasks.dll", 300 | "build/netstandard2.0/ref/System.Threading.Thread.dll", 301 | "build/netstandard2.0/ref/System.Threading.ThreadPool.dll", 302 | "build/netstandard2.0/ref/System.Threading.Timer.dll", 303 | "build/netstandard2.0/ref/System.Threading.dll", 304 | "build/netstandard2.0/ref/System.Transactions.dll", 305 | "build/netstandard2.0/ref/System.ValueTuple.dll", 306 | "build/netstandard2.0/ref/System.Web.dll", 307 | "build/netstandard2.0/ref/System.Windows.dll", 308 | "build/netstandard2.0/ref/System.Xml.Linq.dll", 309 | "build/netstandard2.0/ref/System.Xml.ReaderWriter.dll", 310 | "build/netstandard2.0/ref/System.Xml.Serialization.dll", 311 | "build/netstandard2.0/ref/System.Xml.XDocument.dll", 312 | "build/netstandard2.0/ref/System.Xml.XPath.XDocument.dll", 313 | "build/netstandard2.0/ref/System.Xml.XPath.dll", 314 | "build/netstandard2.0/ref/System.Xml.XmlDocument.dll", 315 | "build/netstandard2.0/ref/System.Xml.XmlSerializer.dll", 316 | "build/netstandard2.0/ref/System.Xml.dll", 317 | "build/netstandard2.0/ref/System.dll", 318 | "build/netstandard2.0/ref/mscorlib.dll", 319 | "build/netstandard2.0/ref/netstandard.dll", 320 | "build/netstandard2.0/ref/netstandard.xml", 321 | "lib/netstandard1.0/_._", 322 | "netstandard.library.2.0.3.nupkg.sha512", 323 | "netstandard.library.nuspec" 324 | ] 325 | }, 326 | "System.Buffers/4.6.0": { 327 | "sha512": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA==", 328 | "type": "package", 329 | "path": "system.buffers/4.6.0", 330 | "files": [ 331 | ".nupkg.metadata", 332 | ".signature.p7s", 333 | "Icon.png", 334 | "PACKAGE.md", 335 | "buildTransitive/net461/System.Buffers.targets", 336 | "buildTransitive/net462/_._", 337 | "lib/net462/System.Buffers.dll", 338 | "lib/net462/System.Buffers.xml", 339 | "lib/netcoreapp2.1/_._", 340 | "lib/netstandard2.0/System.Buffers.dll", 341 | "lib/netstandard2.0/System.Buffers.xml", 342 | "system.buffers.4.6.0.nupkg.sha512", 343 | "system.buffers.nuspec" 344 | ] 345 | }, 346 | "System.Memory/4.6.0": { 347 | "sha512": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==", 348 | "type": "package", 349 | "path": "system.memory/4.6.0", 350 | "files": [ 351 | ".nupkg.metadata", 352 | ".signature.p7s", 353 | "Icon.png", 354 | "PACKAGE.md", 355 | "buildTransitive/net461/System.Memory.targets", 356 | "buildTransitive/net462/_._", 357 | "lib/net462/System.Memory.dll", 358 | "lib/net462/System.Memory.xml", 359 | "lib/netcoreapp2.1/_._", 360 | "lib/netstandard2.0/System.Memory.dll", 361 | "lib/netstandard2.0/System.Memory.xml", 362 | "system.memory.4.6.0.nupkg.sha512", 363 | "system.memory.nuspec" 364 | ] 365 | }, 366 | "System.Numerics.Vectors/4.6.0": { 367 | "sha512": "t+SoieZsRuEyiw/J+qXUbolyO219tKQQI0+2/YI+Qv7YdGValA6WiuokrNKqjrTNsy5ABWU11bdKOzUdheteXg==", 368 | "type": "package", 369 | "path": "system.numerics.vectors/4.6.0", 370 | "files": [ 371 | ".nupkg.metadata", 372 | ".signature.p7s", 373 | "Icon.png", 374 | "PACKAGE.md", 375 | "buildTransitive/net461/System.Numerics.Vectors.targets", 376 | "buildTransitive/net462/_._", 377 | "lib/net462/System.Numerics.Vectors.dll", 378 | "lib/net462/System.Numerics.Vectors.xml", 379 | "lib/netcoreapp2.0/_._", 380 | "lib/netstandard2.0/System.Numerics.Vectors.dll", 381 | "lib/netstandard2.0/System.Numerics.Vectors.xml", 382 | "system.numerics.vectors.4.6.0.nupkg.sha512", 383 | "system.numerics.vectors.nuspec" 384 | ] 385 | }, 386 | "System.Runtime.CompilerServices.Unsafe/6.1.0": { 387 | "sha512": "5o/HZxx6RVqYlhKSq8/zronDkALJZUT2Vz0hx43f0gwe8mwlM0y2nYlqdBwLMzr262Bwvpikeb/yEwkAa5PADg==", 388 | "type": "package", 389 | "path": "system.runtime.compilerservices.unsafe/6.1.0", 390 | "files": [ 391 | ".nupkg.metadata", 392 | ".signature.p7s", 393 | "Icon.png", 394 | "PACKAGE.md", 395 | "buildTransitive/net461/System.Runtime.CompilerServices.Unsafe.targets", 396 | "buildTransitive/net462/_._", 397 | "buildTransitive/net6.0/_._", 398 | "buildTransitive/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.targets", 399 | "lib/net462/System.Runtime.CompilerServices.Unsafe.dll", 400 | "lib/net462/System.Runtime.CompilerServices.Unsafe.xml", 401 | "lib/net7.0/_._", 402 | "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", 403 | "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml", 404 | "system.runtime.compilerservices.unsafe.6.1.0.nupkg.sha512", 405 | "system.runtime.compilerservices.unsafe.nuspec" 406 | ] 407 | } 408 | }, 409 | "projectFileDependencyGroups": { 410 | ".NETStandard,Version=v2.0": [ 411 | "NETStandard.Library >= 2.0.3", 412 | "System.Memory >= 4.6.0", 413 | "mediabrowser.server.core >= 4.8.0.80" 414 | ] 415 | }, 416 | "packageFolders": { 417 | "C:\\Users\\Sebas\\.nuget\\packages\\": {}, 418 | "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder": {} 419 | }, 420 | "project": { 421 | "version": "1.0.0", 422 | "restore": { 423 | "projectUniqueName": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj", 424 | "projectName": "MovieDb", 425 | "projectPath": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj", 426 | "packagesPath": "C:\\Users\\Sebas\\.nuget\\packages\\", 427 | "outputPath": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\obj\\", 428 | "projectStyle": "PackageReference", 429 | "fallbackFolders": [ 430 | "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" 431 | ], 432 | "configFilePaths": [ 433 | "C:\\Users\\Sebas\\AppData\\Roaming\\NuGet\\NuGet.Config", 434 | "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" 435 | ], 436 | "originalTargetFrameworks": [ 437 | "netstandard2.0" 438 | ], 439 | "sources": { 440 | "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, 441 | "https://api.nuget.org/v3/index.json": {} 442 | }, 443 | "frameworks": { 444 | "netstandard2.0": { 445 | "targetAlias": "netstandard2.0", 446 | "projectReferences": {} 447 | } 448 | }, 449 | "warningProperties": { 450 | "warnAsError": [ 451 | "NU1605" 452 | ] 453 | }, 454 | "restoreAuditProperties": { 455 | "enableAudit": "true", 456 | "auditLevel": "low", 457 | "auditMode": "direct" 458 | } 459 | }, 460 | "frameworks": { 461 | "netstandard2.0": { 462 | "targetAlias": "netstandard2.0", 463 | "dependencies": { 464 | "NETStandard.Library": { 465 | "suppressParent": "All", 466 | "target": "Package", 467 | "version": "[2.0.3, )", 468 | "autoReferenced": true 469 | }, 470 | "System.Memory": { 471 | "target": "Package", 472 | "version": "[4.6.0, )" 473 | }, 474 | "mediabrowser.server.core": { 475 | "target": "Package", 476 | "version": "[4.8.0.80, )" 477 | } 478 | }, 479 | "imports": [ 480 | "net461", 481 | "net462", 482 | "net47", 483 | "net471", 484 | "net472", 485 | "net48", 486 | "net481" 487 | ], 488 | "assetTargetFallback": true, 489 | "warn": true, 490 | "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.403\\RuntimeIdentifierGraph.json" 491 | } 492 | } 493 | } 494 | } -------------------------------------------------------------------------------- /obj/project.nuget.cache: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dgSpecHash": "ePGt+/TKOis=", 4 | "success": true, 5 | "projectFilePath": "C:\\Users\\Sebas\\Documents\\GitHub\\Emby-TMDb-Plugin-configurable\\MovieDb.csproj", 6 | "expectedPackageFiles": [ 7 | "C:\\Users\\Sebas\\.nuget\\packages\\mediabrowser.common\\4.8.0.80\\mediabrowser.common.4.8.0.80.nupkg.sha512", 8 | "C:\\Users\\Sebas\\.nuget\\packages\\mediabrowser.server.core\\4.8.0.80\\mediabrowser.server.core.4.8.0.80.nupkg.sha512", 9 | "C:\\Users\\Sebas\\.nuget\\packages\\microsoft.netcore.platforms\\1.1.0\\microsoft.netcore.platforms.1.1.0.nupkg.sha512", 10 | "C:\\Users\\Sebas\\.nuget\\packages\\netstandard.library\\2.0.3\\netstandard.library.2.0.3.nupkg.sha512", 11 | "C:\\Users\\Sebas\\.nuget\\packages\\system.buffers\\4.6.0\\system.buffers.4.6.0.nupkg.sha512", 12 | "C:\\Users\\Sebas\\.nuget\\packages\\system.memory\\4.6.0\\system.memory.4.6.0.nupkg.sha512", 13 | "C:\\Users\\Sebas\\.nuget\\packages\\system.numerics.vectors\\4.6.0\\system.numerics.vectors.4.6.0.nupkg.sha512", 14 | "C:\\Users\\Sebas\\.nuget\\packages\\system.runtime.compilerservices.unsafe\\6.1.0\\system.runtime.compilerservices.unsafe.6.1.0.nupkg.sha512" 15 | ], 16 | "logs": [] 17 | } --------------------------------------------------------------------------------