mirror of
https://github.com/leiurayer/downkyi.git
synced 2025-03-23 15:50:12 +08:00
init v2.0.x
This commit is contained in:
commit
9392c10779
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
src/Config/Settings_debug.json
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
75
README.md
Normal file
75
README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# 哔哩下载姬
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/leiurayer/downkyi/stargazers" style="text-decoration:none" >
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/leiurayer/downkyi">
|
||||
</a>
|
||||
<a href="https://github.com/leiurayer/downkyi/network" style="text-decoration:none" >
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/leiurayer/downkyi">
|
||||
</a>
|
||||
<a href="https://github.com/leiurayer/downkyi/issues" style="text-decoration:none">
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/leiurayer/downkyi">
|
||||
</a>
|
||||
<a href="https://github.com/leiurayer/downkyi/blob/main/LICENSE" style="text-decoration:none" >
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/leiurayer/downkyi">
|
||||
</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
哔哩下载姬(DownKyi)是一个简单易用的哔哩哔哩视频下载工具,具有简洁的界面,流畅的操作逻辑。哔哩下载姬可以下载几乎所有的B站视频,并输出mp4格式的文件;采用Aria下载器多线程下载,采用FFmpeg对视频进行混流、提取音视频等操作。
|
||||
|
||||
[更多详情](src/README.md)
|
||||
|
||||
## 下载
|
||||
|
||||
<p align="left">
|
||||
<a href="https://github.com/leiurayer/downkyi/releases/latest" style="text-decoration:none">
|
||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/leiurayer/downkyi">
|
||||
</a>
|
||||
<a href="https://github.com/leiurayer/downkyi/releases/latest" style="text-decoration:none">
|
||||
<img alt="GitHub Release Date" src="https://img.shields.io/github/release-date/leiurayer/downkyi">
|
||||
</a>
|
||||
<a href="https://github.com/leiurayer/downkyi/releases" style="text-decoration:none">
|
||||
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/leiurayer/downkyi/total">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[更新日志](CHANGELOG.md)
|
||||
|
||||
## 问题
|
||||
|
||||
- Aria下载失败:检查aria2c.exe是否可以正常工作和是否允许通过防火墙;或者尝试切换端口号。
|
||||
- 内建下载器失败:请提issue,也欢迎pr。
|
||||
- 下载时卡在“混流中”:检查ffmpeg.exe是否可以正常工作。
|
||||
- 去水印:宽/高为水印的尺寸,X/Y为水印在图像中的位置(以左上角为原点),这四个数据可通过Photoshop获得。
|
||||
|
||||
## 赞助
|
||||
|
||||
如果这个项目对您有很大帮助,并且您希望支持该项目的开发和维护,请随时扫描一下二维码进行捐赠。非常感谢您的捐款,谢谢!
|
||||
|
||||

