├── .editorconfig
├── .gitattributes
├── .gitignore
├── CI
├── appveyor_deploy.ps1
├── appveyor_install.ps1
└── patch_buildinfo.ps1
├── DGJv3.sln
├── DGJv3
├── BilibiliDM_PluginFramework.dll
├── BilibiliDM_PluginFramework.xml
├── BlackListItem.cs
├── BlackListType.cs
├── BlackListTypeStringConverter.cs
├── BuildInfo.txt
├── Config.cs
├── DGJMain.cs
├── DGJWindow.xaml
├── DGJWindow.xaml.cs
├── DGJv3.csproj
├── DanmuHandler.cs
├── DownloadStatus.cs
├── Downloader.cs
├── EnumerationExtension.cs
├── EqualsVisibilityConverter.cs
├── Extensions.cs
├── FuckRegsvr32.cs
├── InternalModule
│ ├── LwlApiBaidu.cs
│ ├── LwlApiBaseModule.cs
│ ├── LwlApiKugou.cs
│ ├── LwlApiNetease.cs
│ ├── LwlApiTencent.cs
│ └── LwlApiXiami.cs
├── LogEvent.cs
├── LoginCenter.dll
├── LoginCenterAPIWarpper.cs
├── Lrc.cs
├── LyricChangedEvent.cs
├── NotEqualsVisibilityConverter.cs
├── NullSearchModule.cs
├── Player.cs
├── PlayerStatus.cs
├── PlayerType.cs
├── PlayerVolumeConverter.cs
├── Properties
│ └── AssemblyInfo.cs
├── SearchModule.cs
├── SearchModules.cs
├── SongInfo.cs
├── SongItem.cs
├── SongStatus.cs
├── SongStatusStringConverter.cs
├── UniversalCommand.cs
├── Utilities.cs
├── VersionChecker.cs
├── WaveoutEventDeviceInfo.cs
├── Writer.cs
└── packages.config
├── DllExport.bat
├── LICENSE
├── README.md
├── VERSION
└── appveyor.yml
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; EditorConfig to support per-solution formatting.
2 | ; Use the EditorConfig VS add-in to make this work.
3 | ; http://editorconfig.org/
4 |
5 | ; This is the default for the codeline.
6 | root = true
7 |
8 | [*]
9 | end_of_line = CRLF
10 |
11 | [*.{config,cs,xml}]
12 | indent_style = space
13 | indent_size = 4
14 | trim_trailing_whitespace = true
15 |
16 | [*.{proj,props,sln,targets}]
17 | indent_style = tab
18 | trim_trailing_whitespace = true
19 |
20 | [*.{kproj,csproj,json,ps1,psd1,psm1,resx,rst}]
21 | indent_style = space
22 | indent_size = 2
23 | trim_trailing_whitespace = true
24 |
25 | [NuGet.Config]
26 | indent_style = space
27 | indent_size = 2
28 | trim_trailing_whitespace = true
29 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | DGJv3/BuildInfo.cs
7 |
8 | # User-specific files
9 | *.suo
10 | *.user
11 | *.userosscache
12 | *.sln.docstates
13 |
14 | # User-specific files (MonoDevelop/Xamarin Studio)
15 | *.userprefs
16 |
17 | # Build results
18 | [Dd]ebug/
19 | [Dd]ebugPublic/
20 | [Rr]elease/
21 | [Rr]eleases/
22 | x64/
23 | x86/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 | [Ll]og/
28 |
29 | # Visual Studio 2015/2017 cache/options directory
30 | .vs/
31 | # Uncomment if you have tasks that create the project's static files in wwwroot
32 | #wwwroot/
33 |
34 | # Visual Studio 2017 auto generated files
35 | Generated\ Files/
36 |
37 | # MSTest test Results
38 | [Tt]est[Rr]esult*/
39 | [Bb]uild[Ll]og.*
40 |
41 | # NUNIT
42 | *.VisualState.xml
43 | TestResult.xml
44 |
45 | # Build Results of an ATL Project
46 | [Dd]ebugPS/
47 | [Rr]eleasePS/
48 | dlldata.c
49 |
50 | # Benchmark Results
51 | BenchmarkDotNet.Artifacts/
52 |
53 | # .NET Core
54 | project.lock.json
55 | project.fragment.lock.json
56 | artifacts/
57 | **/Properties/launchSettings.json
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_i.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *.log
83 | *.vspscc
84 | *.vssscc
85 | .builds
86 | *.pidb
87 | *.svclog
88 | *.scc
89 |
90 | # Chutzpah Test files
91 | _Chutzpah*
92 |
93 | # Visual C++ cache files
94 | ipch/
95 | *.aps
96 | *.ncb
97 | *.opendb
98 | *.opensdf
99 | *.sdf
100 | *.cachefile
101 | *.VC.db
102 | *.VC.VC.opendb
103 |
104 | # Visual Studio profiler
105 | *.psess
106 | *.vsp
107 | *.vspx
108 | *.sap
109 |
110 | # Visual Studio Trace Files
111 | *.e2e
112 |
113 | # TFS 2012 Local Workspace
114 | $tf/
115 |
116 | # Guidance Automation Toolkit
117 | *.gpState
118 |
119 | # ReSharper is a .NET coding add-in
120 | _ReSharper*/
121 | *.[Rr]e[Ss]harper
122 | *.DotSettings.user
123 |
124 | # JustCode is a .NET coding add-in
125 | .JustCode
126 |
127 | # TeamCity is a build add-in
128 | _TeamCity*
129 |
130 | # DotCover is a Code Coverage Tool
131 | *.dotCover
132 |
133 | # AxoCover is a Code Coverage Tool
134 | .axoCover/*
135 | !.axoCover/settings.json
136 |
137 | # Visual Studio code coverage results
138 | *.coverage
139 | *.coveragexml
140 |
141 | # NCrunch
142 | _NCrunch_*
143 | .*crunch*.local.xml
144 | nCrunchTemp_*
145 |
146 | # MightyMoose
147 | *.mm.*
148 | AutoTest.Net/
149 |
150 | # Web workbench (sass)
151 | .sass-cache/
152 |
153 | # Installshield output folder
154 | [Ee]xpress/
155 |
156 | # DocProject is a documentation generator add-in
157 | DocProject/buildhelp/
158 | DocProject/Help/*.HxT
159 | DocProject/Help/*.HxC
160 | DocProject/Help/*.hhc
161 | DocProject/Help/*.hhk
162 | DocProject/Help/*.hhp
163 | DocProject/Help/Html2
164 | DocProject/Help/html
165 |
166 | # Click-Once directory
167 | publish/
168 |
169 | # Publish Web Output
170 | *.[Pp]ublish.xml
171 | *.azurePubxml
172 | # Note: Comment the next line if you want to checkin your web deploy settings,
173 | # but database connection strings (with potential passwords) will be unencrypted
174 | *.pubxml
175 | *.publishproj
176 |
177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
178 | # checkin your Azure Web App publish settings, but sensitive information contained
179 | # in these scripts will be unencrypted
180 | PublishScripts/
181 |
182 | # NuGet Packages
183 | *.nupkg
184 | # The packages folder can be ignored because of Package Restore
185 | **/[Pp]ackages/*
186 | # except build/, which is used as an MSBuild target.
187 | !**/[Pp]ackages/build/
188 | # Uncomment if necessary however generally it will be regenerated when needed
189 | #!**/[Pp]ackages/repositories.config
190 | # NuGet v3's project.json files produces more ignorable files
191 | *.nuget.props
192 | *.nuget.targets
193 |
194 | # Microsoft Azure Build Output
195 | csx/
196 | *.build.csdef
197 |
198 | # Microsoft Azure Emulator
199 | ecf/
200 | rcf/
201 |
202 | # Windows Store app package directories and files
203 | AppPackages/
204 | BundleArtifacts/
205 | Package.StoreAssociation.xml
206 | _pkginfo.txt
207 | *.appx
208 |
209 | # Visual Studio cache files
210 | # files ending in .cache can be ignored
211 | *.[Cc]ache
212 | # but keep track of directories ending in .cache
213 | !*.[Cc]ache/
214 |
215 | # Others
216 | ClientBin/
217 | ~$*
218 | *~
219 | *.dbmdl
220 | *.dbproj.schemaview
221 | *.jfm
222 | *.pfx
223 | *.publishsettings
224 | orleans.codegen.cs
225 |
226 | # Including strong name files can present a security risk
227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
228 | #*.snk
229 |
230 | # Since there are multiple workflows, uncomment next line to ignore bower_components
231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
232 | #bower_components/
233 |
234 | # RIA/Silverlight projects
235 | Generated_Code/
236 |
237 | # Backup & report files from converting an old project file
238 | # to a newer Visual Studio version. Backup files are not needed,
239 | # because we have git ;-)
240 | _UpgradeReport_Files/
241 | Backup*/
242 | UpgradeLog*.XML
243 | UpgradeLog*.htm
244 | ServiceFabricBackup/
245 | *.rptproj.bak
246 |
247 | # SQL Server files
248 | *.mdf
249 | *.ldf
250 | *.ndf
251 |
252 | # Business Intelligence projects
253 | *.rdl.data
254 | *.bim.layout
255 | *.bim_*.settings
256 | *.rptproj.rsuser
257 |
258 | # Microsoft Fakes
259 | FakesAssemblies/
260 |
261 | # GhostDoc plugin setting file
262 | *.GhostDoc.xml
263 |
264 | # Node.js Tools for Visual Studio
265 | .ntvs_analysis.dat
266 | node_modules/
267 |
268 | # Visual Studio 6 build log
269 | *.plg
270 |
271 | # Visual Studio 6 workspace options file
272 | *.opt
273 |
274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
275 | *.vbw
276 |
277 | # Visual Studio LightSwitch build output
278 | **/*.HTMLClient/GeneratedArtifacts
279 | **/*.DesktopClient/GeneratedArtifacts
280 | **/*.DesktopClient/ModelManifest.xml
281 | **/*.Server/GeneratedArtifacts
282 | **/*.Server/ModelManifest.xml
283 | _Pvt_Extensions
284 |
285 | # Paket dependency manager
286 | .paket/paket.exe
287 | paket-files/
288 |
289 | # FAKE - F# Make
290 | .fake/
291 |
292 | # JetBrains Rider
293 | .idea/
294 | *.sln.iml
295 |
296 | # CodeRush
297 | .cr/
298 |
299 | # Python Tools for Visual Studio (PTVS)
300 | __pycache__/
301 | *.pyc
302 |
303 | # Cake - Uncomment if you are using it
304 | # tools/**
305 | # !tools/packages.config
306 |
307 | # Tabs Studio
308 | *.tss
309 |
310 | # Telerik's JustMock configuration file
311 | *.jmconfig
312 |
313 | # BizTalk build output
314 | *.btp.cs
315 | *.btm.cs
316 | *.odx.cs
317 | *.xsd.cs
318 |
319 | # OpenCover UI analysis results
320 | OpenCover/
321 |
322 | # Azure Stream Analytics local run output
323 | ASALocalRun/
324 |
325 | # MSBuild Binary and Structured Log
326 | *.binlog
327 |
328 | # NVidia Nsight GPU debugger configuration file
329 | *.nvuser
330 |
331 | # MFractors (Xamarin productivity tool) working folder
332 | .mfractor/
333 |
--------------------------------------------------------------------------------
/CI/appveyor_deploy.ps1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bililive/DGJv3/3f4b62a3b286ab045e80d29944cf896d7d29f731/CI/appveyor_deploy.ps1
--------------------------------------------------------------------------------
/CI/appveyor_install.ps1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bililive/DGJv3/3f4b62a3b286ab045e80d29944cf896d7d29f731/CI/appveyor_install.ps1
--------------------------------------------------------------------------------
/CI/patch_buildinfo.ps1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bililive/DGJv3/3f4b62a3b286ab045e80d29944cf896d7d29f731/CI/patch_buildinfo.ps1
--------------------------------------------------------------------------------
/DGJv3.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27428.2027
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DGJv3", "DGJv3\DGJv3.csproj", "{53970AEC-FD0E-46ED-8269-3FD73DC3DD0F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {53970AEC-FD0E-46ED-8269-3FD73DC3DD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {53970AEC-FD0E-46ED-8269-3FD73DC3DD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {53970AEC-FD0E-46ED-8269-3FD73DC3DD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {53970AEC-FD0E-46ED-8269-3FD73DC3DD0F}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {0426D19B-D35A-40EB-AF6F-0999FFEED9FC}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/DGJv3/BilibiliDM_PluginFramework.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bililive/DGJv3/3f4b62a3b286ab045e80d29944cf896d7d29f731/DGJv3/BilibiliDM_PluginFramework.dll
--------------------------------------------------------------------------------
/DGJv3/BilibiliDM_PluginFramework.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | BilibiliDM_PluginFramework
5 |
6 |
7 |
8 |
9 | 插件名稱
10 |
11 |
12 |
13 |
14 | 插件作者
15 |
16 |
17 |
18 |
19 | 插件作者聯繫方式
20 |
21 |
22 |
23 |
24 | 插件版本號
25 |
26 |
27 |
28 |
29 | 插件描述
30 |
31 |
32 |
33 |
34 | 插件描述, 已過期, 請使用PluginDesc
35 |
36 |
37 |
38 |
39 | 插件狀態
40 |
41 |
42 |
43 |
44 | 當前連接中的房間
45 |
46 |
47 |
48 |
49 | 啟用插件方法 請重寫此方法
50 |
51 |
52 |
53 |
54 | 禁用插件方法 請重寫此方法
55 |
56 |
57 |
58 |
59 | 管理插件方法 請重寫此方法
60 |
61 |
62 |
63 |
64 | 此方法在所有插件加载完毕后调用
65 |
66 |
67 |
68 |
69 | 反初始化方法, 在弹幕姬主程序退出时调用, 若有需要请重写,
70 |
71 |
72 |
73 |
74 | 打日志
75 |
76 |
77 |
78 |
79 |
80 | 弹幕姬是否是以Debug模式启动的
81 |
82 |
83 |
84 |
85 | 打彈幕
86 |
87 |
88 |
89 |
90 |
91 |
92 | 发送伪春菜脚本, 前提是用户有打开伪春菜并允许弹幕姬和伪春菜联动(默认允许)
93 |
94 | Sakura Script脚本
95 |
96 |
97 |
98 | 彈幕
99 |
100 |
101 |
102 |
103 | 禮物
104 |
105 |
106 |
107 |
108 | 禮物排名
109 |
110 |
111 |
112 |
113 | 歡迎老爷
114 |
115 |
116 |
117 |
118 | 直播開始
119 |
120 |
121 |
122 |
123 | 直播結束
124 |
125 |
126 |
127 |
128 | 其他
129 |
130 |
131 |
132 |
133 | 欢迎船员
134 |
135 |
136 |
137 |
138 | 购买船票(上船)
139 |
140 |
141 |
142 |
143 | 消息類型
144 |
145 |
146 |
147 |
148 | 彈幕內容
149 | 此项有值的消息类型:
150 |
151 |
152 |
153 |
154 |
155 |
156 | 彈幕用戶
157 |
158 |
159 |
160 |
161 | 消息触发者用户名
162 | 此项有值的消息类型:
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | 消息触发者用户ID
174 | 此项有值的消息类型:
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | 用户舰队等级
186 | 0 为非船员 1 为总督 2 为提督 3 为舰长
187 | 此项有值的消息类型:
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | 禮物用戶
197 |
198 |
199 |
200 |
201 | 禮物名稱
202 |
203 |
204 |
205 |
206 | 禮物數量
207 |
208 |
209 |
210 |
211 | 礼物数量
212 | 此项有值的消息类型:
213 |
214 |
215 |
216 | 此字段也用于标识上船 的数量(月数)
217 |
218 |
219 |
220 |
221 | 当前房间的礼物积分(Room Cost)
222 | 因以前出现过不传递rcost的礼物,并且用处不大,所以弃用
223 |
224 |
225 |
226 |
227 | 禮物排行
228 | 此项有值的消息类型:
229 |
230 |
231 |
232 |
233 |
234 |
235 | 该用户是否为房管(包括主播)
236 | 此项有值的消息类型:
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 | 是否VIP用戶(老爺)
245 | 此项有值的消息类型:
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 | , 事件对应的房间号
254 |
255 |
256 |
257 |
258 | 原始数据, 高级开发用
259 |
260 |
261 |
262 |
263 | 内部用, JSON数据版本号 通常应该是2
264 |
265 |
266 |
267 |
268 | 原始数据, 高级开发用, 如果需要用原始的JSON数据, 建议使用这个而不是用RawData
269 |
270 |
271 |
272 |
273 | 用戶名
274 |
275 |
276 |
277 |
278 | 花銷
279 |
280 |
281 |
282 |
283 | UID
284 |
285 |
286 |
287 |
288 |
--------------------------------------------------------------------------------
/DGJv3/BlackListItem.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace DGJv3
7 | {
8 | class BlackListItem : INotifyPropertyChanged
9 | {
10 | [JsonProperty("type")]
11 | public BlackListType BlackType { get => blackType; set => SetField(ref blackType, value); }
12 |
13 | [JsonProperty("cont")]
14 | public string Content { get => content; set => SetField(ref content, value); }
15 |
16 | private BlackListType blackType = BlackListType.Id;
17 | private string content = string.Empty;
18 |
19 | internal BlackListItem()
20 | { }
21 | internal BlackListItem(BlackListType type, string content)
22 | {
23 | this.BlackType = type;
24 | this.Content = content;
25 | }
26 |
27 | public event PropertyChangedEventHandler PropertyChanged;
28 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "")
29 | {
30 | if (EqualityComparer.Default.Equals(field, value)) return false;
31 | field = value;
32 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
33 | return true;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DGJv3/BlackListType.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace DGJv3
4 | {
5 | public enum BlackListType
6 | {
7 | [Description("歌曲ID")]
8 | Id,
9 | [Description("歌曲名字")]
10 | Name,
11 | [Description("歌手名字")]
12 | Singer
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/DGJv3/BlackListTypeStringConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace DGJv3
6 | {
7 | class BlackListTypeStringConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | if (value is BlackListType blackListType)
12 | {
13 | switch (blackListType)
14 | {
15 | case BlackListType.Id:
16 | return "歌曲ID";
17 | case BlackListType.Name:
18 | return "歌曲名字";
19 | case BlackListType.Singer:
20 | return "歌手名字";
21 | default:
22 | break;
23 | }
24 | }
25 | throw new NotImplementedException();
26 | }
27 |
28 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
29 | {
30 | throw new NotImplementedException();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/DGJv3/BuildInfo.txt:
--------------------------------------------------------------------------------
1 | namespace DGJv3
2 | {
3 | internal class BuildInfo
4 | {
5 | internal const bool Appveyor = [APPVEYOR];
6 | internal const string Version = @"[VERSION]";
7 | internal const string HeadSha1 = @"[GIT_HASH]";
8 | internal const string HeadShaShort = @"[GIT_HASH_S]";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/DGJv3/Config.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace DGJv3
10 | {
11 | class Config
12 | {
13 | [JsonProperty("ptyp")]
14 | public PlayerType PlayerType { get; set; } = PlayerType.DirectSound;
15 |
16 | [JsonProperty("pdsd")]
17 | public Guid DirectSoundDevice { get; set; } = Guid.Empty;
18 |
19 | [JsonProperty("pwed")]
20 | public int WaveoutEventDevice { get; set; } = -1;
21 |
22 | [JsonProperty("pvol")]
23 | public float Volume { get; set; } = 0.5f;
24 |
25 | [JsonProperty("pple")]
26 | public bool IsPlaylistEnabled { get; set; } = true;
27 |
28 | [JsonProperty("mpid")]
29 | public string PrimaryModuleId { get; set; }
30 |
31 | [JsonProperty("msid")]
32 | public string SecondaryModuleId { get; set; }
33 |
34 | [JsonProperty("dmts")]
35 | public uint MaxTotalSongNum { get; set; } = 10;
36 |
37 | [JsonProperty("dmps")]
38 | public uint MaxPersonSongNum { get; set; } = 2;
39 |
40 | [JsonProperty("up")]
41 | public bool IsUserPrior { get; set; } = true;
42 |
43 | [JsonProperty("lrd")]
44 | public bool IsLogRedirectDanmaku { get; set; } = false;
45 |
46 | [JsonProperty("ldll")]
47 | public int LogDanmakuLengthLimit { get; set; } = 20;
48 |
49 | [JsonProperty("plst")]
50 | public SongInfo[] Playlist { get; set; } = new SongInfo[0];
51 |
52 | [JsonProperty("blst")]
53 | public BlackListItem[] Blacklist { get; set; } = new BlackListItem[0];
54 |
55 | [JsonProperty("sbtp")]
56 | public string ScribanTemplate { get; set; } = "播放进度 {{当前播放时间}}/{{当前总时间}}\n" +
57 | "当前列表中有 {{ 歌曲数量 }} 首歌\n还可以再点 {{ 总共最大点歌数量 - 歌曲数量 }} 首歌\n" +
58 | "每个人可以点 {{ 单人最大点歌数量 }} 首歌\n\n歌名 - 点歌人 - 歌手 - 歌曲平台\n" +
59 | "{{~ for 歌曲 in 播放列表 ~}}\n" +
60 | "{{ 歌曲.歌名 }} - {{ 歌曲.点歌人 }} - {{ 歌曲.歌手 }} - {{ 歌曲.搜索模块 }}\n" +
61 | "{{~ end ~}}";
62 |
63 | public Config()
64 | {
65 | }
66 |
67 | #pragma warning disable CS0168 // 声明了变量,但从未使用过
68 | internal static Config Load(bool reset = false)
69 | {
70 | Config config = new Config();
71 | if (!reset)
72 | {
73 | try
74 | {
75 | var str = File.ReadAllText(Utilities.ConfigFilePath, Encoding.UTF8);
76 | config = JsonConvert.DeserializeObject(str);
77 | }
78 |
79 | catch (Exception ex)
80 | {
81 | }
82 | }
83 | return config;
84 | }
85 |
86 | internal static void Write(Config config)
87 | {
88 | try
89 | {
90 | File.WriteAllText(Utilities.ConfigFilePath, JsonConvert.SerializeObject(config), Encoding.UTF8);
91 | }
92 | catch (Exception ex)
93 | {
94 | }
95 | }
96 | #pragma warning restore CS0168 // 声明了变量,但从未使用过
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/DGJv3/DGJMain.cs:
--------------------------------------------------------------------------------
1 | using BilibiliDM_PluginFramework;
2 | using System;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Reflection;
6 | using System.Threading.Tasks;
7 |
8 | namespace DGJv3
9 | {
10 | public class DGJMain : DMPlugin
11 | {
12 | private readonly DGJWindow window;
13 |
14 | private VersionChecker versionChecker;
15 |
16 | public DGJMain()
17 | {
18 | try
19 | {
20 | var info = Directory.CreateDirectory(Utilities.BinDirectoryPath);
21 | info.Attributes = FileAttributes.Directory | FileAttributes.Hidden;
22 | }
23 | catch (Exception) { }
24 | AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
25 |
26 | PluginName = "点歌姬";
27 | PluginVer = BuildInfo.Version;
28 | PluginDesc = "使用弹幕点播歌曲";
29 | PluginAuth = "Genteure";
30 | PluginCont = "dgj3@genteure.com";
31 |
32 | try
33 | {
34 | Directory.CreateDirectory(Utilities.DataDirectoryPath);
35 | }
36 | catch (Exception) { }
37 | window = new DGJWindow(this);
38 | versionChecker = new VersionChecker("DGJv3");
39 | Task.Run(() =>
40 | {
41 | if (versionChecker.FetchInfo())
42 | {
43 | Version current = new Version(BuildInfo.Version);
44 |
45 | if (versionChecker.HasNewVersion(current))
46 | {
47 | Log("插件有新版本" + Environment.NewLine +
48 | $"当前版本:{BuildInfo.Version}" + Environment.NewLine +
49 | $"最新版本:{versionChecker.Version.ToString()} 更新时间:{versionChecker.UpdateDateTime.ToShortDateString()}" + Environment.NewLine +
50 | versionChecker.UpdateDescription);
51 | }
52 | }
53 | else
54 | {
55 | Log("版本检查出错:" + versionChecker?.LastException?.Message);
56 | }
57 | });
58 | }
59 |
60 | public override void Admin()
61 | {
62 | window.Show();
63 | window.Activate();
64 | }
65 |
66 | public override void DeInit() => window.DeInit();
67 |
68 | private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
69 | {
70 | Assembly executingAssembly = Assembly.GetExecutingAssembly();
71 | AssemblyName assemblyName = new AssemblyName(args.Name);
72 |
73 | var path = assemblyName.Name + ".dll";
74 | string filepath = Path.Combine(Utilities.BinDirectoryPath, path);
75 |
76 | if (assemblyName.CultureInfo?.Equals(CultureInfo.InvariantCulture) == false)
77 | { path = string.Format(@"{0}\{1}", assemblyName.CultureInfo, path); }
78 |
79 | using (Stream stream = executingAssembly.GetManifestResourceStream(path))
80 | {
81 | if (stream == null) { return null; }
82 |
83 | var assemblyRawBytes = new byte[stream.Length];
84 | stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
85 | try
86 | {
87 | File.WriteAllBytes(filepath, assemblyRawBytes);
88 | }
89 | catch (Exception) { }
90 | }
91 |
92 | return Assembly.LoadFrom(filepath);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/DGJv3/DGJWindow.xaml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | True
22 | False
23 | 0
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
64 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
150 |
153 |
154 |
155 | 歌名
156 |
157 |
158 |
162 | 确定
163 |
164 |
168 | 取消
169 |
170 |
171 |
172 |
173 | 添加空闲歌曲
174 |
175 |
178 |
179 |
180 |
181 | 歌单ID
182 | (在你网页浏览你的歌单时,可以在网址栏看到一长串数字,那就是歌单ID啦)
183 |
184 |
185 |
186 |
190 | 确定
191 |
192 |
196 | 取消
197 |
198 |
199 |
200 |
201 | 添加空闲歌单
202 |
203 |
204 |
205 |
208 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
223 |
227 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
245 | 缓存歌曲中
246 |
247 |
248 |
249 |
251 |
252 |
253 |
254 |
256 | 缓存歌曲中
257 |
258 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
276 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 | /
286 |
287 |
288 |
289 |
291 |
292 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 | 设置
303 |
304 |
305 |
306 |
307 | 没有点歌时从列表随机播放
308 |
309 |
310 |
311 | 用户点歌优先于空闲歌单播放
312 |
313 |
314 |
315 | 点歌反馈发送到弹幕(需要登录中心)
316 |
317 |
320 |
323 |
326 |
327 |
328 |
329 |
330 |
331 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 | 歌曲黑名单
343 |
344 |
353 |
354 |
355 |
356 |
357 |
358 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
385 |
386 |
387 | 添加黑名单
388 |
391 |
392 |
393 |
397 | 确定
398 |
399 |
403 | 取消
404 |
405 |
406 |
407 |
408 | 新建黑名单
409 |
410 |
411 |
412 |
413 |
414 |
415 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
432 |
436 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
--------------------------------------------------------------------------------
/DGJv3/DGJWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using DGJv3.InternalModule;
2 | using MaterialDesignThemes.Wpf;
3 | using Newtonsoft.Json.Linq;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Collections.ObjectModel;
7 | using System.ComponentModel;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 |
13 | namespace DGJv3
14 | {
15 | ///
16 | /// DGJWindow.xaml 的交互逻辑
17 | ///
18 | internal partial class DGJWindow : Window
19 | {
20 | public DGJMain PluginMain { get; set; }
21 |
22 | public ObservableCollection Songs { get; set; }
23 |
24 | public ObservableCollection Playlist { get; set; }
25 |
26 | public ObservableCollection Blacklist { get; set; }
27 |
28 | public Player Player { get; set; }
29 |
30 | public Downloader Downloader { get; set; }
31 |
32 | public Writer Writer { get; set; }
33 |
34 | public SearchModules SearchModules { get; set; }
35 |
36 | public DanmuHandler DanmuHandler { get; set; }
37 |
38 | public UniversalCommand RemoveSongCommmand { get; set; }
39 |
40 | public UniversalCommand RemoveAndBlacklistSongCommand { get; set; }
41 |
42 | public UniversalCommand RemovePlaylistInfoCommmand { get; set; }
43 |
44 | public UniversalCommand ClearPlaylistCommand { get; set; }
45 |
46 | public UniversalCommand RemoveBlacklistInfoCommmand { get; set; }
47 |
48 | public UniversalCommand ClearBlacklistCommand { get; set; }
49 |
50 | public bool IsLogRedirectDanmaku { get; set; }
51 |
52 | public int LogDanmakuLengthLimit { get; set; }
53 |
54 | public void Log(string text)
55 | {
56 | PluginMain.Log(text);
57 |
58 | if (IsLogRedirectDanmaku)
59 | {
60 | Task.Run(() =>
61 | {
62 | try
63 | {
64 | if (!PluginMain.RoomId.HasValue) { return; }
65 |
66 | string finalText = text.Substring(0, Math.Min(text.Length, LogDanmakuLengthLimit));
67 | string result = LoginCenterAPIWarpper.Send(PluginMain.RoomId.Value, finalText);
68 | if (result == null)
69 | {
70 | PluginMain.Log("发送弹幕时网络错误");
71 | }
72 | else
73 | {
74 | var j = JObject.Parse(result);
75 | if (j["msg"].ToString() != string.Empty)
76 | {
77 | PluginMain.Log("发送弹幕时服务器返回:" + j["msg"].ToString());
78 | }
79 | }
80 | }
81 | catch (Exception ex)
82 | {
83 | if (ex.GetType().FullName.Equals("LoginCenter.API.PluginNotAuthorizedException"))
84 | {
85 | IsLogRedirectDanmaku = false;
86 | }
87 | else
88 | {
89 | PluginMain.Log("弹幕发送错误 " + ex.ToString());
90 | }
91 | }
92 | });
93 | }
94 | }
95 |
96 | public DGJWindow(DGJMain dGJMain)
97 | {
98 | void addResource(string uri)
99 | {
100 | Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary()
101 | {
102 | Source = new Uri(uri)
103 | });
104 | }
105 | addResource("pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml");
106 | addResource("pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Blue.xaml");
107 | addResource("pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.DeepOrange.xaml");
108 | addResource("pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ProgressBar.xaml");
109 |
110 | DataContext = this;
111 | PluginMain = dGJMain;
112 | Songs = new ObservableCollection();
113 | Playlist = new ObservableCollection();
114 | Blacklist = new ObservableCollection();
115 |
116 | Player = new Player(Songs, Playlist);
117 | Downloader = new Downloader(Songs);
118 | SearchModules = new SearchModules();
119 | DanmuHandler = new DanmuHandler(Songs, Player, Downloader, SearchModules, Blacklist);
120 | Writer = new Writer(Songs, Playlist, Player, DanmuHandler);
121 |
122 | Player.LogEvent += (sender, e) => { Log("播放:" + e.Message + (e.Exception == null ? string.Empty : e.Exception.Message)); };
123 | Downloader.LogEvent += (sender, e) => { Log("下载:" + e.Message + (e.Exception == null ? string.Empty : e.Exception.Message)); };
124 | Writer.LogEvent += (sender, e) => { Log("文本:" + e.Message + (e.Exception == null ? string.Empty : e.Exception.Message)); };
125 | SearchModules.LogEvent += (sender, e) => { Log("搜索:" + e.Message + (e.Exception == null ? string.Empty : e.Exception.Message)); };
126 | DanmuHandler.LogEvent += (sender, e) => { Log("" + e.Message + (e.Exception == null ? string.Empty : e.Exception.Message)); };
127 |
128 | RemoveSongCommmand = new UniversalCommand((songobj) =>
129 | {
130 | if (songobj != null && songobj is SongItem songItem)
131 | {
132 | songItem.Remove(Songs, Downloader, Player);
133 | }
134 | });
135 |
136 | RemoveAndBlacklistSongCommand = new UniversalCommand((songobj) =>
137 | {
138 | if (songobj != null && songobj is SongItem songItem)
139 | {
140 | songItem.Remove(Songs, Downloader, Player);
141 | Blacklist.Add(new BlackListItem(BlackListType.Id, songItem.SongId));
142 | }
143 | });
144 |
145 | RemovePlaylistInfoCommmand = new UniversalCommand((songobj) =>
146 | {
147 | if (songobj != null && songobj is SongInfo songInfo)
148 | {
149 | Playlist.Remove(songInfo);
150 | }
151 | });
152 |
153 | ClearPlaylistCommand = new UniversalCommand((e) =>
154 | {
155 | Playlist.Clear();
156 | });
157 |
158 | RemoveBlacklistInfoCommmand = new UniversalCommand((blackobj) =>
159 | {
160 | if (blackobj != null && blackobj is BlackListItem blackListItem)
161 | {
162 | Blacklist.Remove(blackListItem);
163 | }
164 | });
165 |
166 | ClearBlacklistCommand = new UniversalCommand((x) =>
167 | {
168 | Blacklist.Clear();
169 | });
170 |
171 | InitializeComponent();
172 |
173 | ApplyConfig(Config.Load());
174 |
175 | PluginMain.ReceivedDanmaku += (sender, e) => { DanmuHandler.ProcessDanmu(e.Danmaku); };
176 | PluginMain.Connected += (sender, e) => { LwlApiBaseModule.RoomId = e.roomid; };
177 | PluginMain.Disconnected += (sender, e) => { LwlApiBaseModule.RoomId = 0; };
178 | }
179 |
180 | ///
181 | /// 应用设置
182 | ///
183 | ///
184 | private void ApplyConfig(Config config)
185 | {
186 | Player.PlayerType = config.PlayerType;
187 | Player.DirectSoundDevice = config.DirectSoundDevice;
188 | Player.WaveoutEventDevice = config.WaveoutEventDevice;
189 | Player.Volume = config.Volume;
190 | Player.IsUserPrior = config.IsUserPrior;
191 | Player.IsPlaylistEnabled = config.IsPlaylistEnabled;
192 | SearchModules.PrimaryModule = SearchModules.Modules.FirstOrDefault(x => x.UniqueId == config.PrimaryModuleId) ?? SearchModules.NullModule;
193 | SearchModules.SecondaryModule = SearchModules.Modules.FirstOrDefault(x => x.UniqueId == config.SecondaryModuleId) ?? SearchModules.NullModule;
194 | DanmuHandler.MaxTotalSongNum = config.MaxTotalSongNum;
195 | DanmuHandler.MaxPersonSongNum = config.MaxPersonSongNum;
196 | Writer.ScribanTemplate = config.ScribanTemplate;
197 | IsLogRedirectDanmaku = config.IsLogRedirectDanmaku;
198 | LogDanmakuLengthLimit = config.LogDanmakuLengthLimit;
199 |
200 | LogRedirectToggleButton.IsEnabled = LoginCenterAPIWarpper.CheckLoginCenter();
201 | if (LogRedirectToggleButton.IsEnabled && IsLogRedirectDanmaku)
202 | {
203 | Task.Run(async () =>
204 | {
205 | await Task.Delay(2000); // 其实不应该这么写的,不太合理
206 | IsLogRedirectDanmaku = await LoginCenterAPIWarpper.DoAuth(PluginMain);
207 | });
208 | }
209 | else
210 | {
211 | IsLogRedirectDanmaku = false;
212 | }
213 |
214 | Playlist.Clear();
215 | foreach (var item in config.Playlist)
216 | {
217 | item.Module = SearchModules.Modules.FirstOrDefault(x => x.UniqueId == item.ModuleId);
218 | if (item.Module != null)
219 | {
220 | Playlist.Add(item);
221 | }
222 | }
223 |
224 | Blacklist.Clear();
225 | foreach (var item in config.Blacklist)
226 | {
227 | Blacklist.Add(item);
228 | }
229 | }
230 |
231 | ///
232 | /// 收集设置
233 | ///
234 | ///
235 | private Config GatherConfig() => new Config()
236 | {
237 | PlayerType = Player.PlayerType,
238 | DirectSoundDevice = Player.DirectSoundDevice,
239 | WaveoutEventDevice = Player.WaveoutEventDevice,
240 | IsUserPrior = Player.IsUserPrior,
241 | Volume = Player.Volume,
242 | IsPlaylistEnabled = Player.IsPlaylistEnabled,
243 | PrimaryModuleId = SearchModules.PrimaryModule.UniqueId,
244 | SecondaryModuleId = SearchModules.SecondaryModule.UniqueId,
245 | MaxPersonSongNum = DanmuHandler.MaxPersonSongNum,
246 | MaxTotalSongNum = DanmuHandler.MaxTotalSongNum,
247 | ScribanTemplate = Writer.ScribanTemplate,
248 | Playlist = Playlist.ToArray(),
249 | Blacklist = Blacklist.ToArray(),
250 | IsLogRedirectDanmaku = IsLogRedirectDanmaku,
251 | LogDanmakuLengthLimit = LogDanmakuLengthLimit,
252 | };
253 |
254 | ///
255 | /// 弹幕姬退出事件
256 | ///
257 | internal void DeInit()
258 | {
259 | Config.Write(GatherConfig());
260 |
261 | Downloader.CancelDownload();
262 | Player.Next();
263 | try
264 | {
265 | Directory.Delete(Utilities.SongsCacheDirectoryPath, true);
266 | }
267 | catch (Exception)
268 | {
269 | }
270 | }
271 |
272 | ///
273 | /// 主界面右侧
274 | /// 添加歌曲的
275 | /// dialog 的
276 | /// 关闭事件
277 | ///
278 | ///
279 | ///
280 | private void DialogAddSongs(object sender, DialogClosingEventArgs eventArgs)
281 | {
282 | if (eventArgs.Parameter.Equals(true) && !string.IsNullOrWhiteSpace(AddSongsTextBox.Text))
283 | {
284 | var keyword = AddSongsTextBox.Text;
285 | SongInfo songInfo = null;
286 |
287 | if (SearchModules.PrimaryModule != SearchModules.NullModule)
288 | {
289 | songInfo = SearchModules.PrimaryModule.SafeSearch(keyword);
290 | }
291 |
292 | if (songInfo == null)
293 | {
294 | if (SearchModules.SecondaryModule != SearchModules.NullModule)
295 | {
296 | songInfo = SearchModules.SecondaryModule.SafeSearch(keyword);
297 | }
298 | }
299 |
300 | if (songInfo == null)
301 | {
302 | return;
303 | }
304 |
305 | Songs.Add(new SongItem(songInfo, "主播")); // TODO: 点歌人名字
306 | }
307 | AddSongsTextBox.Text = string.Empty;
308 | }
309 |
310 | ///
311 | /// 主界面右侧
312 | /// 添加空闲歌曲按钮的
313 | /// dialog 的
314 | /// 关闭事件
315 | ///
316 | ///
317 | ///
318 | private void DialogAddSongsToPlaylist(object sender, DialogClosingEventArgs eventArgs)
319 | {
320 | if (eventArgs.Parameter.Equals(true) && !string.IsNullOrWhiteSpace(AddSongPlaylistTextBox.Text))
321 | {
322 | var keyword = AddSongPlaylistTextBox.Text;
323 | SongInfo songInfo = null;
324 |
325 | if (SearchModules.PrimaryModule != SearchModules.NullModule)
326 | {
327 | songInfo = SearchModules.PrimaryModule.SafeSearch(keyword);
328 | }
329 |
330 | if (songInfo == null)
331 | {
332 | if (SearchModules.SecondaryModule != SearchModules.NullModule)
333 | {
334 | songInfo = SearchModules.SecondaryModule.SafeSearch(keyword);
335 | }
336 | }
337 |
338 | if (songInfo == null)
339 | {
340 | return;
341 | }
342 |
343 | Playlist.Add(songInfo);
344 | }
345 | AddSongPlaylistTextBox.Text = string.Empty;
346 | }
347 |
348 | ///
349 | /// 主界面右侧
350 | /// 添加空闲歌单按钮的
351 | /// dialog 的
352 | /// 关闭事件
353 | ///
354 | ///
355 | ///
356 | private void DialogAddPlaylist(object sender, DialogClosingEventArgs eventArgs)
357 | {
358 | if (eventArgs.Parameter.Equals(true) && !string.IsNullOrWhiteSpace(AddPlaylistTextBox.Text))
359 | {
360 | var keyword = AddPlaylistTextBox.Text;
361 | List songInfoList = null;
362 |
363 | if (SearchModules.PrimaryModule != SearchModules.NullModule && SearchModules.PrimaryModule.IsPlaylistSupported)
364 | {
365 | songInfoList = SearchModules.PrimaryModule.SafeGetPlaylist(keyword);
366 | }
367 |
368 | // 歌单只使用主搜索模块搜索
369 |
370 | if (songInfoList == null)
371 | {
372 | return;
373 | }
374 |
375 | foreach (var item in songInfoList)
376 | {
377 | Playlist.Add(item);
378 | }
379 | }
380 | AddPlaylistTextBox.Text = string.Empty;
381 | }
382 |
383 | ///
384 | /// 黑名单 popupbox 里的
385 | /// 添加黑名单按钮的
386 | /// dialog 的
387 | /// 关闭事件
388 | ///
389 | ///
390 | ///
391 | private void DialogAddBlacklist(object sender, DialogClosingEventArgs eventArgs)
392 | {
393 | if (eventArgs.Parameter.Equals(true)
394 | && !string.IsNullOrWhiteSpace(AddBlacklistTextBox.Text)
395 | && AddBlacklistComboBox.SelectedValue != null
396 | && AddBlacklistComboBox.SelectedValue is BlackListType)
397 | {
398 | var keyword = AddBlacklistTextBox.Text;
399 | var type = (BlackListType)AddBlacklistComboBox.SelectedValue;
400 |
401 | Blacklist.Add(new BlackListItem(type, keyword));
402 | }
403 | AddBlacklistTextBox.Text = string.Empty;
404 | }
405 |
406 | private void Window_Closing(object sender, CancelEventArgs e)
407 | {
408 | e.Cancel = true;
409 | Hide();
410 | }
411 |
412 | private async void LogRedirectToggleButton_OnChecked(object sender, RoutedEventArgs e)
413 | {
414 | try
415 | {
416 | if (!await LoginCenterAPIWarpper.DoAuth(PluginMain))
417 | {
418 | LogRedirectToggleButton.IsChecked = false;
419 | }
420 | }
421 | catch (Exception)
422 | {
423 | LogRedirectToggleButton.IsChecked = false;
424 | }
425 | }
426 | }
427 | }
428 |
--------------------------------------------------------------------------------
/DGJv3/DGJv3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {53970AEC-FD0E-46ED-8269-3FD73DC3DD0F}
8 | Library
9 | Properties
10 | DGJv3
11 | DGJv3
12 | v4.6.2
13 | 512
14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | none
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 | A914D6F0-B0F3-48F4-9FF8-FB6E9D754BDD
35 | DllExport.dll
36 | System.Runtime.InteropServices
37 | true
38 | false
39 | AnyCPU
40 | 1
41 | false
42 | false
43 | false
44 | 30000
45 | 3
46 |
47 |
48 |
49 | .\BilibiliDM_PluginFramework.dll
50 | False
51 |
52 |
53 | $(SolutionDir)packages\DllExport.1.6.1\gcache\metalib\$(DllExportNamespace)\$(DllExportMetaLibName)
54 | False
55 | False
56 |
57 |
58 | False
59 | .\LoginCenter.dll
60 | False
61 |
62 |
63 | ..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll
64 |
65 |
66 | ..\packages\MaterialDesignThemes.2.4.1.1101\lib\net45\MaterialDesignThemes.Wpf.dll
67 |
68 |
69 | ..\packages\NAudio.1.8.4\lib\net35\NAudio.dll
70 |
71 |
72 | ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll
73 |
74 |
75 |
76 |
77 | ..\packages\Scriban.Signed.1.2.5\lib\net40\Scriban.dll
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | DGJWindow.xaml
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | Designer
143 | MSBuild:Compile
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | %(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | cd $(SolutionDir)
169 | PowerShell -ExecutionPolicy Bypass -File .\CI\patch_buildinfo.ps1
170 |
171 |
--------------------------------------------------------------------------------
/DGJv3/DanmuHandler.cs:
--------------------------------------------------------------------------------
1 | using BilibiliDM_PluginFramework;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.ComponentModel;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Runtime.CompilerServices;
9 | using System.Windows.Threading;
10 |
11 | namespace DGJv3
12 | {
13 | class DanmuHandler : INotifyPropertyChanged
14 | {
15 | private ObservableCollection Songs;
16 |
17 | private ObservableCollection Blacklist;
18 |
19 | private Player Player;
20 |
21 | private Downloader Downloader;
22 |
23 | private SearchModules SearchModules;
24 |
25 | private Dispatcher dispatcher;
26 |
27 | ///
28 | /// 最多点歌数量
29 | ///
30 | public uint MaxTotalSongNum { get => _maxTotalSongCount; set => SetField(ref _maxTotalSongCount, value); }
31 | private uint _maxTotalSongCount;
32 |
33 | ///
34 | /// 每个人最多点歌数量
35 | ///
36 | public uint MaxPersonSongNum { get => _maxPersonSongNum; set => SetField(ref _maxPersonSongNum, value); }
37 | private uint _maxPersonSongNum;
38 |
39 | internal DanmuHandler(ObservableCollection songs, Player player, Downloader downloader, SearchModules searchModules, ObservableCollection blacklist)
40 | {
41 | dispatcher = Dispatcher.CurrentDispatcher;
42 | Songs = songs;
43 | Player = player;
44 | Downloader = downloader;
45 | SearchModules = searchModules;
46 | Blacklist = blacklist;
47 | }
48 |
49 |
50 | ///
51 | /// 处理弹幕消息
52 | ///
53 | /// 注:调用侧可能会在任意线程
54 | ///
55 | ///
56 | ///
57 | internal void ProcessDanmu(DanmakuModel danmakuModel)
58 | {
59 | if (danmakuModel.MsgType != MsgTypeEnum.Comment || string.IsNullOrWhiteSpace(danmakuModel.CommentText))
60 | return;
61 |
62 | string[] commands = danmakuModel.CommentText.Split(SPLIT_CHAR, StringSplitOptions.RemoveEmptyEntries);
63 | string rest = string.Join(" ", commands.Skip(1));
64 |
65 | if (danmakuModel.isAdmin)
66 | {
67 | switch (commands[0])
68 | {
69 | case "切歌":
70 | {
71 | // Player.Next();
72 |
73 | dispatcher.Invoke(() =>
74 | {
75 | if (Songs.Count > 0)
76 | {
77 | Songs[0].Remove(Songs, Downloader, Player);
78 | Log("切歌成功!");
79 | }
80 | });
81 |
82 | /*
83 | if (commands.Length >= 2)
84 | {
85 | // TODO: 切指定序号的歌曲
86 | }
87 | */
88 | }
89 | return;
90 | case "暂停":
91 | case "暫停":
92 | {
93 | Player.Pause();
94 | }
95 | return;
96 | case "播放":
97 | {
98 | Player.Play();
99 | }
100 | return;
101 | case "音量":
102 | {
103 | if (commands.Length > 1
104 | && int.TryParse(commands[1], out int volume100)
105 | && volume100 >= 0
106 | && volume100 <= 100)
107 | {
108 | Player.Volume = volume100 / 100f;
109 | }
110 | }
111 | return;
112 | default:
113 | break;
114 | }
115 | }
116 |
117 | switch (commands[0])
118 | {
119 | case "点歌":
120 | case "點歌":
121 | {
122 | DanmuAddSong(danmakuModel, rest);
123 | }
124 | return;
125 | case "取消點歌":
126 | case "取消点歌":
127 | {
128 | dispatcher.Invoke(() =>
129 | {
130 | SongItem songItem = Songs.LastOrDefault(x => x.UserName == danmakuModel.UserName && x.Status != SongStatus.Playing);
131 | if (songItem != null)
132 | {
133 | songItem.Remove(Songs, Downloader, Player);
134 | }
135 | });
136 | }
137 | return;
138 | case "投票切歌":
139 | {
140 | // TODO: 投票切歌
141 | }
142 | return;
143 | default:
144 | break;
145 | }
146 | }
147 |
148 | private void DanmuAddSong(DanmakuModel danmakuModel, string keyword)
149 | {
150 | if (dispatcher.Invoke(callback: () => CanAddSong(username: danmakuModel.UserName)))
151 | {
152 | SongInfo songInfo = null;
153 |
154 | if (SearchModules.PrimaryModule != SearchModules.NullModule)
155 | songInfo = SearchModules.PrimaryModule.SafeSearch(keyword);
156 |
157 | if (songInfo == null)
158 | if (SearchModules.SecondaryModule != SearchModules.NullModule)
159 | songInfo = SearchModules.SecondaryModule.SafeSearch(keyword);
160 |
161 | if (songInfo == null)
162 | return;
163 |
164 | if (songInfo.IsInBlacklist(Blacklist))
165 | {
166 | Log($"歌曲{songInfo.Name}在黑名单中");
167 | return;
168 | }
169 | Log($"点歌成功:{songInfo.Name}");
170 | dispatcher.Invoke(callback: () =>
171 | {
172 | if (CanAddSong(danmakuModel.UserName) &&
173 | !Songs.Any(x =>
174 | x.SongId == songInfo.Id &&
175 | x.Module.UniqueId == songInfo.Module.UniqueId)
176 | )
177 | Songs.Add(new SongItem(songInfo, danmakuModel.UserName));
178 | });
179 | }
180 | }
181 |
182 | ///
183 | /// 能否点歌
184 | ///
185 | /// 注:调用侧需要在主线程上运行
186 | ///
187 | ///
188 | /// 点歌用户名
189 | ///
190 | private bool CanAddSong(string username)
191 | {
192 | return Songs.Count < MaxTotalSongNum ? (Songs.Where(x => x.UserName == username).Count() < MaxPersonSongNum) : false;
193 | }
194 |
195 | private readonly static char[] SPLIT_CHAR = { ' ' };
196 |
197 | public event PropertyChangedEventHandler PropertyChanged;
198 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "")
199 | {
200 | if (EqualityComparer.Default.Equals(field, value)) return false;
201 | field = value;
202 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
203 | return true;
204 | }
205 |
206 | public event LogEvent LogEvent;
207 | private void Log(string message, Exception exception = null) => LogEvent?.Invoke(this, new LogEventArgs() { Message = message, Exception = exception });
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/DGJv3/DownloadStatus.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3
2 | {
3 | public enum DownloadStatus
4 | {
5 | ///
6 | /// 下载成功
7 | ///
8 | Success = 0,
9 | ///
10 | /// 下载失败
11 | ///
12 | Failed = -1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/DGJv3/Downloader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.ComponentModel;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Runtime.CompilerServices;
9 | using System.Threading.Tasks;
10 | using System.Timers;
11 | using System.Windows.Threading;
12 |
13 | namespace DGJv3
14 | {
15 | internal class Downloader : INotifyPropertyChanged
16 | {
17 | private Dispatcher dispatcher;
18 |
19 | private ObservableCollection Songs;
20 |
21 | private DispatcherTimer newSongTimer = new DispatcherTimer(DispatcherPriority.Normal)
22 | {
23 | Interval = TimeSpan.FromSeconds(1),
24 | IsEnabled = true,
25 | };
26 |
27 | private Timer downloadTimeoutTimer = new Timer(1000)
28 | {
29 | AutoReset = true,
30 | };
31 |
32 | public bool IsModuleDownloading { get => _isModuleDownloading; set => SetField(ref _isModuleDownloading, value); }
33 | private bool _isModuleDownloading = false;
34 |
35 | public double DownloadSpeed { get => _downloadSpeed; set => SetField(ref _downloadSpeed, value, nameof(DownloadSpeed)); }
36 | private double _downloadSpeed = 0;
37 |
38 | public int DownloadPercentage { get => _downloadPercentage; set => SetField(ref _downloadPercentage, value, nameof(DownloadPercentage)); }
39 | private int _downloadPercentage = 0;
40 |
41 | private WebClient webClient = null;
42 |
43 | private SongItem currentSong = null;
44 |
45 | private DateTime lastUpdateTime;
46 |
47 | private long lastUpdateDownloadedSize;
48 |
49 | private DateTime lastHighspeedTime;
50 |
51 | private TimeSpan timeout = TimeSpan.FromSeconds(5);
52 |
53 | public Downloader(ObservableCollection songs)
54 | {
55 | Songs = songs;
56 | newSongTimer.Tick += NewSongTimer_Tick;
57 | downloadTimeoutTimer.Elapsed += DownloadTimeoutTimer_Elapsed;
58 | dispatcher = Dispatcher.CurrentDispatcher;
59 |
60 | PropertyChanged += Downloader_PropertyChanged;
61 | }
62 |
63 | private void DownloadTimeoutTimer_Elapsed(object sender, ElapsedEventArgs e)
64 | {
65 | if (DownloadPercentage > 0 && (DateTime.Now - lastHighspeedTime > timeout))
66 | {
67 | Log("下载速度过慢,防卡下载自动取消");
68 | CancelDownload();
69 | }
70 | }
71 |
72 | private void Downloader_PropertyChanged(object sender, PropertyChangedEventArgs e)
73 | {
74 | if (e.PropertyName == nameof(DownloadSpeed))
75 | {
76 | if (DownloadSpeed > 80)
77 | {
78 | lastHighspeedTime = DateTime.Now;
79 | }
80 | }
81 | }
82 |
83 | private void NewSongTimer_Tick(object sender, EventArgs e)
84 | {
85 | if (currentSong == null)
86 | {
87 | if (IsModuleDownloading)
88 | {
89 | IsModuleDownloading = false;
90 | }
91 |
92 | foreach (var songItem in Songs)
93 | {
94 | if (songItem.Status == SongStatus.WaitingDownload)
95 | {
96 | currentSong = songItem;
97 | Task.Run(() => Download());
98 | break;
99 | }
100 | }
101 | }
102 | }
103 |
104 | private void Download()
105 | {
106 | currentSong.FilePath = Path.Combine(Utilities.SongsCacheDirectoryPath, CleanFileName($"{currentSong.ModuleName}{currentSong.SongName}{currentSong.SongId}{DateTime.Now.ToBinary().ToString("X")}.mp3.点歌姬缓存"));
107 |
108 | try { Directory.CreateDirectory(Utilities.SongsCacheDirectoryPath); } catch (Exception) { }
109 |
110 | // currentSong = songItem;
111 |
112 | currentSong.Status = SongStatus.Downloading;
113 | if (currentSong.Module.IsHandleDownlaod)
114 | {
115 | IsModuleDownloading = true;
116 | new System.Threading.Thread(() =>
117 | {
118 | switch (currentSong.Module.SafeDownload(currentSong))
119 | {
120 | case DownloadStatus.Success:
121 | currentSong.Status = SongStatus.WaitingPlay;
122 | break;
123 | case DownloadStatus.Failed:
124 | default:
125 | dispatcher.Invoke(() => Songs.Remove(currentSong));
126 | break;
127 | }
128 | currentSong = null;
129 | })
130 | {
131 | Name = "SongModuleDownload",
132 | IsBackground = true,
133 | }
134 | .Start();
135 | }
136 | else
137 | {
138 | try
139 | {
140 | string url = currentSong.GetDownloadUrl();
141 | if (url != null)
142 | {
143 | webClient = new WebClient();
144 |
145 | webClient.DownloadProgressChanged += OnDownloadProgressChanged;
146 | webClient.DownloadFileCompleted += OnDownloadFileCompleted;
147 | webClient.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.450 Safari/537.35");
148 |
149 | webClient.DownloadFileAsync(new Uri(url), currentSong.FilePath);
150 | }
151 | else
152 | {
153 | dispatcher.Invoke(() => Songs.Remove(currentSong));
154 | currentSong = null;
155 | }
156 | }
157 | catch (Exception ex)
158 | {
159 | webClient?.Dispose();
160 | webClient = null;
161 |
162 | dispatcher.Invoke(() => Songs.Remove(currentSong));
163 | Log("启动下载错误 " + currentSong.SongName, ex);
164 | currentSong = null;
165 | }
166 | }
167 | }
168 |
169 | internal void CancelDownload()
170 | {
171 | if (webClient != null)
172 | {
173 | webClient.CancelAsync();
174 | }
175 | }
176 |
177 | private void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
178 | {
179 | bool success = true;
180 | if (e.Cancelled)
181 | {
182 | success = false;
183 | try
184 | { File.Delete(currentSong.FilePath); }
185 | catch (Exception) { }
186 | }
187 | else if (e.Error != null)
188 | {
189 | success = false;
190 | }
191 |
192 | if (success)
193 | {
194 | currentSong.Status = SongStatus.WaitingPlay;
195 | }
196 | else
197 | {
198 | dispatcher.Invoke(() => Songs.Remove(currentSong));
199 | Log("下载错误 " + currentSong.SongName, e.Error);
200 | }
201 |
202 | DownloadSpeed = 0;
203 | DownloadPercentage = 0;
204 |
205 | currentSong = null;
206 | webClient.Dispose();
207 | webClient = null;
208 | }
209 |
210 | private void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
211 | {
212 | DateTime now = DateTime.Now;
213 | TimeSpan interval = now - lastUpdateTime;
214 | if (interval.TotalSeconds < 0.5)
215 | { return; }
216 |
217 | int speed_bps = (int)Math.Floor((e.BytesReceived - lastUpdateDownloadedSize) / interval.TotalSeconds);
218 |
219 | lastUpdateDownloadedSize = e.BytesReceived;
220 | lastUpdateTime = now;
221 |
222 | DownloadSpeed = speed_bps / 1024d;
223 | DownloadPercentage = e.ProgressPercentage;
224 | }
225 |
226 | private static string CleanFileName(string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
227 |
228 | public event PropertyChangedEventHandler PropertyChanged;
229 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "")
230 | {
231 | if (EqualityComparer.Default.Equals(field, value))
232 | {
233 | return false;
234 | }
235 |
236 | field = value;
237 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
238 | return true;
239 | }
240 |
241 | public event LogEvent LogEvent;
242 | private void Log(string message, Exception exception = null) => LogEvent?.Invoke(this, new LogEventArgs() { Message = message, Exception = exception });
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/DGJv3/EnumerationExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using System.Windows.Markup;
5 |
6 | namespace DGJv3
7 | {
8 | public class EnumerationExtension : MarkupExtension
9 | {
10 | private Type _enumType;
11 |
12 | public EnumerationExtension(Type enumType) => EnumType = enumType ?? throw new ArgumentNullException("enumType");
13 | public override object ProvideValue(IServiceProvider serviceProvider) => (from object enumValue in Enum.GetValues(EnumType) select new EnumerationMember { Value = enumValue, Description = GetDescription(enumValue) }).ToArray();
14 | private string GetDescription(object enumValue) => EnumType.GetField(enumValue.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() is DescriptionAttribute descriptionAttribute ? descriptionAttribute.Description : enumValue.ToString();
15 |
16 | public Type EnumType
17 | {
18 | get { return _enumType; }
19 | private set
20 | {
21 | if (_enumType == value)
22 | return;
23 |
24 | var enumType = Nullable.GetUnderlyingType(value) ?? value;
25 |
26 | if (enumType.IsEnum == false)
27 | throw new ArgumentException("Type must be an Enum.");
28 |
29 | _enumType = value;
30 | }
31 | }
32 |
33 | public class EnumerationMember
34 | {
35 | public string Description { get; set; }
36 | public object Value { get; set; }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/DGJv3/EqualsVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace DGJv3
7 | {
8 | class EqualsVisibilityConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | return value.Equals(parameter) ? Visibility.Visible : Visibility.Collapsed;
13 | }
14 |
15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
16 | {
17 | return Binding.DoNothing;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/DGJv3/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.IO;
5 | using System.Linq;
6 |
7 | namespace DGJv3
8 | {
9 | internal static class Extensions
10 | {
11 | internal static void Remove(this SongItem songItem, ObservableCollection songList, Downloader downloader, Player player)
12 | {
13 | switch (songItem.Status)
14 | {
15 | case SongStatus.WaitingDownload:
16 | songList.Remove(songItem);
17 | break;
18 | case SongStatus.Downloading:
19 | downloader.CancelDownload();
20 | break;
21 | case SongStatus.WaitingPlay:
22 | songList.Remove(songItem);
23 | try { File.Delete(songItem.FilePath); } catch (Exception) { }
24 | break;
25 | case SongStatus.Playing:
26 | player.Next();
27 | break;
28 | default:
29 | break;
30 | }
31 | }
32 |
33 | internal static string GetDownloadUrl(this SongItem songItem) => songItem.Module.SafeGetDownloadUrl(songItem);
34 |
35 | internal static bool IsInBlacklist(this SongInfo songInfo, IEnumerable blackList)
36 | {
37 | return blackList.ToArray().Any(x =>
38 | {
39 | switch (x.BlackType)
40 | {
41 | case BlackListType.Id: return songInfo.Id.Equals(x.Content);
42 | case BlackListType.Name: return songInfo.Name.IndexOf(x.Content, StringComparison.CurrentCultureIgnoreCase) > -1;
43 | case BlackListType.Singer: return songInfo.SingersText.IndexOf(x.Content, StringComparison.CurrentCultureIgnoreCase) > -1;
44 | default: return false;
45 | }
46 | });
47 | }
48 |
49 | internal static string ToStatusString(this SongStatus songStatus)
50 | {
51 | switch (songStatus)
52 | {
53 | case SongStatus.WaitingDownload:
54 | return "等待下载";
55 | case SongStatus.Downloading:
56 | return "正在下载";
57 | case SongStatus.WaitingPlay:
58 | return "等待播放";
59 | case SongStatus.Playing:
60 | return "正在播放";
61 | default:
62 | return "????";
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/DGJv3/FuckRegsvr32.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | namespace DGJv3
8 | {
9 | internal static class FuckRegsvr32
10 | {
11 | private const uint E_FAIL = 0x80004005;
12 | private const uint E_UNEXPECTED = 0x8000FFFF;
13 |
14 | [DllExport("DllRegisterServer", CallingConvention.StdCall)]
15 | public static uint DllRegisterServer()
16 | {
17 | MessageBox.Show("点歌姬不是双击dll文件安装的\n你看下载页右侧的安装使用说明了吗?\n你看下载页右侧的安装使用说明了吗?\n你看下载页右侧的安装使用说明了吗?", "你看使用说明了吗?", MessageBoxButton.OK, MessageBoxImage.Question);
18 | string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "弹幕姬", "Plugins");
19 | if (!Directory.Exists(path))
20 | {
21 | MessageBox.Show("没有在此计算机找到弹幕姬插件文件夹,你安装弹幕姬了么?\n弹幕姬下载:https://www.danmuji.org", "点歌姬不是双击dll文件安装的", MessageBoxButton.OK, MessageBoxImage.Question);
22 | }
23 | else
24 | {
25 | Process.Start(path);
26 | }
27 | Process.Start("https://www.danmuji.org/plugins/DGJ");
28 | Environment.Exit(1);
29 | return E_FAIL;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/DGJv3/InternalModule/LwlApiBaidu.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3.InternalModule
2 | {
3 | sealed class LwlApiBaidu : LwlApiBaseModule
4 | {
5 | internal LwlApiBaidu()
6 | {
7 | SetServiceName("baidu");
8 | SetInfo("百度音乐", INFO_AUTHOR, INFO_EMAIL, INFO_VERSION, "搜索百度音乐的歌曲");
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/DGJv3/InternalModule/LwlApiBaseModule.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using System.Web;
10 |
11 | namespace DGJv3.InternalModule
12 | {
13 | internal class LwlApiBaseModule : SearchModule
14 | {
15 | private string ServiceName = "undefined";
16 | protected void SetServiceName(string name) => ServiceName = name;
17 |
18 | private const string API_PROTOCOL = "https://";
19 | private const string API_HOST = "api.lwl12.com";
20 | private const string API_PATH = "/music/";
21 |
22 | protected const string INFO_PREFIX = "";
23 | protected const string INFO_AUTHOR = "Genteure & LWL12";
24 | protected const string INFO_EMAIL = "dgj@genteure.com";
25 | protected const string INFO_VERSION = "1.1";
26 |
27 | internal static int RoomId = 0;
28 |
29 | internal LwlApiBaseModule()
30 | {
31 | IsPlaylistSupported = true;
32 | }
33 |
34 | protected override DownloadStatus Download(SongItem item)
35 | {
36 | throw new NotImplementedException();
37 | }
38 |
39 | protected override string GetDownloadUrl(SongItem songInfo)
40 | {
41 | try
42 | {
43 | JObject dlurlobj = JObject.Parse(Fetch(API_PROTOCOL, API_HOST, API_PATH + ServiceName + $"/song?id={songInfo.SongId}"));
44 |
45 | if (dlurlobj["code"].ToString() == "200")
46 | {
47 | if (dlurlobj["result"] is JObject)
48 | {
49 | dlurlobj = (JObject)dlurlobj["result"];
50 | }
51 | else
52 | {
53 | dlurlobj = JObject.Parse(dlurlobj["result"].Value());
54 | }
55 | return dlurlobj["url"].ToString();
56 | }
57 | else
58 | {
59 | Log($"歌曲 {songInfo.SongName} 因为版权不能下载");
60 | return null;
61 | }
62 | }
63 | catch (Exception ex)
64 | {
65 | Log($"歌曲 {songInfo.SongName} 疑似版权不能下载(ex:{ex.Message})");
66 | return null;
67 | }
68 | }
69 |
70 | protected override string GetLyricById(string Id)
71 | {
72 | try
73 | {
74 | JObject lobj = JObject.Parse(Fetch(API_PROTOCOL, API_HOST, API_PATH + ServiceName + $"/lyric?id={Id}"));
75 | if (lobj["result"] is JObject)
76 | {
77 | lobj = (JObject)lobj["result"];
78 | }
79 | else
80 | {
81 | lobj = JObject.Parse(lobj["result"].Value());
82 | }
83 | if (lobj["lwlyric"] != null)
84 | {
85 | return lobj["lwlyric"].ToString();
86 | }
87 | else if (lobj["tlyric"] != null)
88 | {
89 | return lobj["tlyric"].ToString();
90 | }
91 | else if (lobj["lyric"] != null)
92 | {
93 | return lobj["lyric"].ToString();
94 | }
95 | else
96 | { Log("歌词获取错误(id:" + Id + ")"); }
97 |
98 | }
99 | catch (Exception ex)
100 | { Log("歌词获取错误(ex:" + ex.ToString() + ",id:" + Id + ")"); }
101 |
102 | return null;
103 | }
104 |
105 | protected override List GetPlaylist(string keyword)
106 | {
107 | try
108 | {
109 | List songInfos = new List();
110 |
111 | JObject playlist = JObject.Parse(Fetch(API_PROTOCOL, API_HOST, API_PATH + ServiceName + $"/playlist?id={HttpUtility.UrlEncode(keyword)}"));
112 |
113 | if (playlist["code"]?.ToObject() == 200)
114 | {
115 | List result = (playlist["result"] as JArray).ToList();
116 |
117 | //if (result.Count() > 50)
118 | // result = result.Take(50).ToList();
119 |
120 | result.ForEach(song =>
121 | {
122 | try
123 | {
124 | var songInfo = new SongInfo(this,
125 | song["id"].ToString(),
126 | song["name"].ToString(),
127 | (song["artist"] as JArray).Select(x => x.ToString()).ToArray());
128 |
129 | songInfo.Lyric = null;//在之后再获取Lyric
130 |
131 | songInfos.Add(songInfo);
132 | }
133 | catch (Exception) { }
134 | });
135 |
136 | return songInfos;
137 | }
138 | else
139 | {
140 | return null;
141 | }
142 |
143 | }
144 | catch (Exception ex)
145 | {
146 | Log("获取歌单信息时出错 " + ex.Message);
147 | return null;
148 | }
149 | }
150 |
151 | protected override SongInfo Search(string keyword)
152 | {
153 | string result_str;
154 | try
155 | {
156 | result_str = Fetch(API_PROTOCOL, API_HOST, API_PATH + ServiceName + $"/search?keyword={HttpUtility.UrlEncode(keyword)}");
157 | }
158 | catch (Exception ex)
159 | {
160 | Log("搜索歌曲时网络错误:" + ex.Message);
161 | return null;
162 | }
163 |
164 | JObject song = null;
165 | try
166 | {
167 | JObject info = JObject.Parse(result_str);
168 | if (info["code"].ToString() == "200")
169 | {
170 | song = (info["result"] as JArray)?[0] as JObject;
171 | }
172 | }
173 | catch (Exception ex)
174 | {
175 | Log("搜索歌曲解析数据错误:" + ex.Message);
176 | return null;
177 | }
178 |
179 | SongInfo songInfo;
180 |
181 |
182 | try
183 | {
184 | songInfo = new SongInfo(
185 | this,
186 | song["id"].ToString(),
187 | song["name"].ToString(),
188 | (song["artist"] as JArray).Select(x => x.ToString()).ToArray()
189 | );
190 | }
191 | catch (Exception ex)
192 | { Log("歌曲信息获取结果错误:" + ex.Message); return null; }
193 |
194 | songInfo.Lyric = GetLyricById(songInfo.Id);
195 |
196 | return songInfo;
197 | }
198 |
199 | private static string Fetch(string prot, string host, string path, string data = null, string referer = null)
200 | {
201 | for (int retryCount = 0; retryCount < 4; retryCount++)
202 | {
203 | try
204 | {
205 | return Fetch_exec(prot, host, path, data, referer);
206 | }
207 | catch (WebException)
208 | {
209 | if (retryCount >= 3)
210 | {
211 | throw;
212 | }
213 |
214 | continue;
215 | }
216 | }
217 |
218 | return null;
219 | }
220 |
221 | private static string Fetch_exec(string prot, string host, string path, string data = null, string referer = null)
222 | {
223 | string address;
224 | if (GetDNSResult(host, out string ip))
225 | {
226 | address = prot + ip + path;
227 | }
228 | else
229 | {
230 | address = prot + host + path;
231 | }
232 |
233 | var request = (HttpWebRequest)WebRequest.Create(address);
234 |
235 | request.Timeout = 4000;
236 | request.Host = host;
237 | request.UserAgent = "DMPlugin_DGJ/" + (BuildInfo.Appveyor ? BuildInfo.Version : "local") + " RoomId/" + RoomId.ToString();
238 |
239 | if (referer != null)
240 | {
241 | request.Referer = referer;
242 | }
243 |
244 | if (data != null)
245 | {
246 | var postData = Encoding.UTF8.GetBytes(data);
247 | request.Method = "POST";
248 | request.ContentType = "application/x-www-form-urlencoded";
249 | request.ContentLength = postData.Length;
250 | using (var stream = request.GetRequestStream())
251 | {
252 | stream.Write(postData, 0, postData.Length);
253 | }
254 | }
255 |
256 | var response = (HttpWebResponse)request.GetResponse();
257 | var responseString = new StreamReader(response.GetResponseStream(), Encoding.UTF8).ReadToEnd();
258 | return responseString;
259 | }
260 | private static string Fetch(string url)
261 | {
262 | var request = (HttpWebRequest)WebRequest.Create(url);
263 | request.Timeout = 10000;
264 | var response = (HttpWebResponse)request.GetResponse();
265 | var responseString = new StreamReader(response.GetResponseStream(), Encoding.UTF8).ReadToEnd();
266 | return responseString;
267 | }
268 | private static bool GetDNSResult(string domain, out string result)
269 | {
270 | if (DNSList.TryGetValue(domain, out DNSResult result_from_d))
271 | {
272 | if (result_from_d.TTLTime > DateTime.Now)
273 | {
274 | result = result_from_d.IP;
275 | return true;
276 | }
277 | else
278 | {
279 | DNSList.Remove(domain);
280 | if (RequestDNSResult(domain, out DNSResult? result_from_api, out Exception exception))
281 | {
282 | DNSList.Add(domain, result_from_api.Value);
283 | result = result_from_api.Value.IP;
284 | return true;
285 | }
286 | else
287 | {
288 | result = null;
289 | return false;
290 | }
291 | }
292 | }
293 | else
294 | {
295 | if (RequestDNSResult(domain, out DNSResult? result_from_api, out Exception exception))
296 | {
297 | DNSList.Add(domain, result_from_api.Value);
298 | result = result_from_api.Value.IP;
299 | return true;
300 | }
301 | else
302 | {
303 | result = null;
304 | return false;
305 | }
306 | }
307 | }
308 | private static bool RequestDNSResult(string domain, out DNSResult? dnsResult, out Exception exception)
309 | {
310 | dnsResult = null;
311 | exception = null;
312 |
313 | try
314 | {
315 | var http_result = Fetch("http://119.29.29.29/d?ttl=1&dn=" + domain);
316 | if (http_result == string.Empty)
317 | {
318 | return false;
319 | }
320 |
321 | var m = regex.Match(http_result);
322 | if (!m.Success)
323 | {
324 | exception = new Exception("HTTPDNS 返回结果不正确");
325 | return false;
326 | }
327 |
328 | dnsResult = new DNSResult()
329 | {
330 | IP = m.Groups[1].Value,
331 | TTLTime = DateTime.Now + TimeSpan.FromSeconds(double.Parse(m.Groups[2].Value))
332 | };
333 | return true;
334 | }
335 | catch (Exception ex)
336 | {
337 | exception = ex;
338 | return false;
339 | }
340 | }
341 |
342 | [Obsolete("Use GetLyricById instead", true)]
343 | protected override string GetLyric(SongItem songInfo)
344 | {
345 | throw new NotImplementedException();
346 | }
347 |
348 | private static readonly Dictionary DNSList = new Dictionary();
349 | private static readonly Regex regex = new Regex(@"((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\,(\d+)", RegexOptions.Compiled);
350 | private struct DNSResult
351 | {
352 | internal string IP;
353 | internal DateTime TTLTime;
354 | }
355 |
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/DGJv3/InternalModule/LwlApiKugou.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3.InternalModule
2 | {
3 | sealed class LwlApiKugou : LwlApiBaseModule
4 | {
5 | internal LwlApiKugou()
6 | {
7 | SetServiceName("kugou");
8 | SetInfo("酷狗音乐", INFO_AUTHOR, INFO_EMAIL, INFO_VERSION, "搜索酷狗音乐的歌曲");
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/DGJv3/InternalModule/LwlApiNetease.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3.InternalModule
2 | {
3 | sealed class LwlApiNetease : LwlApiBaseModule
4 | {
5 | internal LwlApiNetease()
6 | {
7 | SetServiceName("netease");
8 | SetInfo("网易云音乐", INFO_AUTHOR, INFO_EMAIL, INFO_VERSION, "搜索网易云音乐的歌曲");
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/DGJv3/InternalModule/LwlApiTencent.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3.InternalModule
2 | {
3 | sealed class LwlApiTencent : LwlApiBaseModule
4 | {
5 | internal LwlApiTencent()
6 | {
7 | SetServiceName("tencent");
8 | SetInfo("QQ音乐", INFO_AUTHOR, INFO_EMAIL, INFO_VERSION, "搜索QQ音乐的歌曲");
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/DGJv3/InternalModule/LwlApiXiami.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3.InternalModule
2 | {
3 | sealed class LwlApiXiami : LwlApiBaseModule
4 | {
5 | internal LwlApiXiami()
6 | {
7 | SetServiceName("xiami");
8 | SetInfo("虾米音乐", INFO_AUTHOR, INFO_EMAIL, INFO_VERSION, "搜索虾米音乐的歌曲");
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/DGJv3/LogEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DGJv3
4 | {
5 | public delegate void LogEvent(object sender, LogEventArgs e);
6 |
7 | public class LogEventArgs
8 | {
9 | public string Message { get; set; }
10 | public Exception Exception { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DGJv3/LoginCenter.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bililive/DGJv3/3f4b62a3b286ab045e80d29944cf896d7d29f731/DGJv3/LoginCenter.dll
--------------------------------------------------------------------------------
/DGJv3/LoginCenterAPIWarpper.cs:
--------------------------------------------------------------------------------
1 | using BilibiliDM_PluginFramework;
2 | using LoginCenter.API;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace DGJv3
7 | {
8 | internal class LoginCenterAPIWarpper
9 | {
10 | private static LoginCenterAPIWarpper warpper = null;
11 | private static bool isLoginCenterChecked = false;
12 |
13 | internal static bool CheckLoginCenter()
14 | {
15 | if (isLoginCenterChecked)
16 | {
17 | return warpper != null;
18 | }
19 |
20 | try
21 | {
22 | warpper = new LoginCenterAPIWarpper();
23 | return true;
24 | }
25 | catch (Exception)
26 | {
27 | return false;
28 | }
29 | finally
30 | {
31 | isLoginCenterChecked = true;
32 | }
33 | }
34 |
35 | internal static bool CheckAuth(DMPlugin plugin)
36 | {
37 | if (!CheckLoginCenter())
38 | {
39 | return false;
40 | }
41 | return warpper.checkAuthorization(plugin) == true;
42 | }
43 |
44 | internal static async Task DoAuth(DMPlugin plugin)
45 | {
46 | if (!CheckLoginCenter())
47 | {
48 | return false;
49 | }
50 | return await warpper.doAuthorization(plugin);
51 | }
52 |
53 | internal static string Send(int roomid, string msg, int color = 16777215, int mode = 1, int rnd = -1, int fontsize = 25)
54 | {
55 | if (!CheckLoginCenter())
56 | {
57 | return null;
58 | }
59 | return warpper.trySendMessage(roomid, msg, color, mode, rnd, fontsize).Result;
60 | }
61 |
62 | internal static async Task Send_Async(int roomid, string msg, int color = 16777215, int mode = 1, int rnd = -1, int fontsize = 25)
63 | {
64 | if (!CheckLoginCenter())
65 | {
66 | return null;
67 | }
68 | return await warpper.trySendMessage(roomid, msg, color, mode, rnd, fontsize);
69 | }
70 |
71 |
72 |
73 | public LoginCenterAPIWarpper()
74 | {
75 | checkAuthorization();
76 | }
77 | public bool checkAuthorization()
78 | {
79 | return LoginCenterAPI.checkAuthorization();
80 | }
81 |
82 | public bool checkAuthorization(DMPlugin plugin)
83 | {
84 | return LoginCenterAPI.checkAuthorization(plugin) == LoginCenter.API.AuthorizationResult.Success;
85 | }
86 |
87 | public Task trySendMessage(int roomid, string msg, int color = 16777215, int mode = 1,
88 | int rnd = -1, int fontsize = 25)
89 | {
90 | return LoginCenterAPI.trySendMessage(roomid, msg, color, mode, rnd, fontsize);
91 | }
92 |
93 |
94 | public async Task doAuthorization(DMPlugin plugin)
95 | {
96 | var result = await LoginCenterAPI.doAuthorization(plugin);
97 | return result == AuthorizationResult.Success;
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/DGJv3/Lrc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace DGJv3
7 | {
8 | ///
9 | /// 歌词
10 | ///
11 | public class Lrc
12 | {
13 |
14 | public static readonly Lrc NoLyric = new Lrc()
15 | {
16 | Album = string.Empty,
17 | Artist = string.Empty,
18 | LrcBy = string.Empty,
19 | Offset = string.Empty,
20 | Title = string.Empty,
21 | LrcWord = new Dictionary()
22 | {
23 | {
24 | 0d,
25 | "无歌词"
26 | }
27 | }
28 | };
29 |
30 | ///
31 | /// 歌曲
32 | ///
33 | public string Title
34 | { get; set; }
35 |
36 |
37 | ///
38 | /// 艺术家
39 | ///
40 | public string Artist
41 | { get; set; }
42 |
43 |
44 | ///
45 | /// 专辑
46 | ///
47 | public string Album
48 | { get; set; }
49 |
50 |
51 | ///
52 | /// 歌词作者
53 | ///
54 | public string LrcBy
55 | { get; set; }
56 |
57 |
58 | ///
59 | /// 偏移量
60 | ///
61 | public string Offset
62 | { get; set; }
63 |
64 | ///
65 | /// 歌词
66 | ///
67 | public Dictionary LrcWord
68 | { get; set; }
69 |
70 | public int GetLyric(double seconds, out string current, out string upcoming)
71 | {
72 | if (LrcWord.Count < 1)
73 | {
74 | current = "无歌词";
75 | upcoming = string.Empty;
76 | return -1;
77 | }
78 | var list = LrcWord.ToList();
79 | int i;
80 | if (seconds < list[0].Key)
81 | {
82 | i = 0;
83 | current = string.Empty;
84 | upcoming = list[0].Value;
85 | }
86 | else
87 | {
88 | for (i = 1; i < LrcWord.Count; i++)
89 | if (seconds < list[i].Key)
90 | break;
91 |
92 | current = list[i - 1].Value;
93 | if (list.Count > i)
94 | upcoming = list[i].Value;
95 | else
96 | upcoming = string.Empty;
97 | }
98 | return i;
99 | }
100 |
101 | ///
102 | /// 获得歌词信息
103 | ///
104 | /// 歌词文本
105 | /// 返回歌词信息(Lrc实例)
106 | public static Lrc InitLrc(string LrcText)
107 | {
108 | Lrc lrc = new Lrc();
109 | Dictionary dicword = new Dictionary();
110 |
111 | string[] lines = LrcText.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
112 |
113 | foreach (string line in lines)
114 | {
115 | if (line.StartsWith("[ti:"))
116 | {
117 | lrc.Title = SplitInfo(line);
118 | }
119 | else if (line.StartsWith("[ar:"))
120 | {
121 | lrc.Artist = SplitInfo(line);
122 | }
123 | else if (line.StartsWith("[al:"))
124 | {
125 | lrc.Album = SplitInfo(line);
126 | }
127 | else if (line.StartsWith("[by:"))
128 | {
129 | lrc.LrcBy = SplitInfo(line);
130 | }
131 | else if (line.StartsWith("[offset:"))
132 | {
133 | lrc.Offset = SplitInfo(line);
134 | }
135 | else
136 | {
137 | try
138 | {
139 | Regex regexword = new Regex(@".*\](.*)");
140 | Match mcw = regexword.Match(line);
141 | string word = mcw.Groups[1].Value;
142 | if (word.Replace(" ", "") == "")
143 | continue; // 如果为空歌词则跳过不处理
144 | Regex regextime = new Regex(@"\[([0-9.:]*)\]", RegexOptions.Compiled);
145 | MatchCollection mct = regextime.Matches(line);
146 | foreach (Match item in mct)
147 | {
148 | double time = TimeSpan.Parse("00:" + item.Groups[1].Value).TotalSeconds;
149 | dicword.Add(time, word);
150 | }
151 | }
152 | catch
153 | {
154 | continue;
155 | }
156 | }
157 | }
158 | lrc.LrcWord = dicword.OrderBy(t => t.Key).ToDictionary(t => t.Key, p => p.Value);
159 | return lrc;
160 | }
161 |
162 | ///
163 | /// 处理信息(私有方法)
164 | ///
165 | ///
166 | /// 返回基础信息
167 | static string SplitInfo(string line)
168 | {
169 | return line.Substring(line.IndexOf(":") + 1).TrimEnd(']');
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/DGJv3/LyricChangedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3
2 | {
3 | public delegate void LyricChangedEvent(object sender, LyricChangedEventArgs e);
4 |
5 | public class LyricChangedEventArgs
6 | {
7 | public string CurrentLyric { get; set; }
8 | public string UpcomingLyric { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/DGJv3/NotEqualsVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace DGJv3
7 | {
8 | class NotEqualsVisibilityConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | return value.Equals(parameter) ? Visibility.Hidden : Visibility.Visible;
13 | }
14 |
15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
16 | {
17 | return Binding.DoNothing;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/DGJv3/NullSearchModule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace DGJv3
5 | {
6 | ///
7 | /// 空搜索模块
8 | ///
9 | internal sealed class NullSearchModule : SearchModule
10 | {
11 | public NullSearchModule() => SetInfo("不使用", string.Empty, string.Empty, string.Empty, string.Empty);
12 |
13 | protected override DownloadStatus Download(SongItem item)
14 | {
15 | return DownloadStatus.Failed;
16 | }
17 |
18 | protected override string GetDownloadUrl(SongItem songInfo)
19 | {
20 | return null;
21 | }
22 |
23 | [Obsolete("Use GetLyricById instead", true)]
24 | protected override string GetLyric(SongItem songInfo)
25 | {
26 | return null;
27 | }
28 |
29 | protected override string GetLyricById(string Id)
30 | {
31 | return null;
32 | }
33 |
34 | protected override List GetPlaylist(string keyword)
35 | {
36 | return null;
37 | }
38 |
39 | protected override SongInfo Search(string keyword)
40 | {
41 | return null;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/DGJv3/Player.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using NAudio.Wave.SampleProviders;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.ComponentModel;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Runtime.CompilerServices;
10 | using System.Threading.Tasks;
11 | using System.Windows.Threading;
12 |
13 | namespace DGJv3
14 | {
15 | internal class Player : INotifyPropertyChanged
16 | {
17 | private Random random = new Random();
18 |
19 | private ObservableCollection Songs;
20 |
21 | private ObservableCollection Playlist;
22 |
23 | private Dispatcher dispatcher;
24 |
25 | private DispatcherTimer newSongTimer = new DispatcherTimer(DispatcherPriority.Normal)
26 | {
27 | Interval = TimeSpan.FromSeconds(1),
28 | IsEnabled = true,
29 | };
30 |
31 | private DispatcherTimer updateTimeTimer = new DispatcherTimer(DispatcherPriority.Normal)
32 | {
33 | Interval = TimeSpan.FromMilliseconds(100),
34 | IsEnabled = true,
35 | };
36 |
37 | public UniversalCommand PlayPauseCommand { get; private set; }
38 | public UniversalCommand NextCommand { get; private set; }
39 |
40 | ///
41 | /// 播放器类型
42 | ///
43 | public PlayerType PlayerType { get => _playerType; set => SetField(ref _playerType, value); }
44 | private PlayerType _playerType;
45 |
46 | ///
47 | /// DirectSound 设备
48 | ///
49 | public Guid DirectSoundDevice { get => _directSoundDevice; set => SetField(ref _directSoundDevice, value); }
50 | private Guid _directSoundDevice;
51 |
52 | ///
53 | /// WaveoutEvent 设备
54 | ///
55 | public int WaveoutEventDevice { get => _waveoutEventDevice; set => SetField(ref _waveoutEventDevice, value); }
56 | private int _waveoutEventDevice;
57 |
58 | ///
59 | /// 用户点歌优先
60 | ///
61 | public bool IsUserPrior { get => _isUserPrior; set => SetField(ref _isUserPrior, value); }
62 | private bool _isUserPrior = false;
63 |
64 | ///
65 | /// 当前播放时间
66 | ///
67 | public TimeSpan CurrentTime
68 | {
69 | get => mp3FileReader == null ? TimeSpan.Zero : mp3FileReader.CurrentTime;
70 | set
71 | {
72 | if (mp3FileReader != null)
73 | {
74 | mp3FileReader.CurrentTime = value;
75 | }
76 | }
77 | }
78 |
79 | public string CurrentTimeString { get => Math.Floor(CurrentTime.TotalMinutes) + ":" + CurrentTime.Seconds; }
80 |
81 | ///
82 | /// 当前播放时间秒数
83 | ///
84 | public double CurrentTimeDouble
85 | {
86 | get => CurrentTime.TotalSeconds;
87 | set => CurrentTime = TimeSpan.FromSeconds(value);
88 | }
89 |
90 | ///
91 | /// 歌曲全长
92 | ///
93 | public TimeSpan TotalTime { get => mp3FileReader == null ? TimeSpan.Zero : mp3FileReader.TotalTime; }
94 |
95 | public string TotalTimeString { get => Math.Floor(TotalTime.TotalMinutes) + ":" + TotalTime.Seconds; }
96 |
97 | ///
98 | /// 当前是否正在播放歌曲
99 | ///
100 | public bool IsPlaying
101 | {
102 | get => Status == PlayerStatus.Playing;
103 | set
104 | {
105 | if (value)
106 | {
107 | Play();
108 | }
109 | else
110 | {
111 | Pause();
112 | }
113 | }
114 | }
115 |
116 | ///
117 | /// 当前歌曲播放状态
118 | ///
119 | public PlayerStatus Status
120 | {
121 | get
122 | {
123 | if (wavePlayer != null)
124 | {
125 | switch (wavePlayer.PlaybackState)
126 | {
127 | case PlaybackState.Stopped:
128 | return PlayerStatus.Stopped;
129 | case PlaybackState.Playing:
130 | return PlayerStatus.Playing;
131 | case PlaybackState.Paused:
132 | return PlayerStatus.Paused;
133 | default:
134 | return PlayerStatus.Stopped;
135 | }
136 | }
137 | else
138 | {
139 | return PlayerStatus.Stopped;
140 | }
141 | }
142 | }
143 |
144 | ///
145 | /// 播放器音量
146 | ///
147 | public float Volume
148 | {
149 | get => _volume;
150 | set
151 | {
152 | if (sampleChannel != null)
153 | {
154 | sampleChannel.Volume = value;
155 | }
156 | SetField(ref _volume, value, nameof(Volume));
157 | }
158 | }
159 | private float _volume = 1f;
160 |
161 | ///
162 | /// 当前歌词
163 | ///
164 | public string CurrentLyric { get => currentLyric; set => SetField(ref currentLyric, value); }
165 | private string currentLyric;
166 |
167 | ///
168 | /// 下一句歌词
169 | ///
170 | public string UpcomingLyric { get => upcomingLyric; set => SetField(ref upcomingLyric, value); }
171 |
172 | ///
173 | /// 是否使用空闲歌单
174 | ///
175 | public bool IsPlaylistEnabled { get => _isPlaylistEnabled; set => SetField(ref _isPlaylistEnabled, value); }
176 | private bool _isPlaylistEnabled;
177 |
178 | private string upcomingLyric;
179 |
180 | private IWavePlayer wavePlayer = null;
181 |
182 | private Mp3FileReader mp3FileReader = null;
183 |
184 | private SampleChannel sampleChannel = null;
185 |
186 | private SongItem currentSong = null;
187 |
188 | private int currentLyricIndex = -1;
189 |
190 | public Player(ObservableCollection songs, ObservableCollection playlist)
191 | {
192 | Songs = songs;
193 | Playlist = playlist;
194 | dispatcher = Dispatcher.CurrentDispatcher;
195 | newSongTimer.Tick += NewSongTimer_Tick;
196 | updateTimeTimer.Tick += UpdateTimeTimer_Tick;
197 | PropertyChanged += This_PropertyChanged;
198 | PlayPauseCommand = new UniversalCommand((obj) => { IsPlaying ^= true; });
199 | NextCommand = new UniversalCommand((obj) => { Next(); });
200 | }
201 |
202 | private void This_PropertyChanged(object sender, PropertyChangedEventArgs e)
203 | {
204 | if (e.PropertyName == nameof(Status))
205 | {
206 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsPlaying)));
207 | }
208 | else if (e.PropertyName == nameof(CurrentTime))
209 | {
210 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTimeDouble)));
211 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTimeString)));
212 | }
213 | else if (e.PropertyName == nameof(TotalTime))
214 | {
215 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TotalTimeString)));
216 | }
217 | }
218 |
219 | ///
220 | /// 定时器 100ms 调用一次
221 | ///
222 | ///
223 | ///
224 | private void UpdateTimeTimer_Tick(object sender, EventArgs e)
225 | {
226 | if (mp3FileReader != null)
227 | {
228 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
229 |
230 | if (currentSong != null)
231 | {
232 | var index = currentSong.Lyric.GetLyric(CurrentTimeDouble, out string current, out string upcoming);
233 | if (index != currentLyricIndex)
234 | {
235 | currentLyricIndex = index;
236 | SetLyric(current, upcoming);
237 | }
238 | }
239 | }
240 | }
241 |
242 | ///
243 | /// 定时器 1s 调用一次
244 | ///
245 | ///
246 | ///
247 | private void NewSongTimer_Tick(object sender, EventArgs e)
248 | {
249 | if (Songs.Count > 0 && Songs[0].Status == SongStatus.WaitingPlay)
250 | {
251 | LoadSong(Songs[0]);
252 | }
253 | else if (Songs.Count > 1
254 | && currentSong != null
255 | && currentSong.UserName == Utilities.SparePlaylistUser
256 | && Songs.FirstOrDefault(
257 | s => s.UserName != Utilities.SparePlaylistUser)?.Status == SongStatus.WaitingPlay
258 | && IsUserPrior)
259 | {
260 | Next();
261 | var pendingRemove = Songs.Where(s => s.UserName == Utilities.SparePlaylistUser).ToList();
262 | foreach (var songItem in pendingRemove)
263 | {
264 | Songs.Remove(songItem);
265 | }
266 | }
267 |
268 | if (Songs.Count < 2 && IsPlaylistEnabled && Playlist.Count > 0)
269 | {
270 | int index = -1;
271 | int time = 0;
272 | do
273 | {
274 | index = random.Next(0, Playlist.Count);
275 | time++;
276 | } while (Songs.Any(ele => Playlist[index].Id == ele.SongId) && time < 3);
277 |
278 |
279 | SongInfo info = Playlist[index];
280 | if (info.Lyric == null)
281 | {
282 | info.Lyric = info.Module.SafeGetLyricById(info.Id);
283 | }
284 | Songs.Add(new SongItem(info, Utilities.SparePlaylistUser));
285 | }
286 | }
287 |
288 | ///
289 | /// 加载歌曲并开始播放
290 | ///
291 | ///
292 | private void LoadSong(SongItem songItem)
293 | {
294 | currentSong = songItem;
295 |
296 | currentSong.Status = SongStatus.Playing;
297 |
298 | wavePlayer = CreateIWavePlayer();
299 | mp3FileReader = new Mp3FileReader(currentSong.FilePath);
300 | sampleChannel = new SampleChannel(mp3FileReader)
301 | {
302 | Volume = Volume
303 | };
304 |
305 | wavePlayer.PlaybackStopped += (sender, e) => UnloadSong();
306 |
307 | wavePlayer.Init(sampleChannel);
308 | wavePlayer.Play();
309 |
310 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
311 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TotalTime)));
312 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
313 | }
314 |
315 | ///
316 | /// 卸载歌曲并善后
317 | ///
318 | private void UnloadSong()
319 | {
320 | try
321 | {
322 | wavePlayer?.Dispose();
323 | }
324 | catch (Exception) { }
325 |
326 | try
327 | {
328 | mp3FileReader?.Dispose();
329 | }
330 | catch (Exception) { }
331 |
332 | wavePlayer = null;
333 | sampleChannel = null;
334 | mp3FileReader = null;
335 |
336 | try
337 | {
338 | File.Delete(currentSong.FilePath);
339 | }
340 | catch (Exception)
341 | {
342 | }
343 |
344 | dispatcher.Invoke(() => Songs.Remove(currentSong));
345 |
346 | currentSong = null;
347 |
348 | SetLyric(string.Empty, string.Empty);
349 |
350 | // TODO: PlayerBroadcasterLoop 功能
351 |
352 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
353 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TotalTime)));
354 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
355 | }
356 |
357 | private void SetLyric(string current, string upcoming)
358 | {
359 | CurrentLyric = current;
360 | UpcomingLyric = upcoming;
361 | Task.Run(() => LyricEvent?.Invoke(this, new LyricChangedEventArgs()
362 | {
363 | CurrentLyric = current,
364 | UpcomingLyric = upcoming
365 | }));
366 | }
367 |
368 | ///
369 | /// 根据当前设置初始化 IWavePlayer
370 | ///
371 | ///
372 | private IWavePlayer CreateIWavePlayer()
373 | {
374 | switch (PlayerType)
375 | {
376 | case PlayerType.WaveOutEvent:
377 | return new WaveOutEvent() { DeviceNumber = WaveoutEventDevice };
378 | case PlayerType.DirectSound:
379 | return new DirectSoundOut(DirectSoundDevice);
380 | default:
381 | return null;
382 | }
383 | }
384 |
385 | ///
386 | /// 对外接口 继续
387 | ///
388 | /// 注:此接口可在任意线程同步调用
389 | ///
390 | ///
391 | public void Play()
392 | {
393 | if (wavePlayer != null)
394 | {
395 | wavePlayer.Play();
396 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
397 | }
398 |
399 | }
400 |
401 | ///
402 | /// 对外接口 暂停
403 | ///
404 | /// 注:此接口可在任意线程同步调用
405 | ///
406 | ///
407 | public void Pause()
408 | {
409 | if (wavePlayer != null)
410 | {
411 | wavePlayer.Pause();
412 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
413 | }
414 |
415 | }
416 |
417 | ///
418 | /// 对外接口 下一首
419 | ///
420 | /// 注:此接口可在任意线程同步调用
421 | ///
422 | ///
423 | public void Next()
424 | {
425 | if (wavePlayer != null)
426 | {
427 | wavePlayer.Stop();
428 | }
429 | }
430 |
431 | public event PropertyChangedEventHandler PropertyChanged;
432 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "")
433 | {
434 | if (EqualityComparer.Default.Equals(field, value))
435 | {
436 | return false;
437 | }
438 |
439 | field = value;
440 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
441 | return true;
442 | }
443 |
444 | public event LyricChangedEvent LyricEvent;
445 |
446 | public event LogEvent LogEvent;
447 | private void Log(string message, Exception exception) => LogEvent?.Invoke(this, new LogEventArgs() { Message = message, Exception = exception });
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/DGJv3/PlayerStatus.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3
2 | {
3 | public enum PlayerStatus
4 | {
5 | Stopped = 0,
6 | Playing = 1,
7 | Paused = 2,
8 | }
9 | }
--------------------------------------------------------------------------------
/DGJv3/PlayerType.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace DGJv3
4 | {
5 | public enum PlayerType : int
6 | {
7 | [Description("WaveOutEvent")]
8 | WaveOutEvent = 0,
9 | [Description("DirectSound")]
10 | DirectSound = 1,
11 | }
12 | }
--------------------------------------------------------------------------------
/DGJv3/PlayerVolumeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace DGJv3
6 | {
7 | class PlayerVolumeConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | return Math.Round((float)value * 100d);
12 | }
13 |
14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
15 | {
16 | return (float)((double)value / 100f);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DGJv3/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("点歌姬")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("点歌姬")]
13 | [assembly: AssemblyCopyright("Copyright © 2018")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("53970aec-fd0e-46ed-8269-3fd73dc3dd0f")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("3.0.0.0")]
36 | [assembly: AssemblyFileVersion("3.0.0.0")]
37 |
--------------------------------------------------------------------------------
/DGJv3/SearchModule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Security.Cryptography;
5 | using System.Text;
6 | using System.Threading;
7 |
8 | namespace DGJv3
9 | {
10 | public abstract class SearchModule
11 | {
12 | private bool canChangeName = true;
13 |
14 | #region 模块信息
15 |
16 | ///
17 | /// 模块名称
18 | ///
19 | public string ModuleName { get; private set; } = "歌曲平台名称";
20 |
21 | ///
22 | /// 模块作者
23 | ///
24 | public string ModuleAuthor { get; private set; } = "作者名字";
25 |
26 | ///
27 | /// 模块作者联系方式
28 | ///
29 | public string ModuleContact { get; private set; } = "联系方式";
30 |
31 | ///
32 | /// 模块版本号
33 | ///
34 | public string ModuleVersion { get; private set; } = "未填写";
35 |
36 | ///
37 | /// 搜索模块说明
38 | ///
39 | public string ModuleDescription { get; private set; } = "没有填写说明";
40 |
41 |
42 | ///
43 | /// 是否负责下载
44 | ///
45 | public bool IsHandleDownlaod
46 | { get; protected set; }
47 |
48 | ///
49 | /// 需要显示设置选项
50 | ///
51 | public bool IsNeedSettings
52 | { get; protected set; }
53 |
54 | ///
55 | /// 是否支持歌单搜索
56 | ///
57 | public bool IsPlaylistSupported
58 | { get; protected set; }
59 |
60 | ///
61 | /// 搜索模块的唯一ID
62 | ///
63 | internal string UniqueId
64 | {
65 | get
66 | {
67 | if (uniqueId == null)
68 | {
69 | using (MD5 md5 = MD5.Create())
70 | {
71 | uniqueId = BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes($"{GetType().FullName}{ModuleName}{ModuleAuthor}"))).Replace("-", "");
72 | }
73 | }
74 |
75 | return uniqueId;
76 | }
77 | }
78 | private string uniqueId = null;
79 |
80 |
81 |
82 | ///
83 | /// 设置搜索模块信息
84 | /// 注意:此处只能设置一次
85 | ///
86 | /// 模块名称最好不与其他模块重复。
87 | /// 并且表达出是在哪个平台搜索的。
88 | /// 作者联系方式用于生成报错文件。
89 | ///
90 | ///
91 | /// 模块名称
92 | /// 模块作者
93 | /// 作者联系方式
94 | /// 版本号
95 | /// 模块说明
96 | /// 是否支持歌词
97 | protected void SetInfo(string name, string author, string contact, string version, string description)
98 | {
99 | if (canChangeName)
100 | {
101 | canChangeName = false;
102 | ModuleName = name;
103 | ModuleAuthor = author;
104 | ModuleContact = contact;
105 | ModuleVersion = version;
106 | ModuleDescription = description;
107 | }
108 | }
109 |
110 | #endregion
111 |
112 | ///
113 | /// 搜索歌曲
114 | ///
115 | /// 搜索人昵称
116 | /// 要搜索的字符串
117 | /// 是否需要歌词
118 | /// 打包好的搜索结果
119 | protected abstract SongInfo Search(string keyword);
120 |
121 | ///
122 | /// 请重写此方法
123 | /// 获取播放列表方法
124 | ///
125 | /// 搜索人昵称
126 | /// 歌单的关键词(或ID)
127 | /// 是否需要歌词
128 | ///
129 | protected abstract List GetPlaylist(string keyword);
130 |
131 | ///
132 | /// 主插件调用用
133 | ///
134 | /// 搜索人昵称
135 | /// 要搜索的字符串
136 | /// 是否需要歌词
137 | /// 搜索结果
138 | internal SongInfo SafeSearch(string keyword)
139 | {
140 | try
141 | {
142 | return Search(keyword);
143 | }
144 | catch (Exception ex)
145 | {
146 | WriteError(ex, "keyword: " + keyword);
147 | return null;
148 | }
149 | }
150 |
151 | protected abstract string GetDownloadUrl(SongItem songInfo);
152 |
153 | internal string SafeGetDownloadUrl(SongItem songInfo)
154 | {
155 | try
156 | {
157 | return GetDownloadUrl(songInfo);
158 | }
159 | catch (Exception ex)
160 | {
161 | WriteError(ex, "SongId: " + songInfo.SongId);
162 | return null;
163 | }
164 | }
165 |
166 | [Obsolete("Use GetLyricById instead", true)]
167 | protected abstract string GetLyric(SongItem songInfo);
168 |
169 | protected abstract string GetLyricById(string Id);
170 |
171 | internal string SafeGetLyricById(string Id)
172 | {
173 | try
174 | {
175 | return GetLyricById(Id);
176 | }
177 | catch (Exception ex)
178 | {
179 | WriteError(ex, "Id: " + Id);
180 | return null;
181 | }
182 | }
183 |
184 | ///
185 | /// 主插件调用用
186 | ///
187 | /// 搜索人昵称(一般会是主播)
188 | /// 歌单的关键词(或ID)
189 | /// 是否需要歌词
190 | ///
191 | public List SafeGetPlaylist(string keyword)
192 | {
193 | try
194 | {
195 | return GetPlaylist(keyword);
196 | }
197 | catch (Exception ex)
198 | {
199 | WriteError(ex, "keyword: " + keyword);
200 | return null;
201 | }
202 | }
203 |
204 | ///
205 | /// 请在不能使用普通方式下载歌曲文件的情况下重写替代下载
206 | ///
207 | /// 要下载的歌曲信息
208 | /// 下载是否成功
209 | protected abstract DownloadStatus Download(SongItem item);
210 |
211 | ///
212 | /// 主插件调用用
213 | ///
214 | /// 要下载的歌曲信息
215 | /// 下载是否成功
216 | internal DownloadStatus SafeDownload(SongItem item)
217 | {
218 | try
219 | {
220 | return Download(item);
221 | }
222 | catch (Exception ex)
223 | {
224 | WriteError(ex, "参数:filepath=" + item.FilePath + " id=" + item.SongId);
225 | return DownloadStatus.Failed;
226 | }
227 | }
228 |
229 |
230 | ///
231 | /// 设置搜索模块时会调用
232 | /// 会在新线程调用
233 | ///
234 | protected virtual void Setting()
235 | {
236 |
237 | }
238 |
239 | ///
240 | /// 主插件调用用
241 | ///
242 | internal void SafeSetting()
243 | {
244 | new Thread(() =>
245 | {
246 | try
247 | {
248 | Setting();
249 | }
250 | catch (Exception ex)
251 | {
252 | WriteError(ex, "Settings");
253 | }
254 | })
255 | { Name = "ModuleSafeSetting", IsBackground = true }.Start();
256 | }
257 |
258 | /*
259 | ///
260 | /// 设置插件本体
261 | ///
262 | /// 插件本体
263 | /// 模块本体
264 | internal SearchModule SetMainPlugin(PluginMain pl)
265 | {
266 | plugin = pl;
267 | return this;
268 | }
269 |
270 | ///
271 | /// 点歌姬主插件版本号
272 | ///
273 | public string MainPluginVersion
274 | { get { return plugin.PluginVer; } }
275 |
276 | ///
277 | /// 输出文本到弹幕姬日志
278 | ///
279 | /// 要显示的文本
280 | /// 是否显示到侧边栏
281 | protected void Log(string text, bool danmu = false)
282 | {
283 | if (plugin != null)
284 | {
285 | text = "搜索模块 " + ModuleName + ":" + text;
286 | plugin.Log(text);
287 | if (danmu)
288 | {
289 | plugin.AddDM(text);
290 | }
291 | }
292 | }
293 | */
294 |
295 | internal Action _log { get; set; }
296 |
297 | protected internal void Log(string log) => _log?.Invoke(ModuleName + " " + log);
298 |
299 | private void WriteError(Exception exception, string description)
300 | {
301 | try
302 | {
303 | string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
304 | using (StreamWriter outfile = new StreamWriter(path + @"\B站彈幕姬点歌姬歌曲搜索引擎" + ModuleName + "错误报告.txt"))
305 | {
306 | outfile.WriteLine("请将错误报告发给 " + ModuleAuthor + " 谢谢,联系方式:" + ModuleContact);
307 | outfile.WriteLine(description);
308 | outfile.WriteLine(ModuleName + " 本地时间:" + DateTime.Now.ToString());
309 | outfile.Write(exception.ToString());
310 | new Thread(() =>
311 | {
312 | System.Windows.MessageBox.Show("点歌姬歌曲搜索引擎“" + ModuleName + @"”遇到了未处理的错误
313 | 日志已经保存在桌面,请发给引擎作者 " + ModuleAuthor + ", 联系方式:" + ModuleContact);
314 | }).Start();
315 | }
316 | }
317 | catch (Exception)
318 | { }
319 | }
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/DGJv3/SearchModules.cs:
--------------------------------------------------------------------------------
1 | using DGJv3.InternalModule;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.ComponentModel;
6 | using System.Runtime.CompilerServices;
7 |
8 | namespace DGJv3
9 | {
10 | class SearchModules : INotifyPropertyChanged
11 | {
12 | public SearchModule NullModule { get; private set; }
13 | public ObservableCollection Modules { get; set; }
14 | public SearchModule PrimaryModule { get => primaryModule; set => SetField(ref primaryModule, value); }
15 | public SearchModule SecondaryModule { get => secondaryModule; set => SetField(ref secondaryModule, value); }
16 |
17 | private SearchModule primaryModule;
18 | private SearchModule secondaryModule;
19 |
20 |
21 | internal SearchModules()
22 | {
23 | Modules = new ObservableCollection();
24 |
25 | NullModule = new NullSearchModule();
26 | Modules.Add(NullModule);
27 |
28 | Modules.Add(new LwlApiNetease());
29 | Modules.Add(new LwlApiTencent());
30 | Modules.Add(new LwlApiKugou());
31 | Modules.Add(new LwlApiBaidu());
32 | Modules.Add(new LwlApiXiami());
33 |
34 | // TODO: 加载外置拓展
35 |
36 | void logaction(string log)
37 | {
38 | Log(log);
39 | }
40 |
41 | foreach (var m in Modules)
42 | {
43 | m._log = logaction;
44 | }
45 |
46 | PrimaryModule = Modules[1];
47 | SecondaryModule = Modules[2];
48 | }
49 |
50 | public event PropertyChangedEventHandler PropertyChanged;
51 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "")
52 | {
53 | if (EqualityComparer.Default.Equals(field, value)) return false;
54 | field = value;
55 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
56 | return true;
57 | }
58 |
59 | public event LogEvent LogEvent;
60 | private void Log(string message, Exception exception = null) => LogEvent?.Invoke(this, new LogEventArgs() { Message = message, Exception = exception });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/DGJv3/SongInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.ComponentModel;
3 |
4 | namespace DGJv3
5 | {
6 | public class SongInfo : INotifyPropertyChanged
7 | {
8 | [JsonIgnore]
9 | public SearchModule Module;
10 |
11 | public event PropertyChangedEventHandler PropertyChanged;
12 |
13 | [JsonProperty("smid")]
14 | public string ModuleId { get; set; }
15 |
16 | [JsonProperty("siid")]
17 | public string Id { get; set; }
18 | [JsonProperty("name")]
19 | public string Name { get; set; }
20 | [JsonProperty("sing")]
21 | public string[] Singers { get; set; }
22 | [JsonIgnore]
23 | public string SingersText { get => string.Join(";", Singers); }
24 |
25 | ///
26 | /// Lyric存储的是这个歌曲的歌词文件,为null时,会认为是延迟获取,在下载歌曲时再通过接口尝试获取lrc
27 | ///
28 | [JsonProperty("lrc")]
29 | public string Lyric { get; set; }
30 | [JsonProperty("note")]
31 | public string Note { get; set; }
32 |
33 | [JsonConstructor]
34 | private SongInfo() { }
35 |
36 | public SongInfo(SearchModule module) : this(module, string.Empty, string.Empty, null) { }
37 | public SongInfo(SearchModule module, string id, string name, string[] singers) : this(module, id, name, singers, string.Empty) { }
38 | public SongInfo(SearchModule module, string id, string name, string[] singers, string lyric) : this(module, id, name, singers, lyric, string.Empty) { }
39 | public SongInfo(SearchModule module, string id, string name, string[] singers, string lyric, string note)
40 | {
41 | Module = module;
42 |
43 | ModuleId = Module.UniqueId;
44 |
45 | Id = id;
46 | Name = name;
47 | Singers = singers;
48 | Lyric = lyric;
49 | Note = note;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/DGJv3/SongItem.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace DGJv3
6 | {
7 | public class SongItem : INotifyPropertyChanged
8 | {
9 |
10 | internal SongItem(SongInfo songInfo, string userName)
11 | {
12 | Status = SongStatus.WaitingDownload;
13 |
14 | UserName = userName;
15 |
16 | Module = songInfo.Module;
17 | SongId = songInfo.Id;
18 | SongName = songInfo.Name;
19 | Singers = songInfo.Singers;
20 | Lyric = (songInfo.Lyric == null) ? Lrc.NoLyric : Lrc.InitLrc(songInfo.Lyric);
21 | Note = songInfo.Note;
22 |
23 | }
24 |
25 | ///
26 | /// 搜索模块名称
27 | ///
28 | public string ModuleName
29 | { get { return Module.ModuleName; } }
30 |
31 | ///
32 | /// 搜索模块
33 | ///
34 | internal SearchModule Module
35 | { get; set; }
36 |
37 | ///
38 | /// 歌曲ID
39 | ///
40 | public string SongId
41 | { get; internal set; }
42 |
43 | ///
44 | /// 歌名
45 | ///
46 | public string SongName
47 | { get; internal set; }
48 |
49 | ///
50 | /// string的歌手列表
51 | ///
52 | public string SingersText
53 | {
54 | get
55 | {
56 | string output = "";
57 | foreach (string str in Singers)
58 | output += str + ";";
59 | return output;
60 | }
61 | }
62 |
63 | ///
64 | /// 歌手列表
65 | ///
66 | public string[] Singers
67 | { get; internal set; }
68 |
69 | ///
70 | /// 点歌人
71 | ///
72 | public string UserName
73 | { get; internal set; }
74 |
75 | // ///
76 | /// 下载地址
77 | ///
78 | // public string DownloadURL
79 | // { get; internal set; }
80 |
81 | ///
82 | /// 歌曲文件储存路径
83 | ///
84 | public string FilePath
85 | { get; internal set; }
86 |
87 | ///
88 | /// 文本歌词
89 | ///
90 | public Lrc Lyric
91 | { get; internal set; }
92 |
93 | ///
94 | /// 歌曲备注
95 | ///
96 | public string Note
97 | { get; internal set; }
98 |
99 | ///
100 | /// 歌曲状态
101 | ///
102 | public SongStatus Status
103 | { get => _status; internal set => SetField(ref _status, value); }
104 |
105 | private SongStatus _status;
106 |
107 | public event PropertyChangedEventHandler PropertyChanged;
108 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "")
109 | {
110 | if (EqualityComparer.Default.Equals(field, value)) return false;
111 | field = value;
112 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
113 | return true;
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------
/DGJv3/SongStatus.cs:
--------------------------------------------------------------------------------
1 | namespace DGJv3
2 | {
3 | public enum SongStatus
4 | {
5 | ///
6 | /// 等待下载
7 | ///
8 | WaitingDownload,
9 | ///
10 | /// 正在下载
11 | ///
12 | Downloading,
13 | ///
14 | /// 等待播放
15 | ///
16 | WaitingPlay,
17 | ///
18 | /// 正在播放
19 | ///
20 | Playing
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/DGJv3/SongStatusStringConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace DGJv3
6 | {
7 | class SongStatusStringConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | if (value is SongStatus songStatus)
12 | {
13 | switch (songStatus)
14 | {
15 | case SongStatus.WaitingDownload:
16 | return "等待下载";
17 | case SongStatus.Downloading:
18 | return "正在下载";
19 | case SongStatus.WaitingPlay:
20 | return "等待播放";
21 | case SongStatus.Playing:
22 | return "正在播放";
23 | default:
24 | break;
25 | }
26 | }
27 | throw new NotImplementedException();
28 | }
29 |
30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
31 | {
32 | throw new NotImplementedException();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/DGJv3/UniversalCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Input;
3 |
4 | namespace DGJv3
5 | {
6 | internal class UniversalCommand : ICommand
7 | {
8 | private Func