├── .gitignore
├── .idea
├── .gitignore
├── MusicFreePlugins.iml
├── modules.xml
└── vcs.xml
├── LICENSE
├── dist
├── _plugins
│ └── plugins.json
├── audiomack
│ └── index.js
├── bilibili
│ └── index.js
├── geciqianxun
│ └── index.js
├── geciwang
│ └── index.js
├── kuaishou
│ └── index.js
├── maoerfm
│ └── index.js
├── navidrome
│ └── index.js
├── suno
│ └── index.js
├── udio
│ └── index.js
├── webdav
│ └── index.js
├── yinyuetai
│ └── index.js
└── youtube
│ └── index.js
├── example
└── freesound.js
├── package-lock.json
├── package.json
├── plugins.json
├── plugins
├── audiomack
│ └── index.ts
├── bilibili
│ └── index.ts
├── geciqianxun
│ └── index.ts
├── geciwang
│ └── index.ts
├── kuaishou
│ └── index.ts
├── maoerfm
│ └── index.ts
├── navidrome
│ └── index.ts
├── suno
│ └── index.ts
├── udio
│ └── index.ts
├── webdav
│ └── index.ts
├── yinyuetai
│ └── index.ts
└── youtube
│ └── index.ts
├── readme.md
├── scripts
└── generate.js
├── test
├── geciqianxun.ts
├── geciwang.ts
└── kuaishou.ts
├── tsconfig.json
└── types
├── global.d.ts
├── mediaType.d.ts
└── plugin.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | temp*
3 | .idea
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/MusicFreePlugins.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/dist/_plugins/plugins.json:
--------------------------------------------------------------------------------
1 | {"desc":"此链接为 MusicFree 插件,插件开发及使用方式参考 https://musicfree.upup.fun","plugins":[{"name":"Audiomack","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/audiomack/index.js","version":"0.0.2"},{"name":"歌词网","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciwang/index.js","version":"0.0.0"},{"name":"歌词千寻","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciqianxun/index.js","version":"0.0.0"},{"name":"Navidrome","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/navidrome/index.js","version":"0.0.0"},{"name":"suno","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/suno/index.js","version":"0.0.0"},{"name":"udio","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/udio/index.js","version":"0.0.0"},{"name":"猫耳FM","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/maoerfm/index.js","version":"0.1.4"},{"name":"快手","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/kuaishou/index.js","version":"0.0.2"},{"name":"音悦台","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/yinyuetai/index.js","version":"0.0.1"},{"name":"Youtube","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/youtube/index.js","version":"0.0.1"},{"name":"bilibili","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/bilibili/index.js","version":"0.2.3"},{"name":"WebDAV","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/webdav/index.js","version":"0.0.2"}]}
--------------------------------------------------------------------------------
/dist/audiomack/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const cheerio_1 = require("cheerio");
5 | const CryptoJS = require("crypto-js");
6 | const dayjs = require("dayjs");
7 | const pageSize = 20;
8 | const headers = {
9 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
10 | };
11 | function nonce(e = 10) {
12 | let n = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", r = "";
13 | for (let i = 0; i < e; i++)
14 | r += n.charAt(Math.floor(Math.random() * n.length));
15 | return r;
16 | }
17 | function getNormalizedParams(parameters) {
18 | const sortedKeys = [];
19 | const normalizedParameters = [];
20 | for (let e in parameters) {
21 | sortedKeys.push(_encode(e));
22 | }
23 | sortedKeys.sort();
24 | for (let idx = 0; idx < sortedKeys.length; idx++) {
25 | const e = sortedKeys[idx];
26 | var n, r, i = _decode(e), a = parameters[i];
27 | for (a.sort(), n = 0; n < a.length; n++)
28 | (r = _encode(a[n])), normalizedParameters.push(e + "=" + r);
29 | }
30 | return normalizedParameters.join("&");
31 | }
32 | function _encode(e) {
33 | return e
34 | ? encodeURIComponent(e)
35 | .replace(/[!'()]/g, escape)
36 | .replace(/\*/g, "%2A")
37 | : "";
38 | }
39 | function _decode(e) {
40 | return e ? decodeURIComponent(e) : "";
41 | }
42 | function u(e) {
43 | (this._parameters = {}), this._loadParameters(e || {});
44 | }
45 | u.prototype = {
46 | _loadParameters: function (e) {
47 | e instanceof Array
48 | ? this._loadParametersFromArray(e)
49 | : "object" == typeof e && this._loadParametersFromObject(e);
50 | },
51 | _loadParametersFromArray: function (e) {
52 | var n;
53 | for (n = 0; n < e.length; n++)
54 | this._loadParametersFromObject(e[n]);
55 | },
56 | _loadParametersFromObject: function (e) {
57 | var n;
58 | for (n in e)
59 | if (e.hasOwnProperty(n)) {
60 | var r = this._getStringFromParameter(e[n]);
61 | this._loadParameterValue(n, r);
62 | }
63 | },
64 | _loadParameterValue: function (e, n) {
65 | var r;
66 | if (n instanceof Array) {
67 | for (r = 0; r < n.length; r++) {
68 | var i = this._getStringFromParameter(n[r]);
69 | this._addParameter(e, i);
70 | }
71 | 0 == n.length && this._addParameter(e, "");
72 | }
73 | else
74 | this._addParameter(e, n);
75 | },
76 | _getStringFromParameter: function (e) {
77 | var n = e || "";
78 | try {
79 | ("number" == typeof e || "boolean" == typeof e) && (n = e.toString());
80 | }
81 | catch (e) { }
82 | return n;
83 | },
84 | _addParameter: function (e, n) {
85 | this._parameters[e] || (this._parameters[e] = []),
86 | this._parameters[e].push(n);
87 | },
88 | get: function () {
89 | return this._parameters;
90 | },
91 | };
92 | function getSignature(method, urlPath, params, secret = "f3ac5b086f3eab260520d8e3049561e6") {
93 | urlPath = urlPath.split("?")[0];
94 | urlPath = urlPath.startsWith("http")
95 | ? urlPath
96 | : "https://api.audiomack.com/v1" + urlPath;
97 | const r = new u(params).get();
98 | const httpMethod = method.toUpperCase();
99 | const normdParams = getNormalizedParams(r);
100 | const l = _encode(httpMethod) + "&" + _encode(urlPath) + "&" + _encode(normdParams);
101 | const hash = CryptoJS.HmacSHA1(l, secret + "&").toString(CryptoJS.enc.Base64);
102 | return hash;
103 | }
104 | function formatMusicItem(raw) {
105 | return {
106 | id: raw.id,
107 | artwork: raw.image || raw.image_base,
108 | duration: +raw.duration,
109 | title: raw.title,
110 | artist: raw.artist,
111 | album: raw.album,
112 | url_slug: raw.url_slug,
113 | };
114 | }
115 | function formatAlbumItem(raw) {
116 | var _a, _b;
117 | return {
118 | artist: raw.artist,
119 | artwork: raw.image || raw.image_base,
120 | id: raw.id,
121 | date: dayjs.unix(+raw.released).format("YYYY-MM-DD"),
122 | title: raw.title,
123 | _musicList: (_b = (_a = raw === null || raw === void 0 ? void 0 : raw.tracks) === null || _a === void 0 ? void 0 : _a.map) === null || _b === void 0 ? void 0 : _b.call(_a, (it) => ({
124 | id: it.song_id || it.id,
125 | artwork: raw.image || raw.image_base,
126 | duration: +it.duration,
127 | title: it.title,
128 | artist: it.artist,
129 | album: raw.title,
130 | })),
131 | };
132 | }
133 | function formatMusicSheetItem(raw) {
134 | var _a, _b, _c, _d, _e, _f;
135 | return {
136 | worksNum: raw.track_count,
137 | id: raw.id,
138 | title: raw.title,
139 | artist: (_a = raw.artist) === null || _a === void 0 ? void 0 : _a.name,
140 | artwork: raw.image || raw.image_base,
141 | artistItem: {
142 | id: (_b = raw.artist) === null || _b === void 0 ? void 0 : _b.id,
143 | avatar: ((_c = raw.artist) === null || _c === void 0 ? void 0 : _c.image) || ((_d = raw.artist) === null || _d === void 0 ? void 0 : _d.image_base),
144 | name: (_e = raw.artist) === null || _e === void 0 ? void 0 : _e.name,
145 | url_slug: (_f = raw.artist) === null || _f === void 0 ? void 0 : _f.url_slug,
146 | },
147 | createAt: dayjs.unix(+raw.created).format("YYYY-MM-DD"),
148 | url_slug: raw.url_slug,
149 | };
150 | }
151 | async function searchBase(query, page, show) {
152 | const params = {
153 | limit: pageSize,
154 | oauth_consumer_key: "audiomack-js",
155 | oauth_nonce: nonce(32),
156 | oauth_signature_method: "HMAC-SHA1",
157 | oauth_timestamp: Math.round(Date.now() / 1e3),
158 | oauth_version: "1.0",
159 | page: page,
160 | q: query,
161 | show: show,
162 | sort: "popular",
163 | };
164 | const oauth_signature = getSignature("GET", "/search", params);
165 | const results = (await axios_1.default.get("https://api.audiomack.com/v1/search", {
166 | headers,
167 | params: Object.assign(Object.assign({}, params), { oauth_signature }),
168 | })).data.results;
169 | return results;
170 | }
171 | async function searchMusic(query, page) {
172 | const results = await searchBase(query, page, "songs");
173 | return {
174 | isEnd: results.length < pageSize,
175 | data: results.map(formatMusicItem),
176 | };
177 | }
178 | async function searchAlbum(query, page) {
179 | const results = await searchBase(query, page, "albums");
180 | return {
181 | isEnd: results.length < pageSize,
182 | data: results.map(formatAlbumItem),
183 | };
184 | }
185 | async function searchMusicSheet(query, page) {
186 | const results = await searchBase(query, page, "playlists");
187 | return {
188 | isEnd: results.length < pageSize,
189 | data: results.map(formatMusicSheetItem),
190 | };
191 | }
192 | async function searchArtist(query, page) {
193 | const results = await searchBase(query, page, "artists");
194 | return {
195 | isEnd: results.length < pageSize,
196 | data: results.map((raw) => ({
197 | name: raw.name,
198 | id: raw.id,
199 | avatar: raw.image || raw.image_base,
200 | url_slug: raw.url_slug,
201 | })),
202 | };
203 | }
204 | let dataUrlBase;
205 | async function getDataUrlBase() {
206 | if (dataUrlBase) {
207 | return dataUrlBase;
208 | }
209 | const rawHtml = (await axios_1.default.get("https://audiomack.com/")).data;
210 | const $ = (0, cheerio_1.load)(rawHtml);
211 | const script = $("script#__NEXT_DATA__").text();
212 | const jsonObj = JSON.parse(script);
213 | if (jsonObj.buildId) {
214 | dataUrlBase = `https://audiomack.com/_next/data/${jsonObj.buildId}`;
215 | }
216 | return dataUrlBase;
217 | }
218 | async function getArtistWorks(artistItem, page, type) {
219 | if (type === "music") {
220 | const params = {
221 | artist_id: artistItem.id,
222 | limit: pageSize,
223 | oauth_consumer_key: "audiomack-js",
224 | oauth_nonce: nonce(32),
225 | oauth_signature_method: "HMAC-SHA1",
226 | oauth_timestamp: Math.round(Date.now() / 1e3),
227 | oauth_version: "1.0",
228 | page: page,
229 | sort: "rank",
230 | type: "songs",
231 | };
232 | const oauth_signature = getSignature("GET", "/search_artist_content", params);
233 | const results = (await axios_1.default.get("https://api.audiomack.com/v1/search_artist_content", {
234 | headers,
235 | params: Object.assign(Object.assign({}, params), { oauth_signature }),
236 | })).data.results;
237 | return {
238 | isEnd: results.length < pageSize,
239 | data: results.map(formatMusicItem),
240 | };
241 | }
242 | else if (type === "album") {
243 | const params = {
244 | artist_id: artistItem.id,
245 | limit: pageSize,
246 | oauth_consumer_key: "audiomack-js",
247 | oauth_nonce: nonce(32),
248 | oauth_signature_method: "HMAC-SHA1",
249 | oauth_timestamp: Math.round(Date.now() / 1e3),
250 | oauth_version: "1.0",
251 | page: page,
252 | sort: "rank",
253 | type: "albums",
254 | };
255 | const oauth_signature = getSignature("GET", "/search_artist_content", params);
256 | const results = (await axios_1.default.get("https://api.audiomack.com/v1/search_artist_content", {
257 | headers,
258 | params: Object.assign(Object.assign({}, params), { oauth_signature }),
259 | })).data.results;
260 | return {
261 | isEnd: results.length < pageSize,
262 | data: results.map(formatAlbumItem),
263 | };
264 | }
265 | }
266 | async function getMusicSheetInfo(sheet, page) {
267 | const _dataUrlBase = await getDataUrlBase();
268 | const res = (await axios_1.default.get(`${_dataUrlBase}/${sheet.artistItem.url_slug}/playlist/${sheet.url_slug}.json`, {
269 | params: {
270 | page_slug: sheet.artistItem.url_slug,
271 | playlist_slug: sheet.url_slug,
272 | },
273 | headers: Object.assign({}, headers),
274 | })).data;
275 | const musicPage = res.pageProps.initialState.musicPage;
276 | const targetKey = Object.keys(musicPage).find((it) => it.startsWith("musicMusicPage"));
277 | const tracks = musicPage[targetKey].results.tracks;
278 | return {
279 | isEnd: true,
280 | musicList: tracks.map(formatMusicItem),
281 | };
282 | }
283 | async function getMediaSource(musicItem, quality) {
284 | if (quality !== "standard") {
285 | return;
286 | }
287 | const params = {
288 | environment: "desktop-web",
289 | hq: true,
290 | oauth_consumer_key: "audiomack-js",
291 | oauth_nonce: nonce(32),
292 | oauth_signature_method: "HMAC-SHA1",
293 | oauth_timestamp: Math.round(Date.now() / 1e3),
294 | oauth_version: "1.0",
295 | section: "/search",
296 | };
297 | const oauth_signature = getSignature("GET", `/music/play/${musicItem.id}`, params);
298 | const res = (await axios_1.default.get(`https://api.audiomack.com/v1/music/play/${musicItem.id}`, {
299 | headers: Object.assign(Object.assign({}, headers), { origin: "https://audiomack.com" }),
300 | params: Object.assign(Object.assign({}, params), { oauth_signature }),
301 | })).data;
302 | return {
303 | url: res.signedUrl,
304 | };
305 | }
306 | async function getAlbumInfo(albumItem) {
307 | return {
308 | musicList: albumItem._musicList.map((it) => (Object.assign({}, it))),
309 | };
310 | }
311 | async function getRecommendSheetTags() {
312 | const rawHtml = (await axios_1.default.get("https://audiomack.com/playlists")).data;
313 | const $ = (0, cheerio_1.load)(rawHtml);
314 | const script = $("script#__NEXT_DATA__").text();
315 | const jsonObj = JSON.parse(script);
316 | return {
317 | data: [
318 | {
319 | data: jsonObj.props.pageProps.categories,
320 | },
321 | ],
322 | };
323 | }
324 | async function getRecommendSheetsByTag(tag, page) {
325 | if (!tag.id) {
326 | tag = { id: "34", title: "What's New", url_slug: "whats-new" };
327 | }
328 | const params = {
329 | featured: "yes",
330 | limit: pageSize,
331 | oauth_consumer_key: "audiomack-js",
332 | oauth_nonce: nonce(32),
333 | oauth_signature_method: "HMAC-SHA1",
334 | oauth_timestamp: Math.round(Date.now() / 1e3),
335 | oauth_version: "1.0",
336 | page: page,
337 | slug: tag.url_slug,
338 | };
339 | const oauth_signature = getSignature("GET", "/playlist/categories", params);
340 | const results = (await axios_1.default.get("https://api.audiomack.com/v1/playlist/categories", {
341 | headers,
342 | params: Object.assign(Object.assign({}, params), { oauth_signature }),
343 | })).data.results.playlists;
344 | return {
345 | isEnd: results.length < pageSize,
346 | data: results.map(formatMusicSheetItem),
347 | };
348 | }
349 | async function getTopLists() {
350 | const genres = [
351 | {
352 | title: "All Genres",
353 | url_slug: null,
354 | },
355 | {
356 | title: "Afrosounds",
357 | url_slug: "afrobeats",
358 | },
359 | {
360 | title: "Hip-Hop/Rap",
361 | url_slug: "rap",
362 | },
363 | {
364 | title: "Latin",
365 | url_slug: "latin",
366 | },
367 | {
368 | title: "Caribbean",
369 | url_slug: "caribbean",
370 | },
371 | {
372 | title: "Pop",
373 | url_slug: "pop",
374 | },
375 | {
376 | title: "R&B",
377 | url_slug: "rb",
378 | },
379 | {
380 | title: "Gospel",
381 | url_slug: "gospel",
382 | },
383 | {
384 | title: "Electronic",
385 | url_slug: "electronic",
386 | },
387 | {
388 | title: "Rock",
389 | url_slug: "rock",
390 | },
391 | {
392 | title: "Punjabi",
393 | url_slug: "punjabi",
394 | },
395 | {
396 | title: "Country",
397 | url_slug: "country",
398 | },
399 | {
400 | title: "Instrumental",
401 | url_slug: "instrumental",
402 | },
403 | {
404 | title: "Podcast",
405 | url_slug: "podcast",
406 | },
407 | ];
408 | return [
409 | {
410 | title: "Trending Songs",
411 | data: genres.map((it) => {
412 | var _a;
413 | return (Object.assign(Object.assign({}, it), { type: "trending", id: (_a = it.url_slug) !== null && _a !== void 0 ? _a : it.title }));
414 | }),
415 | },
416 | {
417 | title: "Recently Added Music",
418 | data: genres.map((it) => {
419 | var _a;
420 | return (Object.assign(Object.assign({}, it), { type: "recent", id: (_a = it.url_slug) !== null && _a !== void 0 ? _a : it.title }));
421 | }),
422 | },
423 | ];
424 | }
425 | async function getTopListDetail(topListItem, page = 1) {
426 | const type = topListItem.type;
427 | const partialUrl = `/music/${topListItem.url_slug ? `${topListItem.url_slug}/` : ""}${type}/page/${page}`;
428 | const url = `https://api.audiomack.com/v1${partialUrl}`;
429 | const params = {
430 | oauth_consumer_key: "audiomack-js",
431 | oauth_nonce: nonce(32),
432 | oauth_signature_method: "HMAC-SHA1",
433 | oauth_timestamp: Math.round(Date.now() / 1e3),
434 | oauth_version: "1.0",
435 | type: "song",
436 | };
437 | const oauth_signature = getSignature("GET", partialUrl, params);
438 | const results = (await axios_1.default.get(url, {
439 | headers,
440 | params: Object.assign(Object.assign({}, params), { oauth_signature }),
441 | })).data.results;
442 | return {
443 | musicList: results.map(formatMusicItem),
444 | };
445 | }
446 | module.exports = {
447 | platform: "Audiomack",
448 | version: "0.0.2",
449 | author: '猫头猫',
450 | primaryKey: ["id", "url_slug"],
451 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/audiomack/index.js",
452 | cacheControl: "no-cache",
453 | supportedSearchType: ['music', 'album', 'sheet', 'artist'],
454 | async search(query, page, type) {
455 | if (type === "music") {
456 | return await searchMusic(query, page);
457 | }
458 | else if (type === "album") {
459 | return await searchAlbum(query, page);
460 | }
461 | else if (type === "sheet") {
462 | return await searchMusicSheet(query, page);
463 | }
464 | else if (type === "artist") {
465 | return await searchArtist(query, page);
466 | }
467 | },
468 | getMediaSource,
469 | getAlbumInfo,
470 | getMusicSheetInfo,
471 | getArtistWorks,
472 | getRecommendSheetTags,
473 | getRecommendSheetsByTag,
474 | getTopLists,
475 | getTopListDetail,
476 | };
477 |
--------------------------------------------------------------------------------
/dist/bilibili/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const dayjs = require("dayjs");
5 | const he = require("he");
6 | const CryptoJs = require("crypto-js");
7 | const { load } = require('cheerio');
8 | const headers = {
9 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
10 | accept: "*/*",
11 | "accept-encoding": "gzip, deflate, br",
12 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
13 | };
14 | let cookie;
15 | async function getCid(bvid, aid) {
16 | const params = bvid
17 | ? {
18 | bvid: bvid,
19 | }
20 | : {
21 | aid: aid,
22 | };
23 | const cidRes = (await axios_1.default.get("https://api.bilibili.com/x/web-interface/view?%s", {
24 | headers: headers,
25 | params: params,
26 | })).data;
27 | return cidRes;
28 | }
29 | function durationToSec(duration) {
30 | if (typeof duration === "number") {
31 | return duration;
32 | }
33 | if (typeof duration === "string") {
34 | var dur = duration.split(":");
35 | return dur.reduce(function (prev, curr) {
36 | return 60 * prev + +curr;
37 | }, 0);
38 | }
39 | return 0;
40 | }
41 | const searchHeaders = {
42 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
43 | accept: "application/json, text/plain, */*",
44 | "accept-encoding": "gzip, deflate, br",
45 | origin: "https://search.bilibili.com",
46 | "sec-fetch-site": "same-site",
47 | "sec-fetch-mode": "cors",
48 | "sec-fetch-dest": "empty",
49 | referer: "https://search.bilibili.com/",
50 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
51 | };
52 | async function getCookie() {
53 | if (!cookie) {
54 | cookie = (await axios_1.default.get("https://api.bilibili.com/x/frontend/finger/spi", {
55 | headers: {
56 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/114.0.0.0",
57 | },
58 | })).data.data;
59 | }
60 | }
61 | const pageSize = 20;
62 | async function searchBase(keyword, page, searchType) {
63 | await getCookie();
64 | const params = {
65 | context: "",
66 | page: page,
67 | order: "",
68 | page_size: pageSize,
69 | keyword: keyword,
70 | duration: "",
71 | tids_1: "",
72 | tids_2: "",
73 | __refresh__: true,
74 | _extra: "",
75 | highlight: 1,
76 | single_column: 0,
77 | platform: "pc",
78 | from_source: "",
79 | search_type: searchType,
80 | dynamic_offset: 0,
81 | };
82 | const res = (await axios_1.default.get("https://api.bilibili.com/x/web-interface/search/type", {
83 | headers: Object.assign(Object.assign({}, searchHeaders), { cookie: `buvid3=${cookie.b_3};buvid4=${cookie.b_4}` }),
84 | params: params,
85 | })).data;
86 | return res.data;
87 | }
88 | async function getFavoriteList(id) {
89 | const result = [];
90 | const pageSize = 20;
91 | let page = 1;
92 | while (true) {
93 | try {
94 | const { data: { data: { medias, has_more }, }, } = await axios_1.default.get("https://api.bilibili.com/x/v3/fav/resource/list", {
95 | params: {
96 | media_id: id,
97 | platform: "web",
98 | ps: pageSize,
99 | pn: page,
100 | },
101 | });
102 | result.push(...medias);
103 | if (!has_more) {
104 | break;
105 | }
106 | page += 1;
107 | }
108 | catch (error) {
109 | console.warn(error);
110 | break;
111 | }
112 | }
113 | return result;
114 | }
115 | function formatMedia(result) {
116 | var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
117 | const title = he.decode((_b = (_a = result.title) === null || _a === void 0 ? void 0 : _a.replace(/(\)|(\<\/em\>)/g, "")) !== null && _b !== void 0 ? _b : "");
118 | return {
119 | id: (_d = (_c = result.cid) !== null && _c !== void 0 ? _c : result.bvid) !== null && _d !== void 0 ? _d : result.aid,
120 | aid: result.aid,
121 | bvid: result.bvid,
122 | artist: (_e = result.author) !== null && _e !== void 0 ? _e : (_f = result.owner) === null || _f === void 0 ? void 0 : _f.name,
123 | title,
124 | alias: (_g = title.match(/《(.+?)》/)) === null || _g === void 0 ? void 0 : _g[1],
125 | album: (_h = result.bvid) !== null && _h !== void 0 ? _h : result.aid,
126 | artwork: ((_j = result.pic) === null || _j === void 0 ? void 0 : _j.startsWith("//"))
127 | ? "http:".concat(result.pic)
128 | : result.pic,
129 | duration: durationToSec(result.duration),
130 | tags: (_k = result.tag) === null || _k === void 0 ? void 0 : _k.split(","),
131 | date: dayjs.unix(result.pubdate || result.created).format("YYYY-MM-DD"),
132 | };
133 | }
134 | async function searchAlbum(keyword, page) {
135 | const resultData = await searchBase(keyword, page, "video");
136 | const albums = resultData.result.map(formatMedia);
137 | return {
138 | isEnd: resultData.numResults <= page * pageSize,
139 | data: albums,
140 | };
141 | }
142 | async function searchArtist(keyword, page) {
143 | const resultData = await searchBase(keyword, page, "bili_user");
144 | const artists = resultData.result.map((result) => {
145 | var _a;
146 | return ({
147 | name: result.uname,
148 | id: result.mid,
149 | fans: result.fans,
150 | description: result.usign,
151 | avatar: ((_a = result.upic) === null || _a === void 0 ? void 0 : _a.startsWith("//"))
152 | ? `https://${result.upic}`
153 | : result.upic,
154 | worksNum: result.videos,
155 | });
156 | });
157 | return {
158 | isEnd: resultData.numResults <= page * pageSize,
159 | data: artists,
160 | };
161 | }
162 | function getMixinKey(e) {
163 | var t = [];
164 | return ([
165 | 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5,
166 | 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55,
167 | 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57,
168 | 62, 11, 36, 20, 34, 44, 52,
169 | ].forEach(function (r) {
170 | e.charAt(r) && t.push(e.charAt(r));
171 | }),
172 | t.join("").slice(0, 32));
173 | }
174 | function hmacSha256(key, message) {
175 | const hmac = CryptoJs.HmacSHA256(message, key);
176 | return hmac.toString(CryptoJs.enc.Hex);
177 | }
178 | async function getBiliTicket(csrf) {
179 | const ts = Math.floor(Date.now() / 1000);
180 | const hexSign = hmacSha256('XgwSnGZ1p', `ts${ts}`);
181 | const url = 'https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket';
182 | try {
183 | const response = await axios_1.default.post(url, null, {
184 | params: {
185 | key_id: 'ec02',
186 | hexsign: hexSign,
187 | 'context[ts]': ts,
188 | csrf: csrf || ''
189 | },
190 | headers: {
191 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
192 | }
193 | });
194 | const data = await response.data;
195 | return data.data;
196 | }
197 | catch (e) {
198 | throw e;
199 | }
200 | }
201 | let img, sub, syncedTime;
202 | async function getWBIKeys() {
203 | if (img && sub && syncedTime && syncedTime.getDate() === (new Date()).getDate()) {
204 | return {
205 | img,
206 | sub
207 | };
208 | }
209 | else {
210 | const data = await getBiliTicket('');
211 | img = data.nav.img;
212 | img = img.slice(img.lastIndexOf('/') + 1, img.lastIndexOf('.'));
213 | sub = data.nav.sub;
214 | sub = sub.slice(sub.lastIndexOf('/') + 1, sub.lastIndexOf('.'));
215 | syncedTime = new Date();
216 | return {
217 | img,
218 | sub
219 | };
220 | }
221 | }
222 | async function getRid(params) {
223 | const wbiKeys = await getWBIKeys();
224 | const npi = wbiKeys.img + wbiKeys.sub;
225 | const o = getMixinKey(npi);
226 | const l = Object.keys(params).sort();
227 | let c = [];
228 | for (let d = 0, u = /[!'\(\)*]/g; d < l.length; ++d) {
229 | let [h, p] = [l[d], params[l[d]]];
230 | p && "string" == typeof p && (p = p.replace(u, "")),
231 | null != p &&
232 | c.push("".concat(encodeURIComponent(h), "=").concat(encodeURIComponent(p)));
233 | }
234 | const f = c.join("&");
235 | const w_rid = CryptoJs.MD5(f + o).toString();
236 | return w_rid;
237 | }
238 | let w_webid;
239 | let w_webid_date;
240 | async function getWWebId(id) {
241 | if (w_webid && w_webid_date && (Date.now() - w_webid_date.getTime() < 1000 * 60 * 60)) {
242 | return w_webid;
243 | }
244 | const html = (await axios_1.default.get("https://space.bilibili.com/" + id, {
245 | headers: {
246 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
247 | }
248 | })).data;
249 | const $ = load(html);
250 | const content = $("#__RENDER_DATA__").text();
251 | const jsonContent = JSON.parse(decodeURIComponent(content));
252 | w_webid = jsonContent.access_id;
253 | w_webid_date = new Date();
254 | return w_webid;
255 | }
256 | async function getArtistWorks(artistItem, page, type) {
257 | const queryHeaders = {
258 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
259 | accept: "*/*",
260 | "accept-encoding": "gzip, deflate, br, zstd",
261 | origin: "https://space.bilibili.com",
262 | "sec-fetch-site": "same-site",
263 | "sec-fetch-mode": "cors",
264 | "sec-fetch-dest": "empty",
265 | referer: `https://space.bilibili.com/${artistItem.id}/video`,
266 | };
267 | await getCookie();
268 | const now = Math.round(Date.now() / 1e3);
269 | const params = {
270 | mid: artistItem.id,
271 | ps: 30,
272 | tid: 0,
273 | pn: page,
274 | web_location: 1550101,
275 | order_avoided: true,
276 | order: "pubdate",
277 | keyword: "",
278 | platform: "web",
279 | dm_img_list: "[]",
280 | dm_img_str: "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
281 | dm_cover_img_str: "QU5HTEUgKE5WSURJQSwgTlZJRElBIEdlRm9yY2UgR1RYIDE2NTAgKDB4MDAwMDFGOTEpIERpcmVjdDNEMTEgdnNfNV8wIHBzXzVfMCwgRDNEMTEpR29vZ2xlIEluYy4gKE5WSURJQS",
282 | dm_img_inter: '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
283 | wts: now.toString(),
284 | };
285 | const w_rid = await getRid(params);
286 | const res = (await axios_1.default.get("https://api.bilibili.com/x/space/wbi/arc/search", {
287 | headers: Object.assign(Object.assign({}, queryHeaders), { cookie: `buvid3=${cookie.b_3};buvid4=${cookie.b_4}` }),
288 | params: Object.assign(Object.assign({}, params), { w_rid }),
289 | })).data;
290 | console.log(res);
291 | const resultData = res.data;
292 | const albums = resultData.list.vlist.map(formatMedia);
293 | return {
294 | isEnd: resultData.page.pn * resultData.page.ps >= resultData.page.count,
295 | data: albums,
296 | };
297 | }
298 | async function getMediaSource(musicItem, quality) {
299 | var _a;
300 | let cid = musicItem.cid;
301 | if (!cid) {
302 | cid = (await getCid(musicItem.bvid, musicItem.aid)).data.cid;
303 | }
304 | const _params = musicItem.bvid
305 | ? {
306 | bvid: musicItem.bvid,
307 | }
308 | : {
309 | aid: musicItem.aid,
310 | };
311 | const res = (await axios_1.default.get("https://api.bilibili.com/x/player/playurl", {
312 | headers: headers,
313 | params: Object.assign(Object.assign({}, _params), { cid: cid, fnval: 16 }),
314 | })).data;
315 | let url;
316 | if (res.data.dash) {
317 | const audios = res.data.dash.audio;
318 | audios.sort((a, b) => a.bandwidth - b.bandwidth);
319 | switch (quality) {
320 | case "low":
321 | url = audios[0].baseUrl;
322 | break;
323 | case "standard":
324 | url = audios[1].baseUrl;
325 | break;
326 | case "high":
327 | url = audios[2].baseUrl;
328 | break;
329 | case "super":
330 | url = audios[3].baseUrl;
331 | break;
332 | }
333 | }
334 | else {
335 | url = res.data.durl[0].url;
336 | }
337 | const hostUrl = url.substring(url.indexOf("/") + 2);
338 | const _headers = {
339 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
340 | accept: "*/*",
341 | host: hostUrl.substring(0, hostUrl.indexOf("/")),
342 | "accept-encoding": "gzip, deflate, br",
343 | connection: "keep-alive",
344 | referer: "https://www.bilibili.com/video/".concat((_a = (musicItem.bvid !== null && musicItem.bvid !== undefined
345 | ? musicItem.bvid
346 | : musicItem.aid)) !== null && _a !== void 0 ? _a : ""),
347 | };
348 | return {
349 | url: url,
350 | headers: _headers,
351 | };
352 | }
353 | async function getTopLists() {
354 | const precious = {
355 | title: "入站必刷",
356 | data: [
357 | {
358 | id: "popular/precious?page_size=100&page=1",
359 | title: "入站必刷",
360 | coverImg: "https://s1.hdslb.com/bfs/static/jinkela/popular/assets/icon_history.png",
361 | },
362 | ],
363 | };
364 | const weekly = {
365 | title: "每周必看",
366 | data: [],
367 | };
368 | const weeklyRes = await axios_1.default.get("https://api.bilibili.com/x/web-interface/popular/series/list", {
369 | headers: {
370 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
371 | },
372 | });
373 | weekly.data = weeklyRes.data.data.list.slice(0, 8).map((e) => ({
374 | id: `popular/series/one?number=${e.number}`,
375 | title: e.subject,
376 | description: e.name,
377 | coverImg: "https://s1.hdslb.com/bfs/static/jinkela/popular/assets/icon_weekly.png",
378 | }));
379 | const boardKeys = [
380 | {
381 | id: "ranking/v2?rid=0&type=all",
382 | title: "全站",
383 | },
384 | {
385 | id: "ranking/v2?rid=3&type=all",
386 | title: "音乐",
387 | },
388 | {
389 | id: "ranking/v2?rid=1&type=all",
390 | title: "动画",
391 | },
392 | {
393 | id: "ranking/v2?rid=119&type=all",
394 | title: "鬼畜",
395 | },
396 | {
397 | id: "ranking/v2?rid=168&type=all",
398 | title: "国创相关",
399 | },
400 | {
401 | id: "ranking/v2?rid=129&type=all",
402 | title: "舞蹈",
403 | },
404 | {
405 | id: "ranking/v2?rid=4&type=all",
406 | title: "游戏",
407 | },
408 | {
409 | id: "ranking/v2?rid=36&type=all",
410 | title: "知识",
411 | },
412 | {
413 | id: "ranking/v2?rid=188&type=all",
414 | title: "科技",
415 | },
416 | {
417 | id: "ranking/v2?rid=234&type=all",
418 | title: "运动",
419 | },
420 | {
421 | id: "ranking/v2?rid=223&type=all",
422 | title: "汽车",
423 | },
424 | {
425 | id: "ranking/v2?rid=160&type=all",
426 | title: "生活",
427 | },
428 | {
429 | id: "ranking/v2?rid=211&type=all",
430 | title: "美食",
431 | },
432 | {
433 | id: "ranking/v2?rid=217&type=all",
434 | title: "动物圈",
435 | },
436 | {
437 | id: "ranking/v2?rid=155&type=all",
438 | title: "时尚",
439 | },
440 | {
441 | id: "ranking/v2?rid=5&type=all",
442 | title: "娱乐",
443 | },
444 | {
445 | id: "ranking/v2?rid=181&type=all",
446 | title: "影视",
447 | },
448 | {
449 | id: "ranking/v2?rid=0&type=origin",
450 | title: "原创",
451 | },
452 | {
453 | id: "ranking/v2?rid=0&type=rookie",
454 | title: "新人",
455 | },
456 | ];
457 | const board = {
458 | title: "排行榜",
459 | data: boardKeys.map((_) => (Object.assign(Object.assign({}, _), { coverImg: "https://s1.hdslb.com/bfs/static/jinkela/popular/assets/icon_rank.png" }))),
460 | };
461 | return [weekly, precious, board];
462 | }
463 | async function getTopListDetail(topListItem) {
464 | const res = await axios_1.default.get(`https://api.bilibili.com/x/web-interface/${topListItem.id}`, {
465 | headers: Object.assign(Object.assign({}, headers), { referer: "https://www.bilibili.com/" }),
466 | });
467 | return Object.assign(Object.assign({}, topListItem), { musicList: res.data.data.list.map(formatMedia) });
468 | }
469 | async function importMusicSheet(urlLike) {
470 | var _a, _b, _c, _d;
471 | let id;
472 | if (!id) {
473 | id = (_a = urlLike.match(/^\s*(\d+)\s*$/)) === null || _a === void 0 ? void 0 : _a[1];
474 | }
475 | if (!id) {
476 | id = (_b = urlLike.match(/^(?:.*)fid=(\d+).*$/)) === null || _b === void 0 ? void 0 : _b[1];
477 | }
478 | if (!id) {
479 | id = (_c = urlLike.match(/\/playlist\/pl(\d+)/i)) === null || _c === void 0 ? void 0 : _c[1];
480 | }
481 | if (!id) {
482 | id = (_d = urlLike.match(/\/list\/ml(\d+)/i)) === null || _d === void 0 ? void 0 : _d[1];
483 | }
484 | if (!id) {
485 | return;
486 | }
487 | const musicSheet = await getFavoriteList(id);
488 | return musicSheet.map((_) => {
489 | var _a, _b;
490 | return ({
491 | id: _.id,
492 | aid: _.aid,
493 | bvid: _.bvid,
494 | artwork: _.cover,
495 | title: _.title,
496 | artist: (_a = _.upper) === null || _a === void 0 ? void 0 : _a.name,
497 | album: (_b = _.bvid) !== null && _b !== void 0 ? _b : _.aid,
498 | duration: durationToSec(_.duration),
499 | });
500 | });
501 | }
502 | function formatComment(item) {
503 | var _a, _b, _c, _d, _e;
504 | return {
505 | id: item.rpid,
506 | nickName: (_a = item.member) === null || _a === void 0 ? void 0 : _a.uname,
507 | avatar: (_b = item.member) === null || _b === void 0 ? void 0 : _b.avatar,
508 | comment: (_c = item.content) === null || _c === void 0 ? void 0 : _c.message,
509 | like: item.like,
510 | createAt: item.ctime * 1000,
511 | location: ((_e = (_d = item.reply_control) === null || _d === void 0 ? void 0 : _d.location) === null || _e === void 0 ? void 0 : _e.startsWith("IP属地:")) ? item.reply_control.location.slice(5) : undefined
512 | };
513 | }
514 | async function getMusicComments(musicItem) {
515 | var _a, _b;
516 | const params = {
517 | type: 1,
518 | mode: 3,
519 | oid: musicItem.aid,
520 | plat: 1,
521 | web_location: 1315875,
522 | wts: Math.floor(Date.now() / 1000)
523 | };
524 | const w_rid = await getRid(params);
525 | const res = (await (axios_1.default.get("https://api.bilibili.com/x/v2/reply/wbi/main", {
526 | params: Object.assign(Object.assign({}, params), { w_rid })
527 | }))).data;
528 | const data = res.data.replies;
529 | const comments = [];
530 | for (let i = 0; i < data.length; ++i) {
531 | comments[i] = formatComment(data[i]);
532 | if ((_a = data[i].replies) === null || _a === void 0 ? void 0 : _a.length) {
533 | comments[i].replies = (_b = data[i]) === null || _b === void 0 ? void 0 : _b.replies.map(formatComment);
534 | }
535 | }
536 | return {
537 | isEnd: true,
538 | data: comments
539 | };
540 | }
541 | module.exports = {
542 | platform: "bilibili",
543 | appVersion: ">=0.0",
544 | version: "0.2.3",
545 | author: "猫头猫",
546 | cacheControl: "no-cache",
547 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/bilibili/index.js",
548 | primaryKey: ["id", "aid", "bvid", "cid"],
549 | hints: {
550 | importMusicSheet: [
551 | "bilibili 移动端:APP点击我的,空间,右上角分享,复制链接,浏览器打开切换桌面版网站,点击播放全部视频,复制链接",
552 | "bilibili H5/PC端:复制收藏夹URL,或者直接输入ID即可",
553 | "非公开收藏夹无法导入,编辑收藏夹改为公开即可",
554 | "导入时间和歌单大小有关,请耐心等待",
555 | ],
556 | },
557 | supportedSearchType: ["music", "album", "artist"],
558 | async search(keyword, page, type) {
559 | if (type === "album" || type === "music") {
560 | return await searchAlbum(keyword, page);
561 | }
562 | if (type === "artist") {
563 | return await searchArtist(keyword, page);
564 | }
565 | },
566 | getMediaSource,
567 | async getAlbumInfo(albumItem) {
568 | var _a;
569 | const cidRes = await getCid(albumItem.bvid, albumItem.aid);
570 | const _ref2 = (_a = cidRes === null || cidRes === void 0 ? void 0 : cidRes.data) !== null && _a !== void 0 ? _a : {};
571 | const cid = _ref2.cid;
572 | const pages = _ref2.pages;
573 | let musicList;
574 | if (pages.length === 1) {
575 | musicList = [Object.assign(Object.assign({}, albumItem), { cid: cid })];
576 | }
577 | else {
578 | musicList = pages.map(function (_) {
579 | return Object.assign(Object.assign({}, albumItem), { cid: _.cid, title: _.part, duration: durationToSec(_.duration), id: _.cid });
580 | });
581 | }
582 | return {
583 | musicList,
584 | };
585 | },
586 | getArtistWorks,
587 | getTopLists,
588 | getTopListDetail,
589 | importMusicSheet,
590 | getMusicComments
591 | };
592 |
--------------------------------------------------------------------------------
/dist/geciqianxun/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const cheerio_1 = require("cheerio");
5 | async function search(query, page, type) {
6 | if (type !== 'lyric') {
7 | return;
8 | }
9 | const result = (await axios_1.default.get('https://so.lrcgc.com/', {
10 | params: {
11 | q: query,
12 | }
13 | })).data;
14 | const $ = (0, cheerio_1.load)(result);
15 | const results = $('.resultWrap').children();
16 | const data = [];
17 | if (results.first().prop('tagName') === 'DL') {
18 | const title = results.first().find('dt > a');
19 | const desc = results.first().find('dd > small');
20 | const descText = desc.text().replace(/[\s|\n]/g, '').split(/[歌手:|专辑:]/).filter(it => it.trim() !== '');
21 | data.push({
22 | title: title.text(),
23 | id: title.attr('href'),
24 | artist: descText === null || descText === void 0 ? void 0 : descText[0],
25 | album: descText === null || descText === void 0 ? void 0 : descText[1]
26 | });
27 | }
28 | return {
29 | isEnd: true,
30 | data
31 | };
32 | }
33 | async function getLyric(musicItem) {
34 | const res = (await axios_1.default.get(musicItem.id)).data;
35 | const $ = (0, cheerio_1.load)(res);
36 | const rawLrc = $('p#J_lyric').text().replace(/\n/g, '');
37 | return {
38 | rawLrc: rawLrc,
39 | };
40 | }
41 | module.exports = {
42 | platform: "歌词千寻",
43 | version: "0.0.0",
44 | srcUrl: 'https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciqianxun/index.js',
45 | cacheControl: "no-store",
46 | supportedSearchType: ['lyric'],
47 | search,
48 | getLyric
49 | };
50 |
--------------------------------------------------------------------------------
/dist/geciwang/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const cheerio_1 = require("cheerio");
5 | async function search(query, page, type) {
6 | if (type !== 'lyric') {
7 | return;
8 | }
9 | const result = (await axios_1.default.get('https://zh.followlyrics.com/search', {
10 | params: {
11 | name: query,
12 | type: 'song'
13 | }
14 | })).data;
15 | const $ = (0, cheerio_1.load)(result);
16 | const results = $('.table.table-striped > tbody');
17 | const items = results.children('tr');
18 | const data = items.map((index, el) => {
19 | const tds = $(el).children();
20 | const title = $(tds.get(0)).text().trim();
21 | const artist = $(tds.get(1)).text().trim();
22 | const album = $(tds.get(2)).text().trim();
23 | const id = $(tds.get(3)).children('a').attr('href');
24 | return {
25 | title,
26 | artist,
27 | album,
28 | id
29 | };
30 | }).toArray();
31 | return {
32 | isEnd: true,
33 | data
34 | };
35 | }
36 | async function getLyric(musicItem) {
37 | const res = (await axios_1.default.get(musicItem.id)).data;
38 | const $ = (0, cheerio_1.load)(res);
39 | const rawLrc = $('div#lyrics').text().replace(/\n/g, '');
40 | return {
41 | rawLrc: rawLrc,
42 | };
43 | }
44 | module.exports = {
45 | platform: "歌词网",
46 | version: "0.0.0",
47 | srcUrl: 'https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciwang/index.js',
48 | cacheControl: "no-store",
49 | supportedSearchType: ['lyric'],
50 | search,
51 | getLyric
52 | };
53 |
--------------------------------------------------------------------------------
/dist/kuaishou/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const pageSize = 20;
5 | function formatMusicItem(it) {
6 | return {
7 | id: it.photo.id,
8 | title: it.photo.caption,
9 | artist: it.author.name,
10 | artwork: it.photo.coverUrl || it.photo.photoUrl,
11 | manifest: it.photo.manifest
12 | };
13 | }
14 | async function searchMusic(query, page) {
15 | var _a, _b;
16 | const body = {
17 | query: `fragment photoContent on PhotoEntity {
18 | __typename
19 | id
20 | duration
21 | caption
22 | originCaption
23 | likeCount
24 | viewCount
25 | commentCount
26 | realLikeCount
27 | coverUrl
28 | photoUrl
29 | photoH265Url
30 | manifest
31 | manifestH265
32 | videoResource
33 | coverUrls {
34 | url
35 | __typename
36 | }
37 | timestamp
38 | expTag
39 | animatedCoverUrl
40 | distance
41 | videoRatio
42 | liked
43 | stereoType
44 | profileUserTopPhoto
45 | musicBlocked
46 | riskTagContent
47 | riskTagUrl
48 | }
49 |
50 | fragment recoPhotoFragment on recoPhotoEntity {
51 | __typename
52 | id
53 | duration
54 | caption
55 | originCaption
56 | likeCount
57 | viewCount
58 | commentCount
59 | realLikeCount
60 | coverUrl
61 | photoUrl
62 | photoH265Url
63 | manifest
64 | manifestH265
65 | videoResource
66 | coverUrls {
67 | url
68 | __typename
69 | }
70 | timestamp
71 | expTag
72 | animatedCoverUrl
73 | distance
74 | videoRatio
75 | liked
76 | stereoType
77 | profileUserTopPhoto
78 | musicBlocked
79 | riskTagContent
80 | riskTagUrl
81 | }
82 |
83 | fragment feedContent on Feed {
84 | type
85 | author {
86 | id
87 | name
88 | headerUrl
89 | following
90 | headerUrls {
91 | url
92 | __typename
93 | }
94 | __typename
95 | }
96 | photo {
97 | ...photoContent
98 | ...recoPhotoFragment
99 | __typename
100 | }
101 | canAddComment
102 | llsid
103 | status
104 | currentPcursor
105 | tags {
106 | type
107 | name
108 | __typename
109 | }
110 | __typename
111 | }
112 |
113 | query visionSearchPhoto($keyword: String, $pcursor: String, $searchSessionId: String, $page: String, $webPageArea: String) {
114 | visionSearchPhoto(keyword: $keyword, pcursor: $pcursor, searchSessionId: $searchSessionId, page: $page, webPageArea: $webPageArea) {
115 | result
116 | llsid
117 | webPageArea
118 | feeds {
119 | ...feedContent
120 | __typename
121 | }
122 | searchSessionId
123 | pcursor
124 | aladdinBanner {
125 | imgUrl
126 | link
127 | __typename
128 | }
129 | __typename
130 | }
131 | }`,
132 | variables: {
133 | keyword: query,
134 | page: "search",
135 | pcursor: `${page - 1}`,
136 | },
137 | };
138 | const result = (await axios_1.default.post("https://www.kuaishou.com/graphql", body, {
139 | headers: {
140 | 'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
141 | host: 'www.kuaishou.com',
142 | origin: 'https://www.kuaishou.com',
143 | referer: `https://www.kuaishou.com/search/video?searchKey=${encodeURIComponent((query))}`,
144 | }
145 | })).data.data.visionSearchPhoto;
146 | return {
147 | isEnd: !(result === null || result === void 0 ? void 0 : result.pcursor) || (result === null || result === void 0 ? void 0 : result.pcursor) === 'no_more',
148 | data: (_b = (_a = result === null || result === void 0 ? void 0 : result.feeds) === null || _a === void 0 ? void 0 : _a.map) === null || _b === void 0 ? void 0 : _b.call(_a, formatMusicItem)
149 | };
150 | }
151 | module.exports = {
152 | platform: "快手",
153 | version: "0.0.2",
154 | author: '猫头猫',
155 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/kuaishou/index.js",
156 | cacheControl: "no-cache",
157 | supportedSearchType: ["music"],
158 | async search(query, page, type) {
159 | if (type === "music") {
160 | return await searchMusic(query, page);
161 | }
162 | },
163 | async getMediaSource(musicItem, quality) {
164 | if (!musicItem.manifest) {
165 | return;
166 | }
167 | const adaptationSet = musicItem.manifest.adaptationSet;
168 | const representation = adaptationSet[0].representation;
169 | return {
170 | url: representation[0].url
171 | };
172 | },
173 | };
174 |
--------------------------------------------------------------------------------
/dist/maoerfm/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const pageSize = 30;
5 | function validMusicFilter(_) {
6 | return `${_.pay_type}` === "0";
7 | }
8 | function formatMusicItem(_) {
9 | var _a;
10 | return {
11 | id: _.id,
12 | artwork: _.front_cover,
13 | title: _.soundstr,
14 | artist: _.username,
15 | user_id: _.user_id,
16 | duration: +((_a = _.duration) !== null && _a !== void 0 ? _a : 0),
17 | };
18 | }
19 | function formatAlbumItem(_) {
20 | return {
21 | id: _.id,
22 | artist: _.author,
23 | title: _.name,
24 | artwork: _.cover,
25 | description: _.abstract,
26 | };
27 | }
28 | const searchHeaders = {
29 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
30 | accept: "application/json",
31 | "accept-encoding": "gzip, deflate, br",
32 | referer: "https://www.missevan.com/sound/search",
33 | };
34 | async function searchMusic(query, page) {
35 | const res = (await axios_1.default.get("https://www.missevan.com/sound/getsearch", {
36 | params: {
37 | s: query,
38 | p: page,
39 | type: 3,
40 | page_size: pageSize,
41 | },
42 | headers: searchHeaders,
43 | })).data.info;
44 | return {
45 | isEnd: res.pagination.p >= res.pagination.maxpage,
46 | data: res.Datas.filter(validMusicFilter).map(formatMusicItem),
47 | };
48 | }
49 | async function searchAlbum(query, page) {
50 | const res = (await axios_1.default.get("https://www.missevan.com/dramaapi/search", {
51 | headers: searchHeaders,
52 | params: {
53 | s: query,
54 | page,
55 | },
56 | })).data.info;
57 | return {
58 | isEnd: res.pagination.p >= res.pagination.maxpage,
59 | data: res.Datas.filter(validMusicFilter).map(formatAlbumItem),
60 | };
61 | }
62 | async function getAlbumInfo(albumItem) {
63 | const res = (await axios_1.default.get("https://www.missevan.com/dramaapi/getdrama", {
64 | headers: {
65 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
66 | accept: "application/json",
67 | "accept-encoding": "gzip, deflate, br",
68 | referer: `https://www.missevan.com/mdrama/${albumItem.id}`,
69 | },
70 | params: {
71 | drama_id: albumItem.id,
72 | },
73 | })).data;
74 | return {
75 | musicList: res.info.episodes.episode
76 | .filter(validMusicFilter)
77 | .map(_ => {
78 | const r = formatMusicItem(_);
79 | r.artwork = albumItem.artwork;
80 | return r;
81 | }),
82 | };
83 | }
84 | async function getMediaSource(musicItem, quality) {
85 | if (quality === "high" || quality === "super") {
86 | return;
87 | }
88 | const res = (await axios_1.default.get("https://www.missevan.com/sound/getsound", {
89 | headers: {
90 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
91 | accept: "application/json",
92 | "accept-encoding": "gzip, deflate, br",
93 | referer: `https://www.missevan.com/sound/player?id=${musicItem.id}`,
94 | },
95 | params: {
96 | soundid: musicItem.id,
97 | },
98 | })).data.info;
99 | if (quality === "low") {
100 | return {
101 | url: res.sound.soundurl_128,
102 | };
103 | }
104 | else {
105 | return {
106 | url: res.sound.soundurl,
107 | };
108 | }
109 | }
110 | async function getRecommendSheetTags() {
111 | const res = (await axios_1.default.get(`https://www.missevan.com/malbum/recommand`, {
112 | headers: {
113 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
114 | accept: "application/json",
115 | "accept-encoding": "gzip, deflate, br",
116 | referer: `https://www.missevan.com`,
117 | }
118 | })).data.info;
119 | const data = Object.entries(res !== null && res !== void 0 ? res : {}).map(group => ({
120 | title: group[0],
121 | data: group[1].map(_ => ({
122 | id: _[0],
123 | title: _[1]
124 | }))
125 | }));
126 | return {
127 | data,
128 | };
129 | }
130 | async function getRecommendSheetsByTag(tag, page) {
131 | const res = (await axios_1.default.get(`https://www.missevan.com/explore/tagalbum`, {
132 | headers: {
133 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
134 | accept: "application/json",
135 | "accept-encoding": "gzip, deflate, br",
136 | referer: `https://m.missevan.com`,
137 | },
138 | params: {
139 | order: 0,
140 | tid: (tag === null || tag === void 0 ? void 0 : tag.id) || 0,
141 | p: page
142 | }
143 | })).data;
144 | return {
145 | isEnd: res.page >= res.maxpage,
146 | data: res.albums.map(sheet => ({
147 | id: sheet.id,
148 | title: sheet.title,
149 | artwork: sheet.front_cover,
150 | artist: sheet.username,
151 | createUserId: sheet.user_id
152 | }))
153 | };
154 | }
155 | async function getMusicSheetInfo(sheet, page) {
156 | const res = (await axios_1.default.get(`https://www.missevan.com/sound/soundalllist`, {
157 | headers: {
158 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
159 | accept: "application/json",
160 | "accept-encoding": "gzip, deflate, br",
161 | referer: `https://m.missevan.com`,
162 | },
163 | params: {
164 | albumid: sheet.id
165 | }
166 | })).data.info;
167 | return {
168 | isEnd: true,
169 | musicList: res.sounds.filter(validMusicFilter).map(item => ({
170 | id: item.id,
171 | title: item.soundstr,
172 | artwork: item.front_cover,
173 | url: item.soundurl,
174 | artist: item.username,
175 | }))
176 | };
177 | }
178 | module.exports = {
179 | platform: "猫耳FM",
180 | author: '猫头猫',
181 | version: "0.1.4",
182 | appVersion: ">0.1.0-alpha.0",
183 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/maoerfm/index.js",
184 | cacheControl: "no-cache",
185 | supportedSearchType: ["music", "album",],
186 | async search(query, page, type) {
187 | if (type === "music") {
188 | return await searchMusic(query, page);
189 | }
190 | if (type === "album") {
191 | return await searchAlbum(query, page);
192 | }
193 | },
194 | getMediaSource,
195 | getAlbumInfo,
196 | getRecommendSheetTags,
197 | getRecommendSheetsByTag,
198 | getMusicSheetInfo
199 | };
200 |
--------------------------------------------------------------------------------
/dist/navidrome/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const CryptoJs = require("crypto-js");
5 | const pageSize = 25;
6 | async function httpGet(urlPath, params) {
7 | var _a;
8 | const userVariables = (_a = env === null || env === void 0 ? void 0 : env.getUserVariables()) !== null && _a !== void 0 ? _a : {};
9 | let { url, username, password } = userVariables;
10 | console.log(userVariables);
11 | if (!(url && username && password)) {
12 | return null;
13 | }
14 | if (!url.startsWith("http://") && !url.startsWith("https://")) {
15 | url = `http://${url}`;
16 | }
17 | const salt = Math.random().toString(16).slice(2);
18 | const preParams = {
19 | u: username,
20 | s: salt,
21 | t: CryptoJs.MD5(`${password}${salt}`).toString(CryptoJs.enc.Hex),
22 | c: "MusicFree",
23 | v: "1.14.1",
24 | f: "json",
25 | };
26 | return (await axios_1.default.get(`${url}/rest/${urlPath}`, {
27 | params: Object.assign(Object.assign({}, preParams), params),
28 | })).data;
29 | }
30 | function formatMusicItem(it) {
31 | return Object.assign(Object.assign({}, it), { artwork: it.coverArt });
32 | }
33 | function formatAlbumItem(it) {
34 | return Object.assign(Object.assign({}, it), { artwork: it.coverArt });
35 | }
36 | function formatArtistItem(it) {
37 | return Object.assign(Object.assign({}, it), { avatar: it.artistImageUrl });
38 | }
39 | async function searchMusic(query, page) {
40 | const data = await httpGet('search2', {
41 | query,
42 | songCount: pageSize,
43 | songOffset: (page - 1) * pageSize
44 | });
45 | const songs = data['subsonic-response'].searchResult2.song;
46 | return {
47 | isEnd: songs.length < pageSize,
48 | data: songs.map(formatMusicItem)
49 | };
50 | }
51 | async function searchAlbum(query, page) {
52 | const data = await httpGet('search2', {
53 | query,
54 | albumCount: pageSize,
55 | albumOffset: (page - 1) * pageSize
56 | });
57 | const songs = data['subsonic-response'].searchResult2.album;
58 | return {
59 | isEnd: songs.length < pageSize,
60 | data: songs.map(formatAlbumItem)
61 | };
62 | }
63 | async function searchArtist(query, page) {
64 | const data = await httpGet('search2', {
65 | query,
66 | songCount: pageSize,
67 | songOffset: (page - 1) * pageSize
68 | });
69 | const songs = data['subsonic-response'].searchResult2.song;
70 | return {
71 | isEnd: songs.length < pageSize,
72 | data: songs.map(formatMusicItem)
73 | };
74 | }
75 | async function getAlbumInfo(albumItem) {
76 | const data = await httpGet('getAlbum', {
77 | id: albumItem.id
78 | });
79 | return {
80 | isEnd: true,
81 | data: data['subsonic-response'].album.song.map(formatMusicItem)
82 | };
83 | }
84 | module.exports = {
85 | platform: "Navidrome",
86 | version: "0.0.0",
87 | author: '猫头猫',
88 | appVersion: ">0.1.0-alpha.0",
89 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/navidrome/index.js",
90 | cacheControl: "no-cache",
91 | userVariables: [
92 | {
93 | key: "url",
94 | name: "服务器地址",
95 | },
96 | {
97 | key: "username",
98 | name: "用户名",
99 | },
100 | {
101 | key: "password",
102 | name: "密码",
103 | },
104 | ],
105 | supportedSearchType: ["music", "album"],
106 | async search(query, page, type) {
107 | if (type === "music") {
108 | return await searchMusic(query, page);
109 | }
110 | if (type === "album") {
111 | return await searchAlbum(query, page);
112 | }
113 | },
114 | async getMediaSource(musicItem) {
115 | var _a;
116 | const userVariables = (_a = env === null || env === void 0 ? void 0 : env.getUserVariables()) !== null && _a !== void 0 ? _a : {};
117 | let { url, username, password } = userVariables;
118 | if (!(url && username && password)) {
119 | return null;
120 | }
121 | if (!url.startsWith("http://") && !url.startsWith("https://")) {
122 | url = `http://${url}`;
123 | }
124 | const salt = Math.random().toString(16).slice(2);
125 | const urlObj = new URL(`${url}/rest/stream`);
126 | urlObj.searchParams.append('u', username);
127 | urlObj.searchParams.append('s', salt);
128 | urlObj.searchParams.append('t', CryptoJs.MD5(`${password}${salt}`).toString(CryptoJs.enc.Hex));
129 | urlObj.searchParams.append('c', 'MusicFree');
130 | urlObj.searchParams.append('v', '1.14.1');
131 | urlObj.searchParams.append('f', 'json');
132 | urlObj.searchParams.append('id', musicItem.id);
133 | return {
134 | url: urlObj.toString()
135 | };
136 | }
137 | };
138 |
--------------------------------------------------------------------------------
/dist/suno/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const headers = {
5 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
6 | host: "studio-api.suno.ai"
7 | };
8 | async function getTopLists() {
9 | return [
10 | {
11 | title: "趋势榜",
12 | data: [
13 | {
14 | id: "1190bf92-10dc-4ce5-968a-7a377f37f984",
15 | title: "趋势榜 - 最近一天",
16 | },
17 | {
18 | id: "08a079b2-a63b-4f9c-9f29-de3c1864ddef",
19 | title: "趋势榜 - 最近一周",
20 | },
21 | {
22 | id: "845539aa-2a39-4cf5-b4ae-16d3fe159a77",
23 | title: "趋势榜 - 最近一月",
24 | },
25 | {
26 | id: "6943c7ee-cbc5-4f72-bc4e-f3371a8be9d5",
27 | title: "趋势榜 - 全部时间",
28 | },
29 | ],
30 | },
31 | {
32 | title: "最新榜",
33 | data: [
34 | {
35 | id: "cc14084a-2622-4c4b-8258-1f6b4b4f54b3",
36 | title: "最新榜",
37 | },
38 | ],
39 | },
40 | {
41 | title: "其他类别",
42 | data: [
43 | {
44 | id: "1ac7823f-8faf-474f-b14c-e4f7c7bb373f",
45 | title: "动物开会",
46 | },
47 | {
48 | id: '6713d315-3541-460d-8788-162cce241336',
49 | title: 'Lo-Fi'
50 | }
51 | ],
52 | },
53 | ];
54 | }
55 | async function getTopListDetail(topListItem) {
56 | const result = (await axios_1.default.get(`https://studio-api.suno.ai/api/playlist/${topListItem.id}/?page=0`, {
57 | headers
58 | })).data;
59 | return {
60 | isEnd: true,
61 | musicList: result.playlist_clips.map(it => {
62 | var _a, _b;
63 | const clip = it.clip;
64 | return {
65 | id: clip.id,
66 | url: clip.audio_url,
67 | artwork: clip.image_large_url || clip.image_url,
68 | duration: (_a = clip.metadata) === null || _a === void 0 ? void 0 : _a.duration,
69 | title: clip.title,
70 | artist: clip.display_name,
71 | userId: clip.user_id,
72 | rawLrc: (_b = clip.metadata) === null || _b === void 0 ? void 0 : _b.prompt
73 | };
74 | })
75 | };
76 | }
77 | async function getLyric(musicItem) {
78 | return {
79 | rawLrc: musicItem.rawLrc
80 | };
81 | }
82 | module.exports = {
83 | platform: "suno",
84 | version: "0.0.0",
85 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/suno/index.js",
86 | cacheControl: "no-cache",
87 | getTopLists,
88 | getTopListDetail,
89 | getLyric
90 | };
91 | getTopListDetail({
92 | id: "1ac7823f-8faf-474f-b14c-e4f7c7bb373f",
93 | title: "最新榜",
94 | }).then(e => console.log(e.musicList[0]));
95 |
--------------------------------------------------------------------------------
/dist/udio/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const headers = {
5 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
6 | };
7 | function formatMusicItem(it) {
8 | return {
9 | id: it.id,
10 | artist: it.artist,
11 | title: it.title,
12 | createdAt: it.created_at,
13 | artwork: it.image_path,
14 | url: it.song_path,
15 | duration: it.duration,
16 | mv: it.video_path,
17 | rawLrc: it.lyrics,
18 | };
19 | }
20 | async function searchMusic(query, page) {
21 | const pageSize = 30;
22 | const data = `{"searchQuery":{"sort":"plays","searchTerm":"${query}"},"pageParam":${page - 1},"pageSize":${pageSize},"trendingId":"93de406e-bdc1-40a6-befd-90637a362158"}`;
23 | const config = {
24 | method: "post",
25 | url: "https://www.udio.com/api/songs/search",
26 | headers: {
27 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.0.0",
28 | host: "www.udio.com",
29 | },
30 | data: data,
31 | };
32 | const result = (await (0, axios_1.default)(config)).data.data;
33 | return {
34 | isEnd: result.length < pageSize,
35 | data: result.map(formatMusicItem),
36 | };
37 | }
38 | async function search(query, page, type) {
39 | if (type === "music") {
40 | return await searchMusic(query, page);
41 | }
42 | }
43 | async function getMediaSource(musicItem, quality) {
44 | if (quality !== "standard") {
45 | return;
46 | }
47 | return musicItem.url;
48 | }
49 | async function getTopLists() {
50 | return [
51 | {
52 | title: "趋势榜",
53 | data: [
54 | {
55 | id: "today",
56 | maxAgeInHours: 24,
57 | type: "search",
58 | title: "趋势榜 - 最近一天",
59 | },
60 | {
61 | id: "1week",
62 | maxAgeInHours: 168,
63 | type: "search",
64 | title: "趋势榜 - 最近一周",
65 | },
66 | {
67 | id: "1month",
68 | maxAgeInHours: 720,
69 | type: "search",
70 | title: "趋势榜 - 最近一月",
71 | },
72 | {
73 | id: "alltime",
74 | type: "search",
75 | title: "趋势榜 - 全部时间",
76 | },
77 | ],
78 | },
79 | {
80 | title: "流派榜单",
81 | data: [
82 | {
83 | id: "89f0089f-1bfe-4713-8070-5830a6161afb",
84 | type: "playlist",
85 | title: "爵士",
86 | },
87 | {
88 | id: "935deb12-dc32-4005-a1fe-3c00c284ca52",
89 | type: "playlist",
90 | title: "乡村",
91 | },
92 | {
93 | id: "6028ad08-68cb-406d-aa35-a4917b6467d6",
94 | type: "playlist",
95 | title: "流行",
96 | },
97 | {
98 | id: "d033aa6e-655e-45d0-8138-dc9a0dc6b3a6",
99 | type: "playlist",
100 | title: "摇滚",
101 | },
102 | ],
103 | },
104 | ];
105 | }
106 | async function getTopListDetail(topListItem) {
107 | if (topListItem.type === "playlist") {
108 | const res = (await axios_1.default.get(`https://www.udio.com/api/playlists?id=${topListItem.id}`, {
109 | headers,
110 | })).data;
111 | const songList = res.playlists[0].song_list.join(",");
112 | const songs = (await axios_1.default.get(`https://www.udio.com/api/songs?songIds=${songList}`, {
113 | headers,
114 | })).data.songs;
115 | return {
116 | isEnd: true,
117 | musicList: songs.map(formatMusicItem),
118 | };
119 | }
120 | else if (topListItem.type === "search") {
121 | const pageSize = 30;
122 | const data = `{"searchQuery":{"sort":"plays","searchTerm":""${topListItem.maxAgeInHours
123 | ? `,"maxAgeInHours": ${topListItem.maxAgeInHours}`
124 | : ""}},"pageParam":0,"pageSize":${pageSize}}`;
125 | const config = {
126 | method: "post",
127 | url: "https://www.udio.com/api/songs/search",
128 | headers: {
129 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.0.0",
130 | host: "www.udio.com",
131 | },
132 | data: data,
133 | };
134 | const songs = (await (0, axios_1.default)(config)).data.data;
135 | return {
136 | isEnd: true,
137 | musicList: songs.map(formatMusicItem),
138 | };
139 | }
140 | }
141 | async function getLyric(musicItem) {
142 | return {
143 | rawLrc: musicItem.rawLrc
144 | };
145 | }
146 | module.exports = {
147 | platform: "udio",
148 | author: "猫头猫",
149 | version: "0.0.0",
150 | supportedSearchType: ["music"],
151 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/udio/index.js",
152 | cacheControl: "no-cache",
153 | search,
154 | getMediaSource,
155 | getTopListDetail,
156 | getTopLists,
157 | getLyric
158 | };
159 |
--------------------------------------------------------------------------------
/dist/webdav/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const webdav_1 = require("webdav");
4 | let cachedData = {};
5 | function getClient() {
6 | var _a, _b, _c;
7 | const { url, username, password, searchPath } = (_b = (_a = env === null || env === void 0 ? void 0 : env.getUserVariables) === null || _a === void 0 ? void 0 : _a.call(env)) !== null && _b !== void 0 ? _b : {};
8 | if (!(url && username && password)) {
9 | return null;
10 | }
11 | if (!(cachedData.url === url &&
12 | cachedData.username === username &&
13 | cachedData.password === password &&
14 | cachedData.searchPath === searchPath)) {
15 | cachedData.url = url;
16 | cachedData.username = username;
17 | cachedData.password = password;
18 | cachedData.searchPath = searchPath;
19 | cachedData.searchPathList = (_c = searchPath === null || searchPath === void 0 ? void 0 : searchPath.split) === null || _c === void 0 ? void 0 : _c.call(searchPath, ",");
20 | cachedData.cacheFileList = null;
21 | }
22 | return (0, webdav_1.createClient)(url, {
23 | authType: webdav_1.AuthType.Password,
24 | username,
25 | password,
26 | });
27 | }
28 | async function searchMusic(query) {
29 | var _a, _b;
30 | const client = getClient();
31 | if (!cachedData.cacheFileList) {
32 | const searchPathList = ((_a = cachedData.searchPathList) === null || _a === void 0 ? void 0 : _a.length)
33 | ? cachedData.searchPathList
34 | : ["/"];
35 | let result = [];
36 | for (let search of searchPathList) {
37 | try {
38 | const fileItems = (await client.getDirectoryContents(search)).filter((it) => it.type === "file" && it.mime.startsWith("audio"));
39 | result = [...result, ...fileItems];
40 | }
41 | catch (_c) { }
42 | }
43 | cachedData.cacheFileList = result;
44 | }
45 | return {
46 | isEnd: true,
47 | data: ((_b = cachedData.cacheFileList) !== null && _b !== void 0 ? _b : [])
48 | .filter((it) => it.basename.includes(query))
49 | .map((it) => ({
50 | title: it.basename,
51 | id: it.filename,
52 | artist: "未知作者",
53 | album: "未知专辑",
54 | })),
55 | };
56 | }
57 | async function getTopLists() {
58 | getClient();
59 | const data = {
60 | title: "全部歌曲",
61 | data: (cachedData.searchPathList || []).map((it) => ({
62 | title: it,
63 | id: it,
64 | })),
65 | };
66 | return [data];
67 | }
68 | async function getTopListDetail(topListItem) {
69 | const client = getClient();
70 | const fileItems = (await client.getDirectoryContents(topListItem.id)).filter((it) => it.type === "file" && it.mime.startsWith("audio"));
71 | return {
72 | musicList: fileItems.map((it) => ({
73 | title: it.basename,
74 | id: it.filename,
75 | artist: "未知作者",
76 | album: "未知专辑",
77 | })),
78 | };
79 | }
80 | module.exports = {
81 | platform: "WebDAV",
82 | author: "猫头猫",
83 | description: "使用此插件前先配置用户变量",
84 | userVariables: [
85 | {
86 | key: "url",
87 | name: "WebDAV地址",
88 | },
89 | {
90 | key: "username",
91 | name: "用户名",
92 | },
93 | {
94 | key: "password",
95 | name: "密码",
96 | type: "password",
97 | },
98 | {
99 | key: "searchPath",
100 | name: "存放歌曲的路径",
101 | },
102 | ],
103 | version: "0.0.2",
104 | supportedSearchType: ["music"],
105 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/webdav/index.js",
106 | cacheControl: "no-cache",
107 | search(query, page, type) {
108 | if (type === "music") {
109 | return searchMusic(query);
110 | }
111 | },
112 | getTopLists,
113 | getTopListDetail,
114 | getMediaSource(musicItem) {
115 | const client = getClient();
116 | return {
117 | url: client.getFileDownloadLink(musicItem.id),
118 | };
119 | },
120 | };
121 |
--------------------------------------------------------------------------------
/dist/yinyuetai/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | const pageNum = 20;
5 | function formatMusicItem(item) {
6 | var _a, _b, _c, _d;
7 | return {
8 | id: item.id,
9 | title: item.title,
10 | artist: (item === null || item === void 0 ? void 0 : item.allArtistNames) ||
11 | ((_b = (_a = item.artists) === null || _a === void 0 ? void 0 : _a.map) === null || _b === void 0 ? void 0 : _b.call(_a, (s) => s.name).join(", ")) ||
12 | ((_c = item.user) === null || _c === void 0 ? void 0 : _c.niceName),
13 | artwork: item === null || item === void 0 ? void 0 : item.headImg,
14 | urls: (_d = item === null || item === void 0 ? void 0 : item.fullClip) === null || _d === void 0 ? void 0 : _d.urls,
15 | };
16 | }
17 | function formatArtistItem(item) {
18 | return {
19 | id: item.id,
20 | name: item.name,
21 | avatar: item.headImg,
22 | };
23 | }
24 | let lastQuery;
25 | let lastMusicId;
26 | async function searchMusic(query, page) {
27 | if (query !== lastQuery || page === 1) {
28 | lastMusicId = 0;
29 | }
30 | lastQuery = query;
31 | let data = JSON.stringify({
32 | searchType: "MV",
33 | key: query,
34 | sinceId: lastMusicId,
35 | size: pageNum,
36 | requestTagRows: [
37 | {
38 | key: "sortType",
39 | chosenTags: ["HOTTEST"],
40 | },
41 | {
42 | key: "source",
43 | chosenTags: ["-1"],
44 | },
45 | {
46 | key: "duration",
47 | chosenTags: ["-1"],
48 | },
49 | ],
50 | });
51 | let config = {
52 | method: "post",
53 | maxBodyLength: Infinity,
54 | url: "https://search-api.yinyuetai.com/search/get_search_result.json",
55 | headers: {
56 | referrer: "https://www.yinyuetai.com/",
57 | accept: "application/json",
58 | "content-type": "application/json",
59 | wua: "YYT/1.0.0 (WEB;web;11;zh-CN;kADiV2jNJFy2ryvuyB5Ne)",
60 | },
61 | data: data,
62 | };
63 | const response = (await axios_1.default.request(config)).data.data;
64 | lastMusicId = response[response.length - 1].id;
65 | return {
66 | isEnd: pageNum > response.length,
67 | data: response.map(formatMusicItem),
68 | };
69 | }
70 | async function search(query, page, type) {
71 | if (type === "music") {
72 | return await searchMusic(query, page);
73 | }
74 | }
75 | async function getMediaSource(musicItem, quality) {
76 | let url;
77 | if (quality === "standard") {
78 | url = musicItem.urls.find((it) => it.streamType === 5).url;
79 | }
80 | else if (quality === "high") {
81 | url = musicItem.urls.find((it) => it.streamType === 1).url;
82 | }
83 | return {
84 | url,
85 | };
86 | }
87 | module.exports = {
88 | platform: "音悦台",
89 | author: '猫头猫',
90 | version: "0.0.1",
91 | supportedSearchType: ["music"],
92 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/yinyuetai/index.js",
93 | cacheControl: "no-cache",
94 | search,
95 | getMediaSource,
96 | };
97 |
--------------------------------------------------------------------------------
/dist/youtube/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const axios_1 = require("axios");
4 | function formatMusicItem(item) {
5 | var _a, _b, _c, _d, _e, _f, _g;
6 | return {
7 | id: item.videoId,
8 | title: (_b = (_a = item.title.runs) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text,
9 | artist: (_d = (_c = item.ownerText.runs) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text,
10 | artwork: (_g = (_f = (_e = item === null || item === void 0 ? void 0 : item.thumbnail) === null || _e === void 0 ? void 0 : _e.thumbnails) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.url,
11 | };
12 | }
13 | let lastQuery;
14 | let musicContinToken;
15 | async function searchMusic(query, page) {
16 | if (query !== lastQuery || page === 1) {
17 | musicContinToken = undefined;
18 | }
19 | lastQuery = query;
20 | let data = JSON.stringify({
21 | context: {
22 | client: {
23 | hl: "zh-CN",
24 | gl: "US",
25 | deviceMake: "",
26 | deviceModel: "",
27 | userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0,gzip(gfe)",
28 | clientName: "WEB",
29 | clientVersion: "2.20231121.08.00",
30 | osName: "Windows",
31 | osVersion: "10.0",
32 | platform: "DESKTOP",
33 | userInterfaceTheme: "USER_INTERFACE_THEME_LIGHT",
34 | browserName: "Edge Chromium",
35 | browserVersion: "119.0.0.0",
36 | acceptHeader: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
37 | screenWidthPoints: 1358,
38 | screenHeightPoints: 1012,
39 | screenPixelDensity: 1,
40 | screenDensityFloat: 1.2395833730697632,
41 | utcOffsetMinutes: 480,
42 | memoryTotalKbytes: "8000000",
43 | mainAppWebInfo: {
44 | pwaInstallabilityStatus: "PWA_INSTALLABILITY_STATUS_UNKNOWN",
45 | webDisplayMode: "WEB_DISPLAY_MODE_BROWSER",
46 | isWebNativeShareAvailable: true,
47 | },
48 | timeZone: "Asia/Shanghai",
49 | },
50 | user: {
51 | lockedSafetyMode: false,
52 | },
53 | request: {
54 | useSsl: true,
55 | internalExperimentFlags: [],
56 | },
57 | },
58 | query: musicContinToken ? undefined : query,
59 | continuation: musicContinToken || undefined,
60 | });
61 | var config = {
62 | method: "post",
63 | url: "https://www.youtube.com/youtubei/v1/search?prettyPrint=false",
64 | headers: {
65 | "Content-Type": "text/plain",
66 | },
67 | data: data,
68 | };
69 | const response = (await (0, axios_1.default)(config)).data;
70 | const contents = response.contents.twoColumnSearchResultsRenderer.primaryContents
71 | .sectionListRenderer.contents;
72 | const isEndItem = contents.find((it) => {
73 | var _a, _b, _c;
74 | return ((_c = (_b = (_a = it.continuationItemRenderer) === null || _a === void 0 ? void 0 : _a.continuationEndpoint) === null || _b === void 0 ? void 0 : _b.continuationCommand) === null || _c === void 0 ? void 0 : _c.request) === "CONTINUATION_REQUEST_TYPE_SEARCH";
75 | });
76 | if (isEndItem) {
77 | musicContinToken =
78 | isEndItem.continuationItemRenderer.continuationEndpoint
79 | .continuationCommand.token;
80 | }
81 | const musicData = contents.find((it) => it.itemSectionRenderer)
82 | .itemSectionRenderer.contents;
83 | let resultMusicData = [];
84 | for (let i = 0; i < musicData.length; ++i) {
85 | if (musicData[i].videoRenderer) {
86 | resultMusicData.push(formatMusicItem(musicData[i].videoRenderer));
87 | }
88 | }
89 | return {
90 | isEnd: !isEndItem,
91 | data: resultMusicData,
92 | };
93 | }
94 | async function search(query, page, type) {
95 | if (type === "music") {
96 | return await searchMusic(query, page);
97 | }
98 | }
99 | let cacheMediaSource = {
100 | id: null,
101 | urls: {},
102 | };
103 | function getQuality(label) {
104 | if (label === "small") {
105 | return "standard";
106 | }
107 | else if (label === "tiny") {
108 | return "low";
109 | }
110 | else if (label === "medium") {
111 | return "high";
112 | }
113 | else if (label === "large") {
114 | return "super";
115 | }
116 | else {
117 | return "standard";
118 | }
119 | }
120 | async function getMediaSource(musicItem, quality) {
121 | var _a, _b;
122 | if (musicItem.id === cacheMediaSource.id) {
123 | return {
124 | url: cacheMediaSource.urls[quality],
125 | };
126 | }
127 | cacheMediaSource = {
128 | id: null,
129 | urls: {},
130 | };
131 | const data = {
132 | context: {
133 | client: {
134 | screenWidthPoints: 689,
135 | screenHeightPoints: 963,
136 | screenPixelDensity: 1,
137 | utcOffsetMinutes: 120,
138 | hl: "en",
139 | gl: "GB",
140 | remoteHost: "1.1.1.1",
141 | deviceMake: "",
142 | deviceModel: "",
143 | userAgent: "com.google.android.apps.youtube.music/6.14.50 (Linux; U; Android 13; GB) gzip",
144 | clientName: "ANDROID_MUSIC",
145 | clientVersion: "6.14.50",
146 | osName: "Android",
147 | osVersion: "13",
148 | originalUrl: "https://www.youtube.com/tv?is_account_switch=1&hrld=1&fltor=1",
149 | theme: "CLASSIC",
150 | platform: "MOBILE",
151 | clientFormFactor: "UNKNOWN_FORM_FACTOR",
152 | webpSupport: false,
153 | timeZone: "Europe/Amsterdam",
154 | acceptHeader: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
155 | },
156 | user: { enableSafetyMode: false },
157 | request: {
158 | internalExperimentFlags: [],
159 | consistencyTokenJars: [],
160 | },
161 | },
162 | contentCheckOk: true,
163 | racyCheckOk: true,
164 | video_id: musicItem.id,
165 | };
166 | var config = {
167 | method: "post",
168 | url: "https://www.youtube.com/youtubei/v1/player?prettyPrint=false",
169 | headers: {
170 | "Content-Type": "application/json",
171 | },
172 | data: JSON.stringify(data),
173 | };
174 | const result = (await (0, axios_1.default)(config)).data;
175 | const formats = (_a = result.streamingData.formats) !== null && _a !== void 0 ? _a : [];
176 | const adaptiveFormats = (_b = result.streamingData.adaptiveFormats) !== null && _b !== void 0 ? _b : [];
177 | [...formats, ...adaptiveFormats].forEach((it) => {
178 | const q = getQuality(it.quality);
179 | if (q && it.url && !cacheMediaSource.urls[q]) {
180 | cacheMediaSource.urls[q] = it.url;
181 | }
182 | });
183 | return {
184 | url: cacheMediaSource.urls[quality],
185 | };
186 | }
187 | module.exports = {
188 | platform: "Youtube",
189 | author: '猫头猫',
190 | version: "0.0.1",
191 | supportedSearchType: ["music"],
192 | srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/youtube/index.js",
193 | cacheControl: "no-cache",
194 | search,
195 | getMediaSource,
196 | };
197 |
--------------------------------------------------------------------------------
/example/freesound.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios");
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = {
5 | platform: "FreeSound", // 插件名
6 | version: "0.0.0", // 版本号
7 | cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
8 | async search(query, page, type) {
9 | if (type === "music") {
10 | // 我们能搜索的只有音乐,因此判断下类型
11 | // 获取网站的html
12 | const rawHtml = (
13 | await axios.get("https://freesound.org/search", {
14 | q: query,
15 | page,
16 | })
17 | ).data;
18 |
19 | // 接下来解析html
20 | const $ = cheerio.load(rawHtml);
21 | // 存储搜索结果
22 | const searchResults = [];
23 | // 获取所有的结果
24 | const resultElements = $('.bw-search__result');
25 | // 解析每一个结果
26 | resultElements.each((index, element) => {
27 | const playerElement = $(element).find('.bw-player');
28 | // id
29 | const id = playerElement.data('sound-id');
30 | // 音频名
31 | const title = playerElement.data('title');
32 | // 作者
33 | const artist = $(element).find('.col-12.col-lg-12.middle a').text();
34 | // 专辑封面
35 | const artwork = playerElement.data('waveform');
36 | // 音源
37 | const url = playerElement.data('mp3');
38 | // 专辑名,这里就随便写个了,不写也没事
39 | const album = '来自FreeSound的音频';
40 |
41 | searchResults.push({
42 | // 一定要有一个 id 字段
43 | id,
44 | title,
45 | artist,
46 | artwork,
47 | album,
48 | url
49 | })
50 | });
51 | return {
52 | isEnd: true,
53 | data: searchResults
54 | }
55 | }
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MusicFreePlugins",
3 | "version": "1.0.0",
4 | "description": "MusicFree插件",
5 | "main": "dist/plugin.js",
6 | "scripts": {
7 | "serve": "http-server .",
8 | "build": "tsc & node ./scripts/generate.js",
9 | "publish": "git checkout master & git merge v0.1 & git push & git checkout v0.1 & git push",
10 | "test-kugou": "ts-node -T ./plugins/kugou/index.ts",
11 | "test-navidrome": "ts-node -T ./plugins/navidrome/index.ts",
12 | "test-audiomack": "ts-node -T ./plugins/audiomack/index.ts",
13 | "test-kuwo": "ts-node -T ./plugins/kuwo/index.ts",
14 | "test-migu": "ts-node -T ./plugins/migu/index.ts",
15 | "test-qq": "ts-node -T ./plugins/qq/index.ts",
16 | "test-qianqian": "ts-node -T ./plugins/qianqian/index.ts",
17 | "test-netease": "ts-node -T ./plugins/netease/index.ts",
18 | "test-bilibili": "ts-node -T ./plugins/bilibili/index.ts",
19 | "test-maoerfm": "ts-node -T ./plugins/maoerfm/index.ts",
20 | "test-kuaishou": "ts-node -T ./plugins/kuaishou/index.ts",
21 | "test-yinyuetai": "ts-node -T ./plugins/yinyuetai/index.ts",
22 | "test-youtube": "ts-node -T ./plugins/youtube/index.ts",
23 | "test-xmly": "ts-node -T ./plugins/xmly/index.ts",
24 | "test-suno": "ts-node -T ./plugins/suno/index.ts",
25 | "test-udio": "ts-node -T ./plugins/udio/index.ts",
26 | "test-wxmp3": "ts-node -T ./plugins/wxmp3/index.ts"
27 | },
28 | "keywords": [],
29 | "author": {
30 | "name": "猫头猫",
31 | "email": "lhx_xjtu@163.com"
32 | },
33 | "license": "GPL",
34 | "devDependencies": {
35 | "@react-native-cookies/cookies": "^6.2.1",
36 | "@types/crypto-js": "^4.1.1",
37 | "@types/he": "^1.1.2",
38 | "axios": "^0.27.2",
39 | "big-integer": "^1.6.51",
40 | "cheerio": "^1.0.0-rc.12",
41 | "crypto-js": "^4.1.1",
42 | "dayjs": "^1.11.4",
43 | "form-data": "^4.0.1",
44 | "he": "^1.2.0",
45 | "http-server": "^14.1.1",
46 | "https-proxy-agent": "^7.0.2",
47 | "qs": "^6.11.0",
48 | "rimraf": "^4.1.1",
49 | "ts-node": "^10.9.1",
50 | "typescript": "^4.9.4"
51 | },
52 | "dependencies": {
53 | "webdav": "^5.3.1"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/plugins.json:
--------------------------------------------------------------------------------
1 | {"desc":"此链接为 MusicFree 插件,插件开发及使用方式参考 https://musicfree.upup.fun","plugins":[{"name":"Audiomack","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/audiomack/index.js","version":"0.0.2"},{"name":"歌词网","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciwang/index.js","version":"0.0.0"},{"name":"歌词千寻","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciqianxun/index.js","version":"0.0.0"},{"name":"Navidrome","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/navidrome/index.js","version":"0.0.0"},{"name":"suno","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/suno/index.js","version":"0.0.0"},{"name":"udio","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/udio/index.js","version":"0.0.0"},{"name":"猫耳FM","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/maoerfm/index.js","version":"0.1.4"},{"name":"快手","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/kuaishou/index.js","version":"0.0.2"},{"name":"音悦台","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/yinyuetai/index.js","version":"0.0.1"},{"name":"Youtube","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/youtube/index.js","version":"0.0.1"},{"name":"bilibili","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/bilibili/index.js","version":"0.2.3"},{"name":"WebDAV","url":"https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/webdav/index.js","version":"0.0.2"}]}
--------------------------------------------------------------------------------
/plugins/audiomack/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { load } from "cheerio";
3 | import CryptoJS = require("crypto-js");
4 | import dayjs = require("dayjs");
5 | const pageSize = 20;
6 |
7 | const headers = {
8 | "user-agent":
9 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
10 | };
11 |
12 | /** 工具函数 */
13 | function nonce(e = 10) {
14 | let n = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
15 | r = "";
16 | for (let i = 0; i < e; i++)
17 | r += n.charAt(Math.floor(Math.random() * n.length));
18 | return r;
19 | }
20 |
21 | function getNormalizedParams(parameters) {
22 | const sortedKeys = [];
23 | const normalizedParameters = [];
24 | for (let e in parameters) {
25 | sortedKeys.push(_encode(e));
26 | }
27 | sortedKeys.sort();
28 |
29 | for (let idx = 0; idx < sortedKeys.length; idx++) {
30 | const e = sortedKeys[idx];
31 | var n,
32 | r,
33 | i = _decode(e),
34 | a = parameters[i];
35 | for (a.sort(), n = 0; n < a.length; n++)
36 | (r = _encode(a[n])), normalizedParameters.push(e + "=" + r);
37 | }
38 |
39 | return normalizedParameters.join("&");
40 | }
41 |
42 | function _encode(e) {
43 | return e
44 | ? encodeURIComponent(e)
45 | .replace(/[!'()]/g, escape)
46 | .replace(/\*/g, "%2A")
47 | : "";
48 | }
49 |
50 | function _decode(e) {
51 | return e ? decodeURIComponent(e) : "";
52 | }
53 |
54 | function u(e) {
55 | (this._parameters = {}), this._loadParameters(e || {});
56 | }
57 |
58 | u.prototype = {
59 | _loadParameters: function (e) {
60 | e instanceof Array
61 | ? this._loadParametersFromArray(e)
62 | : "object" == typeof e && this._loadParametersFromObject(e);
63 | },
64 | _loadParametersFromArray: function (e) {
65 | var n;
66 | for (n = 0; n < e.length; n++) this._loadParametersFromObject(e[n]);
67 | },
68 | _loadParametersFromObject: function (e) {
69 | var n;
70 | for (n in e)
71 | if (e.hasOwnProperty(n)) {
72 | var r = this._getStringFromParameter(e[n]);
73 | this._loadParameterValue(n, r);
74 | }
75 | },
76 | _loadParameterValue: function (e, n) {
77 | var r;
78 | if (n instanceof Array) {
79 | for (r = 0; r < n.length; r++) {
80 | var i = this._getStringFromParameter(n[r]);
81 | this._addParameter(e, i);
82 | }
83 | 0 == n.length && this._addParameter(e, "");
84 | } else this._addParameter(e, n);
85 | },
86 | _getStringFromParameter: function (e) {
87 | var n = e || "";
88 | try {
89 | ("number" == typeof e || "boolean" == typeof e) && (n = e.toString());
90 | } catch (e) {}
91 | return n;
92 | },
93 | _addParameter: function (e, n) {
94 | this._parameters[e] || (this._parameters[e] = []),
95 | this._parameters[e].push(n);
96 | },
97 | get: function () {
98 | return this._parameters;
99 | },
100 | };
101 |
102 | // 获取签名
103 | function getSignature(
104 | method: string,
105 | urlPath: string,
106 | params,
107 | secret = "f3ac5b086f3eab260520d8e3049561e6"
108 | ) {
109 | urlPath = urlPath.split("?")[0];
110 | urlPath = urlPath.startsWith("http")
111 | ? urlPath
112 | : "https://api.audiomack.com/v1" + urlPath;
113 | const r = new u(params).get();
114 |
115 | const httpMethod = method.toUpperCase();
116 | const normdParams = getNormalizedParams(r);
117 |
118 | const l =
119 | _encode(httpMethod) + "&" + _encode(urlPath) + "&" + _encode(normdParams);
120 |
121 | const hash = CryptoJS.HmacSHA1(l, secret + "&").toString(CryptoJS.enc.Base64);
122 | return hash;
123 | }
124 |
125 | function formatMusicItem(raw) {
126 | return {
127 | id: raw.id,
128 | artwork: raw.image || raw.image_base,
129 | duration: +raw.duration,
130 | title: raw.title,
131 | artist: raw.artist,
132 | album: raw.album,
133 | url_slug: raw.url_slug,
134 | };
135 | }
136 |
137 | function formatAlbumItem(raw) {
138 | return {
139 | artist: raw.artist,
140 | artwork: raw.image || raw.image_base,
141 | id: raw.id,
142 | date: dayjs.unix(+raw.released).format("YYYY-MM-DD"),
143 | title: raw.title,
144 | _musicList: raw?.tracks?.map?.((it) => ({
145 | id: it.song_id || it.id,
146 | artwork: raw.image || raw.image_base,
147 | duration: +it.duration,
148 | title: it.title,
149 | artist: it.artist,
150 | album: raw.title,
151 | })),
152 | };
153 | }
154 |
155 | function formatMusicSheetItem(raw) {
156 | return {
157 | worksNum: raw.track_count,
158 | id: raw.id,
159 | title: raw.title,
160 | artist: raw.artist?.name,
161 | artwork: raw.image || raw.image_base,
162 | artistItem: {
163 | id: raw.artist?.id,
164 | avatar: raw.artist?.image || raw.artist?.image_base,
165 | name: raw.artist?.name,
166 | url_slug: raw.artist?.url_slug,
167 | },
168 | createAt: dayjs.unix(+raw.created).format("YYYY-MM-DD"),
169 | url_slug: raw.url_slug,
170 | };
171 | }
172 |
173 | async function searchBase(query, page, show) {
174 | const params = {
175 | limit: pageSize,
176 | oauth_consumer_key: "audiomack-js",
177 | oauth_nonce: nonce(32),
178 | oauth_signature_method: "HMAC-SHA1",
179 | oauth_timestamp: Math.round(Date.now() / 1e3),
180 | oauth_version: "1.0",
181 | page: page,
182 | q: query,
183 | show: show,
184 | sort: "popular",
185 | };
186 | const oauth_signature = getSignature("GET", "/search", params);
187 |
188 | const results = (
189 | await axios.get("https://api.audiomack.com/v1/search", {
190 | headers,
191 | params: {
192 | ...params,
193 | oauth_signature,
194 | },
195 | })
196 | ).data.results;
197 | return results;
198 | }
199 |
200 | async function searchMusic(query, page) {
201 | const results = await searchBase(query, page, "songs");
202 |
203 | return {
204 | isEnd: results.length < pageSize,
205 | data: results.map(formatMusicItem),
206 | };
207 | }
208 |
209 | async function searchAlbum(query, page) {
210 | const results = await searchBase(query, page, "albums");
211 |
212 | return {
213 | isEnd: results.length < pageSize,
214 | data: results.map(formatAlbumItem),
215 | };
216 | }
217 |
218 | async function searchMusicSheet(query, page) {
219 | const results = await searchBase(query, page, "playlists");
220 |
221 | return {
222 | isEnd: results.length < pageSize,
223 | data: results.map(formatMusicSheetItem),
224 | };
225 | }
226 |
227 | async function searchArtist(query, page) {
228 | const results = await searchBase(query, page, "artists");
229 |
230 | return {
231 | isEnd: results.length < pageSize,
232 | data: results.map((raw) => ({
233 | name: raw.name,
234 | id: raw.id,
235 | avatar: raw.image || raw.image_base,
236 | url_slug: raw.url_slug,
237 | })),
238 | };
239 | }
240 |
241 | let dataUrlBase;
242 |
243 | async function getDataUrlBase() {
244 | if (dataUrlBase) {
245 | return dataUrlBase;
246 | }
247 | const rawHtml = (await axios.get("https://audiomack.com/")).data;
248 | const $ = load(rawHtml);
249 | const script = $("script#__NEXT_DATA__").text();
250 | const jsonObj = JSON.parse(script);
251 |
252 | if (jsonObj.buildId) {
253 | dataUrlBase = `https://audiomack.com/_next/data/${jsonObj.buildId}`
254 | }
255 | return dataUrlBase;
256 | }
257 |
258 | async function getArtistWorks(artistItem, page, type) {
259 | if (type === "music") {
260 | const params = {
261 | artist_id: artistItem.id,
262 | limit: pageSize,
263 | oauth_consumer_key: "audiomack-js",
264 | oauth_nonce: nonce(32),
265 | oauth_signature_method: "HMAC-SHA1",
266 | oauth_timestamp: Math.round(Date.now() / 1e3),
267 | oauth_version: "1.0",
268 | page: page,
269 | sort: "rank",
270 | type: "songs",
271 | };
272 | const oauth_signature = getSignature(
273 | "GET",
274 | "/search_artist_content",
275 | params
276 | );
277 |
278 | const results = (
279 | await axios.get("https://api.audiomack.com/v1/search_artist_content", {
280 | headers,
281 | params: {
282 | ...params,
283 | oauth_signature,
284 | },
285 | })
286 | ).data.results;
287 | return {
288 | isEnd: results.length < pageSize,
289 | data: results.map(formatMusicItem),
290 | };
291 | } else if (type === "album") {
292 | const params = {
293 | artist_id: artistItem.id,
294 | limit: pageSize,
295 | oauth_consumer_key: "audiomack-js",
296 | oauth_nonce: nonce(32),
297 | oauth_signature_method: "HMAC-SHA1",
298 | oauth_timestamp: Math.round(Date.now() / 1e3),
299 | oauth_version: "1.0",
300 | page: page,
301 | sort: "rank",
302 | type: "albums",
303 | };
304 | const oauth_signature = getSignature(
305 | "GET",
306 | "/search_artist_content",
307 | params
308 | );
309 |
310 | const results = (
311 | await axios.get("https://api.audiomack.com/v1/search_artist_content", {
312 | headers,
313 | params: {
314 | ...params,
315 | oauth_signature,
316 | },
317 | })
318 | ).data.results;
319 | return {
320 | isEnd: results.length < pageSize,
321 | data: results.map(formatAlbumItem),
322 | };
323 | }
324 | }
325 |
326 | async function getMusicSheetInfo(sheet: IMusicSheet.IMusicSheetItem, page) {
327 | const _dataUrlBase = await getDataUrlBase();
328 |
329 | const res = (
330 | await axios.get(
331 | `${_dataUrlBase}/${sheet.artistItem.url_slug}/playlist/${sheet.url_slug}.json`,
332 | {
333 | params: {
334 | page_slug: sheet.artistItem.url_slug,
335 | playlist_slug: sheet.url_slug,
336 | },
337 | headers: {
338 | ...headers,
339 | },
340 | }
341 | )
342 | ).data;
343 |
344 | const musicPage = res.pageProps.initialState.musicPage;
345 | const targetKey = Object.keys(musicPage).find((it) =>
346 | it.startsWith("musicMusicPage")
347 | );
348 | const tracks = musicPage[targetKey].results.tracks;
349 |
350 | return {
351 | isEnd: true,
352 | musicList: tracks.map(formatMusicItem),
353 | };
354 | }
355 |
356 | async function getMediaSource(musicItem, quality: IMusic.IQualityKey) {
357 | if (quality !== "standard") {
358 | return;
359 | }
360 |
361 | const params = {
362 | environment: "desktop-web",
363 | hq: true,
364 | oauth_consumer_key: "audiomack-js",
365 | oauth_nonce: nonce(32),
366 | oauth_signature_method: "HMAC-SHA1",
367 | oauth_timestamp: Math.round(Date.now() / 1e3),
368 | oauth_version: "1.0",
369 | section: "/search",
370 | };
371 | const oauth_signature = getSignature(
372 | "GET",
373 | `/music/play/${musicItem.id}`,
374 | params
375 | );
376 |
377 | const res = (
378 | await axios.get(`https://api.audiomack.com/v1/music/play/${musicItem.id}`, {
379 | headers: {
380 | ...headers,
381 | origin: "https://audiomack.com",
382 | },
383 | params: {
384 | ...params,
385 | oauth_signature,
386 | },
387 | })
388 | ).data;
389 | return {
390 | url: res.signedUrl,
391 | };
392 | }
393 |
394 | async function getAlbumInfo(albumItem) {
395 | return {
396 | // 老版本有bug
397 | musicList: albumItem._musicList.map((it) => ({ ...it })),
398 | };
399 | }
400 |
401 | async function getRecommendSheetTags() {
402 | const rawHtml = (await axios.get("https://audiomack.com/playlists")).data;
403 | const $ = load(rawHtml);
404 | const script = $("script#__NEXT_DATA__").text();
405 | const jsonObj = JSON.parse(script);
406 |
407 | return {
408 | data: [
409 | {
410 | data: jsonObj.props.pageProps.categories,
411 | },
412 | ],
413 | };
414 | }
415 |
416 | async function getRecommendSheetsByTag(tag, page) {
417 | if (!tag.id) {
418 | tag = { id: "34", title: "What's New", url_slug: "whats-new" };
419 | }
420 | const params = {
421 | featured: "yes",
422 | limit: pageSize,
423 | oauth_consumer_key: "audiomack-js",
424 | oauth_nonce: nonce(32),
425 | oauth_signature_method: "HMAC-SHA1",
426 | oauth_timestamp: Math.round(Date.now() / 1e3),
427 | oauth_version: "1.0",
428 | page: page,
429 | slug: tag.url_slug,
430 | };
431 | const oauth_signature = getSignature("GET", "/playlist/categories", params);
432 |
433 | const results = (
434 | await axios.get("https://api.audiomack.com/v1/playlist/categories", {
435 | headers,
436 | params: {
437 | ...params,
438 | oauth_signature,
439 | },
440 | })
441 | ).data.results.playlists;
442 | return {
443 | isEnd: results.length < pageSize,
444 | data: results.map(formatMusicSheetItem),
445 | };
446 | }
447 |
448 | async function getTopLists() {
449 | const genres = [
450 | {
451 | title: "All Genres",
452 | url_slug: null,
453 | },
454 | {
455 | title: "Afrosounds",
456 | url_slug: "afrobeats",
457 | },
458 | {
459 | title: "Hip-Hop/Rap",
460 | url_slug: "rap",
461 | },
462 | {
463 | title: "Latin",
464 | url_slug: "latin",
465 | },
466 | {
467 | title: "Caribbean",
468 | url_slug: "caribbean",
469 | },
470 | {
471 | title: "Pop",
472 | url_slug: "pop",
473 | },
474 | {
475 | title: "R&B",
476 | url_slug: "rb",
477 | },
478 | {
479 | title: "Gospel",
480 | url_slug: "gospel",
481 | },
482 | {
483 | title: "Electronic",
484 | url_slug: "electronic",
485 | },
486 | {
487 | title: "Rock",
488 | url_slug: "rock",
489 | },
490 | {
491 | title: "Punjabi",
492 | url_slug: "punjabi",
493 | },
494 | {
495 | title: "Country",
496 | url_slug: "country",
497 | },
498 | {
499 | title: "Instrumental",
500 | url_slug: "instrumental",
501 | },
502 | {
503 | title: "Podcast",
504 | url_slug: "podcast",
505 | },
506 | ];
507 | return [
508 | {
509 | title: "Trending Songs",
510 | data: genres.map((it) => ({
511 | ...it,
512 | type: "trending",
513 | id: it.url_slug ?? it.title,
514 | })),
515 | },
516 | {
517 | title: "Recently Added Music",
518 | data: genres.map((it) => ({
519 | ...it,
520 | type: "recent",
521 | id: it.url_slug ?? it.title,
522 | })),
523 | },
524 | ];
525 | }
526 |
527 | // TODO: 支持分页
528 | async function getTopListDetail(topListItem, page = 1) {
529 | const type = topListItem.type;
530 | const partialUrl = `/music/${
531 | topListItem.url_slug ? `${topListItem.url_slug}/` : ""
532 | }${type}/page/${page}`;
533 | const url = `https://api.audiomack.com/v1${partialUrl}`;
534 | const params = {
535 | oauth_consumer_key: "audiomack-js",
536 | oauth_nonce: nonce(32),
537 | oauth_signature_method: "HMAC-SHA1",
538 | oauth_timestamp: Math.round(Date.now() / 1e3),
539 | oauth_version: "1.0",
540 | type: "song",
541 | };
542 | const oauth_signature = getSignature("GET", partialUrl, params);
543 |
544 | const results = (
545 | await axios.get(url, {
546 | headers,
547 | params: {
548 | ...params,
549 | oauth_signature,
550 | },
551 | })
552 | ).data.results;
553 |
554 | return {
555 | musicList: results.map(formatMusicItem),
556 | };
557 | }
558 |
559 | module.exports = {
560 | platform: "Audiomack",
561 | version: "0.0.2",
562 | author: '猫头猫',
563 | primaryKey: ["id", "url_slug"],
564 | srcUrl:
565 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/audiomack/index.js",
566 | cacheControl: "no-cache",
567 | supportedSearchType: ['music', 'album', 'sheet', 'artist'],
568 | async search(query, page, type) {
569 | if (type === "music") {
570 | return await searchMusic(query, page);
571 | } else if (type === "album") {
572 | return await searchAlbum(query, page);
573 | } else if (type === "sheet") {
574 | return await searchMusicSheet(query, page);
575 | } else if (type === "artist") {
576 | return await searchArtist(query, page);
577 | }
578 | },
579 | getMediaSource,
580 | getAlbumInfo,
581 | getMusicSheetInfo,
582 | getArtistWorks,
583 | getRecommendSheetTags,
584 | getRecommendSheetsByTag,
585 | getTopLists,
586 | getTopListDetail,
587 | };
588 |
589 | // getMediaSource(
590 | // {
591 | // id: 14925926,
592 | // artwork:
593 | // "https://assets.audiomack.com/1756162437/a16444eb63eb3b3244e31c39ee98066f10c510429bde00f0949b9a0796f5b859.jpeg",
594 | // duration: 227,
595 | // title: "夜曲",
596 | // artist: "周杰伦",
597 | // album: "十一月的萧邦",
598 | // },
599 | // "standard"
600 | // ).then(console.log);
601 |
602 | // searchMusicSheet("周杰伦", 1).then(e => console.log(JSON.stringify(e.data[0])));
603 |
604 |
--------------------------------------------------------------------------------
/plugins/bilibili/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import dayjs = require("dayjs");
3 | import he = require("he");
4 | import CryptoJs = require("crypto-js");
5 | const {load} = require('cheerio');
6 |
7 | const headers = {
8 | "user-agent":
9 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
10 | accept: "*/*",
11 | "accept-encoding": "gzip, deflate, br",
12 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
13 | };
14 | let cookie;
15 |
16 | /** 获取cid */
17 | async function getCid(bvid, aid) {
18 | const params = bvid
19 | ? {
20 | bvid: bvid,
21 | }
22 | : {
23 | aid: aid,
24 | };
25 | const cidRes = (
26 | await axios.get("https://api.bilibili.com/x/web-interface/view?%s", {
27 | headers: headers,
28 | params: params,
29 | })
30 | ).data;
31 | return cidRes;
32 | }
33 |
34 | /** 格式化 */
35 | function durationToSec(duration: string | number) {
36 | if (typeof duration === "number") {
37 | return duration;
38 | }
39 |
40 | if (typeof duration === "string") {
41 | var dur = duration.split(":");
42 | return dur.reduce(function (prev, curr) {
43 | return 60 * prev + +curr;
44 | }, 0);
45 | }
46 |
47 | return 0;
48 | }
49 |
50 | const searchHeaders = {
51 | "user-agent":
52 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
53 | accept: "application/json, text/plain, */*",
54 | "accept-encoding": "gzip, deflate, br",
55 | origin: "https://search.bilibili.com",
56 | "sec-fetch-site": "same-site",
57 | "sec-fetch-mode": "cors",
58 | "sec-fetch-dest": "empty",
59 | referer: "https://search.bilibili.com/",
60 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
61 | };
62 | async function getCookie() {
63 | if (!cookie) {
64 | cookie = (
65 | await axios.get("https://api.bilibili.com/x/frontend/finger/spi", {
66 | headers: {
67 | "User-Agent":
68 | "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/114.0.0.0",
69 | },
70 | })
71 | ).data.data;
72 | }
73 | }
74 | const pageSize = 20;
75 | /** 搜索 */
76 | async function searchBase(keyword: string, page: number, searchType) {
77 | await getCookie();
78 | const params = {
79 | context: "",
80 | page: page,
81 | order: "",
82 | page_size: pageSize,
83 | keyword: keyword,
84 | duration: "",
85 | tids_1: "",
86 | tids_2: "",
87 | __refresh__: true,
88 | _extra: "",
89 | highlight: 1,
90 | single_column: 0,
91 | platform: "pc",
92 | from_source: "",
93 | search_type: searchType,
94 | dynamic_offset: 0,
95 | };
96 | const res = (
97 | await axios.get("https://api.bilibili.com/x/web-interface/search/type", {
98 | headers: {
99 | ...searchHeaders,
100 | cookie: `buvid3=${cookie.b_3};buvid4=${cookie.b_4}`,
101 | },
102 | params: params,
103 | })
104 | ).data;
105 | return res.data;
106 | }
107 |
108 | /** 获取收藏夹 */
109 | async function getFavoriteList(id: number | string) {
110 | const result = [];
111 | const pageSize = 20;
112 | let page = 1;
113 |
114 | while (true) {
115 | try {
116 | const {
117 | data: {
118 | data: { medias, has_more },
119 | },
120 | } = await axios.get("https://api.bilibili.com/x/v3/fav/resource/list", {
121 | params: {
122 | media_id: id,
123 | platform: "web",
124 | ps: pageSize,
125 | pn: page,
126 | },
127 | });
128 | result.push(...medias);
129 |
130 | if (!has_more) {
131 | break;
132 | }
133 | page += 1;
134 | } catch (error) {
135 | console.warn(error);
136 | break;
137 | }
138 | }
139 |
140 | return result;
141 | }
142 |
143 | function formatMedia(result: any) {
144 | const title = he.decode(
145 | result.title?.replace(/(\)|(\<\/em\>)/g, "") ?? ""
146 | );
147 | return {
148 | id: result.cid ?? result.bvid ?? result.aid,
149 | aid: result.aid,
150 | bvid: result.bvid,
151 | artist: result.author ?? result.owner?.name,
152 | title,
153 | alias: title.match(/《(.+?)》/)?.[1],
154 | album: result.bvid ?? result.aid,
155 | artwork: result.pic?.startsWith("//")
156 | ? "http:".concat(result.pic)
157 | : result.pic,
158 | // description: result.description,
159 | duration: durationToSec(result.duration),
160 | tags: result.tag?.split(","),
161 | date: dayjs.unix(result.pubdate || result.created).format("YYYY-MM-DD"),
162 | };
163 | }
164 |
165 | async function searchAlbum(keyword, page) {
166 | const resultData = await searchBase(keyword, page, "video");
167 | const albums = resultData.result.map(formatMedia);
168 | return {
169 | isEnd: resultData.numResults <= page * pageSize,
170 | data: albums,
171 | };
172 | }
173 |
174 | async function searchArtist(keyword, page) {
175 | const resultData = await searchBase(keyword, page, "bili_user");
176 | const artists = resultData.result.map((result) => ({
177 | name: result.uname,
178 | id: result.mid,
179 | fans: result.fans,
180 | description: result.usign,
181 | avatar: result.upic?.startsWith("//")
182 | ? `https://${result.upic}`
183 | : result.upic,
184 | worksNum: result.videos,
185 | }));
186 | return {
187 | isEnd: resultData.numResults <= page * pageSize,
188 | data: artists,
189 | };
190 | }
191 |
192 | function getMixinKey(e) {
193 | var t = [];
194 | return (
195 | [
196 | 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5,
197 | 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55,
198 | 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57,
199 | 62, 11, 36, 20, 34, 44, 52,
200 | ].forEach(function (r) {
201 | e.charAt(r) && t.push(e.charAt(r));
202 | }),
203 | t.join("").slice(0, 32)
204 | );
205 | }
206 |
207 | function hmacSha256(key, message) {
208 | const hmac = CryptoJs.HmacSHA256(message, key);
209 | return hmac.toString(CryptoJs.enc.Hex);
210 | }
211 |
212 |
213 |
214 | async function getBiliTicket(csrf) {
215 | const ts = Math.floor(Date.now() / 1000);
216 | const hexSign = hmacSha256('XgwSnGZ1p', `ts${ts}`);
217 | const url = 'https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket';
218 |
219 | try {
220 | const response = await axios.post(url, null, {
221 | params: {
222 | key_id: 'ec02',
223 | hexsign: hexSign,
224 | 'context[ts]': ts,
225 | csrf: csrf || ''
226 | },
227 | headers: {
228 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
229 | }
230 | });
231 |
232 | const data = await response.data;
233 | return data.data;
234 | } catch (e) {
235 | throw e;
236 | }
237 | }
238 |
239 | let img, sub, syncedTime: Date;
240 | async function getWBIKeys() {
241 | if (img && sub && syncedTime && syncedTime.getDate() === (new Date()).getDate()) {
242 | return {
243 | img,
244 | sub
245 | }
246 | } else {
247 | const data = await getBiliTicket('');
248 | img = data.nav.img;
249 | img = img.slice(img.lastIndexOf('/') + 1, img.lastIndexOf('.'));
250 | sub = data.nav.sub;
251 | sub = sub.slice(sub.lastIndexOf('/') + 1, sub.lastIndexOf('.'))
252 | syncedTime = new Date();
253 | return {
254 | img,
255 | sub
256 | }
257 | }
258 | }
259 |
260 |
261 | async function getRid(params) {
262 | const wbiKeys = await getWBIKeys();
263 | const npi = wbiKeys.img + wbiKeys.sub;
264 | const o = getMixinKey(npi);
265 | const l = Object.keys(params).sort();
266 | let c = [];
267 | for (let d = 0, u = /[!'\(\)*]/g; d < l.length; ++d) {
268 | let [h, p] = [l[d], params[l[d]]];
269 | p && "string" == typeof p && (p = p.replace(u, "")),
270 | null != p &&
271 | c.push(
272 | "".concat(encodeURIComponent(h), "=").concat(encodeURIComponent(p))
273 | );
274 | }
275 | const f = c.join("&");
276 | const w_rid = CryptoJs.MD5(f + o).toString();
277 | return w_rid;
278 | }
279 |
280 |
281 | let w_webid;
282 | let w_webid_date: Date;
283 | async function getWWebId(id: string) {
284 | if (w_webid && w_webid_date && (Date.now() - w_webid_date.getTime() < 1000 * 60 * 60)) {
285 | return w_webid;
286 | }
287 | const html = (await axios.get("https://space.bilibili.com/" + id, {
288 | headers: {
289 | "user-agent":
290 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
291 | }
292 | })).data
293 |
294 | const $ = load(html);
295 | const content = $("#__RENDER_DATA__").text();
296 | const jsonContent = JSON.parse(decodeURIComponent(content))
297 | w_webid = jsonContent.access_id;
298 | w_webid_date = new Date();
299 | return w_webid;
300 | }
301 |
302 |
303 | async function getArtistWorks(artistItem, page, type) {
304 | const queryHeaders = {
305 | "user-agent":
306 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
307 | accept: "*/*",
308 | "accept-encoding": "gzip, deflate, br, zstd",
309 | origin: "https://space.bilibili.com",
310 | "sec-fetch-site": "same-site",
311 | "sec-fetch-mode": "cors",
312 | "sec-fetch-dest": "empty",
313 | referer: `https://space.bilibili.com/${artistItem.id}/video`,
314 | };
315 |
316 | await getCookie();
317 |
318 | const now = Math.round(Date.now() / 1e3);
319 | const params = {
320 | mid: artistItem.id,
321 | ps: 30,
322 | tid: 0,
323 | pn: page,
324 | web_location: 1550101,
325 | order_avoided: true,
326 | order: "pubdate",
327 | keyword: "",
328 | platform: "web",
329 | dm_img_list: "[]",
330 | dm_img_str: "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
331 | dm_cover_img_str:
332 | "QU5HTEUgKE5WSURJQSwgTlZJRElBIEdlRm9yY2UgR1RYIDE2NTAgKDB4MDAwMDFGOTEpIERpcmVjdDNEMTEgdnNfNV8wIHBzXzVfMCwgRDNEMTEpR29vZ2xlIEluYy4gKE5WSURJQS",
333 | dm_img_inter: '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
334 | wts: now.toString(),
335 | };
336 |
337 | const w_rid = await getRid(params);
338 | const res = (
339 | await axios.get("https://api.bilibili.com/x/space/wbi/arc/search", {
340 | headers: {
341 | ...queryHeaders,
342 | cookie: `buvid3=${cookie.b_3};buvid4=${cookie.b_4}`,
343 | },
344 | params: {
345 | ...params,
346 | w_rid,
347 | },
348 | })
349 | ).data;
350 | console.log(res);
351 |
352 | const resultData = res.data;
353 | const albums = resultData.list.vlist.map(formatMedia);
354 |
355 | return {
356 | isEnd: resultData.page.pn * resultData.page.ps >= resultData.page.count,
357 | data: albums,
358 | };
359 | }
360 |
361 | /** 获取音源 */
362 | async function getMediaSource(
363 | musicItem: IMusic.IMusicItem,
364 | quality: IMusic.IQualityKey
365 | ) {
366 | let cid = musicItem.cid;
367 |
368 | if (!cid) {
369 | cid = (await getCid(musicItem.bvid, musicItem.aid)).data.cid;
370 | }
371 |
372 | const _params = musicItem.bvid
373 | ? {
374 | bvid: musicItem.bvid,
375 | }
376 | : {
377 | aid: musicItem.aid,
378 | };
379 |
380 | const res = (
381 | await axios.get("https://api.bilibili.com/x/player/playurl", {
382 | headers: headers,
383 | params: { ..._params, cid: cid, fnval: 16 },
384 | })
385 | ).data;
386 | let url;
387 |
388 | if (res.data.dash) {
389 | const audios = res.data.dash.audio;
390 | audios.sort((a, b) => a.bandwidth - b.bandwidth);
391 | switch (quality) {
392 | case "low":
393 | url = audios[0].baseUrl;
394 | break;
395 | case "standard":
396 | url = audios[1].baseUrl;
397 | break;
398 | case "high":
399 | url = audios[2].baseUrl;
400 | break;
401 | case "super":
402 | url = audios[3].baseUrl;
403 | break;
404 | }
405 | } else {
406 | url = res.data.durl[0].url;
407 | }
408 |
409 | const hostUrl = url.substring(url.indexOf("/") + 2);
410 | const _headers = {
411 | "user-agent":
412 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63",
413 | accept: "*/*",
414 | host: hostUrl.substring(0, hostUrl.indexOf("/")),
415 | "accept-encoding": "gzip, deflate, br",
416 | connection: "keep-alive",
417 | referer: "https://www.bilibili.com/video/".concat(
418 | (musicItem.bvid !== null && musicItem.bvid !== undefined
419 | ? musicItem.bvid
420 | : musicItem.aid) ?? ""
421 | ),
422 | };
423 | return {
424 | url: url,
425 | headers: _headers,
426 | };
427 | }
428 |
429 | async function getTopLists() {
430 | // 入站必刷
431 | const precious = {
432 | title: "入站必刷",
433 | data: [
434 | {
435 | id: "popular/precious?page_size=100&page=1",
436 | title: "入站必刷",
437 | coverImg:
438 | "https://s1.hdslb.com/bfs/static/jinkela/popular/assets/icon_history.png",
439 | },
440 | ],
441 | };
442 |
443 | // 每周必看
444 | const weekly = {
445 | title: "每周必看",
446 | data: [],
447 | };
448 |
449 | const weeklyRes = await axios.get(
450 | "https://api.bilibili.com/x/web-interface/popular/series/list",
451 | {
452 | headers: {
453 | "user-agent":
454 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
455 | },
456 | }
457 | );
458 |
459 | weekly.data = weeklyRes.data.data.list.slice(0, 8).map((e) => ({
460 | id: `popular/series/one?number=${e.number}`,
461 | title: e.subject,
462 | description: e.name,
463 | coverImg:
464 | "https://s1.hdslb.com/bfs/static/jinkela/popular/assets/icon_weekly.png",
465 | }));
466 |
467 | // 排行榜
468 |
469 | const boardKeys = [
470 | {
471 | id: "ranking/v2?rid=0&type=all",
472 | title: "全站",
473 | },
474 | {
475 | id: "ranking/v2?rid=3&type=all",
476 | title: "音乐",
477 | },
478 | {
479 | id: "ranking/v2?rid=1&type=all",
480 | title: "动画",
481 | },
482 | {
483 | id: "ranking/v2?rid=119&type=all",
484 | title: "鬼畜",
485 | },
486 | {
487 | id: "ranking/v2?rid=168&type=all",
488 | title: "国创相关",
489 | },
490 | {
491 | id: "ranking/v2?rid=129&type=all",
492 | title: "舞蹈",
493 | },
494 | {
495 | id: "ranking/v2?rid=4&type=all",
496 | title: "游戏",
497 | },
498 | {
499 | id: "ranking/v2?rid=36&type=all",
500 | title: "知识",
501 | },
502 | {
503 | id: "ranking/v2?rid=188&type=all",
504 | title: "科技",
505 | },
506 | {
507 | id: "ranking/v2?rid=234&type=all",
508 | title: "运动",
509 | },
510 | {
511 | id: "ranking/v2?rid=223&type=all",
512 | title: "汽车",
513 | },
514 | {
515 | id: "ranking/v2?rid=160&type=all",
516 | title: "生活",
517 | },
518 | {
519 | id: "ranking/v2?rid=211&type=all",
520 | title: "美食",
521 | },
522 | {
523 | id: "ranking/v2?rid=217&type=all",
524 | title: "动物圈",
525 | },
526 | {
527 | id: "ranking/v2?rid=155&type=all",
528 | title: "时尚",
529 | },
530 | {
531 | id: "ranking/v2?rid=5&type=all",
532 | title: "娱乐",
533 | },
534 | {
535 | id: "ranking/v2?rid=181&type=all",
536 | title: "影视",
537 | },
538 | {
539 | id: "ranking/v2?rid=0&type=origin",
540 | title: "原创",
541 | },
542 | {
543 | id: "ranking/v2?rid=0&type=rookie",
544 | title: "新人",
545 | },
546 | ];
547 | const board = {
548 | title: "排行榜",
549 | data: boardKeys.map((_) => ({
550 | ..._,
551 | coverImg:
552 | "https://s1.hdslb.com/bfs/static/jinkela/popular/assets/icon_rank.png",
553 | })),
554 | };
555 | return [weekly, precious, board];
556 | }
557 |
558 | async function getTopListDetail(topListItem: IMusicSheet.IMusicSheetItem) {
559 | const res = await axios.get(
560 | `https://api.bilibili.com/x/web-interface/${topListItem.id}`,
561 | {
562 | headers: {
563 | ...headers,
564 | referer: "https://www.bilibili.com/",
565 | },
566 | }
567 | );
568 | return {
569 | ...topListItem,
570 | musicList: res.data.data.list.map(formatMedia),
571 | };
572 | }
573 |
574 | async function importMusicSheet(urlLike: string) {
575 | let id: string;
576 | if (!id) {
577 | id = urlLike.match(/^\s*(\d+)\s*$/)?.[1];
578 | }
579 | if (!id) {
580 | id = urlLike.match(/^(?:.*)fid=(\d+).*$/)?.[1];
581 | }
582 | if (!id) {
583 | id = urlLike.match(/\/playlist\/pl(\d+)/i)?.[1];
584 | }
585 | if (!id) {
586 | id = urlLike.match(/\/list\/ml(\d+)/i)?.[1];
587 | }
588 | if (!id) {
589 | return;
590 | }
591 | const musicSheet = await getFavoriteList(id);
592 | return musicSheet.map((_) => ({
593 | id: _.id,
594 | aid: _.aid,
595 | bvid: _.bvid,
596 | artwork: _.cover,
597 | title: _.title,
598 | artist: _.upper?.name,
599 | album: _.bvid ?? _.aid,
600 | duration: durationToSec(_.duration),
601 | }));
602 | }
603 |
604 | function formatComment(item) {
605 | return {
606 | id: item.rpid,
607 | // 用户名
608 | nickName: item.member?.uname,
609 | // 头像
610 | avatar: item.member?.avatar,
611 | // 评论内容
612 | comment: item.content?.message,
613 | // 点赞数
614 | like: item.like,
615 | // 评论时间
616 | createAt: item.ctime * 1000,
617 | // 地址
618 | location: item.reply_control?.location?.startsWith("IP属地:") ? item.reply_control.location.slice(5): undefined
619 | }
620 | }
621 |
622 | async function getMusicComments(musicItem) {
623 | const params = {
624 | type: 1,
625 | mode: 3,
626 | oid: musicItem.aid,
627 | plat: 1,
628 | web_location: 1315875,
629 | wts: Math.floor(Date.now() / 1000)
630 | };
631 | const w_rid = await getRid(params);
632 | const res = (await (axios.get("https://api.bilibili.com/x/v2/reply/wbi/main", {
633 | params: {
634 | ...params,
635 | w_rid
636 | }
637 | }))).data;
638 |
639 | const data = res.data.replies;
640 |
641 | const comments = [];
642 | for (let i = 0; i < data.length; ++i) {
643 | comments[i] = formatComment(data[i]);
644 | if (data[i].replies?.length) {
645 | comments[i].replies = data[i]?.replies.map(formatComment);
646 | }
647 | }
648 |
649 | return {
650 | isEnd: true,
651 | data: comments
652 | }
653 | }
654 |
655 | module.exports = {
656 | platform: "bilibili",
657 | appVersion: ">=0.0",
658 | version: "0.2.3",
659 | author: "猫头猫",
660 | cacheControl: "no-cache",
661 | srcUrl:
662 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/bilibili/index.js",
663 | primaryKey: ["id", "aid", "bvid", "cid"],
664 | hints: {
665 | importMusicSheet: [
666 | "bilibili 移动端:APP点击我的,空间,右上角分享,复制链接,浏览器打开切换桌面版网站,点击播放全部视频,复制链接",
667 | "bilibili H5/PC端:复制收藏夹URL,或者直接输入ID即可",
668 | "非公开收藏夹无法导入,编辑收藏夹改为公开即可",
669 | "导入时间和歌单大小有关,请耐心等待",
670 | ],
671 | },
672 | supportedSearchType: ["music", "album", "artist"],
673 | async search(keyword, page, type) {
674 | if (type === "album" || type === "music") {
675 | return await searchAlbum(keyword, page);
676 | }
677 | if (type === "artist") {
678 | return await searchArtist(keyword, page);
679 | }
680 | },
681 |
682 | getMediaSource,
683 | async getAlbumInfo(albumItem) {
684 | const cidRes = await getCid(albumItem.bvid, albumItem.aid);
685 |
686 | const _ref2 = cidRes?.data ?? {};
687 | const cid = _ref2.cid;
688 | const pages = _ref2.pages;
689 |
690 | let musicList;
691 | if (pages.length === 1) {
692 | musicList = [{ ...albumItem, cid: cid }];
693 | } else {
694 | musicList = pages.map(function (_) {
695 | return {
696 | ...albumItem,
697 | cid: _.cid,
698 | title: _.part,
699 | duration: durationToSec(_.duration),
700 | id: _.cid,
701 | };
702 | });
703 | }
704 | return {
705 | musicList,
706 | };
707 | },
708 |
709 | getArtistWorks,
710 | getTopLists,
711 | getTopListDetail,
712 | importMusicSheet,
713 | getMusicComments
714 | };
715 |
716 | // searchAlbum('周杰伦', 2)
717 |
718 | // {
719 | // url: 'https://xy60x29x234x168xy.mcdn.bilivideo.cn:4483/upgcxcode/01/93/935359301/935359301-1-30232.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M=&uipk=5&nbs=1&deadline=1685552083&gen=playurlv2&os=mcdn&oi=1698964255&trid=0000c4b8722cca5a4b88b6ffceabb89e7330u&mid=0&platform=pc&upsig=5317110e9e7617d7a04a47fb15f3bd87&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,mid,platform&mcdnid=1003026&bvc=vod&nettype=0&orderid=0,3&buvid=&build=0&agrr=1&bw=13831&logo=A0000001',
720 | // headers: {
721 | // 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63',
722 | // accept: '*/*',
723 | // host: 'xy60x29x234x168xy.mcdn.bilivideo.cn:4483',
724 | // 'accept-encoding': 'gzip, deflate, br',
725 | // connection: 'keep-alive',
726 | // referer: 'https://www.bilibili.com/video/BV1Pv4y1z7A1'
727 | // }
728 | // }
729 |
730 | // getMediaComment( {
731 | // id: 'BV1r7411p7R4',
732 | // aid: 86670567,
733 | // bvid: 'BV1r7411p7R4',
734 | // artist: 'zyl2012',
735 | // title: '【4K修复】周杰伦 - 青花瓷MV 2160P修复版 经典中国风',
736 | // album: 'BV1r7411p7R4',
737 | // artwork: 'http://i2.hdslb.com/bfs/archive/d6d5176730f19c23e03d3304c7bd30041024d5d8.jpg',
738 | // description: '转载自我自己修复\n' +
739 | // 'DVD修复伪1080P,修复仅为提升观感\n' +
740 | // '---------------------\n' +
741 | // '2021.4.9\n' +
742 | // '重新修复伪4K,修复仅为提升观感',
743 | // duration: 242,
744 | // date: '2020-02-04'
745 | // }).then(console.log)
746 |
747 | // getArtistWorks({
748 | // name: '不想睡觉猫头猫',
749 | // id: 12866223,
750 | // fans: 1103,
751 | // description: '不定期搞搞事情~点个关注吧\n(๑><๑)',
752 | // avatar: '//i1.hdslb.com/bfs/face/ec98b6458cdc8fdde2a72f705151b0e81cadff71.jpg',
753 | // worksNum: 20
754 | // }, 1, 'music').then(console.log);
755 |
756 | // console.log(
757 | // getRid({
758 | // mid: 12866223,
759 | // ps: 30,
760 | // tid: 0,
761 | // pn: 1,
762 | // keyword: "",
763 | // order: "pubdate",
764 | // platform: "web",
765 | // web_location: 1550101,
766 | // order_avoided: true,
767 | // dm_img_list: [],
768 | // dm_img_str: "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
769 | // dm_cover_img_str:
770 | // "QU5HTEUgKE5WSURJQSwgTlZJRElBIEdlRm9yY2UgR1RYIDE2NTAgKDB4MDAwMDFGOTEpIERpcmVjdDNEMTEgdnNfNV8wIHBzXzVfMCwgRDNEMTEpR29vZ2xlIEluYy4gKE5WSURJQS",
771 | // wts: 1701483964,
772 | // })
773 | // );
774 |
--------------------------------------------------------------------------------
/plugins/geciqianxun/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { load } from "cheerio";
3 |
4 |
5 | async function search(query, page, type){
6 | if (type !== 'lyric') {
7 | return;
8 | }
9 | const result = (await axios.get('https://so.lrcgc.com/', {
10 | params: {
11 | q: query,
12 | }
13 | })).data;
14 |
15 | const $ = load(result);
16 | const results = $('.resultWrap').children();
17 | const data = [];
18 |
19 | if (results.first().prop('tagName') === 'DL') {
20 | const title = results.first().find('dt > a');
21 | const desc = results.first().find('dd > small');
22 | const descText = desc.text().replace(/[\s|\n]/g, '').split(/[歌手:|专辑:]/).filter(it => it.trim() !== '');
23 |
24 |
25 | data.push({
26 | title: title.text(),
27 | id: title.attr('href'),
28 | artist: descText?.[0],
29 | album: descText?.[1]
30 | })
31 | }
32 |
33 | return {
34 | isEnd: true,
35 | data
36 | }
37 |
38 | }
39 |
40 | async function getLyric(musicItem) {
41 | const res = (await axios.get(musicItem.id)).data;
42 |
43 | const $ = load(res);
44 |
45 | const rawLrc = $('p#J_lyric').text().replace(/\n/g, '');
46 |
47 | return {
48 | rawLrc: rawLrc,
49 | };
50 | }
51 |
52 | module.exports = {
53 | platform: "歌词千寻",
54 | version: "0.0.0",
55 | srcUrl: 'https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciqianxun/index.js',
56 | cacheControl: "no-store",
57 | supportedSearchType: ['lyric'],
58 | search,
59 | getLyric
60 | };
61 |
--------------------------------------------------------------------------------
/plugins/geciwang/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { load } from "cheerio";
3 |
4 |
5 | async function search(query, page, type){
6 | if (type !== 'lyric') {
7 | return;
8 | }
9 | const result = (await axios.get('https://zh.followlyrics.com/search', {
10 | params: {
11 | name: query,
12 | type: 'song'
13 | }
14 | })).data;
15 |
16 | const $ = load(result);
17 | const results = $('.table.table-striped > tbody');
18 |
19 | const items = results.children('tr');
20 |
21 | const data = items.map((index, el) => {
22 | const tds = $(el).children();
23 |
24 |
25 | const title = $(tds.get(0)).text().trim();
26 | const artist = $(tds.get(1)).text().trim();
27 | const album = $(tds.get(2)).text().trim();
28 | const id = $(tds.get(3)).children('a').attr('href');
29 |
30 | return {
31 | title,
32 | artist,
33 | album,
34 | id
35 | }
36 | }).toArray();
37 |
38 | return {
39 | isEnd: true,
40 | data
41 | }
42 |
43 | }
44 |
45 | async function getLyric(musicItem) {
46 | const res = (await axios.get(musicItem.id)).data;
47 |
48 | const $ = load(res);
49 |
50 | const rawLrc = $('div#lyrics').text().replace(/\n/g, '');
51 |
52 | return {
53 | rawLrc: rawLrc,
54 | };
55 | }
56 |
57 | module.exports = {
58 | platform: "歌词网",
59 | version: "0.0.0",
60 | srcUrl: 'https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/geciwang/index.js',
61 | cacheControl: "no-store",
62 | supportedSearchType: ['lyric'],
63 | search,
64 | getLyric
65 |
66 | };
67 |
68 |
69 | // search('夜曲', 1, 'lyric').then(console.log);
70 |
71 | // getLyric({
72 | // title: '夜曲',
73 | // artist: '周杰伦',
74 | // album: '十一月的萧邦',
75 | // id: 'https://zh.followlyrics.com/lyrics/99416/ye-qu'
76 | // }).then(console.log);
77 |
--------------------------------------------------------------------------------
/plugins/kuaishou/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const pageSize = 20;
4 |
5 | function formatMusicItem(it){
6 | return {
7 | id: it.photo.id,
8 | title: it.photo.caption,
9 | artist: it.author.name,
10 | artwork: it.photo.coverUrl || it.photo.photoUrl,
11 | manifest: it.photo.manifest
12 | }
13 | }
14 |
15 | async function searchMusic(query, page) {
16 | const body = {
17 | query: `fragment photoContent on PhotoEntity {
18 | __typename
19 | id
20 | duration
21 | caption
22 | originCaption
23 | likeCount
24 | viewCount
25 | commentCount
26 | realLikeCount
27 | coverUrl
28 | photoUrl
29 | photoH265Url
30 | manifest
31 | manifestH265
32 | videoResource
33 | coverUrls {
34 | url
35 | __typename
36 | }
37 | timestamp
38 | expTag
39 | animatedCoverUrl
40 | distance
41 | videoRatio
42 | liked
43 | stereoType
44 | profileUserTopPhoto
45 | musicBlocked
46 | riskTagContent
47 | riskTagUrl
48 | }
49 |
50 | fragment recoPhotoFragment on recoPhotoEntity {
51 | __typename
52 | id
53 | duration
54 | caption
55 | originCaption
56 | likeCount
57 | viewCount
58 | commentCount
59 | realLikeCount
60 | coverUrl
61 | photoUrl
62 | photoH265Url
63 | manifest
64 | manifestH265
65 | videoResource
66 | coverUrls {
67 | url
68 | __typename
69 | }
70 | timestamp
71 | expTag
72 | animatedCoverUrl
73 | distance
74 | videoRatio
75 | liked
76 | stereoType
77 | profileUserTopPhoto
78 | musicBlocked
79 | riskTagContent
80 | riskTagUrl
81 | }
82 |
83 | fragment feedContent on Feed {
84 | type
85 | author {
86 | id
87 | name
88 | headerUrl
89 | following
90 | headerUrls {
91 | url
92 | __typename
93 | }
94 | __typename
95 | }
96 | photo {
97 | ...photoContent
98 | ...recoPhotoFragment
99 | __typename
100 | }
101 | canAddComment
102 | llsid
103 | status
104 | currentPcursor
105 | tags {
106 | type
107 | name
108 | __typename
109 | }
110 | __typename
111 | }
112 |
113 | query visionSearchPhoto($keyword: String, $pcursor: String, $searchSessionId: String, $page: String, $webPageArea: String) {
114 | visionSearchPhoto(keyword: $keyword, pcursor: $pcursor, searchSessionId: $searchSessionId, page: $page, webPageArea: $webPageArea) {
115 | result
116 | llsid
117 | webPageArea
118 | feeds {
119 | ...feedContent
120 | __typename
121 | }
122 | searchSessionId
123 | pcursor
124 | aladdinBanner {
125 | imgUrl
126 | link
127 | __typename
128 | }
129 | __typename
130 | }
131 | }`,
132 | variables: {
133 | keyword: query,
134 | page: "search",
135 | pcursor: `${page - 1}`,
136 | },
137 | };
138 | const result = (await axios.post("https://www.kuaishou.com/graphql", body, {
139 | headers: {
140 | 'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
141 | host: 'www.kuaishou.com',
142 | origin: 'https://www.kuaishou.com',
143 | referer: `https://www.kuaishou.com/search/video?searchKey=${encodeURIComponent((query))}`,
144 |
145 |
146 | }
147 | })).data.data.visionSearchPhoto;
148 | return {
149 | isEnd: !result?.pcursor || result?.pcursor === 'no_more',
150 | data: result?.feeds?.map?.(formatMusicItem)
151 | }
152 | }
153 |
154 |
155 | module.exports = {
156 | platform: "快手",
157 | version: "0.0.2",
158 | author: '猫头猫',
159 | srcUrl:
160 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/kuaishou/index.js",
161 | cacheControl: "no-cache",
162 | supportedSearchType: ["music"],
163 | async search(query, page, type) {
164 | if (type === "music") {
165 | return await searchMusic(query, page);
166 | }
167 | },
168 | async getMediaSource(musicItem, quality) {
169 | if(!musicItem.manifest) {
170 | return;
171 | }
172 | const adaptationSet = musicItem.manifest.adaptationSet;
173 | const representation = adaptationSet[0].representation;
174 | return {
175 | url: representation[0].url
176 | };
177 |
178 | },
179 | };
180 |
181 |
182 | // searchMusic('夜曲', 1).then(e => console.log(e.data[1].manifest.adaptationSet[0].representation));
183 |
--------------------------------------------------------------------------------
/plugins/maoerfm/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import he = require("he");
3 |
4 | const pageSize = 30;
5 |
6 | function validMusicFilter(_) {
7 | return `${_.pay_type}` === "0";
8 | }
9 |
10 | function formatMusicItem(_) {
11 | return {
12 | id: _.id,
13 | artwork: _.front_cover,
14 | title: _.soundstr,
15 | artist: _.username,
16 | user_id: _.user_id,
17 | duration: +(_.duration ?? 0),
18 | };
19 | }
20 |
21 | function formatAlbumItem(_) {
22 | return {
23 | id: _.id,
24 | artist: _.author,
25 | title: _.name,
26 | artwork: _.cover,
27 | description: _.abstract,
28 | };
29 | }
30 |
31 | const searchHeaders = {
32 | "user-agent":
33 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
34 | accept: "application/json",
35 | "accept-encoding": "gzip, deflate, br",
36 | referer: "https://www.missevan.com/sound/search",
37 | };
38 |
39 | async function searchMusic(query, page) {
40 | const res = (
41 | await axios.get("https://www.missevan.com/sound/getsearch", {
42 | params: {
43 | s: query,
44 | p: page,
45 | type: 3,
46 | page_size: pageSize,
47 | },
48 | headers: searchHeaders,
49 | })
50 | ).data.info;
51 |
52 | return {
53 | isEnd: res.pagination.p >= res.pagination.maxpage,
54 | data: res.Datas.filter(validMusicFilter).map(formatMusicItem),
55 | };
56 | }
57 |
58 | async function searchAlbum(query, page) {
59 | const res = (
60 | await axios.get("https://www.missevan.com/dramaapi/search", {
61 | headers: searchHeaders,
62 | params: {
63 | s: query,
64 | page,
65 | },
66 | })
67 | ).data.info;
68 |
69 | return {
70 | isEnd: res.pagination.p >= res.pagination.maxpage,
71 | data: res.Datas.filter(validMusicFilter).map(formatAlbumItem),
72 | };
73 | }
74 |
75 | async function getAlbumInfo(albumItem) {
76 | const res = (
77 | await axios.get("https://www.missevan.com/dramaapi/getdrama", {
78 | headers: {
79 | "user-agent":
80 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
81 | accept: "application/json",
82 | "accept-encoding": "gzip, deflate, br",
83 | referer: `https://www.missevan.com/mdrama/${albumItem.id}`,
84 | },
85 | params: {
86 | drama_id: albumItem.id,
87 | },
88 | })
89 | ).data;
90 |
91 | return {
92 | musicList: res.info.episodes.episode
93 | .filter(validMusicFilter)
94 | .map(_ => {
95 | const r = formatMusicItem(_);
96 | r.artwork = albumItem.artwork;
97 | return r;
98 | }),
99 | };
100 | }
101 |
102 | async function getMediaSource(musicItem, quality: IMusic.IQualityKey) {
103 | if (quality === "high" || quality === "super") {
104 | return;
105 | }
106 |
107 | const res = (
108 | await axios.get("https://www.missevan.com/sound/getsound", {
109 | headers: {
110 | "user-agent":
111 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
112 | accept: "application/json",
113 | "accept-encoding": "gzip, deflate, br",
114 | referer: `https://www.missevan.com/sound/player?id=${musicItem.id}`,
115 | },
116 | params: {
117 | soundid: musicItem.id,
118 | },
119 | })
120 | ).data.info;
121 |
122 | if (quality === "low") {
123 | return {
124 | url: res.sound.soundurl_128,
125 | };
126 | } else {
127 | return {
128 | url: res.sound.soundurl,
129 | };
130 | }
131 | }
132 |
133 | async function getRecommendSheetTags() {
134 | const res = (
135 | await axios.get(
136 | `https://www.missevan.com/malbum/recommand`,
137 | {
138 | headers: {
139 | "user-agent":
140 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
141 | accept: "application/json",
142 | "accept-encoding": "gzip, deflate, br",
143 | referer: `https://www.missevan.com`,
144 | }
145 | }
146 | )
147 | ).data.info;
148 |
149 | const data = Object.entries(res ?? {}).map(group => ({
150 | title: group[0],
151 | data: (group[1] as any).map(_ => ({
152 | id: _[0],
153 | title: _[1]
154 | }))
155 | }));
156 |
157 | return {
158 | data,
159 | };
160 | }
161 |
162 | async function getRecommendSheetsByTag(tag, page) {
163 | const res = (
164 | await axios.get(
165 | `https://www.missevan.com/explore/tagalbum`,
166 | {
167 | headers: {
168 | "user-agent":
169 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
170 | accept: "application/json",
171 | "accept-encoding": "gzip, deflate, br",
172 | referer: `https://m.missevan.com`,
173 | },
174 | params: {
175 | order: 0,
176 | tid: tag?.id || 0,
177 | p: page
178 | }
179 | }
180 | )
181 | ).data;
182 |
183 | return {
184 | isEnd: res.page >= res.maxpage,
185 | data: res.albums.map(sheet => ({
186 | id: sheet.id,
187 | title: sheet.title,
188 | artwork: sheet.front_cover,
189 | artist: sheet.username,
190 | createUserId: sheet.user_id
191 | }))
192 | }
193 | }
194 |
195 | async function getMusicSheetInfo(sheet: IMusicSheet.IMusicSheetItem, page) {
196 | const res = (
197 | await axios.get(
198 | `https://www.missevan.com/sound/soundalllist`,
199 | {
200 | headers: {
201 | "user-agent":
202 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
203 | accept: "application/json",
204 | "accept-encoding": "gzip, deflate, br",
205 | referer: `https://m.missevan.com`,
206 | },
207 | params: {
208 | albumid: sheet.id
209 | }
210 | }
211 | )
212 | ).data.info;
213 | return {
214 | isEnd: true,
215 | musicList: res.sounds.filter(validMusicFilter).map(item => ({
216 | id: item.id,
217 | title: item.soundstr,
218 | artwork: item.front_cover,
219 | url: item.soundurl,
220 | artist: item.username,
221 | }))
222 | }
223 | }
224 |
225 | module.exports = {
226 | platform: "猫耳FM",
227 | author: '猫头猫',
228 | version: "0.1.4",
229 | appVersion: ">0.1.0-alpha.0",
230 | srcUrl:
231 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/maoerfm/index.js",
232 | cacheControl: "no-cache",
233 | supportedSearchType: ["music", "album", ],
234 | async search(query, page, type) {
235 | if (type === "music") {
236 | return await searchMusic(query, page);
237 | }
238 | if (type === "album") {
239 | return await searchAlbum(query, page);
240 | }
241 | },
242 | getMediaSource,
243 | getAlbumInfo,
244 | getRecommendSheetTags,
245 | getRecommendSheetsByTag,
246 | getMusicSheetInfo
247 | };
--------------------------------------------------------------------------------
/plugins/navidrome/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import CryptoJs = require("crypto-js");
3 |
4 | const pageSize = 25;
5 |
6 |
7 | async function httpGet(
8 | urlPath: string,
9 | params?: Record
10 | ) {
11 | const userVariables = env?.getUserVariables() ?? {};
12 | // @ts-ignore
13 | let { url, username, password } = userVariables;
14 | console.log(userVariables);
15 | if (!(url && username && password)) {
16 | return null;
17 | }
18 | if (!url.startsWith("http://") && !url.startsWith("https://")) {
19 | url = `http://${url}`;
20 | }
21 |
22 | const salt = Math.random().toString(16).slice(2);
23 | const preParams = {
24 | u: username,
25 | s: salt,
26 | t: CryptoJs.MD5(`${password}${salt}`).toString(CryptoJs.enc.Hex),
27 | c: "MusicFree",
28 | v: "1.14.1",
29 | f: "json",
30 | };
31 |
32 |
33 | return (
34 | await axios.get(`${url}/rest/${urlPath}`, {
35 | params: {
36 | ...preParams,
37 | ...params
38 | },
39 | })
40 | ).data;
41 | }
42 |
43 | // httpGet('search2', {
44 | // query: 'Fl00t',
45 | // artistCount: pageSize
46 | // }).then(e => console.log(JSON.stringify(e, undefined, 4)))
47 | // httpGet('download', {
48 | // id: '9724c9544a4bd2a042c43bfe41d1aec4',
49 | // }).then(e => console.log(JSON.stringify(e, undefined, 4)))
50 |
51 |
52 | function formatMusicItem(it) {
53 | return {
54 | ...it,
55 | artwork: it.coverArt
56 | }
57 | }
58 |
59 | function formatAlbumItem(it) {
60 | return {
61 | ...it,
62 | artwork: it.coverArt
63 | }
64 | }
65 |
66 |
67 | function formatArtistItem(it) {
68 | return {
69 | ...it,
70 | avatar: it.artistImageUrl
71 | }
72 | }
73 |
74 | async function searchMusic(query, page){
75 | const data = await httpGet('search2', {
76 | query,
77 | songCount: pageSize,
78 | songOffset: (page - 1) * pageSize
79 | });
80 |
81 | const songs = data['subsonic-response'].searchResult2.song;
82 |
83 | return {
84 | isEnd: songs.length < pageSize,
85 | data: songs.map(formatMusicItem)
86 | }
87 | }
88 |
89 | async function searchAlbum(query, page){
90 | const data = await httpGet('search2', {
91 | query,
92 | albumCount: pageSize,
93 | albumOffset: (page - 1) * pageSize
94 | });
95 |
96 | const songs = data['subsonic-response'].searchResult2.album;
97 |
98 | return {
99 | isEnd: songs.length < pageSize,
100 | data: songs.map(formatAlbumItem)
101 | }
102 | }
103 |
104 | async function searchArtist(query, page){
105 | const data = await httpGet('search2', {
106 | query,
107 | songCount: pageSize,
108 | songOffset: (page - 1) * pageSize
109 | });
110 |
111 | const songs = data['subsonic-response'].searchResult2.song;
112 |
113 | return {
114 | isEnd: songs.length < pageSize,
115 | data: songs.map(formatMusicItem)
116 | }
117 | }
118 |
119 | async function getAlbumInfo(albumItem) {
120 | const data = await httpGet('getAlbum', {
121 | id: albumItem.id
122 | });
123 | return {
124 | isEnd: true,
125 | data: data['subsonic-response'].album.song.map(formatMusicItem)
126 | };
127 |
128 | }
129 |
130 |
131 | module.exports = {
132 | platform: "Navidrome",
133 | version: "0.0.0",
134 | author: '猫头猫',
135 | appVersion: ">0.1.0-alpha.0",
136 | srcUrl:
137 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/navidrome/index.js",
138 | cacheControl: "no-cache",
139 | userVariables: [
140 | {
141 | key: "url",
142 | name: "服务器地址",
143 | },
144 | {
145 | key: "username",
146 | name: "用户名",
147 | },
148 | {
149 | key: "password",
150 | name: "密码",
151 | },
152 | ],
153 | supportedSearchType: ["music", "album"],
154 | async search(query, page, type) {
155 | if (type === "music") {
156 | return await searchMusic(query, page);
157 | }
158 | if (type === "album") {
159 | return await searchAlbum(query, page);
160 | }
161 | },
162 |
163 | async getMediaSource(musicItem){
164 | const userVariables = env?.getUserVariables() ?? {};
165 | // @ts-ignore
166 | let { url, username, password } = userVariables;
167 | if (!(url && username && password)) {
168 | return null;
169 | }
170 | if (!url.startsWith("http://") && !url.startsWith("https://")) {
171 | url = `http://${url}`;
172 | }
173 |
174 | const salt = Math.random().toString(16).slice(2);
175 |
176 | const urlObj = new URL(`${url}/rest/stream`);
177 | urlObj.searchParams.append('u', username);
178 | urlObj.searchParams.append('s', salt);
179 | urlObj.searchParams.append('t', CryptoJs.MD5(`${password}${salt}`).toString(CryptoJs.enc.Hex));
180 | urlObj.searchParams.append('c', 'MusicFree');
181 | urlObj.searchParams.append('v', '1.14.1');
182 | urlObj.searchParams.append('f', 'json');
183 | urlObj.searchParams.append('id', musicItem.id);
184 |
185 | return {
186 | url: urlObj.toString()
187 | }
188 | }
189 | };
190 |
--------------------------------------------------------------------------------
/plugins/suno/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const headers = {
4 | "user-agent":
5 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
6 | host: "studio-api.suno.ai"
7 | }
8 |
9 | async function getTopLists() {
10 | return [
11 | {
12 | title: "趋势榜",
13 | data: [
14 | {
15 | id: "1190bf92-10dc-4ce5-968a-7a377f37f984",
16 | title: "趋势榜 - 最近一天",
17 | },
18 | {
19 | id: "08a079b2-a63b-4f9c-9f29-de3c1864ddef",
20 | title: "趋势榜 - 最近一周",
21 | },
22 | {
23 | id: "845539aa-2a39-4cf5-b4ae-16d3fe159a77",
24 | title: "趋势榜 - 最近一月",
25 | },
26 | {
27 | id: "6943c7ee-cbc5-4f72-bc4e-f3371a8be9d5",
28 | title: "趋势榜 - 全部时间",
29 | },
30 | ],
31 | },
32 | {
33 | title: "最新榜",
34 | data: [
35 | {
36 | id: "cc14084a-2622-4c4b-8258-1f6b4b4f54b3",
37 | title: "最新榜",
38 | },
39 | ],
40 | },
41 | {
42 | title: "其他类别",
43 | data: [
44 | {
45 | id: "1ac7823f-8faf-474f-b14c-e4f7c7bb373f",
46 | title: "动物开会",
47 | },
48 | {
49 | id: '6713d315-3541-460d-8788-162cce241336',
50 | title: 'Lo-Fi'
51 | }
52 | ],
53 | },
54 | ];
55 | }
56 |
57 |
58 | async function getTopListDetail(topListItem) {
59 | const result = (await axios.get(`https://studio-api.suno.ai/api/playlist/${topListItem.id}/?page=0`, {
60 | headers
61 | })).data;
62 |
63 | return {
64 | isEnd: true,
65 | musicList: result.playlist_clips.map(it => {
66 | const clip = it.clip;
67 |
68 | return {
69 | id: clip.id,
70 | url: clip.audio_url,
71 | artwork: clip.image_large_url || clip.image_url,
72 | duration: clip.metadata?.duration,
73 | title: clip.title,
74 | artist: clip.display_name,
75 | userId: clip.user_id,
76 | rawLrc: clip.metadata?.prompt
77 | }
78 | })
79 | }
80 | }
81 |
82 | async function getLyric(musicItem) {
83 | return {
84 | rawLrc: musicItem.rawLrc
85 | }
86 | }
87 |
88 | module.exports = {
89 | platform: "suno",
90 | version: "0.0.0",
91 | srcUrl:
92 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/suno/index.js",
93 | cacheControl: "no-cache",
94 | getTopLists,
95 | getTopListDetail,
96 | getLyric
97 | };
98 |
99 |
100 | getTopListDetail( {
101 | id: "1ac7823f-8faf-474f-b14c-e4f7c7bb373f",
102 | title: "最新榜",
103 | }).then(e => console.log(e.musicList[0]))
--------------------------------------------------------------------------------
/plugins/udio/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const headers = {
4 | "user-agent":
5 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61",
6 | };
7 |
8 | function formatMusicItem(it) {
9 | return {
10 | id: it.id,
11 | artist: it.artist,
12 | title: it.title,
13 | createdAt: it.created_at,
14 | artwork: it.image_path,
15 | url: it.song_path,
16 | duration: it.duration,
17 | mv: it.video_path,
18 | rawLrc: it.lyrics,
19 | };
20 | }
21 |
22 | async function searchMusic(query, page) {
23 | const pageSize = 30;
24 | const data = `{"searchQuery":{"sort":"plays","searchTerm":"${query}"},"pageParam":${
25 | page - 1
26 | },"pageSize":${pageSize},"trendingId":"93de406e-bdc1-40a6-befd-90637a362158"}`;
27 |
28 | const config = {
29 | method: "post",
30 | url: "https://www.udio.com/api/songs/search",
31 | headers: {
32 | "user-agent":
33 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.0.0",
34 | host: "www.udio.com",
35 | },
36 | data: data,
37 | };
38 |
39 | const result = (await axios(config)).data.data;
40 |
41 | return {
42 | isEnd: result.length < pageSize,
43 | data: result.map(formatMusicItem),
44 | };
45 | }
46 |
47 | async function search(query, page, type) {
48 | if (type === "music") {
49 | return await searchMusic(query, page);
50 | }
51 | }
52 |
53 | async function getMediaSource(musicItem, quality) {
54 | if (quality !== "standard") {
55 | return;
56 | }
57 |
58 | return musicItem.url;
59 | }
60 |
61 | async function getTopLists() {
62 | return [
63 | {
64 | title: "趋势榜",
65 | data: [
66 | {
67 | id: "today",
68 | maxAgeInHours: 24,
69 | type: "search",
70 | title: "趋势榜 - 最近一天",
71 | },
72 | {
73 | id: "1week",
74 | maxAgeInHours: 168,
75 | type: "search",
76 | title: "趋势榜 - 最近一周",
77 | },
78 | {
79 | id: "1month",
80 | maxAgeInHours: 720,
81 | type: "search",
82 | title: "趋势榜 - 最近一月",
83 | },
84 | {
85 | id: "alltime",
86 | type: "search",
87 | title: "趋势榜 - 全部时间",
88 | },
89 | ],
90 | },
91 | {
92 | title: "流派榜单",
93 | data: [
94 | {
95 | id: "89f0089f-1bfe-4713-8070-5830a6161afb",
96 | type: "playlist",
97 | title: "爵士",
98 | },
99 | {
100 | id: "935deb12-dc32-4005-a1fe-3c00c284ca52",
101 | type: "playlist",
102 | title: "乡村",
103 | },
104 | {
105 | id: "6028ad08-68cb-406d-aa35-a4917b6467d6",
106 | type: "playlist",
107 | title: "流行",
108 | },
109 | {
110 | id: "d033aa6e-655e-45d0-8138-dc9a0dc6b3a6",
111 | type: "playlist",
112 | title: "摇滚",
113 | },
114 | ],
115 | },
116 | ];
117 | }
118 |
119 | async function getTopListDetail(topListItem) {
120 | if (topListItem.type === "playlist") {
121 | const res = (
122 | await axios.get(
123 | `https://www.udio.com/api/playlists?id=${topListItem.id}`,
124 | {
125 | headers,
126 | }
127 | )
128 | ).data;
129 |
130 | const songList = res.playlists[0].song_list.join(",")
131 | const songs = (
132 | await axios.get(`https://www.udio.com/api/songs?songIds=${songList}`, {
133 | headers,
134 | })
135 | ).data.songs;
136 |
137 | return {
138 | isEnd: true,
139 | musicList: songs.map(formatMusicItem),
140 | };
141 | } else if (topListItem.type === "search") {
142 | const pageSize = 30;
143 | const data = `{"searchQuery":{"sort":"plays","searchTerm":""${
144 | topListItem.maxAgeInHours
145 | ? `,"maxAgeInHours": ${topListItem.maxAgeInHours}`
146 | : ""
147 | }},"pageParam":0,"pageSize":${pageSize}}`;
148 |
149 | const config = {
150 | method: "post",
151 | url: "https://www.udio.com/api/songs/search",
152 | headers: {
153 | "user-agent":
154 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.0.0",
155 | host: "www.udio.com",
156 | },
157 | data: data,
158 | };
159 |
160 | const songs = (await axios(config)).data.data;
161 |
162 | return {
163 | isEnd: true,
164 | musicList: songs.map(formatMusicItem),
165 | };
166 | }
167 | }
168 |
169 | async function getLyric(musicItem) {
170 | return {
171 | rawLrc: musicItem.rawLrc
172 | }
173 | }
174 |
175 | module.exports = {
176 | platform: "udio",
177 | author: "猫头猫",
178 | version: "0.0.0",
179 | supportedSearchType: ["music"],
180 | srcUrl:
181 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/udio/index.js",
182 | cacheControl: "no-cache",
183 | search,
184 | getMediaSource,
185 | getTopListDetail,
186 | getTopLists,
187 | getLyric
188 | };
189 |
--------------------------------------------------------------------------------
/plugins/webdav/index.ts:
--------------------------------------------------------------------------------
1 | import { AuthType, FileStat, createClient } from "webdav";
2 |
3 | interface ICachedData {
4 | url?: string;
5 | username?: string;
6 | password?: string;
7 | searchPath?: string;
8 | searchPathList?: string[];
9 | cacheFileList?: FileStat[];
10 | }
11 | let cachedData: ICachedData = {};
12 |
13 | function getClient() {
14 | const { url, username, password, searchPath } =
15 | env?.getUserVariables?.() ?? {};
16 | if (!(url && username && password)) {
17 | return null;
18 | }
19 |
20 | if (
21 | !(
22 | cachedData.url === url &&
23 | cachedData.username === username &&
24 | cachedData.password === password &&
25 | cachedData.searchPath === searchPath
26 | )
27 | ) {
28 | cachedData.url = url;
29 | cachedData.username = username;
30 | cachedData.password = password;
31 | cachedData.searchPath = searchPath;
32 | cachedData.searchPathList = searchPath?.split?.(",");
33 | cachedData.cacheFileList = null;
34 | }
35 |
36 | return createClient(url, {
37 | authType: AuthType.Password,
38 | username,
39 | password,
40 | });
41 | }
42 |
43 | async function searchMusic(query: string) {
44 | const client = getClient();
45 | if (!cachedData.cacheFileList) {
46 | const searchPathList = cachedData.searchPathList?.length
47 | ? cachedData.searchPathList
48 | : ["/"];
49 | let result: FileStat[] = [];
50 |
51 | for (let search of searchPathList) {
52 | try {
53 | const fileItems = (
54 | (await client.getDirectoryContents(search)) as FileStat[]
55 | ).filter((it) => it.type === "file" && it.mime.startsWith("audio"));
56 | result = [...result, ...fileItems];
57 | } catch {}
58 | }
59 | cachedData.cacheFileList = result;
60 | }
61 |
62 | return {
63 | isEnd: true,
64 | data: (cachedData.cacheFileList ?? [])
65 | .filter((it) => it.basename.includes(query))
66 | .map((it) => ({
67 | title: it.basename,
68 | id: it.filename,
69 | artist: "未知作者",
70 | album: "未知专辑",
71 | })),
72 | };
73 | }
74 |
75 | async function getTopLists() {
76 | getClient();
77 | const data = {
78 | title: "全部歌曲",
79 | data: (cachedData.searchPathList || []).map((it) => ({
80 | title: it,
81 | id: it,
82 | })),
83 | };
84 | return [data];
85 | }
86 |
87 | async function getTopListDetail(topListItem: IMusicSheet.IMusicSheetItem) {
88 | const client = getClient();
89 | const fileItems = (
90 | (await client.getDirectoryContents(topListItem.id)) as FileStat[]
91 | ).filter((it) => it.type === "file" && it.mime.startsWith("audio"));
92 |
93 | return {
94 | musicList: fileItems.map((it) => ({
95 | title: it.basename,
96 | id: it.filename,
97 | artist: "未知作者",
98 | album: "未知专辑",
99 | })),
100 | };
101 | }
102 |
103 | module.exports = {
104 | platform: "WebDAV",
105 | author: "猫头猫",
106 | description: "使用此插件前先配置用户变量",
107 | userVariables: [
108 | {
109 | key: "url",
110 | name: "WebDAV地址",
111 | },
112 | {
113 | key: "username",
114 | name: "用户名",
115 | },
116 | {
117 | key: "password",
118 | name: "密码",
119 | type: "password",
120 | },
121 | {
122 | key: "searchPath",
123 | name: "存放歌曲的路径",
124 | },
125 | ],
126 | version: "0.0.2",
127 | supportedSearchType: ["music"],
128 | srcUrl:
129 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/webdav/index.js",
130 | cacheControl: "no-cache",
131 | search(query, page, type) {
132 | if (type === "music") {
133 | return searchMusic(query);
134 | }
135 | },
136 | getTopLists,
137 | getTopListDetail,
138 | getMediaSource(musicItem) {
139 | const client = getClient();
140 | return {
141 | url: client.getFileDownloadLink(musicItem.id),
142 | };
143 | },
144 | };
145 |
--------------------------------------------------------------------------------
/plugins/yinyuetai/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const pageNum = 20;
4 |
5 | function formatMusicItem(item) {
6 | return {
7 | id: item.id,
8 | title: item.title,
9 | artist:
10 | item?.allArtistNames ||
11 | item.artists?.map?.((s) => s.name).join(", ") ||
12 | item.user?.niceName,
13 | artwork: item?.headImg,
14 | urls: item?.fullClip?.urls,
15 | };
16 | }
17 |
18 | function formatArtistItem(item) {
19 | return {
20 | id: item.id,
21 | name: item.name,
22 | avatar: item.headImg,
23 | };
24 | }
25 |
26 | let lastQuery;
27 | let lastMusicId;
28 | async function searchMusic(query, page) {
29 | // 新的搜索
30 | if (query !== lastQuery || page === 1) {
31 | lastMusicId = 0;
32 | }
33 | lastQuery = query;
34 |
35 | let data = JSON.stringify({
36 | searchType: "MV",
37 | key: query,
38 | sinceId: lastMusicId,
39 | size: pageNum,
40 | requestTagRows: [
41 | {
42 | key: "sortType",
43 | chosenTags: ["HOTTEST"],
44 | },
45 | {
46 | key: "source",
47 | chosenTags: ["-1"],
48 | },
49 | {
50 | key: "duration",
51 | chosenTags: ["-1"],
52 | },
53 | ],
54 | });
55 |
56 | let config = {
57 | method: "post",
58 | maxBodyLength: Infinity,
59 | url: "https://search-api.yinyuetai.com/search/get_search_result.json",
60 | headers: {
61 | referrer: "https://www.yinyuetai.com/",
62 | accept: "application/json",
63 | "content-type": "application/json",
64 | wua: "YYT/1.0.0 (WEB;web;11;zh-CN;kADiV2jNJFy2ryvuyB5Ne)",
65 | },
66 | data: data,
67 | };
68 |
69 | const response = (await axios.request(config)).data.data;
70 |
71 | lastMusicId = response[response.length - 1].id;
72 |
73 | return {
74 | isEnd: pageNum > response.length,
75 | data: response.map(formatMusicItem),
76 | };
77 | }
78 |
79 | // async function searchArtist(query, page) {
80 | // let data = JSON.stringify({
81 | // searchType: "ARTIST",
82 | // key: query,
83 | // sinceId: 0,
84 | // size: 2 * pageNum,
85 | // });
86 |
87 | // let config = {
88 | // method: "post",
89 | // maxBodyLength: Infinity,
90 | // url: "https://search-api.yinyuetai.com/search/get_search_result.json",
91 | // headers: {
92 | // referrer: "https://www.yinyuetai.com/",
93 | // accept: "application/json",
94 | // "content-type": "application/json",
95 | // wua: "YYT/1.0.0 (WEB;web;11;zh-CN;kADiV2jNJFy2ryvuyB5Ne)",
96 | // },
97 | // data: data,
98 | // };
99 |
100 | // const response = (await axios.request(config)).data.data;
101 |
102 | // return {
103 | // isEnd: true,
104 | // data: response.map(formatArtistItem),
105 | // };
106 | // }
107 |
108 | async function search(query, page, type) {
109 | if (type === "music") {
110 | return await searchMusic(query, page);
111 | }
112 | // else if (type === "artist") {
113 | // return await searchArtist(query, page);
114 | // }
115 | }
116 |
117 | async function getMediaSource(musicItem, quality) {
118 | let url;
119 | if (quality === "standard") {
120 | url = musicItem.urls.find((it) => it.streamType === 5).url;
121 | } else if (quality === "high") {
122 | url = musicItem.urls.find((it) => it.streamType === 1).url;
123 | }
124 |
125 | return {
126 | url,
127 | };
128 | }
129 |
130 | // let lastArtistId;
131 | // let lastArtistSinceId = 0;
132 | // let cacheExtendId;
133 | // async function getArtistWorks(artistItem, page, type) {
134 | // if (type === "music") {
135 | // let sinceId =
136 | // page === 1 || artistItem.id !== lastArtistId ? 0 : lastArtistSinceId;
137 | // lastArtistId = artistItem.id;
138 |
139 | // if (sinceId === 0) {
140 | // const personBaseInfo = (
141 | // await axios.get("https://person-api.yinyuetai.com/person/getBase", {
142 | // params: {
143 | // id: artistItem.id,
144 | // },
145 | // headers: {
146 | // referrer: "https://www.yinyuetai.com/",
147 | // accept: "application/json",
148 | // "content-type": "application/json",
149 | // wua: "YYT/1.0.0 (WEB;web;11;zh-CN;kADiV2jNJFy2ryvuyB5Ne)",
150 | // },
151 | // })
152 | // ).data;
153 |
154 |
155 | // console.log(personBaseInfo)
156 | // cacheExtendId = personBaseInfo.extendId;
157 | // }
158 |
159 | // const medias = (
160 | // await axios.get("https://video-api.yinyuetai.com/video/listByArtist", {
161 | // params: {
162 | // artistId: cacheExtendId,
163 | // size: pageNum,
164 | // sinceId,
165 | // },
166 | // })
167 | // ).data.data;
168 |
169 | // lastArtistSinceId = medias[medias.length - 1].id;
170 |
171 | // return {
172 | // isEnd: medias.length < pageNum,
173 | // data: medias.map(formatMusicItem),
174 | // };
175 | // }
176 | // }
177 |
178 | module.exports = {
179 | platform: "音悦台",
180 | author: '猫头猫',
181 | version: "0.0.1",
182 | supportedSearchType: ["music"],
183 | srcUrl:
184 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/yinyuetai/index.js",
185 | cacheControl: "no-cache",
186 | search,
187 | getMediaSource,
188 | // getArtistWorks,
189 | };
190 |
--------------------------------------------------------------------------------
/plugins/youtube/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | // import { HttpsProxyAgent } from "https-proxy-agent";
3 |
4 | // axios.defaults.httpsAgent = new HttpsProxyAgent("http://127.0.0.1:10809");
5 |
6 | function formatMusicItem(item) {
7 | return {
8 | id: item.videoId,
9 | title: item.title.runs?.[0]?.text,
10 | artist: item.ownerText.runs?.[0]?.text,
11 | artwork: item?.thumbnail?.thumbnails?.[0]?.url,
12 | };
13 | }
14 |
15 | let lastQuery;
16 | let musicContinToken;
17 |
18 | async function searchMusic(query, page) {
19 | // 新的搜索
20 | if (query !== lastQuery || page === 1) {
21 | musicContinToken = undefined;
22 | }
23 | lastQuery = query;
24 |
25 | let data = JSON.stringify({
26 | context: {
27 | client: {
28 | hl: "zh-CN",
29 | gl: "US",
30 | deviceMake: "",
31 | deviceModel: "",
32 | userAgent:
33 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0,gzip(gfe)",
34 | clientName: "WEB",
35 | clientVersion: "2.20231121.08.00",
36 | osName: "Windows",
37 | osVersion: "10.0",
38 | platform: "DESKTOP",
39 | userInterfaceTheme: "USER_INTERFACE_THEME_LIGHT",
40 | browserName: "Edge Chromium",
41 | browserVersion: "119.0.0.0",
42 | acceptHeader:
43 | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
44 | screenWidthPoints: 1358,
45 | screenHeightPoints: 1012,
46 | screenPixelDensity: 1,
47 | screenDensityFloat: 1.2395833730697632,
48 | utcOffsetMinutes: 480,
49 | memoryTotalKbytes: "8000000",
50 | mainAppWebInfo: {
51 | pwaInstallabilityStatus: "PWA_INSTALLABILITY_STATUS_UNKNOWN",
52 | webDisplayMode: "WEB_DISPLAY_MODE_BROWSER",
53 | isWebNativeShareAvailable: true,
54 | },
55 | timeZone: "Asia/Shanghai",
56 | },
57 | user: {
58 | lockedSafetyMode: false,
59 | },
60 | request: {
61 | useSsl: true,
62 | internalExperimentFlags: [],
63 | },
64 | },
65 | query: musicContinToken ? undefined : query,
66 | continuation: musicContinToken || undefined,
67 | });
68 |
69 | var config = {
70 | method: "post",
71 | url: "https://www.youtube.com/youtubei/v1/search?prettyPrint=false",
72 | headers: {
73 | "Content-Type": "text/plain",
74 | },
75 | data: data,
76 | };
77 |
78 | const response = (await axios(config)).data;
79 |
80 | const contents =
81 | response.contents.twoColumnSearchResultsRenderer.primaryContents
82 | .sectionListRenderer.contents;
83 |
84 | const isEndItem = contents.find(
85 | (it) =>
86 | it.continuationItemRenderer?.continuationEndpoint?.continuationCommand
87 | ?.request === "CONTINUATION_REQUEST_TYPE_SEARCH"
88 | );
89 | if (isEndItem) {
90 | musicContinToken =
91 | isEndItem.continuationItemRenderer.continuationEndpoint
92 | .continuationCommand.token;
93 | }
94 |
95 | const musicData = contents.find((it) => it.itemSectionRenderer)
96 | .itemSectionRenderer.contents;
97 |
98 | let resultMusicData = [];
99 | for (let i = 0; i < musicData.length; ++i) {
100 | if (musicData[i].videoRenderer) {
101 | resultMusicData.push(formatMusicItem(musicData[i].videoRenderer));
102 | }
103 | }
104 |
105 | return {
106 | isEnd: !isEndItem,
107 | data: resultMusicData,
108 | };
109 | }
110 |
111 | async function search(query, page, type) {
112 | if (type === "music") {
113 | return await searchMusic(query, page);
114 | }
115 | }
116 |
117 | let cacheMediaSource = {
118 | id: null,
119 | urls: {},
120 | };
121 |
122 | function getQuality(label) {
123 | if (label === "small") {
124 | return "standard";
125 | } else if (label === "tiny") {
126 | return "low";
127 | } else if (label === "medium") {
128 | return "high";
129 | } else if (label === "large") {
130 | return "super";
131 | } else {
132 | return "standard";
133 | }
134 | }
135 |
136 | async function getMediaSource(musicItem, quality) {
137 | if (musicItem.id === cacheMediaSource.id) {
138 | return {
139 | url: cacheMediaSource.urls[quality],
140 | };
141 | }
142 |
143 | cacheMediaSource = {
144 | id: null,
145 | urls: {},
146 | };
147 |
148 | const data = {
149 | context: {
150 | client: {
151 | screenWidthPoints: 689,
152 | screenHeightPoints: 963,
153 | screenPixelDensity: 1,
154 | utcOffsetMinutes: 120,
155 | hl: "en",
156 | gl: "GB",
157 | remoteHost: "1.1.1.1",
158 | deviceMake: "",
159 | deviceModel: "",
160 | userAgent:
161 | "com.google.android.apps.youtube.music/6.14.50 (Linux; U; Android 13; GB) gzip",
162 | clientName: "ANDROID_MUSIC",
163 | clientVersion: "6.14.50",
164 | osName: "Android",
165 | osVersion: "13",
166 | originalUrl:
167 | "https://www.youtube.com/tv?is_account_switch=1&hrld=1&fltor=1",
168 | theme: "CLASSIC",
169 | platform: "MOBILE",
170 | clientFormFactor: "UNKNOWN_FORM_FACTOR",
171 | webpSupport: false,
172 | timeZone: "Europe/Amsterdam",
173 | acceptHeader:
174 | "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
175 | },
176 | user: { enableSafetyMode: false },
177 | request: {
178 | internalExperimentFlags: [],
179 | consistencyTokenJars: [],
180 | },
181 | },
182 | contentCheckOk: true,
183 | racyCheckOk: true,
184 | video_id: musicItem.id,
185 | };
186 |
187 | var config = {
188 | method: "post",
189 | url: "https://www.youtube.com/youtubei/v1/player?prettyPrint=false",
190 | headers: {
191 | "Content-Type": "application/json",
192 | },
193 | data: JSON.stringify(data),
194 | };
195 |
196 | const result = (await axios(config)).data;
197 | const formats = result.streamingData.formats ?? [];
198 | const adaptiveFormats = result.streamingData.adaptiveFormats ?? [];
199 |
200 | [...formats, ...adaptiveFormats].forEach((it) => {
201 | const q = getQuality(it.quality);
202 | if (q && it.url && !cacheMediaSource.urls[q]) {
203 | cacheMediaSource.urls[q] = it.url;
204 | }
205 | });
206 |
207 | return {
208 | url: cacheMediaSource.urls[quality],
209 | };
210 | }
211 |
212 | module.exports = {
213 | platform: "Youtube",
214 | author: '猫头猫',
215 | version: "0.0.1",
216 | supportedSearchType: ["music"],
217 | srcUrl:
218 | "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/youtube/index.js",
219 | cacheControl: "no-cache",
220 | search,
221 | getMediaSource,
222 | };
223 |
224 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## MusicFree插件
2 |
3 | 使用方式请参考:[这里](https://mp.weixin.qq.com/s?__biz=MzkxOTM5MDI4MA==&mid=2247483875&idx=1&sn=aedf8bb909540634d927de7fd2b4b8b1&chksm=c1a390c4f6d419d233908bb781d418c6b9fd2ca82e9e93291e7c93b8ead3c50ca5ae39668212#rd)
4 |
5 | ---
6 | 本仓库的插件只是示例,来自于网络公开的接口,**并过滤掉所有VIP/收费/试听歌曲**。仅供学习参考使用,请不要用于任何商业用途,合理合法使用。
7 |
8 | ---
9 |
10 | 收到了告知函,因此本示例插件仓库不再提供国内音乐厂商的源。
11 | https://mp.weixin.qq.com/s/NXww80a6YX4rbjwmepL48w
12 |
--------------------------------------------------------------------------------
/scripts/generate.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs/promises');
2 | const path = require('path');
3 | const rimraf = require('rimraf')
4 |
5 | const basePath = path.resolve(__dirname, '../dist');
6 |
7 | async function run() {
8 | console.log('生成json文件...');
9 | const pluginPath = path.resolve(basePath, '_plugins');
10 | await rimraf(pluginPath);
11 | await fs.mkdir(pluginPath);
12 | const bundledPlugins = await fs.readdir(basePath);
13 | const output = {
14 | desc: "此链接为 MusicFree 插件,插件开发及使用方式参考 https://musicfree.upup.fun",
15 | plugins: []
16 | };
17 | await Promise.all(bundledPlugins.map(async (bundleFolder) => {
18 | if (!bundleFolder.startsWith('_')) {
19 | try {
20 | const targetPluginPath = path.resolve(basePath, bundleFolder, 'index.js');
21 | await fs.stat(targetPluginPath);
22 | const origin = await fs.readFile(targetPluginPath, 'utf-8');
23 | const mexports = origin.match(/module.exports\s*=\s*([\s\S]*)$/)[1];
24 | const platform = mexports.match(/platform:\s*['"`](.*)['"`]/)[1]
25 | const version = mexports.match(/version:\s*['"`](.*)['"`]/)?.[1]
26 | const srcUrl = mexports.match(/srcUrl:\s*['"`](.*)['"`]/)?.[1]
27 |
28 | output.plugins.push({
29 | name: platform,
30 | url: srcUrl,
31 | version: version
32 | })
33 | } catch(e) {
34 | console.warn('异常:', e);
35 | }
36 | }
37 | }))
38 |
39 | await fs.writeFile(path.resolve(pluginPath, 'plugins.json'), JSON.stringify(output));
40 | await fs.copyFile(path.resolve(pluginPath, 'plugins.json'), path.resolve(__dirname, '../plugins.json'))
41 | console.log('done√');
42 |
43 | }
44 |
45 |
46 | run();
47 |
--------------------------------------------------------------------------------
/test/geciqianxun.ts:
--------------------------------------------------------------------------------
1 | const plugin = require('../plugins/geciqianxun');
2 |
3 |
4 | async function run() {
5 | // 测试搜索歌词
6 | const searchResult = await plugin.search('七里香', 1, 'lyric');
7 | console.log(searchResult);
8 |
9 | if (searchResult.data.length > 0) {
10 | const musicItem = searchResult.data[0];
11 | const lyricResult = await plugin.getLyric(musicItem);
12 | console.log(lyricResult);
13 | }
14 | }
15 |
16 | run();
17 |
18 |
--------------------------------------------------------------------------------
/test/geciwang.ts:
--------------------------------------------------------------------------------
1 | const plugin = require('../plugins/geciwang');
2 |
3 |
4 | async function run() {
5 | // 测试搜索歌词
6 | const searchResult = await plugin.search('七里香', 1, 'lyric');
7 | console.log(searchResult);
8 |
9 | if (searchResult.data.length > 0) {
10 | const musicItem = searchResult.data[0];
11 | const lyricResult = await plugin.getLyric(musicItem);
12 | console.log(lyricResult);
13 | }
14 | }
15 |
16 | run();
17 |
18 |
--------------------------------------------------------------------------------
/test/kuaishou.ts:
--------------------------------------------------------------------------------
1 | const plugin = require('../plugins/kuaishou');
2 |
3 |
4 | async function run() {
5 | // 搜索歌曲
6 | const res = await plugin.search('七里香', 1,'music');
7 |
8 | console.log(res);
9 |
10 | if (res.data.length > 0) {
11 | const musicItem = res.data[0];
12 | const sourceResult = await plugin.getMediaSource(musicItem, 'standard');
13 | console.log(sourceResult);
14 | }
15 | }
16 |
17 | run();
18 |
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noImplicitAny": false,
4 | "noEmitOnError": true,
5 | "removeComments": true,
6 | "allowJs": true,
7 | "sourceMap": false,
8 | "module": "CommonJS",
9 | "target": "ES2017",
10 | "outDir": "dist",
11 | "baseUrl": "./",
12 | },
13 | "include": [
14 | "./types/*",
15 | "./plugins/*/index.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/types/global.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | var env: {
3 | getUserVariables?: () => Record
4 | os: string;
5 | appVersion: string;
6 | }
7 | }
8 |
9 | export {};
--------------------------------------------------------------------------------
/types/mediaType.d.ts:
--------------------------------------------------------------------------------
1 | /** 音乐 */
2 | declare namespace IMusic {
3 | interface IMusicItem {
4 | /** 插件名 */
5 | platform?: string;
6 | /** 唯一id */
7 | id: string | number;
8 | /** 作者 */
9 | artist: string;
10 | /** 标题 */
11 | title: string;
12 | /** 时长(s) */
13 | duration: number;
14 | /** 专辑名 */
15 | album: string;
16 | /** 专辑封面图 */
17 | artwork: string;
18 | /** 默认音源 */
19 | url?: string;
20 | /** 歌词URL */
21 | lrc?: string;
22 | /** 歌词 */
23 | rawLrc?: string;
24 | /** 音质信息 */
25 | qualities?: IMusic.IQuality;
26 | // TODO: 在这里补充完整类型
27 | [k: string | number]: any;
28 | }
29 | }
30 |
31 | /** 专辑 */
32 | declare namespace IAlbum {
33 | interface IAlbumItem {
34 | /** 插件名 */
35 | platform?: string;
36 | /** 唯一id */
37 | id: string | number;
38 | /** 作者 */
39 | artist: string;
40 | /** 标题 */
41 | title: string;
42 | /** 专辑封面图 */
43 | artwork: string;
44 | /** 日期YYYY-MM-DD */
45 | date: string;
46 | /** 描述文本 */
47 | description?: string;
48 | // TODO: 在这里补充完整类型
49 | [k: string | number]: any;
50 | }
51 |
52 | interface IAlbumInfoResult {
53 | isEnd?: boolean;
54 | albumItem?: Partial;
55 | musicList?: IMusic.IMusicItem[];
56 | }
57 | }
58 |
59 | /** 作者 */
60 | declare namespace IArtist {
61 | interface IArtistItem {
62 | /** 插件名 */
63 | platform?: string;
64 | /** 唯一id */
65 | id: string | number;
66 | /** 姓名 */
67 | name: string;
68 | /** 粉丝数 */
69 | fans?: number;
70 | /** 简介 */
71 | description?: string;
72 | /** 头像 */
73 | avatar?: string;
74 | /** 作品数目 */
75 | worksNum?: number;
76 | // TODO: 在这里补充完整类型
77 | [k: string | number]: any;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/types/plugin.d.ts:
--------------------------------------------------------------------------------
1 | type WithMusicList = T & {
2 | musicList?: IMusic.IMusicItem[];
3 | };
4 |
5 | declare namespace ICommon {
6 | export type SupportMediaType = "music" | "album" | "artist" | 'sheet';
7 | export type SupportMediaItemBase = {
8 | music: Partial;
9 | album: Partial;
10 | artist: Partial;
11 | sheet: Partial;
12 |
13 | };
14 | export type IMediaBase = {
15 | id: string;
16 | platform: string;
17 | [k: string]: any;
18 | };
19 | }
20 |
21 | declare namespace IMusic {
22 | type IQualityKey = "low" | "standard" | "high" | "super";
23 | type IQuality = Record<
24 | IQualityKey,
25 | {
26 | url?: string;
27 | size?: string | number;
28 | }
29 | >;
30 | }
31 |
32 | declare namespace ILyric {
33 | interface ILyricSource {
34 | lrc?: string;
35 | rawLrc?: string;
36 | }
37 | }
38 |
39 | declare namespace IMusicSheet {
40 | interface IMusicSheetItem {
41 | /** 封面图 */
42 | coverImg?: string;
43 | /** 标题 */
44 | title: string;
45 | /** 歌单id */
46 | id: string;
47 | /** 描述 */
48 | description: string;
49 | [k: string]: any;
50 | }
51 |
52 | interface IMusicTopListGroupItem {
53 | /** 分组标题 */
54 | title?: string;
55 | /** 数据 */
56 | data: Array;
57 | }
58 | }
59 |
60 | declare namespace IPlugin {
61 | type ICacheControl = "cache" | "no-cache" | "no-store";
62 | interface ISearchResult {
63 | isEnd?: boolean;
64 | data: ICommon.SupportMediaItemBase[T][];
65 | }
66 | type ISearchFunc = (
67 | query: string,
68 | page: number,
69 | type: T
70 | ) => Promise>;
71 |
72 | interface ISearchResult {
73 | isEnd?: boolean;
74 | data: ICommon.SupportMediaItemBase[T][];
75 | }
76 |
77 | interface IMediaSourceResult {
78 | headers?: Record;
79 | /** 兜底播放 */
80 | url?: string;
81 | /** UA */
82 | userAgent?: string;
83 | /** 音质 */
84 | quality?: IMusic.IQualityKey;
85 | }
86 |
87 | interface IUserVariable {
88 | /** 变量键名 */
89 | key: string;
90 | /** 变量名 */
91 | name?: string;
92 | }
93 |
94 |
95 | interface IPluginDefine {
96 | /** 插件名 */
97 | platform: string;
98 | /** 匹配的版本号 */
99 | appVersion?: string;
100 | /** 插件版本 */
101 | version?: string;
102 | /** 远程更新的url */
103 | srcUrl?: string;
104 | /** 主键,会被存储到mediameta中 */
105 | primaryKey?: string[];
106 | /** 默认搜索类型 */
107 | defaultSearchType?: ICommon.SupportMediaType;
108 | /** 插件缓存控制 */
109 | cacheControl?: ICacheControl;
110 | /** 用户自定义输入 */
111 | userVariables?: IUserVariable[];
112 | /** 搜索 */
113 | search?: ISearchFunc;
114 | /** 获取根据音乐信息获取url */
115 | getMediaSource?: (
116 | musicItem: IMusic.IMusicItem,
117 | quality: IMusic.IQualityKey
118 | ) => Promise;
119 | /** 根据主键去查询歌曲信息 */
120 | getMusicInfo?: (
121 | musicBase: ICommon.IMediaBase
122 | ) => Promise | null>;
123 | /** 获取歌词 */
124 | getLyric?: (
125 | musicItem: IMusic.IMusicItem
126 | ) => Promise;
127 | /** 获取专辑信息,里面的歌曲分页 */
128 | getAlbumInfo?: (
129 | albumItem: IAlbum.IAlbumItem,
130 | page: number
131 | ) => Promise;
132 | /** 获取作品,有分页 */
133 | getArtistWorks?: >(
134 | artistItem: IArtist.IArtistItem,
135 | page: number,
136 | type: T
137 | ) => Promise>;
138 | /** 导入歌单 */
139 | importMusicSheet?: (urlLike: string) => Promise;
140 | /** 导入单曲 */
141 | importMusicItem?: (urlLike: string) => Promise;
142 | /** 获取榜单 */
143 | getTopLists?: () => Promise;
144 | /** 获取榜单详情 */
145 | getTopListDetail?: (
146 | topListItem: IMusicSheet.IMusicSheetItem
147 | ) => Promise>;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------