|
||||
|
||||
## 开发
|
||||
|
||||
### x86 & x64
|
||||
|
||||
发布的压缩包中aria2c.exe和ffmpeg.exe均为32位,如果需要请用下面链接中的文件替换。
|
||||
|
||||
- [aria2-1.36.0-win-32bit](third_party/aria2-1.36.0-win-32bit-build1.zip)
|
||||
- [aria2-1.36.0-win-64bit](third_party/aria2-1.36.0-win-64bit-build1.zip)
|
||||
- [FFmpeg](https://github.com/leiurayer/FFmpeg-Builds/releases/tag/latest)
|
||||
|
||||
### 相关项目
|
||||
|
||||
- [哔哩哔哩-API收集整理](https://github.com/SocialSisterYi/bilibili-API-collect):B站API归档
|
||||
- [Prism](https://github.com/PrismLibrary/Prism):MVVM框架
|
||||
- [WebPSharp](https://github.com/leiurayer/WebPSharp):WebP格式图片支持,[NuGet程序包](third_party/WebPSharp.0.5.1.nupkg)
|
||||
|
||||
## 免责申明
|
||||
|
||||
1. 本软件只提供视频解析,不提供任何资源上传、存储到服务器的功能。
|
||||
2. 本软件仅解析来自B站的内容,不会对解析到的音视频进行二次编码,部分视频会进行有限的格式转换、拼接等操作。
|
||||
3. 本软件解析得到的所有内容均来自B站UP主上传、分享,其版权均归原作者所有。内容提供者、上传者(UP主)应对其提供、上传的内容承担全部责任。
|
||||
4. **本软件提供的所有内容,仅可用作学习交流使用,未经原作者授权,禁止用于其他用途。请在下载24小时内删除。为尊重作者版权,请前往资源的原始发布网站观看,支持原创,谢谢。**
|
||||
5. 因使用本软件产生的版权问题,软件作者概不负责。
|
BIN
images/Alipay.png
Normal file
BIN
images/Alipay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
images/WeChat.png
Normal file
BIN
images/WeChat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
images/app/index.png
Normal file
BIN
images/app/index.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
63
src/.gitattributes
vendored
Normal file
63
src/.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
363
src/.gitignore
vendored
Normal file
363
src/.gitignore
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
35
src/BiliSharp.UnitTest/Api/Login/TestLoginInfo.cs
Normal file
35
src/BiliSharp.UnitTest/Api/Login/TestLoginInfo.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using BiliSharp.Api.Login;
|
||||
|
||||
namespace BiliSharp.UnitTest.Api.Login;
|
||||
|
||||
public class TestLoginInfo
|
||||
{
|
||||
[Fact]
|
||||
public void TestGetNavigationInfo_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
long mid = 42018135;
|
||||
var info = LoginInfo.GetNavigationInfo();
|
||||
Assert.Equal(mid, info.Data.Mid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetLoginInfoStat_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
var stat = LoginInfo.GetLoginInfoStat();
|
||||
Assert.NotNull(stat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetMyCoin_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
var coin = LoginInfo.GetMyCoin();
|
||||
Assert.NotNull(coin);
|
||||
}
|
||||
|
||||
}
|
16
src/BiliSharp.UnitTest/Api/Login/TestLoginNotice.cs
Normal file
16
src/BiliSharp.UnitTest/Api/Login/TestLoginNotice.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using BiliSharp.Api.Login;
|
||||
|
||||
namespace BiliSharp.UnitTest.Api.Login;
|
||||
|
||||
public class TestLoginNotice
|
||||
{
|
||||
[Fact]
|
||||
public void TestGetLoginNotice_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
long mid = 42018135;
|
||||
var notice = LoginNotice.GetLoginNotice(mid);
|
||||
Assert.Equal(mid, notice.Data.Mid);
|
||||
}
|
||||
}
|
23
src/BiliSharp.UnitTest/Api/Login/TestLoginQR.cs
Normal file
23
src/BiliSharp.UnitTest/Api/Login/TestLoginQR.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using BiliSharp.Api.Login;
|
||||
|
||||
namespace BiliSharp.UnitTest.Api.Login;
|
||||
|
||||
public class TestLoginQR
|
||||
{
|
||||
[Fact]
|
||||
public void TestGenerateQRCode_Default()
|
||||
{
|
||||
var qrcode = LoginQR.GenerateQRCode();
|
||||
Assert.NotNull(qrcode.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPollQRCode_Default()
|
||||
{
|
||||
var qrcode = LoginQR.GenerateQRCode();
|
||||
Assert.NotNull(qrcode.Data);
|
||||
|
||||
var poll = LoginQR.PollQRCode(qrcode.Data.QrcodeKey);
|
||||
Assert.NotNull(poll.Data);
|
||||
}
|
||||
}
|
87
src/BiliSharp.UnitTest/Api/Sign/TestWbiSign.cs
Normal file
87
src/BiliSharp.UnitTest/Api/Sign/TestWbiSign.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using BiliSharp.Api.Sign;
|
||||
|
||||
namespace BiliSharp.UnitTest.Api.Sign
|
||||
{
|
||||
public class TestWbiSign
|
||||
{
|
||||
[Fact]
|
||||
public void TestParametersToQuery_Default()
|
||||
{
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "fourk", 1 },
|
||||
{ "fnver", 0 },
|
||||
{ "fnval", 4048 },
|
||||
{ "cid", 405595939 },
|
||||
{ "qn", 120 },
|
||||
{ "bvid", "BV1Sg411F7cb" },
|
||||
{ "aid", 505421421 }
|
||||
};
|
||||
string expected = "fourk=1&fnver=0&fnval=4048&cid=405595939&qn=120&bvid=BV1Sg411F7cb&aid=505421421";
|
||||
|
||||
string query = WbiSign.ParametersToQuery(parameters);
|
||||
Assert.Equal(expected, query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestParametersToQuery_Empty()
|
||||
{
|
||||
var parameters = new Dictionary<string, object>();
|
||||
string query = WbiSign.ParametersToQuery(parameters);
|
||||
Assert.Equal("", query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestKeys_Default()
|
||||
{
|
||||
var key1 = WbiSign.GetKey();
|
||||
Assert.NotNull(key1);
|
||||
|
||||
string imgKey = "34478ba821254d9d93542680e3b86100";
|
||||
string subKey = "7e16a90d190a4355a78fd00b32a38de6";
|
||||
var keys = new Tuple<string, string>(imgKey, subKey);
|
||||
WbiSign.SetKey(keys);
|
||||
|
||||
var key2 = WbiSign.GetKey();
|
||||
Assert.Equal(imgKey, key2.Item1);
|
||||
Assert.Equal(subKey, key2.Item2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEncodeWbi_Default()
|
||||
{
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "fourk", 1 },
|
||||
{ "fnver", 0 },
|
||||
{ "fnval", 4048 },
|
||||
{ "cid", 405595939 },
|
||||
{ "qn", 120 },
|
||||
{ "bvid", "BV1Sg411F7cb" },
|
||||
{ "aid", 505421421 }
|
||||
};
|
||||
|
||||
var wbi = WbiSign.EncodeWbi(parameters);
|
||||
Assert.NotNull(wbi["w_rid"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEncodeWbi_Default2()
|
||||
{
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "fourk", 1 },
|
||||
{ "fnver", 0 },
|
||||
{ "fnval", 4048 },
|
||||
{ "cid", 405595939 },
|
||||
{ "qn", 120 },
|
||||
{ "bvid", "BV1Sg411F7cb" },
|
||||
{ "aid", 505421421 }
|
||||
};
|
||||
|
||||
var wbi = WbiSign.EncodeWbi(parameters, "653657f524a547ac981ded72ea172057", "6e4909c702f846728e64f6007736a338");
|
||||
Assert.NotNull(wbi["w_rid"]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
25
src/BiliSharp.UnitTest/Api/User/TestNickname.cs
Normal file
25
src/BiliSharp.UnitTest/Api/User/TestNickname.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using BiliSharp.Api.User;
|
||||
|
||||
namespace BiliSharp.UnitTest.Api.User
|
||||
{
|
||||
public class TestNickname
|
||||
{
|
||||
[Fact]
|
||||
public void TestCheckNickname_Default()
|
||||
{
|
||||
Equal("downkyi", 0);
|
||||
Equal("maozedong", 40002);
|
||||
Equal("//", 40004);
|
||||
Equal("test0000000000000", 40005);
|
||||
Equal("0", 40006);
|
||||
Equal("test", 40014);
|
||||
}
|
||||
|
||||
private static void Equal(string nickname, int code)
|
||||
{
|
||||
var response = Nickname.CheckNickname(nickname);
|
||||
Assert.Equal(code, response.Code);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
70
src/BiliSharp.UnitTest/Api/User/TestUserInfo.cs
Normal file
70
src/BiliSharp.UnitTest/Api/User/TestUserInfo.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using BiliSharp.Api.Login;
|
||||
using BiliSharp.Api.Sign;
|
||||
using BiliSharp.Api.User;
|
||||
|
||||
namespace BiliSharp.UnitTest.Api.User
|
||||
{
|
||||
public class TestUserInfo
|
||||
{
|
||||
[Fact]
|
||||
public void TestGetUserInfo_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
// 设置wbi keys
|
||||
var info = LoginInfo.GetNavigationInfo();
|
||||
var imgKey = info.Data.WbiImg.ImgUrl.Split('/').ToList().Last().Split('.')[0];
|
||||
var subKey = info.Data.WbiImg.SubUrl.Split('/').ToList().Last().Split('.')[0];
|
||||
var keys = new Tuple<string, string>(imgKey, subKey);
|
||||
WbiSign.SetKey(keys);
|
||||
|
||||
long mid = 42018135;
|
||||
var userInfo = UserInfo.GetUserInfo(mid);
|
||||
|
||||
Assert.Equal(mid, userInfo.Data.Mid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetUserCard_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
long mid = 42018135;
|
||||
var userCard = UserInfo.GetUserCard(mid);
|
||||
|
||||
Assert.Equal(mid.ToString(), userCard.Data.Card.Mid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetMyInfo_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
long mid = 42018135;
|
||||
var myInfo = UserInfo.GetMyInfo();
|
||||
|
||||
Assert.Equal(mid, myInfo.Data.Mid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetUserCards_Default()
|
||||
{
|
||||
Cookies.GetMyCookies();
|
||||
|
||||
// https://api.vc.bilibili.com/account/v1/user/cards?uids=314521322,206840230,49246269
|
||||
List<long> ids = new()
|
||||
{
|
||||
314521322,
|
||||
206840230,
|
||||
49246269
|
||||
};
|
||||
var users = UserInfo.GetUserCards(ids);
|
||||
|
||||
foreach (var user in users.Data)
|
||||
{
|
||||
Assert.Contains(user.Mid, ids);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
29
src/BiliSharp.UnitTest/BiliSharp.UnitTest.csproj
Normal file
29
src/BiliSharp.UnitTest/BiliSharp.UnitTest.csproj
Normal file
@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BiliSharp\BiliSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
43
src/BiliSharp.UnitTest/Cookies.cs
Normal file
43
src/BiliSharp.UnitTest/Cookies.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Net;
|
||||
|
||||
namespace BiliSharp.UnitTest;
|
||||
|
||||
public static class Cookies
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析从浏览器获取的cookies,用于设置cookie
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static CookieContainer ParseCookieByString(string str)
|
||||
{
|
||||
var cookieContainer = new CookieContainer();
|
||||
|
||||
var cookies = str.Replace(" ", "").Split(";");
|
||||
foreach (var cookie in cookies)
|
||||
{
|
||||
DateTime dateTime = DateTime.Now;
|
||||
dateTime = dateTime.AddMonths(12);
|
||||
|
||||
var temp = cookie.Split("=");
|
||||
var name = temp[0];
|
||||
var value = temp[1];
|
||||
|
||||
// 添加cookie
|
||||
cookieContainer.Add(new Cookie(name, value, "/", ".bilibili.com") { Expires = dateTime });
|
||||
}
|
||||
return cookieContainer;
|
||||
}
|
||||
|
||||
public static void GetMyCookies()
|
||||
{
|
||||
string cookiesStr = "DedeUserID=42018135; " +
|
||||
"DedeUserID__ckMd5=44e22fa30fe34ac4; " +
|
||||
"SESSDATA=32c16297%2C1700815953%2Cb11cd%2A51; " +
|
||||
"bili_jct=98dbd091dc07d8f9b69ba3845974e7c8; " +
|
||||
"sid=6vomjg3u";
|
||||
var cookies = ParseCookieByString(cookiesStr);
|
||||
BiliManager.Instance().SetCookies(cookies);
|
||||
}
|
||||
|
||||
}
|
1
src/BiliSharp.UnitTest/Usings.cs
Normal file
1
src/BiliSharp.UnitTest/Usings.cs
Normal file
@ -0,0 +1 @@
|
||||
global using Xunit;
|
40
src/BiliSharp/Api/Login/LoginInfo.cs
Normal file
40
src/BiliSharp/Api/Login/LoginInfo.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using BiliSharp.Api.Models.Login;
|
||||
|
||||
namespace BiliSharp.Api.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录基本信息
|
||||
/// </summary>
|
||||
public static class LoginInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 导航栏用户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static NavigationLoginInfo GetNavigationInfo()
|
||||
{
|
||||
string url = "https://api.bilibili.com/x/web-interface/nav";
|
||||
return Utils.GetData<NavigationLoginInfo>(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录用户状态数(双端)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static LoginInfoStat GetLoginInfoStat()
|
||||
{
|
||||
string url = "https://api.bilibili.com/x/web-interface/nav/stat";
|
||||
return Utils.GetData<LoginInfoStat>(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取硬币数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MyCoin GetMyCoin()
|
||||
{
|
||||
string url = "https://account.bilibili.com/site/getCoin";
|
||||
return Utils.GetData<MyCoin>(url);
|
||||
}
|
||||
}
|
||||
}
|
18
src/BiliSharp/Api/Login/LoginNotice.cs
Normal file
18
src/BiliSharp/Api/Login/LoginNotice.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace BiliSharp.Api.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录记录
|
||||
/// </summary>
|
||||
public static class LoginNotice
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询登录记录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Models.Login.LoginNotice GetLoginNotice(long mid)
|
||||
{
|
||||
string url = $"https://api.bilibili.com/x/safecenter/login_notice?mid={mid}";
|
||||
return Utils.GetData<Models.Login.LoginNotice>(url);
|
||||
}
|
||||
}
|
||||
}
|
30
src/BiliSharp/Api/Login/LoginQR.cs
Normal file
30
src/BiliSharp/Api/Login/LoginQR.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using BiliSharp.Api.Models.Login;
|
||||
|
||||
namespace BiliSharp.Api.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码登录
|
||||
/// </summary>
|
||||
public static class LoginQR
|
||||
{
|
||||
/// <summary>
|
||||
/// 申请二维码(web端)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static LoginQRCode GenerateQRCode()
|
||||
{
|
||||
string url = "https://passport.bilibili.com/x/passport-login/web/qrcode/generate";
|
||||
return Utils.GetData<LoginQRCode>(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫码登录(web端)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static LoginQRCodePoll PollQRCode(string qrcodeKey)
|
||||
{
|
||||
string url = $"https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key={qrcodeKey}";
|
||||
return Utils.GetData<LoginQRCodePoll>(url);
|
||||
}
|
||||
}
|
||||
}
|
58
src/BiliSharp/Api/Models/Login/LoginInfoStat.cs
Normal file
58
src/BiliSharp/Api/Models/Login/LoginInfoStat.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.bilibili.com/x/web-interface/nav/stat
|
||||
/// </summary>
|
||||
public class LoginInfoStat
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public LoginInfoStatData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class LoginInfoStatData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("following")]
|
||||
public int Following { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("follower")]
|
||||
public int Follower { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("dynamic_count")]
|
||||
public int DynamicCount { get; set; }
|
||||
}
|
||||
}
|
76
src/BiliSharp/Api/Models/Login/LoginNotice.cs
Normal file
76
src/BiliSharp/Api/Models/Login/LoginNotice.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.bilibili.com/x/safecenter/login_notice?mid=
|
||||
/// </summary>
|
||||
public class LoginNotice
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public LoginNoticeData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class LoginNoticeData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("device_name")]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("login_type")]
|
||||
public string LoginType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("login_time")]
|
||||
public string LoginTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("location")]
|
||||
public string Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ip")]
|
||||
public string Ip { get; set; }
|
||||
}
|
||||
}
|
52
src/BiliSharp/Api/Models/Login/LoginQRCode.cs
Normal file
52
src/BiliSharp/Api/Models/Login/LoginQRCode.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// https://passport.bilibili.com/x/passport-login/web/qrcode/generate
|
||||
/// </summary>
|
||||
public class LoginQRCode
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public LoginQRCodeData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class LoginQRCodeData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("qrcode_key")]
|
||||
public string QrcodeKey { get; set; }
|
||||
}
|
||||
}
|
70
src/BiliSharp/Api/Models/Login/LoginQRCodePoll.cs
Normal file
70
src/BiliSharp/Api/Models/Login/LoginQRCodePoll.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=
|
||||
/// </summary>
|
||||
public class LoginQRCodePoll
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public LoginQRCodePollData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class LoginQRCodePollData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public long Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
40
src/BiliSharp/Api/Models/Login/MyCoin.cs
Normal file
40
src/BiliSharp/Api/Models/Login/MyCoin.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// https://account.bilibili.com/site/getCoin
|
||||
/// </summary>
|
||||
public class MyCoin
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public bool Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public MyCoinData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyCoinData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("money")]
|
||||
public int Money { get; set; }
|
||||
}
|
||||
}
|
556
src/BiliSharp/Api/Models/Login/NavigationLoginInfo.cs
Normal file
556
src/BiliSharp/Api/Models/Login/NavigationLoginInfo.cs
Normal file
@ -0,0 +1,556 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.bilibili.com/x/web-interface/nav
|
||||
/// </summary>
|
||||
public class NavigationLoginInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public NavigationLoginInfoData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("isLogin")]
|
||||
public bool Islogin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("email_verified")]
|
||||
public int EmailVerified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face")]
|
||||
public string Face { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft")]
|
||||
public int FaceNft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft_type")]
|
||||
public int FaceNftType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level_info")]
|
||||
public NavigationLoginInfoDataLevelInfo LevelInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mobile_verified")]
|
||||
public int MobileVerified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("money")]
|
||||
public int Money { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("moral")]
|
||||
public int Moral { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("official")]
|
||||
public NavigationLoginInfoDataOfficial Official { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("officialVerify")]
|
||||
public NavigationLoginInfoDataOfficialverify Officialverify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pendant")]
|
||||
public NavigationLoginInfoDataPendant Pendant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("scores")]
|
||||
public int Scores { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("uname")]
|
||||
public string Uname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vipDueDate")]
|
||||
public long Vipduedate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vipStatus")]
|
||||
public int Vipstatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vipType")]
|
||||
public int Viptype { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_pay_type")]
|
||||
public int VipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_theme_type")]
|
||||
public int VipThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_label")]
|
||||
public NavigationLoginInfoDataVipLabel VipLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_avatar_subscript")]
|
||||
public int VipAvatarSubscript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_nickname_color")]
|
||||
public string VipNicknameColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip")]
|
||||
public NavigationLoginInfoDataVip Vip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("wallet")]
|
||||
public NavigationLoginInfoDataWallet Wallet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("has_shop")]
|
||||
public bool HasShop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("shop_url")]
|
||||
public string ShopUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("allowance_count")]
|
||||
public int AllowanceCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("answer_status")]
|
||||
public int AnswerStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_senior_member")]
|
||||
public int IsSeniorMember { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("wbi_img")]
|
||||
public NavigationLoginInfoDataWbiImg WbiImg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_jury")]
|
||||
public bool IsJury { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataLevelInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_level")]
|
||||
public int CurrentLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_min")]
|
||||
public int CurrentMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_exp")]
|
||||
public int CurrentExp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("next_exp")]
|
||||
public object NextExp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataOfficial
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataOfficialverify
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataPendant
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pid")]
|
||||
public int Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("expire")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance")]
|
||||
public string ImageEnhance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance_frame")]
|
||||
public string ImageEnhanceFrame { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataVipLabel
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label_theme")]
|
||||
public string LabelTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_color")]
|
||||
public string TextColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_style")]
|
||||
public int BgStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_color")]
|
||||
public string BgColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("border_color")]
|
||||
public string BorderColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("use_img_label")]
|
||||
public bool UseImgLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans")]
|
||||
public string ImgLabelUriHans { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant")]
|
||||
public string ImgLabelUriHant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans_static")]
|
||||
public string ImgLabelUriHansStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant_static")]
|
||||
public string ImgLabelUriHantStatic { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataVip
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("due_date")]
|
||||
public long DueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_pay_type")]
|
||||
public int VipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("theme_type")]
|
||||
public int ThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label")]
|
||||
public NavigationLoginInfoDataVipLabel Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript")]
|
||||
public int AvatarSubscript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nickname_color")]
|
||||
public string NicknameColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript_url")]
|
||||
public string AvatarSubscriptUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_status")]
|
||||
public int TvVipStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_pay_type")]
|
||||
public int TvVipPayType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataWallet
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bcoin_balance")]
|
||||
public int BcoinBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("coupon_balance")]
|
||||
public int CouponBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("coupon_due_time")]
|
||||
public int CouponDueTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class NavigationLoginInfoDataWbiImg
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_url")]
|
||||
public string ImgUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sub_url")]
|
||||
public string SubUrl { get; set; }
|
||||
}
|
||||
}
|
652
src/BiliSharp/Api/Models/User/MyInfo.cs
Normal file
652
src/BiliSharp/Api/Models/User/MyInfo.cs
Normal file
@ -0,0 +1,652 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.User
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.bilibili.com/x/space/myinfo
|
||||
/// </summary>
|
||||
public class MyInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public MyInfoData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sex")]
|
||||
public string Sex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face")]
|
||||
public string Face { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sign")]
|
||||
public string Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("rank")]
|
||||
public int Rank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("jointime")]
|
||||
public int Jointime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("moral")]
|
||||
public int Moral { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("silence")]
|
||||
public int Silence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("email_status")]
|
||||
public int EmailStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tel_status")]
|
||||
public int TelStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("identification")]
|
||||
public int Identification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip")]
|
||||
public MyInfoDataVip Vip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pendant")]
|
||||
public MyInfoDataPendant Pendant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nameplate")]
|
||||
public MyInfoDataNameplate Nameplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("official")]
|
||||
public MyInfoDataOfficial Official { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("birthday")]
|
||||
public long Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_tourist")]
|
||||
public int IsTourist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_fake_account")]
|
||||
public int IsFakeAccount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pin_prompting")]
|
||||
public int PinPrompting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_deleted")]
|
||||
public int IsDeleted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("in_reg_audit")]
|
||||
public int InRegAudit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_rip_user")]
|
||||
public bool IsRipUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("profession")]
|
||||
public MyInfoDataProfession Profession { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft")]
|
||||
public int FaceNft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft_new")]
|
||||
public int FaceNftNew { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_senior_member")]
|
||||
public int IsSeniorMember { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("honours")]
|
||||
public MyInfoDataHonours Honours { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("digital_id")]
|
||||
public string DigitalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("digital_type")]
|
||||
public int DigitalType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level_exp")]
|
||||
public MyInfoDataLevelExp LevelExp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("coins")]
|
||||
public int Coins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("following")]
|
||||
public int Following { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("follower")]
|
||||
public int Follower { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataVip
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("due_date")]
|
||||
public long DueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_pay_type")]
|
||||
public int VipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("theme_type")]
|
||||
public int ThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label")]
|
||||
public MyInfoDataVipLabel Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript")]
|
||||
public int AvatarSubscript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nickname_color")]
|
||||
public string NicknameColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript_url")]
|
||||
public string AvatarSubscriptUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_status")]
|
||||
public int TvVipStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_pay_type")]
|
||||
public int TvVipPayType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataVipLabel
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label_theme")]
|
||||
public string LabelTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_color")]
|
||||
public string TextColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_style")]
|
||||
public int BgStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_color")]
|
||||
public string BgColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("border_color")]
|
||||
public string BorderColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("use_img_label")]
|
||||
public bool UseImgLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans")]
|
||||
public string ImgLabelUriHans { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant")]
|
||||
public string ImgLabelUriHant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans_static")]
|
||||
public string ImgLabelUriHansStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant_static")]
|
||||
public string ImgLabelUriHantStatic { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataPendant
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pid")]
|
||||
public int Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("expire")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance")]
|
||||
public string ImageEnhance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance_frame")]
|
||||
public string ImageEnhanceFrame { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataNameplate
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nid")]
|
||||
public int Nid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_small")]
|
||||
public string ImageSmall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("condition")]
|
||||
public string Condition { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataOfficial
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataProfession
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("show_name")]
|
||||
public string ShowName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_show")]
|
||||
public int IsShow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("category_one")]
|
||||
public string CategoryOne { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("realname")]
|
||||
public string Realname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("department")]
|
||||
public string Department { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataHonours
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("colour")]
|
||||
public MyInfoDataHonoursColour Colour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tags")]
|
||||
public object Tags { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataHonoursColour
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("dark")]
|
||||
public string Dark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("normal")]
|
||||
public string Normal { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MyInfoDataLevelExp
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_level")]
|
||||
public int CurrentLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_min")]
|
||||
public int CurrentMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_exp")]
|
||||
public int CurrentExp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("next_exp")]
|
||||
public int NextExp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level_up")]
|
||||
public long LevelUp { get; set; }
|
||||
}
|
||||
}
|
22
src/BiliSharp/Api/Models/User/Nickname.cs
Normal file
22
src/BiliSharp/Api/Models/User/Nickname.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.User
|
||||
{
|
||||
/// <summary>
|
||||
/// https://passport.bilibili.com/web/generic/check/nickname?nickName=hahaha
|
||||
/// </summary>
|
||||
public class Nickname
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
599
src/BiliSharp/Api/Models/User/UserCard.cs
Normal file
599
src/BiliSharp/Api/Models/User/UserCard.cs
Normal file
@ -0,0 +1,599 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.User
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.bilibili.com/x/web-interface/card?mid=314521322&photo=true
|
||||
/// </summary>
|
||||
public class UserCard
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public UserCardData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("card")]
|
||||
public UserCardDataCard Card { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("space")]
|
||||
public UserCardDataSpace Space { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("following")]
|
||||
public bool Following { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("archive_count")]
|
||||
public int ArchiveCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("article_count")]
|
||||
public int ArticleCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("follower")]
|
||||
public long Follower { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("like_num")]
|
||||
public long LikeNum { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCard
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public string Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("approve")]
|
||||
public bool Approve { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sex")]
|
||||
public string Sex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("rank")]
|
||||
public string Rank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face")]
|
||||
public string Face { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft")]
|
||||
public int FaceNft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft_type")]
|
||||
public int FaceNftType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("DisplayRank")]
|
||||
public string Displayrank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("regtime")]
|
||||
public int Regtime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("spacesta")]
|
||||
public int Spacesta { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("birthday")]
|
||||
public string Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("place")]
|
||||
public string Place { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("article")]
|
||||
public int Article { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("attentions")]
|
||||
public List<object> Attentions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("fans")]
|
||||
public long Fans { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("friend")]
|
||||
public int Friend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("attention")]
|
||||
public int Attention { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sign")]
|
||||
public string Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level_info")]
|
||||
public UserCardDataCardLevelInfo LevelInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pendant")]
|
||||
public UserCardDataCardPendant Pendant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nameplate")]
|
||||
public UserCardDataCardNameplate Nameplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("Official")]
|
||||
public UserCardDataCardOfficial Official { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("official_verify")]
|
||||
public UserCardDataCardOfficialVerify OfficialVerify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip")]
|
||||
public UserCardDataCardVip Vip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_senior_member")]
|
||||
public int IsSeniorMember { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardLevelInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_level")]
|
||||
public int CurrentLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_min")]
|
||||
public int CurrentMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_exp")]
|
||||
public int CurrentExp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("next_exp")]
|
||||
public int NextExp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardPendant
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pid")]
|
||||
public int Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("expire")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance")]
|
||||
public string ImageEnhance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance_frame")]
|
||||
public string ImageEnhanceFrame { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardNameplate
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nid")]
|
||||
public int Nid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_small")]
|
||||
public string ImageSmall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("condition")]
|
||||
public string Condition { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardOfficial
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardOfficialVerify
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardVip
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("due_date")]
|
||||
public long DueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_pay_type")]
|
||||
public int VipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("theme_type")]
|
||||
public int ThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label")]
|
||||
public UserCardDataCardVipLabel Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript")]
|
||||
public int AvatarSubscript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nickname_color")]
|
||||
public string NicknameColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript_url")]
|
||||
public string AvatarSubscriptUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_status")]
|
||||
public int TvVipStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_pay_type")]
|
||||
public int TvVipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vipType")]
|
||||
public int Viptype { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vipStatus")]
|
||||
public int Vipstatus { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataCardVipLabel
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label_theme")]
|
||||
public string LabelTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_color")]
|
||||
public string TextColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_style")]
|
||||
public int BgStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_color")]
|
||||
public string BgColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("border_color")]
|
||||
public string BorderColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("use_img_label")]
|
||||
public bool UseImgLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans")]
|
||||
public string ImgLabelUriHans { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant")]
|
||||
public string ImgLabelUriHant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans_static")]
|
||||
public string ImgLabelUriHansStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant_static")]
|
||||
public string ImgLabelUriHantStatic { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardDataSpace
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("s_img")]
|
||||
public string SImg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("l_img")]
|
||||
public string LImg { get; set; }
|
||||
}
|
||||
}
|
443
src/BiliSharp/Api/Models/User/UserCards.cs
Normal file
443
src/BiliSharp/Api/Models/User/UserCards.cs
Normal file
@ -0,0 +1,443 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.User
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.vc.bilibili.com/account/v1/user/cards?uids=314521322,206840230,49246269
|
||||
/// </summary>
|
||||
public class UserCards
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("msg")]
|
||||
public string Msg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public List<UserCardsData> Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardsData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sex")]
|
||||
public string Sex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face")]
|
||||
public string Face { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sign")]
|
||||
public string Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("rank")]
|
||||
public int Rank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("silence")]
|
||||
public int Silence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip")]
|
||||
public UserCardsDataVip Vip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pendant")]
|
||||
public UserCardsDataPendant Pendant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nameplate")]
|
||||
public UserCardsDataNameplate Nameplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("official")]
|
||||
public UserCardsDataOfficial Official { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("birthday")]
|
||||
public long Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_fake_account")]
|
||||
public int IsFakeAccount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_deleted")]
|
||||
public int IsDeleted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("in_reg_audit")]
|
||||
public int InRegAudit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft")]
|
||||
public int FaceNft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft_new")]
|
||||
public int FaceNftNew { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_senior_member")]
|
||||
public int IsSeniorMember { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("digital_id")]
|
||||
public string DigitalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("digital_type")]
|
||||
public int DigitalType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardsDataVip
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("due_date")]
|
||||
public long DueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_pay_type")]
|
||||
public int VipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("theme_type")]
|
||||
public int ThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label")]
|
||||
public UserCardsDataVipLabel Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript")]
|
||||
public int AvatarSubscript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nickname_color")]
|
||||
public string NicknameColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript_url")]
|
||||
public string AvatarSubscriptUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_status")]
|
||||
public int TvVipStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_pay_type")]
|
||||
public int TvVipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_due_date")]
|
||||
public long TvDueDate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardsDataVipLabel
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label_theme")]
|
||||
public string LabelTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_color")]
|
||||
public string TextColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_style")]
|
||||
public int BgStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_color")]
|
||||
public string BgColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("border_color")]
|
||||
public string BorderColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("use_img_label")]
|
||||
public bool UseImgLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans")]
|
||||
public string ImgLabelUriHans { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant")]
|
||||
public string ImgLabelUriHant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans_static")]
|
||||
public string ImgLabelUriHansStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant_static")]
|
||||
public string ImgLabelUriHantStatic { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardsDataPendant
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pid")]
|
||||
public int Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("expire")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance")]
|
||||
public string ImageEnhance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance_frame")]
|
||||
public string ImageEnhanceFrame { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardsDataNameplate
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nid")]
|
||||
public int Nid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_small")]
|
||||
public string ImageSmall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("condition")]
|
||||
public string Condition { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserCardsDataOfficial
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
}
|
||||
}
|
827
src/BiliSharp/Api/Models/User/UserSpaceInfo.cs
Normal file
827
src/BiliSharp/Api/Models/User/UserSpaceInfo.cs
Normal file
@ -0,0 +1,827 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BiliSharp.Api.Models.User
|
||||
{
|
||||
/// <summary>
|
||||
/// https://api.bilibili.com/x/space/acc/info?mid=
|
||||
/// </summary>
|
||||
public class UserSpaceInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public UserSpaceInfoData Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public long Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sex")]
|
||||
public string Sex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face")]
|
||||
public string Face { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft")]
|
||||
public int FaceNft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("face_nft_type")]
|
||||
public int FaceNftType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sign")]
|
||||
public string Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("rank")]
|
||||
public int Rank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("jointime")]
|
||||
public int Jointime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("moral")]
|
||||
public int Moral { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("silence")]
|
||||
public int Silence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("coins")]
|
||||
public int Coins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("fans_badge")]
|
||||
public bool FansBadge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("fans_medal")]
|
||||
public UserSpaceInfoDataFansMedal FansMedal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("official")]
|
||||
public UserSpaceInfoDataOfficial Official { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip")]
|
||||
public UserSpaceInfoDataVip Vip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pendant")]
|
||||
public UserSpaceInfoDataPendant Pendant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nameplate")]
|
||||
public UserSpaceInfoDataNameplate Nameplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_honour_info")]
|
||||
public UserSpaceInfoDataUserHonourInfo UserHonourInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_followed")]
|
||||
public bool IsFollowed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("top_photo")]
|
||||
public string TopPhoto { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("theme")]
|
||||
public UserSpaceInfoDataTheme Theme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("sys_notice")]
|
||||
public UserSpaceInfoDataSysNotice SysNotice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("live_room")]
|
||||
public UserSpaceInfoDataLiveRoom LiveRoom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("birthday")]
|
||||
public string Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("school")]
|
||||
public UserSpaceInfoDataSchool School { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("profession")]
|
||||
public UserSpaceInfoDataProfession Profession { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tags")]
|
||||
public object Tags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("series")]
|
||||
public UserSpaceInfoDataSeries Series { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_senior_member")]
|
||||
public int IsSeniorMember { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mcn_info")]
|
||||
public object McnInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("gaia_res_type")]
|
||||
public int GaiaResType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("gaia_data")]
|
||||
public object GaiaData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_risk")]
|
||||
public bool IsRisk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("elec")]
|
||||
public UserSpaceInfoDataElec Elec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("contract")]
|
||||
public UserSpaceInfoDataContract Contract { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataFansMedal
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public bool Show { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("wear")]
|
||||
public bool Wear { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("medal")]
|
||||
public object Medal { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataOfficial
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")]
|
||||
public string Desc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataVip
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("due_date")]
|
||||
public long DueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("vip_pay_type")]
|
||||
public int VipPayType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("theme_type")]
|
||||
public int ThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label")]
|
||||
public UserSpaceInfoDataVipLabel Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript")]
|
||||
public int AvatarSubscript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nickname_color")]
|
||||
public string NicknameColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_subscript_url")]
|
||||
public string AvatarSubscriptUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_status")]
|
||||
public int TvVipStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tv_vip_pay_type")]
|
||||
public int TvVipPayType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataVipLabel
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("label_theme")]
|
||||
public string LabelTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_color")]
|
||||
public string TextColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_style")]
|
||||
public int BgStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("bg_color")]
|
||||
public string BgColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("border_color")]
|
||||
public string BorderColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("use_img_label")]
|
||||
public bool UseImgLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans")]
|
||||
public string ImgLabelUriHans { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant")]
|
||||
public string ImgLabelUriHant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hans_static")]
|
||||
public string ImgLabelUriHansStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_label_uri_hant_static")]
|
||||
public string ImgLabelUriHantStatic { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataPendant
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("pid")]
|
||||
public int Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("expire")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance")]
|
||||
public string ImageEnhance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_enhance_frame")]
|
||||
public string ImageEnhanceFrame { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataNameplate
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("nid")]
|
||||
public int Nid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("image_small")]
|
||||
public string ImageSmall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("condition")]
|
||||
public string Condition { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataUserHonourInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public int Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("colour")]
|
||||
public object Colour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("tags")]
|
||||
public List<object> Tags { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataTheme
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataSysNotice
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataLiveRoom
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("roomStatus")]
|
||||
public int Roomstatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("liveStatus")]
|
||||
public int Livestatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("cover")]
|
||||
public string Cover { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("roomid")]
|
||||
public long Roomid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("roundStatus")]
|
||||
public int Roundstatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("broadcast_type")]
|
||||
public int BroadcastType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("watched_show")]
|
||||
public UserSpaceInfoDataLiveRoomWatchedShow WatchedShow { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataLiveRoomWatchedShow
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("switch")]
|
||||
public bool Switch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("num")]
|
||||
public int Num { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_small")]
|
||||
public string TextSmall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("text_large")]
|
||||
public string TextLarge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon_location")]
|
||||
public string IconLocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon_web")]
|
||||
public string IconWeb { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataSchool
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataProfession
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("department")]
|
||||
public string Department { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_show")]
|
||||
public int IsShow { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataSeries
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_upgrade_status")]
|
||||
public int UserUpgradeStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("show_upgrade_window")]
|
||||
public bool ShowUpgradeWindow { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataElec
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("show_info")]
|
||||
public UserSpaceInfoDataElecShowInfo ShowInfo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataElecShowInfo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public bool Show { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("state")]
|
||||
public int State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("jump_url")]
|
||||
public string JumpUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserSpaceInfoDataContract
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_display")]
|
||||
public bool IsDisplay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_follow_display")]
|
||||
public bool IsFollowDisplay { get; set; }
|
||||
}
|
||||
}
|
133
src/BiliSharp/Api/Sign/WbiSign.cs
Normal file
133
src/BiliSharp/Api/Sign/WbiSign.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BiliSharp.Api.Sign
|
||||
{
|
||||
public static class WbiSign
|
||||
{
|
||||
private static Tuple<string, string> Keys;
|
||||
|
||||
/// <summary>
|
||||
/// 打乱重排实时口令
|
||||
/// </summary>
|
||||
/// <param name="origin"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetMixinKey(string origin)
|
||||
{
|
||||
int[] mixinKeyEncTab = new int[]
|
||||
{
|
||||
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
||||
33, 9, 42, 19, 29, 28, 14, 39,12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
||||
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
||||
36, 20, 34, 44, 52
|
||||
};
|
||||
|
||||
var temp = new StringBuilder();
|
||||
foreach (var i in mixinKeyEncTab)
|
||||
{
|
||||
temp.Append(origin[i]);
|
||||
}
|
||||
return temp.ToString()[..32];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字典参数转为字符串
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static string ParametersToQuery(Dictionary<string, object> parameters)
|
||||
{
|
||||
var keys = parameters.Keys.ToList();
|
||||
var queryList = new List<string>();
|
||||
foreach (var item in keys)
|
||||
{
|
||||
var value = parameters[item];
|
||||
queryList.Add($"{item}={value}");
|
||||
}
|
||||
return string.Join("&", queryList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回imgKey,subKey
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Tuple<string, string> GetKey()
|
||||
{
|
||||
// 当Keys为null时,返回一个硬编码的key,
|
||||
// 这个key无效,只为了保证不报错
|
||||
return Keys ?? new Tuple<string, string>("653657f524a547ac981ded72ea172057", "6e4909c702f846728e64f6007736a338");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置imgKey,subKey
|
||||
/// </summary>
|
||||
/// <param name="keys"></param>
|
||||
public static void SetKey(Tuple<string, string> keys)
|
||||
{
|
||||
Keys = keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wbi签名,返回所有参数字典
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> EncodeWbi(Dictionary<string, object> parameters)
|
||||
{
|
||||
return EncodeWbi(parameters, GetKey().Item1, GetKey().Item2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wbi签名,返回所有参数字典
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="imgKey"></param>
|
||||
/// <param name="subKey"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> EncodeWbi(Dictionary<string, object> parameters, string imgKey, string subKey)
|
||||
{
|
||||
var mixinKey = GetMixinKey(imgKey + subKey);
|
||||
|
||||
var chrFilter = new Regex("[!'()*]");
|
||||
|
||||
var newParameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "wts", (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds }
|
||||
};
|
||||
|
||||
foreach (var para in parameters)
|
||||
{
|
||||
var key = para.Key;
|
||||
var value = para.Value.ToString();
|
||||
|
||||
var encodedValue = chrFilter.Replace(value, "");
|
||||
|
||||
newParameters.Add(Uri.EscapeDataString(key), Uri.EscapeDataString(encodedValue));
|
||||
}
|
||||
|
||||
var keys = newParameters.Keys.ToList();
|
||||
keys.Sort();
|
||||
|
||||
var queryList = new List<string>();
|
||||
foreach (var item in keys)
|
||||
{
|
||||
var value = newParameters[item];
|
||||
queryList.Add($"{item}={value}");
|
||||
}
|
||||
|
||||
var queryString = string.Join("&", queryList);
|
||||
var md5Hasher = MD5.Create();
|
||||
var hashStr = queryString + mixinKey;
|
||||
var hashedQueryString = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(hashStr));
|
||||
var wbiSign = BitConverter.ToString(hashedQueryString).Replace("-", "").ToLower();
|
||||
|
||||
newParameters.Add("w_rid", wbiSign);
|
||||
return newParameters;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
19
src/BiliSharp/Api/User/Nickname.cs
Normal file
19
src/BiliSharp/Api/User/Nickname.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace BiliSharp.Api.User
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查昵称是否可注册
|
||||
/// </summary>
|
||||
public static class Nickname
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查昵称
|
||||
/// </summary>
|
||||
/// <param name="nickname"></param>
|
||||
/// <returns></returns>
|
||||
public static Models.User.Nickname CheckNickname(string nickname)
|
||||
{
|
||||
string url = $"https://passport.bilibili.com/web/generic/check/nickname?nickName={nickname}";
|
||||
return Utils.GetData<Models.User.Nickname>(url);
|
||||
}
|
||||
}
|
||||
}
|
66
src/BiliSharp/Api/User/UserInfo.cs
Normal file
66
src/BiliSharp/Api/User/UserInfo.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using BiliSharp.Api.Models.User;
|
||||
using BiliSharp.Api.Sign;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BiliSharp.Api.User
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户基本信息
|
||||
/// </summary>
|
||||
public static class UserInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户空间详细信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static UserSpaceInfo GetUserInfo(long mid)
|
||||
{
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "mid", mid }
|
||||
};
|
||||
string query = WbiSign.ParametersToQuery(WbiSign.EncodeWbi(parameters));
|
||||
string url = $"https://api.bilibili.com/x/space/wbi/acc/info?{query}";
|
||||
return Utils.GetData<UserSpaceInfo>(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户名片信息
|
||||
/// </summary>
|
||||
/// <param name="mid"></param>
|
||||
/// <param name="isPhoto"></param>
|
||||
/// <returns></returns>
|
||||
public static UserCard GetUserCard(long mid, bool isPhoto = false)
|
||||
{
|
||||
string url = $"https://api.bilibili.com/x/web-interface/card?mid={mid}&photo={isPhoto}";
|
||||
return Utils.GetData<UserCard>(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录用户空间详细信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MyInfo GetMyInfo()
|
||||
{
|
||||
string url = "https://api.bilibili.com/x/space/myinfo";
|
||||
return Utils.GetData<MyInfo>(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多用户详细信息
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
public static UserCards GetUserCards(List<long> ids)
|
||||
{
|
||||
string url = "https://api.vc.bilibili.com/account/v1/user/cards?uids=";
|
||||
foreach (long id in ids)
|
||||
{
|
||||
url += $"{id},";
|
||||
}
|
||||
url = url.TrimEnd(',');
|
||||
|
||||
return Utils.GetData<UserCards>(url);
|
||||
}
|
||||
}
|
||||
}
|
53
src/BiliSharp/BiliManager.cs
Normal file
53
src/BiliSharp/BiliManager.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.Net;
|
||||
|
||||
namespace BiliSharp
|
||||
{
|
||||
public class BiliManager
|
||||
{
|
||||
private static readonly BiliManager _instance = new BiliManager();
|
||||
private string _userAgent;
|
||||
private CookieContainer _cookies;
|
||||
|
||||
private BiliManager() { }
|
||||
|
||||
public static BiliManager Instance()
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置cookies
|
||||
/// </summary>
|
||||
/// <param name="cookies"></param>
|
||||
/// <returns></returns>
|
||||
public BiliManager SetCookies(CookieContainer cookies)
|
||||
{
|
||||
_cookies = cookies;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取cookies
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CookieContainer GetCookies() { return _cookies; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置userAgent
|
||||
/// </summary>
|
||||
/// <param name="userAgent"></param>
|
||||
/// <returns></returns>
|
||||
public BiliManager SetUserAgent(string userAgent)
|
||||
{
|
||||
_userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取userAgent
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetUserAgent() { return _userAgent; }
|
||||
|
||||
}
|
||||
}
|
11
src/BiliSharp/BiliSharp.csproj
Normal file
11
src/BiliSharp/BiliSharp.csproj
Normal file
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
29
src/BiliSharp/Utils.cs
Normal file
29
src/BiliSharp/Utils.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BiliSharp
|
||||
{
|
||||
internal static class Utils
|
||||
{
|
||||
internal static T GetData<T>(string url)
|
||||
{
|
||||
string referer = "https://www.bilibili.com";
|
||||
string userAgent = BiliManager.Instance().GetUserAgent();
|
||||
CookieContainer cookies = BiliManager.Instance().GetCookies();
|
||||
|
||||
string response = WebClient.RequestWeb(url, referer, userAgent, cookies);
|
||||
|
||||
try
|
||||
{
|
||||
var obj = JsonSerializer.Deserialize<T>(response);
|
||||
return obj;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
121
src/BiliSharp/WebClient.cs
Normal file
121
src/BiliSharp/WebClient.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace BiliSharp
|
||||
{
|
||||
internal static class WebClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送get或post请求
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="referer"></param>
|
||||
/// <param name="userAgent"></param>
|
||||
/// <param name="cookies"></param>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="retry"></param>
|
||||
/// <returns></returns>
|
||||
internal static string RequestWeb(string url, string referer = null, string userAgent = null, CookieContainer cookies = null, string method = "GET", Dictionary<string, string> parameters = null, int retry = 3)
|
||||
{
|
||||
// 重试次数
|
||||
if (retry <= 0) { return ""; }
|
||||
|
||||
// post请求,发送参数
|
||||
if (method == "POST" && parameters != null)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
int i = 0;
|
||||
foreach (var item in parameters)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
builder.Append('&');
|
||||
}
|
||||
|
||||
builder.AppendFormat("{0}={1}", item.Key, item.Value);
|
||||
i++;
|
||||
}
|
||||
|
||||
url += "?" + builder.ToString();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.Method = method;
|
||||
request.Timeout = 60 * 1000;
|
||||
|
||||
request.ContentType = "application/json,text/html,application/xhtml+xml,application/xml;charset=UTF-8";
|
||||
request.Headers["accept-language"] = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7";
|
||||
request.Headers["accept-encoding"] = "gzip, deflate, br";
|
||||
|
||||
// userAgent
|
||||
if (userAgent != null)
|
||||
{
|
||||
request.UserAgent = userAgent;
|
||||
}
|
||||
else
|
||||
{
|
||||
request.UserAgent = "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36";
|
||||
}
|
||||
|
||||
// referer
|
||||
if (referer != null)
|
||||
{
|
||||
request.Referer = referer;
|
||||
}
|
||||
|
||||
// 构造cookie
|
||||
if (!url.Contains("getLogin"))
|
||||
{
|
||||
//request.Headers["origin"] = "https://www.bilibili.com";
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
request.CookieContainer = cookies;
|
||||
}
|
||||
}
|
||||
|
||||
string html = string.Empty;
|
||||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
||||
{
|
||||
if (response.ContentEncoding.ToLower().Contains("gzip"))
|
||||
{
|
||||
using var stream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
html = reader.ReadToEnd();
|
||||
}
|
||||
else if (response.ContentEncoding.ToLower().Contains("deflate"))
|
||||
{
|
||||
using var stream = new DeflateStream(response.GetResponseStream(), CompressionMode.Decompress);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
html = reader.ReadToEnd();
|
||||
}
|
||||
else if (response.ContentEncoding.ToLower().Contains("br"))
|
||||
{
|
||||
using var stream = new BrotliStream(response.GetResponseStream(), CompressionMode.Decompress);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
html = reader.ReadToEnd();
|
||||
}
|
||||
else
|
||||
{
|
||||
using var stream = response.GetResponseStream();
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
html = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return RequestWeb(url, referer, userAgent, cookies, method, parameters, retry - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
src/Downkyi.Core/Bili/BiliLocator.cs
Normal file
24
src/Downkyi.Core/Bili/BiliLocator.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Downkyi.Core.Bili;
|
||||
|
||||
public static class BiliLocator
|
||||
{
|
||||
private static ILogin _login;
|
||||
public static ILogin Login
|
||||
{
|
||||
get
|
||||
{
|
||||
_login ??= new Web.Login();
|
||||
return _login;
|
||||
}
|
||||
}
|
||||
|
||||
private static IUser _user;
|
||||
public static IUser User
|
||||
{
|
||||
get
|
||||
{
|
||||
_user ??= new Web.User();
|
||||
return _user;
|
||||
}
|
||||
}
|
||||
}
|
144
src/Downkyi.Core/Bili/Cookies.cs
Normal file
144
src/Downkyi.Core/Bili/Cookies.cs
Normal file
@ -0,0 +1,144 @@
|
||||
using Downkyi.Core.Utils;
|
||||
using System.Collections;
|
||||
using System.Net;
|
||||
using System.Web;
|
||||
|
||||
namespace Downkyi.Core.Bili;
|
||||
|
||||
public static class Cookies
|
||||
{
|
||||
/// <summary>
|
||||
/// 写入cookies到磁盘
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="cookieJar"></param>
|
||||
/// <returns></returns>
|
||||
public static bool WriteCookiesToDisk(string file, CookieContainer cookieJar)
|
||||
{
|
||||
return ObjectHelper.WriteObjectToDisk(file, cookieJar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从磁盘读取cookie
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public static CookieContainer ReadCookiesFromDisk(string file)
|
||||
{
|
||||
return (CookieContainer)ObjectHelper.ReadObjectFromDisk(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将CookieContainer中的所有的Cookie读出来
|
||||
/// </summary>
|
||||
/// <param name="cc"></param>
|
||||
/// <returns></returns>
|
||||
public static List<Cookie> GetAllCookies(CookieContainer cc)
|
||||
{
|
||||
var lstCookies = new List<Cookie>();
|
||||
|
||||
Hashtable table = (Hashtable)cc.GetType().InvokeMember("m_domainTable",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField |
|
||||
System.Reflection.BindingFlags.Instance, null, cc, Array.Empty<object>());
|
||||
|
||||
foreach (object pathList in table.Values)
|
||||
{
|
||||
SortedList lstCookieCol = (SortedList)pathList.GetType().InvokeMember("m_list",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField
|
||||
| System.Reflection.BindingFlags.Instance, null, pathList, Array.Empty<object>());
|
||||
foreach (CookieCollection colCookies in lstCookieCol.Values)
|
||||
{
|
||||
foreach (Cookie c in colCookies.Cast<Cookie>())
|
||||
{
|
||||
lstCookies.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lstCookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回cookies的字符串
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetCookiesString(CookieContainer cc)
|
||||
{
|
||||
var cookies = GetAllCookies(cc);
|
||||
|
||||
string cookie = string.Empty;
|
||||
foreach (var item in cookies)
|
||||
{
|
||||
cookie += item.ToString() + ";";
|
||||
}
|
||||
return cookie.TrimEnd(';');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析二维码登录返回的url,用于设置cookie
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static CookieContainer ParseCookieByUrl(string url)
|
||||
{
|
||||
var cookieContainer = new CookieContainer();
|
||||
|
||||
if (url == null || url == "") { return cookieContainer; }
|
||||
|
||||
string[] strList = url.Split('?');
|
||||
if (strList.Length < 2) { return cookieContainer; }
|
||||
|
||||
string[] strList2 = strList[1].Split('&');
|
||||
if (strList2.Length == 0) { return cookieContainer; }
|
||||
|
||||
// 获取expires
|
||||
string expires = strList2.FirstOrDefault(it => it.Contains("Expires")).Split('=')[1];
|
||||
DateTime dateTime = DateTime.Now;
|
||||
dateTime = dateTime.AddSeconds(int.Parse(expires));
|
||||
|
||||
foreach (var item in strList2)
|
||||
{
|
||||
string[] strList3 = item.Split('=');
|
||||
if (strList3.Length < 2) { continue; }
|
||||
|
||||
string name = strList3[0];
|
||||
string value = strList3[1];
|
||||
value = HttpUtility.UrlEncode(value);
|
||||
|
||||
// 不需要
|
||||
if (name == "Expires" || name == "gourl") { continue; }
|
||||
|
||||
// 添加cookie
|
||||
cookieContainer.Add(new Cookie(name, value, "/", ".bilibili.com") { Expires = dateTime });
|
||||
|
||||
}
|
||||
|
||||
return cookieContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析从浏览器获取的cookies,用于设置cookie
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static CookieContainer ParseCookieByString(string str)
|
||||
{
|
||||
var cookieContainer = new CookieContainer();
|
||||
|
||||
var cookies = str.Replace(" ", "").Split(";");
|
||||
foreach (var cookie in cookies)
|
||||
{
|
||||
DateTime dateTime = DateTime.Now;
|
||||
dateTime = dateTime.AddMonths(12);
|
||||
|
||||
var temp = cookie.Split("=");
|
||||
var name = temp[0];
|
||||
var value = temp[1];
|
||||
|
||||
// 添加cookie
|
||||
cookieContainer.Add(new Cookie(name, value, "/", ".bilibili.com") { Expires = dateTime });
|
||||
}
|
||||
return cookieContainer;
|
||||
}
|
||||
|
||||
}
|
25
src/Downkyi.Core/Bili/ILogin.cs
Normal file
25
src/Downkyi.Core/Bili/ILogin.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Downkyi.Core.Bili.Models;
|
||||
|
||||
namespace Downkyi.Core.Bili;
|
||||
|
||||
public interface ILogin
|
||||
{
|
||||
/// <summary>
|
||||
/// 申请二维码
|
||||
/// </summary>
|
||||
/// <returns>(url, key)</returns>
|
||||
Tuple<string, string> GetQRCodeUrl();
|
||||
|
||||
/// <summary>
|
||||
/// 扫码登录
|
||||
/// </summary>
|
||||
/// <param name="qrcodeKey"></param>
|
||||
/// <returns></returns>
|
||||
QRCodeStatus PollQRCode(string qrcodeKey);
|
||||
|
||||
/// <summary>
|
||||
/// 导航栏用户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
NavigationInfo GetNavigationInfo();
|
||||
}
|
5
src/Downkyi.Core/Bili/IUser.cs
Normal file
5
src/Downkyi.Core/Bili/IUser.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace Downkyi.Core.Bili;
|
||||
|
||||
public interface IUser
|
||||
{
|
||||
}
|
10
src/Downkyi.Core/Bili/Models/NavigationInfo.cs
Normal file
10
src/Downkyi.Core/Bili/Models/NavigationInfo.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Downkyi.Core.Bili.Models;
|
||||
|
||||
public class NavigationInfo
|
||||
{
|
||||
public long Mid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Header { get; set; }
|
||||
public int VipStatus { get; set; } // 会员开通状态 // 0:无;1:有
|
||||
public bool IsLogin { get; set; }
|
||||
}
|
17
src/Downkyi.Core/Bili/Models/QRCodeStatus.cs
Normal file
17
src/Downkyi.Core/Bili/Models/QRCodeStatus.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Downkyi.Core.Bili.Models;
|
||||
|
||||
public class QRCodeStatus
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Token { get; set; }
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 0:扫码登录成功
|
||||
/// 86038:二维码已失效
|
||||
/// 86090:二维码已扫码未确认
|
||||
/// 86101:未扫码
|
||||
/// </summary>
|
||||
public long Code { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
7
src/Downkyi.Core/Bili/Models/Quality.cs
Normal file
7
src/Downkyi.Core/Bili/Models/Quality.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Downkyi.Core.Bili.Models;
|
||||
|
||||
public class Quality
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Id { get; set; }
|
||||
}
|
60
src/Downkyi.Core/Bili/Utils/BvId.cs
Normal file
60
src/Downkyi.Core/Bili/Utils/BvId.cs
Normal file
@ -0,0 +1,60 @@
|
||||
namespace Downkyi.Core.Bili.Utils;
|
||||
|
||||
public static class BvId
|
||||
{
|
||||
private const string tableStr = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"; //码表
|
||||
private static readonly char[] table = tableStr.ToCharArray();
|
||||
|
||||
private static readonly char[] tr = new char[124]; //反查码表
|
||||
private const ulong Xor = 177451812; //固定异或值
|
||||
private const ulong add = 8728348608; //固定加法值
|
||||
private static readonly int[] s = { 11, 10, 3, 8, 4, 6 }; //位置编码表
|
||||
|
||||
static BvId()
|
||||
{
|
||||
Tr_init();
|
||||
}
|
||||
|
||||
//初始化反查码表
|
||||
private static void Tr_init()
|
||||
{
|
||||
for (int i = 0; i < 58; i++)
|
||||
tr[table[i]] = (char)i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bvid转avid
|
||||
/// </summary>
|
||||
/// <param name="bvid"></param>
|
||||
/// <returns></returns>
|
||||
public static ulong Bv2Av(string bvid)
|
||||
{
|
||||
char[] bv = bvid.ToCharArray();
|
||||
|
||||
ulong r = 0;
|
||||
ulong av;
|
||||
for (int i = 0; i < 6; i++)
|
||||
r += tr[bv[s[i]]] * (ulong)Math.Pow(58, i);
|
||||
av = (r - add) ^ Xor;
|
||||
return av;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// avid转bvid
|
||||
/// </summary>
|
||||
/// <param name="av"></param>
|
||||
/// <returns></returns>
|
||||
public static string Av2Bv(ulong av)
|
||||
{
|
||||
//编码结果
|
||||
string res = "BV1 4 1 7 ";
|
||||
char[] result = res.ToCharArray();
|
||||
|
||||
av = (av ^ Xor) + add;
|
||||
for (int i = 0; i < 6; i++)
|
||||
result[s[i]] = table[av / (ulong)Math.Pow(58, i) % 58];
|
||||
var bv = new string(result);
|
||||
return bv;
|
||||
}
|
||||
|
||||
}
|
151
src/Downkyi.Core/Bili/Utils/DanmakuSender.cs
Normal file
151
src/Downkyi.Core/Bili/Utils/DanmakuSender.cs
Normal file
@ -0,0 +1,151 @@
|
||||
namespace Downkyi.Core.Bili.Utils;
|
||||
|
||||
public static class DanmakuSender
|
||||
{
|
||||
private const uint CRCPOLYNOMIAL = 0xEDB88320;
|
||||
private static readonly uint[] crctable = new uint[256];
|
||||
|
||||
static DanmakuSender()
|
||||
{
|
||||
CreateTable();
|
||||
}
|
||||
|
||||
private static void CreateTable()
|
||||
{
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
uint crcreg = (uint)i;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
if ((crcreg & 1) != 0)
|
||||
{
|
||||
crcreg = CRCPOLYNOMIAL ^ (crcreg >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
crcreg >>= 1;
|
||||
}
|
||||
}
|
||||
crctable[i] = crcreg;
|
||||
}
|
||||
}
|
||||
|
||||
private static uint Crc32(string userId)
|
||||
{
|
||||
uint crcstart = 0xFFFFFFFF;
|
||||
for (int i = 0; i < userId.Length; i++)
|
||||
{
|
||||
uint index = (uint)(crcstart ^ (int)userId[i]) & 255;
|
||||
crcstart = (crcstart >> 8) ^ crctable[index];
|
||||
}
|
||||
return crcstart;
|
||||
}
|
||||
|
||||
private static uint Crc32LastIndex(string userId)
|
||||
{
|
||||
uint index = 0;
|
||||
uint crcstart = 0xFFFFFFFF;
|
||||
for (int i = 0; i < userId.Length; i++)
|
||||
{
|
||||
index = (uint)((crcstart ^ (int)userId[i]) & 255);
|
||||
crcstart = (crcstart >> 8) ^ crctable[index];
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private static int GetCrcIndex(long t)
|
||||
{
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
if ((crctable[i] >> 24) == t)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static object[] DeepCheck(int i, int[] index)
|
||||
{
|
||||
object[] resultArray = new object[2];
|
||||
|
||||
string result = "";
|
||||
uint tc;// = 0x00;
|
||||
var hashcode = Crc32(i.ToString());
|
||||
tc = (uint)(hashcode & 0xff ^ index[2]);
|
||||
|
||||
if (!(tc <= 57 && tc >= 48))
|
||||
{
|
||||
resultArray[0] = 0;
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
result += (tc - 48).ToString();
|
||||
hashcode = crctable[index[2]] ^ (hashcode >> 8);
|
||||
tc = (uint)(hashcode & 0xff ^ index[1]);
|
||||
|
||||
if (!(tc <= 57 && tc >= 48))
|
||||
{
|
||||
resultArray[0] = 0;
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
result += (tc - 48).ToString();
|
||||
hashcode = crctable[index[1]] ^ (hashcode >> 8);
|
||||
tc = (uint)(hashcode & 0xff ^ index[0]);
|
||||
|
||||
if (!(tc <= 57 && tc >= 48))
|
||||
{
|
||||
resultArray[0] = 0;
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
result += (tc - 48).ToString();
|
||||
//hashcode = crctable[index[0]] ^ (hashcode >> 8);
|
||||
|
||||
resultArray[0] = 1;
|
||||
resultArray[1] = result;
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询弹幕发送者
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string FindDanmakuSender(string userId)
|
||||
{
|
||||
object[] deepCheckData = new object[2];
|
||||
|
||||
int[] index = new int[4];
|
||||
uint ht = (uint)Convert.ToInt32($"0x{userId}", 16);
|
||||
ht ^= 0xffffffff;
|
||||
|
||||
int i;
|
||||
for (i = 3; i > -1; i--)
|
||||
{
|
||||
index[3 - i] = GetCrcIndex(ht >> (i * 8));
|
||||
uint snum = crctable[index[3 - i]];
|
||||
ht ^= snum >> ((3 - i) * 8);
|
||||
}
|
||||
for (i = 0; i < 100000000; i++)
|
||||
{
|
||||
uint lastindex = Crc32LastIndex(i.ToString());
|
||||
if (lastindex == index[3])
|
||||
{
|
||||
deepCheckData = DeepCheck(i, index);
|
||||
if ((int)deepCheckData[0] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == 100000000)
|
||||
{
|
||||
return "-1";
|
||||
}
|
||||
return $"{i}{deepCheckData[1]}";
|
||||
}
|
||||
|
||||
}
|
556
src/Downkyi.Core/Bili/Utils/ParseEntrance.cs
Normal file
556
src/Downkyi.Core/Bili/Utils/ParseEntrance.cs
Normal file
@ -0,0 +1,556 @@
|
||||
using Downkyi.Core.Utils.Validator;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Downkyi.Core.Bili.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// 解析输入的字符串<para/>
|
||||
/// 支持的格式有:<para/>
|
||||
/// av号:av170001, AV170001, https://www.bilibili.com/video/av170001 <para/>
|
||||
/// BV号:BV17x411w7KC, https://www.bilibili.com/video/BV17x411w7KC, https://b23.tv/BV17x411w7KC <para/>
|
||||
/// 番剧(电影、电视剧)ss号:ss32982, SS32982, https://www.bilibili.com/bangumi/play/ss32982 <para/>
|
||||
/// 番剧(电影、电视剧)ep号:ep317925, EP317925, https://www.bilibili.com/bangumi/play/ep317925 <para/>
|
||||
/// 番剧(电影、电视剧)md号:md28228367, MD28228367, https://www.bilibili.com/bangumi/media/md28228367 <para/>
|
||||
/// 课程ss号:https://www.bilibili.com/cheese/play/ss205 <para/>
|
||||
/// 课程ep号:https://www.bilibili.com/cheese/play/ep3489 <para/>
|
||||
/// 收藏夹:ml1329019876, ML1329019876, https://www.bilibili.com/medialist/detail/ml1329019876, https://www.bilibili.com/medialist/play/ml1329019876/ <para/>
|
||||
/// 用户空间:uid928123, UID928123, uid:928123, UID:928123, https://space.bilibili.com/928123
|
||||
/// </summary>
|
||||
public static class ParseEntrance
|
||||
{
|
||||
public static readonly string WwwUrl = "https://www.bilibili.com";
|
||||
public static readonly string ShareWwwUrl = "https://www.bilibili.com/s";
|
||||
public static readonly string ShortUrl = "https://b23.tv/";
|
||||
public static readonly string MobileUrl = "https://m.bilibili.com";
|
||||
|
||||
public static readonly string SpaceUrl = "https://space.bilibili.com";
|
||||
|
||||
public static readonly string VideoUrl = $"{WwwUrl}/video/";
|
||||
public static readonly string BangumiUrl = $"{WwwUrl}/bangumi/play/";
|
||||
public static readonly string BangumiMediaUrl = $"{WwwUrl}/bangumi/media/";
|
||||
public static readonly string CheeseUrl = $"{WwwUrl}/cheese/play/";
|
||||
public static readonly string FavoritesUrl1 = $"{WwwUrl}/medialist/detail/";
|
||||
public static readonly string FavoritesUrl2 = $"{WwwUrl}/medialist/play/";
|
||||
|
||||
#region 视频
|
||||
|
||||
/// <summary>
|
||||
/// 是否为av id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsAvId(string input)
|
||||
{
|
||||
return IsIntId(input, "av");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为av url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsAvUrl(string input)
|
||||
{
|
||||
string id = GetVideoId(input);
|
||||
return IsAvId(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取av id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetAvId(string input)
|
||||
{
|
||||
if (IsAvId(input))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 2));
|
||||
}
|
||||
else if (IsAvUrl(input))
|
||||
{
|
||||
return Number.GetInt(GetVideoId(input).Remove(0, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为bv id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBvId(string input)
|
||||
{
|
||||
return input.StartsWith("BV") && input.Length == 12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为bv url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBvUrl(string input)
|
||||
{
|
||||
string id = GetVideoId(input);
|
||||
return IsBvId(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取bv id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetBvId(string input)
|
||||
{
|
||||
if (IsBvId(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
else if (IsBvUrl(input))
|
||||
{
|
||||
return GetVideoId(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 番剧(电影、电视剧)
|
||||
|
||||
/// <summary>
|
||||
/// 是否为番剧season id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBangumiSeasonId(string input)
|
||||
{
|
||||
return IsIntId(input, "ss");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为番剧season url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBangumiSeasonUrl(string input)
|
||||
{
|
||||
string id = GetBangumiId(input);
|
||||
return IsBangumiSeasonId(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取番剧season id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetBangumiSeasonId(string input)
|
||||
{
|
||||
if (IsBangumiSeasonId(input))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 2));
|
||||
}
|
||||
else if (IsBangumiSeasonUrl(input))
|
||||
{
|
||||
return Number.GetInt(GetBangumiId(input).Remove(0, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为番剧episode id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBangumiEpisodeId(string input)
|
||||
{
|
||||
return IsIntId(input, "ep");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为番剧episode url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBangumiEpisodeUrl(string input)
|
||||
{
|
||||
string id = GetBangumiId(input);
|
||||
return IsBangumiEpisodeId(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取番剧episode id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetBangumiEpisodeId(string input)
|
||||
{
|
||||
if (IsBangumiEpisodeId(input))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 2));
|
||||
}
|
||||
else if (IsBangumiEpisodeUrl(input))
|
||||
{
|
||||
return Number.GetInt(GetBangumiId(input).Remove(0, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为番剧media id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBangumiMediaId(string input)
|
||||
{
|
||||
return IsIntId(input, "md");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为番剧media url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBangumiMediaUrl(string input)
|
||||
{
|
||||
string id = GetBangumiId(input);
|
||||
return IsBangumiMediaId(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取番剧media id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetBangumiMediaId(string input)
|
||||
{
|
||||
if (IsBangumiMediaId(input))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 2));
|
||||
}
|
||||
else if (IsBangumiMediaUrl(input))
|
||||
{
|
||||
return Number.GetInt(GetBangumiId(input).Remove(0, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 课程
|
||||
|
||||
/// <summary>
|
||||
/// 是否为课程season url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsCheeseSeasonUrl(string input)
|
||||
{
|
||||
string id = GetCheeseId(input);
|
||||
return IsIntId(id, "ss");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取课程season id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetCheeseSeasonId(string input)
|
||||
{
|
||||
return IsCheeseSeasonUrl(input) ? Number.GetInt(GetCheeseId(input).Remove(0, 2)) : -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为课程episode url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsCheeseEpisodeUrl(string input)
|
||||
{
|
||||
string id = GetCheeseId(input);
|
||||
return IsIntId(id, "ep");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取课程episode id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetCheeseEpisodeId(string input)
|
||||
{
|
||||
return IsCheeseEpisodeUrl(input) ? Number.GetInt(GetCheeseId(input).Remove(0, 2)) : -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 收藏夹
|
||||
|
||||
/// <summary>
|
||||
/// 是否为收藏夹id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsFavoritesId(string input)
|
||||
{
|
||||
return IsIntId(input, "ml");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为收藏夹url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsFavoritesUrl(string input)
|
||||
{
|
||||
return IsFavoritesUrl1(input) || IsFavoritesUrl2(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为收藏夹url1
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsFavoritesUrl1(string input)
|
||||
{
|
||||
string favoritesId = GetId(input, FavoritesUrl1);
|
||||
return IsFavoritesId(favoritesId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为收藏夹ur2
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsFavoritesUrl2(string input)
|
||||
{
|
||||
string favoritesId = GetId(input, FavoritesUrl2);
|
||||
return IsFavoritesId(favoritesId.Split('/')[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取收藏夹id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetFavoritesId(string input)
|
||||
{
|
||||
if (IsFavoritesId(input))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 2));
|
||||
}
|
||||
else if (IsFavoritesUrl1(input))
|
||||
{
|
||||
return Number.GetInt(GetId(input, FavoritesUrl1).Remove(0, 2));
|
||||
}
|
||||
else if (IsFavoritesUrl2(input))
|
||||
{
|
||||
return Number.GetInt(GetId(input, FavoritesUrl2).Remove(0, 2).Split('/')[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 用户空间
|
||||
|
||||
/// <summary>
|
||||
/// 是否为用户id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsUserId(string input)
|
||||
{
|
||||
if (input.ToLower().StartsWith("uid:"))
|
||||
{
|
||||
return Regex.IsMatch(input.Remove(0, 4), @"^\d+$");
|
||||
}
|
||||
else if (input.ToLower().StartsWith("uid"))
|
||||
{
|
||||
return Regex.IsMatch(input.Remove(0, 3), @"^\d+$");
|
||||
}
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为用户空间url
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsUserUrl(string input)
|
||||
{
|
||||
if (!IsUrl(input)) { return false; }
|
||||
|
||||
if (input.Contains("space.bilibili.com"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户mid
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUserId(string input)
|
||||
{
|
||||
if (input.ToLower().StartsWith("uid:"))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 4));
|
||||
}
|
||||
else if (input.ToLower().StartsWith("uid"))
|
||||
{
|
||||
return Number.GetInt(input.Remove(0, 3));
|
||||
}
|
||||
else if (IsUserUrl(input))
|
||||
{
|
||||
string url = EnableHttps(input);
|
||||
url = DeleteUrlParam(url);
|
||||
var match = Regex.Match(url, @"\d+");
|
||||
if (match.Success)
|
||||
{
|
||||
return long.Parse(match.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 是否为网址
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsUrl(string input)
|
||||
{
|
||||
return input.StartsWith("http://") || input.StartsWith("https://");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将http转为https
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string EnableHttps(string url)
|
||||
{
|
||||
if (!IsUrl(url)) { return null; }
|
||||
|
||||
return url.Replace("http://", "https://");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除url中的参数
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
private static string DeleteUrlParam(string url)
|
||||
{
|
||||
string[] strList = url.Split('?');
|
||||
|
||||
return strList[0].EndsWith("/") ? strList[0].TrimEnd('/') : strList[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从url中获取视频id(avid/bvid)
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetVideoId(string input)
|
||||
{
|
||||
return GetId(input, VideoUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从url中获取番剧id(ss/ep/md)
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetBangumiId(string input)
|
||||
{
|
||||
string id = GetId(input, BangumiUrl);
|
||||
if (id != "") { return id; }
|
||||
return GetId(input, BangumiMediaUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从url中获取课程id(ss/ep)
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetCheeseId(string input)
|
||||
{
|
||||
return GetId(input, CheeseUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为数字型id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsIntId(string input, string prefix)
|
||||
{
|
||||
if (input.ToLower().StartsWith(prefix))
|
||||
{
|
||||
return Regex.IsMatch(input.Remove(0, 2), @"^\d+$");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从url中获取id
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="baseUrl"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetId(string input, string baseUrl)
|
||||
{
|
||||
if (!IsUrl(input)) { return ""; }
|
||||
|
||||
string url = EnableHttps(input);
|
||||
url = DeleteUrlParam(url);
|
||||
|
||||
url = url.Replace(ShareWwwUrl, WwwUrl);
|
||||
url = url.Replace(MobileUrl, WwwUrl);
|
||||
|
||||
if (url.Contains("b23.tv/ss") || url.Contains("b23.tv/ep"))
|
||||
{
|
||||
url = url.Replace(ShortUrl, BangumiUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
url = url.Replace(ShortUrl, VideoUrl);
|
||||
}
|
||||
|
||||
if (!url.StartsWith(baseUrl)) { return ""; }
|
||||
|
||||
return url.Replace(baseUrl, "");
|
||||
}
|
||||
|
||||
}
|
73
src/Downkyi.Core/Bili/Utils/QualityList.cs
Normal file
73
src/Downkyi.Core/Bili/Utils/QualityList.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Downkyi.Core.Bili.Models;
|
||||
|
||||
namespace Downkyi.Core.Bili.Utils;
|
||||
|
||||
public static class QualityList
|
||||
{
|
||||
private static readonly List<Quality> resolutions = new()
|
||||
{
|
||||
new Quality { Name = "360P 流畅", Id = 16 },
|
||||
new Quality { Name = "480P 清晰", Id = 32 },
|
||||
new Quality { Name = "720P 高清", Id = 64 },
|
||||
new Quality { Name = "720P 60帧", Id = 74 },
|
||||
new Quality { Name = "1080P 高清", Id = 80 },
|
||||
new Quality { Name = "1080P 高码率", Id = 112 },
|
||||
new Quality { Name = "1080P 60帧", Id = 116 },
|
||||
new Quality { Name = "4K 超清", Id = 120 },
|
||||
new Quality { Name = "HDR 真彩", Id = 125 },
|
||||
new Quality { Name = "杜比视界", Id = 126 },
|
||||
new Quality { Name = "超高清 8K", Id = 127 },
|
||||
};
|
||||
|
||||
private static readonly List<Quality> codecIds = new()
|
||||
{
|
||||
new Quality { Name = "H.264/AVC", Id = 7 },
|
||||
new Quality { Name = "H.265/HEVC", Id = 12 },
|
||||
new Quality { Name = "AV1", Id = 13 },
|
||||
};
|
||||
|
||||
private static readonly List<Quality> qualities = new()
|
||||
{
|
||||
new Quality { Name = "低质量", Id = 30216 },
|
||||
new Quality { Name = "中质量", Id = 30232 },
|
||||
new Quality { Name = "高质量", Id = 30280 },
|
||||
new Quality { Name = "Dolby Atmos", Id = 30250 },
|
||||
new Quality { Name = "Hi-Res无损", Id = 30251 },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取支持的视频画质
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<Quality> GetResolutions()
|
||||
{
|
||||
// 使用深复制,
|
||||
// 保证外部修改list后,
|
||||
// 不会影响其他调用处
|
||||
return new List<Quality>(resolutions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视频编码代码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<Quality> GetCodecIds()
|
||||
{
|
||||
// 使用深复制,
|
||||
// 保证外部修改list后,
|
||||
// 不会影响其他调用处
|
||||
return new List<Quality>(codecIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支持的视频音质
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<Quality> GetAudioQualities()
|
||||
{
|
||||
// 使用深复制,
|
||||
// 保证外部修改list后,
|
||||
// 不会影响其他调用处
|
||||
return new List<Quality>(qualities);
|
||||
}
|
||||
}
|
72
src/Downkyi.Core/Bili/Web/Login.cs
Normal file
72
src/Downkyi.Core/Bili/Web/Login.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using BiliSharp;
|
||||
using BiliSharp.Api.Login;
|
||||
using Downkyi.Core.Bili.Models;
|
||||
using Downkyi.Core.Settings;
|
||||
|
||||
namespace Downkyi.Core.Bili.Web;
|
||||
|
||||
public class Login : ILogin
|
||||
{
|
||||
/// <summary>
|
||||
/// 申请二维码(web端)
|
||||
/// </summary>
|
||||
/// <returns>(url, key)</returns>
|
||||
public Tuple<string, string> GetQRCodeUrl()
|
||||
{
|
||||
string userAgent = SettingsManager.GetInstance().GetUserAgent();
|
||||
BiliManager.Instance().SetUserAgent(userAgent);
|
||||
|
||||
var qrcode = LoginQR.GenerateQRCode();
|
||||
if (qrcode == null || qrcode.Data == null) { return null; }
|
||||
|
||||
return Tuple.Create(qrcode.Data.Url, qrcode.Data.QrcodeKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫码登录(web端)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public QRCodeStatus PollQRCode(string qrcodeKey)
|
||||
{
|
||||
string userAgent = SettingsManager.GetInstance().GetUserAgent();
|
||||
BiliManager.Instance().SetUserAgent(userAgent);
|
||||
|
||||
var qrcode = LoginQR.PollQRCode(qrcodeKey);
|
||||
if (qrcode == null || qrcode.Data == null) { return null; }
|
||||
|
||||
return new QRCodeStatus()
|
||||
{
|
||||
Url = qrcode.Data.Url,
|
||||
Token = qrcode.Data.RefreshToken,
|
||||
Timestamp = qrcode.Data.Timestamp,
|
||||
Code = qrcode.Data.Code,
|
||||
Message = qrcode.Data.Message
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导航栏用户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public NavigationInfo GetNavigationInfo()
|
||||
{
|
||||
string userAgent = SettingsManager.GetInstance().GetUserAgent();
|
||||
BiliManager.Instance().SetUserAgent(userAgent);
|
||||
BiliManager.Instance().SetCookies(LoginHelper.GetLoginInfoCookies());
|
||||
|
||||
var origin = LoginInfo.GetNavigationInfo();
|
||||
if (origin == null || origin.Data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new NavigationInfo()
|
||||
{
|
||||
Mid = origin.Data.Mid,
|
||||
Name = origin.Data.Uname,
|
||||
Header = origin.Data.Face,
|
||||
VipStatus = origin.Data.Vipstatus,
|
||||
IsLogin = origin.Data.Islogin
|
||||
};
|
||||
}
|
||||
}
|
137
src/Downkyi.Core/Bili/Web/LoginHelper.cs
Normal file
137
src/Downkyi.Core/Bili/Web/LoginHelper.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Downkyi.Core.Settings;
|
||||
using Downkyi.Core.Settings.Models;
|
||||
using Downkyi.Core.Utils.Encryptor;
|
||||
using System.Net;
|
||||
|
||||
namespace Downkyi.Core.Bili.Web;
|
||||
|
||||
public static class LoginHelper
|
||||
{
|
||||
// 本地位置
|
||||
private static readonly string LOCAL_LOGIN_INFO = Storage.StorageManager.GetLogin();
|
||||
|
||||
private static readonly EncryptorFile encryptor = new();
|
||||
|
||||
/// <summary>
|
||||
/// 保存登录的cookies到文件
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static bool SaveLoginInfoCookies(string url)
|
||||
{
|
||||
CookieContainer cookieContainer = Cookies.ParseCookieByUrl(url);
|
||||
|
||||
return SaveLoginInfoCookies(cookieContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存cookies到文件
|
||||
/// </summary>
|
||||
/// <param name="cookieContainer"></param>
|
||||
/// <returns></returns>
|
||||
public static bool SaveLoginInfoCookies(CookieContainer cookieContainer)
|
||||
{
|
||||
string tempFile = LOCAL_LOGIN_INFO + "-" + Guid.NewGuid().ToString("N");
|
||||
|
||||
bool isSucceed = Cookies.WriteCookiesToDisk(tempFile, cookieContainer);
|
||||
if (isSucceed)
|
||||
{
|
||||
try
|
||||
{
|
||||
encryptor.EncryptFile(tempFile, LOCAL_LOGIN_INFO);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
return isSucceed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得登录的cookies
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static CookieContainer GetLoginInfoCookies()
|
||||
{
|
||||
string tempFile = LOCAL_LOGIN_INFO + "-" + Guid.NewGuid().ToString("N");
|
||||
|
||||
if (File.Exists(LOCAL_LOGIN_INFO))
|
||||
{
|
||||
try
|
||||
{
|
||||
encryptor.DecryptFile(LOCAL_LOGIN_INFO, tempFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else { return null; }
|
||||
|
||||
CookieContainer cookies = Cookies.ReadCookiesFromDisk(tempFile);
|
||||
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回登录信息的cookies的字符串
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetLoginInfoCookiesString()
|
||||
{
|
||||
var cookieContainer = GetLoginInfoCookies();
|
||||
if (cookieContainer == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return Cookies.GetCookiesString(cookieContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销登录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool Logout()
|
||||
{
|
||||
if (File.Exists(LOCAL_LOGIN_INFO))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(LOCAL_LOGIN_INFO);
|
||||
|
||||
SettingsManager.GetInstance().SetUserInfo(new UserInfoSettings
|
||||
{
|
||||
Mid = -1,
|
||||
Name = "",
|
||||
IsLogin = false,
|
||||
IsVip = false
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
5
src/Downkyi.Core/Bili/Web/User.cs
Normal file
5
src/Downkyi.Core/Bili/Web/User.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace Downkyi.Core.Bili.Web;
|
||||
|
||||
public class User : IUser
|
||||
{
|
||||
}
|
22
src/Downkyi.Core/Downkyi.Core.csproj
Normal file
22
src/Downkyi.Core/Downkyi.Core.csproj
Normal file
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aria2cNet" Version="1.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.2.2" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BiliSharp\BiliSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
441
src/Downkyi.Core/Downloader/MultiThreadDownloader.cs
Normal file
441
src/Downkyi.Core/Downloader/MultiThreadDownloader.cs
Normal file
@ -0,0 +1,441 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
|
||||
namespace Downkyi.Core.Downloader;
|
||||
|
||||
/// <summary>
|
||||
/// 文件合并改变事件
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
public delegate void FileMergeProgressChangedEventHandler(object sender, int e);
|
||||
|
||||
/// <summary>
|
||||
/// 多线程下载器
|
||||
/// </summary>
|
||||
public class MultiThreadDownloader
|
||||
{
|
||||
#region 属性
|
||||
|
||||
private string _url;
|
||||
private bool _rangeAllowed;
|
||||
private readonly HttpWebRequest _request;
|
||||
private Action<HttpWebRequest> _requestConfigure = req => req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36";
|
||||
|
||||
#endregion 属性
|
||||
|
||||
#region 公共属性
|
||||
|
||||
/// <summary>
|
||||
/// RangeAllowed
|
||||
/// </summary>
|
||||
public bool RangeAllowed
|
||||
{
|
||||
get => _rangeAllowed;
|
||||
set => _rangeAllowed = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 临时文件夹
|
||||
/// </summary>
|
||||
public string TempFileDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// url地址
|
||||
/// </summary>
|
||||
public string Url
|
||||
{
|
||||
get => _url;
|
||||
set => _url = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 第几部分
|
||||
/// </summary>
|
||||
public int NumberOfParts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已接收字节数
|
||||
/// </summary>
|
||||
public long TotalBytesReceived
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return PartialDownloaderList.Where(t => t != null).Sum(t => t.TotalBytesRead);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 总进度
|
||||
/// </summary>
|
||||
public float TotalProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件大小
|
||||
/// </summary>
|
||||
public long Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下载速度
|
||||
/// </summary>
|
||||
public float TotalSpeedInBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return PartialDownloaderList.Sum(t => t.SpeedInBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载块
|
||||
/// </summary>
|
||||
public List<PartialDownloader> PartialDownloaderList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
#endregion 公共属性
|
||||
|
||||
#region 变量
|
||||
|
||||
/// <summary>
|
||||
/// 总下载进度更新事件
|
||||
/// </summary>
|
||||
public event EventHandler TotalProgressChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 文件合并完成事件
|
||||
/// </summary>
|
||||
public event EventHandler FileMergedComplete;
|
||||
|
||||
/// <summary>
|
||||
/// 文件合并事件
|
||||
/// </summary>
|
||||
public event FileMergeProgressChangedEventHandler FileMergeProgressChanged;
|
||||
|
||||
private readonly AsyncOperation _aop;
|
||||
|
||||
#endregion 变量
|
||||
|
||||
#region 下载管理器
|
||||
|
||||
/// <summary>
|
||||
/// 多线程下载管理器
|
||||
/// </summary>
|
||||
/// <param name="sourceUrl"></param>
|
||||
/// <param name="tempDir"></param>
|
||||
/// <param name="savePath"></param>
|
||||
/// <param name="numOfParts"></param>
|
||||
public MultiThreadDownloader(string sourceUrl, string tempDir, string savePath, int numOfParts)
|
||||
{
|
||||
_url = sourceUrl;
|
||||
NumberOfParts = numOfParts;
|
||||
TempFileDirectory = tempDir;
|
||||
PartialDownloaderList = new List<PartialDownloader>();
|
||||
_aop = AsyncOperationManager.CreateOperation(null);
|
||||
FilePath = savePath;
|
||||
#pragma warning disable SYSLIB0014 // 类型或成员已过时
|
||||
_request = WebRequest.Create(sourceUrl) as HttpWebRequest;
|
||||
#pragma warning restore SYSLIB0014 // 类型或成员已过时
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多线程下载管理器
|
||||
/// </summary>
|
||||
/// <param name="sourceUrl"></param>
|
||||
/// <param name="savePath"></param>
|
||||
/// <param name="numOfParts"></param>
|
||||
public MultiThreadDownloader(string sourceUrl, string savePath, int numOfParts) : this(sourceUrl, null, savePath, numOfParts)
|
||||
{
|
||||
TempFileDirectory = Environment.GetEnvironmentVariable("temp");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多线程下载管理器
|
||||
/// </summary>
|
||||
/// <param name="sourceUrl"></param>
|
||||
/// <param name="numOfParts"></param>
|
||||
public MultiThreadDownloader(string sourceUrl, int numOfParts) : this(sourceUrl, null, numOfParts)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion 下载管理器
|
||||
|
||||
#region 事件
|
||||
|
||||
private void temp_DownloadPartCompleted(object sender, EventArgs e)
|
||||
{
|
||||
WaitOrResumeAll(PartialDownloaderList, true);
|
||||
|
||||
if (TotalBytesReceived == Size)
|
||||
{
|
||||
UpdateProgress();
|
||||
MergeParts();
|
||||
return;
|
||||
}
|
||||
|
||||
PartialDownloaderList.Sort((x, y) => (int)(y.RemainingBytes - x.RemainingBytes));
|
||||
var rem = PartialDownloaderList[0].RemainingBytes;
|
||||
if (rem < 50 * 1024)
|
||||
{
|
||||
WaitOrResumeAll(PartialDownloaderList, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var from = PartialDownloaderList[0].CurrentPosition + rem / 2;
|
||||
var to = PartialDownloaderList[0].To;
|
||||
if (from > to)
|
||||
{
|
||||
WaitOrResumeAll(PartialDownloaderList, false);
|
||||
return;
|
||||
}
|
||||
|
||||
PartialDownloaderList[0].To = from - 1;
|
||||
WaitOrResumeAll(PartialDownloaderList, false);
|
||||
var temp = new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString(), from, to, true);
|
||||
temp.DownloadPartCompleted += temp_DownloadPartCompleted;
|
||||
temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
|
||||
lock (this)
|
||||
{
|
||||
PartialDownloaderList.Add(temp);
|
||||
}
|
||||
temp.Start(_requestConfigure);
|
||||
}
|
||||
|
||||
private void temp_DownloadPartProgressChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateProgress();
|
||||
}
|
||||
|
||||
private void UpdateProgress()
|
||||
{
|
||||
int pr = (int)(TotalBytesReceived * 1d / Size * 100);
|
||||
if (TotalProgress != pr)
|
||||
{
|
||||
TotalProgress = pr;
|
||||
if (TotalProgressChanged != null)
|
||||
{
|
||||
_aop.Post(state => TotalProgressChanged(this, EventArgs.Empty), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 事件
|
||||
|
||||
#region 方法
|
||||
|
||||
private void CreateFirstPartitions()
|
||||
{
|
||||
Size = GetContentLength(ref _rangeAllowed, ref _url);
|
||||
int maximumPart = (int)(Size / (25 * 1024));
|
||||
maximumPart = maximumPart == 0 ? 1 : maximumPart;
|
||||
if (!_rangeAllowed)
|
||||
{
|
||||
NumberOfParts = 1;
|
||||
}
|
||||
else if (NumberOfParts > maximumPart)
|
||||
{
|
||||
NumberOfParts = maximumPart;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NumberOfParts; i++)
|
||||
{
|
||||
var temp = CreateNew(i, NumberOfParts, Size);
|
||||
temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
|
||||
temp.DownloadPartCompleted += temp_DownloadPartCompleted;
|
||||
lock (this)
|
||||
{
|
||||
PartialDownloaderList.Add(temp);
|
||||
}
|
||||
temp.Start(_requestConfigure);
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeParts()
|
||||
{
|
||||
var mergeOrderedList = PartialDownloaderList.OrderBy(x => x.From);
|
||||
var dir = new FileInfo(FilePath).DirectoryName;
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
|
||||
using (var fs = File.OpenWrite(FilePath))
|
||||
{
|
||||
long totalBytesWrite = 0;
|
||||
int mergeProgress = 0;
|
||||
foreach (var item in mergeOrderedList)
|
||||
{
|
||||
using (var pdi = File.OpenRead(item.FullPath))
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = pdi.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
fs.Write(buffer, 0, read);
|
||||
totalBytesWrite += read;
|
||||
int temp = (int)(totalBytesWrite * 1d / Size * 100);
|
||||
if (temp != mergeProgress && FileMergeProgressChanged != null)
|
||||
{
|
||||
mergeProgress = temp;
|
||||
_aop.Post(state => FileMergeProgressChanged(this, temp), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(item.FullPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FileMergedComplete != null)
|
||||
{
|
||||
_aop.Post(state => FileMergedComplete(state, EventArgs.Empty), this);
|
||||
}
|
||||
}
|
||||
|
||||
private PartialDownloader CreateNew(int order, int parts, long contentLength)
|
||||
{
|
||||
var division = contentLength / parts;
|
||||
var remaining = contentLength % parts;
|
||||
var start = division * order;
|
||||
var end = start + division - 1;
|
||||
end += order == parts - 1 ? remaining : 0;
|
||||
return new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString("N"), start, end, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停或继续
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="wait"></param>
|
||||
public static void WaitOrResumeAll(List<PartialDownloader> list, bool wait)
|
||||
{
|
||||
for (var index = 0; index < list.Count; index++)
|
||||
{
|
||||
if (wait)
|
||||
{
|
||||
list[index].Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
list[index].ResumeAfterWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置请求头
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public void Configure(Action<HttpWebRequest> config)
|
||||
{
|
||||
_requestConfigure = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内容长度
|
||||
/// </summary>
|
||||
/// <param name="rangeAllowed"></param>
|
||||
/// <param name="redirectedUrl"></param>
|
||||
/// <returns></returns>
|
||||
public long GetContentLength(ref bool rangeAllowed, ref string redirectedUrl)
|
||||
{
|
||||
_request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36";
|
||||
_request.ServicePoint.ConnectionLimit = 4;
|
||||
_requestConfigure(_request);
|
||||
|
||||
using (var resp = _request.GetResponse() as HttpWebResponse)
|
||||
{
|
||||
redirectedUrl = resp.ResponseUri.OriginalString;
|
||||
var ctl = resp.ContentLength;
|
||||
rangeAllowed = resp.Headers.AllKeys.Select((v, i) => new
|
||||
{
|
||||
HeaderName = v,
|
||||
HeaderValue = resp.Headers[i]
|
||||
}).Any(k => k.HeaderName.ToLower().Contains("range") && k.HeaderValue.ToLower().Contains("byte"));
|
||||
_request.Abort();
|
||||
return ctl;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 暂停下载
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
foreach (var t in PartialDownloaderList.Where(t => !t.Completed))
|
||||
{
|
||||
t.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始下载
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
Task th = new Task(CreateFirstPartitions);
|
||||
th.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 唤醒下载
|
||||
/// </summary>
|
||||
public void Resume()
|
||||
{
|
||||
int count = PartialDownloaderList.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (PartialDownloaderList[i].Stopped)
|
||||
{
|
||||
var from = PartialDownloaderList[i].CurrentPosition + 1;
|
||||
var to = PartialDownloaderList[i].To;
|
||||
if (from > to)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var temp = new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString(), from, to, _rangeAllowed);
|
||||
temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
|
||||
temp.DownloadPartCompleted += temp_DownloadPartCompleted;
|
||||
lock (this)
|
||||
{
|
||||
PartialDownloaderList.Add(temp);
|
||||
}
|
||||
PartialDownloaderList[i].To = PartialDownloaderList[i].CurrentPosition;
|
||||
temp.Start(_requestConfigure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 公共方法
|
||||
}
|
262
src/Downkyi.Core/Downloader/PartialDownloader.cs
Normal file
262
src/Downkyi.Core/Downloader/PartialDownloader.cs
Normal file
@ -0,0 +1,262 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
|
||||
namespace Downkyi.Core.Downloader;
|
||||
|
||||
/// <summary>
|
||||
/// 部分下载器
|
||||
/// </summary>
|
||||
public class PartialDownloader
|
||||
{
|
||||
/// <summary>
|
||||
/// 这部分完成事件
|
||||
/// </summary>
|
||||
public event EventHandler DownloadPartCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 部分下载进度改变事件
|
||||
/// </summary>
|
||||
public event EventHandler DownloadPartProgressChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 部分下载停止事件
|
||||
/// </summary>
|
||||
public event EventHandler DownloadPartStopped;
|
||||
|
||||
private readonly AsyncOperation _aop = AsyncOperationManager.CreateOperation(null);
|
||||
private readonly int[] _lastSpeeds;
|
||||
private long _counter;
|
||||
private long _to;
|
||||
private long _totalBytesRead;
|
||||
private bool _wait;
|
||||
|
||||
/// <summary>
|
||||
/// 下载已停止
|
||||
/// </summary>
|
||||
public bool Stopped { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下载已完成
|
||||
/// </summary>
|
||||
public bool Completed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下载进度
|
||||
/// </summary>
|
||||
public int Progress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下载目录
|
||||
/// </summary>
|
||||
public string Directory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件名
|
||||
/// </summary>
|
||||
public string FileName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 已读字节数
|
||||
/// </summary>
|
||||
public long TotalBytesRead => _totalBytesRead;
|
||||
|
||||
/// <summary>
|
||||
/// 内容长度
|
||||
/// </summary>
|
||||
public long ContentLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RangeAllowed
|
||||
/// </summary>
|
||||
public bool RangeAllowed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// url
|
||||
/// </summary>
|
||||
public string Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// to
|
||||
/// </summary>
|
||||
public long To
|
||||
{
|
||||
get => _to;
|
||||
set
|
||||
{
|
||||
_to = value;
|
||||
ContentLength = _to - From + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// from
|
||||
/// </summary>
|
||||
public long From { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前位置
|
||||
/// </summary>
|
||||
public long CurrentPosition => From + _totalBytesRead - 1;
|
||||
|
||||
/// <summary>
|
||||
/// 剩余字节数
|
||||
/// </summary>
|
||||
public long RemainingBytes => ContentLength - _totalBytesRead;
|
||||
|
||||
/// <summary>
|
||||
/// 完整路径
|
||||
/// </summary>
|
||||
public string FullPath => Path.Combine(Directory, FileName);
|
||||
|
||||
/// <summary>
|
||||
/// 下载速度
|
||||
/// </summary>
|
||||
public int SpeedInBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Completed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int totalSpeeds = _lastSpeeds.Sum();
|
||||
return totalSpeeds / 10;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 部分块下载
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="dir"></param>
|
||||
/// <param name="fileGuid"></param>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="rangeAllowed"></param>
|
||||
public PartialDownloader(string url, string dir, string fileGuid, long from, long to, bool rangeAllowed)
|
||||
{
|
||||
From = from;
|
||||
_to = to;
|
||||
Url = url;
|
||||
RangeAllowed = rangeAllowed;
|
||||
FileName = fileGuid;
|
||||
Directory = dir;
|
||||
_lastSpeeds = new int[10];
|
||||
}
|
||||
|
||||
private void DownloadProcedure(Action<HttpWebRequest> config)
|
||||
{
|
||||
using var file = new FileStream(FullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete);
|
||||
var sw = new Stopwatch();
|
||||
#pragma warning disable SYSLIB0014 // 类型或成员已过时
|
||||
if (WebRequest.Create(Url) is HttpWebRequest req)
|
||||
{
|
||||
req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36";
|
||||
req.AllowAutoRedirect = true;
|
||||
req.MaximumAutomaticRedirections = 5;
|
||||
req.ServicePoint.ConnectionLimit += 1;
|
||||
req.ServicePoint.Expect100Continue = true;
|
||||
req.ProtocolVersion = HttpVersion.Version11;
|
||||
req.Proxy = WebRequest.GetSystemWebProxy();
|
||||
config(req);
|
||||
if (RangeAllowed)
|
||||
{
|
||||
req.AddRange(From, _to);
|
||||
}
|
||||
|
||||
if (req.GetResponse() is HttpWebResponse resp)
|
||||
{
|
||||
ContentLength = resp.ContentLength;
|
||||
if (ContentLength <= 0 || (RangeAllowed && ContentLength != _to - From + 1))
|
||||
{
|
||||
throw new Exception("Invalid response content");
|
||||
}
|
||||
|
||||
using var tempStream = resp.GetResponseStream();
|
||||
int bytesRead;
|
||||
byte[] buffer = new byte[4096];
|
||||
sw.Start();
|
||||
while ((bytesRead = tempStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
if (_totalBytesRead + bytesRead > ContentLength)
|
||||
{
|
||||
bytesRead = (int)(ContentLength - _totalBytesRead);
|
||||
}
|
||||
|
||||
file.Write(buffer, 0, bytesRead);
|
||||
_totalBytesRead += bytesRead;
|
||||
_lastSpeeds[_counter] = (int)(_totalBytesRead / Math.Ceiling(sw.Elapsed.TotalSeconds));
|
||||
_counter = (_counter >= 9) ? 0 : _counter + 1;
|
||||
int tempProgress = (int)(_totalBytesRead * 100 / ContentLength);
|
||||
if (Progress != tempProgress)
|
||||
{
|
||||
Progress = tempProgress;
|
||||
_aop.Post(state =>
|
||||
{
|
||||
DownloadPartProgressChanged?.Invoke(this, EventArgs.Empty);
|
||||
}, null);
|
||||
}
|
||||
|
||||
if (Stopped || (RangeAllowed && _totalBytesRead == ContentLength))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req.Abort();
|
||||
}
|
||||
#pragma warning restore SYSLIB0014 // 类型或成员已过时
|
||||
|
||||
sw.Stop();
|
||||
if (!Stopped && DownloadPartCompleted != null)
|
||||
{
|
||||
_aop.Post(state =>
|
||||
{
|
||||
Completed = true;
|
||||
DownloadPartCompleted(this, EventArgs.Empty);
|
||||
}, null);
|
||||
}
|
||||
|
||||
if (Stopped && DownloadPartStopped != null)
|
||||
{
|
||||
_aop.Post(state => DownloadPartStopped(this, EventArgs.Empty), null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动下载
|
||||
/// </summary>
|
||||
public void Start(Action<HttpWebRequest> config)
|
||||
{
|
||||
Stopped = false;
|
||||
var procThread = new Thread(_ => DownloadProcedure(config));
|
||||
procThread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载停止
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
Stopped = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停等待下载
|
||||
/// </summary>
|
||||
public void Wait()
|
||||
{
|
||||
_wait = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 稍后唤醒
|
||||
/// </summary>
|
||||
public void ResumeAfterWait()
|
||||
{
|
||||
_wait = false;
|
||||
}
|
||||
}
|
243
src/Downkyi.Core/FFmpeg/FFmpegHelper.cs
Normal file
243
src/Downkyi.Core/FFmpeg/FFmpegHelper.cs
Normal file
@ -0,0 +1,243 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Downkyi.Core.FFmpeg;
|
||||
|
||||
public static class FFmpegHelper
|
||||
{
|
||||
private const string Tag = "FFmpegHelper";
|
||||
|
||||
private static readonly bool is64Bit = false;
|
||||
private static readonly string exec = "";
|
||||
|
||||
static FFmpegHelper()
|
||||
{
|
||||
is64Bit = IntPtr.Size == 8;
|
||||
|
||||
if (is64Bit)
|
||||
{
|
||||
exec = Path.Combine(Environment.CurrentDirectory, "ffmpeg.exe");
|
||||
}
|
||||
else
|
||||
{
|
||||
exec = Path.Combine(Environment.CurrentDirectory, "ffmpeg.exe");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并音频和视频
|
||||
/// </summary>
|
||||
/// <param name="video1">音频</param>
|
||||
/// <param name="video2">视频</param>
|
||||
/// <param name="destVideo"></param>
|
||||
public static bool MergeVideo(string video1, string video2, string destVideo)
|
||||
{
|
||||
string param = $"-y -i \"{video1}\" -i \"{video2}\" -strict -2 -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
|
||||
if (video1 == null || !File.Exists(video1))
|
||||
{
|
||||
param = $"-y -i \"{video2}\" -strict -2 -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
|
||||
}
|
||||
if (video2 == null || !File.Exists(video2))
|
||||
{
|
||||
param = $"-y -i \"{video1}\" -strict -2 -acodec copy \"{destVideo}\"";
|
||||
}
|
||||
|
||||
// 支持flac格式音频
|
||||
//param += " -strict -2";
|
||||
|
||||
if (!File.Exists(video1) && !File.Exists(video2)) { return false; }
|
||||
|
||||
// 如果存在
|
||||
try { File.Delete(destVideo); }
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
ExcuteProcess(exec, param, null, (s, e) => Console.WriteLine(e.Data));
|
||||
|
||||
try
|
||||
{
|
||||
if (video1 != null) { File.Delete(video1); }
|
||||
if (video2 != null) { File.Delete(video2); }
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拼接多个视频
|
||||
/// </summary>
|
||||
/// <param name="workingDirectory"></param>
|
||||
/// <param name="flvFiles"></param>
|
||||
/// <param name="destVideo"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ConcatVideo(string workingDirectory, List<string> flvFiles, string destVideo)
|
||||
{
|
||||
// contact的文件名,不包含路径
|
||||
string concatFileName = Guid.NewGuid().ToString("N") + "_concat.txt";
|
||||
try
|
||||
{
|
||||
string contact = "";
|
||||
foreach (string flv in flvFiles)
|
||||
{
|
||||
contact += $"file '{flv}'\n";
|
||||
}
|
||||
|
||||
FileStream fileStream = new(workingDirectory + "/" + concatFileName, FileMode.Create);
|
||||
StreamWriter streamWriter = new(fileStream);
|
||||
//开始写入
|
||||
streamWriter.Write(contact);
|
||||
//清空缓冲区
|
||||
streamWriter.Flush();
|
||||
//关闭流
|
||||
streamWriter.Close();
|
||||
fileStream.Close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ffmpeg -y -f concat -safe 0 -i filelist.txt -c copy output.mkv
|
||||
// 加上-y,表示如果有同名文件,则默认覆盖
|
||||
string param = $"-y -f concat -safe 0 -i {concatFileName} -c copy \"{destVideo}\" -y";
|
||||
ExcuteProcess(exec, param, workingDirectory, (s, e) => Console.WriteLine(e.Data));
|
||||
|
||||
// 删除临时文件
|
||||
try
|
||||
{
|
||||
// 删除concat文件
|
||||
File.Delete(workingDirectory + "/" + concatFileName);
|
||||
|
||||
foreach (string flv in flvFiles)
|
||||
{
|
||||
File.Delete(flv);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去水印,非常消耗cpu资源
|
||||
/// </summary>
|
||||
/// <param name="video"></param>
|
||||
/// <param name="destVideo"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <param name="action"></param>
|
||||
public static void Delogo(string video, string destVideo, int x, int y, int width, int height, Action<string> action)
|
||||
{
|
||||
// ffmpeg -y -i "video.mp4" -vf delogo=x=1670:y=50:w=180:h=70:show=1 "delogo.mp4"
|
||||
string param = $"-y -i \"{video}\" -vf delogo=x={x}:y={y}:w={width}:h={height}:show=0 \"{destVideo}\" -hide_banner";
|
||||
ExcuteProcess(exec, param, null, (s, e) =>
|
||||
{
|
||||
Console.WriteLine(e.Data);
|
||||
action.Invoke(e.Data!);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从一个视频中仅提取音频
|
||||
/// </summary>
|
||||
/// <param name="video">源视频</param>
|
||||
/// <param name="audio">目标音频</param>
|
||||
/// <param name="action">输出信息</param>
|
||||
public static void ExtractAudio(string video, string audio, Action<string> action)
|
||||
{
|
||||
// 抽取音频命令
|
||||
// ffmpeg -i 3.mp4 -vn -y -acodec copy 3.aac
|
||||
// ffmpeg -i 3.mp4 -vn -y -acodec copy 3.m4a
|
||||
string param = $"-i \"{video}\" -vn -y -acodec copy \"{audio}\" -hide_banner";
|
||||
ExcuteProcess(exec, param,
|
||||
null, (s, e) =>
|
||||
{
|
||||
Console.WriteLine(e.Data);
|
||||
action.Invoke(e.Data!);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从一个视频中仅提取视频
|
||||
/// </summary>
|
||||
/// <param name="video">源视频</param>
|
||||
/// <param name="destVideo">目标视频</param>
|
||||
/// <param name="action">输出信息</param>
|
||||
public static void ExtractVideo(string video, string destVideo, Action<string> action)
|
||||
{
|
||||
// 提取视频 (Extract Video)
|
||||
// ffmpeg -i Life.of.Pi.has.subtitles.mkv -vcodec copy –an videoNoAudioSubtitle.mp4
|
||||
string param = $"-i \"{video}\" -y -vcodec copy -an \"{destVideo}\" -hide_banner";
|
||||
ExcuteProcess(exec, param,
|
||||
null, (s, e) =>
|
||||
{
|
||||
Console.WriteLine(e.Data);
|
||||
action.Invoke(e.Data!);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提取视频的帧,输出为图片
|
||||
/// </summary>
|
||||
/// <param name="video"></param>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="number"></param>
|
||||
public static void ExtractFrame(string video, string image, uint number)
|
||||
{
|
||||
// 提取帧
|
||||
// ffmpeg -i caiyilin.wmv -vframes 1 wm.bmp
|
||||
string param = $"-i \"{video}\" -y -vframes {number} \"{image}\"";
|
||||
ExcuteProcess(exec, param, null, (s, e) => Console.WriteLine(e.Data));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行一个控制台程序
|
||||
/// </summary>
|
||||
/// <param name="exe">程序名称</param>
|
||||
/// <param name="arg">参数</param>
|
||||
/// <param name="workingDirectory">工作路径</param>
|
||||
/// <param name="output">输出重定向</param>
|
||||
private static void ExcuteProcess(string exe, string arg, string workingDirectory, DataReceivedEventHandler output)
|
||||
{
|
||||
using var p = new Process();
|
||||
p.StartInfo.FileName = exe;
|
||||
p.StartInfo.Arguments = arg;
|
||||
|
||||
// 工作目录
|
||||
if (workingDirectory != null)
|
||||
{
|
||||
p.StartInfo.WorkingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
p.StartInfo.UseShellExecute = false; //输出信息重定向
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.RedirectStandardError = true;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
|
||||
// 将 StandardErrorEncoding 改为 UTF-8 才不会出现中文乱码
|
||||
p.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
|
||||
p.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
p.OutputDataReceived += output;
|
||||
p.ErrorDataReceived += output;
|
||||
|
||||
p.Start(); //启动线程
|
||||
p.BeginOutputReadLine();
|
||||
p.BeginErrorReadLine();
|
||||
p.WaitForExit(); //等待进程结束
|
||||
}
|
||||
|
||||
}
|
192
src/Downkyi.Core/FileName/FileName.cs
Normal file
192
src/Downkyi.Core/FileName/FileName.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Downkyi.Core.FileName;
|
||||
|
||||
public class FileName
|
||||
{
|
||||
private readonly List<FileNamePart> nameParts;
|
||||
private string order = "ORDER";
|
||||
private string section = "SECTION";
|
||||
private string mainTitle = "MAIN_TITLE";
|
||||
private string pageTitle = "PAGE_TITLE";
|
||||
private string videoZone = "VIDEO_ZONE";
|
||||
private string audioQuality = "AUDIO_QUALITY";
|
||||
private string videoQuality = "VIDEO_QUALITY";
|
||||
private string videoCodec = "VIDEO_CODEC";
|
||||
|
||||
private string videoPublishTime = "VIDEO_PUBLISH_TIME";
|
||||
|
||||
private long avid = -1;
|
||||
private string bvid = "BVID";
|
||||
private long cid = -1;
|
||||
|
||||
private long upMid = -1;
|
||||
private string upName = "UP_NAME";
|
||||
|
||||
private FileName(List<FileNamePart> nameParts)
|
||||
{
|
||||
this.nameParts = nameParts;
|
||||
}
|
||||
|
||||
public static FileName Builder(List<FileNamePart> nameParts)
|
||||
{
|
||||
return new FileName(nameParts);
|
||||
}
|
||||
|
||||
public FileName SetOrder(int order)
|
||||
{
|
||||
this.order = order.ToString();
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetOrder(int order, int count)
|
||||
{
|
||||
int length = Math.Abs(count).ToString().Length;
|
||||
this.order = order.ToString("D" + length);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetSection(string section)
|
||||
{
|
||||
this.section = section;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetMainTitle(string mainTitle)
|
||||
{
|
||||
this.mainTitle = mainTitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetPageTitle(string pageTitle)
|
||||
{
|
||||
this.pageTitle = pageTitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetVideoZone(string videoZone)
|
||||
{
|
||||
this.videoZone = videoZone;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetAudioQuality(string audioQuality)
|
||||
{
|
||||
this.audioQuality = audioQuality;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetVideoQuality(string videoQuality)
|
||||
{
|
||||
this.videoQuality = videoQuality;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetVideoCodec(string videoCodec)
|
||||
{
|
||||
this.videoCodec = videoCodec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetVideoPublishTime(string videoPublishTime)
|
||||
{
|
||||
this.videoPublishTime = videoPublishTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetAvid(long avid)
|
||||
{
|
||||
this.avid = avid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetBvid(string bvid)
|
||||
{
|
||||
this.bvid = bvid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetCid(long cid)
|
||||
{
|
||||
this.cid = cid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetUpMid(long upMid)
|
||||
{
|
||||
this.upMid = upMid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileName SetUpName(string upName)
|
||||
{
|
||||
this.upName = upName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public string RelativePath()
|
||||
{
|
||||
string path = string.Empty;
|
||||
|
||||
foreach (FileNamePart part in nameParts)
|
||||
{
|
||||
switch (part)
|
||||
{
|
||||
case FileNamePart.ORDER:
|
||||
path += order;
|
||||
break;
|
||||
case FileNamePart.SECTION:
|
||||
path += section;
|
||||
break;
|
||||
case FileNamePart.MAIN_TITLE:
|
||||
path += mainTitle;
|
||||
break;
|
||||
case FileNamePart.PAGE_TITLE:
|
||||
path += pageTitle;
|
||||
break;
|
||||
case FileNamePart.VIDEO_ZONE:
|
||||
path += videoZone;
|
||||
break;
|
||||
case FileNamePart.AUDIO_QUALITY:
|
||||
path += audioQuality;
|
||||
break;
|
||||
case FileNamePart.VIDEO_QUALITY:
|
||||
path += videoQuality;
|
||||
break;
|
||||
case FileNamePart.VIDEO_CODEC:
|
||||
path += videoCodec;
|
||||
break;
|
||||
case FileNamePart.VIDEO_PUBLISH_TIME:
|
||||
path += videoPublishTime;
|
||||
break;
|
||||
case FileNamePart.AVID:
|
||||
path += avid;
|
||||
break;
|
||||
case FileNamePart.BVID:
|
||||
path += bvid;
|
||||
break;
|
||||
case FileNamePart.CID:
|
||||
path += cid;
|
||||
break;
|
||||
case FileNamePart.UP_MID:
|
||||
path += upMid;
|
||||
break;
|
||||
case FileNamePart.UP_NAME:
|
||||
path += upName;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((int)part) >= 100)
|
||||
{
|
||||
path += HyphenSeparated.Hyphen[(int)part];
|
||||
}
|
||||
}
|
||||
|
||||
// 避免连续多个斜杠
|
||||
path = Regex.Replace(path, @"//+", "/");
|
||||
// 避免以斜杠开头和结尾的情况
|
||||
return path.TrimEnd('/').TrimStart('/');
|
||||
}
|
||||
|
||||
}
|
43
src/Downkyi.Core/FileName/FileNamePart.cs
Normal file
43
src/Downkyi.Core/FileName/FileNamePart.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace Downkyi.Core.FileName;
|
||||
|
||||
public enum FileNamePart
|
||||
{
|
||||
// Video
|
||||
ORDER = 1,
|
||||
SECTION,
|
||||
MAIN_TITLE,
|
||||
PAGE_TITLE,
|
||||
VIDEO_ZONE,
|
||||
AUDIO_QUALITY,
|
||||
VIDEO_QUALITY,
|
||||
VIDEO_CODEC,
|
||||
|
||||
VIDEO_PUBLISH_TIME,
|
||||
|
||||
AVID,
|
||||
BVID,
|
||||
CID,
|
||||
|
||||
UP_MID,
|
||||
UP_NAME,
|
||||
|
||||
// 斜杠
|
||||
SLASH = 100,
|
||||
|
||||
// HyphenSeparated
|
||||
UNDERSCORE = 101, // 下划线
|
||||
HYPHEN, // 连字符
|
||||
PLUS, // 加号
|
||||
COMMA, // 逗号
|
||||
PERIOD, // 句号
|
||||
AND, // and
|
||||
NUMBER, // #
|
||||
OPEN_PAREN, // 左圆括号
|
||||
CLOSE_PAREN, // 右圆括号
|
||||
OPEN_BRACKET, // 左方括号
|
||||
CLOSE_BRACKET, // 右方括号
|
||||
OPEN_BRACE, // 左花括号
|
||||
CLOSE_brace, // 右花括号
|
||||
BLANK, // 空白符
|
||||
|
||||
}
|
28
src/Downkyi.Core/FileName/HyphenSeparated.cs
Normal file
28
src/Downkyi.Core/FileName/HyphenSeparated.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Downkyi.Core.FileName;
|
||||
|
||||
/// <summary>
|
||||
/// 文件名字段
|
||||
/// </summary>
|
||||
public static class HyphenSeparated
|
||||
{
|
||||
// 文件名的分隔符
|
||||
public static Dictionary<int, string> Hyphen = new Dictionary<int, string>()
|
||||
{
|
||||
{ 100, "/" },
|
||||
{ 101, "_" },
|
||||
{ 102, "-" },
|
||||
{ 103, "+" },
|
||||
{ 104, "," },
|
||||
{ 105, "." },
|
||||
{ 106, "&" },
|
||||
{ 107, "#" },
|
||||
{ 108, "(" },
|
||||
{ 109, ")" },
|
||||
{ 110, "[" },
|
||||
{ 111, "]" },
|
||||
{ 112, "{" },
|
||||
{ 113, "}" },
|
||||
{ 114, " " },
|
||||
};
|
||||
|
||||
}
|
47
src/Downkyi.Core/Logger/Log.cs
Normal file
47
src/Downkyi.Core/Logger/Log.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using NLog;
|
||||
|
||||
namespace Downkyi.Core.Log;
|
||||
|
||||
public static class Log
|
||||
{
|
||||
static Log()
|
||||
{
|
||||
var config = new NLog.Config.LoggingConfiguration();
|
||||
|
||||
// Targets where to log to Debugger
|
||||
var logConsole = new NLog.Targets.ConsoleTarget("logConsole")
|
||||
{
|
||||
Layout = "${date:format=HH\\:mm\\:ss} [${level:padding=-5}] ${message}"
|
||||
};
|
||||
|
||||
// Targets where to log to Debugger
|
||||
var logDebugger = new NLog.Targets.DebuggerTarget("logDebugger")
|
||||
{
|
||||
Layout = "${date:format=HH\\:mm\\:ss} [${level:padding=-5}] ${message}"
|
||||
};
|
||||
|
||||
// Targets where to log to File
|
||||
var logFile = new NLog.Targets.FileTarget("logFile")
|
||||
{
|
||||
FileName = "${baseDir}/Logs/${shortdate}/${level}.log",
|
||||
CreateDirs = true,
|
||||
KeepFileOpen = true,
|
||||
ArchiveAboveSize = 1024 * 1024,
|
||||
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Sequence,
|
||||
MaxArchiveDays = 30,
|
||||
Layout = "${longdate} [${level:uppercase=false:padding=-5}] ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"
|
||||
};
|
||||
|
||||
// Rules for mapping loggers to targets
|
||||
config.AddRule(LogLevel.Trace, LogLevel.Fatal, logConsole);
|
||||
config.AddRule(LogLevel.Trace, LogLevel.Fatal, logDebugger);
|
||||
config.AddRule(LogLevel.Debug, LogLevel.Fatal, logFile);
|
||||
|
||||
// Apply config
|
||||
LogManager.Configuration = config;
|
||||
|
||||
LogManager.ThrowExceptions = false;
|
||||
}
|
||||
|
||||
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
}
|
10
src/Downkyi.Core/Settings/Enum/AfterDownloadOperation.cs
Normal file
10
src/Downkyi.Core/Settings/Enum/AfterDownloadOperation.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum AfterDownloadOperation
|
||||
{
|
||||
NOT_SET = 0,
|
||||
NONE = 1,
|
||||
OPEN_FOLDER,
|
||||
CLOSE_APP,
|
||||
CLOSE_SYSTEM
|
||||
}
|
8
src/Downkyi.Core/Settings/Enum/AllowStatus.cs
Normal file
8
src/Downkyi.Core/Settings/Enum/AllowStatus.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum AllowStatus
|
||||
{
|
||||
NONE = 0,
|
||||
NO,
|
||||
YES
|
||||
}
|
8
src/Downkyi.Core/Settings/Enum/DanmakuLayoutAlgorithm.cs
Normal file
8
src/Downkyi.Core/Settings/Enum/DanmakuLayoutAlgorithm.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum DanmakuLayoutAlgorithm
|
||||
{
|
||||
NONE = 0,
|
||||
ASYNC,
|
||||
SYNC
|
||||
}
|
8
src/Downkyi.Core/Settings/Enum/DownloadFinishedSort.cs
Normal file
8
src/Downkyi.Core/Settings/Enum/DownloadFinishedSort.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum DownloadFinishedSort
|
||||
{
|
||||
NOT_SET = 0,
|
||||
DOWNLOAD,
|
||||
NUMBER
|
||||
}
|
9
src/Downkyi.Core/Settings/Enum/Downloader.cs
Normal file
9
src/Downkyi.Core/Settings/Enum/Downloader.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum Downloader
|
||||
{
|
||||
NOT_SET = 0,
|
||||
BUILT_IN,
|
||||
ARIA,
|
||||
CUSTOM_ARIA,
|
||||
}
|
8
src/Downkyi.Core/Settings/Enum/OrderFormat.cs
Normal file
8
src/Downkyi.Core/Settings/Enum/OrderFormat.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum OrderFormat
|
||||
{
|
||||
NOT_SET = 0,
|
||||
NATURAL, // 自然数
|
||||
LEADING_ZEROS, // 前导零填充
|
||||
}
|
10
src/Downkyi.Core/Settings/Enum/ParseScope.cs
Normal file
10
src/Downkyi.Core/Settings/Enum/ParseScope.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Downkyi.Core.Settings.Enum;
|
||||
|
||||
public enum ParseScope
|
||||
{
|
||||
NOT_SET = 0,
|
||||
NONE = 1,
|
||||
SELECTED_ITEM,
|
||||
CURRENT_SECTION,
|
||||
ALL
|
||||
}
|
9
src/Downkyi.Core/Settings/Enum/VideoCodecs.cs
Normal file
9
src/Downkyi.Core/Settings/Enum/VideoCodecs.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Downkyi.Core.Settings.Enum
|
||||
{
|
||||
public enum VideoCodecs
|
||||
{
|
||||
NONE = 0,
|
||||
AVC,
|
||||
HEVC
|
||||
}
|
||||
}
|
12
src/Downkyi.Core/Settings/Models/AboutSettings.cs
Normal file
12
src/Downkyi.Core/Settings/Models/AboutSettings.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 关于
|
||||
/// </summary>
|
||||
public class AboutSettings
|
||||
{
|
||||
public AllowStatus IsReceiveBetaVersion { get; set; } = AllowStatus.NONE;
|
||||
public AllowStatus AutoUpdateWhenLaunch { get; set; } = AllowStatus.NONE;
|
||||
}
|
11
src/Downkyi.Core/Settings/Models/AppSettings.cs
Normal file
11
src/Downkyi.Core/Settings/Models/AppSettings.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
public BasicSettings Basic { get; set; } = new BasicSettings();
|
||||
public NetworkSettings Network { get; set; } = new NetworkSettings();
|
||||
public VideoSettings Video { get; set; } = new VideoSettings();
|
||||
public DanmakuSettings Danmaku { get; set; } = new DanmakuSettings();
|
||||
public AboutSettings About { get; set; } = new AboutSettings();
|
||||
public UserInfoSettings UserInfo { get; set; } = new UserInfoSettings();
|
||||
}
|
16
src/Downkyi.Core/Settings/Models/BasicSettings.cs
Normal file
16
src/Downkyi.Core/Settings/Models/BasicSettings.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 基本
|
||||
/// </summary>
|
||||
public class BasicSettings
|
||||
{
|
||||
public AfterDownloadOperation AfterDownload { get; set; } = AfterDownloadOperation.NOT_SET;
|
||||
public AllowStatus IsListenClipboard { get; set; } = AllowStatus.NONE;
|
||||
public AllowStatus IsAutoParseVideo { get; set; } = AllowStatus.NONE;
|
||||
public ParseScope ParseScope { get; set; } = ParseScope.NOT_SET;
|
||||
public AllowStatus IsAutoDownloadAll { get; set; } = AllowStatus.NONE;
|
||||
public DownloadFinishedSort DownloadFinishedSort { get; set; } = DownloadFinishedSort.NOT_SET;
|
||||
}
|
20
src/Downkyi.Core/Settings/Models/DanmakuSettings.cs
Normal file
20
src/Downkyi.Core/Settings/Models/DanmakuSettings.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 弹幕
|
||||
/// </summary>
|
||||
public class DanmakuSettings
|
||||
{
|
||||
public AllowStatus DanmakuTopFilter { get; set; } = AllowStatus.NONE;
|
||||
public AllowStatus DanmakuBottomFilter { get; set; } = AllowStatus.NONE;
|
||||
public AllowStatus DanmakuScrollFilter { get; set; } = AllowStatus.NONE;
|
||||
public AllowStatus IsCustomDanmakuResolution { get; set; } = AllowStatus.NONE;
|
||||
public int DanmakuScreenWidth { get; set; } = -1;
|
||||
public int DanmakuScreenHeight { get; set; } = -1;
|
||||
public string DanmakuFontName { get; set; } = null;
|
||||
public int DanmakuFontSize { get; set; } = -1;
|
||||
public int DanmakuLineCount { get; set; } = -1;
|
||||
public DanmakuLayoutAlgorithm DanmakuLayoutAlgorithm { get; set; } = DanmakuLayoutAlgorithm.NONE;
|
||||
}
|
40
src/Downkyi.Core/Settings/Models/NetworkSettings.cs
Normal file
40
src/Downkyi.Core/Settings/Models/NetworkSettings.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Aria2cNet.Server;
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 网络
|
||||
/// </summary>
|
||||
public class NetworkSettings
|
||||
{
|
||||
public AllowStatus IsLiftingOfRegion { get; set; } = AllowStatus.NONE;
|
||||
|
||||
public AllowStatus UseSSL { get; set; } = AllowStatus.NONE;
|
||||
public string UserAgent { get; set; } = string.Empty;
|
||||
|
||||
public Enum.Downloader Downloader { get; set; } = Enum.Downloader.NOT_SET;
|
||||
public int MaxCurrentDownloads { get; set; } = -1;
|
||||
|
||||
#region built-in
|
||||
public int Split { get; set; } = -1;
|
||||
public AllowStatus IsHttpProxy { get; set; } = AllowStatus.NONE;
|
||||
public string HttpProxy { get; set; } = null;
|
||||
public int HttpProxyListenPort { get; set; } = -1;
|
||||
#endregion
|
||||
|
||||
#region Aria
|
||||
public string AriaToken { get; set; } = null;
|
||||
public string AriaHost { get; set; } = null;
|
||||
public int AriaListenPort { get; set; } = -1;
|
||||
public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET;
|
||||
public int AriaSplit { get; set; } = -1;
|
||||
public int AriaMaxOverallDownloadLimit { get; set; } = -1;
|
||||
public int AriaMaxDownloadLimit { get; set; } = -1;
|
||||
public AriaConfigFileAllocation AriaFileAllocation { get; set; } = AriaConfigFileAllocation.NOT_SET;
|
||||
|
||||
public AllowStatus IsAriaHttpProxy { get; set; } = AllowStatus.NONE;
|
||||
public string AriaHttpProxy { get; set; } = null;
|
||||
public int AriaHttpProxyListenPort { get; set; } = -1;
|
||||
#endregion
|
||||
}
|
9
src/Downkyi.Core/Settings/Models/UserInfoSettings.cs
Normal file
9
src/Downkyi.Core/Settings/Models/UserInfoSettings.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
public class UserInfoSettings
|
||||
{
|
||||
public long Mid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsLogin { get; set; } // 是否登录
|
||||
public bool IsVip { get; set; } // 是否为大会员,未登录时为false
|
||||
}
|
10
src/Downkyi.Core/Settings/Models/VideoContentSettings.cs
Normal file
10
src/Downkyi.Core/Settings/Models/VideoContentSettings.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
public class VideoContentSettings
|
||||
{
|
||||
public bool DownloadAudio { get; set; } = true;
|
||||
public bool DownloadVideo { get; set; } = true;
|
||||
public bool DownloadDanmaku { get; set; } = true;
|
||||
public bool DownloadSubtitle { get; set; } = true;
|
||||
public bool DownloadCover { get; set; } = true;
|
||||
}
|
22
src/Downkyi.Core/Settings/Models/VideoSettings.cs
Normal file
22
src/Downkyi.Core/Settings/Models/VideoSettings.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Downkyi.Core.FileName;
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 视频
|
||||
/// </summary>
|
||||
public class VideoSettings
|
||||
{
|
||||
public int VideoCodecs { get; set; } = -1; // AVC or HEVC
|
||||
public int Quality { get; set; } = -1; // 画质
|
||||
public int AudioQuality { get; set; } = -1; // 音质
|
||||
public AllowStatus IsTranscodingFlvToMp4 { get; set; } = AllowStatus.NONE; // 是否将flv转为mp4
|
||||
public string SaveVideoRootPath { get; set; } = null; // 视频保存路径
|
||||
public List<string> HistoryVideoRootPaths { get; set; } = null; // 历史视频保存路径
|
||||
public AllowStatus IsUseSaveVideoRootPath { get; set; } = AllowStatus.NONE; // 是否使用默认视频保存路径
|
||||
public VideoContentSettings VideoContent { get; set; } = null; // 下载内容
|
||||
public List<FileNamePart> FileNameParts { get; set; } = null; // 文件命名格式
|
||||
public string FileNamePartTimeFormat { get; set; } = null; // 文件命名中的时间格式
|
||||
public OrderFormat OrderFormat { get; set; } = OrderFormat.NOT_SET; // 文件命名中的序号格式
|
||||
}
|
67
src/Downkyi.Core/Settings/SettingsManager.About.cs
Normal file
67
src/Downkyi.Core/Settings/SettingsManager.About.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
// 是否接收测试版更新
|
||||
private readonly AllowStatus isReceiveBetaVersion = AllowStatus.NO;
|
||||
|
||||
// 是否在启动时自动检查更新
|
||||
private readonly AllowStatus autoUpdateWhenLaunch = AllowStatus.YES;
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否接收测试版更新
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsReceiveBetaVersion()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.About.IsReceiveBetaVersion == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsReceiveBetaVersion(isReceiveBetaVersion);
|
||||
return isReceiveBetaVersion;
|
||||
}
|
||||
return appSettings.About.IsReceiveBetaVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否接收测试版更新
|
||||
/// </summary>
|
||||
/// <param name="isReceiveBetaVersion"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsReceiveBetaVersion(AllowStatus isReceiveBetaVersion)
|
||||
{
|
||||
appSettings.About.IsReceiveBetaVersion = isReceiveBetaVersion;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否允许启动时检查更新
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus GetAutoUpdateWhenLaunch()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.About.AutoUpdateWhenLaunch == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAutoUpdateWhenLaunch(autoUpdateWhenLaunch);
|
||||
return autoUpdateWhenLaunch;
|
||||
}
|
||||
return appSettings.About.AutoUpdateWhenLaunch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否允许启动时检查更新
|
||||
/// </summary>
|
||||
/// <param name="autoUpdateWhenLaunch"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAutoUpdateWhenLaunch(AllowStatus autoUpdateWhenLaunch)
|
||||
{
|
||||
appSettings.About.AutoUpdateWhenLaunch = autoUpdateWhenLaunch;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
}
|
187
src/Downkyi.Core/Settings/SettingsManager.Basic.cs
Normal file
187
src/Downkyi.Core/Settings/SettingsManager.Basic.cs
Normal file
@ -0,0 +1,187 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
// 默认下载完成后的操作
|
||||
private readonly AfterDownloadOperation afterDownload = AfterDownloadOperation.NONE;
|
||||
|
||||
// 是否监听剪贴板
|
||||
private readonly AllowStatus isListenClipboard = AllowStatus.YES;
|
||||
|
||||
// 视频详情页面是否自动解析
|
||||
private readonly AllowStatus isAutoParseVideo = AllowStatus.NO;
|
||||
|
||||
// 默认的视频解析项
|
||||
private readonly ParseScope parseScope = ParseScope.NONE;
|
||||
|
||||
// 解析后自动下载解析视频
|
||||
private readonly AllowStatus isAutoDownloadAll = AllowStatus.NO;
|
||||
|
||||
// 下载完成列表排序
|
||||
private readonly DownloadFinishedSort finishedSort = DownloadFinishedSort.DOWNLOAD;
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载完成后的操作
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AfterDownloadOperation GetAfterDownloadOperation()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Basic.AfterDownload == AfterDownloadOperation.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAfterDownloadOperation(afterDownload);
|
||||
return afterDownload;
|
||||
}
|
||||
return appSettings.Basic.AfterDownload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置下载完成后的操作
|
||||
/// </summary>
|
||||
/// <param name="afterDownload"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAfterDownloadOperation(AfterDownloadOperation afterDownload)
|
||||
{
|
||||
appSettings.Basic.AfterDownload = afterDownload;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否监听剪贴板
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsListenClipboard()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Basic.IsListenClipboard == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsListenClipboard(isListenClipboard);
|
||||
return isListenClipboard;
|
||||
}
|
||||
return appSettings.Basic.IsListenClipboard;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否监听剪贴板
|
||||
/// </summary>
|
||||
/// <param name="isListen"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsListenClipboard(AllowStatus isListen)
|
||||
{
|
||||
appSettings.Basic.IsListenClipboard = isListen;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视频详情页面是否自动解析
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsAutoParseVideo()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Basic.IsAutoParseVideo == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsAutoParseVideo(isAutoParseVideo);
|
||||
return isAutoParseVideo;
|
||||
}
|
||||
return appSettings.Basic.IsAutoParseVideo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视频详情页面是否自动解析
|
||||
/// </summary>
|
||||
/// <param name="IsAuto"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsAutoParseVideo(AllowStatus IsAuto)
|
||||
{
|
||||
appSettings.Basic.IsAutoParseVideo = IsAuto;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视频解析项
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ParseScope GetParseScope()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Basic.ParseScope == ParseScope.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetParseScope(parseScope);
|
||||
return parseScope;
|
||||
}
|
||||
return appSettings.Basic.ParseScope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置视频解析项
|
||||
/// </summary>
|
||||
/// <param name="parseScope"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetParseScope(ParseScope parseScope)
|
||||
{
|
||||
appSettings.Basic.ParseScope = parseScope;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析后是否自动下载解析视频
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsAutoDownloadAll()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Basic.IsAutoDownloadAll == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsAutoDownloadAll(isAutoDownloadAll);
|
||||
return isAutoDownloadAll;
|
||||
}
|
||||
return appSettings.Basic.IsAutoDownloadAll;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析后是否自动下载解析视频
|
||||
/// </summary>
|
||||
/// <param name="isAutoDownloadAll"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsAutoDownloadAll(AllowStatus isAutoDownloadAll)
|
||||
{
|
||||
appSettings.Basic.IsAutoDownloadAll = isAutoDownloadAll;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载完成列表排序
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DownloadFinishedSort GetDownloadFinishedSort()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Basic.DownloadFinishedSort == DownloadFinishedSort.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDownloadFinishedSort(finishedSort);
|
||||
return finishedSort;
|
||||
}
|
||||
return appSettings.Basic.DownloadFinishedSort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置下载完成列表排序
|
||||
/// </summary>
|
||||
/// <param name="finishedSort"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDownloadFinishedSort(DownloadFinishedSort finishedSort)
|
||||
{
|
||||
appSettings.Basic.DownloadFinishedSort = finishedSort;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
}
|
308
src/Downkyi.Core/Settings/SettingsManager.Danmaku.cs
Normal file
308
src/Downkyi.Core/Settings/SettingsManager.Danmaku.cs
Normal file
@ -0,0 +1,308 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
// 是否屏蔽顶部弹幕
|
||||
private readonly AllowStatus danmakuTopFilter = AllowStatus.NO;
|
||||
|
||||
// 是否屏蔽底部弹幕
|
||||
private readonly AllowStatus danmakuBottomFilter = AllowStatus.NO;
|
||||
|
||||
// 是否屏蔽滚动弹幕
|
||||
private readonly AllowStatus danmakuScrollFilter = AllowStatus.NO;
|
||||
|
||||
// 是否自定义分辨率
|
||||
private readonly AllowStatus isCustomDanmakuResolution = AllowStatus.NO;
|
||||
|
||||
// 分辨率-宽
|
||||
private readonly int danmakuScreenWidth = 1920;
|
||||
|
||||
// 分辨率-高
|
||||
private readonly int danmakuScreenHeight = 1080;
|
||||
|
||||
// 弹幕字体
|
||||
private readonly string danmakuFontName = "黑体";
|
||||
|
||||
// 弹幕字体大小
|
||||
private readonly int danmakuFontSize = 50;
|
||||
|
||||
// 弹幕限制行数
|
||||
private readonly int danmakuLineCount = 0;
|
||||
|
||||
// 弹幕布局算法
|
||||
private readonly DanmakuLayoutAlgorithm danmakuLayoutAlgorithm = DanmakuLayoutAlgorithm.SYNC;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否屏蔽顶部弹幕
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus GetDanmakuTopFilter()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuTopFilter == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuTopFilter(danmakuTopFilter);
|
||||
return danmakuTopFilter;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuTopFilter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否屏蔽顶部弹幕
|
||||
/// </summary>
|
||||
/// <param name="danmakuFilter"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuTopFilter(AllowStatus danmakuFilter)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuTopFilter = danmakuFilter;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否屏蔽底部弹幕
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus GetDanmakuBottomFilter()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuBottomFilter == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuBottomFilter(danmakuBottomFilter);
|
||||
return danmakuBottomFilter;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuBottomFilter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否屏蔽底部弹幕
|
||||
/// </summary>
|
||||
/// <param name="danmakuFilter"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuBottomFilter(AllowStatus danmakuFilter)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuBottomFilter = danmakuFilter;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否屏蔽滚动弹幕
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus GetDanmakuScrollFilter()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuScrollFilter == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuScrollFilter(danmakuScrollFilter);
|
||||
return danmakuScrollFilter;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuScrollFilter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否屏蔽滚动弹幕
|
||||
/// </summary>
|
||||
/// <param name="danmakuFilter"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuScrollFilter(AllowStatus danmakuFilter)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuScrollFilter = danmakuFilter;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否自定义分辨率
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsCustomDanmakuResolution()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.IsCustomDanmakuResolution == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsCustomDanmakuResolution(isCustomDanmakuResolution);
|
||||
return isCustomDanmakuResolution;
|
||||
}
|
||||
return appSettings.Danmaku.IsCustomDanmakuResolution;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否自定义分辨率
|
||||
/// </summary>
|
||||
/// <param name="isCustomResolution"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsCustomDanmakuResolution(AllowStatus isCustomResolution)
|
||||
{
|
||||
appSettings.Danmaku.IsCustomDanmakuResolution = isCustomResolution;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取分辨率-宽
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetDanmakuScreenWidth()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuScreenWidth == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuScreenWidth(danmakuScreenWidth);
|
||||
return danmakuScreenWidth;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuScreenWidth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置分辨率-宽
|
||||
/// </summary>
|
||||
/// <param name="screenWidth"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuScreenWidth(int screenWidth)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuScreenWidth = screenWidth;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取分辨率-高
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetDanmakuScreenHeight()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuScreenHeight == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuScreenHeight(danmakuScreenHeight);
|
||||
return danmakuScreenHeight;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuScreenHeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置分辨率-高
|
||||
/// </summary>
|
||||
/// <param name="screenHeight"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuScreenHeight(int screenHeight)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuScreenHeight = screenHeight;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取弹幕字体
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetDanmakuFontName()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuFontName == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuFontName(danmakuFontName);
|
||||
return danmakuFontName;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuFontName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置弹幕字体
|
||||
/// </summary>
|
||||
/// <param name="danmakuFontName"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuFontName(string danmakuFontName)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuFontName = danmakuFontName;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取弹幕字体大小
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetDanmakuFontSize()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuFontSize == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuFontSize(danmakuFontSize);
|
||||
return danmakuFontSize;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuFontSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置弹幕字体大小
|
||||
/// </summary>
|
||||
/// <param name="danmakuFontSize"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuFontSize(int danmakuFontSize)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuFontSize = danmakuFontSize;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取弹幕限制行数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetDanmakuLineCount()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuLineCount == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuLineCount(danmakuLineCount);
|
||||
return danmakuLineCount;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuLineCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置弹幕限制行数
|
||||
/// </summary>
|
||||
/// <param name="danmakuLineCount"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuLineCount(int danmakuLineCount)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuLineCount = danmakuLineCount;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取弹幕布局算法
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DanmakuLayoutAlgorithm GetDanmakuLayoutAlgorithm()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Danmaku.DanmakuLayoutAlgorithm == DanmakuLayoutAlgorithm.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDanmakuLayoutAlgorithm(danmakuLayoutAlgorithm);
|
||||
return danmakuLayoutAlgorithm;
|
||||
}
|
||||
return appSettings.Danmaku.DanmakuLayoutAlgorithm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置弹幕布局算法
|
||||
/// </summary>
|
||||
/// <param name="danmakuLayoutAlgorithm"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDanmakuLayoutAlgorithm(DanmakuLayoutAlgorithm danmakuLayoutAlgorithm)
|
||||
{
|
||||
appSettings.Danmaku.DanmakuLayoutAlgorithm = danmakuLayoutAlgorithm;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
}
|
600
src/Downkyi.Core/Settings/SettingsManager.Network.cs
Normal file
600
src/Downkyi.Core/Settings/SettingsManager.Network.cs
Normal file
@ -0,0 +1,600 @@
|
||||
using Aria2cNet.Server;
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
// 是否开启解除地区限制
|
||||
private readonly AllowStatus isLiftingOfRegion = AllowStatus.YES;
|
||||
|
||||
// 启用https
|
||||
private readonly AllowStatus useSSL = AllowStatus.YES;
|
||||
|
||||
// UserAgent
|
||||
private readonly string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
|
||||
|
||||
// 下载器
|
||||
private readonly Enum.Downloader downloader = Enum.Downloader.BUILT_IN;
|
||||
|
||||
// 最大同时下载数(任务数)
|
||||
private readonly int maxCurrentDownloads = 3;
|
||||
|
||||
// 单文件最大线程数
|
||||
private readonly int split = 8;
|
||||
|
||||
// HttpProxy代理
|
||||
private readonly AllowStatus isHttpProxy = AllowStatus.NO;
|
||||
private readonly string httpProxy = string.Empty;
|
||||
private readonly int httpProxyListenPort = 0;
|
||||
|
||||
// Aria服务器token
|
||||
private readonly string ariaToken = "downkyi";
|
||||
|
||||
// Aria服务器host
|
||||
private readonly string ariaHost = "http://localhost";
|
||||
|
||||
// Aria服务器端口号
|
||||
private readonly int ariaListenPort = 6800;
|
||||
|
||||
// Aria日志等级
|
||||
private readonly AriaConfigLogLevel ariaLogLevel = AriaConfigLogLevel.INFO;
|
||||
|
||||
// Aria单文件最大线程数
|
||||
private readonly int ariaSplit = 5;
|
||||
|
||||
// Aria下载速度限制
|
||||
private readonly int ariaMaxOverallDownloadLimit = 0;
|
||||
|
||||
// Aria下载单文件速度限制
|
||||
private readonly int ariaMaxDownloadLimit = 0;
|
||||
|
||||
// Aria文件预分配
|
||||
private readonly AriaConfigFileAllocation ariaFileAllocation = AriaConfigFileAllocation.NONE;
|
||||
|
||||
// Aria HttpProxy代理
|
||||
private readonly AllowStatus isAriaHttpProxy = AllowStatus.NO;
|
||||
private readonly string ariaHttpProxy = string.Empty;
|
||||
private readonly int ariaHttpProxyListenPort = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否解除地区限制
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsLiftingOfRegion()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.IsLiftingOfRegion == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsLiftingOfRegion(isLiftingOfRegion);
|
||||
return isLiftingOfRegion;
|
||||
}
|
||||
return appSettings.Network.IsLiftingOfRegion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否解除地区限制
|
||||
/// </summary>
|
||||
/// <param name="isLiftingOfRegion"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsLiftingOfRegion(AllowStatus isLiftingOfRegion)
|
||||
{
|
||||
appSettings.Network.IsLiftingOfRegion = isLiftingOfRegion;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否启用https
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus UseSSL()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.UseSSL == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
UseSSL(useSSL);
|
||||
return useSSL;
|
||||
}
|
||||
return appSettings.Network.UseSSL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否启用https
|
||||
/// </summary>
|
||||
/// <param name="useSSL"></param>
|
||||
/// <returns></returns>
|
||||
public bool UseSSL(AllowStatus useSSL)
|
||||
{
|
||||
appSettings.Network.UseSSL = useSSL;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取UserAgent
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetUserAgent()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.UserAgent == string.Empty)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetUserAgent(userAgent);
|
||||
return userAgent;
|
||||
}
|
||||
return appSettings.Network.UserAgent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置UserAgent
|
||||
/// </summary>
|
||||
/// <param name="userAgent"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetUserAgent(string userAgent)
|
||||
{
|
||||
appSettings.Network.UserAgent = userAgent;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载器
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Enum.Downloader GetDownloader()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.Downloader == Enum.Downloader.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetDownloader(downloader);
|
||||
return downloader;
|
||||
}
|
||||
return appSettings.Network.Downloader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置下载器
|
||||
/// </summary>
|
||||
/// <param name="downloader"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetDownloader(Enum.Downloader downloader)
|
||||
{
|
||||
appSettings.Network.Downloader = downloader;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取最大同时下载数(任务数)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetMaxCurrentDownloads()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.MaxCurrentDownloads == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetMaxCurrentDownloads(maxCurrentDownloads);
|
||||
return maxCurrentDownloads;
|
||||
}
|
||||
return appSettings.Network.MaxCurrentDownloads;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大同时下载数(任务数)
|
||||
/// </summary>
|
||||
/// <param name="ariaMaxConcurrentDownloads"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetMaxCurrentDownloads(int maxCurrentDownloads)
|
||||
{
|
||||
appSettings.Network.MaxCurrentDownloads = maxCurrentDownloads;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单文件最大线程数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetSplit()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.Split == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetSplit(split);
|
||||
return split;
|
||||
}
|
||||
return appSettings.Network.Split;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置单文件最大线程数
|
||||
/// </summary>
|
||||
/// <param name="split"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetSplit(int split)
|
||||
{
|
||||
appSettings.Network.Split = split;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否开启Http代理
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsHttpProxy()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.IsHttpProxy == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsHttpProxy(isHttpProxy);
|
||||
return isHttpProxy;
|
||||
}
|
||||
return appSettings.Network.IsHttpProxy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否开启Http代理
|
||||
/// </summary>
|
||||
/// <param name="isHttpProxy"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsHttpProxy(AllowStatus isHttpProxy)
|
||||
{
|
||||
appSettings.Network.IsHttpProxy = isHttpProxy;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Http代理的地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetHttpProxy()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.HttpProxy == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetHttpProxy(httpProxy);
|
||||
return httpProxy;
|
||||
}
|
||||
return appSettings.Network.HttpProxy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria的http代理的地址
|
||||
/// </summary>
|
||||
/// <param name="httpProxy"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetHttpProxy(string httpProxy)
|
||||
{
|
||||
appSettings.Network.HttpProxy = httpProxy;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Http代理的端口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetHttpProxyListenPort()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.HttpProxyListenPort == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetHttpProxyListenPort(httpProxyListenPort);
|
||||
return httpProxyListenPort;
|
||||
}
|
||||
return appSettings.Network.HttpProxyListenPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Http代理的端口
|
||||
/// </summary>
|
||||
/// <param name="httpProxyListenPort"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetHttpProxyListenPort(int httpProxyListenPort)
|
||||
{
|
||||
appSettings.Network.HttpProxyListenPort = httpProxyListenPort;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria服务器的token
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAriaToken()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaToken == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetHttpProxy(ariaToken);
|
||||
return ariaToken;
|
||||
}
|
||||
return appSettings.Network.AriaToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria服务器的token
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaToken(string token)
|
||||
{
|
||||
appSettings.Network.AriaToken = token;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria服务器的host
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAriaHost()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaHost == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetHttpProxy(ariaHost);
|
||||
return ariaHost;
|
||||
}
|
||||
return appSettings.Network.AriaHost;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria服务器的host
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaHost(string host)
|
||||
{
|
||||
appSettings.Network.AriaHost = host;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria服务器的端口号
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetAriaListenPort()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaListenPort == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaListenPort(ariaListenPort);
|
||||
return ariaListenPort;
|
||||
}
|
||||
return appSettings.Network.AriaListenPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria服务器的端口号
|
||||
/// </summary>
|
||||
/// <param name="ariaListenPort"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaListenPort(int ariaListenPort)
|
||||
{
|
||||
appSettings.Network.AriaListenPort = ariaListenPort;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria日志等级
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AriaConfigLogLevel GetAriaLogLevel()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaLogLevel == AriaConfigLogLevel.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaLogLevel(ariaLogLevel);
|
||||
return ariaLogLevel;
|
||||
}
|
||||
return appSettings.Network.AriaLogLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria日志等级
|
||||
/// </summary>
|
||||
/// <param name="ariaLogLevel"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaLogLevel(AriaConfigLogLevel ariaLogLevel)
|
||||
{
|
||||
appSettings.Network.AriaLogLevel = ariaLogLevel;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria单文件最大线程数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetAriaSplit()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaSplit == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaSplit(ariaSplit);
|
||||
return ariaSplit;
|
||||
}
|
||||
return appSettings.Network.AriaSplit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria单文件最大线程数
|
||||
/// </summary>
|
||||
/// <param name="ariaSplit"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaSplit(int ariaSplit)
|
||||
{
|
||||
appSettings.Network.AriaSplit = ariaSplit;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria下载速度限制
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetAriaMaxOverallDownloadLimit()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaMaxOverallDownloadLimit == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaMaxOverallDownloadLimit(ariaMaxOverallDownloadLimit);
|
||||
return ariaMaxOverallDownloadLimit;
|
||||
}
|
||||
return appSettings.Network.AriaMaxOverallDownloadLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria下载速度限制
|
||||
/// </summary>
|
||||
/// <param name="ariaMaxOverallDownloadLimit"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaMaxOverallDownloadLimit(int ariaMaxOverallDownloadLimit)
|
||||
{
|
||||
appSettings.Network.AriaMaxOverallDownloadLimit = ariaMaxOverallDownloadLimit;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria下载单文件速度限制
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetAriaMaxDownloadLimit()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaMaxDownloadLimit == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaMaxDownloadLimit(ariaMaxDownloadLimit);
|
||||
return ariaMaxDownloadLimit;
|
||||
}
|
||||
return appSettings.Network.AriaMaxDownloadLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria下载单文件速度限制
|
||||
/// </summary>
|
||||
/// <param name="ariaMaxDownloadLimit"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaMaxDownloadLimit(int ariaMaxDownloadLimit)
|
||||
{
|
||||
appSettings.Network.AriaMaxDownloadLimit = ariaMaxDownloadLimit;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria文件预分配
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AriaConfigFileAllocation GetAriaFileAllocation()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaFileAllocation == AriaConfigFileAllocation.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaFileAllocation(ariaFileAllocation);
|
||||
return ariaFileAllocation;
|
||||
}
|
||||
return appSettings.Network.AriaFileAllocation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria文件预分配
|
||||
/// </summary>
|
||||
/// <param name="ariaFileAllocation"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaFileAllocation(AriaConfigFileAllocation ariaFileAllocation)
|
||||
{
|
||||
appSettings.Network.AriaFileAllocation = ariaFileAllocation;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否开启Aria http代理
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsAriaHttpProxy()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.IsAriaHttpProxy == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsAriaHttpProxy(isAriaHttpProxy);
|
||||
return isAriaHttpProxy;
|
||||
}
|
||||
return appSettings.Network.IsAriaHttpProxy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否开启Aria http代理
|
||||
/// </summary>
|
||||
/// <param name="isAriaHttpProxy"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsAriaHttpProxy(AllowStatus isAriaHttpProxy)
|
||||
{
|
||||
appSettings.Network.IsAriaHttpProxy = isAriaHttpProxy;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria的http代理的地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAriaHttpProxy()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaHttpProxy == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaHttpProxy(ariaHttpProxy);
|
||||
return ariaHttpProxy;
|
||||
}
|
||||
return appSettings.Network.AriaHttpProxy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria的http代理的地址
|
||||
/// </summary>
|
||||
/// <param name="ariaHttpProxy"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaHttpProxy(string ariaHttpProxy)
|
||||
{
|
||||
appSettings.Network.AriaHttpProxy = ariaHttpProxy;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Aria的http代理的端口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetAriaHttpProxyListenPort()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Network.AriaHttpProxyListenPort == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAriaHttpProxyListenPort(ariaHttpProxyListenPort);
|
||||
return ariaHttpProxyListenPort;
|
||||
}
|
||||
return appSettings.Network.AriaHttpProxyListenPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Aria的http代理的端口
|
||||
/// </summary>
|
||||
/// <param name="ariaHttpProxyListenPort"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAriaHttpProxyListenPort(int ariaHttpProxyListenPort)
|
||||
{
|
||||
appSettings.Network.AriaHttpProxyListenPort = ariaHttpProxyListenPort;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
}
|
43
src/Downkyi.Core/Settings/SettingsManager.UserInfo.cs
Normal file
43
src/Downkyi.Core/Settings/SettingsManager.UserInfo.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Downkyi.Core.Settings.Models;
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
// 登录用户的mid
|
||||
private readonly UserInfoSettings userInfo = new()
|
||||
{
|
||||
Mid = -1,
|
||||
Name = "",
|
||||
IsLogin = false,
|
||||
IsVip = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录用户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public UserInfoSettings GetUserInfo()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.UserInfo == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetUserInfo(userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
return appSettings.UserInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置中保存登录用户的信息,在index刷新用户状态时使用
|
||||
/// </summary>
|
||||
/// <param name="mid"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetUserInfo(UserInfoSettings userInfo)
|
||||
{
|
||||
appSettings.UserInfo = userInfo;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
}
|
352
src/Downkyi.Core/Settings/SettingsManager.Video.cs
Normal file
352
src/Downkyi.Core/Settings/SettingsManager.Video.cs
Normal file
@ -0,0 +1,352 @@
|
||||
using Downkyi.Core.FileName;
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
using Downkyi.Core.Settings.Models;
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
// 设置优先下载的视频编码
|
||||
private readonly int videoCodecs = 7;
|
||||
|
||||
// 设置优先下载画质
|
||||
private readonly int quality = 120;
|
||||
|
||||
// 设置优先下载音质
|
||||
private readonly int audioQuality = 30280;
|
||||
|
||||
// 是否下载flv视频后转码为mp4
|
||||
private readonly AllowStatus isTranscodingFlvToMp4 = AllowStatus.YES;
|
||||
|
||||
// 默认下载目录
|
||||
private readonly string saveVideoRootPath = Path.Combine(Environment.CurrentDirectory, "Media");
|
||||
|
||||
// 历史下载目录
|
||||
private readonly List<string> historyVideoRootPaths = new();
|
||||
|
||||
// 是否使用默认下载目录,如果是,则每次点击下载选中项时不再询问下载目录
|
||||
private readonly AllowStatus isUseSaveVideoRootPath = AllowStatus.NO;
|
||||
|
||||
// 下载内容
|
||||
private readonly VideoContentSettings videoContent = new();
|
||||
|
||||
// 文件命名格式
|
||||
private readonly List<FileNamePart> fileNameParts = new()
|
||||
{
|
||||
FileNamePart.MAIN_TITLE,
|
||||
FileNamePart.SLASH,
|
||||
FileNamePart.SECTION,
|
||||
FileNamePart.SLASH,
|
||||
FileNamePart.ORDER,
|
||||
FileNamePart.HYPHEN,
|
||||
FileNamePart.PAGE_TITLE,
|
||||
FileNamePart.HYPHEN,
|
||||
FileNamePart.VIDEO_QUALITY,
|
||||
FileNamePart.HYPHEN,
|
||||
FileNamePart.VIDEO_CODEC,
|
||||
};
|
||||
|
||||
// 文件命名中的时间格式
|
||||
private readonly string fileNamePartTimeFormat = "yyyy-MM-dd";
|
||||
|
||||
// 文件命名中的序号格式
|
||||
private readonly OrderFormat orderFormat = OrderFormat.NATURAL;
|
||||
|
||||
/// <summary>
|
||||
/// 获取优先下载的视频编码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetVideoCodecs()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.VideoCodecs == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetVideoCodecs(videoCodecs);
|
||||
return videoCodecs;
|
||||
}
|
||||
return appSettings.Video.VideoCodecs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置优先下载的视频编码
|
||||
/// </summary>
|
||||
/// <param name="videoCodecs"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetVideoCodecs(int videoCodecs)
|
||||
{
|
||||
appSettings.Video.VideoCodecs = videoCodecs;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取优先下载画质
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetQuality()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.Quality == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetQuality(quality);
|
||||
return quality;
|
||||
}
|
||||
return appSettings.Video.Quality;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置优先下载画质
|
||||
/// </summary>
|
||||
/// <param name="quality"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetQuality(int quality)
|
||||
{
|
||||
appSettings.Video.Quality = quality;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取优先下载音质
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetAudioQuality()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.AudioQuality == -1)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetAudioQuality(audioQuality);
|
||||
return audioQuality;
|
||||
}
|
||||
return appSettings.Video.AudioQuality;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置优先下载音质
|
||||
/// </summary>
|
||||
/// <param name="quality"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetAudioQuality(int quality)
|
||||
{
|
||||
appSettings.Video.AudioQuality = quality;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否下载flv视频后转码为mp4
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsTranscodingFlvToMp4()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.IsTranscodingFlvToMp4 == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsTranscodingFlvToMp4(isTranscodingFlvToMp4);
|
||||
return isTranscodingFlvToMp4;
|
||||
}
|
||||
return appSettings.Video.IsTranscodingFlvToMp4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否下载flv视频后转码为mp4
|
||||
/// </summary>
|
||||
/// <param name="isTranscodingFlvToMp4"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsTranscodingFlvToMp4(AllowStatus isTranscodingFlvToMp4)
|
||||
{
|
||||
appSettings.Video.IsTranscodingFlvToMp4 = isTranscodingFlvToMp4;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载目录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetSaveVideoRootPath()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.SaveVideoRootPath == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetSaveVideoRootPath(saveVideoRootPath);
|
||||
return saveVideoRootPath;
|
||||
}
|
||||
return appSettings.Video.SaveVideoRootPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置下载目录
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetSaveVideoRootPath(string path)
|
||||
{
|
||||
appSettings.Video.SaveVideoRootPath = path;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取历史下载目录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<string> GetHistoryVideoRootPaths()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.HistoryVideoRootPaths == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetHistoryVideoRootPaths(historyVideoRootPaths);
|
||||
return historyVideoRootPaths;
|
||||
}
|
||||
return appSettings.Video.HistoryVideoRootPaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置历史下载目录
|
||||
/// </summary>
|
||||
/// <param name="historyPaths"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetHistoryVideoRootPaths(List<string> historyPaths)
|
||||
{
|
||||
appSettings.Video.HistoryVideoRootPaths = historyPaths;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否使用默认下载目录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AllowStatus IsUseSaveVideoRootPath()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.IsUseSaveVideoRootPath == AllowStatus.NONE)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
IsUseSaveVideoRootPath(isUseSaveVideoRootPath);
|
||||
return isUseSaveVideoRootPath;
|
||||
}
|
||||
return appSettings.Video.IsUseSaveVideoRootPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否使用默认下载目录
|
||||
/// </summary>
|
||||
/// <param name="isUseSaveVideoRootPath"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsUseSaveVideoRootPath(AllowStatus isUseSaveVideoRootPath)
|
||||
{
|
||||
appSettings.Video.IsUseSaveVideoRootPath = isUseSaveVideoRootPath;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载内容
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public VideoContentSettings GetVideoContent()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.VideoContent == null)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetVideoContent(videoContent);
|
||||
return videoContent;
|
||||
}
|
||||
return appSettings.Video.VideoContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置下载内容
|
||||
/// </summary>
|
||||
/// <param name="videoContent"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetVideoContent(VideoContentSettings videoContent)
|
||||
{
|
||||
appSettings.Video.VideoContent = videoContent;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件命名格式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<FileNamePart> GetFileNameParts()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.FileNameParts == null || appSettings.Video.FileNameParts.Count == 0)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetFileNameParts(fileNameParts);
|
||||
return fileNameParts;
|
||||
}
|
||||
return appSettings.Video.FileNameParts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文件命名格式
|
||||
/// </summary>
|
||||
/// <param name="historyPaths"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetFileNameParts(List<FileNamePart> fileNameParts)
|
||||
{
|
||||
appSettings.Video.FileNameParts = fileNameParts;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件命名中的时间格式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetFileNamePartTimeFormat()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.FileNamePartTimeFormat == null || appSettings.Video.FileNamePartTimeFormat == string.Empty)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetFileNamePartTimeFormat(fileNamePartTimeFormat);
|
||||
return fileNamePartTimeFormat;
|
||||
}
|
||||
return appSettings.Video.FileNamePartTimeFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文件命名中的时间格式
|
||||
/// </summary>
|
||||
/// <param name="timeFormat"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetFileNamePartTimeFormat(string timeFormat)
|
||||
{
|
||||
appSettings.Video.FileNamePartTimeFormat = timeFormat;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件命名中的序号格式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public OrderFormat GetOrderFormat()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
if (appSettings.Video.OrderFormat == OrderFormat.NOT_SET)
|
||||
{
|
||||
// 第一次获取,先设置默认值
|
||||
SetOrderFormat(orderFormat);
|
||||
return orderFormat;
|
||||
}
|
||||
return appSettings.Video.OrderFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文件命名中的序号格式
|
||||
/// </summary>
|
||||
/// <param name="orderFormat"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetOrderFormat(OrderFormat orderFormat)
|
||||
{
|
||||
appSettings.Video.OrderFormat = orderFormat;
|
||||
return SetSettings();
|
||||
}
|
||||
|
||||
}
|
102
src/Downkyi.Core/Settings/SettingsManager.cs
Normal file
102
src/Downkyi.Core/Settings/SettingsManager.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using Downkyi.Core.Settings.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
#if DEBUG
|
||||
#else
|
||||
using Downkyi.Core.Utils.Encryptor;
|
||||
#endif
|
||||
|
||||
namespace Downkyi.Core.Settings;
|
||||
|
||||
public partial class SettingsManager
|
||||
{
|
||||
private static SettingsManager instance;
|
||||
|
||||
// 内存中保存一份配置
|
||||
private AppSettings appSettings;
|
||||
|
||||
#if DEBUG
|
||||
// 设置的配置文件
|
||||
private readonly string settingsName = Storage.StorageManager.GetSettings() + "_debug.json";
|
||||
#else
|
||||
// 设置的配置文件
|
||||
private readonly string settingsName = Storage.StorageManager.GetSettings();
|
||||
|
||||
// 密钥
|
||||
private readonly string password = "YO1J$4#p";
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 获取SettingsManager实例
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static SettingsManager GetInstance()
|
||||
{
|
||||
instance ??= new SettingsManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏Settings()方法,必须使用单例模式
|
||||
/// </summary>
|
||||
private SettingsManager()
|
||||
{
|
||||
appSettings = GetSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取AppSettingsModel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private AppSettings GetSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileStream = new FileStream(settingsName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
var streamReader = new StreamReader(fileStream, System.Text.Encoding.UTF8);
|
||||
string jsonWordTemplate = streamReader.ReadToEnd();
|
||||
streamReader.Close();
|
||||
fileStream.Close();
|
||||
|
||||
#if DEBUG
|
||||
#else
|
||||
// 解密字符串
|
||||
jsonWordTemplate = Encryptor.DecryptString(jsonWordTemplate, password);
|
||||
#endif
|
||||
|
||||
return JsonConvert.DeserializeObject<AppSettings>(jsonWordTemplate);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return new AppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置AppSettingsModel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool SetSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(appSettings);
|
||||
|
||||
#if DEBUG
|
||||
#else
|
||||
// 加密字符串
|
||||
json = Encryptor.EncryptString(json, password);
|
||||
#endif
|
||||
|
||||
File.WriteAllText(settingsName, json);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
67
src/Downkyi.Core/Storage/Constant.cs
Normal file
67
src/Downkyi.Core/Storage/Constant.cs
Normal file
@ -0,0 +1,67 @@
|
||||
namespace Downkyi.Core.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// 存储到本地时使用的一些常量
|
||||
/// </summary>
|
||||
internal static class Constant
|
||||
{
|
||||
// 根目录
|
||||
//private static string Root { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/Downkyi";
|
||||
private static string Root { get; } = Environment.CurrentDirectory;
|
||||
|
||||
// Aria
|
||||
public static string Aria { get; } = $"{Root}/Aria";
|
||||
|
||||
// 日志
|
||||
public static string Logs { get; } = $"{Root}/Logs";
|
||||
|
||||
// 数据库
|
||||
public static string Database { get; } = $"{Root}/Storage";
|
||||
|
||||
// 历史(搜索、下载) (加密)
|
||||
public static string Download { get; } = $"{Database}/Download.db";
|
||||
public static string History { get; } = $"{Database}/History.db";
|
||||
|
||||
// 配置
|
||||
public static string Config { get; } = $"{Root}/Config";
|
||||
|
||||
// 设置
|
||||
public static string Settings { get; } = $"{Config}/Settings";
|
||||
|
||||
// 登录cookies
|
||||
public static string Login { get; } = $"{Config}/Login";
|
||||
|
||||
// Bilibili
|
||||
private static string Bilibili { get; } = $"{Root}/Bilibili";
|
||||
|
||||
// 弹幕
|
||||
public static string Danmaku { get; } = $"{Bilibili}/Danmakus";
|
||||
|
||||
// 字幕
|
||||
public static string Subtitle { get; } = $"{Bilibili}/Subtitle";
|
||||
|
||||
// 评论
|
||||
// TODO
|
||||
|
||||
// 头图
|
||||
public static string Toutu { get; } = $"{Bilibili}/Toutu";
|
||||
|
||||
// 封面
|
||||
public static string Cover { get; } = $"{Bilibili}/Cover";
|
||||
|
||||
// 封面文件索引
|
||||
public static string CoverIndex { get; } = $"{Cover}/Index.db";
|
||||
|
||||
// 视频快照
|
||||
public static string Snapshot { get; } = $"{Bilibili}/Snapshot";
|
||||
|
||||
// 视频快照文件索引
|
||||
public static string SnapshotIndex { get; } = $"{Cover}/Index.db";
|
||||
|
||||
// 用户头像
|
||||
public static string Header { get; } = $"{Bilibili}/Header";
|
||||
|
||||
// 用户头像文件索引
|
||||
public static string HeaderIndex { get; } = $"{Header}/Index.db";
|
||||
|
||||
}
|
153
src/Downkyi.Core/Storage/StorageManager.cs
Normal file
153
src/Downkyi.Core/Storage/StorageManager.cs
Normal file
@ -0,0 +1,153 @@
|
||||
namespace Downkyi.Core.Storage;
|
||||
|
||||
public static class StorageManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取历史记录的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetAriaDir()
|
||||
{
|
||||
CreateDirectory(Constant.Aria);
|
||||
return Constant.Aria;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取历史记录的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetDownload()
|
||||
{
|
||||
CreateDirectory(Constant.Database);
|
||||
return Constant.Download;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取历史记录的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetHistory()
|
||||
{
|
||||
CreateDirectory(Constant.Database);
|
||||
return Constant.History;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设置的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSettings()
|
||||
{
|
||||
CreateDirectory(Constant.Config);
|
||||
return Constant.Settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录cookies的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetLogin()
|
||||
{
|
||||
CreateDirectory(Constant.Config);
|
||||
return Constant.Login;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取弹幕的文件夹路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetDanmaku()
|
||||
{
|
||||
return CreateDirectory(Constant.Danmaku);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字幕的文件夹路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSubtitle()
|
||||
{
|
||||
return CreateDirectory(Constant.Subtitle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取头图的文件夹路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetToutu()
|
||||
{
|
||||
return CreateDirectory(Constant.Toutu);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取封面的文件夹路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetCover()
|
||||
{
|
||||
return CreateDirectory(Constant.Cover);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取封面索引的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetCoverIndex()
|
||||
{
|
||||
CreateDirectory(Constant.Cover);
|
||||
return Constant.CoverIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视频快照的文件夹路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSnapshot()
|
||||
{
|
||||
return CreateDirectory(Constant.Snapshot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视频快照索引的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSnapshotIndex()
|
||||
{
|
||||
CreateDirectory(Constant.Snapshot);
|
||||
return Constant.SnapshotIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户头像的文件夹路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetHeader()
|
||||
{
|
||||
return CreateDirectory(Constant.Header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户头像索引的文件路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetHeaderIndex()
|
||||
{
|
||||
CreateDirectory(Constant.Header);
|
||||
return Constant.HeaderIndex;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 若文件夹不存在,则创建文件夹
|
||||
/// </summary>
|
||||
/// <param name="directory"></param>
|
||||
/// <returns></returns>
|
||||
private static string CreateDirectory(string directory)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
}
|
185
src/Downkyi.Core/Utils/Encryptor/Encryptor.File.cs
Normal file
185
src/Downkyi.Core/Utils/Encryptor/Encryptor.File.cs
Normal file
@ -0,0 +1,185 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Downkyi.Core.Utils.Encryptor;
|
||||
|
||||
public sealed class EncryptorFile
|
||||
{
|
||||
string iv = "xIrlMBGX";
|
||||
string key = "A2&P!buv";
|
||||
|
||||
/// <summary>
|
||||
/// DES加密偏移量,必须是>=8位长的字符串
|
||||
/// </summary>
|
||||
public string IV
|
||||
{
|
||||
get { return iv; }
|
||||
set { iv = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DES加密的私钥,必须是8位长的字符串
|
||||
/// </summary>
|
||||
public string Key
|
||||
{
|
||||
get { return key; }
|
||||
set { key = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对字符串进行DES加密
|
||||
/// </summary>
|
||||
/// <param name="sourceString">待加密的字符串</param>
|
||||
/// <returns>加密后的BASE64编码的字符串</returns>
|
||||
public string Encrypt(string sourceString)
|
||||
{
|
||||
byte[] btKey = Encoding.Default.GetBytes(key);
|
||||
byte[] btIV = Encoding.Default.GetBytes(iv);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
var des = new DESCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
byte[] inData = Encoding.Default.GetBytes(sourceString);
|
||||
try
|
||||
{
|
||||
using (CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(btKey, btIV), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(inData, 0, inData.Length);
|
||||
cs.FlushFinalBlock();
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(ms.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对DES加密后的字符串进行解密
|
||||
/// </summary>
|
||||
/// <param name="encryptedString">待解密的字符串</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public string Decrypt(string encryptedString)
|
||||
{
|
||||
byte[] btKey = Encoding.Default.GetBytes(key);
|
||||
byte[] btIV = Encoding.Default.GetBytes(iv);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
var des = new DESCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
byte[] inData = Convert.FromBase64String(encryptedString);
|
||||
try
|
||||
{
|
||||
using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(btKey, btIV), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(inData, 0, inData.Length);
|
||||
cs.FlushFinalBlock();
|
||||
}
|
||||
|
||||
return Encoding.Default.GetString(ms.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对文件内容进行DES加密
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">待加密的文件绝对路径</param>
|
||||
/// <param name="destFile">加密后的文件保存的绝对路径</param>
|
||||
public void EncryptFile(string sourceFile, string destFile)
|
||||
{
|
||||
if (!File.Exists(sourceFile)) throw new FileNotFoundException("指定的文件路径不存在!", sourceFile);
|
||||
|
||||
byte[] btKey = Encoding.Default.GetBytes(key);
|
||||
byte[] btIV = Encoding.Default.GetBytes(iv);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
var des = new DESCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
byte[] btFile = File.ReadAllBytes(sourceFile);
|
||||
|
||||
using (FileStream fs = new FileStream(destFile, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (CryptoStream cs = new CryptoStream(fs, des.CreateEncryptor(btKey, btIV), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(btFile, 0, btFile.Length);
|
||||
cs.FlushFinalBlock();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对文件内容进行DES加密,加密后覆盖掉原来的文件
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">待加密的文件的绝对路径</param>
|
||||
public void EncryptFile(string sourceFile)
|
||||
{
|
||||
EncryptFile(sourceFile, sourceFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对文件内容进行DES解密
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">待解密的文件绝对路径</param>
|
||||
/// <param name="destFile">解密后的文件保存的绝对路径</param>
|
||||
public void DecryptFile(string sourceFile, string destFile)
|
||||
{
|
||||
if (!File.Exists(sourceFile)) throw new FileNotFoundException("指定的文件路径不存在!", sourceFile);
|
||||
|
||||
byte[] btKey = Encoding.Default.GetBytes(key);
|
||||
byte[] btIV = Encoding.Default.GetBytes(iv);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
var des = new DESCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
byte[] btFile = File.ReadAllBytes(sourceFile);
|
||||
|
||||
using (FileStream fs = new FileStream(destFile, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (CryptoStream cs = new CryptoStream(fs, des.CreateDecryptor(btKey, btIV), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(btFile, 0, btFile.Length);
|
||||
cs.FlushFinalBlock();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对文件内容进行DES解密,加密后覆盖掉原来的文件
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">待解密的文件的绝对路径</param>
|
||||
public void DecryptFile(string sourceFile)
|
||||
{
|
||||
DecryptFile(sourceFile, sourceFile);
|
||||
}
|
||||
}
|
68
src/Downkyi.Core/Utils/Encryptor/Encryptor.String.cs
Normal file
68
src/Downkyi.Core/Utils/Encryptor/Encryptor.String.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Downkyi.Core.Utils.Encryptor;
|
||||
|
||||
public static partial class Encryptor
|
||||
{
|
||||
/// <summary>
|
||||
/// DES加密字符串
|
||||
/// </summary>
|
||||
/// <param name="encryptString">待加密的字符串</param>
|
||||
/// <param name="encryptKey">加密密钥,要求为8位</param>
|
||||
/// <returns>加密成功返回加密后的字符串,失败返回源串</returns>
|
||||
public static string EncryptString(string encryptString, string encryptKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey[..8]);//转换为字节
|
||||
byte[] rgbIV = Encoding.UTF8.GetBytes(encryptKey[..8]);
|
||||
byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
var dCSP = new DESCryptoServiceProvider();//实例化数据加密标准
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
var mStream = new MemoryStream();//实例化内存流
|
||||
//将数据流链接到加密转换的流
|
||||
var cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
// 转base64
|
||||
return Convert.ToBase64String(mStream.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return encryptString;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DES解密字符串
|
||||
/// </summary>
|
||||
/// <param name="decryptString">待解密的字符串</param>
|
||||
/// <param name="decryptKey">解密密钥,要求为8位,和加密密钥相同</param>
|
||||
/// <returns>解密成功返回解密后的字符串,失败返源串</returns>
|
||||
public static string DecryptString(string decryptString, string decryptKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(decryptKey);
|
||||
byte[] rgbIV = Encoding.UTF8.GetBytes(decryptKey);
|
||||
byte[] inputByteArray = Convert.FromBase64String(decryptString);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
var DCSP = new DESCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
var mStream = new MemoryStream();
|
||||
var cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
return Encoding.UTF8.GetString(mStream.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return decryptString;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
67
src/Downkyi.Core/Utils/Encryptor/Hash.cs
Normal file
67
src/Downkyi.Core/Utils/Encryptor/Hash.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Downkyi.Core.Utils.Encryptor;
|
||||
|
||||
public static class Hash
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算字符串MD5值
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMd5Hash(string input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MD5 md5Hash = MD5.Create();
|
||||
|
||||
// 将输入字符串转换为字节数组并计算哈希数据
|
||||
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
|
||||
|
||||
// 创建一个 Stringbuilder 来收集字节并创建字符串
|
||||
var sBuilder = new StringBuilder();
|
||||
|
||||
// 循环遍历哈希数据的每一个字节并格式化为十六进制字符串
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
sBuilder.Append(data[i].ToString("x2"));
|
||||
}
|
||||
|
||||
// 返回十六进制字符串
|
||||
return sBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件MD5值
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMD5HashFromFile(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = new FileStream(fileName, FileMode.Open);
|
||||
#pragma warning disable SYSLIB0021 // 类型或成员已过时
|
||||
MD5 md5 = new MD5CryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021 // 类型或成员已过时
|
||||
byte[] retVal = md5.ComputeHash(file);
|
||||
file.Close();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < retVal.Length; i++)
|
||||
{
|
||||
sb.Append(retVal[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("GetMD5HashFromFile()发生异常: {0}" + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
220
src/Downkyi.Core/Utils/Format.cs
Normal file
220
src/Downkyi.Core/Utils/Format.cs
Normal file
@ -0,0 +1,220 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Downkyi.Core.Utils;
|
||||
|
||||
public static class Format
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 格式化Duration时间
|
||||
/// </summary>
|
||||
/// <param name="duration"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatDuration(long duration)
|
||||
{
|
||||
string formatDuration;
|
||||
if (duration / 60 > 0)
|
||||
{
|
||||
long dur = duration / 60;
|
||||
if (dur / 60 > 0)
|
||||
{
|
||||
formatDuration = $"{dur / 60}h{dur % 60}m{duration % 60}s";
|
||||
}
|
||||
else
|
||||
{
|
||||
formatDuration = $"{duration / 60}m{duration % 60}s";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formatDuration = $"{duration}s";
|
||||
}
|
||||
return formatDuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化Duration时间,格式为00:00:00
|
||||
/// </summary>
|
||||
/// <param name="duration"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatDuration2(long duration)
|
||||
{
|
||||
string formatDuration;
|
||||
if (duration / 60 > 0)
|
||||
{
|
||||
long dur = duration / 60;
|
||||
if (dur / 60 > 0)
|
||||
{
|
||||
formatDuration = string.Format("{0:D2}", dur / 60) + ":" + string.Format("{0:D2}", dur % 60) + ":" + string.Format("{0:D2}", duration % 60);
|
||||
}
|
||||
else
|
||||
{
|
||||
formatDuration = "00:" + string.Format("{0:D2}", duration / 60) + ":" + string.Format("{0:D2}", duration % 60);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formatDuration = "00:00:" + string.Format("{0:D2}", duration);
|
||||
}
|
||||
return formatDuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化Duration时间,格式为00:00
|
||||
/// </summary>
|
||||
/// <param name="duration"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatDuration3(long duration)
|
||||
{
|
||||
string formatDuration;
|
||||
if (duration / 60 > 0)
|
||||
{
|
||||
long dur = duration / 60;
|
||||
if (dur / 60 > 0)
|
||||
{
|
||||
formatDuration = string.Format("{0:D2}", dur / 60) + ":" + string.Format("{0:D2}", dur % 60) + ":" + string.Format("{0:D2}", duration % 60);
|
||||
}
|
||||
else
|
||||
{
|
||||
formatDuration = string.Format("{0:D2}", duration / 60) + ":" + string.Format("{0:D2}", duration % 60);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formatDuration = "00:" + string.Format("{0:D2}", duration);
|
||||
}
|
||||
return formatDuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化数字,超过10000的数字将单位改为万,超过100000000的数字将单位改为亿,并保留1位小数
|
||||
/// </summary>
|
||||
/// <param name="number"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatNumber(long number)
|
||||
{
|
||||
if (number > 99999999)
|
||||
{
|
||||
return (number / 100000000.0f).ToString("F1") + "亿";
|
||||
}
|
||||
|
||||
if (number > 9999)
|
||||
{
|
||||
return (number / 10000.0f).ToString("F1") + "万";
|
||||
}
|
||||
else
|
||||
{
|
||||
return number.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化网速
|
||||
/// </summary>
|
||||
/// <param name="speed"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatSpeed(float speed)
|
||||
{
|
||||
string formatSpeed;
|
||||
if (speed <= 0)
|
||||
{
|
||||
formatSpeed = "0B/s";
|
||||
}
|
||||
else if (speed < 1024)
|
||||
{
|
||||
formatSpeed = string.Format("{0:F2}", speed) + "B/s";
|
||||
}
|
||||
else if (speed < 1024 * 1024)
|
||||
{
|
||||
formatSpeed = string.Format("{0:F2}", speed / 1024) + "KB/s";
|
||||
}
|
||||
else
|
||||
{
|
||||
formatSpeed = string.Format("{0:F2}", speed / 1024 / 1024) + "MB/s";
|
||||
}
|
||||
return formatSpeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化字节大小,可用于文件大小的显示
|
||||
/// </summary>
|
||||
/// <param name="fileSize"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatFileSize(long fileSize)
|
||||
{
|
||||
string formatFileSize;
|
||||
if (fileSize <= 0)
|
||||
{
|
||||
formatFileSize = "0B";
|
||||
}
|
||||
else if (fileSize < 1024)
|
||||
{
|
||||
formatFileSize = fileSize.ToString() + "B";
|
||||
}
|
||||
else if (fileSize < 1024 * 1024)
|
||||
{
|
||||
formatFileSize = (fileSize / 1024.0).ToString("#.##") + "KB";
|
||||
}
|
||||
else if (fileSize < 1024 * 1024 * 1024)
|
||||
{
|
||||
formatFileSize = (fileSize / 1024.0 / 1024.0).ToString("#.##") + "MB";
|
||||
}
|
||||
else
|
||||
{
|
||||
formatFileSize = (fileSize / 1024.0 / 1024.0 / 1024.0).ToString("#.##") + "GB";
|
||||
}
|
||||
return formatFileSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除非法字符
|
||||
/// </summary>
|
||||
/// <param name="originName"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatFileName(string originName)
|
||||
{
|
||||
string destName = originName;
|
||||
|
||||
// Windows中不能作为文件名的字符
|
||||
destName = destName.Replace("\\", " ");
|
||||
destName = destName.Replace("/", " ");
|
||||
destName = destName.Replace(":", " ");
|
||||
destName = destName.Replace("*", " ");
|
||||
destName = destName.Replace("?", " ");
|
||||
destName = destName.Replace("\"", " ");
|
||||
destName = destName.Replace("<", " ");
|
||||
destName = destName.Replace(">", " ");
|
||||
destName = destName.Replace("|", " ");
|
||||
|
||||
// 转义字符
|
||||
destName = destName.Replace("\a", "");
|
||||
destName = destName.Replace("\b", "");
|
||||
destName = destName.Replace("\f", "");
|
||||
destName = destName.Replace("\n", "");
|
||||
destName = destName.Replace("\r", "");
|
||||
destName = destName.Replace("\t", "");
|
||||
destName = destName.Replace("\v", "");
|
||||
|
||||
// 控制字符
|
||||
destName = Regex.Replace(destName, @"\p{C}+", string.Empty);
|
||||
|
||||
// 移除前导和尾部的空白字符、dot符
|
||||
int i, j;
|
||||
for (i = 0; i < destName.Length; i++)
|
||||
{
|
||||
if (destName[i] != ' ' && destName[i] != '.')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (j = destName.Length - 1; j >= 0; j--)
|
||||
{
|
||||
if (destName[j] != ' ' && destName[j] != '.')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return destName.Substring(i, j - i + 1);
|
||||
}
|
||||
|
||||
}
|
64
src/Downkyi.Core/Utils/HardDisk.cs
Normal file
64
src/Downkyi.Core/Utils/HardDisk.cs
Normal file
@ -0,0 +1,64 @@
|
||||
namespace Downkyi.Core.Utils;
|
||||
|
||||
public static class HardDisk
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定驱动器的空间总大小
|
||||
/// </summary>
|
||||
/// <param name="hardDiskName">只需输入代表驱动器的字母即可</param>
|
||||
/// <returns></returns>
|
||||
public static long GetHardDiskSpace(string hardDiskName)
|
||||
{
|
||||
long totalSize = 0;
|
||||
|
||||
try
|
||||
{
|
||||
hardDiskName = $"{hardDiskName}:\\";
|
||||
DriveInfo[] drives = DriveInfo.GetDrives();
|
||||
|
||||
foreach (DriveInfo drive in drives)
|
||||
{
|
||||
if (drive.Name == hardDiskName)
|
||||
{
|
||||
totalSize = drive.TotalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定驱动器的剩余空间总大小
|
||||
/// </summary>
|
||||
/// <param name="hardDiskName">只需输入代表驱动器的字母即可</param>
|
||||
/// <returns></returns>
|
||||
public static long GetHardDiskFreeSpace(string hardDiskName)
|
||||
{
|
||||
long freeSpace = 0;
|
||||
try
|
||||
{
|
||||
hardDiskName = $"{hardDiskName}:\\";
|
||||
DriveInfo[] drives = DriveInfo.GetDrives();
|
||||
|
||||
foreach (DriveInfo drive in drives)
|
||||
{
|
||||
if (drive.Name == hardDiskName)
|
||||
{
|
||||
freeSpace = drive.TotalFreeSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
}
|
||||
|
||||
return freeSpace;
|
||||
}
|
||||
|
||||
}
|
87
src/Downkyi.Core/Utils/LinkStack.cs
Normal file
87
src/Downkyi.Core/Utils/LinkStack.cs
Normal file
@ -0,0 +1,87 @@
|
||||
namespace Downkyi.Core.Utils;
|
||||
|
||||
// 链表实现栈
|
||||
public class LinkStack<T>
|
||||
{
|
||||
//栈顶指示器
|
||||
public Node<T> Top { get; set; }
|
||||
|
||||
//栈中结点的个数
|
||||
public int NCount { get; set; }
|
||||
|
||||
//初始化
|
||||
public LinkStack()
|
||||
{
|
||||
Top = null;
|
||||
NCount = 0;
|
||||
}
|
||||
|
||||
//获取栈的长度
|
||||
public int GetLength()
|
||||
{
|
||||
return NCount;
|
||||
}
|
||||
|
||||
//判断栈是否为空
|
||||
public bool IsEmpty()
|
||||
{
|
||||
if ((Top == null) && (0 == NCount))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//入栈
|
||||
public void Push(T item)
|
||||
{
|
||||
Node<T> p = new(item);
|
||||
if (Top == null)
|
||||
{
|
||||
Top = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.Next = Top;
|
||||
Top = p;
|
||||
}
|
||||
NCount++;
|
||||
}
|
||||
|
||||
//出栈
|
||||
public T Pop()
|
||||
{
|
||||
if (IsEmpty())
|
||||
{
|
||||
return default;
|
||||
}
|
||||
Node<T> p = Top;
|
||||
Top = Top.Next;
|
||||
--NCount;
|
||||
return p.Data;
|
||||
}
|
||||
|
||||
//
|
||||
public T Peek()
|
||||
{
|
||||
if (IsEmpty())
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return Top.Data;
|
||||
}
|
||||
}
|
||||
|
||||
//结点定义
|
||||
public class Node<T>
|
||||
{
|
||||
public T Data;
|
||||
|
||||
public Node<T> Next;
|
||||
|
||||
public Node(T item)
|
||||
{
|
||||
Data = item;
|
||||
}
|
||||
}
|
56
src/Downkyi.Core/Utils/ListHelper.cs
Normal file
56
src/Downkyi.Core/Utils/ListHelper.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Downkyi.Core.Utils;
|
||||
|
||||
public static class ListHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 判断ObservableCollection中是否存在,不存在则添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="item"></param>
|
||||
public static void AddUnique<T>(ObservableCollection<T> list, T item)
|
||||
{
|
||||
if (!list.Contains(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断List中是否存在,不存在则添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="item"></param>
|
||||
public static void AddUnique<T>(List<T> list, T item)
|
||||
{
|
||||
if (!list.Exists(t => t.Equals(item)))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断List中是否存在,不存在则添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="index"></param>
|
||||
public static void InsertUnique<T>(List<T> list, T item, int index)
|
||||
{
|
||||
if (!list.Exists(t => t.Equals(item)))
|
||||
{
|
||||
list.Insert(index, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Remove(item);
|
||||
list.Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
63
src/Downkyi.Core/Utils/ObjectHelper.cs
Normal file
63
src/Downkyi.Core/Utils/ObjectHelper.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
namespace Downkyi.Core.Utils;
|
||||
|
||||
public static class ObjectHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 写入序列化对象到磁盘
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool WriteObjectToDisk(string file, object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
using Stream stream = File.Create(file);
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning disable SYSLIB0011 // 类型或成员已过时
|
||||
formatter.Serialize(stream, obj);
|
||||
#pragma warning restore SYSLIB0011 // 类型或成员已过时
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从磁盘读取序列化对象
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public static object ReadObjectFromDisk(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using Stream stream = File.Open(file, FileMode.Open);
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning disable SYSLIB0011 // 类型或成员已过时
|
||||
return formatter.Deserialize(stream);
|
||||
#pragma warning restore SYSLIB0011 // 类型或成员已过时
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log.Logger.Error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
58
src/Downkyi.Core/Utils/Stack.cs
Normal file
58
src/Downkyi.Core/Utils/Stack.cs
Normal file
@ -0,0 +1,58 @@
|
||||
namespace Downkyi.Core.Utils;
|
||||
|
||||
public class Stack<T>
|
||||
{
|
||||
private int _maxSize;
|
||||
private int _top = -1;
|
||||
private T[] _data;
|
||||
|
||||
public int Count => _maxSize;
|
||||
public bool IsFull { get => _top == _maxSize - 1; }
|
||||
public bool IsEmpty { get => _top == -1; }
|
||||
|
||||
public Stack(int maxSize)
|
||||
{
|
||||
_maxSize = maxSize;
|
||||
_data = new T[maxSize];
|
||||
}
|
||||
|
||||
public void Push(T value)
|
||||
{
|
||||
if (IsFull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_data[++_top] = value;
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return _data[_top--];
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return _data[_top];
|
||||
}
|
||||
|
||||
//public Stack<T>? Traverse()
|
||||
//{
|
||||
// if (IsEmpty) { return default; }
|
||||
|
||||
// for (int i = _top; i >= 0; i--)
|
||||
// {
|
||||
// // TODO
|
||||
// }
|
||||
//}
|
||||
|
||||
}
|
28
src/Downkyi.Core/Utils/Validator/Number.cs
Normal file
28
src/Downkyi.Core/Utils/Validator/Number.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Downkyi.Core.Utils.Validator;
|
||||
|
||||
public static class Number
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转数字(长整型)
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetInt(string value)
|
||||
{
|
||||
return IsInt(value) ? long.Parse(value) : -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为数字
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsInt(string value)
|
||||
{
|
||||
return Regex.IsMatch(value, @"^\d+$");
|
||||
}
|
||||
|
||||
}
|
19
src/Downkyi.UI/Downkyi.UI.csproj
Normal file
19
src/Downkyi.UI/Downkyi.UI.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Downkyi.Core\Downkyi.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
7
src/Downkyi.UI/Models/Cookie.cs
Normal file
7
src/Downkyi.UI/Models/Cookie.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Downkyi.UI.Models;
|
||||
|
||||
public class Cookie
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
16
src/Downkyi.UI/Models/FileNamePartDisplay.cs
Normal file
16
src/Downkyi.UI/Models/FileNamePartDisplay.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Downkyi.Core.FileName;
|
||||
|
||||
namespace Downkyi.UI.Models;
|
||||
|
||||
public partial class FileNamePartDisplay : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public int _index;
|
||||
|
||||
[ObservableProperty]
|
||||
public FileNamePart _id;
|
||||
|
||||
[ObservableProperty]
|
||||
public string _title = string.Empty;
|
||||
}
|
9
src/Downkyi.UI/Models/OrderFormatDisplay.cs
Normal file
9
src/Downkyi.UI/Models/OrderFormatDisplay.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.UI.Models;
|
||||
|
||||
public class OrderFormatDisplay
|
||||
{
|
||||
public OrderFormat Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
9
src/Downkyi.UI/Models/ParseScopeDisplay.cs
Normal file
9
src/Downkyi.UI/Models/ParseScopeDisplay.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Downkyi.Core.Settings.Enum;
|
||||
|
||||
namespace Downkyi.UI.Models;
|
||||
|
||||
public class ParseScopeDisplay
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public ParseScope ParseScope { get; set; }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user