[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# Build result!!!!!!!!!!!!!!!!\npkg/\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n**/Properties/launchSettings.json\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Typescript v1 declaration files\ntypings/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n"
  },
  {
    "path": "DataStucture.cs",
    "content": "﻿using System.Collections.Generic;\n#pragma warning disable 649 // Suppresses: ___ is never assigned to\n\n// ReSharper disable All\n\nnamespace MusicBeePlugin\n{\n    internal class SearchResult\n    {\n        public SearchResultResult result;\n        public int code;\n    }\n\n    internal class SearchResultResult\n    {\n        public int songCount;\n        public IEnumerable<SearchResultSong> songs;\n    }\n\n    internal class SearchResultSong\n    {\n        public string name;\n        public long id;\n        public long duration; // in ms\n        public SearchResultAlbum album;\n        public SearchResultArtist[] artists;\n    }\n\n    internal class SearchResultAlbum \n    {\n        public long id;\n        public string name;\n    }\n\n    internal class SearchResultArtist\n    {\n        public long id;\n        public string name;\n    }\n\n    internal class LyricResult\n    {\n        public LyricInner lrc;\n        public LyricInner tlyric;\n        public int code;\n    }\n\n    internal class LyricInner\n    {\n        public string lyric;\n    }\n}\n"
  },
  {
    "path": "FodyWeavers.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Weavers xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"FodyWeavers.xsd\">\n  <Costura DisableCompression='true' />\n</Weavers>"
  },
  {
    "path": "FodyWeavers.xsd",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n  <!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->\n  <xs:element name=\"Weavers\">\n    <xs:complexType>\n      <xs:all>\n        <xs:element name=\"Costura\" minOccurs=\"0\" maxOccurs=\"1\">\n          <xs:complexType>\n            <xs:all>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"ExcludeAssemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of assembly names to exclude from the default action of \"embed all Copy Local references\", delimited with line breaks</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"IncludeAssemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of assembly names to include from the default action of \"embed all Copy Local references\", delimited with line breaks.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"ExcludeRuntimeAssemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of runtime assembly names to exclude from the default action of \"embed all Copy Local references\", delimited with line breaks</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"IncludeRuntimeAssemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of runtime assembly names to include from the default action of \"embed all Copy Local references\", delimited with line breaks.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"Unmanaged32Assemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"UnmanagedWinX86Assemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"Unmanaged64Assemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"UnmanagedWinX64Assemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"UnmanagedWinArm64Assemblies\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n              <xs:element minOccurs=\"0\" maxOccurs=\"1\" name=\"PreloadOrder\" type=\"xs:string\">\n                <xs:annotation>\n                  <xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>\n                </xs:annotation>\n              </xs:element>\n            </xs:all>\n            <xs:attribute name=\"CreateTemporaryAssemblies\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"IncludeDebugSymbols\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"IncludeRuntimeReferences\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"UseRuntimeReferencePaths\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"DisableCompression\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"DisableCleanup\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"DisableEventSubscription\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"LoadAtModuleInit\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"IgnoreSatelliteAssemblies\" type=\"xs:boolean\">\n              <xs:annotation>\n                <xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"ExcludeAssemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of assembly names to exclude from the default action of \"embed all Copy Local references\", delimited with |</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"IncludeAssemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of assembly names to include from the default action of \"embed all Copy Local references\", delimited with |.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"ExcludeRuntimeAssemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of runtime assembly names to exclude from the default action of \"embed all Copy Local references\", delimited with |</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"IncludeRuntimeAssemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of runtime assembly names to include from the default action of \"embed all Copy Local references\", delimited with |.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"Unmanaged32Assemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"UnmanagedWinX86Assemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"Unmanaged64Assemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"UnmanagedWinX64Assemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"UnmanagedWinArm64Assemblies\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"PreloadOrder\" type=\"xs:string\">\n              <xs:annotation>\n                <xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>\n              </xs:annotation>\n            </xs:attribute>\n          </xs:complexType>\n        </xs:element>\n      </xs:all>\n      <xs:attribute name=\"VerifyAssembly\" type=\"xs:boolean\">\n        <xs:annotation>\n          <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>\n        </xs:annotation>\n      </xs:attribute>\n      <xs:attribute name=\"VerifyIgnoreCodes\" type=\"xs:string\">\n        <xs:annotation>\n          <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>\n        </xs:annotation>\n      </xs:attribute>\n      <xs:attribute name=\"GenerateXsd\" type=\"xs:boolean\">\n        <xs:annotation>\n          <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>\n        </xs:annotation>\n      </xs:attribute>\n    </xs:complexType>\n  </xs:element>\n</xs:schema>"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LICENSE-Zony",
    "content": "[Included due to adoption of code from ZonyLrcToolsX in the Netease API part.]\n\nMIT License\n\nCopyright (c) 2019 Zony\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "LyricProcessor.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing System.Text.RegularExpressions;\n\nnamespace MusicBeePlugin\n{\n    [SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n    internal static class LyricProcessor\n    {\n        private static readonly Regex LyricLineRegex = new Regex(@\"((\\[.+?])+)(.*)\", RegexOptions.Compiled);\n        public static string InjectTranslation(string originalLrc, string translationLrc)\n        {\n            var originalEntries = ExpandEntries(Parse(originalLrc));\n            var translationEntries = ExpandEntries(Parse(translationLrc));\n            foreach (var originalEntry in originalEntries)\n            {\n                var translationEntry = translationEntries.FirstOrDefault(entry => entry.timeLabel == originalEntry.timeLabel);\n                if (translationEntry != null)\n                    originalEntry.content += \"/\" + translationEntry.content;\n            }\n\n            originalEntries.Sort();\n            return string.Join(\"\\n\", originalEntries);\n        }\n\n        private static List<LyricEntry> Parse(string lrc)\n        {\n            var result = lrc.Split('\\n')\n                .Where(line => !string.IsNullOrWhiteSpace(line))\n                .Select(line => LyricLineRegex.Matches(line))\n                .Where(matches => matches.Count >= 1)\n                .Select(matches => matches[0])\n                .Where(match => match.Groups.Count >= 3)\n                .SelectMany(match => match.Groups[1].Captures.Cast<Capture>(),\n                    (match, capture) => new LyricEntry(capture.Value, match.Groups[3].Value))\n                .ToList();\n\n            return result;\n        }\n\n        private static List<LyricEntry> ExpandEntries(List<LyricEntry> entries)\n        {\n            return entries.SelectMany(entry => entry.ExpandTimeLabel()).ToList();\n        }\n    }\n\n    [SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n    internal class LyricEntry : IComparable<LyricEntry>\n    {\n        private static readonly Regex LyricTimeRegex = new Regex(@\"(\\[[0-9.:]*])\", RegexOptions.Compiled);\n\n        public string timeLabel;\n        public string content;\n\n        public LyricEntry(string timeLabel, string content)\n        {\n            this.timeLabel = timeLabel;\n            this.content = content;\n        }\n\n        public override string ToString()\n        {\n            return timeLabel + content;\n        }\n\n        public IEnumerable<LyricEntry> ExpandTimeLabel()\n        {\n            var matches = LyricTimeRegex.Matches(timeLabel);\n            foreach (var match in matches.Cast<Match>())\n                yield return new LyricEntry(match.Value, content);\n        }\n\n        public int CompareTo(LyricEntry other)\n        {\n            if (ReferenceEquals(this, other)) return 0;\n            if (ReferenceEquals(null, other)) return 1;\n            return string.Compare(timeLabel, other.timeLabel, StringComparison.Ordinal);\n        }\n    }\n}\n"
  },
  {
    "path": "MusicBeeInterface.cs",
    "content": "﻿// WARNING: This file is NOT a part of my work and it's from downloaded MusicBee API source!!!\n\nusing System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.InteropServices;\n\nnamespace MusicBeePlugin\n{\n    [SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n    public partial class Plugin\n    {\n        public const short PluginInfoVersion = 1;\n        public const short MinInterfaceVersion = 36;\n        public const short MinApiRevision = 48;\n\n        [StructLayout(LayoutKind.Sequential)]\n        public struct MusicBeeApiInterface\n        {\n            public void Initialise(IntPtr apiInterfacePtr)\n            {\n                CopyMemory(ref this, apiInterfacePtr, 4);\n                if (MusicBeeVersion == MusicBeeVersion.v2_0)\n                    // MusicBee version 2.0 - Api methods > revision 25 are not available\n                    CopyMemory(ref this, apiInterfacePtr, 456);\n                else if (MusicBeeVersion == MusicBeeVersion.v2_1)\n                    CopyMemory(ref this, apiInterfacePtr, 516);\n                else if (MusicBeeVersion == MusicBeeVersion.v2_2)\n                    CopyMemory(ref this, apiInterfacePtr, 584);\n                else if (MusicBeeVersion == MusicBeeVersion.v2_3)\n                    CopyMemory(ref this, apiInterfacePtr, 596);\n                else if (MusicBeeVersion == MusicBeeVersion.v2_4)\n                    CopyMemory(ref this, apiInterfacePtr, 604);\n                else if (MusicBeeVersion == MusicBeeVersion.v2_5)\n                    CopyMemory(ref this, apiInterfacePtr, 648);\n                else\n                    CopyMemory(ref this, apiInterfacePtr, Marshal.SizeOf(this));\n            }\n            public MusicBeeVersion MusicBeeVersion\n            {\n                get {\n                    if (ApiRevision <= 25)\n                        return MusicBeeVersion.v2_0;\n                    else if (ApiRevision <= 31)\n                        return MusicBeeVersion.v2_1;\n                    else if (ApiRevision <= 33)\n                        return MusicBeeVersion.v2_2;\n                    else if (ApiRevision <= 38)\n                        return MusicBeeVersion.v2_3;\n                    else if (ApiRevision <= 43)\n                        return MusicBeeVersion.v2_4;\n                    else if (ApiRevision <= 47)\n                        return MusicBeeVersion.v2_5;\n                    else\n                        return MusicBeeVersion.v3_0;\n                }\n            }\n            public short InterfaceVersion;\n            public short ApiRevision;\n            public MB_ReleaseStringDelegate MB_ReleaseString;\n            public MB_TraceDelegate MB_Trace;\n            public Setting_GetPersistentStoragePathDelegate Setting_GetPersistentStoragePath;\n            public Setting_GetSkinDelegate Setting_GetSkin;\n            public Setting_GetSkinElementColourDelegate Setting_GetSkinElementColour;\n            public Setting_IsWindowBordersSkinnedDelegate Setting_IsWindowBordersSkinned;\n            public Library_GetFilePropertyDelegate Library_GetFileProperty;\n            public Library_GetFileTagDelegate Library_GetFileTag;\n            public Library_SetFileTagDelegate Library_SetFileTag;\n            public Library_CommitTagsToFileDelegate Library_CommitTagsToFile;\n            public Library_GetLyricsDelegate Library_GetLyrics;\n            [Obsolete(\"Use Library_GetArtworkEx\")]\n            public Library_GetArtworkDelegate Library_GetArtwork;\n            public Library_QueryFilesDelegate Library_QueryFiles;\n            public Library_QueryGetNextFileDelegate Library_QueryGetNextFile;\n            public Player_GetPositionDelegate Player_GetPosition;\n            public Player_SetPositionDelegate Player_SetPosition;\n            public Player_GetPlayStateDelegate Player_GetPlayState;\n            public Player_ActionDelegate Player_PlayPause;\n            public Player_ActionDelegate Player_Stop;\n            public Player_ActionDelegate Player_StopAfterCurrent;\n            public Player_ActionDelegate Player_PlayPreviousTrack;\n            public Player_ActionDelegate Player_PlayNextTrack;\n            public Player_ActionDelegate Player_StartAutoDj;\n            public Player_ActionDelegate Player_EndAutoDj;\n            public Player_GetVolumeDelegate Player_GetVolume;\n            public Player_SetVolumeDelegate Player_SetVolume;\n            public Player_GetMuteDelegate Player_GetMute;\n            public Player_SetMuteDelegate Player_SetMute;\n            public Player_GetShuffleDelegate Player_GetShuffle;\n            public Player_SetShuffleDelegate Player_SetShuffle;\n            public Player_GetRepeatDelegate Player_GetRepeat;\n            public Player_SetRepeatDelegate Player_SetRepeat;\n            public Player_GetEqualiserEnabledDelegate Player_GetEqualiserEnabled;\n            public Player_SetEqualiserEnabledDelegate Player_SetEqualiserEnabled;\n            public Player_GetDspEnabledDelegate Player_GetDspEnabled;\n            public Player_SetDspEnabledDelegate Player_SetDspEnabled;\n            public Player_GetScrobbleEnabledDelegate Player_GetScrobbleEnabled;\n            public Player_SetScrobbleEnabledDelegate Player_SetScrobbleEnabled;\n            public NowPlaying_GetFileUrlDelegate NowPlaying_GetFileUrl;\n            public NowPlaying_GetDurationDelegate NowPlaying_GetDuration;\n            public NowPlaying_GetFilePropertyDelegate NowPlaying_GetFileProperty;\n            public NowPlaying_GetFileTagDelegate NowPlaying_GetFileTag;\n            public NowPlaying_GetLyricsDelegate NowPlaying_GetLyrics;\n            public NowPlaying_GetArtworkDelegate NowPlaying_GetArtwork;\n            public NowPlayingList_ActionDelegate NowPlayingList_Clear;\n            public Library_QueryFilesDelegate NowPlayingList_QueryFiles;\n            public Library_QueryGetNextFileDelegate NowPlayingList_QueryGetNextFile;\n            public NowPlayingList_FileActionDelegate NowPlayingList_PlayNow;\n            public NowPlayingList_FileActionDelegate NowPlayingList_QueueNext;\n            public NowPlayingList_FileActionDelegate NowPlayingList_QueueLast;\n            public NowPlayingList_ActionDelegate NowPlayingList_PlayLibraryShuffled;\n            public Playlist_QueryPlaylistsDelegate Playlist_QueryPlaylists;\n            public Playlist_QueryGetNextPlaylistDelegate Playlist_QueryGetNextPlaylist;\n            public Playlist_GetTypeDelegate Playlist_GetType;\n            public Playlist_QueryFilesDelegate Playlist_QueryFiles;\n            public Library_QueryGetNextFileDelegate Playlist_QueryGetNextFile;\n            public MB_WindowHandleDelegate MB_GetWindowHandle;\n            public MB_RefreshPanelsDelegate MB_RefreshPanels;\n            public MB_SendNotificationDelegate MB_SendNotification;\n            public MB_AddMenuItemDelegate MB_AddMenuItem;\n            public Setting_GetFieldNameDelegate Setting_GetFieldName;\n            [Obsolete(\"Use Library_QueryFilesEx\", true)]\n            public Library_QueryGetAllFilesDelegate Library_QueryGetAllFiles;\n            [Obsolete(\"Use NowPlayingList_QueryFilesEx\", true)]\n            public Library_QueryGetAllFilesDelegate NowPlayingList_QueryGetAllFiles;\n            [Obsolete(\"Use Playlist_QueryFilesEx\", true)]\n            public Library_QueryGetAllFilesDelegate Playlist_QueryGetAllFiles;\n            public MB_CreateBackgroundTaskDelegate MB_CreateBackgroundTask;\n            public MB_SetBackgroundTaskMessageDelegate MB_SetBackgroundTaskMessage;\n            public MB_RegisterCommandDelegate MB_RegisterCommand;\n            public Setting_GetDefaultFontDelegate Setting_GetDefaultFont;\n            public Player_GetShowTimeRemainingDelegate Player_GetShowTimeRemaining;\n            public NowPlayingList_GetCurrentIndexDelegate NowPlayingList_GetCurrentIndex;\n            public NowPlayingList_GetFileUrlDelegate NowPlayingList_GetListFileUrl;\n            public NowPlayingList_GetFilePropertyDelegate NowPlayingList_GetFileProperty;\n            public NowPlayingList_GetFileTagDelegate NowPlayingList_GetFileTag;\n            public NowPlaying_GetSpectrumDataDelegate NowPlaying_GetSpectrumData;\n            public NowPlaying_GetSoundGraphDelegate NowPlaying_GetSoundGraph;\n            public MB_GetPanelBoundsDelegate MB_GetPanelBounds;\n            public MB_AddPanelDelegate MB_AddPanel;\n            public MB_RemovePanelDelegate MB_RemovePanel;\n            public MB_GetLocalisationDelegate MB_GetLocalisation;\n            public NowPlayingList_IsAnyPriorTracksDelegate NowPlayingList_IsAnyPriorTracks;\n            public NowPlayingList_IsAnyFollowingTracksDelegate NowPlayingList_IsAnyFollowingTracks;\n            public Player_ShowEqualiserDelegate Player_ShowEqualiser;\n            public Player_GetAutoDjEnabledDelegate Player_GetAutoDjEnabled;\n            public Player_GetStopAfterCurrentEnabledDelegate Player_GetStopAfterCurrentEnabled;\n            public Player_GetCrossfadeDelegate Player_GetCrossfade;\n            public Player_SetCrossfadeDelegate Player_SetCrossfade;\n            public Player_GetReplayGainModeDelegate Player_GetReplayGainMode;\n            public Player_SetReplayGainModeDelegate Player_SetReplayGainMode;\n            public Player_QueueRandomTracksDelegate Player_QueueRandomTracks;\n            public Setting_GetDataTypeDelegate Setting_GetDataType;\n            public NowPlayingList_GetNextIndexDelegate NowPlayingList_GetNextIndex;\n            public NowPlaying_GetArtistPictureDelegate NowPlaying_GetArtistPicture;\n            public NowPlaying_GetArtworkDelegate NowPlaying_GetDownloadedArtwork;\n            // api version 16\n            public MB_ShowNowPlayingAssistantDelegate MB_ShowNowPlayingAssistant;\n            // api version 17\n            public NowPlaying_GetLyricsDelegate NowPlaying_GetDownloadedLyrics;\n            // api version 18\n            public Player_GetShowRatingTrackDelegate Player_GetShowRatingTrack;\n            public Player_GetShowRatingLoveDelegate Player_GetShowRatingLove;\n            // api version 19\n            public MB_CreateParameterisedBackgroundTaskDelegate MB_CreateParameterisedBackgroundTask;\n            public Setting_GetLastFmUserIdDelegate Setting_GetLastFmUserId;\n            public Playlist_GetNameDelegate Playlist_GetName;\n            public Playlist_CreatePlaylistDelegate Playlist_CreatePlaylist;\n            public Playlist_SetFilesDelegate Playlist_SetFiles;\n            public Library_QuerySimilarArtistsDelegate Library_QuerySimilarArtists;\n            public Library_QueryLookupTableDelegate Library_QueryLookupTable;\n            public Library_QueryGetLookupTableValueDelegate Library_QueryGetLookupTableValue;\n            public NowPlayingList_FilesActionDelegate NowPlayingList_QueueFilesNext;\n            public NowPlayingList_FilesActionDelegate NowPlayingList_QueueFilesLast;\n            // api version 20\n            public Setting_GetWebProxyDelegate Setting_GetWebProxy;\n            // api version 21\n            public NowPlayingList_RemoveAtDelegate NowPlayingList_RemoveAt;\n            // api version 22\n            public Playlist_RemoveAtDelegate Playlist_RemoveAt;\n            // api version 23\n            public MB_SetPanelScrollableAreaDelegate MB_SetPanelScrollableArea;\n            // api version 24\n            public MB_InvokeCommandDelegate MB_InvokeCommand;\n            public MB_OpenFilterInTabDelegate MB_OpenFilterInTab;\n            // api version 25\n            public MB_SetWindowSizeDelegate MB_SetWindowSize;\n            public Library_GetArtistPictureDelegate Library_GetArtistPicture;\n            public Pending_GetFileUrlDelegate Pending_GetFileUrl;\n            public Pending_GetFilePropertyDelegate Pending_GetFileProperty;\n            public Pending_GetFileTagDelegate Pending_GetFileTag;\n            // api version 26\n            public Player_GetButtonEnabledDelegate Player_GetButtonEnabled;\n            // api version 27\n            public NowPlayingList_MoveFilesDelegate NowPlayingList_MoveFiles;\n            // api version 28\n            public Library_GetArtworkDelegate Library_GetArtworkUrl;\n            public Library_GetArtistPictureThumbDelegate Library_GetArtistPictureThumb;\n            public NowPlaying_GetArtworkDelegate NowPlaying_GetArtworkUrl;\n            public NowPlaying_GetArtworkDelegate NowPlaying_GetDownloadedArtworkUrl;\n            public NowPlaying_GetArtistPictureThumbDelegate NowPlaying_GetArtistPictureThumb;\n            // api version 29\n            public Playlist_IsInListDelegate Playlist_IsInList;\n            // api version 30\n            public Library_GetArtistPictureUrlsDelegate Library_GetArtistPictureUrls;\n            public NowPlaying_GetArtistPictureUrlsDelegate NowPlaying_GetArtistPictureUrls;\n            // api version 31\n            public Playlist_AddFilesDelegate Playlist_AppendFiles;\n            // api version 32\n            public Sync_FileStartDelegate Sync_FileStart;\n            public Sync_FileEndDelegate Sync_FileEnd;\n            // api version 33\n            public Library_QueryFilesExDelegate Library_QueryFilesEx;\n            public Library_QueryFilesExDelegate NowPlayingList_QueryFilesEx;\n            public Playlist_QueryFilesExDelegate Playlist_QueryFilesEx;\n            public Playlist_MoveFilesDelegate Playlist_MoveFiles;\n            public Playlist_PlayNowDelegate Playlist_PlayNow;\n            public NowPlaying_IsSoundtrackDelegate NowPlaying_IsSoundtrack;\n            public NowPlaying_GetArtistPictureUrlsDelegate NowPlaying_GetSoundtrackPictureUrls;\n            public Library_GetDevicePersistentIdDelegate Library_GetDevicePersistentId;\n            public Library_SetDevicePersistentIdDelegate Library_SetDevicePersistentId;\n            public Library_FindDevicePersistentIdDelegate Library_FindDevicePersistentId;\n            public Setting_GetValueDelegate Setting_GetValue;\n            public Library_AddFileToLibraryDelegate Library_AddFileToLibrary;\n            public Playlist_DeletePlaylistDelegate Playlist_DeletePlaylist;\n            public Library_GetSyncDeltaDelegate Library_GetSyncDelta;\n            // api version 35\n            public Library_GetFileTagsDelegate Library_GetFileTags;\n            public NowPlaying_GetFileTagsDelegate NowPlaying_GetFileTags;\n            public NowPlayingList_GetFileTagsDelegate NowPlayingList_GetFileTags;\n            // api version 43\n            public MB_AddTreeNodeDelegate MB_AddTreeNode;\n            public MB_DownloadFileDelegate MB_DownloadFile;\n            // api version 47\n            public Setting_GetFileConvertCommandLineDelegate Setting_GetFileConvertCommandLine;\n            public Player_OpenStreamHandleDelegate Player_OpenStreamHandle;\n            public Player_UpdatePlayStatisticsDelegate Player_UpdatePlayStatistics;\n            public Library_GetArtworkExDelegate Library_GetArtworkEx;\n            public Library_SetArtworkExDelegate Library_SetArtworkEx;\n            public MB_GetVisualiserInformationDelegate MB_GetVisualiserInformation;\n            public MB_ShowVisualiserDelegate MB_ShowVisualiser;\n            public MB_GetPluginViewInformationDelegate MB_GetPluginViewInformation;\n            public MB_ShowPluginViewDelegate MB_ShowPluginView;\n            public Player_GetOutputDevicesDelegate Player_GetOutputDevices;\n            public Player_SetOutputDeviceDelegate Player_SetOutputDevice;\n            // api version 48\n            public MB_UninistallPluginDelegate MB_UninstallPlugin;\n        }\n\n        public enum MusicBeeVersion\n        {\n            v2_0 = 0,\n            v2_1 = 1,\n            v2_2 = 2,\n            v2_3 = 3,\n            v2_4 = 4,\n            v2_5 = 5,\n            v3_0 = 6\n        }\n\n        public enum PluginType\n        {\n            Unknown = 0,\n            General = 1,\n            LyricsRetrieval = 2,\n            ArtworkRetrieval = 3,\n            PanelView = 4,\n            DataStream = 5,\n            InstantMessenger = 6,\n            Storage = 7,\n            VideoPlayer = 8,\n            DSP = 9,\n            TagRetrieval = 10,\n            TagOrArtworkRetrieval = 11,\n            Upnp = 12\n        }\n\n        [StructLayout(LayoutKind.Sequential)]\n        public class PluginInfo\n        {\n            public short PluginInfoVersion;\n            public PluginType Type;\n            public string Name;\n            public string Description;\n            public string Author;\n            public string TargetApplication;\n            public short VersionMajor;\n            public short VersionMinor;\n            public short Revision;\n            public short MinInterfaceVersion;\n            public short MinApiRevision;\n            public ReceiveNotificationFlags ReceiveNotifications;\n            public int ConfigurationPanelHeight;\n        }\n\n        [Flags()]\n        public enum ReceiveNotificationFlags\n        {\n            StartupOnly = 0x0,\n            PlayerEvents = 0x1,\n            DataStreamEvents = 0x2,\n            TagEvents = 0x04,\n            DownloadEvents = 0x08\n        }\n\n        public enum NotificationType\n        {\n            PluginStartup = 0,          // notification sent after successful initialisation for an enabled plugin\n            TrackChanging = 16,\n            TrackChanged = 1,\n            PlayStateChanged = 2,\n            AutoDjStarted = 3,\n            AutoDjStopped = 4,\n            VolumeMuteChanged = 5,\n            VolumeLevelChanged = 6,\n            NowPlayingListChanged = 7,\n            NowPlayingListEnded = 18,\n            NowPlayingArtworkReady = 8,\n            NowPlayingLyricsReady = 9,\n            TagsChanging = 10,\n            TagsChanged = 11,\n            RatingChanging = 15,\n            RatingChanged = 12,\n            PlayCountersChanged = 13,\n            ScreenSaverActivating = 14,\n            ShutdownStarted = 17,\n            EmbedInPanel = 19,\n            PlayerRepeatChanged = 20,\n            PlayerShuffleChanged = 21,\n            PlayerEqualiserOnOffChanged = 22,\n            PlayerScrobbleChanged = 23,\n            ReplayGainChanged = 24,\n            FileDeleting = 25,\n            FileDeleted = 26,\n            ApplicationWindowChanged = 27,\n            StopAfterCurrentChanged = 28,\n            LibrarySwitched = 29,\n            FileAddedToLibrary = 30,\n            FileAddedToInbox = 31,\n            SynchCompleted = 32,\n            DownloadCompleted = 33,\n            MusicBeeStarted = 34\n        }\n\n        public enum PluginCloseReason\n        {\n            MusicBeeClosing = 1,\n            UserDisabled = 2,\n            StopNoUnload = 3\n        }\n\n        public enum CallbackType\n        {\n            SettingsUpdated = 1,\n            StorageReady = 2,\n            StorageFailed = 3,\n            FilesRetrievedChanged = 4,\n            FilesRetrievedNoChange = 5,\n            FilesRetrievedFail = 6,\n            LyricsDownloaded = 7,\n            StorageEject = 8,\n            SuspendPlayCounters = 9,\n            ResumePlayCounters = 10,\n            EnablePlugin = 11,\n            DisablePlugin = 12,\n            RenderingDevicesChanged = 13,\n            FullscreenOn = 14,\n            FullscreenOff = 15\n        }\n\n        public enum FilePropertyType\n        {\n            Url = 2,\n            Kind = 4,\n            Format = 5,\n            Size = 7,\n            Channels = 8,\n            SampleRate = 9,\n            Bitrate = 10,\n            DateModified = 11,\n            DateAdded = 12,\n            LastPlayed = 13,\n            PlayCount = 14,\n            SkipCount = 15,\n            Duration = 16,\n            Status = 21,\n            NowPlayingListIndex = 78,  // only has meaning when called from NowPlayingList_* commands\n            ReplayGainTrack = 94,\n            ReplayGainAlbum = 95\n        }\n\n        public enum MetaDataType\n        {\n            TrackTitle = 65,\n            Album = 30,\n            AlbumArtist = 31,        // displayed album artist\n            AlbumArtistRaw = 34,     // stored album artist\n            Artist = 32,             // displayed artist\n            MultiArtist = 33,        // individual artists, separated by a null char\n            PrimaryArtist = 19,      // first artist from multi-artist tagged file, otherwise displayed artist\n            Artists = 144,\n            ArtistsWithArtistRole = 145,\n            ArtistsWithPerformerRole = 146,\n            ArtistsWithGuestRole = 147,\n            ArtistsWithRemixerRole = 148,\n            Artwork = 40,\n            BeatsPerMin = 41,\n            Composer = 43,           // displayed composer\n            MultiComposer = 89,      // individual composers, separated by a null char\n            Comment = 44,\n            Conductor = 45,\n            Custom1 = 46,\n            Custom2 = 47,\n            Custom3 = 48,\n            Custom4 = 49,\n            Custom5 = 50,\n            Custom6 = 96,\n            Custom7 = 97,\n            Custom8 = 98,\n            Custom9 = 99,\n            Custom10 = 128,\n            Custom11 = 129,\n            Custom12 = 130,\n            Custom13 = 131,\n            Custom14 = 132,\n            Custom15 = 133,\n            Custom16 = 134,\n            DiscNo = 52,\n            DiscCount = 54,\n            Encoder = 55,\n            Genre = 59,\n            Genres = 143,\n            GenreCategory = 60,\n            Grouping = 61,\n            Keywords = 84,\n            HasLyrics = 63,\n            Lyricist = 62,\n            Lyrics = 114,\n            Mood = 64,\n            Occasion = 66,\n            Origin = 67,\n            Publisher = 73,\n            Quality = 74,\n            Rating = 75,\n            RatingLove = 76,\n            RatingAlbum = 104,\n            Tempo = 85,\n            TrackNo = 86,\n            TrackCount = 87,\n            Virtual1 = 109,\n            Virtual2 = 110,\n            Virtual3 = 111,\n            Virtual4 = 112,\n            Virtual5 = 113,\n            Virtual6 = 122,\n            Virtual7 = 123,\n            Virtual8 = 124,\n            Virtual9 = 125,\n            Virtual10 = 135,\n            Virtual11 = 136,\n            Virtual12 = 137,\n            Virtual13 = 138,\n            Virtual14 = 139,\n            Virtual15 = 140,\n            Virtual16 = 141,\n            Year = 88\n        }\n        \n        public enum FileCodec\n        {\n            Unknown = -1,\n            Mp3 = 1,\n            Aac = 2,\n            Flac = 3,\n            Ogg = 4,\n            WavPack = 5,\n            Wma = 6,\n            Tak = 7,\n            Mpc = 8,\n            Wave = 9,\n            Asx = 10,\n            Alac = 11,\n            Aiff = 12,\n            Pcm = 13,\n            Opus = 15,\n            Spx = 16,\n            Dsd = 17,\n            AacNoContainer = 18\n        }\n\n        public enum EncodeQuality\n        {\n            SmallSize = 1,\n            Portable = 2,\n            HighQuality = 3,\n            Archiving = 4\n        }\n\n        [Flags()]\n        public enum LibraryCategory\n        {\n            Music = 0,\n            Audiobook = 1,\n            Video = 2,\n            Inbox = 4\n        }\n\n        public enum DeviceIdType\n        {\n            GooglePlay = 1,\n            AppleDevice = 2,\n            GooglePlay2 = 3,\n            AppleDevice2 = 4\n        }\n\n        public enum DataType\n        {\n            String = 0,\n            Number = 1,\n            DateTime = 2,\n            Rating = 3\n        }\n\n        public enum SettingId\n        {\n            CompactPlayerFlickrEnabled = 1,\n            FileTaggingPreserveModificationTime = 2,\n            LastDownloadFolder = 3,\n            ArtistGenresOnly = 4,\n            IgnoreNamePrefixes = 5,\n            IgnoreNameChars = 6,\n            PlayCountTriggerPercent = 7,\n            PlayCountTriggerSeconds = 8,\n            SkipCountTriggerPercent = 9,\n            SkipCountTriggerSeconds = 10,\n            CustomWebLinkName1 = 11,\n            CustomWebLinkName2 = 12,\n            CustomWebLinkName3 = 13,\n            CustomWebLinkName4 = 14,\n            CustomWebLinkName5 = 15,\n            CustomWebLinkName6 = 16,\n            CustomWebLink1 = 17,\n            CustomWebLink2 = 18,\n            CustomWebLink3 = 19,\n            CustomWebLink4 = 20,\n            CustomWebLink5 = 21,\n            CustomWebLink6 = 22,\n            CustomWebLinkNowPlaying1 = 23,\n            CustomWebLinkNowPlaying2 = 24,\n            CustomWebLinkNowPlaying3 = 25,\n            CustomWebLinkNowPlaying4 = 26,\n            CustomWebLinkNowPlaying5 = 27,\n            CustomWebLinkNowPlaying6 = 28\n        }\n\n        public enum ComparisonType\n        {\n            Is = 0,\n            IsSimilar = 20\n        }\n\n        public enum LyricsType\n        {\n            NotSpecified = 0,\n            Synchronised = 1,\n            UnSynchronised = 2\n        }\n\n        public enum PlayState\n        {\n            Undefined = 0,\n            Loading = 1,\n            Playing = 3,\n            Paused = 6,\n            Stopped = 7\n        }\n\n        public enum RepeatMode\n        {\n            None = 0,\n            All = 1,\n            One = 2\n        }\n\n        public enum PlayButtonType\n        {\n            PreviousTrack = 0,\n            PlayPause = 1,\n            NextTrack = 2,\n            Stop = 3\n        }\n\n        public enum PlaylistFormat\n        {\n            Unknown = 0,\n            M3u = 1,\n            Xspf = 2,\n            Asx = 3,\n            Wpl = 4,\n            Pls = 5,\n            Auto = 7,\n            M3uAscii = 8,\n            AsxFile = 9,\n            Radio = 10,\n            M3uExtended = 11,\n            Mbp = 12\n        }\n\n        public enum SkinElement\n        {\n            SkinInputControl = 7,\n            SkinInputPanel = 10,\n            SkinInputPanelLabel = 14,\n            SkinTrackAndArtistPanel = -1\n        }\n\n        public enum ElementState\n        {\n            ElementStateDefault = 0,\n            ElementStateModified = 6\n        }\n\n        public enum ElementComponent\n        {\n            ComponentBorder = 0,\n            ComponentBackground = 1,\n            ComponentForeground = 3\n        }\n\n        public enum PluginPanelDock\n        {\n            ApplicationWindow = 0,\n            TrackAndArtistPanel = 1,\n            TextBox = 3,\n            ComboBox = 4,\n            MainPanel = 5\n        }\n\n        \n        public enum ReplayGainMode\n        {\n            Off = 0,\n            Track = 1,\n            Album = 2,\n            Smart = 3\n        }\n        \n        public enum PlayStatisticType\n        {\n            NoChange = 0,\n            IncreasePlayCount = 1,\n            IncreaseSkipCount = 2\n        }\n\n        public enum Command\n        {\n            NavigateTo = 1\n        }\n        \n        public enum DownloadTarget\n        {\n            Inbox = 0,\n            MusicLibrary = 1,\n            SpecificFolder = 3\n        }\n\n        [Flags()]\n        public enum PictureLocations: byte\n        {\n            None = 0,\n            EmbedInFile = 1,\n            LinkToOrganisedCopy = 2,\n            LinkToSource = 4,\n            FolderThumb = 8\n        }\n\n        public enum WindowState\n        {\n            Off = -1,\n            Normal = 0,\n            Fullscreen = 1,\n            Desktop = 2\n        }\n\n        public delegate void MB_ReleaseStringDelegate(string p1);\n        public delegate void MB_TraceDelegate(string p1);\n        public delegate IntPtr MB_WindowHandleDelegate();\n        public delegate void MB_RefreshPanelsDelegate();\n        public delegate void MB_SendNotificationDelegate(CallbackType type);\n        public delegate System.Windows.Forms.ToolStripItem MB_AddMenuItemDelegate(string menuPath, string hotkeyDescription, EventHandler handler);\n        public delegate bool MB_AddTreeNodeDelegate(string treePath, string name, System.Drawing.Bitmap icon, EventHandler openHandler, EventHandler closeHandler);\n        public delegate void MB_RegisterCommandDelegate(string command, EventHandler handler);\n        public delegate void MB_CreateBackgroundTaskDelegate(System.Threading.ThreadStart taskCallback, System.Windows.Forms.Form owner);\n        public delegate void MB_CreateParameterisedBackgroundTaskDelegate(System.Threading.ParameterizedThreadStart taskCallback, object parameters, System.Windows.Forms.Form owner);\n        public delegate void MB_SetBackgroundTaskMessageDelegate(string message);\n        public delegate System.Drawing.Rectangle MB_GetPanelBoundsDelegate(PluginPanelDock dock);\n        public delegate bool MB_SetPanelScrollableAreaDelegate(System.Windows.Forms.Control panel, System.Drawing.Size scrollArea, bool alwaysShowScrollBar);\n        public delegate System.Windows.Forms.Control MB_AddPanelDelegate(System.Windows.Forms.Control panel, PluginPanelDock dock);\n        public delegate void MB_RemovePanelDelegate(System.Windows.Forms.Control panel);\n        public delegate string MB_GetLocalisationDelegate(string id, string defaultText);\n        public delegate bool MB_ShowNowPlayingAssistantDelegate();\n        public delegate bool MB_InvokeCommandDelegate(Command command, object parameter);\n        public delegate bool MB_OpenFilterInTabDelegate(MetaDataType field1, ComparisonType comparison1, string value1, MetaDataType field2, ComparisonType comparison2, string value2);\n        public delegate bool MB_SetWindowSizeDelegate(int width, int height);\n        public delegate bool MB_DownloadFileDelegate(string url, DownloadTarget target, string targetFolder, bool cancelDownload);\n        public delegate bool MB_GetVisualiserInformationDelegate(out string[] visualiserNames, out string defaultVisualiserName, out WindowState defaultState, out WindowState currentState);\n        public delegate bool MB_ShowVisualiserDelegate(string visualiserName, WindowState state);\n        public delegate bool MB_GetPluginViewInformationDelegate(string pluginFilename, out string[] viewNames, out string defaultViewName, out WindowState defaultState, out WindowState currentState);\n        public delegate bool MB_ShowPluginViewDelegate(string pluginFilename, string viewName, WindowState state);\n        public delegate bool MB_UninistallPluginDelegate(string pluginFilename, string password);\n        public delegate string Setting_GetFieldNameDelegate(MetaDataType field);\n        public delegate string Setting_GetPersistentStoragePathDelegate();\n        public delegate string Setting_GetSkinDelegate();\n        public delegate int Setting_GetSkinElementColourDelegate(SkinElement element, ElementState state, ElementComponent component);\n        public delegate bool Setting_IsWindowBordersSkinnedDelegate();\n        public delegate System.Drawing.Font Setting_GetDefaultFontDelegate();\n        public delegate DataType Setting_GetDataTypeDelegate(MetaDataType field);\n        public delegate string Setting_GetLastFmUserIdDelegate();\n        public delegate string Setting_GetWebProxyDelegate();\n        public delegate bool Setting_GetValueDelegate(SettingId settingId, ref object value);\n        public delegate string Setting_GetFileConvertCommandLineDelegate(FileCodec codec, EncodeQuality encodeQuality);\n        public delegate string Library_GetFilePropertyDelegate(string sourceFileUrl, FilePropertyType type);\n        public delegate string Library_GetFileTagDelegate(string sourceFileUrl, MetaDataType field);\n        public delegate bool Library_GetFileTagsDelegate(string sourceFileUrl, MetaDataType[] fields, ref string[] results);\n        public delegate bool Library_SetFileTagDelegate(string sourceFileUrl, MetaDataType field, string value);\n        public delegate string Library_GetDevicePersistentIdDelegate(string sourceFileUrl, DeviceIdType idType);\n        public delegate bool Library_SetDevicePersistentIdDelegate(string sourceFileUrl, DeviceIdType idType, string value);\n        public delegate bool Library_FindDevicePersistentIdDelegate(DeviceIdType idType, string[] ids, ref string[] values);\n        public delegate bool Library_CommitTagsToFileDelegate(string sourceFileUrl);\n        public delegate string Library_AddFileToLibraryDelegate(string sourceFileUrl, LibraryCategory category);\n        public delegate bool Library_GetSyncDeltaDelegate(string[] cachedFiles, DateTime updatedSince, LibraryCategory categories, ref string[] newFiles, ref string[] updatedFiles, ref string[] deletedFiles);\n        public delegate string Library_GetLyricsDelegate(string sourceFileUrl, LyricsType type);\n        public delegate string Library_GetArtworkDelegate(string sourceFileUrl, int index);\n        public delegate bool Library_GetArtworkExDelegate(string sourceFileUrl, int index, bool retrievePictureData, ref PictureLocations pictureLocations, ref string pictureUrl, ref byte[] imageData);\n        public delegate bool Library_SetArtworkExDelegate(string sourceFileUrl, int index, byte[] imageData);\n        public delegate string Library_GetArtistPictureDelegate(string artistName, int fadingPercent, int fadingColor);\n        public delegate bool Library_GetArtistPictureUrlsDelegate(string artistName, bool localOnly, ref string[] urls);\n        public delegate string Library_GetArtistPictureThumbDelegate(string artistName);\n        public delegate bool Library_QueryFilesDelegate(string query);\n        public delegate string Library_QueryGetNextFileDelegate();\n        public delegate string Library_QueryGetAllFilesDelegate();\n        public delegate bool Library_QueryFilesExDelegate(string query, ref string[] files);\n        public delegate string Library_QuerySimilarArtistsDelegate(string artistName, double minimumArtistSimilarityRating);\n        public delegate bool Library_QueryLookupTableDelegate(string keyTags, string valueTags, string query);\n        public delegate string Library_QueryGetLookupTableValueDelegate(string key);\n        public delegate int Player_GetPositionDelegate();\n        public delegate bool Player_SetPositionDelegate(int position);\n        public delegate PlayState Player_GetPlayStateDelegate();\n        public delegate bool Player_GetButtonEnabledDelegate(PlayButtonType button);\n        public delegate bool Player_ActionDelegate();\n        public delegate int Player_QueueRandomTracksDelegate(int count);\n        public delegate float Player_GetVolumeDelegate();\n        public delegate bool Player_SetVolumeDelegate(float volume);\n        public delegate bool Player_GetMuteDelegate();\n        public delegate bool Player_SetMuteDelegate(bool mute);\n        public delegate bool Player_GetShuffleDelegate();\n        public delegate bool Player_SetShuffleDelegate(bool shuffle);\n        public delegate RepeatMode Player_GetRepeatDelegate();\n        public delegate bool Player_SetRepeatDelegate(RepeatMode repeat);\n        public delegate bool Player_GetEqualiserEnabledDelegate();\n        public delegate bool Player_SetEqualiserEnabledDelegate(bool enabled);\n        public delegate bool Player_GetDspEnabledDelegate();\n        public delegate bool Player_SetDspEnabledDelegate(bool enabled);\n        public delegate bool Player_GetScrobbleEnabledDelegate();\n        public delegate bool Player_SetScrobbleEnabledDelegate(bool enabled);\n        public delegate bool Player_GetShowTimeRemainingDelegate();\n        public delegate bool Player_GetShowRatingTrackDelegate();\n        public delegate bool Player_GetShowRatingLoveDelegate();\n        public delegate bool Player_ShowEqualiserDelegate();\n        public delegate bool Player_GetAutoDjEnabledDelegate();\n        public delegate bool Player_GetStopAfterCurrentEnabledDelegate();\n        public delegate bool Player_GetCrossfadeDelegate();\n        public delegate bool Player_SetCrossfadeDelegate(bool crossfade);\n        public delegate ReplayGainMode Player_GetReplayGainModeDelegate();\n        public delegate bool Player_SetReplayGainModeDelegate(ReplayGainMode mode);\n        public delegate int Player_OpenStreamHandleDelegate(string url, bool useMusicBeeSettings, bool enableDsp, ReplayGainMode gainType);\n        public delegate bool Player_UpdatePlayStatisticsDelegate(string url, PlayStatisticType countType, bool disableScrobble);\n        public delegate bool Player_GetOutputDevicesDelegate(out string[] deviceNames, out string activeDeviceName);\n        public delegate bool Player_SetOutputDeviceDelegate(string deviceName);\n        public delegate string NowPlaying_GetFileUrlDelegate();\n        public delegate int NowPlaying_GetDurationDelegate();\n        public delegate string NowPlaying_GetFilePropertyDelegate(FilePropertyType type);\n        public delegate string NowPlaying_GetFileTagDelegate(MetaDataType field);\n        public delegate bool NowPlaying_GetFileTagsDelegate(MetaDataType[] fields, ref string[] results);\n        public delegate string NowPlaying_GetLyricsDelegate();\n        public delegate string NowPlaying_GetArtworkDelegate();\n        public delegate string NowPlaying_GetArtistPictureDelegate(int fadingPercent);\n        public delegate bool NowPlaying_GetArtistPictureUrlsDelegate(bool localOnly, ref string[] urls);\n        public delegate string NowPlaying_GetArtistPictureThumbDelegate();\n        public delegate bool NowPlaying_IsSoundtrackDelegate();\n        public delegate int NowPlaying_GetSpectrumDataDelegate(float[] fftData);\n        public delegate bool NowPlaying_GetSoundGraphDelegate(float[] graphData);\n        public delegate int NowPlayingList_GetCurrentIndexDelegate();\n        public delegate int NowPlayingList_GetNextIndexDelegate(int offset);\n        public delegate bool NowPlayingList_IsAnyPriorTracksDelegate();\n        public delegate bool NowPlayingList_IsAnyFollowingTracksDelegate();\n        public delegate string NowPlayingList_GetFileUrlDelegate(int index);\n        public delegate string NowPlayingList_GetFilePropertyDelegate(int index, FilePropertyType type);\n        public delegate string NowPlayingList_GetFileTagDelegate(int index, MetaDataType field);\n        public delegate bool NowPlayingList_GetFileTagsDelegate(int index, MetaDataType[] fields, ref string[] results);\n        public delegate bool NowPlayingList_ActionDelegate();\n        public delegate bool NowPlayingList_FileActionDelegate(string sourceFileUrl);\n        public delegate bool NowPlayingList_FilesActionDelegate(string[] sourceFileUrl);\n        public delegate bool NowPlayingList_RemoveAtDelegate(int index);\n        public delegate bool NowPlayingList_MoveFilesDelegate(int[] fromIndices, int toIndex);\n        public delegate string Playlist_GetNameDelegate(string playlistUrl);\n        public delegate PlaylistFormat Playlist_GetTypeDelegate(string playlistUrl);\n        public delegate bool Playlist_QueryPlaylistsDelegate();\n        public delegate string Playlist_QueryGetNextPlaylistDelegate();\n        public delegate bool Playlist_IsInListDelegate(string playlistUrl, string filename);\n        public delegate bool Playlist_QueryFilesDelegate(string playlistUrl);\n        public delegate bool Playlist_QueryFilesExDelegate(string playlistUrl, ref string[] filenames);\n        public delegate string Playlist_CreatePlaylistDelegate(string folderName, string playlistName, string[] filenames);\n        public delegate bool Playlist_DeletePlaylistDelegate(string playlistUrl);\n        public delegate bool Playlist_SetFilesDelegate(string playlistUrl, string[] filenames);\n        public delegate bool Playlist_AddFilesDelegate(string playlistUrl, string[] filenames);\n        public delegate bool Playlist_RemoveAtDelegate(string playlistUrl, int index);\n        public delegate bool Playlist_MoveFilesDelegate(string playlistUrl, int[] fromIndices, int toIndex);\n        public delegate bool Playlist_PlayNowDelegate(string playlistUrl);\n        public delegate string Pending_GetFileUrlDelegate();\n        public delegate string Pending_GetFilePropertyDelegate(FilePropertyType field);\n        public delegate string Pending_GetFileTagDelegate(MetaDataType field);\n        public delegate string Sync_FileStartDelegate(string filename);\n        public delegate void Sync_FileEndDelegate(string filename, bool success, string errorMessage);\n\n        [System.Security.SuppressUnmanagedCodeSecurity()]\n        [DllImport(\"kernel32.dll\")]\n        private static extern void CopyMemory(ref MusicBeeApiInterface mbApiInterface, IntPtr src, int length);\n    }\n}"
  },
  {
    "path": "NeteaseApi.cs",
    "content": "﻿using Newtonsoft.Json;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Text;\n\nnamespace MusicBeePlugin\n{\n    /*\n     * Partially adopted from https://github.com/real-zony/ZonyLrcToolsX/blob/dev/src/ZonyLrcTools.Common/Lyrics/Providers/NetEase/NetEaseLyricsProvider.cs\n     */\n    internal static class NeteaseApi\n    {\n        public static IEnumerable<SearchResultSong> Search(string s)\n        {\n            var postData = new Dictionary<string, object>\n                    {\n                        { \"csrf_token\", \"\" },\n                        { \"s\", s },\n                        { \"offset\", 0 },\n                        { \"type\", 1 },\n                        { \"limit\", 20 }\n                    };\n            var result = RequestNewApi<SearchResult>(\n                @\"https://music.163.com/weapi/search/get\", \n                postData, it => it.code == 200);\n            if (result == null)\n                return SearchLegacy(s);\n            return result.result.songCount > 0 ? result.result.songs : Enumerable.Empty<SearchResultSong>();\n        }\n\n        public static LyricResult RequestLyric(long id)\n        {\n            var postData = new Dictionary<string, object>\n                    {\n                        { \"OS\", \"pc\" },\n                        { \"id\", id },\n                        { \"lv\", -1 },\n                        { \"kv\", -1 },\n                        { \"tv\", -1 },\n                        { \"rv\", -1 }\n                    };\n            return RequestNewApi<LyricResult>(\n                @\"https://music.163.com/weapi/song/lyric?csrf_token=\", \n                postData, it => it.code == 200) ?? RequestLyricLegacy(id);\n        }\n\n        private static T RequestNewApi<T>(string url, Dictionary<string, object> postData, Func<T, bool> checkFunc) \n            where T : class\n        {\n            try\n            {\n                using (var client = new HttpClient())\n                {\n                    client.DefaultRequestHeaders.Referrer = new Uri(@\"https://music.163.com\");\n                    client.DefaultRequestHeaders.UserAgent.ParseAdd(\n                        \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\");\n                    using (var result = client.PostAsync(\n                               url,\n                               new FormUrlEncodedContent(EncryptRequest(postData))).Result)\n                    {\n                        var resultString = result.Content.ReadAsStringAsync().Result;\n                        if (!result.IsSuccessStatusCode)\n                            throw new HttpRequestException($\"non 200 response from {url}: {resultString}\");;\n                        var resultObject = JsonConvert.DeserializeObject<T>(resultString);\n                        if (!checkFunc(resultObject))\n                            throw new HttpRequestException($\"non 200 response from {url}: {resultString}\");\n                        return resultObject;\n                    }\n                }\n            }\n            catch (Exception e)\n            {\n                Debug.WriteLine(e);\n                return null;\n            }\n        }\n\n        private static Dictionary<string, string> EncryptRequest(object srcParams)\n        {\n            var secretKey = NeteaseMusicEncryptionHelper.CreateSecretKey(16);\n            var encSecKey = NeteaseMusicEncryptionHelper.RsaEncode(secretKey);\n            return new Dictionary<string, string>\n            {\n                {\n                    \"params\", \n                    NeteaseMusicEncryptionHelper.AesEncode(\n                        NeteaseMusicEncryptionHelper.AesEncode(\n                            JsonConvert.SerializeObject(srcParams),\n                            NeteaseMusicEncryptionHelper.Nonce),\n                        secretKey)\n                },\n                { \"encSecKey\", encSecKey }\n            };\n        }\n\n        private static IEnumerable<SearchResultSong> SearchLegacy(string s)\n        {\n            try\n            {\n                using (var client = new WebClient())\n                {\n                    client.Headers[HttpRequestHeader.Referer] = \"https://music.163.com/\";\n                    client.Headers[HttpRequestHeader.UserAgent] =\n                        \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\";\n\n                    var nameEncoded = Uri.EscapeDataString(s);\n                    var resultStr = Encoding.UTF8.GetString(\n                        client.DownloadData(\n                            $\"http://music.163.com/api/search/get/?csrf_token=hlpretag=&hlposttag=&s={nameEncoded}&type=1&offset=0&total=true&limit=6\")\n                    );\n                    var searchResult = JsonConvert.DeserializeObject<SearchResult>(resultStr);\n                    if (searchResult.code != 200) return null;\n                    return searchResult.result.songCount <= 0\n                        ? Enumerable.Empty<SearchResultSong>()\n                        : searchResult.result.songs;\n                }\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine(ex);\n                return Enumerable.Empty<SearchResultSong>();\n            }\n        }\n\n        private static LyricResult RequestLyricLegacy(long id)\n        {\n            try\n            {\n                using (var client = new WebClient())\n                {\n                    client.Headers[HttpRequestHeader.Referer] = \"http://music.163.com/\";\n                    client.Headers[HttpRequestHeader.Cookie] = \"appver=1.5.0.75771;\";\n                    var lyricResult = JsonConvert.DeserializeObject<LyricResult>(\n                        Encoding.UTF8.GetString(client.DownloadData(\"http://music.163.com/api/song/lyric?os=pc&id=\" +\n                                                                    id + \"&lv=-1&kv=-1&tv=-1\")));\n                    return lyricResult.code != 200 ? null : lyricResult;\n                }\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine(ex);\n                return null;\n            }\n        }\n    }\n}"
  },
  {
    "path": "NeteaseLyrics.cs",
    "content": "using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Drawing;\nusing System.Windows.Forms;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing Newtonsoft.Json;\nusing System.Text.RegularExpressions;\nusing System.Runtime.CompilerServices;\n\n[assembly: InternalsVisibleTo(\"NeteaseLyricsTest\")]\n\nnamespace MusicBeePlugin\n{\n    public class NeteaseConfig\n    {\n        public enum OutputFormat\n        {\n            Original = 0,\n            Both = 1,\n            Translation = 2\n        }\n\n        public OutputFormat Format { get; set; } = OutputFormat.Both;\n        public bool Fuzzy { get; set; }\n        public bool UseLegacyMatch { get; set; }\n    }\n\n    public partial class Plugin\n    {\n        private const string ProviderName = \"Netease Cloud Music(网易云音乐)\";\n        private const string ConfigFilename = \"netease_config\";\n        private const string NoTranslateFilename = \"netease_notranslate\";\n        private NeteaseConfig _config = new NeteaseConfig();\n        private ComboBox _formatComboBox;\n        private CheckBox _fuzzyCheckBox;\n        private CheckBox _useLegacyCheckBox;\n\n        private MusicBeeApiInterface _mbApiInterface;\n        private readonly PluginInfo _about = new PluginInfo();\n\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        public PluginInfo Initialise(IntPtr apiInterfacePtr)\n        {\n            var versions = Assembly.GetExecutingAssembly().GetName().Version.ToString().Split('.');\n\n            _mbApiInterface = new MusicBeeApiInterface();\n            _mbApiInterface.Initialise(apiInterfacePtr);\n            _about.PluginInfoVersion = PluginInfoVersion;\n            _about.Name = \"Netease Lyrics\";\n            _about.Description = \"A plugin to retrieve lyrics from Netease Cloud Music.(从网易云音乐获取歌词的插件。)\";\n            _about.Author = \"Charlie Jiang\";\n            _about.TargetApplication = \"\";   // current only applies to artwork, lyrics or instant messenger name that appears in the provider drop down selector or target Instant Messenger\n            _about.Type = PluginType.LyricsRetrieval;\n            _about.VersionMajor = short.Parse(versions[0]);  // your plugin version\n            _about.VersionMinor = short.Parse(versions[1]);\n            _about.Revision = short.Parse(versions[2]);\n            _about.MinInterfaceVersion = MinInterfaceVersion;\n            _about.MinApiRevision = MinApiRevision;\n            _about.ReceiveNotifications = ReceiveNotificationFlags.DownloadEvents;\n            _about.ConfigurationPanelHeight = 120;   // height in pixels that musicbee should reserve in a panel for config settings. When set, a handle to an empty panel will be passed to the Configure function\n\n            ReadConfig();\n            MigrateLegacySetting();\n            return _about;\n        }\n\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        public bool Configure(IntPtr panelHandle)\n        {\n            if (panelHandle == IntPtr.Zero) return false;\n            var configPanel = (Panel)Control.FromHandle(panelHandle);\n            // Components are automatically disposed when this is called.\n            configPanel.Controls.Clear();\n\n            // MB_AddPanel doesn't skin the component correctly either\n            //_formatComboBox = (ComboBox)_mbApiInterface.MB_AddPanel(null, PluginPanelDock.ComboBox);\n            _formatComboBox = new ComboBox\n            {\n                DropDownStyle = ComboBoxStyle.DropDownList,\n                AutoSize = true,\n                Location = new Point(0, 0),\n                Width = 300\n            };\n            _formatComboBox.Items.Add(\"Only original text\");\n            _formatComboBox.Items.Add(\"Original text and translation\");\n            _formatComboBox.Items.Add(\"Only translation\");\n            _formatComboBox.SelectedIndex = (int)_config.Format;\n            configPanel.Controls.Add(_formatComboBox);\n\n            _useLegacyCheckBox = new CheckBox\n            {\n                Text = \"Use legacy matching strategy\",\n                Location = new Point(0, 50),\n                Checked = _config.UseLegacyMatch,\n                AutoSize = true\n            };\n\n            _fuzzyCheckBox = new CheckBox\n            {\n                Text = \"Fuzzy matching (Don't double check match and use first result directly)\",\n                Location = new Point(0, 80),\n                Checked = _config.Fuzzy,\n                AutoSize = true\n            };\n\n            _useLegacyCheckBox.CheckedChanged += (sender, e) =>\n            {\n                // \"Fuzzy\" not available when using new strategy\n                _fuzzyCheckBox.Enabled = !_useLegacyCheckBox.Checked;\n            };\n\n            configPanel.Controls.Add(_useLegacyCheckBox);\n            configPanel.Controls.Add(_fuzzyCheckBox);\n\n            return false;\n        }\n\n        // called by MusicBee when the user clicks Apply or Save in the MusicBee Preferences screen.\n        // It's up to you to figure out whether anything has changed and needs updating\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        public void SaveSettings()\n        {\n            if (_formatComboBox.SelectedIndex < 0 || _formatComboBox.SelectedIndex > 2)\n                _config.Format = NeteaseConfig.OutputFormat.Both;\n            else\n                _config.Format = (NeteaseConfig.OutputFormat)_formatComboBox.SelectedIndex;\n            _config.Fuzzy = _fuzzyCheckBox.Checked;\n            _config.UseLegacyMatch = _useLegacyCheckBox.Checked;\n            SaveSettingsInternal();\n        }\n\n        private void SaveSettingsInternal()\n        {\n            var configPath = Path.Combine(_mbApiInterface.Setting_GetPersistentStoragePath(), ConfigFilename);\n            var json = JsonConvert.SerializeObject(_config);\n            File.WriteAllText(configPath, json, Encoding.UTF8);\n        }\n\n        // MusicBee is closing the plugin (plugin is being disabled by user or MusicBee is shutting down)\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        [SuppressMessage(\"ReSharper\", \"UnusedParameter.Global\")]\n        public void Close(PluginCloseReason reason)\n        {\n        }\n\n        // uninstall this plugin - clean up any persisted files\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        public void Uninstall()\n        {\n            var dataPath = _mbApiInterface.Setting_GetPersistentStoragePath();\n            var p = Path.Combine(dataPath, NoTranslateFilename);\n            if (File.Exists(p)) File.Delete(p);\n            var configPath = Path.Combine(_mbApiInterface.Setting_GetPersistentStoragePath(), ConfigFilename);\n            if (File.Exists(configPath)) File.Delete(configPath);\n        }\n\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        [SuppressMessage(\"ReSharper\", \"UnusedParameter.Global\")]\n        public string RetrieveLyrics(string sourceFileUrl, \n            string artist, string trackTitle, string album,\n            bool synchronisedPreferred, string provider)\n        {\n            if (provider != ProviderName) return null;\n\n            var specifiedId = _mbApiInterface.Library_GetFileTag(sourceFileUrl, MetaDataType.Custom10)\n                              ?? _mbApiInterface.NowPlaying_GetFileTag(MetaDataType.Custom10);\n\n            var id = TryParseNeteaseUrl(specifiedId);\n            if (id == 0)\n            {\n                var realTitle = _mbApiInterface.Library_GetFileTag(sourceFileUrl, MetaDataType.TrackTitle);\n                var realArtist = _mbApiInterface.Library_GetFileTag(sourceFileUrl, MetaDataType.Artist);\n                var realAlbum = _mbApiInterface.Library_GetFileTag(sourceFileUrl, MetaDataType.Album);\n                var durationStr = _mbApiInterface.Library_GetFileProperty(sourceFileUrl, FilePropertyType.Duration);\n                id = !_config.UseLegacyMatch \n                    ? SearchMatch.SearchAndMatch(realTitle, realArtist, realAlbum, ParseDurationString(durationStr))\n                    : SearchMatchLegacy.QueryWithFeatRemoved(realTitle, realArtist, _config.Fuzzy);\n            }\n\n            if (id == 0)\n                return null;\n\n            var lyricResult = NeteaseApi.RequestLyric(id);\n\n            if (lyricResult.lrc?.lyric == null) return null;\n            if (lyricResult.tlyric?.lyric == null || _config.Format == NeteaseConfig.OutputFormat.Original)\n                return lyricResult.lrc.lyric; // No need to process translation\n\n            if (_config.Format == NeteaseConfig.OutputFormat.Translation)\n                return lyricResult.tlyric?.lyric ?? lyricResult.lrc.lyric;\n            // translation\n            return LyricProcessor.InjectTranslation(lyricResult.lrc.lyric, lyricResult.tlyric.lyric);\n        }\n\n        private void ReadConfig()\n        {\n            var configPath = Path.Combine(_mbApiInterface.Setting_GetPersistentStoragePath(), ConfigFilename);\n            if (!File.Exists(configPath)) \n                return;\n            try\n            {\n                _config = JsonConvert.DeserializeObject<NeteaseConfig>(File.ReadAllText(configPath, Encoding.UTF8));\n            }\n            catch (Exception ex)\n            {\n                _mbApiInterface.MB_Trace(\"[NeteaseMusic] Failed to load config\" + ex);\n            }\n        }\n\n        private void MigrateLegacySetting()\n        {\n            var noTranslatePath = Path.Combine(_mbApiInterface.Setting_GetPersistentStoragePath(), NoTranslateFilename);\n            if (!File.Exists(noTranslatePath))\n                return;\n            File.Delete(noTranslatePath);\n            _config.Format = NeteaseConfig.OutputFormat.Original;\n            SaveSettingsInternal();\n        }\n\n        private static long TryParseNeteaseUrl(string input)\n        {\n            if (input == null)\n                return 0;\n            if (input.StartsWith(\"netease=\"))\n            {\n                input = input.Substring(\"netease=\".Length);\n                long.TryParse(input, out var id);\n                return id;\n            }\n\n            if (!input.Contains(\"music.163.com\"))\n                return 0;\n\n            var matches = Regex.Matches(input, \"id=(\\\\d+)\");\n            if (matches.Count <= 0)\n                return 0;\n\n            var groups = matches[0].Groups;\n            if (groups.Count <= 1)\n                return 0;\n\n            var idString = groups[1].Captures[0].Value;\n            long.TryParse(idString, out var id2);\n            return id2;\n        }\n\n        private static long ParseDurationString(string durationStr)\n        {\n            var multiplier = 1000L;\n            var sum = 0L;\n            foreach (var part in durationStr.Split(':').Reverse())\n            {\n                if (part.Length > 0)\n                    sum += multiplier * long.Parse(part);\n                multiplier *= 60;\n            }\n            return sum;\n        }\n\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        public string[] GetProviders()\n        {\n            return new []{ProviderName};\n        }\n        [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n        public void ReceiveNotification(string sourceFileUrl, NotificationType type)\n        {\n        }\n    }\n}"
  },
  {
    "path": "NeteaseLyrics.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"12.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProductVersion>9.0.30729</ProductVersion>\n    <SchemaVersion>2.0</SchemaVersion>\n    <ProjectGuid>{F5D46BA1-6F21-40EF-9695-46105CCACD08}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>MusicBeePlugin</RootNamespace>\n    <AssemblyName>mb_NeteaseLyrics</AssemblyName>\n    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <FileUpgradeFlags>\n    </FileUpgradeFlags>\n    <OldToolsVersion>3.5</OldToolsVersion>\n    <UpgradeBackupLocation />\n    <PublishUrl>publish\\</PublishUrl>\n    <Install>true</Install>\n    <InstallFrom>Disk</InstallFrom>\n    <UpdateEnabled>false</UpdateEnabled>\n    <UpdateMode>Foreground</UpdateMode>\n    <UpdateInterval>7</UpdateInterval>\n    <UpdateIntervalUnits>Days</UpdateIntervalUnits>\n    <UpdatePeriodically>false</UpdatePeriodically>\n    <UpdateRequired>false</UpdateRequired>\n    <MapFileExtensions>true</MapFileExtensions>\n    <ApplicationRevision>0</ApplicationRevision>\n    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>\n    <IsWebBootstrapper>false</IsWebBootstrapper>\n    <UseApplicationTrust>false</UseApplicationTrust>\n    <BootstrapperEnabled>true</BootstrapperEnabled>\n    <NuGetPackageImportStamp>\n    </NuGetPackageImportStamp>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug|x86'\">\n    <DebugSymbols>true</DebugSymbols>\n    <OutputPath>bin\\x86\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <DebugType>full</DebugType>\n    <PlatformTarget>x86</PlatformTarget>\n    <ErrorReport>prompt</ErrorReport>\n    <CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>\n    <CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release|x86'\">\n    <OutputPath>bin\\x86\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <Optimize>true</Optimize>\n    <DebugType>pdbonly</DebugType>\n    <PlatformTarget>x86</PlatformTarget>\n    <ErrorReport>prompt</ErrorReport>\n    <CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>\n    <CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"Costura, Version=3.2.2.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL\">\n      <HintPath>packages\\Costura.Fody.3.2.2\\lib\\net40\\Costura.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Net.Http\" />\n    <Reference Include=\"System.Numerics\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"DataStucture.cs\" />\n    <Compile Include=\"LyricProcessor.cs\" />\n    <Compile Include=\"MusicBeeInterface.cs\" />\n    <Compile Include=\"NeteaseLyrics.cs\" />\n    <Compile Include=\"NeteaseApi.cs\" />\n    <Compile Include=\"SearchMatch.cs\" />\n    <Compile Include=\"SearchMatchLegacy.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"app.config\" />\n    <Compile Include=\"NeteaseMusicEncryptionHandler.cs\" />\n    <None Include=\"LICENSE-Zony\" />\n  </ItemGroup>\n  <ItemGroup>\n    <BootstrapperPackage Include=\".NETFramework,Version=v4.0\">\n      <Visible>False</Visible>\n      <ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>\n      <Install>true</Install>\n    </BootstrapperPackage>\n    <BootstrapperPackage Include=\"Microsoft.Net.Client.3.5\">\n      <Visible>False</Visible>\n      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>\n      <Install>false</Install>\n    </BootstrapperPackage>\n    <BootstrapperPackage Include=\"Microsoft.Net.Framework.3.5.SP1\">\n      <Visible>False</Visible>\n      <ProductName>.NET Framework 3.5 SP1</ProductName>\n      <Install>false</Install>\n    </BootstrapperPackage>\n    <BootstrapperPackage Include=\"Microsoft.Windows.Installer.3.1\">\n      <Visible>False</Visible>\n      <ProductName>Windows Installer 3.1</ProductName>\n      <Install>true</Install>\n    </BootstrapperPackage>\n  </ItemGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Costura.Fody\">\n      <Version>6.0.0</Version>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n    <PackageReference Include=\"F23.StringSimilarity\">\n      <Version>6.0.0</Version>\n    </PackageReference>\n    <PackageReference Include=\"MSBuildTasks\">\n      <Version>1.5.0.235</Version>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n    <PackageReference Include=\"Newtonsoft.Json\">\n      <Version>13.0.4</Version>\n    </PackageReference>\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <Target Name=\"AfterBuild\" Condition=\"'$(Configuration)'=='Release'\">\n    <ItemGroup>\n      <ZipFiles Include=\"pkgtemp\\\" />\n    </ItemGroup>\n    <PropertyGroup>\n      <MyAssemblies>$(TargetPath)</MyAssemblies>\n    </PropertyGroup>\n    <GetAssemblyIdentity AssemblyFiles=\"$(MyAssemblies)\">\n      <Output TaskParameter=\"Assemblies\" ItemName=\"MyAssemblyIdentities\" />\n    </GetAssemblyIdentity>\n    <RemoveDir Directories=\"@(ZipFiles)\" />\n    <Copy SourceFiles=\"$(TargetPath)\" DestinationFolder=\"pkgtemp\\\" />\n    <Zip ZipFileName=\"pkg\\$(ProjectName)_%(MyAssemblyIdentities.Version).zip\" WorkingDirectory=\"pkg\" Files=\"@(ZipFiles)\" Flatten=\"true\" Quiet=\"true\" />\n    <RemoveDir Directories=\"@(ZipFiles)\" />\n  </Target>\n</Project>"
  },
  {
    "path": "NeteaseLyrics.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 18\nVisualStudioVersion = 18.0.11111.16 d18.0\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"NeteaseLyrics\", \"NeteaseLyrics.csproj\", \"{F5D46BA1-6F21-40EF-9695-46105CCACD08}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|x86.ActiveCfg = Debug|x86\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|x86.Build.0 = Debug|x86\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|x86.ActiveCfg = Release|x86\n\t\t{F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|x86.Build.0 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "NeteaseMusicEncryptionHandler.cs",
    "content": "using System.IO;\nusing System.Linq;\nusing System.Numerics;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System;\n\nnamespace MusicBeePlugin\n{\n    /*\n     * Adopted from https://github.com/real-zony/ZonyLrcToolsX/blob/dev/src/ZonyLrcTools.Common/Infrastructure/Encryption/NetEaseMusicEncryptionHelper.cs\n     * (MIT License)\n     * and  https://github.com/jitwxs/163MusicLyrics/blob/master/MusicLyricApp/Api/Music/NetEaseMusicNativeApi.cs\n     * and  https://github.com/mos9527/pyncm/blob/ad0a84b2ed5f1affa9890d5f54f6170c2cf99bbb/pyncm/utils/crypto.py#L53\n     */\n    internal static class NeteaseMusicEncryptionHelper\n    {\n        private const string Modulus =\n            \"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7\";\n\n        internal const string Nonce = \"0CoJUm6Qyw8W8jud\";\n        private const string PubKey = \"010001\";\n        private const string Vi = \"0102030405060708\";\n        private static readonly byte[] ID_XOR_KEY_1 = Encoding.UTF8.GetBytes(\"3go8&$8*3*3h0k(2)2\");\n\n        public static string RsaEncode(string text)\n        {\n            var srText = new string(text.Reverse().ToArray());\n            var a = BCHexDec(BitConverter.ToString(Encoding.Default.GetBytes(srText)).Replace(\"-\", string.Empty));\n            var b = BCHexDec(PubKey);\n            var c = BCHexDec(Modulus);\n            var key = BigInteger.ModPow(a, b, c).ToString(\"x\");\n            key = key.PadLeft(256, '0');\n\n            return key.Length > 256 ? key.Substring(key.Length - 256, 256) : key;\n        }\n\n        public static BigInteger BCHexDec(string hex)\n        {\n            var dec = new BigInteger(0);\n            var len = hex.Length;\n\n            for (var i = 0; i < len; i++)\n            {\n                dec += BigInteger.Multiply(new BigInteger(Convert.ToInt32(hex[i].ToString(), 16)),\n                    BigInteger.Pow(new BigInteger(16), len - i - 1));\n            }\n\n            return dec;\n        }\n\n        public static string AesEncode(string secretData, string secret = \"TA3YiYCfY2dDJQgg\")\n        {\n            byte[] encrypted;\n            var iv = Encoding.UTF8.GetBytes(Vi);\n\n            using (var aes = Aes.Create())\n            {\n                aes.Key = Encoding.UTF8.GetBytes(secret);\n                aes.IV = iv;\n                aes.Mode = CipherMode.CBC;\n                using (var encryptor = aes.CreateEncryptor())\n                {\n                    using (var stream = new MemoryStream())\n                    {\n                        using (var cryptoStream = new CryptoStream(stream, encryptor, CryptoStreamMode.Write))\n                        {\n                            using (var sw = new StreamWriter(cryptoStream))\n                            {\n                                sw.Write(secretData);\n                            }\n\n                            encrypted = stream.ToArray();\n                        }\n                    }\n                }\n            }\n\n            return Convert.ToBase64String(encrypted);\n        }\n\n        public static string CreateSecretKey(int length)\n        {\n            const string str = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n            var sb = new StringBuilder(length);\n            var rnd = new Random();\n\n            for (var i = 0; i < length; ++i)\n            {\n                sb.Append(str[rnd.Next(0, str.Length)]);\n            }\n\n            return sb.ToString();\n        }\n\n        public static string CloudMusicDllEncode(string deviceId)\n        {\n            var xored = new byte[deviceId.Length];\n            for (var i = 0; i < deviceId.Length; i++)\n            {\n                xored[i] = (byte)(deviceId[i] ^ ID_XOR_KEY_1[i % ID_XOR_KEY_1.Length]);\n            }\n\n            using (var md5 = MD5.Create())\n            {\n                var digest = md5.ComputeHash(xored);\n                return Convert.ToBase64String(digest);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Properties/AssemblyInfo.cs",
    "content": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following \n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"NeteaseLyrics\")]\n[assembly: AssemblyDescription(\"A plugin to retrieve lyrics from Netease Cloud Music.\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"NeteaseLyrics\")]\n[assembly: AssemblyCopyright(\"Copyright © chariri 2024\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible \n// to COM components.  If you need to access a type in this assembly from \n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"34d2725e-ae30-4f65-ad54-9eade3bb5973\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version \n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers \n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.9.0.0\")]\n[assembly: AssemblyFileVersion(\"1.9.0.0\")]\n"
  },
  {
    "path": "README.MD",
    "content": "# Netease Lyric\n\nThis is another lyrics retrieval plugin for music player MusicBee. It can get **synchronized** lyrics from Netease Cloud Music(网易云音乐, a cloud music service in China).\n\nIt can combine the translation to the lyrics if available, like:  \nBlalalala(Original)  \nAlalalala(Translation)  \nis wrapped to: Blalalala/Alalalalala  \netc..\n\nBut if you don't want this function you can disable it in plugin settings.\n\n## Usage\nTo use this plugin, download it from links below:  \nFor users who is not in China:  \n[OneDrive](https://1drv.ms/f/s!AicHZ6DLvCtX6Qp6KQfEppoQtjLG)  \nFor users who is in China:  \n[BaiduYun](https://pan.baidu.com/s/1ZL9dyrAczhPSMvMtvvG5yA?pwd=vede) , passcode: `vede`\nInstall(via \"Add plugin\" button) and enable it in the `Plugin` tab in the preferences.  \nAnd adjust the retrieving provider priority of \"Netease Cloud Music\" in the Tags(2) Tab.  \nIf you want a specific song's lyric from NetEase Cloud Music to be matched, you may set the `custom10` tag to the song URL like `https://music.163.com/#/song?id=29126914` (You can directly copy URL like this from the Netease website or client) or `netease=123123` (where \"123123\" should be your song id) in the music and re-search lyrics.\n\n## For non-Chinese users \nAnd this plugin is also useful for people who aren't from China as Netease CloudMusic also has bunch of songs in other languages. You can disable the translation in the plugin settings.\n\n# 网易云音乐歌词\n\n这是MusicBee另一个获取歌词的插件。可以从网易云音乐获取**同步歌词**。  \n\n该插件可以合并歌词翻译（如果有的话）到歌词。例如：  \n巴拉巴拉巴拉巴（原句）  \n阿啦啦啦啦啦（翻译）  \n会被合并为：巴拉巴拉巴拉巴/阿啦啦啦啦啦  \n\n如果不想要这个功能可以在设置里面的插件设置中关闭。\n如果想设置网易云音乐中特定的歌曲，可以在 `标签 (2)` 标签页给歌曲设定 `custom10` 标签，内容为曲目的 URL，比如 `https://music.163.com/#/song?id=29126914`（可以直接从网易云网站或客户端复制下来），或者 `netease=123123`，其中 123123 是曲目的 ID，并重新搜索歌词。\n\n## 使用\n从[度盘](https://pan.baidu.com/s/1ZL9dyrAczhPSMvMtvvG5yA?pwd=vede)下载（提取码 `vede`），并在首选项的 `插件` 中安装（“添加插件”按钮）、启用插件，在 `标签 (2)` 标签页调整歌词的提供者优先级。\n"
  },
  {
    "path": "SearchMatch.cs",
    "content": "using F23.StringSimilarity;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.RegularExpressions;\n\nnamespace MusicBeePlugin\n{\n    internal static class SearchMatch\n    {\n        /// <summary>\n        /// 有多个 artist 时，分割各 artist 的符号。\n        /// 部分圈子的人喜欢用比较有个性的分割符，比如“ x ”，此事在《みんなみくみくにしてあげる♪》中亦有记载\n        /// </summary>\n        private static readonly string[] Delimiters = {\"/\", \"&\", \",\", \"，\", \" x \", \" * \", \"\\u00d7\", \"\\u00B7\"};\n        private static readonly Regex FeatPatternWithoutParenthesis = new Regex(@\"\\s+feat(.+)\");\n        private static readonly Regex FeatPatternWithParenthesis = new Regex(@\"\\s*\\(feat(.+)\\)\");\n\n        public static long SearchAndMatch(string title, string artist, string album, long duration)\n        {\n            var (titleWithoutArtist, artists) = SplitTitleArtist(title, artist);\n            var artistsStr = string.Join(\" \", artists);\n            var results = new HashSet<SearchResultSong>(new IdOnlyEqualityComparer());\n            results.UnionWith(NeteaseApi.Search(titleWithoutArtist));\n            results.UnionWith(NeteaseApi.Search($\"{titleWithoutArtist} {artistsStr}\"));\n            results.UnionWith(NeteaseApi.Search($\"{titleWithoutArtist} {artistsStr} {album}\"));\n\n            if (results.Count <= 0)\n                return 0;\n            var ranked = results.Select(it => (\n                rank: CalculateMatchScore(it, titleWithoutArtist, artistsStr, album, duration),\n                it.id, // prevent comparer from checking `song`, because SearchResultSong is not comparable.\n                song: it)\n            ).ToList();\n            ranked.Sort();\n            return ranked.Max().song.id;\n        }\n\n        private static double CalculateMatchScore(\n            SearchResultSong song, string titleWithoutArtist, string artistsStr,\n            string album, long duration)\n        {\n            var resultArtists = song.artists.Select(it => it.name).ToList();\n            resultArtists.Sort();\n            var resultArtistsStr = string.Join(\" \", resultArtists);\n\n            // “距离”公式：\n            // 歌曲长度距离^2 + 标题距离 * 2 + 表演者距离 * 0.7 + 专辑距离 * 1\n            // 因为长度是比较重要的 metrics，并且当长度差得超过一定距离的时候应该起到“一票否决”的效果，因此使用了平方\n            var l = new Levenshtein();\n            var durationDiff = (duration / 1000.0 - song.duration / 1000.0);\n            var score = -(durationDiff * durationDiff);\n            score -= l.Distance(titleWithoutArtist, song.name) * 2;\n            score -= l.Distance(artistsStr, resultArtistsStr) * 0.7;\n            score -= l.Distance(album, song.album.name);\n            return score;\n        }\n\n        /// <summary>\n        /// 有些人会把 (feat. Somebody) 这样的信息写在曲目标题里面，\n        /// 并且网易云不会做特殊处理（网易云本身支持多 artist），因此会搜出来一堆奇怪的结果。\n        /// 因此需要先把这些 feat. 的子句提出来放到 artist 里面。\n        /// 当然 Artist 里面的也需要处理\n        /// </summary>\n        /// <returns></returns>\n        public static (string, IEnumerable<string>) SplitTitleArtist(string title, string artist)\n        {\n            var (artistsWithoutFeat, featArtists) = ExtractFeat(SanitizeString(artist));\n\n            var artists = artistsWithoutFeat.Split(Delimiters, StringSplitOptions.RemoveEmptyEntries)\n                .Select(it => it.Trim())\n                .ToList();\n            artists.AddRange(featArtists);\n            \n            var (titleWithoutFeat, featArtists2) = ExtractFeat(SanitizeString(title));\n            artists.AddRange(featArtists2);\n            \n            artists.Sort();\n            return (titleWithoutFeat, artists);\n        }\n\n        private static string SanitizeString(string str)\n        {\n            return str.Replace('（', '(').Replace('）', ')').Replace('\\u00A0', ' ');\n        }\n\n        private static (string, IEnumerable<string>) ExtractFeat(string str)\n        {\n            var match = FeatPatternWithParenthesis.Match(str);\n            if (!match.Success)\n                match = FeatPatternWithoutParenthesis.Match(str);\n            if (!match.Success) return (str, Enumerable.Empty<string>());\n\n            str = str.Remove(match.Index, match.Length).Trim();\n            if (match.Groups.Count <= 0) return (str, Enumerable.Empty<string>());\n\n            var featClause = match.Groups[1].Captures[0].Value;\n            featClause = featClause.TrimStart('.', ' ');\n            if (featClause.EndsWith(\")\"))\n                featClause = featClause.Substring(0, featClause.Length - 1);\n\n            return (str, featClause.Split(Delimiters, StringSplitOptions.RemoveEmptyEntries).Select(it => it.Trim()));\n        }\n\n        private class IdOnlyEqualityComparer : EqualityComparer<SearchResultSong>\n        {\n            public override bool Equals(SearchResultSong x, SearchResultSong y)\n            {\n                if (ReferenceEquals(x, y)) return true;\n                if (x is null) return false;\n                if (y is null) return false;\n                if (x.GetType() != y.GetType()) return false;\n                return x.id == y.id;\n            }\n\n            public override int GetHashCode(SearchResultSong obj)\n            {\n                return obj.id.GetHashCode();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "SearchMatchLegacy.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Text.RegularExpressions;\n\nnamespace MusicBeePlugin\n{\n    /// <summary>\n    /// 旧的匹配设置\n    /// </summary>\n    internal static class SearchMatchLegacy\n    {\n        public static long QueryWithFeatRemoved(string trackTitle, string artist, bool fuzzy)\n        {\n            var ret = Query(trackTitle, artist, fuzzy);\n            if (ret != null) return ret.id;\n\n            ret = Query(RemoveLeadingNumber(RemoveFeat(trackTitle)), artist, fuzzy);\n            return ret?.id ?? 0;\n        }\n\n        private static SearchResultSong Query(string trackTitle, string artist, bool fuzzy)\n        {\n            var ret = NeteaseApi.Search(trackTitle + \" \" + artist)?.Where(rst =>\n                fuzzy || string.Equals(GetFirstSeq(RemoveLeadingNumber(rst.name)), GetFirstSeq(trackTitle),\n                    StringComparison.OrdinalIgnoreCase)).ToList();\n            if (ret != null && ret.Count > 0) return ret[0];\n\n            ret = NeteaseApi.Search(trackTitle)?.Where(rst =>\n                fuzzy || string.Equals(GetFirstSeq(RemoveLeadingNumber(rst.name)), GetFirstSeq(trackTitle),\n                    StringComparison.OrdinalIgnoreCase)).ToList();\n            return ret != null && ret.Count > 0 ? ret[0] : null;\n        }\n\n        private static string GetFirstSeq(string s)\n        {\n            s = s.Replace(\"\\u00A0\", \" \");\n            var pos = s.IndexOf(' ');\n            return s.Substring(0, pos == -1 ? s.Length : pos).Trim();\n        }\n\n        private static string RemoveFeat(string name)\n        {\n            return Regex.Replace(name, @\"\\s*\\(feat.+\\)\", \"\", RegexOptions.IgnoreCase);\n        }\n\n        private static string RemoveLeadingNumber(string name)\n        {\n            return Regex.Replace(name, @\"^\\d+\\.?\\s*\", \"\", RegexOptions.IgnoreCase);\n        }\n    }\n}\n"
  },
  {
    "path": "app.config",
    "content": "<?xml version=\"1.0\"?>\n<configuration>\n\t<startup><supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.8\"/></startup></configuration>\n"
  },
  {
    "path": "packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Costura.Fody\" version=\"3.2.2\" targetFramework=\"net40\" />\n  <package id=\"Fody\" version=\"3.3.5\" targetFramework=\"net40\" developmentDependency=\"true\" />\n  <package id=\"MSBuildTasks\" version=\"1.5.0.235\" targetFramework=\"net40\" developmentDependency=\"true\" />\n  <package id=\"Newtonsoft.Json\" version=\"13.0.1\" targetFramework=\"net40\" />\n</packages>"
  }
]