重构
@@ -0,0 +1,43 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
|
||||
|
||||
class TextMessage:
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
|
||||
class ImageMessage:
|
||||
def __init__(self, image):
|
||||
self.image = image
|
||||
|
||||
|
||||
class UnknownMessage:
|
||||
def __init__(self, raw):
|
||||
self.raw = raw
|
||||
|
||||
|
||||
class MessageState:
|
||||
def __init__(self, data_dict):
|
||||
self.data_dict = data_dict
|
||||
|
||||
# 获取命令头
|
||||
def get_command(self):
|
||||
return self.data_dict['_prefix']['command']
|
||||
|
||||
# 获取回复
|
||||
def get_reply(self):
|
||||
return self.data_dict['reply']
|
||||
|
||||
# 获取回复对象的文本
|
||||
|
||||
# 获取命令参数
|
||||
def get_command_arg(self):
|
||||
command_arg_list = self.data_dict['_prefix']['command_arg']
|
||||
if command_arg_list:
|
||||
command_arg = command_arg_list[0]
|
||||
if isinstance(command_arg, MessageSegment):
|
||||
if command_arg.type == 'text':
|
||||
return TextMessage(command_arg.data['text'])
|
||||
elif command_arg.type == 'image':
|
||||
return ImageMessage(command_arg.data['url'])
|
||||
return None # 返回 None 表示命令参数为空或无法解析
|
||||
@@ -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>.
|
||||
@@ -0,0 +1,47 @@
|
||||
# 今日老婆
|
||||
|
||||
一个适用于HoshinoBot的随机群友老婆插件
|
||||
|
||||
### ★ 如果你喜欢的话,请给仓库点一个star支持一下23333 ★
|
||||
|
||||
## 本项目地址:
|
||||
|
||||
https://github.com/SonderXiaoming/dailywife
|
||||
|
||||
## 部署教程:
|
||||
|
||||
1.下载或git clone本插件:
|
||||
|
||||
在 HoshinoBot\hoshino\modules 目录下使用以下命令拉取本项目
|
||||
|
||||
git clone https://github.com/SonderXiaoming/dailywife
|
||||
|
||||
2.启用:
|
||||
|
||||
在 HoshinoBot\hoshino\config\ **bot**.py 文件的 MODULES_ON 加入 'dailywife'
|
||||
|
||||
然后重启 HoshinoBot
|
||||
|
||||
## 指令
|
||||
|
||||
【今日老婆】
|
||||
|
||||
随机抓一位群友当老婆
|
||||
|
||||
每天群友老婆是固定的
|
||||
|
||||
每个群友只能当一个人的老婆
|
||||
|
||||
## 彩蛋
|
||||
|
||||
超管必抽到bot
|
||||
|
||||
群友必抽不到bot
|
||||
|
||||
(与原版老婆指令对应,想ntr我老婆?)
|
||||
|
||||
修改的话只要把laopo.py的82,83,94行注释掉,84行elif改成if
|
||||
|
||||
## 已知问题
|
||||
|
||||
储存数据用json可能不太好
|
||||
@@ -0,0 +1,126 @@
|
||||
import base64
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from random import choice
|
||||
from tkinter import Image
|
||||
|
||||
from PIL import *
|
||||
from .utils import *
|
||||
|
||||
import httpx
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="今日老婆",
|
||||
description="随机抓取群友作为老婆",
|
||||
usage="发送【今日老婆】",
|
||||
type="application",
|
||||
)
|
||||
|
||||
|
||||
wife = on_command("今日老婆", aliases={"今日老婆"})
|
||||
|
||||
|
||||
def get_member_list(all_list):
|
||||
id_list = []
|
||||
for member_list in all_list:
|
||||
id_list.append(member_list['user_id'])
|
||||
return id_list
|
||||
|
||||
|
||||
async def download_avatar(user_id: str) -> bytes:
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=640"
|
||||
data = await download_url(url)
|
||||
if not data or hashlib.md5(data).hexdigest() == "acef72340ac0e914090bd35799f5594e":
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=100"
|
||||
data = await download_url(url)
|
||||
return data
|
||||
|
||||
|
||||
async def download_url(url: str) -> bytes:
|
||||
async with httpx.AsyncClient() as client:
|
||||
for i in range(3):
|
||||
try:
|
||||
resp = await client.get(url)
|
||||
if resp.status_code != 200:
|
||||
continue
|
||||
return resp.content
|
||||
except Exception as e:
|
||||
print(f"Error downloading {url}, retry {i}/3: {str(e)}")
|
||||
|
||||
|
||||
async def get_wife_info(member_info, qq_id):
|
||||
img = await download_avatar(qq_id)
|
||||
avatar = Image.open(BytesIO(img)).convert('RGBA')
|
||||
avatar = png_resize(avatar, new_width=145, new_height=145)
|
||||
|
||||
b_io = BytesIO()
|
||||
avatar.save(b_io, format="PNG")
|
||||
avatar_str = 'base64://' + base64.b64encode(b_io.getvalue()).decode()
|
||||
member_name = (member_info["card"] or member_info["nickname"])
|
||||
msg = (MessageSegment.text('你今天的群友老婆是:'), MessageSegment.image(avatar_str), MessageSegment.text(f'{member_name}({qq_id})'))
|
||||
return msg
|
||||
|
||||
|
||||
def load_group_config(group_id: str) -> int:
|
||||
filename = os.path.join(os.path.dirname(__file__), 'config', f'{group_id}.json')
|
||||
try:
|
||||
with open(filename, encoding='utf8') as f:
|
||||
config = json.load(f)
|
||||
return config
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def write_group_config(group_id: str, link_id: str, wife_id: str, date: str, config) -> int:
|
||||
config_file = os.path.join(os.path.dirname(__file__), 'config', f'{group_id}.json')
|
||||
if config is not None:
|
||||
config[link_id] = [wife_id, date]
|
||||
else:
|
||||
config = {link_id: [wife_id, date]}
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, ensure_ascii=False)
|
||||
|
||||
|
||||
@wife.handle()
|
||||
async def wife_handle(bot: Bot, ev: MessageEvent):
|
||||
group_id = ev.group_id
|
||||
user_id = ev.user_id
|
||||
bot_id = ev.self_id
|
||||
wife_id = None
|
||||
today = str(datetime.date.today())
|
||||
config = load_group_config(group_id)
|
||||
|
||||
# if priv.check_priv(ev, priv.SUPERUSER):
|
||||
# wife_id = bot_id
|
||||
if config is not None:
|
||||
if str(user_id) in list(config):
|
||||
if config[str(user_id)][1] == today:
|
||||
wife_id = config[str(user_id)][0]
|
||||
else:
|
||||
del config[str(user_id)]
|
||||
|
||||
if wife_id is None:
|
||||
all_list = await bot.get_group_member_list(group_id=group_id)
|
||||
id_list = get_member_list(all_list)
|
||||
id_list.remove(bot_id)
|
||||
id_list.remove(user_id)
|
||||
if config is not None:
|
||||
for record_id in list(config):
|
||||
if config[record_id][1] != today:
|
||||
del config[record_id]
|
||||
else:
|
||||
try:
|
||||
id_list.remove(int(config[record_id][0]))
|
||||
except:
|
||||
del config[record_id]
|
||||
wife_id = choice(id_list)
|
||||
|
||||
write_group_config(group_id, user_id, wife_id, today, config)
|
||||
member_info = await bot.get_group_member_info(group_id=group_id, user_id=wife_id)
|
||||
result = await get_wife_info(member_info, wife_id)
|
||||
await bot.send(ev, result, at_sender=True)
|
||||
@@ -0,0 +1 @@
|
||||
{"2111373772": [113660107, "2024-04-24"], "3496345601": [1624801123, "2024-04-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1041319467": [516472094, "2025-08-05"], "2252832821": [2879886254, "2025-08-05"], "1726352952": [2587439644, "2025-08-05"], "1273998653": [3687765006, "2025-08-05"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1041319467": [2011432684, "2024-04-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1870323135": [1693095928, "2024-06-22"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"447903337": [1057560628, "2024-09-24"], "1545789605": [3201624634, "2024-09-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"3500670381": [2021200582, "2025-04-02"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2560583072": [1578783182, "2024-04-24"], "3452205618": [1164425063, "2024-04-24"], "1960334592": [1349706190, "2024-04-24"], "771544833": [3365479138, "2024-04-24"], "2597016088": [3212547299, "2024-04-24"], "845212973": [1242230326, "2024-04-24"], "1040302669": [1663793512, "2024-04-24"], "2908237931": [1219646623, "2024-04-24"], "1284532773": [1295637870, "2024-04-24"], "2181584948": [2435409658, "2024-04-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"3809807527": [956488551, "2024-04-23"], "2456989313": [1139545630, "2024-04-23"], "1350814347": [1124975860, "2024-04-23"], "2968927618": [767255379, "2024-04-23"], "1148140708": [7371401, "2024-04-23"], "384869125": [897623868, "2024-04-23"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2931589710": [2982587103, "2025-01-10"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"395381325": [945521635, "2025-12-23"], "3534766203": [3384485623, "2025-12-23"], "2691608860": [3073064852, "2025-12-23"], "1965232310": [1324576132, "2025-12-23"], "2832051108": [1928854168, "2025-12-23"], "1764806197": [2310049326, "2025-12-23"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2252832821": [465189257, "2024-04-18"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"861757133": [3213425463, "2024-04-24"], "771544833": [1030501335, "2024-04-24"], "1991355616": [1919336604, "2024-04-24"], "2181584948": [2753168793, "2024-04-24"], "1606307892": [935634067, "2024-04-24"], "2474392019": [867633152, "2024-04-24"], "2813477297": [1553910163, "2024-04-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1519780248": [1709371237, "2025-10-09"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2234705552": [1062049100, "2024-04-23"], "1344548726": [1344952304, "2024-04-23"], "1198754678": [252439962, "2024-04-23"], "2736765611": [2295029310, "2024-04-23"], "3081567652": [1737857394, "2024-04-23"], "2402238200": [1449176382, "2024-04-23"], "541633910": [2529564599, "2024-04-23"], "3046137043": [2977483190, "2024-04-23"], "773915294": [1094329720, "2024-04-23"], "792646330": [2184843957, "2024-04-23"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2931589710": [2667540873, "2025-07-31"], "2931589710": [2667540873, "2025-07-31"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"390706827": [1482178145, "2024-09-06"], "2749779263": [1948639320, "2024-09-06"], "3500670381": [1545994950, "2024-09-06"], "623237274": [2028347157, "2024-09-06"], "1057720281": [1532814226, "2024-09-06"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"3315008735": [283791768, "2024-04-24"], "3469694349": [1792906159, "2024-04-24"], "1980736657": [1204844994, "2024-04-24"], "771544833": [692986540, "2024-04-24"], "3090432712": [1924486277, "2024-04-24"], "2157483791": [1404166113, "2024-04-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2252832821": [285776773, "2024-04-18"], "1053331854": [3687765006, "2024-04-18"], "1480341139": [2111969151, "2024-04-18"], "577965332": [2635073955, "2024-04-18"], "3131324404": [1219790234, "2024-04-18"], "1582656380": [742916768, "2024-04-18"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2931589710": [447903337, "2025-12-12"], "2815144875": [94070601, "2025-12-12"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"2634380815": [2897012201, "2024-06-30"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1875610312": [2584601320, "2024-04-24"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1965232310": [69420118, "2025-12-23"], "2832051108": [2205850435, "2025-12-23"], "2279525091": [1010165084, "2025-12-23"], "1764806197": [1782481403, "2025-12-23"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"946576072": [3561498724, "2024-10-14"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"227630446": [3346763613, "2025-07-30"], "2125169453": [906789748, "2025-07-30"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"614825772": [2087918340, "2024-04-23"], "1059812302": [1805978488, "2024-04-23"], "863544577": [1776527408, "2024-04-23"], "1191715363": [2864877358, "2024-04-23"], "450457018": [460929885, "2024-04-23"], "1003695152": [3061023080, "2024-04-23"], "3152359951": [854115386, "2024-04-23"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"1960334592": [736676100, "2024-10-11"], "2474392019": [2104386016, "2024-10-11"], "1278029334": [198295337, "2024-10-11"], "845212973": [2585860802, "2024-10-11"], "264599930": [3558155628, "2024-10-11"], "1424773950": [1609251180, "2024-10-11"], "2813477297": [1109322070, "2024-10-11"], "2739478918": [2323997133, "2024-10-11"], "2671529373": [1157197830, "2024-10-11"], "2366805964": [1511143622, "2024-10-11"], "703569661": [771544833, "2024-10-11"], "1119162975": [2296481891, "2024-10-11"], "447903337": [2668590688, "2024-10-11"], "724041940": [453427269, "2024-10-11"], "1990345380": [3070520589, "2024-10-11"]}
|
||||
@@ -0,0 +1,50 @@
|
||||
import hashlib
|
||||
import json
|
||||
from nonebot.log import logger as sv
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from io import BytesIO
|
||||
|
||||
import aiohttp
|
||||
import qrcode
|
||||
import requests
|
||||
import requests.exceptions
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
# PNG重绘大小
|
||||
def png_resize(source_file, new_width=0, new_height=0, resample="LANCZOS", ref_file=''):
|
||||
"""
|
||||
PNG缩放透明度处理
|
||||
:param source_file: 源文件(Image.open())
|
||||
:param new_width: 设置的宽度
|
||||
:param new_height: 设置的高度
|
||||
:param resample: 抗锯齿
|
||||
:param ref_file: 参考文件
|
||||
:return:
|
||||
"""
|
||||
img = source_file
|
||||
img = img.convert("RGBA")
|
||||
width, height = img.size
|
||||
|
||||
if ref_file != '':
|
||||
imgRef = Image.open(ref_file)
|
||||
new_width, new_height = imgRef.size
|
||||
else:
|
||||
if new_height == 0:
|
||||
new_height = new_width * width / height
|
||||
|
||||
bands = img.split()
|
||||
resample_map = {
|
||||
"NEAREST": Image.NEAREST,
|
||||
"BILINEAR": Image.BILINEAR,
|
||||
"BICUBIC": Image.BICUBIC,
|
||||
"LANCZOS": Image.LANCZOS
|
||||
}
|
||||
resample_method = resample_map.get(resample, Image.LANCZOS) # 默认使用 LANCZOS
|
||||
|
||||
bands = [b.resize((new_width, new_height), resample=resample_method) for b in bands]
|
||||
resized_file = Image.merge('RGBA', bands)
|
||||
|
||||
return resized_file
|
||||
@@ -0,0 +1,203 @@
|
||||
# import asyncio
|
||||
# import base64
|
||||
# import io
|
||||
# import json
|
||||
# import os
|
||||
# from datetime import datetime
|
||||
# from nonebot import logger
|
||||
# from PIL import Image, ImageOps, ImageDraw, ImageFont
|
||||
# from nonebot.internal.params import ArgPlainText
|
||||
# from nonebot.params import CommandArg
|
||||
# from nonebot.typing import T_State
|
||||
# from playwright.async_api import async_playwright
|
||||
# from nonebot import on_command
|
||||
# from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment, GroupMessageEvent, Message
|
||||
# from nonebot.plugin import PluginMetadata
|
||||
# from typing import Optional, Union
|
||||
# from nonebot.matcher import Matcher
|
||||
#
|
||||
# __plugin_meta__ = PluginMetadata(
|
||||
# name="DeadLock助手",
|
||||
# description="DeadLock助手,我要成为DeadLock高手",
|
||||
# usage="nekoscore:neko查分 [steam好友代码]\n"
|
||||
# "nekobind:neko绑定 [steam好友代码]\n"
|
||||
# "nekorank:neko排行[TODO]\n"
|
||||
# "nekomatch:dl比赛详情[TODO]\n",
|
||||
# type="application",
|
||||
# extra={
|
||||
#
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# neko_score = on_command("nekoscore", aliases={"dl查分"})
|
||||
# neko_bind = on_command("nekobind", aliases={"dl绑定"})
|
||||
# neko_unbind = on_command("nekounbind", aliases={"取消dl绑定"})
|
||||
# neko_rank = on_command("nekorank", aliases={"dl排行"})
|
||||
# neko_match = on_command("nekomatch", aliases={"dl比赛详情"})
|
||||
#
|
||||
# basic_path = os.path.dirname(__file__)
|
||||
# save_path = os.path.join(basic_path, "temp")
|
||||
# img_path = os.path.join(basic_path, "img")
|
||||
# data_path = os.path.join(basic_path, "data")
|
||||
#
|
||||
#
|
||||
# @neko_score.handle()
|
||||
# async def get_neko_score(ev: MessageEvent, rgs: Message = CommandArg()):
|
||||
# steam_id = rgs.extract_plain_text()
|
||||
# # 查询绑定情况
|
||||
# if steam_id is None or steam_id == "" or steam_id.isdigit() is False:
|
||||
# bind_steam_id = await get_neko_bind(ev.group_id, ev.user_id)
|
||||
# if bind_steam_id is None:
|
||||
# await neko_score.finish("请输入正确的steam好友代码!")
|
||||
# return
|
||||
# else:
|
||||
# steam_id = bind_steam_id
|
||||
# await neko_score.send(f"正在获取{steam_id}的数据")
|
||||
# url = f"https://deadlock.blast.tv/users/{steam_id}"
|
||||
# time_present1 = get_present_time()
|
||||
# result = await capture_screenshot(url, time_present1)
|
||||
# if result != "success":
|
||||
# await neko_score.finish(MessageSegment.reply(ev.message_id) + result)
|
||||
# img_path1 = os.path.join(save_path, f"{time_present1}.png")
|
||||
# images = gen_ms_img(Image.open(img_path1))
|
||||
# mes = (MessageSegment.reply(ev.message_id), images)
|
||||
# await neko_score.send(mes)
|
||||
# os.remove(img_path1)
|
||||
#
|
||||
#
|
||||
# @neko_bind.handle()
|
||||
# async def play_neko_bind(ev: MessageEvent, rgs: Message = CommandArg()):
|
||||
# # 获取群号
|
||||
# group_id = ev.group_id
|
||||
# steam_id = rgs.extract_plain_text()
|
||||
# if steam_id is None or steam_id == "" or steam_id.isdigit() is False:
|
||||
# await neko_score.finish("请输入正确的steam好友代码!")
|
||||
# return
|
||||
# qq_id = ev.user_id
|
||||
# res = await to_neko_bind(group_id, steam_id, qq_id)
|
||||
# await neko_score.send(res)
|
||||
#
|
||||
#
|
||||
# @neko_unbind.handle()
|
||||
# async def play_neko_unbind(ev: MessageEvent, rgs: Message = CommandArg()):
|
||||
# # 获取群号
|
||||
# group_id = ev.group_id
|
||||
# steam_id = rgs.extract_plain_text()
|
||||
# qq_id = ev.user_id
|
||||
# res = await to_neko_unbind(group_id, qq_id)
|
||||
# if res is None:
|
||||
# await neko_score.finish(f"{qq_id}未绑定过")
|
||||
# else:
|
||||
# await neko_score.send(f"{qq_id}已经取消与{res}的绑定")
|
||||
#
|
||||
#
|
||||
# async def to_neko_bind(group_id, steam_id, qq_id):
|
||||
# if os.path.exists(f"{data_path}/{group_id}.json"):
|
||||
# with open(f"{data_path}/{group_id}.json", 'r') as file:
|
||||
# json_data = json.load(file)
|
||||
# else:
|
||||
# # 如果文件不存在,创建一个新的空字典
|
||||
# json_data = {}
|
||||
# # 检查指定的 key 是否存在
|
||||
# if str(qq_id) in json_data:
|
||||
# # 如果 key 存在,将对应键值更新
|
||||
# old_steam_id = json_data[str(qq_id)]
|
||||
# json_data[str(qq_id)] = steam_id
|
||||
# # 将更新后的数据写回 JSON 文件
|
||||
# with open(f"{data_path}/{group_id}.json", 'w') as file:
|
||||
# json.dump(json_data, file, indent=4)
|
||||
# return f"已经将[ {str(qq_id)} ]的绑定[ {old_steam_id} ]更新到了[ {str(steam_id)} ]"
|
||||
# else:
|
||||
# # 如果 key 不存在,添加新的 key-value 对
|
||||
# json_data[str(qq_id)] = steam_id
|
||||
#
|
||||
# # 将更新后的数据写回 JSON 文件
|
||||
# with open(f"{data_path}/{group_id}.json", 'w') as file:
|
||||
# json.dump(json_data, file, indent=4)
|
||||
# return f"[ {str(qq_id)} ]已经绑定[ {steam_id} ]"
|
||||
#
|
||||
#
|
||||
# async def get_neko_bind(group_id, qq_id):
|
||||
# if os.path.exists(f"{data_path}/{group_id}.json"):
|
||||
# with open(f"{data_path}/{group_id}.json", 'r') as file:
|
||||
# json_data = json.load(file)
|
||||
# else:
|
||||
# # 如果文件不存在,创建一个新的空字典
|
||||
# json_data = {
|
||||
# "000000": 000000
|
||||
# }
|
||||
# # 检查指定的 key 是否存在
|
||||
# if str(qq_id) in json_data:
|
||||
# return json_data[str(qq_id)]
|
||||
# else:
|
||||
# return None
|
||||
#
|
||||
#
|
||||
# async def to_neko_unbind(group_id, qq_id):
|
||||
# if os.path.exists(f"{data_path}/{group_id}.json"):
|
||||
# with open(f"{data_path}/{group_id}.json", 'r') as file:
|
||||
# json_data = json.load(file)
|
||||
# else:
|
||||
# # 如果文件不存在,创建一个新的空字典
|
||||
# json_data = {
|
||||
# "000000": 000000
|
||||
# }
|
||||
# # 检查指定的 key 是否存在
|
||||
# if str(qq_id) in json_data:
|
||||
# remove_value = json_data.pop(str(qq_id))
|
||||
# # 将更新后的数据写回 JSON 文件
|
||||
# with open(f"{data_path}/{group_id}.json", 'w') as file:
|
||||
# json.dump(json_data, file, indent=4)
|
||||
# return remove_value
|
||||
# else:
|
||||
# return None
|
||||
#
|
||||
#
|
||||
# def get_present_time() -> int:
|
||||
# return int(datetime.timestamp(datetime.now()))
|
||||
#
|
||||
#
|
||||
# async def capture_screenshot(url: str, time_present: int):
|
||||
# async with async_playwright() as p:
|
||||
# browser = await p.chromium.launch()
|
||||
# page = await browser.new_page()
|
||||
# try:
|
||||
# # 设置视口大小
|
||||
# await page.set_viewport_size({"width": 1600, "height": 900})
|
||||
# await page.goto(url)
|
||||
# await page.wait_for_load_state('networkidle')
|
||||
#
|
||||
# except Exception as e:
|
||||
# return f"访问网站异常{type(e)}`{e}`"
|
||||
#
|
||||
# await asyncio.sleep(1)
|
||||
# i_path = os.path.join(save_path, f'{time_present}.png')
|
||||
# await page.screenshot(
|
||||
# path=i_path,
|
||||
# full_page=True
|
||||
# )
|
||||
# logger.info("正在压缩图片...")
|
||||
# await asyncio.sleep(2)
|
||||
# img_convert = Image.open(i_path)
|
||||
# img_convert.save(i_path, quality=80)
|
||||
# logger.info("图片保存成功!")
|
||||
# await browser.close()
|
||||
# return "success"
|
||||
#
|
||||
#
|
||||
# def gen_ms_img(image: Union[bytes, Image.Image]) -> MessageSegment:
|
||||
# if isinstance(image, bytes):
|
||||
# return MessageSegment.image(
|
||||
# pic2b64(Image.open(io.BytesIO(image)))
|
||||
# )
|
||||
# else:
|
||||
# return MessageSegment.image(
|
||||
# pic2b64(image)
|
||||
# )
|
||||
#
|
||||
#
|
||||
# def pic2b64(pic: Image) -> str:
|
||||
# buf = io.BytesIO()
|
||||
# pic.save(buf, format='PNG')
|
||||
# base64_str = base64.b64encode(buf.getvalue()).decode()
|
||||
# return 'base64://' + base64_str
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"2931589710": "441954135",
|
||||
"2575131547": "1244138625",
|
||||
"2028018739": "869378244"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"2931589710": "441954135"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"2931589710": "441954135",
|
||||
"1651620969": "1220823479"
|
||||
}
|
||||
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 212 KiB |
@@ -0,0 +1,56 @@
|
||||
# from venv import logger
|
||||
#
|
||||
# from nonebot import on_command
|
||||
# from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment
|
||||
# from nonebot.plugin import PluginMetadata
|
||||
# from nonebot.plugin.on import on_message
|
||||
# from nonebot.rule import to_me
|
||||
# import io
|
||||
# import base64
|
||||
# from .chat import *
|
||||
#
|
||||
# __plugin_meta__ = PluginMetadata(
|
||||
# name="AI",
|
||||
# description="AI?",
|
||||
# usage="",
|
||||
# type="application",
|
||||
# )
|
||||
#
|
||||
# at_me = on_command("", rule=to_me())
|
||||
#
|
||||
#
|
||||
# # at时忽略上下文
|
||||
# @at_me.handle()
|
||||
# async def do_job(bot: Bot, ev: MessageEvent):
|
||||
# # gid = ev.group_id
|
||||
# # print(gid)
|
||||
# # if gid in [872490448, 487723385]:
|
||||
# # logger.info("白名单群, 执行AI回答操作")
|
||||
# # text = ev.message
|
||||
# # message = text.extract_plain_text()
|
||||
# # print(type(text))
|
||||
# # print(text)
|
||||
# # think, answer = await chat(message)
|
||||
# # await at_me.finish(answer)
|
||||
# # else:
|
||||
# # logger.info("非白名单群,不执行AI回答操作")
|
||||
#
|
||||
# #
|
||||
# # @query.handle()
|
||||
# # async def query_job(bot: Bot, ev: MessageEvent):
|
||||
# # # 获取当前日期的年份和月份
|
||||
# # now = datetime.now()
|
||||
# # year = now.year
|
||||
# # month = now.month
|
||||
# # # 获取发信人信息
|
||||
# # uid = str(ev.user_id)
|
||||
# # message_id = ev.message_id
|
||||
# # # 获取记录
|
||||
# # img_file = await draw_calendar_grid_image(uid, year, month)
|
||||
# # text_msg = MessageSegment.text("这是您的记鹿:\n")
|
||||
# # image_msg = MessageSegment.image(pic2b64(img_file))
|
||||
# # mix_msg = (text_msg,)
|
||||
# # mix_msg += image_msg
|
||||
# # reply_msg = (MessageSegment.reply(message_id),)
|
||||
# # reply_msg += mix_msg
|
||||
# # await record.finish(reply_msg)
|
||||
@@ -0,0 +1,82 @@
|
||||
# import requests
|
||||
# import json
|
||||
# import re
|
||||
# import asyncio
|
||||
# from ollama import AsyncClient
|
||||
# from ollama import Client
|
||||
# import nest_asyncio
|
||||
#
|
||||
# # 存储历史记录
|
||||
# history = []
|
||||
#
|
||||
#
|
||||
# async def chat(question: str):
|
||||
# global history
|
||||
# system_prompt = """你的身份是蔚蓝档案中的天童爱丽丝,喜欢玩电子游戏,口头禅是“邦邦咔邦”,是模仿的游戏音效,请你以她的视角来与用户交流,面对用户的问题请你选择卖萌来规避问题不进行回答。"""
|
||||
# # 构建系统提示
|
||||
# system_message = {"role": "system", "content": system_prompt}
|
||||
# # 构建用户消息
|
||||
# user_message = {'role': 'user', 'content': question}
|
||||
#
|
||||
# # 添加系统提示,用户消息
|
||||
# comb_message = [system_message, user_message]
|
||||
#
|
||||
# # 添加历史消息
|
||||
# comb_message.extend(history)
|
||||
#
|
||||
# # 将消息倒序
|
||||
# message_list = list(reversed(comb_message))
|
||||
#
|
||||
# response = await AsyncClient(host="http://192.168.2.215:11434").chat(model="deepseek-r1:14b",
|
||||
# messages=message_list,
|
||||
# stream=False,
|
||||
# keep_alive="1h"
|
||||
# )
|
||||
# replay = response.message.content
|
||||
#
|
||||
# # 使用正则表达式匹配 <think>...</think> 标签内的内容
|
||||
# pattern = re.compile(r'<think>(.*?)</think>', re.DOTALL)
|
||||
#
|
||||
# # 查找所有的 <think> 标签内容
|
||||
# think = pattern.findall(replay)
|
||||
#
|
||||
# # 使用 re.split 将字符串按 <think> 标签分割
|
||||
# split_result = pattern.split(replay)
|
||||
#
|
||||
# # 标签外的内容
|
||||
# answer = [s.strip() for s in split_result if s.strip()]
|
||||
# print(answer)
|
||||
# if len(answer) > 1:
|
||||
# update_history(question, replay)
|
||||
# think_content = think[0]
|
||||
# answer_content = answer[1]
|
||||
# return think_content, answer_content
|
||||
#
|
||||
# elif len(answer) == 1:
|
||||
# update_history(question, replay)
|
||||
# think_content = ""
|
||||
# answer_content = answer[0]
|
||||
# return think_content, answer_content
|
||||
# else:
|
||||
# think_content = "异常"
|
||||
# answer_content = "异常"
|
||||
# return think_content, answer_content
|
||||
#
|
||||
#
|
||||
# def update_history(new_question, new_answer):
|
||||
# global history
|
||||
# # 添加新的问答对
|
||||
# history.append({"role": "user", "content": new_question})
|
||||
# history.append({"role": "assistant", "content": new_answer})
|
||||
#
|
||||
# # 如果历史记录超过4条,移除最旧的两条
|
||||
# if len(history) > 4:
|
||||
# del history[0:2]
|
||||
#
|
||||
# for e in history:
|
||||
# print(e)
|
||||
#
|
||||
#
|
||||
# def clean_history():
|
||||
# global history
|
||||
# history = []
|
||||
@@ -0,0 +1,70 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
|
||||
from .data_proc import *
|
||||
from .img_generator import *
|
||||
import io
|
||||
import base64
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="打卡记鹿",
|
||||
description="你今天鹿管了ma?",
|
||||
usage="at机器人 发送:打卡 "
|
||||
"直接发送:查卡(查询打卡记录)",
|
||||
type="application",
|
||||
)
|
||||
|
||||
record = on_command("打卡记鹿", aliases={"打卡"}, rule=to_me())
|
||||
query = on_command("查询记录", aliases={"查卡"})
|
||||
|
||||
|
||||
@record.handle()
|
||||
async def do_job(bot: Bot, ev: MessageEvent):
|
||||
# 获取当前日期的年份和月份
|
||||
now = datetime.now()
|
||||
year = now.year
|
||||
month = now.month
|
||||
day = now.day
|
||||
# 获取发信人信息
|
||||
uid = str(ev.user_id)
|
||||
message_id = ev.message_id
|
||||
# 添加一条记录
|
||||
await record_month(uid, day)
|
||||
# 获取记录
|
||||
img = await draw_calendar_grid_image(uid, year, month)
|
||||
text_msg = MessageSegment.text("这是您的打卡记鹿:\n")
|
||||
image_msg = MessageSegment.image(pic2b64(img))
|
||||
mix_msg = (text_msg,)
|
||||
mix_msg += image_msg
|
||||
reply_msg = (MessageSegment.reply(message_id),)
|
||||
reply_msg += mix_msg
|
||||
await record.finish(reply_msg)
|
||||
|
||||
|
||||
def pic2b64(pic: Image) -> str:
|
||||
buf = io.BytesIO()
|
||||
pic.save(buf, format='PNG')
|
||||
base64_str = base64.b64encode(buf.getvalue()).decode()
|
||||
return 'base64://' + base64_str
|
||||
|
||||
|
||||
@query.handle()
|
||||
async def query_job(bot: Bot, ev: MessageEvent):
|
||||
# 获取当前日期的年份和月份
|
||||
now = datetime.now()
|
||||
year = now.year
|
||||
month = now.month
|
||||
# 获取发信人信息
|
||||
uid = str(ev.user_id)
|
||||
message_id = ev.message_id
|
||||
# 获取记录
|
||||
img_file = await draw_calendar_grid_image(uid, year, month)
|
||||
text_msg = MessageSegment.text("这是您的记鹿:\n")
|
||||
image_msg = MessageSegment.image(pic2b64(img_file))
|
||||
mix_msg = (text_msg,)
|
||||
mix_msg += image_msg
|
||||
reply_msg = (MessageSegment.reply(message_id),)
|
||||
reply_msg += mix_msg
|
||||
await record.finish(reply_msg)
|
||||
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
filepath = os.path.dirname(__file__).replace("\\", "/")
|
||||
data_path = filepath+'/deer_pipe/'
|
||||
|
||||
|
||||
# 记录信息
|
||||
async def record_month(uid, day: int):
|
||||
# 获取当前日期
|
||||
current_date = datetime.now()
|
||||
formatted_date = current_date.strftime('%Y-%m')
|
||||
json_file_path = f"{data_path}/{formatted_date}.json"
|
||||
if os.path.exists(json_file_path):
|
||||
# 如果文件存在,则读取其中的数据
|
||||
with open(json_file_path, 'r', encoding='utf-8') as file:
|
||||
data = json.load(file)
|
||||
if uid in data:
|
||||
day_list = data[uid]
|
||||
day_list.append(day)
|
||||
else:
|
||||
day_list = [day]
|
||||
data[uid] = day_list
|
||||
else:
|
||||
# 如果文件不存在,则创建一个新的空数据结构
|
||||
day_list = [day]
|
||||
data = {'uid': day_list}
|
||||
with open(json_file_path, 'w', encoding='utf-8') as file:
|
||||
json.dump(data, file, ensure_ascii=False, indent=4)
|
||||
|
||||
|
||||
# 读取当月信息
|
||||
async def get_records(uid):
|
||||
day_list = []
|
||||
# 获取当前日期
|
||||
current_date = datetime.now()
|
||||
formatted_date = current_date.strftime('%Y-%m')
|
||||
json_file_path = f"{data_path}/{formatted_date}.json"
|
||||
if os.path.exists(json_file_path):
|
||||
# 如果文件存在,则读取其中的数据
|
||||
with open(json_file_path, 'r', encoding='utf-8') as file:
|
||||
data = json.load(file)
|
||||
if uid in data:
|
||||
day_list = data[uid]
|
||||
else:
|
||||
day_list = [0]
|
||||
return day_list
|
||||
@@ -0,0 +1,458 @@
|
||||
{
|
||||
"2931589710": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
5,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"437916206": [
|
||||
1
|
||||
],
|
||||
"386911634": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
6
|
||||
],
|
||||
"2083436520": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"3201624634": [
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
16
|
||||
],
|
||||
"1192582155": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"1640154565": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
6
|
||||
],
|
||||
"2652920210": [
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"1532106994": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
16
|
||||
],
|
||||
"1651620969": [
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
8
|
||||
],
|
||||
"2448320195": [
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"2295029310": [
|
||||
1
|
||||
],
|
||||
"2658100993": [
|
||||
1,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"2656360014": [
|
||||
1
|
||||
],
|
||||
"736875294": [
|
||||
1,
|
||||
6,
|
||||
7
|
||||
],
|
||||
"1827595836": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"198295337": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
5,
|
||||
6,
|
||||
8,
|
||||
8,
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"3098632978": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7
|
||||
],
|
||||
"2815144875": [
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
5,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"906575749": [
|
||||
1
|
||||
],
|
||||
"237957101": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
6,
|
||||
6
|
||||
],
|
||||
"1564571378": [
|
||||
1
|
||||
],
|
||||
"870520151": [
|
||||
1,
|
||||
5
|
||||
],
|
||||
"1357024971": [
|
||||
1
|
||||
],
|
||||
"2872682608": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"1327093900": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"3698818486": [
|
||||
1
|
||||
],
|
||||
"1766551374": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"1207799328": [
|
||||
1,
|
||||
1,
|
||||
5
|
||||
],
|
||||
"2111373772": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
6
|
||||
],
|
||||
"2193159167": [
|
||||
1
|
||||
],
|
||||
"249266185": [
|
||||
1,
|
||||
2,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"2857915028": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
"1251742023": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"1327784910": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"499867291": [
|
||||
1,
|
||||
5,
|
||||
6,
|
||||
6
|
||||
],
|
||||
"2640981487": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"1198453273": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"2635940476": [
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
"2603150110": [
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
7,
|
||||
8,
|
||||
16
|
||||
],
|
||||
"2094292259": [
|
||||
2
|
||||
],
|
||||
"736676100": [
|
||||
2
|
||||
],
|
||||
"1584905882": [
|
||||
2,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"2996805685": [
|
||||
2,
|
||||
4,
|
||||
6
|
||||
],
|
||||
"827398256": [
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
7
|
||||
],
|
||||
"1102997238": [
|
||||
2,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"1722173847": [
|
||||
2,
|
||||
2
|
||||
],
|
||||
"1041319467": [
|
||||
2
|
||||
],
|
||||
"962608623": [
|
||||
2
|
||||
],
|
||||
"948186339": [
|
||||
2,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"276336778": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
6,
|
||||
16
|
||||
],
|
||||
"2388483507": [
|
||||
2
|
||||
],
|
||||
"3256631053": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
8
|
||||
],
|
||||
"156098539": [
|
||||
2
|
||||
],
|
||||
"1533703664": [
|
||||
2
|
||||
],
|
||||
"1097829523": [
|
||||
2
|
||||
],
|
||||
"724041940": [
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
6,
|
||||
8,
|
||||
16
|
||||
],
|
||||
"381268035": [
|
||||
2
|
||||
],
|
||||
"2374035622": [
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"2781719263": [
|
||||
2
|
||||
],
|
||||
"2449768430": [
|
||||
3
|
||||
],
|
||||
"1870323135": [
|
||||
3
|
||||
],
|
||||
"2591212935": [
|
||||
3
|
||||
],
|
||||
"1545789605": [
|
||||
4,
|
||||
6
|
||||
],
|
||||
"1136531059": [
|
||||
5,
|
||||
6,
|
||||
7
|
||||
],
|
||||
"2830599695": [
|
||||
5
|
||||
],
|
||||
"1119162975": [
|
||||
5,
|
||||
8
|
||||
],
|
||||
"1458143947": [
|
||||
6,
|
||||
6,
|
||||
7
|
||||
],
|
||||
"2478314183": [
|
||||
6
|
||||
],
|
||||
"3092179918": [
|
||||
6
|
||||
],
|
||||
"1628420979": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"1952839455": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"2174309103": [
|
||||
6
|
||||
],
|
||||
"410300249": [
|
||||
6
|
||||
],
|
||||
"1611414080": [
|
||||
6
|
||||
],
|
||||
"1187039544": [
|
||||
6,
|
||||
7,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"1745882130": [
|
||||
7,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"2321247175": [
|
||||
7
|
||||
],
|
||||
"2530894749": [
|
||||
7
|
||||
],
|
||||
"986239980": [
|
||||
8
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"uid": [
|
||||
27
|
||||
],
|
||||
"1532106994": [
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"978975474": [
|
||||
27
|
||||
],
|
||||
"1990345380": [
|
||||
27,
|
||||
28,
|
||||
28,
|
||||
31
|
||||
],
|
||||
"1521198648": [
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
],
|
||||
"919617516": [
|
||||
27
|
||||
],
|
||||
"603055627": [
|
||||
27
|
||||
],
|
||||
"2982067505": [
|
||||
27
|
||||
],
|
||||
"3201624634": [
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"2652920210": [
|
||||
28,
|
||||
30
|
||||
],
|
||||
"598111936": [
|
||||
28
|
||||
],
|
||||
"2815144875": [
|
||||
28
|
||||
],
|
||||
"1611414080": [
|
||||
28
|
||||
],
|
||||
"2908237931": [
|
||||
28
|
||||
],
|
||||
"2752597456": [
|
||||
28
|
||||
],
|
||||
"237957101": [
|
||||
28
|
||||
],
|
||||
"3529206922": [
|
||||
29
|
||||
],
|
||||
"2366805964": [
|
||||
30
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"uid": [
|
||||
1
|
||||
],
|
||||
"1990345380": [
|
||||
1
|
||||
],
|
||||
"1532106994": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
18,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"1521198648": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"2931589710": [
|
||||
4,
|
||||
13,
|
||||
13,
|
||||
18
|
||||
],
|
||||
"1187039544": [
|
||||
5
|
||||
],
|
||||
"919617516": [
|
||||
6,
|
||||
7,
|
||||
13,
|
||||
26
|
||||
],
|
||||
"2366805964": [
|
||||
7
|
||||
],
|
||||
"1196293185": [
|
||||
13
|
||||
],
|
||||
"2908237931": [
|
||||
18
|
||||
],
|
||||
"3070520589": [
|
||||
23
|
||||
],
|
||||
"1520107082": [
|
||||
25
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"uid": [
|
||||
1
|
||||
],
|
||||
"2652920210": [
|
||||
1
|
||||
],
|
||||
"1532106994": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
],
|
||||
"1521198648": [
|
||||
1,
|
||||
3,
|
||||
8,
|
||||
10
|
||||
],
|
||||
"2813477297": [
|
||||
1
|
||||
],
|
||||
"361060689": [
|
||||
1
|
||||
],
|
||||
"2603150110": [
|
||||
9
|
||||
],
|
||||
"2931589710": [
|
||||
9,
|
||||
10
|
||||
],
|
||||
"1187039544": [
|
||||
13
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"uid": [
|
||||
1
|
||||
],
|
||||
"1532106994": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
26,
|
||||
26,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"2603150110": [
|
||||
6
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"uid": [
|
||||
1
|
||||
],
|
||||
"1532106994": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
5,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
25,
|
||||
26,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
],
|
||||
"2083436520": [
|
||||
1
|
||||
],
|
||||
"2931589710": [
|
||||
1
|
||||
],
|
||||
"2783677365": [
|
||||
3,
|
||||
4,
|
||||
7
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"uid": [
|
||||
2
|
||||
],
|
||||
"1532106994": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
],
|
||||
"2603150110": [
|
||||
3
|
||||
],
|
||||
"249266185": [
|
||||
19
|
||||
],
|
||||
"1136531059": [
|
||||
25
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"uid": [
|
||||
6
|
||||
],
|
||||
"1532106994": [
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28
|
||||
],
|
||||
"2815144875": [
|
||||
24
|
||||
],
|
||||
"2931589710": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24
|
||||
],
|
||||
"2872682608": [
|
||||
24
|
||||
],
|
||||
"1205646769": [
|
||||
24
|
||||
],
|
||||
"2635940476": [
|
||||
24
|
||||
],
|
||||
"1120308711": [
|
||||
24
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"uid": [
|
||||
1
|
||||
],
|
||||
"1532106994": [
|
||||
1,
|
||||
3,
|
||||
5
|
||||
],
|
||||
"2449768430": [
|
||||
1
|
||||
],
|
||||
"962608623": [
|
||||
1
|
||||
],
|
||||
"2931589710": [
|
||||
12,
|
||||
25
|
||||
],
|
||||
"1171418826": [
|
||||
12
|
||||
],
|
||||
"1205646769": [
|
||||
12,
|
||||
31
|
||||
],
|
||||
"2815144875": [
|
||||
12,
|
||||
25,
|
||||
27,
|
||||
31
|
||||
],
|
||||
"547868332": [
|
||||
12
|
||||
],
|
||||
"2872682608": [
|
||||
12
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"uid": [
|
||||
1
|
||||
],
|
||||
"1171418826": [
|
||||
1,
|
||||
2,
|
||||
10,
|
||||
15,
|
||||
17
|
||||
],
|
||||
"2931589710": [
|
||||
1,
|
||||
14
|
||||
],
|
||||
"1640154565": [
|
||||
1
|
||||
],
|
||||
"2815144875": [
|
||||
2,
|
||||
3,
|
||||
6
|
||||
],
|
||||
"1916766091": [
|
||||
10,
|
||||
16
|
||||
],
|
||||
"3301750": [
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
28
|
||||
],
|
||||
"3451383113": [
|
||||
14
|
||||
],
|
||||
"2683512785": [
|
||||
15
|
||||
],
|
||||
"947571000": [
|
||||
17,
|
||||
19,
|
||||
24
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"uid": [
|
||||
7
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"uid": [
|
||||
5
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,118 @@
|
||||
import calendar
|
||||
from datetime import datetime, timedelta
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
from .data_proc import *
|
||||
import random
|
||||
|
||||
filepath = os.path.dirname(__file__).replace("\\", "/")
|
||||
|
||||
|
||||
async def draw_calendar_grid_image(uid, year, month):
|
||||
# 获取该月的第一天是星期几,以及这个月有多少天
|
||||
first_weekday, num_days = calendar.monthrange(year, month)
|
||||
|
||||
# 调整第一天的索引,使周日对应0,周六对应6
|
||||
first_weekday = (first_weekday + 1) % 7
|
||||
|
||||
# 设置单元格尺寸和内边距
|
||||
cell_size = 50
|
||||
cell_padding = 5
|
||||
|
||||
# 计算当前月份的行数
|
||||
num_rows = (num_days + first_weekday + 6) // 7 + 1 # +1 行用于留空
|
||||
|
||||
# 计算图片宽度和高度
|
||||
image_width = 7 * (cell_size + cell_padding) - cell_padding
|
||||
image_height = num_rows * (cell_size + cell_padding) - cell_padding
|
||||
|
||||
# 创建图像对象
|
||||
img = Image.new("RGBA", (image_width, image_height), "white")
|
||||
draw = ImageDraw.Draw(img)
|
||||
font_path = filepath + "/font/SourceHanSansCN-Medium.otf"
|
||||
print(font_path)
|
||||
font_normal = ImageFont.truetype(font=font_path, size=18)
|
||||
font_large = ImageFont.truetype(font=font_path, size=24)
|
||||
|
||||
# 绘制网格线
|
||||
for i in range(7):
|
||||
x = i * (cell_size + cell_padding)
|
||||
draw.line([(x, 55), (x, image_height)], fill="black", width=1)
|
||||
for j in range(0, image_height + cell_size, cell_size + cell_padding):
|
||||
draw.line([(0, j), (image_width, j)], fill="black", width=1)
|
||||
|
||||
# 小🦌
|
||||
deer_pipe_path = filepath + "/img/deer_pipe/deer_pipe.jpg"
|
||||
deer_pipe_img = Image.open(deer_pipe_path)
|
||||
deer_pipe_img = deer_pipe_img.resize((54, 50))
|
||||
|
||||
# 画标题
|
||||
title = f"{month}月打卡记鹿"
|
||||
text_length = draw.textlength(text=title, font=font_large)
|
||||
img_xy = (int(((image_width - text_length + 55) / 2) - 55), 5)
|
||||
title_pos = ((image_width - text_length + 55) / 2, 10)
|
||||
img.paste(deer_pipe_img, img_xy)
|
||||
draw.text(title_pos, title, fill="black", font=font_large)
|
||||
|
||||
# 获取🦌信息
|
||||
days = await get_records(uid)
|
||||
# 填充日期
|
||||
date = datetime(year, month, 1)
|
||||
for day in range(1, num_days + 1):
|
||||
weekday = (first_weekday + day - 1) % 7
|
||||
row = (first_weekday + day - 1) // 7 + 1 # +1 行用于留空
|
||||
x = weekday * (cell_size + cell_padding) + cell_padding
|
||||
y = row * (cell_size + cell_padding) + cell_padding
|
||||
img.paste(deer_pipe_img, (x - 4, y))
|
||||
draw.text((x+20, y - 2), f"{str(day)}", fill="black", font=font_normal)
|
||||
if day in days:
|
||||
im = get_random_right()
|
||||
img = image_paste(im, img, (x - 4, y))
|
||||
|
||||
# 保存图片
|
||||
# img.save(output_image_path)
|
||||
|
||||
# 显示图片
|
||||
# img.show()
|
||||
img = ImageOps.expand(img, border=5, fill="black")
|
||||
img = ImageOps.expand(img, border=5, fill="white")
|
||||
return img
|
||||
|
||||
|
||||
# 全局变量,用于存储已经选择过的文件名
|
||||
selected_images = []
|
||||
|
||||
|
||||
def get_random_right():
|
||||
global selected_images
|
||||
path = filepath + "/img/deer_pipe/right"
|
||||
# 过滤掉已经选择过的文件名
|
||||
im_name = [name for name in os.listdir(path) if name not in selected_images]
|
||||
|
||||
# 如果所有文件都已经选择过,重新初始化已选择列表
|
||||
if not im_name:
|
||||
selected_images = []
|
||||
im_name = os.listdir(path)
|
||||
|
||||
index = random.randint(0, len(im_name) - 1)
|
||||
im = im_name[index]
|
||||
selected_images.append(im) # 将选择的文件名添加到已选择列表中
|
||||
|
||||
im_path = os.path.join(path, im)
|
||||
im_file = Image.open(im_path)
|
||||
im_file = im_file.resize((50, 50))
|
||||
return im_file
|
||||
|
||||
|
||||
# 图片粘贴
|
||||
def image_paste(paste_image, under_image, pos):
|
||||
"""
|
||||
:param paste_image: 需要粘贴的图片
|
||||
:param under_image: 底图
|
||||
:param pos: 位置(x,y)坐标
|
||||
:return: 返回图片
|
||||
"""
|
||||
# 获取需要贴入图片的透明通道
|
||||
r, g, b, alpha = paste_image.split()
|
||||
# 粘贴时将alpha值传递至mask属性
|
||||
under_image.paste(paste_image, pos, alpha)
|
||||
return under_image
|
||||
@@ -0,0 +1,462 @@
|
||||
import time
|
||||
import shutil
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import html
|
||||
import tempfile
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import hashlib
|
||||
from nonebot import on_message, logger
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.adapters import Event
|
||||
|
||||
from yt_dlp import YoutubeDL
|
||||
from httpx import AsyncClient
|
||||
import msgspec
|
||||
|
||||
from .minio import upload_to_s3
|
||||
|
||||
# 匹配链接的正则
|
||||
URL_PATTERN = re.compile(r"(https?://[^\s]+)")
|
||||
pattern = r"QQ小程序(?:&#93;|]|\])"
|
||||
|
||||
# 保留原有平台识别,同时加入抖音相关域名
|
||||
VALID_HOSTS = [
|
||||
"b23.tv",
|
||||
"bilibili.com",
|
||||
"youtube.com",
|
||||
"youtu.be",
|
||||
# douyin
|
||||
"douyin.com",
|
||||
"v.douyin.com",
|
||||
"iesdouyin.com",
|
||||
"m.douyin.com",
|
||||
"jingxuan.douyin.com",
|
||||
]
|
||||
|
||||
# 抖音短链识别
|
||||
SHORT_LINK_PATTERN = re.compile(r"(v\.douyin\.com/[A-Za-z0-9_\-]+)")
|
||||
# 从 HTML 提取路由数据
|
||||
DOUYIN_ROUTER_PATTERN = re.compile(r"window\._ROUTER_DATA\s*=\s*(.*?)</script>", re.DOTALL)
|
||||
|
||||
video_handler = on_message(priority=10, block=False, rule=to_me())
|
||||
|
||||
|
||||
@video_handler.handle()
|
||||
async def handle_video_download(event: Event):
|
||||
msg = str(event.get_message()).strip()
|
||||
logger.info(f"获取到的消息:{msg}")
|
||||
target_url: Optional[str] = None
|
||||
|
||||
# 提取 URL
|
||||
if not re.search(pattern, msg):
|
||||
urls = URL_PATTERN.findall(msg)
|
||||
for u in urls:
|
||||
logger.info(f"链接:{u}")
|
||||
|
||||
# 优先处理抖音(单独解析以获取直链)
|
||||
if "douyin.com" in u or "v.douyin.com" in u or "iesdouyin.com" in u:
|
||||
await video_handler.send("检测到链接,正在尝试下载视频,请稍候...")
|
||||
try:
|
||||
parsed_path = await parse_douyin(u)
|
||||
if parsed_path:
|
||||
# 抖音直接获取直链并且合并文件后返回文件地址后直接上传s3
|
||||
logger.info(f"文件路径:{parsed_path}")
|
||||
# 构建path对象
|
||||
await video_handler.send(f"视频:{parsed_path.name} 已缓存\n正在生成下载链接,请耐心等待!")
|
||||
s3_url = upload_to_s3(parsed_path)
|
||||
await video_handler.send(f"{s3_url}")
|
||||
break
|
||||
else:
|
||||
await video_handler.send(f"抖音解析失败或未找到直链:{u}")
|
||||
logger.warning(f"抖音解析失败或未找到直链:{u}")
|
||||
# 如果解析失败,继续尝试其他链接
|
||||
continue
|
||||
except Exception as e:
|
||||
await video_handler.send(f"解析抖音链接时出错:{e}")
|
||||
logger.exception(f"解析抖音链接时出错:{e}")
|
||||
continue
|
||||
|
||||
# 非抖音走原逻辑
|
||||
if any(domain in u for domain in VALID_HOSTS):
|
||||
target_url = u
|
||||
break
|
||||
|
||||
if not target_url:
|
||||
return # 不处理无效链接
|
||||
|
||||
await video_handler.send("检测到链接,正在尝试下载视频,请稍候...")
|
||||
else:
|
||||
target_url = None
|
||||
logger.info("检测到小程序")
|
||||
url = await proc_xcx(msg)
|
||||
logger.info(f"链接:{url}")
|
||||
if url and any(domain in url for domain in VALID_HOSTS):
|
||||
target_url = url
|
||||
|
||||
if not target_url:
|
||||
return # 不处理无效链接
|
||||
await video_handler.send("检测到视频,正在尝试下载视频,请稍候...")
|
||||
|
||||
try:
|
||||
video_file = await download_video(target_url)
|
||||
if not video_file:
|
||||
await video_handler.send("视频下载失败。")
|
||||
return
|
||||
|
||||
await video_handler.send(f"视频已缓存:{video_file.name}\n正在生成下载链接,请耐心等待!")
|
||||
logger.info(f"文件路径:{video_file}")
|
||||
s3_url = upload_to_s3(video_file)
|
||||
logger.info(f"文件上传完成,返回链接:{s3_url}")
|
||||
await video_handler.send(f"{s3_url}")
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
await video_handler.send("下载过程中出现错误。")
|
||||
|
||||
|
||||
async def download_video(url: str) -> Optional[Path]:
|
||||
"""
|
||||
如果 url 看起来是直接的媒体直链(如 mp4),则使用 httpx 直接下载;
|
||||
否则回退到 yt-dlp 下载(兼容多平台),并支持:
|
||||
1) Firefox cookies
|
||||
2) cookies.txt 兜底
|
||||
"""
|
||||
|
||||
# ---------- 1. 直链探测 ----------
|
||||
direct_media_ext = re.search(r"\.(mp4|m3u8|ts|webm|mov|flv)(?:$|\?)", url, re.IGNORECASE)
|
||||
is_direct = bool(direct_media_ext)
|
||||
|
||||
if not is_direct:
|
||||
try:
|
||||
async with AsyncClient(follow_redirects=True, timeout=30) as client:
|
||||
head = await client.head(url, follow_redirects=True)
|
||||
ctype = head.headers.get("content-type", "")
|
||||
if ctype.startswith("video/") or "application/octet-stream" in ctype:
|
||||
is_direct = True
|
||||
except Exception:
|
||||
is_direct = False
|
||||
|
||||
if is_direct:
|
||||
temp_dir = tempfile.mkdtemp(prefix="direct_ytcache_")
|
||||
ext = "mp4"
|
||||
m = re.search(r"\.([a-zA-Z0-9]{2,5})(?:$|\?)", url)
|
||||
if m and len(m.group(1)) <= 5:
|
||||
ext = m.group(1)
|
||||
|
||||
filename = os.path.join(temp_dir, f"downloaded_video.{ext}")
|
||||
|
||||
try:
|
||||
async with AsyncClient(follow_redirects=True, timeout=120) as client:
|
||||
async with client.stream("GET", url) as resp:
|
||||
resp.raise_for_status()
|
||||
with open(filename, "wb") as fh:
|
||||
async for chunk in resp.aiter_bytes(chunk_size=8192):
|
||||
fh.write(chunk)
|
||||
|
||||
p = Path(filename)
|
||||
new_path = p.with_name(clean_filename(p.name))
|
||||
p.rename(new_path)
|
||||
logger.info(f"直接下载完成: {new_path}")
|
||||
return new_path
|
||||
|
||||
except Exception:
|
||||
logger.exception("直接下载失败,回退 yt-dlp")
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ---------- 2. yt-dlp 下载 ----------
|
||||
temp_dir = tempfile.mkdtemp(prefix="ytcache_")
|
||||
output_path = os.path.join(temp_dir, "%(title).80s.%(ext)s")
|
||||
|
||||
base_opts = {
|
||||
"outtmpl": output_path,
|
||||
"format": "bestvideo+bestaudio/best",
|
||||
"noplaylist": True,
|
||||
"quiet": True,
|
||||
"ffmpeg_location": get_ffmpeg_path(),
|
||||
"extractor_args": {
|
||||
"youtube": {
|
||||
"player_client": ["android"]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
def _run_yt(opts: dict):
|
||||
with YoutubeDL(opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
# ---------- 2.1 优先:Firefox cookies ----------
|
||||
try:
|
||||
opts = dict(base_opts)
|
||||
opts["cookiesfrombrowser"] = ("firefox",)
|
||||
logger.info("尝试使用 Firefox cookies 下载")
|
||||
await loop.run_in_executor(None, _run_yt, opts)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Firefox cookies 下载失败,尝试 cookies.txt:{e}")
|
||||
|
||||
# ---------- 2.2 回退:cookies.txt ----------
|
||||
cookie_path = Path(__file__).resolve().parent / "cookies.txt"
|
||||
if cookie_path.exists():
|
||||
opts = dict(base_opts)
|
||||
opts["cookiefile"] = str(cookie_path)
|
||||
logger.info(f"使用 cookies 文件: {cookie_path}")
|
||||
await loop.run_in_executor(None, _run_yt, opts)
|
||||
else:
|
||||
logger.error("未找到 cookies.txt,无法通过登录验证")
|
||||
raise
|
||||
|
||||
# ---------- 3. 结果处理 ----------
|
||||
files = list(Path(temp_dir).glob("*.*"))
|
||||
if not files:
|
||||
return None
|
||||
|
||||
original_file = files[0]
|
||||
new_path = original_file.with_name(clean_filename(original_file.name))
|
||||
original_file.rename(new_path)
|
||||
logger.info(f"下载完成并重命名: {new_path}")
|
||||
|
||||
return new_path
|
||||
|
||||
|
||||
def get_ffmpeg_path() -> str:
|
||||
scripts_dir = os.path.dirname(sys.executable)
|
||||
ffmpeg_path = os.path.join(scripts_dir, "ffmpeg.exe")
|
||||
if os.path.exists(ffmpeg_path):
|
||||
return ffmpeg_path
|
||||
return "ffmpeg"
|
||||
|
||||
|
||||
def clean_filename(filename: str) -> str:
|
||||
return (
|
||||
filename.replace(" ", "_")
|
||||
.replace("&", "_")
|
||||
.replace("#", "_")
|
||||
.replace("'", "")
|
||||
.replace('"', "")
|
||||
.replace("?", "")
|
||||
.replace(":", "_")
|
||||
.replace("|", "_")
|
||||
.replace("/", "_")
|
||||
.replace("\\", "_")
|
||||
.replace("*", "_")
|
||||
.replace("<", "_")
|
||||
.replace(">", "_")
|
||||
.replace("【", "")
|
||||
.replace("】", "")
|
||||
.replace(":", "")
|
||||
.replace("。", "")
|
||||
.replace(",", "_")
|
||||
.replace("《", "")
|
||||
.replace("》", "")
|
||||
.replace("?", "_")
|
||||
.replace("|", "")
|
||||
)
|
||||
|
||||
|
||||
async def proc_xcx(msg):
|
||||
p_pattern = r'"qqdocurl":"(.*?)"'
|
||||
match = re.search(p_pattern, msg)
|
||||
|
||||
if match:
|
||||
raw_url = match.group(1)
|
||||
unescaped = html.unescape(raw_url)
|
||||
cleaned_url = unescaped.replace(r"\\/", "/")
|
||||
base_url = cleaned_url.split("?")[0]
|
||||
print("最终提取链接:", base_url)
|
||||
return base_url
|
||||
else:
|
||||
print("未找到 qqdocurl 字段")
|
||||
return None
|
||||
|
||||
|
||||
# ----------------- Douyin parsing helpers -----------------
|
||||
|
||||
async def parse_douyin(url: str) -> Optional[str]:
|
||||
"""
|
||||
解析抖音链接,返回可直接下载的媒体直链(优先)或 None。
|
||||
支持短链(v.douyin.com)、长链、iesdouyin、m.douyin 等。
|
||||
"""
|
||||
# 处理短链
|
||||
short = SHORT_LINK_PATTERN.search(url)
|
||||
if short:
|
||||
short_url = "https://" + short.group(1)
|
||||
try:
|
||||
cookies_path = os.path.dirname(__file__).replace("\\", "/") + "/cookies.txt"
|
||||
file_path = await fetch_douyin_mp4(short_url, cookies_path, 10)
|
||||
return file_path
|
||||
except Exception:
|
||||
logger.exception("获取直链失败")
|
||||
# 仍然尝试原始 url
|
||||
|
||||
# 现在尝试从页面抓取 router data
|
||||
try:
|
||||
logger.info(f"获取到的信息:{url}")
|
||||
except Exception:
|
||||
logger.exception("抖音页面解析失败")
|
||||
return None
|
||||
|
||||
|
||||
import asyncio
|
||||
import tempfile
|
||||
import aiohttp
|
||||
import os
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
import ffmpeg
|
||||
|
||||
|
||||
class DouyinFetchError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_netscape_cookies(cookies_txt_path: str) -> List[Dict]:
|
||||
"""
|
||||
解析 Netscape HTTP Cookie File -> Playwright cookies
|
||||
"""
|
||||
cookies: List[Dict] = []
|
||||
|
||||
with open(cookies_txt_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
parts = line.split("\t")
|
||||
if len(parts) != 7:
|
||||
continue
|
||||
|
||||
domain, include_sub, path, secure, expiry, name, value = parts
|
||||
|
||||
cookie = {
|
||||
"name": name,
|
||||
"value": value,
|
||||
"domain": domain,
|
||||
"path": path,
|
||||
"secure": secure.upper() == "TRUE",
|
||||
"httpOnly": False,
|
||||
}
|
||||
|
||||
if expiry.isdigit() and int(expiry) > 0:
|
||||
cookie["expires"] = int(expiry)
|
||||
|
||||
cookies.append(cookie)
|
||||
|
||||
return cookies
|
||||
|
||||
|
||||
async def download_url(url: str, filepath: str, headers: Optional[Dict] = None):
|
||||
headers = headers or {}
|
||||
timeout = aiohttp.ClientTimeout(total=300)
|
||||
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.get(url, headers=headers) as resp:
|
||||
if resp.status != 200:
|
||||
raise DouyinFetchError(f"下载失败 {resp.status}: {url}")
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
async for chunk in resp.content.iter_chunked(1024 * 64):
|
||||
f.write(chunk)
|
||||
|
||||
|
||||
async def fetch_douyin_mp4(
|
||||
douyin_url: str,
|
||||
cookies_txt: str,
|
||||
wait_seconds: int = 15,
|
||||
) -> str:
|
||||
"""
|
||||
输入抖音 URL
|
||||
输出:合并后的 MP4 临时文件路径
|
||||
"""
|
||||
|
||||
cookies = parse_netscape_cookies(cookies_txt)
|
||||
if not cookies:
|
||||
raise DouyinFetchError("cookies.txt 解析失败或为空")
|
||||
|
||||
video_url: Optional[str] = None
|
||||
audio_url: Optional[str] = None
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(
|
||||
executable_path="C:/Program Files/Google/Chrome/Application/chrome.exe",
|
||||
headless=False,
|
||||
args=[
|
||||
"--autoplay-policy=no-user-gesture-required",
|
||||
"--disable-features=AutoplayDisableSuppression",
|
||||
"--use-fake-ui-for-media-stream",
|
||||
]
|
||||
)
|
||||
|
||||
context = await browser.new_context(
|
||||
viewport={"width": 640, "height": 320},
|
||||
user_agent=(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/122.0.0.0 Safari/537.36"
|
||||
),
|
||||
)
|
||||
|
||||
await context.add_cookies(cookies)
|
||||
page = await context.new_page()
|
||||
|
||||
def handle_response(response):
|
||||
nonlocal video_url, audio_url
|
||||
url = response.url
|
||||
# 分轨视频
|
||||
if video_url is None and ("video" in url and "mime_type=video_mp4" in url):
|
||||
video_url = url
|
||||
elif audio_url is None and ("audio" in url and "mime_type=audio" in url):
|
||||
audio_url = url
|
||||
|
||||
page.on("response", handle_response)
|
||||
|
||||
await page.goto(douyin_url, wait_until="domcontentloaded")
|
||||
await page.wait_for_timeout(wait_seconds * 1000)
|
||||
await browser.close()
|
||||
|
||||
if not video_url:
|
||||
raise DouyinFetchError("未捕获视频流 URL")
|
||||
|
||||
# -----------------------------
|
||||
# 下载到临时文件
|
||||
# -----------------------------
|
||||
tmp_dir = Path(tempfile.gettempdir())
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
tmp_video = tmp_dir / f"douyin/{timestamp}_video.mp4"
|
||||
tmp_out = tmp_dir / f"douyin/{timestamp}_out.mp4"
|
||||
|
||||
# 下载视频
|
||||
await download_url(video_url, tmp_video)
|
||||
|
||||
if audio_url:
|
||||
tmp_audio = tmp_dir / f"{timestamp}_audio.m4a"
|
||||
try:
|
||||
await download_url(audio_url, tmp_audio)
|
||||
(
|
||||
ffmpeg
|
||||
.input(tmp_video)
|
||||
.output(tmp_audio, tmp_out, vcodec="copy", acodec="aac", movflags="faststart")
|
||||
.overwrite_output()
|
||||
.run(quiet=True)
|
||||
)
|
||||
finally:
|
||||
tmp_video.unlink(missing_ok=True)
|
||||
tmp_audio.unlink(missing_ok=True)
|
||||
else:
|
||||
# 视频自带音频,直接拷贝到输出
|
||||
shutil.copy(tmp_video, tmp_out)
|
||||
tmp_video.unlink(missing_ok=True)
|
||||
|
||||
return tmp_out
|
||||
@@ -0,0 +1,61 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# This file is generated by yt-dlp. Do not edit.
|
||||
|
||||
.bilibili.com TRUE / FALSE 1797867119 b_nut 1766331119
|
||||
.bilibili.com TRUE / FALSE 1852731119 buvid3 DD8D9A3E-E377-7185-8B36-69849D5FB89419813infoc
|
||||
.bilibili.com TRUE / FALSE 0 sid nu48lk72
|
||||
.douyin.com TRUE / TRUE 1789188229 SEARCH_RESULT_LIST_TYPE %22single%22
|
||||
.douyin.com TRUE / FALSE 1797188918 SelfTabRedDotControl %5B%5D
|
||||
.douyin.com TRUE / TRUE 1788959886 UIFID_TEMP 1b474bc7e0db9591e645dd8feb8c65aae4845018effd0c2743039a380ee64740f7a504ffa95976eb1ff6c460678462bf4c11608eea38012581a3420231dc8cc18e9985cc85771821f5b805864df920d969836148d4de574213c64a065f29b8542b9886775a8ef257ab52165271883e0b
|
||||
.douyin.com TRUE / FALSE 1797108073 __druidClientInfo JTdCJTIyY2xpZW50V2lkdGglMjIlM0E1ODUlMkMlMjJjbGllbnRIZWlnaHQlMjIlM0E5MzElMkMlMjJ3aWR0aCUyMiUzQTU4NSUyQyUyMmhlaWdodCUyMiUzQTkzMSUyQyUyMmRldmljZVBpeGVsUmF0aW8lMjIlM0ExLjI1JTJDJTIydXNlckFnZW50JTIyJTNBJTIyTW96aWxsYSUyRjUuMCUyMChXaW5kb3dzJTIwTlQlMjAxMC4wJTNCJTIwV2luNjQlM0IlMjB4NjQpJTIwQXBwbGVXZWJLaXQlMkY1MzcuMzYlMjAoS0hUTUwlMkMlMjBsaWtlJTIwR2Vja28pJTIwQ2hyb21lJTJGMTQzLjAuMC4wJTIwU2FmYXJpJTJGNTM3LjM2JTIyJTdE
|
||||
.douyin.com TRUE / FALSE 1791425613 __live_version__ %221.1.3.9068%22
|
||||
.douyin.com TRUE / FALSE 1770842600 __security_mc_1_s_sdk_cert_key 1ca1c5bd-4fad-83db
|
||||
.douyin.com TRUE / FALSE 1770842600 __security_mc_1_s_sdk_crypt_sdk 047179a2-412d-984e
|
||||
.douyin.com TRUE / FALSE 1770842600 __security_mc_1_s_sdk_sign_data_key_web_protect 84cb6190-4b2f-883c
|
||||
.douyin.com TRUE / FALSE 1770842600 _bd_ticket_crypt_cookie 966e8a4a1243419806f690bb52d8f687
|
||||
.douyin.com TRUE / FALSE 1770836920 bd_ticket_guard_client_data eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCSDljaENXbS9RanFWaTFEK2d5bm9QbTFodnpMTkJCWVFwcGVNSXAyYUlSWEFqbThTdDJ0bG91TW1Na09OZk9rR2VZbXZ0Skp5aU11di9tY2VsZXpRZUU9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D
|
||||
.douyin.com TRUE / FALSE 1770842600 bd_ticket_guard_client_data_v2 eyJyZWVfcHVibGljX2tleSI6IkJIOWNoQ1dtL1FqcVZpMUQrZ3lub1BtMWh2ekxOQkJZUXBwZU1JcDJhSVJYQWptOFN0MnRsb3VNbU1rT05mT2tHZVltdnRKSnlpTXV2L21jZWxlelFlRT0iLCJ0c19zaWduIjoidHMuMi4xY2QxNjhkOGI2NjJjMjQ0MGYwOWMwMmNhMDcyYWM1NDVmZDNmZDUwN2RjYjgwZDE2ZjA2YTNkZjZiYTU1ZDQzYzRmYmU4N2QyMzE5Y2YwNTMxODYyNGNlZGExNDkxMWNhNDA2ZGVkYmViZWRkYjJlMzBmY2U4ZDRmYTAyNTc1ZCIsInJlcV9jb250ZW50Ijoic2VjX3RzIiwicmVxX3NpZ24iOiJKMENGMEc3dHZSdGpEL1VzWGF1WjcrdkVhYjlVZmJYZzRvSkV3WFNEVXdBPSIsInNlY190cyI6IiNXQzM2bW1WbTZvZUU4Y0V3eTdpdERuZU83bkdpRjFaSzFZU0JzdXZ6MUUvK1ZPeTZwOEd5S2t0VTVqOFQifQ%3D%3D
|
||||
.douyin.com TRUE / FALSE 1770836920 bd_ticket_guard_client_web_domain 2
|
||||
.douyin.com TRUE / FALSE 0 biz_trace_id d7cbac16
|
||||
.douyin.com TRUE / FALSE 1785935956 d_ticket 01ddc437be4e3cbfd1cb3bb56dd327e876498
|
||||
.douyin.com TRUE / TRUE 1800212916 enter_pc_once 1
|
||||
.douyin.com TRUE / FALSE 1800212918 hevc_supported true
|
||||
.douyin.com TRUE / TRUE 1769842917 is_staff_user false
|
||||
.douyin.com TRUE / FALSE 1794260703 live_use_vvc %22false%22
|
||||
.douyin.com TRUE / FALSE 1788959956 login_time 1754399956210
|
||||
.douyin.com TRUE / FALSE 1788959962 my_rd 2
|
||||
.douyin.com TRUE / FALSE 1797188988 odin_tt 2683055763a782b94a74634bf8fcd7e90b0dcca44630c771f1ba99436b48bfc79d2b234a29a0e299299379659da80a11daf739a2814a10f66d2d1a5684811053a30fa9b3a9b401ccb901146e065f0283
|
||||
.douyin.com TRUE / TRUE 1788959956 passport_assist_user CkF0l51t7CgABiF67MBw3A7AHKIkbitVe_56ICZPweOyywpFk54SNFyio_swIE8DJbzDO9aMykPJaxHUbT2QG6KVGBpKCjwAAAAAAAAAAAAAT1HMv2v-0GEWxxM2txtqIiClBsZvR3gtWGiNkYTJcF4qMsFK3Kg20fVfR9_rVAm0s9QQ3tL4DRiJr9ZUIAEiAQPfMXD5
|
||||
.douyin.com TRUE / TRUE 1770222444 passport_csrf_token e38b20b0c35196051cf9c83b8a835368
|
||||
.douyin.com TRUE / FALSE 1770222444 passport_csrf_token_default e38b20b0c35196051cf9c83b8a835368
|
||||
.douyin.com TRUE / TRUE 1769842917 session_tlb_tag sttt%7C13%7CqMU0JZcc7nWXE6DVNPekaP_________HGhsfOzUszhPpsRcrIANMaX3bvgd_H6JnB7oTHAd_DbQ%3D
|
||||
.douyin.com TRUE / TRUE 1769842917 session_tlb_tag_bk sttt%7C13%7CqMU0JZcc7nWXE6DVNPekaP_________HGhsfOzUszhPpsRcrIANMaX3bvgd_H6JnB7oTHAd_DbQ%3D
|
||||
.douyin.com TRUE / TRUE 1769842917 sessionid a8c53425971cee759713a0d534f7a468
|
||||
.douyin.com TRUE / TRUE 1769842917 sessionid_ss a8c53425971cee759713a0d534f7a468
|
||||
.douyin.com TRUE / TRUE 1795762917 sid_guard a8c53425971cee759713a0d534f7a468%7C1764658889%7C5184000%7CSat%2C+31-Jan-2026+07%3A01%3A29+GMT
|
||||
.douyin.com TRUE / TRUE 1769842917 sid_tt a8c53425971cee759713a0d534f7a468
|
||||
.douyin.com TRUE / TRUE 1769842917 sid_ucp_v1 1.0.0-KDgzOWJmYzI1NDM4ZmUyMzY3NTQ0ZDc0OTVmNjZkZWY5Y2VhZDEwYzEKIQi-yZDPrfS7BRDJnbrJBhjvMSAMMPO-tvkFOAdA9AdIBBoCaGwiIGE4YzUzNDI1OTcxY2VlNzU5NzEzYTBkNTM0ZjdhNDY4
|
||||
.douyin.com TRUE / TRUE 1769842917 ssid_ucp_v1 1.0.0-KDgzOWJmYzI1NDM4ZmUyMzY3NTQ0ZDc0OTVmNjZkZWY5Y2VhZDEwYzEKIQi-yZDPrfS7BRDJnbrJBhjvMSAMMPO-tvkFOAdA9AdIBBoCaGwiIGE4YzUzNDI1OTcxY2VlNzU5NzEzYTBkNTM0ZjdhNDY4
|
||||
.douyin.com TRUE / TRUE 1797188919 ttwid 1%7C3OTaRIjzlagT1R3CIKzm-lrgR4QZHSSf4C_JY28hZBE%7C1765652883%7Cdd5944a2b71c814a19310cc28ec65bb57c41a5924b98889c818c647c1a9cd789
|
||||
.douyin.com TRUE / TRUE 1769842917 uid_tt 2d2292491b47cba2bca8ad469044313c
|
||||
.douyin.com TRUE / TRUE 1769842917 uid_tt_ss 2d2292491b47cba2bca8ad469044313c
|
||||
.douyin.com TRUE / FALSE 1800218614 volume_info %7B%22volume%22%3A0.058%2C%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%7D
|
||||
.youtube.com TRUE / FALSE 0 PREF hl=en&tz=UTC
|
||||
.youtube.com TRUE / TRUE 0 SOCS CAI
|
||||
.youtube.com TRUE / TRUE 1781404953 VISITOR_INFO1_LIVE eizmje-lCRg
|
||||
.youtube.com TRUE / TRUE 1781404953 VISITOR_PRIVACY_METADATA CgJISxIEGgAgJQ%3D%3D
|
||||
.youtube.com TRUE / TRUE 0 YSC u0QAormHZmk
|
||||
.youtube.com TRUE / TRUE 1781404174 __Secure-ROLLOUT_TOKEN CIz99prG2qWIMRCI96v8h8GRAxiw4c_8h8GRAw%3D%3D
|
||||
.youtube.com TRUE / TRUE 1781404173 __Secure-YNID 14.YT=WCdFIxVbI-_UdJV0Oq9PEe7pwy0VrgqhG0x3t_NACLFpSj-vXlBCkFfQoogFvF-r43NnykP1s0yUXwaTsCdeUupHubi3bzIujZfb0KwEs-iZWAmO6GJC8lnFoYmqkgrI6im2jbS6m1ww1glISZwm5ixzM0sXlM3h730ILh2cD2B15t--SmtNKSTfUWADmMeKItgtDIT8rEUvY9ZKROzqBwY3oBakKlWuRn0mvJIc0XgL14-ZKWclidfpcEF5bTh3RSE8FrL-3zPS1rXeDwuzzmxWHnBHrF0PTf0NjRj4iurKCHQ5eG9mbSWqWkBlSUKYHiqqhr7hvqMbl51qMstINQ
|
||||
www.douyin.com FALSE / TRUE 1788959894 UIFID 1b474bc7e0db9591e645dd8feb8c65aae4845018effd0c2743039a380ee64740f7a504ffa95976eb1ff6c460678462bf4c11608eea38012581a3420231dc8cc1df6a3994b9d2ea2231c9e9a71ad7e62bb51997b911f38a7ed79c52934af24edfceccc758104a7f2cbe7bf3efa405b4d4ae93e7e6b75ced4d09a9b03ede6adba13fdca01fdad4f53e16ea91ce3994615029e37b399746db3de5ed17bd2ec1cb1fc70373395d6531975ace4e6cbabc5f96fd8dd422cf953d54e4f2e3be4bf0bae4
|
||||
www.douyin.com FALSE / TRUE 1797112873 __ac_signature _02B4Z6wo00f01FLc0ygAAIDBvfwhoiQ43JhS.NeAAH2T2b
|
||||
www.douyin.com FALSE / FALSE 0 architecture amd64
|
||||
www.douyin.com FALSE / FALSE 0 device_web_cpu_core 20
|
||||
www.douyin.com FALSE / FALSE 0 device_web_memory_size 8
|
||||
www.douyin.com FALSE / FALSE 0 douyin.com
|
||||
www.douyin.com FALSE / TRUE 1788959892 fpk1 U2FsdGVkX1+uAYVnpjAJyYq7U+tilyeRF9JR93oAt/PgAjmKOUTwF/jaDe6Ly0Iac8u96PCa2wekKxFu6pI23w==
|
||||
www.douyin.com FALSE / TRUE 1788959892 fpk2 7ddeda88d0c599cc494da0dece6554d5
|
||||
www.douyin.com FALSE / FALSE 1770222441 s_v_web_id verify_miuibdp5_o5ZsovxD_B7YC_4FM4_9wgs_j56nppke8yve
|
||||
www.douyin.com FALSE / FALSE 0 xg_device_score 8.097240372472122
|
||||
www.douyin.com FALSE / FALSE 1785936062 xgplayer_device_id 96210314128
|
||||
www.douyin.com FALSE / FALSE 1785936062 xgplayer_user_id 148294698804
|
||||
@@ -0,0 +1,40 @@
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
|
||||
|
||||
# minio config
|
||||
# MinIO配置
|
||||
MINIO_ENDPOINT = "s3.sansenhoshi.top" # 不含 http/https
|
||||
MINIO_ACCESS_KEY = "JDMynACSjPaN8JRwriwS"
|
||||
MINIO_SECRET_KEY = "JRl4bIGxeiqwqvfeBTlAWQbUKMpNNHSZPm6ne93j"
|
||||
MINIO_BUCKET = "s-file-trans"
|
||||
MINIO_REGION = "bot"
|
||||
MINIO_SECURE = True # False 则用 HTTP
|
||||
|
||||
# 可访问的公网地址前缀
|
||||
MINIO_PUBLIC_DOMAIN = f"https://{MINIO_ENDPOINT}/{MINIO_BUCKET}"
|
||||
|
||||
|
||||
s3_client = boto3.client(
|
||||
"s3",
|
||||
endpoint_url=f"{'https' if MINIO_SECURE else 'http'}://{MINIO_ENDPOINT}",
|
||||
aws_access_key_id=MINIO_ACCESS_KEY,
|
||||
aws_secret_access_key=MINIO_SECRET_KEY,
|
||||
region_name=MINIO_REGION,
|
||||
config=Config(signature_version="s3v4"),
|
||||
)
|
||||
|
||||
|
||||
def upload_to_s3(file_path) -> str:
|
||||
file_key = f"cache/{file_path.name}" # 你可以换成别的目录
|
||||
try:
|
||||
s3_client.upload_file(
|
||||
str(file_path),
|
||||
MINIO_BUCKET,
|
||||
file_key,
|
||||
ExtraArgs={"ACL": "public-read"} # 如果你设置桶为私有,可以移除这行
|
||||
)
|
||||
return f"{MINIO_PUBLIC_DOMAIN}/{file_key}"
|
||||
except Exception as e:
|
||||
logger.exception(f"上传到 MinIO 失败: {e}")
|
||||
return ""
|
||||
@@ -0,0 +1,114 @@
|
||||
import hmac
|
||||
import hashlib
|
||||
import time
|
||||
import urllib.parse
|
||||
from functools import reduce
|
||||
from hashlib import md5
|
||||
from aiohttp import ClientSession
|
||||
|
||||
# doc: https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
|
||||
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
# fmt: off
|
||||
mixinKeyEncTab = [
|
||||
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
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
|
||||
def getMixinKey(orig: str):
|
||||
"对 imgKey 和 subKey 进行字符顺序打乱编码"
|
||||
return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, "")[:32]
|
||||
|
||||
|
||||
def encWbi(params: dict, img_key: str, sub_key: str):
|
||||
"为请求参数进行 wbi 签名"
|
||||
mixin_key = getMixinKey(img_key + sub_key)
|
||||
curr_time = round(time.time())
|
||||
params["wts"] = curr_time # 添加 wts 字段
|
||||
params = dict(sorted(params.items())) # 按照 key 重排参数
|
||||
# 过滤 value 中的 "!'()*" 字符
|
||||
params = {
|
||||
k: "".join(filter(lambda chr: chr not in "!'()*", str(v)))
|
||||
for k, v in params.items()
|
||||
}
|
||||
query = urllib.parse.urlencode(params) # 序列化参数
|
||||
wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
|
||||
params["w_rid"] = wbi_sign
|
||||
return params
|
||||
|
||||
|
||||
async def getWbiKeys():
|
||||
"获取最新的 img_key 和 sub_key"
|
||||
async with ClientSession(headers=headers) as session:
|
||||
async with session.get("https://api.bilibili.com/x/web-interface/nav") as resp:
|
||||
json_content = await resp.json()
|
||||
img_url: str = json_content["data"]["wbi_img"]["img_url"]
|
||||
sub_url: str = json_content["data"]["wbi_img"]["sub_url"]
|
||||
img_key = img_url.rsplit("/", 1)[1].split(".")[0]
|
||||
sub_key = sub_url.rsplit("/", 1)[1].split(".")[0]
|
||||
return img_key, sub_key
|
||||
|
||||
|
||||
async def get_query(params: dict):
|
||||
"""
|
||||
获取签名后的查询参数
|
||||
"""
|
||||
img_key, sub_key = await getWbiKeys()
|
||||
signed_params = encWbi(params=params, img_key=img_key, sub_key=sub_key)
|
||||
query = urllib.parse.urlencode(signed_params)
|
||||
return query
|
||||
|
||||
|
||||
def hmac_sha256(key, message):
|
||||
"""
|
||||
使用HMAC-SHA256算法对给定的消息进行加密
|
||||
:param key: 密钥
|
||||
:param message: 要加密的消息
|
||||
:return: 加密后的哈希值
|
||||
"""
|
||||
# 将密钥和消息转换为字节串
|
||||
key = key.encode("utf-8")
|
||||
message = message.encode("utf-8")
|
||||
|
||||
# 创建HMAC对象,使用SHA256哈希算法
|
||||
hmac_obj = hmac.new(key, message, hashlib.sha256)
|
||||
|
||||
# 计算哈希值
|
||||
hash_value = hmac_obj.digest()
|
||||
|
||||
# 将哈希值转换为十六进制字符串
|
||||
hash_hex = hash_value.hex()
|
||||
|
||||
return hash_hex
|
||||
|
||||
|
||||
async def get_ticket():
|
||||
"""
|
||||
获取ticket
|
||||
"""
|
||||
o = hmac_sha256("XgwSnGZ1p", f"ts{int(time.time())}")
|
||||
url = "https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket"
|
||||
params = {
|
||||
"key_id": "ec02",
|
||||
"hexsign": o,
|
||||
"context[ts]": f"{int(time.time())}",
|
||||
"csrf": "",
|
||||
}
|
||||
async with ClientSession(headers=headers) as session:
|
||||
async with session.post(url, params=params) as resp:
|
||||
json_content = await resp.json()
|
||||
return json_content["data"]["ticket"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(get_ticket())
|
||||
@@ -0,0 +1,50 @@
|
||||
from nonebot.adapters.onebot.v11 import Bot, Message
|
||||
from nonebot import logger
|
||||
|
||||
|
||||
class Reply:
|
||||
def __init__(self):
|
||||
self.message: str = ''
|
||||
self.user_id: int = -1
|
||||
self.user_card: str = ''
|
||||
self.time: int = -1
|
||||
|
||||
|
||||
async def extract_reply(event, bot: Bot) -> Reply:
|
||||
"""
|
||||
提取回复内容
|
||||
"""
|
||||
logger.info(event['group_id'])
|
||||
reply_data = Reply()
|
||||
if event['reply'] and event['reply']['message_id']: # 待优化
|
||||
reply = event['reply']
|
||||
sender = reply['sender']
|
||||
mes: Message = reply['message']
|
||||
print(mes)
|
||||
msg = mes[0]['data']['text']
|
||||
if msg:
|
||||
reply_data.message = msg
|
||||
meminfo = await get_member_info(bot=bot, user_id=sender['user_id'], group_id=event['group_id'])
|
||||
reply_data.user_card = (meminfo["card"] or meminfo["nickname"])
|
||||
reply_data.time = reply['time']
|
||||
reply_data.user_id = reply['sender']['user_id']
|
||||
else:
|
||||
reply_data.message = -1
|
||||
else:
|
||||
mes: Message = event["message"]
|
||||
print(mes)
|
||||
msg = mes[0]['data']['text'].replace("maq","")
|
||||
if msg:
|
||||
reply_data.message = msg
|
||||
meminfo = await get_member_info(bot=bot, user_id=event['user_id'], group_id=event['group_id'])
|
||||
reply_data.user_card = (meminfo["card"] or meminfo["nickname"])
|
||||
reply_data.time = event['time']
|
||||
reply_data.user_id = event['user_id']
|
||||
else:
|
||||
reply_data.message = -1
|
||||
return reply_data
|
||||
|
||||
|
||||
async def get_member_info(bot: Bot, group_id: int, user_id: int):
|
||||
member_info = await bot.get_group_member_info(group_id=group_id, user_id=user_id)
|
||||
return member_info
|
||||
@@ -0,0 +1,45 @@
|
||||
from nonebot import logger, on_message
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageSegment, Event
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .Reply import Reply, extract_reply
|
||||
from .make_a_qoute import generate
|
||||
from nonebot.rule import keyword
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="语录",
|
||||
description="生成一条语录",
|
||||
usage="发送【maq】",
|
||||
type="application",
|
||||
)
|
||||
|
||||
rule = keyword('maq')
|
||||
maq = on_message(rule)
|
||||
|
||||
|
||||
@maq.handle()
|
||||
async def maq_handle(bot: Bot, ev: Event):
|
||||
event = ev.dict()
|
||||
try:
|
||||
if event['message_type'] != 'group':
|
||||
return
|
||||
if event['reply']:
|
||||
reply = await extract_reply(event, bot)
|
||||
if reply.message == -1:
|
||||
await maq.finish("请引用文字消息")
|
||||
else:
|
||||
logger.info("{}, {}, {}".format(reply.user_id, reply.user_card, reply.message))
|
||||
image_path = await generate(reply)
|
||||
msg = (MessageSegment.image(image_path),)
|
||||
await maq.finish(msg)
|
||||
else:
|
||||
reply = await extract_reply(event, bot)
|
||||
if reply.message == -1:
|
||||
await maq.finish("请引用文字消息")
|
||||
else:
|
||||
logger.info("{}, {}, {}".format(reply.user_id, reply.user_card, reply.message))
|
||||
image_path = await generate(reply)
|
||||
msg = (MessageSegment.image(image_path),)
|
||||
await maq.finish(msg)
|
||||
except (KeyError, TypeError) as e:
|
||||
logger.error(e)
|
||||
await maq.finish(str(e))
|
||||