Compare commits

..
10 Commits
Author SHA1 Message Date
HibiKier 059b5c3a33 Merge pull request #169 from lhhxxxxx/run
[New Feature] en-US support
2022-05-17 20:42:14 +08:00
HibiKier 024f1e015b Merge pull request #174 from MingxuanGame/run
繁體中文支持
2022-05-17 20:40:07 +08:00
lhhxxxxxandKotoki 929bbe5095 Update README_en-US.md
Co-authored-by: Kotoki <Kotoki1337@gmail.com>
2022-05-17 20:38:59 +08:00
lhhxxxxxandKotoki b06795cfaf Update README.md
Co-authored-by: Kotoki <Kotoki1337@gmail.com>
2022-05-17 20:38:51 +08:00
MingxuanGame 1b51344332 繁體中文支持 2022-05-17 20:36:35 +08:00
lhhxxxxx c0abb730b6 Update README_en-US.md 2022-05-17 20:06:37 +08:00
lhhxxxxx 71e674efc0 Create README_en-US.md 2022-05-17 20:06:22 +08:00
lhhxxxxx af0d8415ae Update README.md 2022-05-17 19:57:25 +08:00
HibiKier c30f6b56c6 Update README.md 2022-05-17 19:36:21 +08:00
HibiKier 9eda3e4e48 run 2022-05-17 19:27:17 +08:00
715 changed files with 13 additions and 61235 deletions
-1
View File
@@ -1 +0,0 @@
ENVIRONMENT=dev
-16
View File
@@ -1,16 +0,0 @@
SUPERUSERS=[""]
COMMAND_START=[""]
SESSION_RUNNING_EXPRESSION="别急呀,小真寻要宕机了!QAQ"
NICKNAME=["真寻", "小真寻", "绪山真寻", "小寻子"]
SESSION_EXPIRE_TIMEOUT=30
DEBUG=False
# 服务器和端口
HOST = 127.0.0.1
PORT = 8080
-23
View File
@@ -1,23 +0,0 @@
---
name: Issue template
about: " issue template's purpose here."
title: ''
labels: ''
assignees: ''
---
### 系统版本:Ubuntu 20.04
### 真寻版本:0.1.5.3
### 错误截图
[img]
### 日志截图
[img]
### 错误说明
发生了xx错误...
-9
View File
@@ -1,9 +0,0 @@
### 系统版本:Ubuntu 20.04
### 真寻版本:0.1.5.3
### 错误截图
[img]
### 错误说明
发生了xx错误...
-169
View File
@@ -1,169 +0,0 @@
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
demo.py
test.py
server_ip.py
member_activity_handle.py
Yu-Gi-Oh/
csgo/
fantasy_card/
data/
log/
backup/
extensive_plugin/
test/
bot.py
data/
.env
.env.dev
/resources/text/
/resources/image/
/resources/temp/
!/resources/template/
/configs/config.py
configs/config.yaml
./.env
./.env.dev
plugins/csgo_server/
plugins/activity/
!/resources/image/genshin/alc/back.png
!/data/genshin_alc/
.vscode/launch.json
-661
View File
@@ -1,661 +0,0 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are 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.
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.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
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 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 work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero 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 your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
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 AGPL, see
<https://www.gnu.org/licenses/>.
+4 -685
View File
@@ -1,687 +1,6 @@
<div align=center><img width="320" height="320" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/logo.png"/></div>
## 要不跑路吧
![maven](https://img.shields.io/badge/python-3.8%2B-blue)
![maven](https://img.shields.io/badge/nonebot-2.0.0-yellow)
![maven](https://img.shields.io/badge/go--cqhttp-1.0.0-red)
**中文** | [English](README_en-US.md)
# 绪山真寻Bot
****
此项目基于 Nonebot2 和 go-cqhttp 开发,以 postgresql 作为数据库的QQ群娱乐机器人
## 关于
用爱发电,某些功能学习借鉴了大佬们的代码,因为绪山真寻实在太可爱了因此开发了
绪山真寻bot,实现了一些对群友的娱乐功能和实用功能(大概)。
如果该项目的图片等等侵犯猫豆腐老师权益请联系我删除!
是新手!希望有个地方讨论绪山真寻Bot,或者有问题或建议,可以发送issues或加入[ <strong>[是真寻酱哒(萌新版)](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) </strong>]
[//]: # (是老手!讨论插件开发,nonebot2开发,可以加入[ <strong>[真寻酱的技术群]&#40;https://jq.qq.com/?_wv=1027&k=u8PgBkMZ&#41; </strong>])
## 声明
此项目仅用于学习交流,请勿用于非法用途
# Nonebot2
<img style="height: 200px;width: 200px;" src="https://camo.githubusercontent.com/0ef71e86056da694c540790aa4a4e314396884d6c4fdb95362a7538b27a1b034/68747470733a2f2f76322e6e6f6e65626f742e6465762f6c6f676f2e706e67">
非常 [ **[NICE](https://github.com/nonebot/nonebot2)** ] 的OneBot框架
## 未完成的文档
# [传送门](https://hibikier.github.io/zhenxun_bot/)
## 真寻的帮助
请对真寻说: '真寻帮助' or '管理员帮助' or '超级用户帮助' or '真寻帮助 指令'
## 普通帮助图片
![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/help.png)
## HTML版帮助图片
![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/html_help.png)
## Web UI
[zhenxun_bot_webui](https://github.com/HibiKier/zhenxun_bot_webui)
## 一键安装脚本
[zhenxun_bot-deploy](https://github.com/AkashiCoin/zhenxun_bot-deploy)
## 提供符合真寻标准的插件仓库
[AkashiCoin/nonebot_plugins_zhenxun_bot](https://github.com/AkashiCoin/nonebot_plugins_zhenxun_bot)
## 来点优点?
* 实现了许多功能,且提供了大量功能管理命令
* 通过Config配置项将所有插件配置统计保存至config.yaml,利于统一用户修改
* 方便增删插件,原生nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
* 提供了cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`__plugin_cd_limit__`
* __..... 更多详细请通过`传送门`查看文档!__
## 功能列表
<details>
<summary>已实现的功能</summary>
### 已实现的常用功能
* [x] 昵称系统(群与群与私聊分开.)
* [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味
* [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
* [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸)
* [x] 色图(这不是基础功能嘛喂)
* [x] coser
* [x] 黑白草图生成器
* [x] 鸡汤/语录
* [x] 骂我(钉宫语音)
* [x] 戳一戳(概率发送美图,钉宫语音或者戳回去)
* [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要session,可能需要代理,阿里云服务器等ip也许已经被ban了(我无代理访问失败),如果访问太多账号API调用可能被禁止访问api!)
* [x] 鲁迅说过
* [x] 构造假消息(自定义的分享链接)
* [x] 商店/我的金币/购买道具/使用道具
* [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw))
* [x] 我有一个朋友想问问..(借鉴pcrbot插件)
* [x] 原神黄历
* [x] 原神今日素材
* [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
* [x] 原神便笺查询
* [x] 原神玩家查询
* [x] 原神树脂提醒
* [x] 原神签到/自动签到
* [x] 金币红包
* [x] 微博热搜
* [x] B站主播/UP/番剧订阅
* [x] pil对图片的一些操作
* [x] BUFF饰品底价查询(需要session)
* [x] 天气查询
* [x] 疫情查询
* [x] bt磁力搜索(咳咳,这功能我想dddd)
* [x] reimu搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
* [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
* [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件)
* [x] 搜番
* [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论)
* [x] epic免费游戏
* [x] p站排行榜
* [x] p站搜图
* [x] 翻译(日英韩)
* [x] pix图库(一个自己的图库,含有增删查改,黑名单等命令)
* [x] 查看当前群欢迎消息
* [x] 查看该群自己的权限
* [x] 我的信息(只是为了看看什么时候入群)
* [x] 更新信息(如果继续更新的话)
* [x] go-cqhttp最新版下载和上传(不需要请删除)
* [x] 撤回
* [x] 滴滴滴-(用户对超级用户发送消息)
* [x] 金币红包/金币排行
* [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行
* [x] 网易云热评
* [x] 念首古诗
* [x] 获取b站视频封面
* [x] 通过PID获取图片
* [x] 功能统计可视化
* [x] 词云
* [x] 关于
### 已实现的管理员功能
* [x] 更新群组成员信息
* [x] 95%的群功能开关
* [x] 查看群内被动技能状态
* [x] 自定义群欢迎消息(是真寻的不是管家的!)
* [x] .ban/.unban(支持设置ban时长)= 黑白名单
* [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数
* [x] 上传图片/连续上传图片 (上传图片至指定图库)
* [x] 移动图片 (同上)
* [x] 删除图片 (同上)
* [x] 群内B站订阅
* [x] 词条设置
* [x] 休息吧/醒来
### 已实现的超级用户功能
* [x] 添加/删除权限(是真寻的管理员权限,不是群管理员)
* [x] 开启/关闭指定群的广播通知
* [x] 广播
* [x] 自检(检查系统状态)
* [x] 所有群组/所有好友
* [x] 退出指定群
* [x] 更新好友信息/更新群信息
* [x] /t(对用户进行回复或发送消息)
* [x] 上传/删除/修改商品(需要编写对应的商品功能)
* [x] 节日红包发送
* [x] 修改群权限
* [x] ban
* [x] 更新色图
* [x] 更新价格/更加图片(csgo开箱)
* [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池
* [x] 更新原神今日素材/更新原神资源信息
* [x] PIX相关操作
* [x] 检查更新真寻
* [x] 重启
* [x] 添加/删除/查看群白名单
* [x] 功能开关(更多设置)
* [x] 功能状态
* [x] b了
* [x] 执行sql
* [x] 重载配置
* [x] 清理临时数据
* [x] 增删群认证
* [x] 同意/拒绝好友/群聊请求
* [x] 配置重载
#### 超级用户的被动技能
* [x] 邀请入群提醒(别人邀请真寻入群)
* [x] 添加好友提醒(别人添加真寻好友)
### 已实现的被动技能
* [x] 进群欢迎消息
* [x] 群早晚安
* [x] 每日开箱重置提醒
* [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv),且5分钟内不解析相同url
* [x] 丢人爬(爬表情包)
* [x] epic通知(每日发送epic免费游戏链接)
* [x] 原神黄历提醒
* [x] 复读
### 已实现的看不见的技能
* [x] 刷屏禁言检测
* [x] 功能调用统计
* [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban)
* [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
* [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能))
* [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
* [x] 群权限系统
* [x] 定时更新权限
* [x] 自动配置重载
</details>
## 详细配置请前往文档,以下为最简部署和配置,如果你有基础并学习过nonebot2的话
## 简单部署
```
# 配置gocq
在 https://github.com/Mrs4s/go-cqhttp 下载Releases最新版本,运行后选择反向代理,
后将gocq的配置文件config.yml中的universal改为universal: ws://127.0.0.1:8080/onebot/v11/ws
# 获取代码
git clone https://github.com/HibiKier/zhenxun_bot.git
# 进入目录
cd zhenxun_bot
# 安装依赖
pip install poetry # 安装 poetry
poetry install # 安装依赖
# 进行基础配置
####请查看 配置 部分####
# 开始运行
poetry shell # 进入虚拟环境
python bot.py
```
## 简单配置
```
1.在.env.dev文件中
SUPERUSERS = [""] # 填写你的QQ
2.在configs/config.py文件中
* 数据库配置
3.在configs/config.yaml文件中 # 该文件需要启动一次后生成
* 修改插件配置项
```
## 使用Docker
**Docker 单机版(仅真寻Bot)**
**点击下方的 GitHub 徽标查看教程**
[![Github](https://shields.io/badge/GITHUB-Sakuracio-4476AF?logo=github&style=for-the-badge)](https://github.com/Sakuracio/zhenxun_bot_docker)
[![DOCKER](https://shields.io/badge/docker-hibikier/zhenxun_bot-4476AF?logo=docker&style=for-the-badge)](https://hub.docker.com/r/hibikier/zhenxun_bot)
**Docker 全量版(包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等)**
[![Github](https://shields.io/badge/GITHUB-SinKy--Yan-4476AF?logo=github&style=for-the-badge)](https://github.com/SinKy-Yan/zhenxunbot-docker)
[![DOCKER](https://shields.io/badge/docker-jyishit/zhenxun_bot-4476AF?logo=docker&style=for-the-badge)](https://hub.docker.com/r/jyishit/zhenxun_bot)
**点击上方的 GitHub 徽标查看教程**
PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能无法正常启动全量版容器**
## [爱发电](https://afdian.net/@HibiKier)
<details>
<summary>爱发电 以及 感谢投喂 </summary>
<img width="365px" height="450px" src="https://user-images.githubusercontent.com/45528451/175059389-cfeb8174-fa07-4939-80ab-a039087a50f6.png">
### 感谢名单
(可以告诉我你的 **github** 地址,我偷偷换掉0v|)
[shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377)
[A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377)
[疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377)
[投冥](https://afdian.net/a/144514mm)
[茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377)
[AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377)
[爱发电用户_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377)
[qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377)
[溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377)
[伝木](https://afdian.net/u/246b80683f9511edba7552540025c377)
[阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377)
[醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377)
[Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377)
[本喵无敌哒](https://afdian.net/u/dffaa9005bc911ebb69b52540025c377)
[椎名冬羽](https://afdian.net/u/ca1ebd64395e11ed81b452540025c377)
[kaito](https://afdian.net/u/a055e20a498811eab1f052540025c377)
[笑柒XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377)
[请问一份爱多少钱](https://afdian.net/u/f57ef6602dbd11ed977f52540025c377)
[咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377)
[Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377)
[墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377)
[爱发电用户_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377)
[笑柒XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377)
[noahzark](https://afdian.net/a/noahzark)
[腊条](https://afdian.net/u/f739c4d69eca11eba94b52540025c377)
[ze roller](https://afdian.net/u/0e599e96257211ed805152540025c377)
[爱发电用户_4jrf](https://afdian.net/u/6b2cdcc817c611ed949152540025c377)
[爱发电用户_TBsd](https://afdian.net/u/db638b60217911ed9efd52540025c377)
[烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377)
[ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377)
[爱发电用户_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377)
[爱发电用户_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377)
[爱发电用户_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377)
[MangataAkihi](https://github.com/Sakuracio)
[炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377)
[爱发电用户_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377)
[大魔王](https://github.com/xipesoy)
[CopilotLaLaLa](https://github.com/CopilotLaLaLa)
[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377)
[回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377)
[十年くん](https://github.com/shinianj)
[哇](https://afdian.net/u/9b266244f23911eca19052540025c377)
[yajiwa](https://github.com/yajiwa)
[爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)
</details>
## 更新
### 2023/3/12 \[v0.1.6.7]
* 新增`更新武器箱ALL`命令来更新所有武器箱
* 新增`查看武器箱`命令
* 色图bug修复、增加指令 [@pull/1301](https://github.com/HibiKier/zhenxun_bot/pull/1301)
### 2023/3/9
* 更正sql语句 [@pull/1302](https://github.com/HibiKier/zhenxun_bot/pull/1302)
* 修改签到卡片中签到增加好感度显示错误 [@pull/1299](https://github.com/HibiKier/zhenxun_bot/pull/1299)
### 2023/3/5
* 更新开箱会记录箱子数据以及开箱时箱子价格加入花费
* 修复开箱BUG
### 2023/3/4
* 重写翻译,使用百度翻译API
* 新增开箱日志以及自动更新武器箱
### 2023/3/2
* 修复config.yaml中把False也当成None的问题 [@pull/1288](https://github.com/HibiKier/zhenxun_bot/pull/1288)
* 删除道具表无用字段(props) [@pull/1287](https://github.com/HibiKier/zhenxun_bot/pull/1287)
* 修复词云
* 修复我的签到签到图片
* 更正BuffSkin添加语句
* 修复词条单图片/表情/at无法添加
### 2022/3/1
* 重写开箱更新箱子,允许更新目前所有箱子的皮肤
* 修复消息统计
### 2023/2/28
* 把Config的type字段默认类型由str改为None [@pull/1283](https://github.com/HibiKier/zhenxun_bot/pull/1283)
* 修复同意群聊请求以及添加群认证 更新变成查询的问题 [@pull/1282](https://github.com/HibiKier/zhenxun_bot/pull/1282)
### 2023/2/26
* Config提供`type`字段确定配置项类型
* 重写开箱功能
### 2023/2/25
* 修复ys查询,尘歌壶背景尺寸与内容不匹配的问题 [@pull/1270](https://github.com/HibiKier/zhenxun_bot/pull/1275)
* 更换cos url [@pull/1270](https://github.com/HibiKier/zhenxun_bot/pull/1274)
### 2023/2/20
* chat_history部分字段调整为可null [@pull/1270](https://github.com/HibiKier/zhenxun_bot/pull/1270)
### 2023/2/19
* 修正了`重载插件`的帮助提示
* 修改BUG
### 2023/2/18
* 数据库舍弃`gino`使用`tortoise`
* 昵称提供命令`全局昵称设置`
* `manager_group`群管理操作中`退群`,`修改群权限`,`添加/删除群白名单`,`添加/删除群认证`在群聊中使用命令时且未指定群聊时,默认指定当前群聊
* 修复插件帮助命令不生效的问题 [@pull/1263](https://github.com/HibiKier/zhenxun_bot/pull/1263)
* 解决开红包经常误触的问题,有红包和未领取的时候才会触发“开”命令 [@pull/1257](https://github.com/HibiKier/zhenxun_bot/pull/1257)
* 细节优化,原神今日素材重写 [@pull/1258](https://github.com/HibiKier/zhenxun_bot/pull/1258)
### 2023/1/31
* 修复B站转发卡片BUG [@pull/1249](https://github.com/HibiKier/zhenxun_bot/pull/1249)
### 2023/1/27
* 替换pixiv反向代理地址 [@pull/1244](https://github.com/HibiKier/zhenxun_bot/pull/1244)
### 2022/12/31
* 修复epic报错,优化简介 [@pull/1226](https://github.com/HibiKier/zhenxun_bot/pull/1226)
* 修复词条在某些回答下出错
* 原神黄历改为PIL
* 允许真寻自身触发命令,提供配置项 `self_message:STATUS`
### 2022/12/27 \[v0.1.6.6]
* 添加权限检查依赖注入
### 2022/12/26
* 优化`gamedraw`插件
* 提供全局被动控制
* 群被动状态改为图片
* 修复epic获取到的简介不是中文的bug [@pull/1221](https://github.com/HibiKier/zhenxun_bot/pull/1221)
## 2022/12/24
* 修复群管理员权限检测会阻挡超级用户权限
### 2022/12/23
* 优化`管理员帮助`,`超级用户帮助`图片
* 重新移植`gamedraw`
* 修复pil帮助私聊时无法生成
### 2022/12/17
* 修复查看插件仓库当已安装插件版本不一致时出错
### 2022/12/15
* 修复自定义群欢迎消息无法使用
### 2022/12/13
* 修复.unban
### 2022/12/12
* 修改HTML帮助禁用提示文本错误
* 修复HTML帮助私聊无法生成
### 2022/12/11
* 词条问题支持真寻的昵称开头与at真寻开头并优化回复
* 帮助新增HTML生成(新布局),添加配置`TYPE`切换
* 更正私聊时功能管理回复错误
* 修复加入新群聊时初始化功能开关错误
* 添加单例注解
* 添加统计表
### 2022/12/10
* 重写帮助,删除 `详细帮助` 命令
### 2022/12/4
* 优化管理代码
### 2022/11/28
* 修复web_ui群组无法获取
* 修复web_ui修改插件数据时cmd格式错误
### 2022/11/28
* :bug: Fix a bug in open_cases to get vanilla knives' prices [@pull/1188](https://github.com/HibiKier/zhenxun_bot/pull/1188)
### 2022/11/24
* 修复管理员插件加载路径错误
### 2022/11/23
* 修复webui插件无法获取修改
### 2022/11/22
* fix switch_rule [@pull/1185](https://github.com/HibiKier/zhenxun_bot/pull/1185)
### 2022/11/21 \[v0.1.6.5]
* 优化manager, hook代码
* 修复pid搜图 [@pull/1180](https://github.com/HibiKier/zhenxun_bot/pull/1180)
### 2022/11/19
* 修改优化帮助图片生成逻辑
### 2022/11/18
* poetry添加适配器依赖,更新支持py3.10 [@pull/1176](https://github.com/HibiKier/zhenxun_bot/pull/1176)
### 2022/11/13
* 更新天气api
* 使用道具可以附带额外信息供函数使用
* 限制帮助图片最小宽度
### 2022/11/12
* 更新yiqing插件数据显示 [@pull/1168](https://github.com/HibiKier/zhenxun_bot/pull/1168)
### 2022/11/11
* fix: B站直播订阅的相关问题 [@pull/1158](https://github.com/HibiKier/zhenxun_bot/pull/1158)
### 2022/10/30
* 商店简介动态行数,根据文字长度自动换行
### 2022/10/28
* 为exec指令进行了SELECT语句适配,添加了查看所有表指令 [@pull/1155](https://github.com/HibiKier/zhenxun_bot/pull/1155)
* 修复复读 [@pull/1154](https://github.com/HibiKier/zhenxun_bot/pull/1154)
### 2022/10/23
* 复读修改回图片下载
### 2022/10/22
* 更新依赖注入
### 2022/10/16 \[v0.1.6.4]
* 修改商店道具icon可以为空
### 2022/10/15
* nonebot2版本更新为rc1
* 我的道具改为图片形式
* 商品添加图标与是否为被动道具(被动道具无法被主动使用)
* 商品添加使用前方法和使用后方法(类似hook),使用方法具体查看文档或签到商品文件中注册的例子
* 新增用户使用道具,花费金币(包括插件)及用途记录
* 更细致的金币使用依赖注入
* 更多的依赖注入(包含图片获取等等..
* 修复我的道具仅有被动或主动道具时图片显示错误
* 色图插件p站反向代理失效 [@pull/1139](https://github.com/HibiKier/zhenxun_bot/pull/1139)
### 2022/10/9
* 修复碧蓝档案角色获取问题,换源 [@pull/1124](https://github.com/HibiKier/zhenxun_bot/pull/1124)
### 2022/10/7
* 修复 B 站请求返回 -401 错误 [@pull/1119](https://github.com/HibiKier/zhenxun_bot/pull/1119)
* 关闭功能与被动时不再区分大小写,同名时仅被动关闭操作生效
### 2022/9/30
* 修改重置开箱的使用权限 [@pull/1118](https://github.com/HibiKier/zhenxun_bot/pull/1118)
### 2022/9/27
* 更新b站转发解析 [@pull/1117](https://github.com/HibiKier/zhenxun_bot/pull/1117)
### 2022/9/24
* 修复b站订阅 [@pull/1112](https://github.com/HibiKier/zhenxun_bot/pull/1112)
* fix: 重载赛马娘卡池失败 [@pull/1114](https://github.com/HibiKier/zhenxun_bot/pull/1114)
### 2022/9/19
* 更换bilibili_sub获取用户昵称用的API&尝试修了一下get_video() [@pull/1097](https://github.com/HibiKier/zhenxun_bot/pull/1097)
* 修复csgo每日开箱可以多开一次
### 2022/9/18
* 修复 bilireq 版本过低导致 B 站视频解析错误 [@pull/1090](https://github.com/HibiKier/zhenxun_bot/pull/1096)
### 2022/9/16
* fix: bilibili_sub, azur_draw_card [@pull/1090](https://github.com/HibiKier/zhenxun_bot/pull/1090)
* 修复原神资源查询查询完毕后图片存储错误
* b站订阅发送 与 b站订阅 使用相同开关,即:关闭b站订阅
### 2022/9/10
* 自定义群欢迎消息参数不完全时提示报错
* 修改bt插件的url地址 [@pull/1067](https://github.com/HibiKier/zhenxun_bot/pull/1067)
### 2022/9/8
* 添加插件数据初始化判断
### 2022/9/4
* 旧词条提供图片迁移(需要重新获取old_model文件,并将数据库中user_qq为0的数据删除)
### 2022/9/3
* 原神玩家查询增加须弥地区 [@pull/1053](https://github.com/HibiKier/zhenxun_bot/pull/1053)
* av号覆盖全面,且修复av号链接 [@pull/1033](https://github.com/HibiKier/zhenxun_bot/pull/1033)
* 修复词条含有CQ回答的模糊匹配无法被解析
* 禁言检测图片在内存中获取图片hash
* B站订阅在群里中任意群管理员可以统一管理(原来为管理员1无法删除管理员2的订阅)
* 修复原神资源查询地图api数据变更导致更新的地图不完全
### 2022/8/27
* 修复签到积分双倍后,日志记录获得积分变4倍问题 [@pull/1044](https://github.com/HibiKier/zhenxun_bot/pull/1044)
### 2022/8/26
* 修复群管理员无法添加词条
* 修复词条关键词"问"前空格问题
### 2022/8/23
* 修了下模糊匹配 issue#1026 [@pull/1026](https://github.com/HibiKier/zhenxun_bot/pull/1026)
### 2022/8/22
* 修复首次安装时词条旧表出错(因为根本就没有这张表!)
* 取消配置替换定时任务,统一存储
* 对米游社cookie进行判断,整合米游社签到信息 [@pull/1014](https://github.com/HibiKier/zhenxun_bot/pull/1014)
* 修正尘歌壶和质变仪图片获取地址 [@pull/1010](https://github.com/HibiKier/zhenxun_bot/pull/1010)
* 修复词库问答 **很多** 问题[@pull/1012](https://github.com/HibiKier/zhenxun_bot/pull/1012)
### 2022/8/21 \[v0.1.6.3]
* 重构群词条,改为词库Plus,增加 精准|模糊|正则 问题匹配,问题与回答均支持at,image,face,超级用户额外提供 全局|私聊 词库设置,数据迁移目前只提供了问题和回答都是纯文本的词条
* 修复b站转发解析av号无法解析
* B站订阅直播订阅支持短号
* 开箱提供重置开箱命令,重置今日所有开箱数据(重置次数,并不会删除今日已开箱记录)
* 提供全局字典GDict,通过from utils.utils import GDict导入
* 适配omega 13w张图的数据结构表(建议删表重导)
* 除首次启动外将配置替换加入单次定时任务,加快启动速度
* fix: WordBank.check() [@pull/1008](https://github.com/HibiKier/zhenxun_bot/pull/1008)
* 改进插件 `我有一个朋友`,避免触发过于频繁 [@pull/1001](https://github.com/HibiKier/zhenxun_bot/pull/1001)
* 原神便笺新增洞天宝钱和参量质变仪提示 [@pull/1005](https://github.com/HibiKier/zhenxun_bot/pull/1005)
* 新增米游社签到功能,自动领取(白嫖)米游币 [@pull/991](https://github.com/HibiKier/zhenxun_bot/pull/991)
### 2022/8/14
* 修复epic未获取到时间时出错
* 修复订阅主播时动态获取的id是直播间id
### 2022/8/8
* 修复赛马娘重载卡池失败的问题 [@pull/969](https://github.com/HibiKier/zhenxun_bot/pull/969)
### 2022/8/3
* 修复 bili动态链接在投稿视频时URL和分割线连在一起 [@pull/951](https://github.com/HibiKier/zhenxun_bot/pull/961)
* 更新 Epic 免费游戏商城链接拼接规则 [@pull/957](https://github.com/HibiKier/zhenxun_bot/pull/957)
### 2022/8/6
* 修复了原神自动签到返回invalid request的问题,新增查看我的cookie命令 [@pull/971](https://github.com/HibiKier/zhenxun_bot/pull/971)
<br>
**..... 更多更新信息请查看文档**
## Todo
* [x] web管理
## 感谢
[botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议
[Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp的golang实现,轻量、原生跨平台.
[nonebot / nonebot2](https://github.com/nonebot/nonebot2) :跨平台Python异步机器人框架
[Angel-Hair / XUN_Bot](https://github.com/Angel-Hair/XUN_Bot) :一个基于NoneBot和酷Q的功能性QQ机器人
[pcrbot / cappuccilo_plugins](https://github.com/pcrbot/cappuccilo_plugins) :hoshino插件合集
[MeetWq /nonebot-plugin-withdraw](https://github.com/MeetWq/nonebot-plugin-withdraw) :A simple withdraw plugin for Nonebot2
[maxesisn / nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2) :适用于nonebot2的点歌插件
[nonepkg / nonebot-plugin-manager](https://github.com/nonepkg/nonebot-plugin-manager) :Nonebot Plugin Manager base on import hook
[H-K-Y / Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot) :原神bot,这是一个基于nonebot和HoshinoBot的原神娱乐及信息查询插件
[NothAmor / nonebot2_luxun_says](https://github.com/NothAmor/nonebot2_luxun_says) :基于nonebot2机器人框架的鲁迅说插件
[Kyomotoi / AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus) :一个~~特二刺螈~~(文爱)的适用于任何bot的词库
[Ailitonia / omega-miya](https://github.com/Ailitonia/omega-miya) :基于nonebot2的qq机器人
[KimigaiiWuyi / GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) :一个基于HoshinoBot/NoneBot2的原神UID查询插件
代码和人有一个能run就行了
已经坐船到柬埔寨了,马上转船到金三角了(
+6
View File
@@ -0,0 +1,6 @@
## I'D BETTER RUN
[中文](README.md) | **English**
Either code or me runable is fine
Already took the boat to Cambodia, and will transfer to the Golden Triangle soon(
+3
View File
@@ -0,0 +1,3 @@
## 要不跑路吧
代碼和人有一個能run就行了
已經坐船到柬埔寨了,馬上轉船到金三角了(
-1
View File
@@ -1 +0,0 @@
__version__: v0.1.6.7
View File
@@ -1,35 +0,0 @@
from pathlib import Path
import nonebot
from configs.config import Config
Config.add_plugin_config(
"admin_bot_manage:custom_welcome_message",
"SET_GROUP_WELCOME_MESSAGE_LEVEL [LEVEL]",
2,
name="群管理员操作",
help_="设置群欢迎消息权限",
default_value=2,
type=int,
)
Config.add_plugin_config(
"admin_bot_manage:switch_rule",
"CHANGE_GROUP_SWITCH_LEVEL [LEVEL]",
2,
help_="开关群功能权限",
default_value=2,
type=int,
)
Config.add_plugin_config(
"admin_bot_manage",
"ADMIN_DEFAULT_AUTH",
5,
help_="默认群管理员权限",
default_value=5,
type=int,
)
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
@@ -1,387 +0,0 @@
import asyncio
import os
import time
from datetime import datetime
from pathlib import Path
from typing import List
import ujson as json
from nonebot.adapters.onebot.v11.message import MessageSegment
from configs.config import Config
from configs.path_config import DATA_PATH, IMAGE_PATH
from models.group_member_info import GroupInfoUser
from models.level_user import LevelUser
from services.log import logger
from utils.http_utils import AsyncHttpx
from utils.image_utils import BuildImage
from utils.manager import group_manager, plugins2settings_manager, plugins_manager
from utils.message_builder import image
from utils.utils import get_bot, get_matchers
custom_welcome_msg_json = (
Path() / "data" / "custom_welcome_msg" / "custom_welcome_msg.json"
)
ICON_PATH = IMAGE_PATH / "other"
async def group_current_status(group_id: int) -> str:
"""
说明:
获取当前群聊所有通知的开关
参数:
:param group_id: 群号
"""
_data = group_manager.get_task_data()
image_list = []
for i, task in enumerate(_data):
name = _data[task]
name_image = BuildImage(0, 0, plain_text=f"{i+1}.{name}", font_size=20)
bk = BuildImage(
name_image.w + 200, name_image.h + 20, color=(103, 177, 109), font_size=15
)
await bk.apaste(name_image, (10, 0), True, "by_height")
a_icon = BuildImage(40, 40, background=ICON_PATH / "btn_false.png")
if group_manager.check_group_task_status(group_id, task):
a_icon = BuildImage(40, 40, background=ICON_PATH / "btn_true.png")
b_icon = BuildImage(40, 40, background=ICON_PATH / "btn_false.png")
if group_manager.check_task_super_status(task):
b_icon = BuildImage(40, 40, background=ICON_PATH / "btn_true.png")
await bk.atext((name_image.w + 20, 10), "状态")
await bk.apaste(a_icon, (name_image.w + 50, 0), True)
await bk.atext((name_image.w + 100, 10), "全局")
await bk.apaste(b_icon, (name_image.w + 130, 0), True)
image_list.append(bk)
w = max([x.w for x in image_list])
h = sum([x.h + 10 for x in image_list])
A = BuildImage(w + 20, h + 70, font_size=30, color=(119, 97, 177))
await A.atext((15, 20), "群被动状态")
curr_h = 75
for img in image_list:
# await img.acircle_corner()
await A.apaste(img, (0, curr_h), True)
curr_h += img.h + 10
return A.pic2bs4()
async def custom_group_welcome(
msg: str, img_list: List[str], user_id: int, group_id: int
) -> str:
"""
说明:
替换群欢迎消息
参数:
:param msg: 欢迎消息文本
:param img_list: 欢迎消息图片,只取第一张
:param user_id: 用户id,用于log记录
:param group_id: 群号
"""
img_result = ""
result = ""
img = img_list[0] if img_list else ""
if (DATA_PATH / f"custom_welcome_msg/{group_id}.jpg").exists():
(DATA_PATH / f"custom_welcome_msg/{group_id}.jpg").unlink()
data = {}
if not custom_welcome_msg_json.exists():
custom_welcome_msg_json.parent.mkdir(parents=True, exist_ok=True)
else:
try:
data = json.load(open(custom_welcome_msg_json, "r"))
except FileNotFoundError:
pass
try:
if msg:
data[str(group_id)] = str(msg)
json.dump(
data, open(custom_welcome_msg_json, "w"), indent=4, ensure_ascii=False
)
logger.info(f"USER {user_id} GROUP {group_id} 更换群欢迎消息 {msg}")
result += msg
if img:
await AsyncHttpx.download_file(
img, DATA_PATH / "custom_welcome_msg" / f"{group_id}.jpg"
)
img_result = image(DATA_PATH / "custom_welcome_msg" / f"{group_id}.jpg")
logger.info(f"USER {user_id} GROUP {group_id} 更换群欢迎消息图片")
except Exception as e:
logger.error(f"GROUP {group_id} 替换群消息失败 e:{e}")
return "替换群消息失败.."
return f"替换群欢迎消息成功:\n{result}" + img_result
task_data = None
def change_global_task_status(cmd: str) -> str:
"""
说明:
修改全局被动任务状态
参数:
:param cmd: 功能名称
"""
global task_data
if not task_data:
task_data = group_manager.get_task_data()
status = cmd[:2]
_cmd = cmd[4:]
if "全部被动" in cmd:
for task in task_data:
if status == "开启":
group_manager.open_global_task(task)
else:
group_manager.close_global_task(task)
group_manager.save()
return f"已 {status} 全局全部被动技能!"
else:
modules = [x for x in task_data if task_data[x].lower() == _cmd.lower()]
if not modules:
return "未查询到该被动任务"
if status == "开启":
group_manager.open_global_task(modules[0])
else:
group_manager.close_global_task(modules[0])
group_manager.save()
return f"已 {status} 全局{_cmd}"
async def change_group_switch(cmd: str, group_id: int, is_super: bool = False) -> str:
"""
说明:
修改群功能状态
参数:
:param cmd: 功能名称
:param group_id: 群号
:param is_super: 是否位超级用户,超级用户用于私聊开关功能状态
"""
global task_data
if not task_data:
task_data = group_manager.get_task_data()
group_help_file = DATA_PATH / "group_help" / f"{group_id}.png"
status = cmd[:2]
cmd = cmd[2:]
type_ = "plugin"
modules = plugins2settings_manager.get_plugin_module(cmd, True)
if cmd == "全部被动":
for task in task_data:
if status == "开启":
if not group_manager.check_group_task_status(group_id, task):
group_manager.open_group_task(group_id, task)
else:
if group_manager.check_group_task_status(group_id, task):
group_manager.close_group_task(group_id, task)
if group_help_file.exists():
group_help_file.unlink()
return f"已 {status} 全部被动技能!"
if cmd == "全部功能":
for f in plugins2settings_manager.get_data():
if status == "开启":
group_manager.unblock_plugin(f, group_id, False)
else:
group_manager.block_plugin(f, group_id, False)
group_manager.save()
if group_help_file.exists():
group_help_file.unlink()
return f"已 {status} 全部功能!"
if cmd.lower() in [task_data[x].lower() for x in task_data.keys()]:
type_ = "task"
modules = [x for x in task_data.keys() if task_data[x].lower() == cmd.lower()]
for module in modules:
if is_super:
module = f"{module}:super"
if status == "开启":
if type_ == "task":
if group_manager.check_group_task_status(group_id, module):
return f"被动 {task_data[module]} 正处于开启状态!不要重复开启."
group_manager.open_group_task(group_id, module)
else:
if group_manager.get_plugin_status(module, group_id):
return f"功能 {cmd} 正处于开启状态!不要重复开启."
group_manager.unblock_plugin(module, group_id)
else:
if type_ == "task":
if not group_manager.check_group_task_status(group_id, module):
return f"被动 {task_data[module]} 正处于关闭状态!不要重复关闭."
group_manager.close_group_task(group_id, module)
else:
if not group_manager.get_plugin_status(module, group_id):
return f"功能 {cmd} 正处于关闭状态!不要重复关闭."
group_manager.block_plugin(module, group_id)
if group_help_file.exists():
group_help_file.unlink()
if is_super:
for file in os.listdir(DATA_PATH / "group_help"):
file = DATA_PATH / "group_help" / file
file.unlink()
else:
_help_image = DATA_PATH / "group_help" / f"{group_id}.png"
if _help_image.exists():
_help_image.unlink()
return f"{status} {cmd} 功能!"
def set_plugin_status(cmd: str, block_type: str = "all"):
"""
说明:
设置插件功能状态(超级用户使用)
参数:
:param cmd: 功能名称
:param block_type: 限制类型, 'all': 私聊+群里, 'private': 私聊, 'group': 群聊
"""
status = cmd[:2]
cmd = cmd[2:]
module = plugins2settings_manager.get_plugin_module(cmd)
if status == "开启":
plugins_manager.unblock_plugin(module)
else:
plugins_manager.block_plugin(module, block_type=block_type)
for file in os.listdir(DATA_PATH / "group_help"):
file = DATA_PATH / "group_help" / file
file.unlink()
async def get_plugin_status():
"""
说明:
获取功能状态
"""
return await asyncio.get_event_loop().run_in_executor(None, _get_plugin_status)
def _get_plugin_status() -> MessageSegment:
"""
说明:
合成功能状态图片
"""
rst = "\t功能\n"
flag_str = "状态".rjust(4) + "\n"
tmp_name = []
for matcher in get_matchers():
if matcher.plugin_name not in tmp_name:
tmp_name.append(matcher.plugin_name)
module = matcher.plugin_name
flag = plugins_manager.get_plugin_block_type(module)
flag = flag.upper() + " CLOSE" if flag else "OPEN"
try:
plugin_name = plugins_manager.get(module).plugin_name
if (
"[Hidden]" in plugin_name
or "[Admin]" in plugin_name
or "[Superuser]" in plugin_name
):
continue
rst += f"{plugin_name}"
except KeyError:
rst += f"{module}"
if plugins_manager.get(module).error:
rst += "[ERROR]"
rst += "\n"
flag_str += f"{flag}\n"
height = len(rst.split("\n")) * 24
a = BuildImage(250, height, font_size=20)
a.text((10, 10), rst)
b = BuildImage(200, height, font_size=20)
b.text((10, 10), flag_str)
A = BuildImage(500, height)
A.paste(a)
A.paste(b, (270, 0))
return image(b64=A.pic2bs4())
async def update_member_info(group_id: int, remind_superuser: bool = False) -> bool:
"""
说明:
更新群成员信息
参数:
:param group_id: 群号
:param remind_superuser: 失败信息提醒超级用户
"""
bot = get_bot()
_group_user_list = await bot.get_group_member_list(group_id=group_id)
_error_member_list = []
_exist_member_list = []
# try:
for user_info in _group_user_list:
nickname = user_info["card"] or user_info["nickname"]
# 更新权限
if user_info["role"] in [
"owner",
"admin",
] and not await LevelUser.is_group_flag(user_info["user_id"], group_id):
await LevelUser.set_level(
user_info["user_id"],
user_info["group_id"],
Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH"),
)
if str(user_info["user_id"]) in bot.config.superusers:
await LevelUser.set_level(user_info["user_id"], user_info["group_id"], 9)
user = await GroupInfoUser.get_or_none(
user_qq=user_info["user_id"], group_id=user_info["group_id"]
)
if user:
if user.user_name != nickname:
user.user_name = nickname
await user.save(update_fields=["user_name"])
logger.info(
f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新群昵称成功"
)
_exist_member_list.append(int(user_info["user_id"]))
continue
join_time = datetime.strptime(
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])),
"%Y-%m-%d %H:%M:%S",
)
user, _ = await GroupInfoUser.get_or_create(
user_qq=user_info["user_id"],
group_id=user_info["group_id"],
defaults={
"user_name": nickname,
},
)
# await GroupInfoUser.update_or_create(
# user_qq=user_info["user_id"],
# group_id=user_info["group_id"],
# defaults={
# "user_name": nickname,
# # "user_join_time": datetime.now().replace(tzinfo=None),
# },
# )
user.user_join_time = join_time
await user.save()
_exist_member_list.append(int(user_info["user_id"]))
logger.info("更新成功", "更新成员信息", user_info["user_id"], user_info["group_id"])
_del_member_list = list(
set(_exist_member_list).difference(
set(await GroupInfoUser.get_group_member_id_list(group_id))
)
)
if _del_member_list:
for del_user in _del_member_list:
await GroupInfoUser.filter(user_qq=del_user, group_id=group_id).delete()
logger.info(f"退群用户{del_user} 所属{group_id} 已删除")
if _error_member_list and remind_superuser:
result = ""
for error_user in _error_member_list:
result += error_user
await bot.send_private_msg(
user_id=int(list(bot.config.superusers)[0]), message=result[:-1]
)
return True
def set_group_bot_status(group_id: int, status: bool) -> str:
"""
说明:
设置群聊bot开关状态
参数:
:param group_id: 群号
:param status: 状态
"""
if status:
if group_manager.check_group_bot_status(group_id):
return "我还醒着呢!"
group_manager.turn_on_group_bot_status(group_id)
return "呜..醒来了..."
else:
group_manager.shutdown_group_bot_status(group_id)
return "那我先睡觉了..."
@@ -1,37 +0,0 @@
from nonebot import on_notice
from nonebot.adapters.onebot.v11 import GroupAdminNoticeEvent
from configs.config import Config
from models.group_member_info import GroupInfoUser
from models.level_user import LevelUser
from services.log import logger
__zx_plugin_name__ = "群管理员变动监测 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
admin_notice = on_notice(priority=5)
@admin_notice.handle()
async def _(event: GroupAdminNoticeEvent):
if user := await GroupInfoUser.filter(
user_qq=event.user_id, group_id=event.group_id
).first():
nickname = user.nickname
else:
nickname = event.user_id
if event.sub_type == "set":
await LevelUser.set_level(
event.user_id,
event.group_id,
Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH"),
)
logger.info(
f"为新晋管理员 {nickname}({event.user_id}) "
f"添加权限等级:{Config.get_config('admin_bot_manage', 'ADMIN_DEFAULT_AUTH')}"
)
elif event.sub_type == "unset":
await LevelUser.delete_level(event.user_id, event.group_id)
logger.info(f"将非管理员 {nickname}({event.user_id}) 取消权限等级")
@@ -1,56 +0,0 @@
from typing import List
from configs.config import Config
from nonebot import on_command
from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message
from nonebot.adapters.onebot.v11.permission import GROUP
from nonebot.params import CommandArg
from services.log import logger
from utils.depends import ImageList
from ._data_source import custom_group_welcome
__zx_plugin_name__ = "自定义进群欢迎消息 [Admin]"
__plugin_usage__ = """
usage:
指令:
自定义进群欢迎消息 ?[文本] ?[图片]
示例:自定义进群欢迎消息 欢迎新人![图片]
Note:可以通过[at]来确认是否艾特新成员
示例:自定义进群欢迎消息 欢迎你[at]
""".strip()
__plugin_des__ = "简易的自定义群欢迎消息"
__plugin_cmd__ = ["自定义群欢迎消息 ?[文本] ?[图片]"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"admin_level": Config.get_config(
"admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL"
),
}
custom_welcome = on_command(
"自定义进群欢迎消息",
aliases={"自定义欢迎消息", "自定义群欢迎消息", "设置群欢迎消息"},
permission=GROUP,
priority=5,
block=True,
)
@custom_welcome.handle()
async def _(
event: GroupMessageEvent, arg: Message = CommandArg(), img: List[str] = ImageList()
):
msg = arg.extract_plain_text().strip()
if not msg and not img:
await custom_welcome.finish(__plugin_usage__)
try:
await custom_welcome.send(
await custom_group_welcome(msg, img, event.user_id, event.group_id),
at_sender=True,
)
logger.info(f"USER {event.user_id} GROUP {event.group_id} 自定义群欢迎消息:{msg}")
except Exception as e:
logger.error(f"自定义进群欢迎消息发生错误 {type(e)}:{e}")
await custom_welcome.send("发生了一些未知错误...")
-53
View File
@@ -1,53 +0,0 @@
from nonebot.adapters.onebot.v11 import Event
from utils.manager import group_manager, plugins2settings_manager
from utils.utils import get_message_text
from services.log import logger
import time
cmd = []
v = time.time()
def switch_rule(event: Event) -> bool:
"""
说明:
检测文本是否是关闭功能命令
参数:
:param event: pass
"""
global cmd, v
try:
if not cmd or time.time() - v > 60 * 60:
cmd = ["关闭全部被动", "开启全部被动", "开启全部功能", "关闭全部功能"]
_data = group_manager.get_task_data()
for key in _data:
cmd.append(f"开启{_data[key]}")
cmd.append(f"关闭{_data[key]}")
cmd.append(f"开启被动{_data[key]}")
cmd.append(f"关闭被动{_data[key]}")
cmd.append(f"开启 {_data[key]}")
cmd.append(f"关闭 {_data[key]}")
_data = plugins2settings_manager.get_data()
for key in _data.keys():
try:
if isinstance(_data[key].cmd, list):
for x in _data[key].cmd:
cmd.append(f"开启{x}")
cmd.append(f"关闭{x}")
cmd.append(f"开启 {x}")
cmd.append(f"关闭 {x}")
else:
cmd.append(f"开启{key}")
cmd.append(f"关闭{key}")
cmd.append(f"开启 {key}")
cmd.append(f"关闭 {key}")
except KeyError:
pass
v = time.time()
msg = get_message_text(event.json()).split()
msg = msg[0] if msg else ""
return msg in cmd
except Exception as e:
logger.error(f"检测是否为功能开关命令发生错误 {type(e)}: {e}")
return False
@@ -1,129 +0,0 @@
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent, GROUP, PrivateMessageEvent
from nonebot import on_command, on_message, on_regex
from nonebot.params import RegexGroup
from utils.message_builder import image
from ._data_source import (
change_group_switch,
set_plugin_status,
get_plugin_status,
group_current_status,
set_group_bot_status, change_global_task_status
)
from services.log import logger
from configs.config import NICKNAME, Config
from utils.utils import get_message_text, is_number
from nonebot.permission import SUPERUSER
from typing import Tuple, Any
from .rule import switch_rule
__zx_plugin_name__ = "群功能开关 [Admin]"
__plugin_usage__ = """
usage:
群内功能与被动技能开关
指令:
开启/关闭[功能]
群被动状态
开启全部被动
关闭全部被动
醒来/休息吧
示例:开启/关闭色图
""".strip()
__plugin_superuser_usage__ = """
usage:
(私聊)功能总开关与指定群禁用
指令:
功能状态
开启/关闭[功能] [group]
开启/关闭[功能] ['private'/'group']
开启被动/关闭被动[被动名称] # 全局被动控制
""".strip()
__plugin_des__ = "群内功能开关"
__plugin_cmd__ = [
"开启/关闭[功能]",
"群被动状态",
"开启全部被动",
"关闭全部被动",
"醒来/休息吧",
"功能状态 [_superuser]",
"开启/关闭[功能] [group] [_superuser]",
"开启/关闭[功能] ['private'/'group'] [_superuser]",
]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"admin_level": Config.get_config("admin_bot_manage", "CHANGE_GROUP_SWITCH_LEVEL"),
"cmd": ["开启功能", "关闭功能", "开关"]
}
switch_rule_matcher = on_message(rule=switch_rule, priority=4, block=True)
plugins_status = on_command("功能状态", permission=SUPERUSER, priority=5, block=True)
group_task_status = on_command("群被动状态", permission=GROUP, priority=5, block=True)
group_status = on_regex("^(休息吧|醒来)$", permission=GROUP, priority=5, block=True)
@switch_rule_matcher.handle()
async def _(bot: Bot, event: MessageEvent):
_cmd = get_message_text(event.json()).split()[0]
if isinstance(event, GroupMessageEvent):
await switch_rule_matcher.send(await change_group_switch(_cmd, event.group_id))
logger.info(f"USER {event.user_id} GROUP {event.group_id} 使用群功能管理命令 {_cmd}")
else:
if str(event.user_id) in bot.config.superusers:
block_type = " ".join(get_message_text(event.json()).split()[1:])
block_type = block_type if block_type else "a"
if ("关闭被动" in _cmd or "开启被动" in _cmd) and isinstance(event, PrivateMessageEvent):
await switch_rule_matcher.send(change_global_task_status(_cmd))
elif is_number(block_type):
if not int(block_type) in [
g["group_id"]
for g in await bot.get_group_list()
]:
await switch_rule_matcher.finish(f"{NICKNAME}未加入群聊:{block_type}")
await change_group_switch(_cmd, int(block_type), True)
group_name = (await bot.get_group_info(group_id=int(block_type)))[
"group_name"
]
await switch_rule_matcher.send(
f"已{_cmd[:2]}群聊 {group_name}({block_type}) 的 {_cmd[2:]} 功能"
)
elif block_type in ["all", "private", "group", "a", "p", "g"]:
block_type = "all" if block_type == "a" else block_type
block_type = "private" if block_type == "p" else block_type
block_type = "group" if block_type == "g" else block_type
set_plugin_status(_cmd, block_type)
if block_type == "all":
await switch_rule_matcher.send(f"已{_cmd[:2]}功能:{_cmd[2:]}")
elif block_type == "private":
await switch_rule_matcher.send(f"已在私聊中{_cmd[:2]}功能:{_cmd[2:]}")
else:
await switch_rule_matcher.send(f"已在群聊中{_cmd[:2]}功能:{_cmd[2:]}")
else:
await switch_rule_matcher.finish("格式错误:关闭[功能] [group]/[p/g]")
logger.info(f"USER {event.user_id} 使用功能管理命令 {_cmd} | {block_type}")
@plugins_status.handle()
async def _():
await plugins_status.send(await get_plugin_status())
@group_task_status.handle()
async def _(event: GroupMessageEvent):
await group_task_status.send(image(b64=await group_current_status(event.group_id)))
@group_status.handle()
async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
cmd = reg_group[0]
if cmd == "休息吧":
msg = set_group_bot_status(event.group_id, False)
else:
msg = set_group_bot_status(event.group_id, True)
await group_status.send(msg)
logger.info(f"USER {event.user_id} GROUP {event.group_id} 使用总开关命令:{cmd}")
@@ -1,52 +0,0 @@
from asyncpg.exceptions import ConnectionDoesNotExistError, UndefinedColumnError
from models.group_info import GroupInfo
from services.log import logger
from utils.utils import get_bot, scheduler
from ._data_source import update_member_info
__zx_plugin_name__ = "管理方面定时任务 [Hidden]"
__plugin_usage__ = "无"
__plugin_des__ = "成员信息和管理权限的定时更新"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
# 自动更新群员信息
@scheduler.scheduled_job(
"cron",
hour=2,
minute=1,
)
async def _():
bot = get_bot()
if bot:
gl = await bot.get_group_list()
gl = [g["group_id"] for g in gl]
for g in gl:
try:
await update_member_info(g)
logger.info(f"更新群组 g:{g} 成功")
except Exception as e:
logger.error(f"更新群组错误 g:{g} e:{e}")
# 快速更新群员信息以及管理员权限
@scheduler.scheduled_job(
"interval",
minutes=5,
)
async def _():
try:
bot = get_bot()
if bot:
gl = await bot.get_group_list()
gl = [g["group_id"] for g in gl]
all_group = [x.group_id for x in await GroupInfo.all()]
for g in gl:
if g not in all_group:
await update_member_info(g, False)
logger.info(f"快速更新群信息以及权限:{g}")
except (IndexError, ConnectionDoesNotExistError, UndefinedColumnError):
pass
@@ -1,41 +0,0 @@
from nonebot import on_command, on_notice
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, GROUP, GroupIncreaseNoticeEvent
from ._data_source import update_member_info
__zx_plugin_name__ = "更新群组成员列表 [Admin]"
__plugin_usage__ = """
usage:
更新群组成员的基本信息
指令:
更新群组成员列表/更新群组成员信息
""".strip()
__plugin_des__ = '更新群组成员列表'
__plugin_cmd__ = ['更新群组成员列表']
__plugin_version__ = 0.1
__plugin_author__ = 'HibiKier'
__plugin_settings__ = {
"admin_level": 1,
}
refresh_member_group = on_command(
"更新群组成员列表", aliases={"更新群组成员信息"}, permission=GROUP, priority=5, block=True
)
@refresh_member_group.handle()
async def _(event: GroupMessageEvent):
if await update_member_info(event.group_id):
await refresh_member_group.finish("更新群员信息成功!", at_sender=True)
else:
await refresh_member_group.finish("更新群员信息失败!", at_sender=True)
group_increase_handle = on_notice(priority=1, block=False)
@group_increase_handle.handle()
async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
if event.user_id == int(bot.self_id):
await update_member_info(event.group_id)
-27
View File
@@ -1,27 +0,0 @@
from nonebot import on_command
from nonebot.typing import T_State
from nonebot.adapters import Bot
from nonebot.adapters.onebot.v11 import GroupMessageEvent
from utils.message_builder import image
from .data_source import create_help_image, ADMIN_HELP_IMAGE
__zx_plugin_name__ = '管理帮助 [Admin]'
__plugin_usage__ = '管理员帮助,在群内回复“管理员帮助”'
__plugin_version__ = 0.1
__plugin_author__ = 'HibiKier'
__plugin_settings__ = {
"admin_level": 1,
}
admin_help = on_command("管理员帮助", aliases={"管理帮助"}, priority=5, block=True)
if ADMIN_HELP_IMAGE.exists():
ADMIN_HELP_IMAGE.unlink()
@admin_help.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
if not ADMIN_HELP_IMAGE.exists():
await create_help_image()
await admin_help.send(image(ADMIN_HELP_IMAGE))
-87
View File
@@ -1,87 +0,0 @@
import nonebot
from configs.path_config import IMAGE_PATH
from nonebot import Driver
from services.log import logger
from utils.image_template import help_template
from utils.image_utils import (BuildImage, build_sort_image, group_image,
text2image)
from utils.manager import group_manager, plugin_data_manager
from utils.manager.models import PluginType
driver: Driver = nonebot.get_driver()
background = IMAGE_PATH / "background" / "0.png"
ADMIN_HELP_IMAGE = IMAGE_PATH / "admin_help_img.png"
@driver.on_bot_connect
async def init_task():
if not group_manager.get_task_data():
group_manager.load_task()
logger.info(f"已成功加载 {len(group_manager.get_task_data())} 个被动技能.")
async def create_help_image():
"""
创建管理员帮助图片
"""
await _create_help_image()
async def _create_help_image():
"""
创建管理员帮助图片
"""
if ADMIN_HELP_IMAGE.exists():
return
plugin_data_ = plugin_data_manager.get_data()
image_list = []
task_list = []
for plugin_data in [plugin_data_[x] for x in plugin_data_]:
try:
usage = None
if plugin_data.plugin_type == PluginType.ADMIN and plugin_data.usage:
usage = await text2image(
plugin_data.usage, padding=5, color=(204, 196, 151)
)
if usage:
await usage.acircle_corner()
level = 5
if plugin_data.plugin_setting:
level = plugin_data.plugin_setting.level or level
image = await help_template(plugin_data.name + f"[{level}]", usage)
image_list.append(image)
if plugin_data.task:
for x in plugin_data.task.keys():
task_list.append(plugin_data.task[x])
except Exception as e:
logger.warning(
f"获取群管理员插件 {plugin_data.model}: {plugin_data.name} 设置失败... {type(e)}:{e}"
)
task_str = "\n".join(task_list)
task_str = "通过 开启/关闭 来控制群被动\n----------\n" + task_str
task_image = await text2image(task_str, padding=5, color=(204, 196, 151))
task_image = await help_template("被动任务", task_image)
image_list.append(task_image)
image_group, _ = group_image(image_list)
A = await build_sort_image(image_group, color="#f9f6f2", padding_top=180)
await A.apaste(
BuildImage(0, 0, font="CJGaoDeGuo.otf", plain_text="群管理员帮助", font_size=50),
(50, 30),
True,
)
await A.apaste(
BuildImage(
0,
0,
font="CJGaoDeGuo.otf",
plain_text="注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数",
font_size=30,
font_color="red",
),
(50, 90),
True,
)
await A.asave(ADMIN_HELP_IMAGE)
logger.info(f"已成功加载 {len(image_list)} 条管理员命令")
-242
View File
@@ -1,242 +0,0 @@
import shutil
from pathlib import Path
from typing import List
import nonebot
from nonebot import on_message
from nonebot.adapters.onebot.v11 import ActionFailed
from configs.config import NICKNAME, Config
from configs.path_config import IMAGE_PATH
from models.friend_user import FriendUser
from models.group_info import GroupInfo
from services.log import logger
from utils.message_builder import image
from utils.utils import broadcast_group, get_bot, scheduler
__zx_plugin_name__ = "定时任务相关 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_task__ = {"zwa": "早晚安"}
Config.add_plugin_config(
"_task", "DEFAULT_ZWA", True, help_="被动 早晚安 进群默认开关状态", default_value=True, type=bool
)
Config.add_plugin_config(
"_backup", "BACKUP_FLAG", True, help_="是否开启文件备份", default_value=True, type=bool
)
Config.add_plugin_config(
"_backup",
"BACKUP_DIR_OR_FILE",
[
"data/black_word",
"data/configs",
"data/statistics",
"data/word_bank",
"data/manager",
"configs",
],
name="文件备份",
help_="备份的文件夹或文件",
default_value=[],
type=List[str],
)
cx = on_message(priority=9999, block=False, rule=lambda: False)
# 早上好
@scheduler.scheduled_job(
"cron",
hour=6,
minute=1,
)
async def _():
img = image(IMAGE_PATH / "zhenxun" / "zao.jpg")
await broadcast_group("[[_task|zwa]]早上好" + img, log_cmd="被动早晚安")
logger.info("每日早安发送")
# 睡觉了
@scheduler.scheduled_job(
"cron",
hour=23,
minute=59,
)
async def _():
img = image(IMAGE_PATH / "zhenxun" / "sleep.jpg")
await broadcast_group(
f"[[_task|zwa]]{NICKNAME}要睡觉了,你们也要早点睡呀" + img, log_cmd="被动早晚安"
)
logger.info("每日晚安发送")
# 自动更新群组信息
@scheduler.scheduled_job(
"cron",
hour=3,
minute=1,
)
async def _():
bots = nonebot.get_bots()
_used_group = []
for bot in bots.values():
try:
group_list = await bot.get_group_list()
gl = [g["group_id"] for g in group_list if g["group_id"] not in _used_group]
for g in gl:
_used_group.append(g)
group_info = await bot.get_group_info(group_id=g)
await GroupInfo.update_or_create(
group_id=group_info["group_id"],
defaults={
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
"group_flag": 1,
},
)
logger.info(f"自动更新群组信息成功", group_id=g)
except Exception as e:
logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e)
# 自动更新好友信息
@scheduler.scheduled_job(
"cron",
hour=3,
minute=1,
)
async def _():
try:
bot = get_bot()
fl = await bot.get_friend_list()
for f in fl:
await FriendUser.create(user_id=f["user_id"], user_name=f["nickname"])
logger.info(f'自动更新好友 {f["user_id"]} 信息成功')
# else:
# logger.warning(f'自动更新好友 {f["user_id"]} 信息失败')
except Exception as e:
logger.error(f"自动更新群组信息错误 e:{e}")
# 自动备份
@scheduler.scheduled_job(
"cron",
hour=3,
minute=25,
)
async def _():
if Config.get_config("_backup", "BACKUP_FLAG"):
_backup_path = Path() / "backup"
_backup_path.mkdir(exist_ok=True, parents=True)
for x in Config.get_config("_backup", "BACKUP_DIR_OR_FILE"):
try:
path = Path(x)
_p = _backup_path / x
if path.exists():
if path.is_dir():
if _p.exists():
shutil.rmtree(_p, ignore_errors=True)
shutil.copytree(x, _p)
else:
if _p.exists():
_p.unlink()
shutil.copy(x, _p)
logger.info(f"已完成自动备份:{x}")
except Exception as e:
logger.error(f"自动备份文件 {x} 发生错误 {type(e)}:{e}")
# 一次性任务
# 固定时间触发,仅触发一次:
#
# from datetime import datetime
#
# @nonebot.scheduler.scheduled_job(
# 'date',
# run_date=datetime(2021, 1, 1, 0, 0),
# # timezone=None,
# )
# async def _():
# await bot.send_group_msg(group_id=123456,
# message="2021,新年快乐!")
# 定期任务
# 从 start_date 开始到 end_date 结束,根据类似 Cron
#
# 的规则触发任务:
#
# @nonebot.scheduler.scheduled_job(
# 'cron',
# # year=None,
# # month=None,
# # day=None,
# # week=None,
# day_of_week="mon,tue,wed,thu,fri",
# hour=7,
# # minute=None,
# # second=None,
# # start_date=None,
# # end_date=None,
# # timezone=None,
# )
# async def _():
# await bot.send_group_msg(group_id=123456,
# message="起床啦!")
# 间隔任务
#
# interval 触发器
#
# 从 start_date 开始,每间隔一段时间触发,到 end_date 结束:
#
# @nonebot.scheduler.scheduled_job(
# 'interval',
# # weeks=0,
# # days=0,
# # hours=0,
# minutes=5,
# # seconds=0,
# # start_date=time.now(),
# # end_date=None,
# )
# async def _():
# has_new_item = check_new_item()
# if has_new_item:
# await bot.send_group_msg(group_id=123456,
# message="XX有更新啦!")
# 动态的计划任务
# import datetime
#
# from apscheduler.triggers.date import DateTrigger # 一次性触发器
# # from apscheduler.triggers.cron import CronTrigger # 定期触发器
# # from apscheduler.triggers.interval import IntervalTrigger # 间隔触发器
# from nonebot import on_command, scheduler
#
# @on_command('赖床')
# async def _(session: CommandSession):
# await session.send('我会在5分钟后再喊你')
#
# # 制作一个“5分钟后”触发器
# delta = datetime.timedelta(minutes=5)
# trigger = DateTrigger(
# run_date=datetime.datetime.now() + delta
# )
#
# # 添加任务
# scheduler.add_job(
# func=session.send, # 要添加任务的函数,不要带参数
# trigger=trigger, # 触发器
# args=('不要再赖床啦!',), # 函数的参数列表,注意:只有一个值时,不能省略末尾的逗号
# # kwargs=None,
# misfire_grace_time=60, # 允许的误差时间,建议不要省略
# # jobstore='default', # 任务储存库,在下一小节中说明
# )
-179
View File
@@ -1,179 +0,0 @@
from typing import List
from nonebot import on_command
from nonebot.adapters.onebot.v11 import (
Bot,
GroupMessageEvent,
Message,
MessageEvent,
PrivateMessageEvent,
)
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from configs.config import NICKNAME, Config
from models.ban_user import BanUser
from models.level_user import LevelUser
from services.log import logger
from utils.depends import AtList, OneCommand
from utils.utils import is_number
from .data_source import a_ban, parse_ban_time
__zx_plugin_name__ = "封禁Ban用户 [Admin]"
__plugin_usage__ = """
usage:
将用户拉入或拉出黑名单
指令:
.ban [at] ?[小时] ?[分钟]
.unban
示例:.ban @user
示例:.ban @user 6
示例:.ban @user 3 10
示例:.unban @user
""".strip()
__plugin_superuser_usage__ = """
usage:
b了=屏蔽用户消息,相当于最上级.ban
跨群ban以及跨群b了
指令:
b了 [at/qq]
.ban [user_id] ?[小时] ?[分钟]
示例:b了 @user
示例:b了 1234567
示例:.ban 12345567
""".strip()
__plugin_des__ = "你被逮捕了!丢进小黑屋!"
__plugin_cmd__ = [".ban [at] ?[小时] ?[分钟]", ".unban [at]", "b了 [at] [_superuser]"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"admin_level": Config.get_config("ban", "BAN_LEVEL"),
"cmd": [".ban", ".unban", "ban", "unban"],
}
__plugin_configs__ = {
"BAN_LEVEL [LEVEL]": {
"value": 5,
"help": "ban/unban所需要的管理员权限等级",
"default_value": 5,
"type": int
}
}
ban = on_command(
".ban",
aliases={".unban", "/ban", "/unban"},
priority=5,
block=True,
)
super_ban = on_command("b了", permission=SUPERUSER, priority=5, block=True)
@ban.handle()
async def _(
bot: Bot,
event: GroupMessageEvent,
cmd: str = OneCommand(),
arg: Message = CommandArg(),
at_list: List[int] = AtList(),
):
result = ""
if at_list:
qq = at_list[0]
user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
user_name = user["card"] or user["nickname"]
msg = arg.extract_plain_text().strip()
time = parse_ban_time(msg)
if isinstance(time, str):
await ban.finish(time, at_sender=True)
user_level = await LevelUser.get_user_level(event.user_id, event.group_id)
is_not_superuser = str(event.user_id) not in bot.config.superusers
if cmd in [".ban", "/ban"]:
at_user_level = await LevelUser.get_user_level(qq, event.group_id)
if user_level <= at_user_level and is_not_superuser:
await ban.finish(
f"您的权限等级比对方低或相等, {NICKNAME}不能为您使用此功能!",
at_sender=True,
)
logger.info(f"用户封禁 时长: {time}", cmd, event.user_id, event.group_id, qq)
result = await a_ban(qq, time, user_name, event)
else:
if await BanUser.check_ban_level(qq, user_level) and is_not_superuser:
await ban.finish(
f"ban掉 {user_name} 的管理员权限比您高,无法进行unban", at_sender=True
)
if await BanUser.unban(qq):
logger.info(f"解除用户封禁", cmd, event.user_id, event.group_id, qq)
result = f"已经将 {user_name} 从黑名单中删除了!"
else:
result = f"{user_name} 不在黑名单!"
else:
await ban.finish("艾特人了吗??", at_sender=True)
await ban.send(result, at_sender=True)
@ban.handle()
async def _(
bot: Bot,
event: PrivateMessageEvent,
cmd: str = OneCommand(),
arg: Message = CommandArg(),
):
msg = arg.extract_plain_text().strip()
if msg and str(event.user_id) in bot.config.superusers:
msg_split = msg.split()
if msg_split and is_number(msg_split[0]):
qq = int(msg_split[0])
param = msg_split[1:]
if cmd in [".ban", "/ban"]:
time = parse_ban_time(" ".join(param))
if isinstance(time, str):
logger.info(time, cmd, event.user_id, target=qq)
await ban.finish(time)
result = await a_ban(qq, time, str(qq), event, 9)
else:
if await BanUser.unban(qq):
result = f"已经把 {qq} 从黑名单中删除了!"
else:
result = f"{qq} 不在黑名单!"
await ban.send(result)
logger.info(result, cmd, event.user_id, target=qq)
else:
await ban.send("参数不正确!\n格式:.ban [qq] [hour]? [minute]?", at_sender=True)
@super_ban.handle()
async def _(
bot: Bot,
event: MessageEvent,
cmd: str = OneCommand(),
arg: Message = CommandArg(),
at_list: List[int] = AtList(),
):
user_name = ""
qq = None
if isinstance(event, GroupMessageEvent):
if at_list:
qq = at_list[0]
user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
user_name = user["card"] or user["nickname"]
else:
msg = arg.extract_plain_text().strip()
if not is_number(msg):
await super_ban.finish("对象qq必须为纯数字...")
qq = int(msg)
user_name = msg
if qq:
await BanUser.ban(qq, 10, 99999999)
await ban.send(f"已将 {user_name} 拉入黑名单!")
logger.info(
f"已将 {user_name} 拉入黑名单!",
cmd,
event.user_id,
event.group_id if isinstance(event, GroupMessageEvent) else None,
qq,
)
else:
await super_ban.send("需要提供被super ban的对象,可以使用at或者指定qq...")
-77
View File
@@ -1,77 +0,0 @@
from typing import Optional, Union
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
from configs.config import NICKNAME
from models.ban_user import BanUser
from models.level_user import LevelUser
from services.log import logger
from utils.utils import is_number
def parse_ban_time(msg: str) -> Union[int, str]:
"""
解析ban时长
:param msg: 文本消息
"""
try:
if not msg:
return -1
msg_split = msg.split()
if len(msg_split) == 1:
if not is_number(msg_split[0].strip()):
return "参数必须是数字!"
return int(msg_split[0]) * 60 * 60
else:
if not is_number(msg_split[0].strip()) or not is_number(
msg_split[1].strip()
):
return "参数必须是数字!"
return int(msg_split[0]) * 60 * 60 + int(msg_split[1]) * 60
except ValueError as e:
logger.error("解析ban时长错误", ".ban", e=e)
return "时长不可以带小数点!"
async def a_ban(
qq: int,
time: int,
user_name: str,
event: MessageEvent,
ban_level: Optional[int] = None,
) -> str:
"""
ban
:param qq: qq
:param time: ban时长
:param user_name: ban用户昵称
:param event: event
:param ban_level: ban级别
"""
group_id = None
if isinstance(event, GroupMessageEvent):
group_id = event.group_id
ban_level = await LevelUser.get_user_level(event.user_id, event.group_id)
if not ban_level:
return "未查询到ban级用户权限"
if await BanUser.ban(qq, ban_level, time):
logger.info(
f"将 [Target]({qq})封禁 时长 {time / 60} 分钟", ".ban", event.user_id, group_id
)
result = f"已经将 {user_name} 加入{NICKNAME}的黑名单了!"
if time != -1:
result += f"将在 {time / 60} 分钟后解封"
else:
result += f"将在 ∞ 分钟后解封"
else:
ban_time = await BanUser.check_ban_time(qq)
if isinstance(ban_time, int):
ban_time = abs(float(ban_time))
if ban_time < 60:
ban_time = str(ban_time) + " 秒"
else:
ban_time = str(int(ban_time / 60)) + " 分钟"
else:
ban_time += " 分钟"
result = f"{user_name} 已在黑名单!预计 {ban_time}后解封"
return result
-68
View File
@@ -1,68 +0,0 @@
import asyncio
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from configs.config import Config
from services.log import logger
from utils.manager import group_manager
from utils.message_builder import image
from utils.utils import get_message_img
__zx_plugin_name__ = "广播 [Superuser]"
__plugin_usage__ = """
usage:
指令:
广播- ?[消息] ?[图片]
示例:广播- 你们好!
""".strip()
__plugin_des__ = "昭告天下!"
__plugin_cmd__ = ["广播-"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_task__ = {"broadcast": "广播"}
Config.add_plugin_config(
"_task",
"DEFAULT_BROADCAST",
True,
help_="被动 广播 进群默认开关状态",
default_value=True,
type=bool,
)
broadcast = on_command("广播-", priority=1, permission=SUPERUSER, block=True)
@broadcast.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip()
img_list = get_message_img(event.json())
rst = ""
for img in img_list:
rst += image(img)
gl = [
g["group_id"]
for g in await bot.get_group_list()
if group_manager.check_group_task_status(g["group_id"], "broadcast")
]
g_cnt = len(gl)
cnt = 0
error = ""
x = 0.25
for g in gl:
cnt += 1
if cnt / g_cnt > x:
await broadcast.send(f"已播报至 {int(cnt / g_cnt * 100)}% 的群聊")
x += 0.25
try:
await bot.send_group_msg(group_id=g, message=msg + rst)
logger.info(f"GROUP {g} 投递广播成功")
except Exception as e:
logger.error(f"GROUP {g} 投递广播失败:{type(e)}")
error += f"GROUP {g} 投递广播失败:{type(e)}\n"
await asyncio.sleep(0.5)
await broadcast.send(f"已播报至 100% 的群聊")
if error:
await broadcast.send(f"播报时错误:{error}")
-4
View File
@@ -1,4 +0,0 @@
import nonebot
from pathlib import Path
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
-6
View File
@@ -1,6 +0,0 @@
from nonebot.adapters.onebot.v11 import Event, MessageEvent
from configs.config import Config
def rule(event: Event) -> bool:
return Config.get_config("chat_history", "FLAG") and isinstance(event, MessageEvent)
@@ -1,72 +0,0 @@
from nonebot import on_message
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
from configs.config import Config
from models.chat_history import ChatHistory
from services.log import logger
from utils.depends import PlaintText
from utils.utils import scheduler
from ._rule import rule
__zx_plugin_name__ = "消息存储 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
Config.add_plugin_config(
"chat_history",
"FLAG",
True,
help_="是否开启消息自从存储",
name="消息存储",
default_value=True,
type=bool,
)
chat_history = on_message(rule=rule, priority=1, block=False)
TEMP_LIST = []
@chat_history.handle()
async def _(event: MessageEvent, msg: str = PlaintText()):
group_id = None
if isinstance(event, GroupMessageEvent):
group_id = event.group_id
TEMP_LIST.append(
{
"user_qq": event.user_id,
"group_id": group_id,
"text": str(event.get_message()),
"plain_text": msg,
}
)
@scheduler.scheduled_job(
"interval",
minutes=1,
)
async def _():
try:
message_list = TEMP_LIST.copy()
TEMP_LIST.clear()
if message_list:
model_list = [ChatHistory(**x) for x in message_list]
await ChatHistory.bulk_create(model_list)
logger.debug(f"批量添加聊天记录 {len(message_list)} 条", "定时任务")
except Exception as e:
logger.error(f"定时批量添加聊天记录", "定时任务", e=e)
# @test.handle()
# async def _(event: MessageEvent):
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
# print(await ChatHistory.get_group_msg(event.group_id))
# print(await ChatHistory.get_group_msg_count(event.group_id))
@@ -1,113 +0,0 @@
from datetime import datetime, timedelta
from typing import Any, Tuple
import pytz
from nonebot import on_regex
from nonebot.adapters.onebot.v11 import GroupMessageEvent
from nonebot.params import RegexGroup
from models.chat_history import ChatHistory
from models.group_member_info import GroupInfoUser
from utils.image_utils import BuildImage, text2image
from utils.message_builder import image
from utils.utils import is_number
__zx_plugin_name__ = "消息统计"
__plugin_usage__ = """
usage:
发言记录统计
regex:(周|月|日)?消息排行(des|DES)?(n=[0-9]{1,2})?
指令:
消息统计?(des)?(n=?)
周消息统计?(des)?(n=?)
月消息统计?(des)?(n=?)
日消息统计?(des)?(n=?)
示例:
消息统计
消息统计des
消息统计DESn=15
消息统计n=15
""".strip()
__plugin_des__ = "发言消息排行"
__plugin_cmd__ = ["消息统计", "周消息统计", "月消息统计", "日消息统计"]
__plugin_type__ = ("数据统计", 1)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"cmd": ["消息统计"],
}
msg_handler = on_regex(
r"^(周|月|日)?消息统计(des|DES)?(n=[0-9]{1,2})?$", priority=5, block=True
)
@msg_handler.handle()
async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
gid = event.group_id
date_scope = None
date, order, num = reg_group
num = num.split("=")[-1] if num else 10
if num and is_number(num) and 10 < int(num) < 50:
num = int(num)
time_now = datetime.now()
zero_today = time_now - timedelta(
hours=time_now.hour, minutes=time_now.minute, seconds=time_now.second
)
if date in ["日"]:
date_scope = (zero_today, time_now)
elif date in ["周"]:
date_scope = (time_now - timedelta(days=7), time_now)
elif date in ["月"]:
date_scope = (time_now - timedelta(days=30), time_now)
if rank_data := await ChatHistory.get_group_msg_rank(
gid, num, order or "DESC", date_scope
):
name = "昵称:\n\n"
num_str = "发言次数:\n\n"
idx = 1
for uid, num in rank_data:
if user := await GroupInfoUser.filter(user_qq=uid, group_id=gid).first():
user_name = user.user_name
else:
user_name = uid
name += f"\t{idx}.{user_name} \n\n"
num_str += f"\t{num}\n\n"
idx += 1
name_img = await text2image(name.strip(), padding=10, color="#f9f6f2")
num_img = await text2image(num_str.strip(), padding=10, color="#f9f6f2")
if not date_scope:
if date_scope := await ChatHistory.get_group_first_msg_datetime(gid):
date_scope = date_scope.astimezone(
pytz.timezone("Asia/Shanghai")
).replace(microsecond=0)
else:
date_scope = time_now.replace(microsecond=0)
date_str = f"日期:{date_scope} - 至今"
else:
date_str = f"日期:{date_scope[0].replace(microsecond=0)} - {date_scope[1].replace(microsecond=0)}"
date_w = BuildImage(0, 0, font_size=15).getsize(date_str)[0]
img_w = date_w if date_w > name_img.w + num_img.w else name_img.w + num_img.w
A = BuildImage(
img_w + 15,
num_img.h + 30,
color="#f9f6f2",
font="CJGaoDeGuo.otf",
font_size=15,
)
await A.atext((10, 10), date_str)
await A.apaste(name_img, (0, 30))
await A.apaste(num_img, (name_img.w, 30))
await msg_handler.send(image(b64=A.pic2bs4()))
# @test.handle()
# async def _(event: MessageEvent):
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
# print(await ChatHistory.get_group_msg(event.group_id))
# print(await ChatHistory.get_group_msg_count(event.group_id))
-223
View File
@@ -1,223 +0,0 @@
import os
import random
from datetime import datetime
from pathlib import Path
import ujson as json
from nonebot import on_notice, on_request
from nonebot.adapters.onebot.v11 import (
ActionFailed,
Bot,
GroupDecreaseNoticeEvent,
GroupIncreaseNoticeEvent,
)
from configs.config import NICKNAME, Config
from configs.path_config import DATA_PATH, IMAGE_PATH
from models.group_info import GroupInfo
from models.group_member_info import GroupInfoUser
from models.level_user import LevelUser
from services.log import logger
from utils.depends import GetConfig
from utils.manager import group_manager, plugins2settings_manager, requests_manager
from utils.message_builder import image
from utils.utils import FreqLimiter
__zx_plugin_name__ = "群事件处理 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_task__ = {"group_welcome": "进群欢迎", "refund_group_remind": "退群提醒"}
Config.add_plugin_config(
"invite_manager", "message", f"请不要未经同意就拉{NICKNAME}入群!告辞!", help_="强制拉群后进群回复的内容.."
)
Config.add_plugin_config(
"invite_manager", "flag", True, help_="被强制拉群后是否直接退出", default_value=True, type=bool
)
Config.add_plugin_config(
"invite_manager", "welcome_msg_cd", 5, help_="群欢迎消息cd", default_value=5, type=int
)
Config.add_plugin_config(
"_task",
"DEFAULT_GROUP_WELCOME",
True,
help_="被动 进群欢迎 进群默认开关状态",
default_value=True,
type=bool,
)
Config.add_plugin_config(
"_task",
"DEFAULT_REFUND_GROUP_REMIND",
True,
help_="被动 退群提醒 进群默认开关状态",
default_value=True,
type=bool,
)
_flmt = FreqLimiter(Config.get_config("invite_manager", "welcome_msg_cd") or 5)
# 群员增加处理
group_increase_handle = on_notice(priority=1, block=False)
# 群员减少处理
group_decrease_handle = on_notice(priority=1, block=False)
# (群管理)加群同意请求
add_group = on_request(priority=1, block=False)
@group_increase_handle.handle()
async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
if event.user_id == int(bot.self_id):
group = await GroupInfo.filter(group_id=event.group_id).first()
# 群聊不存在或被强制拉群,退出该群
if (not group or group.group_flag == 0) and Config.get_config(
"invite_manager", "flag"
):
try:
msg = Config.get_config("invite_manager", "message")
if msg:
await bot.send_group_msg(group_id=event.group_id, message=msg)
await bot.set_group_leave(group_id=event.group_id)
await bot.send_private_msg(
user_id=int(list(bot.config.superusers)[0]),
message=f"触发强制入群保护,已成功退出群聊 {event.group_id}..",
)
logger.info(f"强制拉群或未有群信息,退出群聊 {group} 成功")
requests_manager.remove_request("group", event.group_id)
except Exception as e:
logger.info(f"强制拉群或未有群信息,退出群聊 {group} 失败 e:{e}")
await bot.send_private_msg(
user_id=int(list(bot.config.superusers)[0]),
message=f"触发强制入群保护,退出群聊 {event.group_id} 失败..",
)
# 默认群功能开关
elif event.group_id not in group_manager.get_data().group_manager.keys():
data = plugins2settings_manager.get_data()
for plugin in data.keys():
if not data[plugin].default_status:
group_manager.block_plugin(plugin, event.group_id)
# 即刻刷新权限
for user_info in await bot.get_group_member_list(group_id=event.group_id):
if user_info["role"] in [
"owner",
"admin",
] and not await LevelUser.is_group_flag(
user_info["user_id"], event.group_id
):
await LevelUser.set_level(
user_info["user_id"],
user_info["group_id"],
Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH"),
)
if str(user_info["user_id"]) in bot.config.superusers:
await LevelUser.set_level(
user_info["user_id"], user_info["group_id"], 9
)
else:
join_time = datetime.now()
user_info = await bot.get_group_member_info(
group_id=event.group_id, user_id=event.user_id
)
user, _ = await GroupInfoUser.get_or_create(
user_qq=user_info["user_id"],
group_id=user_info["group_id"],
defaults={
"user_name": user_info["nickname"],
},
)
user.user_join_time = join_time
await user.save()
# await GroupInfoUser.update_or_create(
# user_qq=user_info["user_id"],
# group_id=user_info["group_id"],
# defaults={"user_name": user_info["nickname"], "user_join_time": join_time},
# )
logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功")
# 群欢迎消息
if _flmt.check(event.group_id):
_flmt.start_cd(event.group_id)
msg = ""
img = ""
at_flag = False
custom_welcome_msg_json = (
Path() / "data" / "custom_welcome_msg" / "custom_welcome_msg.json"
)
if custom_welcome_msg_json.exists():
data = json.load(open(custom_welcome_msg_json, "r"))
if data.get(str(event.group_id)):
msg = data[str(event.group_id)]
if msg.find("[at]") != -1:
msg = msg.replace("[at]", "")
at_flag = True
if (DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg").exists():
img = image(DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg")
if msg or img:
msg = msg.strip() + img
msg = "\n" + msg if at_flag else msg
await group_increase_handle.send(
"[[_task|group_welcome]]" + msg, at_sender=at_flag
)
else:
await group_increase_handle.send(
"[[_task|group_welcome]]新人快跑啊!!本群现状↓(快使用自定义!)"
+ image(
IMAGE_PATH
/ "qxz"
/ random.choice(os.listdir(IMAGE_PATH / "qxz"))
)
)
@group_decrease_handle.handle()
async def _(bot: Bot, event: GroupDecreaseNoticeEvent):
# 被踢出群
if event.sub_type == "kick_me":
group_id = event.group_id
operator_id = event.operator_id
if user := await GroupInfoUser.get_or_none(
user_qq=event.operator_id, group_id=event.group_id
):
operator_name = user.user_name
else:
operator_name = "None"
group = await GroupInfo.filter(group_id=group_id).first()
group_name = group.group_name if group else ""
coffee = int(list(bot.config.superusers)[0])
await bot.send_private_msg(
user_id=coffee,
message=f"****呜..一份踢出报告****\n"
f"我被 {operator_name}({operator_id})\n"
f"踢出了 {group_name}({group_id})\n"
f"日期:{str(datetime.now()).split('.')[0]}",
)
return
if event.user_id == int(bot.self_id):
group_manager.delete_group(event.group_id)
return
if user := await GroupInfoUser.get_or_none(
user_qq=event.user_id, group_id=event.group_id
):
user_name = user.user_name
else:
user_name = f"{event.user_id}"
await GroupInfoUser.filter(user_qq=event.user_id, group_id=event.group_id).delete()
logger.info(
f"名称: {user_name} 退出群聊",
"group_decrease_handle",
event.user_id,
event.group_id,
)
rst = ""
if event.sub_type == "leave":
rst = f"{user_name}离开了我们..."
if event.sub_type == "kick":
operator = await bot.get_group_member_info(
user_id=event.operator_id, group_id=event.group_id
)
operator_name = operator["card"] if operator["card"] else operator["nickname"]
rst = f"{user_name} 被 {operator_name} 送走了."
try:
await group_decrease_handle.send(f"[[_task|refund_group_remind]]{rst}")
except ActionFailed:
return
-56
View File
@@ -1,56 +0,0 @@
from nonebot import on_command
from nonebot.adapters.onebot.v11 import (
Bot,
MessageEvent,
GroupMessageEvent,
Message
)
from nonebot.params import CommandArg
from nonebot.rule import to_me
from configs.path_config import IMAGE_PATH, DATA_PATH
from utils.message_builder import image
from ._data_source import create_help_img, get_plugin_help
from ._utils import GROUP_HELP_PATH
import os
__zx_plugin_name__ = "帮助"
__plugin_configs__ = {
"TYPE": {"value": "normal", "help": "帮助图片样式 ['normal', 'HTML']", "default_value": "normal"}
}
simple_help_image = IMAGE_PATH / "simple_help.png"
if simple_help_image.exists():
simple_help_image.unlink()
simple_help = on_command("功能", rule=to_me(), aliases={"help", "帮助"}, priority=1, block=True)
@simple_help.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip()
is_super = False
if msg:
if '-super' in msg:
if str(event.user_id) in bot.config.superusers:
is_super = True
msg = msg.replace('-super', '').strip()
msg = get_plugin_help(msg, is_super)
if msg:
await simple_help.send(image(b64=msg))
else:
await simple_help.send("没有此功能的帮助信息...")
else:
if isinstance(event, GroupMessageEvent):
_image_path = GROUP_HELP_PATH / f"{event.group_id}.png"
if not _image_path.exists():
await create_help_img(event.group_id)
await simple_help.send(image(_image_path))
else:
if not simple_help_image.exists():
if simple_help_image.exists():
simple_help_image.unlink()
await create_help_img(None)
await simple_help.finish(image("simple_help.png"))
-14
View File
@@ -1,14 +0,0 @@
from typing import Optional, List, Any, Union, Dict
from pydantic import BaseModel
class Item(BaseModel):
plugin_name: str
sta: int
class PluginList(BaseModel):
plugin_type: str
icon: str
logo: str
items: List[Item]
-71
View File
@@ -1,71 +0,0 @@
from ._utils import HelpImageBuild
from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from utils.manager import (
plugins2settings_manager,
admin_manager,
)
from typing import Optional
import nonebot
random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help"
background = IMAGE_PATH / "background" / "0.png"
async def create_help_img(group_id: Optional[int]):
"""
说明:
生成帮助图片
参数:
:param group_id: 群号
"""
await HelpImageBuild().build_image(group_id)
def get_plugin_help(msg: str, is_super: bool = False) -> Optional[str]:
"""
说明:
获取功能的帮助信息
参数:
:param msg: 功能cmd
:param is_super: 是否为超级用户
"""
module = plugins2settings_manager.get_plugin_module(
msg
) or admin_manager.get_plugin_module(msg)
if module:
try:
plugin = nonebot.plugin.get_plugin(module)
metadata = plugin.metadata
if plugin:
if is_super:
result = plugin.module.__getattribute__(
"__plugin_superuser_usage__"
)
else:
result = (
metadata.usage
if metadata
else plugin.module.__getattribute__("__plugin_usage__")
)
if result:
width = 0
for x in result.split("\n"):
_width = len(x) * 24
width = width if width > _width else _width
height = len(result.split("\n")) * 45
A = BuildImage(width, height, font_size=24)
bk = BuildImage(
width,
height,
background=IMAGE_PATH / "background" / "1.png",
)
A.paste(bk, alpha=True)
A.text((int(width * 0.048), int(height * 0.21)), result)
return A.pic2bs4()
except AttributeError:
pass
return None
-255
View File
@@ -1,255 +0,0 @@
import os
import random
from typing import Dict, List, Optional
from configs.config import Config
from configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH
from utils.decorator import Singleton
from utils.image_utils import BuildImage, build_sort_image, group_image
from utils.manager import group_manager, plugin_data_manager
from utils.manager.models import PluginData, PluginType
from ._config import Item
GROUP_HELP_PATH = DATA_PATH / "group_help"
GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True)
for x in os.listdir(GROUP_HELP_PATH):
group_help_image = GROUP_HELP_PATH / x
group_help_image.unlink()
BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help"
LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo"
@Singleton
class HelpImageBuild:
def __init__(self):
self._data: Dict[str, PluginData] = plugin_data_manager.get_data()
self._sort_data: Dict[str, List[PluginData]] = {}
self._image_list = []
self.icon2str = {
"normal": "fa fa-cog",
"原神相关": "fa fa-circle-o",
"常规插件": "fa fa-cubes",
"联系管理员": "fa fa-envelope-o",
"抽卡相关": "fa fa-credit-card-alt",
"来点好康的": "fa fa-picture-o",
"数据统计": "fa fa-bar-chart",
"一些工具": "fa fa-shopping-cart",
"商店": "fa fa-shopping-cart",
"其它": "fa fa-tags",
"群内小游戏": "fa fa-gamepad",
}
def sort_type(self):
"""
说明:
对插件按照菜单类型分类
"""
if not self._sort_data.keys():
for key in self._data.keys():
plugin_data = self._data[key]
if plugin_data.plugin_type == PluginType.NORMAL:
if not self._sort_data.get(plugin_data.menu_type[0]):
self._sort_data[plugin_data.menu_type[0]] = []
self._sort_data[plugin_data.menu_type[0]].append(self._data[key])
async def build_image(self, group_id: Optional[int]):
if group_id:
help_image = GROUP_HELP_PATH / f"{group_id}.png"
else:
help_image = IMAGE_PATH / f"simple_help.png"
build_type = Config.get_config("help", "TYPE")
if build_type == "HTML":
byt = await self.build_html_image(group_id)
with open(help_image, "wb") as f:
f.write(byt)
else:
img = await self.build_pil_image(group_id)
img.save(help_image)
async def build_html_image(self, group_id: Optional[int]) -> bytes:
from nonebot_plugin_htmlrender import template_to_pic
self.sort_type()
classify = {}
for menu in self._sort_data:
for plugin in self._sort_data[menu]:
sta = 0
if not plugin.plugin_status.status:
if group_id and plugin.plugin_status.block_type in ["all", "group"]:
sta = 2
if not group_id and plugin.plugin_status.block_type in [
"all",
"private",
]:
sta = 2
if group_id and not group_manager.get_plugin_super_status(
plugin.model, group_id
):
sta = 2
if group_id and not group_manager.get_plugin_status(
plugin.model, group_id
):
sta = 1
if classify.get(menu):
classify[menu].append(Item(plugin_name=plugin.name, sta=sta))
else:
classify[menu] = [Item(plugin_name=plugin.name, sta=sta)]
max_len = 0
flag_index = -1
max_data = None
plugin_list = []
for index, plu in enumerate(classify.keys()):
if plu in self.icon2str.keys():
icon = self.icon2str[plu]
else:
icon = "fa fa-pencil-square-o"
logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH))
data = {
"name": plu if plu != "normal" else "功能",
"items": classify[plu],
"icon": icon,
"logo": str(logo.absolute()),
}
if len(classify[plu]) > max_len:
max_len = len(classify[plu])
flag_index = index
max_data = data
plugin_list.append(data)
del plugin_list[flag_index]
plugin_list.insert(0, max_data)
pic = await template_to_pic(
template_path=str((TEMPLATE_PATH / "menu").absolute()),
template_name="zhenxun_menu.html",
templates={"plugin_list": plugin_list},
pages={
"viewport": {"width": 1903, "height": 975},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
)
return pic
async def build_pil_image(self, group_id: Optional[int]) -> BuildImage:
"""
说明:
构造帮助图片
参数:
:param group_id: 群号
"""
self._image_list = []
self.sort_type()
font_size = 24
build_type = Config.get_config("help", "TYPE")
_image = BuildImage(0, 0, plain_text="1", font_size=font_size)
for idx, menu_type in enumerate(self._sort_data.keys()):
plugin_list = self._sort_data[menu_type]
wh_list = [_image.getsize(x.name) for x in plugin_list]
wh_list.append(_image.getsize(menu_type))
# sum_height = sum([x[1] for x in wh_list])
if build_type == "VV":
sum_height = 50 * len(plugin_list) + 10
else:
sum_height = (font_size + 6) * len(plugin_list) + 10
max_width = max([x[0] for x in wh_list]) + 20
bk = BuildImage(
max_width + 40,
sum_height + 50,
font_size=30,
color="#a7d1fc",
font="CJGaoDeGuo.otf",
)
title_size = bk.getsize(menu_type)
max_width = max_width if max_width > title_size[0] else title_size[0]
B = BuildImage(
max_width + 40,
sum_height,
font_size=font_size,
color="white" if not idx % 2 else "black",
)
curr_h = 10
for i, plugin_data in enumerate(plugin_list):
text_color = (255, 255, 255) if idx % 2 else (0, 0, 0)
if group_id and not group_manager.get_plugin_status(
plugin_data.model, group_id
):
text_color = (252, 75, 13)
pos = None
# 禁用状态划线
if (
not plugin_data.plugin_status.status
and plugin_data.plugin_status.block_type in ["group", "all"]
) or (group_id and not group_manager.get_plugin_super_status(
plugin_data.model, group_id
)):
w = curr_h + int(B.getsize(plugin_data.name)[1] / 2) + 2
pos = (
7,
w,
B.getsize(plugin_data.name)[0] + 35,
w,
)
if build_type == "VV":
name_image = await self.build_name_image(
max_width,
plugin_data.name,
"black" if not idx % 2 else "white",
text_color,
pos,
)
await B.apaste(
name_image, (0, curr_h), True, center_type="by_width"
)
curr_h += name_image.h + 5
else:
await B.atext(
(10, curr_h), f"{i + 1}.{plugin_data.name}", text_color
)
if pos:
await B.aline(pos, (236, 66, 7), 3)
curr_h += font_size + 5
if menu_type == "normal":
menu_type = "功能"
await bk.atext((0, 14), menu_type, center_type="by_width")
await bk.apaste(B, (0, 50))
await bk.atransparent(2)
# await bk.acircle_corner(point_list=['lt', 'rt'])
self._image_list.append(bk)
image_group, h = group_image(self._image_list)
B = await build_sort_image(
image_group,
h,
background_path=BACKGROUND_PATH,
background_handle=lambda image: image.filter("GaussianBlur", 5),
)
w = 10
h = 10
for msg in [
"目前支持的功能列表:",
"可以通过 ‘帮助[功能名称]’ 来获取对应功能的使用方法",
]:
text = BuildImage(
0,
0,
plain_text=msg,
font_size=24,
font="HYWenHei-85W.ttf",
)
B.paste(text, (w, h), True)
h += 50
if msg == "目前支持的功能列表:":
w += 50
await B.apaste(
BuildImage(
0,
0,
plain_text="注: 红字代表功能被群管理员禁用,红线代表功能正在维护",
font_size=24,
font="HYWenHei-85W.ttf",
font_color=(231, 74, 57),
),
(300, 10),
True,
)
return B
-33
View File
@@ -1,33 +0,0 @@
from configs.config import Config
Config.add_plugin_config(
"hook",
"CHECK_NOTICE_INFO_CD",
300,
name="基础hook配置",
help_="群检测,个人权限检测等各种检测提示信息cd",
default_value=300,
type=int,
)
Config.add_plugin_config(
"hook",
"MALICIOUS_BAN_TIME",
30,
help_="恶意命令触发检测触发后ban的时长(分钟)",
default_value=30,
type=int,
)
Config.add_plugin_config(
"hook",
"MALICIOUS_CHECK_TIME",
5,
help_="恶意命令触发检测规定时间内(秒)",
default_value=5,
type=int,
)
Config.add_plugin_config(
"hook", "MALICIOUS_BAN_COUNT", 6, help_="恶意命令触发检测最大触发次数", default_value=6, type=int
)
-525
View File
@@ -1,525 +0,0 @@
import time
from typing import Optional
import nonebot
from nonebot.adapters.onebot.v11 import (
Bot,
Event,
GroupMessageEvent,
Message,
MessageEvent,
PokeNotifyEvent,
PrivateMessageEvent,
)
from nonebot.exception import ActionFailed, IgnoredException
from nonebot.internal.matcher import Matcher
from configs.config import Config
from models.bag_user import BagUser
from models.ban_user import BanUser
from models.friend_user import FriendUser
from models.group_member_info import GroupInfoUser
from models.level_user import LevelUser
from models.user_shop_gold_log import UserShopGoldLog
from services.log import logger
from utils.decorator import Singleton
from utils.manager import (
StaticData,
admin_manager,
group_manager,
plugins2block_manager,
plugins2cd_manager,
plugins2count_manager,
plugins2settings_manager,
plugins_manager,
)
from utils.message_builder import at
from utils.utils import FreqLimiter
ignore_rst_module = ["ai", "poke", "dialogue"]
other_limit_plugins = ["poke"]
class StatusMessageManager(StaticData):
def __init__(self):
super().__init__(None)
def add(self, id_: int):
self._data[id_] = time.time()
def delete(self, id_: int):
if self._data.get(id_):
del self._data[id_]
def check(self, id_: int, t: int = 30) -> bool:
if self._data.get(id_):
if time.time() - self._data[id_] > t:
del self._data[id_]
return True
return False
return True
status_message_manager = StatusMessageManager()
def set_block_limit_false(event, module):
"""
设置用户block为false
:param event: event
:param module: 插件模块
"""
if plugins2block_manager.check_plugin_block_status(module):
plugin_block_data = plugins2block_manager.get_plugin_block_data(module)
check_type = plugin_block_data.check_type
limit_type = plugin_block_data.limit_type
if not (
(isinstance(event, GroupMessageEvent) and check_type == "private")
or (isinstance(event, PrivateMessageEvent) and check_type == "group")
):
block_type_ = event.user_id
if limit_type == "group" and isinstance(event, GroupMessageEvent):
block_type_ = event.group_id
plugins2block_manager.set_false(block_type_, module)
async def send_msg(msg: str, bot: Bot, event: MessageEvent):
"""
说明:
发送信息
参数:
:param msg: pass
:param bot: pass
:param event: pass
"""
if "[uname]" in msg:
uname = event.sender.card or event.sender.nickname
msg = msg.replace("[uname]", uname)
if "[nickname]" in msg:
if isinstance(event, GroupMessageEvent):
nickname = await GroupInfoUser.get_user_nickname(
event.user_id, event.group_id
)
else:
nickname = await FriendUser.get_user_nickname(event.user_id)
msg = msg.replace("[nickname]", nickname)
if "[at]" in msg and isinstance(event, GroupMessageEvent):
msg = msg.replace("[at]", str(at(event.user_id)))
try:
if isinstance(event, GroupMessageEvent):
status_message_manager.add(event.group_id)
await bot.send_group_msg(group_id=event.group_id, message=Message(msg))
else:
status_message_manager.add(event.user_id)
await bot.send_private_msg(user_id=event.user_id, message=Message(msg))
except ActionFailed:
pass
class IsSuperuserException(Exception):
pass
@Singleton
class AuthChecker:
"""
权限检查
"""
def __init__(self):
self._flmt = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
self._flmt_g = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
self._flmt_s = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
self._flmt_c = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
async def auth(self, matcher: Matcher, bot: Bot, event: Event):
"""
说明:
权限检查
参数:
:param matcher: matcher
:param bot: bot
:param event: event
"""
try:
if plugin_name := matcher.plugin_name:
cost_gold = await self.auth_cost(plugin_name, bot, event)
user_id = getattr(event, "user_id", None)
if user_id and str(user_id) not in bot.config.superusers:
await self.auth_basic(plugin_name, bot, event)
self.auth_group(plugin_name, bot, event)
await self.auth_admin(plugin_name, matcher, bot, event)
await self.auth_plugin(plugin_name, matcher, bot, event)
await self.auth_limit(plugin_name, bot, event)
if cost_gold:
await BagUser.spend_gold(
event.user_id, event.group_id, cost_gold
)
except IsSuperuserException:
return
async def auth_limit(self, plugin_name: str, bot: Bot, event: Event):
"""
说明:
插件限制
参数:
:param plugin_name: 模块名
:param bot: bot
:param event: event
"""
if plugins2cd_manager.check_plugin_cd_status(plugin_name):
plugin_cd_data = plugins2cd_manager.get_plugin_cd_data(plugin_name)
check_type = plugin_cd_data.check_type
limit_type = plugin_cd_data.limit_type
msg = plugin_cd_data.rst
if (
(isinstance(event, PrivateMessageEvent) and check_type == "private")
or (isinstance(event, GroupMessageEvent) and check_type == "group")
or plugins2cd_manager.get_plugin_data(plugin_name).check_type == "all"
):
cd_type_ = event.user_id
if limit_type == "group" and isinstance(event, GroupMessageEvent):
cd_type_ = event.group_id
if not plugins2cd_manager.check(plugin_name, cd_type_):
if msg:
await send_msg(msg, bot, event)
raise IgnoredException(f"{plugin_name} 正在cd中...")
else:
plugins2cd_manager.start_cd(plugin_name, cd_type_)
# Block
if plugins2block_manager.check_plugin_block_status(plugin_name):
plugin_block_data = plugins2block_manager.get_plugin_block_data(plugin_name)
check_type = plugin_block_data.check_type
limit_type = plugin_block_data.limit_type
msg = plugin_block_data.rst
if (
(isinstance(event, PrivateMessageEvent) and check_type == "private")
or (isinstance(event, GroupMessageEvent) and check_type == "group")
or check_type == "all"
):
block_type_ = event.user_id
if limit_type == "group" and isinstance(event, GroupMessageEvent):
block_type_ = event.group_id
if plugins2block_manager.check(block_type_, plugin_name):
if msg:
await send_msg(msg, bot, event)
raise IgnoredException(f"{event.user_id}正在调用{plugin_name}....")
else:
plugins2block_manager.set_true(block_type_, plugin_name)
# Count
if (
plugins2count_manager.check_plugin_count_status(plugin_name)
and event.user_id not in bot.config.superusers
):
plugin_count_data = plugins2count_manager.get_plugin_count_data(plugin_name)
limit_type = plugin_count_data.limit_type
msg = plugin_count_data.rst
count_type_ = event.user_id
if limit_type == "group" and isinstance(event, GroupMessageEvent):
count_type_ = event.group_id
if not plugins2count_manager.check(plugin_name, count_type_):
if msg:
await send_msg(msg, bot, event)
raise IgnoredException(f"{plugin_name} count次数限制...")
else:
plugins2count_manager.increase(plugin_name, count_type_)
async def auth_plugin(
self, plugin_name: str, matcher: Matcher, bot: Bot, event: Event
):
"""
说明:
插件状态
参数:
:param plugin_name: 模块名
:param matcher: matcher
:param bot: bot
:param event: event
"""
if plugin_name in plugins2settings_manager.keys() and matcher.priority not in [
1,
999,
]:
# 戳一戳单独判断
if (
isinstance(event, GroupMessageEvent)
or isinstance(event, PokeNotifyEvent)
or matcher.plugin_name in other_limit_plugins
):
if status_message_manager.get(event.group_id) is None:
status_message_manager.delete(event.group_id)
if plugins2settings_manager[
plugin_name
].level > group_manager.get_group_level(event.group_id):
try:
if (
self._flmt_g.check(event.user_id)
and plugin_name not in ignore_rst_module
):
self._flmt_g.start_cd(event.user_id)
await bot.send_group_msg(
group_id=event.group_id, message="群权限不足..."
)
except ActionFailed:
pass
if event.is_tome():
status_message_manager.add(event.group_id)
set_block_limit_false(event, plugin_name)
raise IgnoredException("群权限不足")
# 插件状态
if not group_manager.get_plugin_status(plugin_name, event.group_id):
try:
if plugin_name not in ignore_rst_module and self._flmt_s.check(
event.group_id
):
self._flmt_s.start_cd(event.group_id)
await bot.send_group_msg(
group_id=event.group_id, message="该群未开启此功能.."
)
except ActionFailed:
pass
if event.is_tome():
status_message_manager.add(event.group_id)
set_block_limit_false(event, plugin_name)
raise IgnoredException("未开启此功能...")
# 管理员禁用
if not group_manager.get_plugin_status(
f"{plugin_name}:super", event.group_id
):
try:
if (
self._flmt_s.check(event.group_id)
and plugin_name not in ignore_rst_module
):
self._flmt_s.start_cd(event.group_id)
await bot.send_group_msg(
group_id=event.group_id, message="管理员禁用了此群该功能..."
)
except ActionFailed:
pass
if event.is_tome():
status_message_manager.add(event.group_id)
set_block_limit_false(event, plugin_name)
raise IgnoredException("管理员禁用了此群该功能...")
# 群聊禁用
if not plugins_manager.get_plugin_status(
plugin_name, block_type="group"
):
try:
if (
self._flmt_c.check(event.group_id)
and plugin_name not in ignore_rst_module
):
self._flmt_c.start_cd(event.group_id)
await bot.send_group_msg(
group_id=event.group_id, message="该功能在群聊中已被禁用..."
)
except ActionFailed:
pass
if event.is_tome():
status_message_manager.add(event.group_id)
set_block_limit_false(event, plugin_name)
raise IgnoredException("该插件在群聊中已被禁用...")
else:
# 私聊禁用
if not plugins_manager.get_plugin_status(
plugin_name, block_type="private"
):
try:
if self._flmt_c.check(event.user_id):
self._flmt_c.start_cd(event.user_id)
await bot.send_private_msg(
user_id=event.user_id, message="该功能在私聊中已被禁用..."
)
except ActionFailed:
pass
if event.is_tome():
status_message_manager.add(event.user_id)
set_block_limit_false(event, plugin_name)
raise IgnoredException("该插件在私聊中已被禁用...")
# 维护
if not plugins_manager.get_plugin_status(plugin_name, block_type="all"):
if isinstance(
event, GroupMessageEvent
) and group_manager.check_group_is_white(event.group_id):
raise IsSuperuserException()
try:
if isinstance(event, GroupMessageEvent):
if (
self._flmt_c.check(event.group_id)
and plugin_name not in ignore_rst_module
):
self._flmt_c.start_cd(event.group_id)
logger.info(f"{event.user_id} ||XXXXXX: {matcher.module}")
await bot.send_group_msg(
group_id=event.group_id, message="此功能正在维护..."
)
else:
await bot.send_private_msg(
user_id=event.user_id, message="此功能正在维护..."
)
except ActionFailed:
pass
if event.is_tome():
id_ = (
event.group_id
if isinstance(event, GroupMessageEvent)
else event.user_id
)
status_message_manager.add(id_)
set_block_limit_false(event, plugin_name)
raise IgnoredException("此功能正在维护...")
async def auth_admin(
self, plugin_name: str, matcher: Matcher, bot: Bot, event: Event
):
"""
说明:
管理员命令 个人权限
参数:
:param plugin_name: 模块名
:param matcher: matcher
:param bot: bot
:param event: event
"""
if plugin_name in admin_manager.keys() and matcher.priority not in [1, 999]:
if isinstance(event, GroupMessageEvent):
# 个人权限
if (
not await LevelUser.check_level(
event.user_id,
event.group_id,
admin_manager.get_plugin_level(plugin_name),
)
and admin_manager.get_plugin_level(plugin_name) > 0
):
try:
if self._flmt.check(event.user_id):
self._flmt.start_cd(event.user_id)
await bot.send_group_msg(
group_id=event.group_id,
message=f"{at(event.user_id)}你的权限不足喔,该功能需要的权限等级:"
f"{admin_manager.get_plugin_level(plugin_name)}",
)
except ActionFailed:
pass
set_block_limit_false(event, plugin_name)
if event.is_tome():
status_message_manager.add(event.group_id)
raise IgnoredException("权限不足")
else:
if not await LevelUser.check_level(
event.user_id, 0, admin_manager.get_plugin_level(plugin_name)
):
try:
await bot.send_private_msg(
user_id=event.user_id,
message=f"你的权限不足喔,该功能需要的权限等级:{admin_manager.get_plugin_level(plugin_name)}",
)
except ActionFailed:
pass
set_block_limit_false(event, plugin_name)
if event.is_tome():
status_message_manager.add(event.user_id)
raise IgnoredException("权限不足")
def auth_group(self, plugin_name: str, bot: Bot, event: Event):
"""
说明:
群黑名单检测 群总开关检测
参数:
:param plugin_name: 模块名
:param bot: bot
:param event: event
"""
if isinstance(event, GroupMessageEvent) or plugin_name in other_limit_plugins:
try:
if (
group_manager.get_group_level(event.group_id) < 0
and str(event.user_id) not in bot.config.superusers
):
raise IgnoredException("群黑名单")
if not group_manager.check_group_bot_status(event.group_id):
try:
if str(event.get_message()) != "醒来":
raise IgnoredException("功能总开关关闭状态")
except ValueError:
raise IgnoredException("功能总开关关闭状态")
except AttributeError:
pass
async def auth_basic(self, plugin_name: str, bot: Bot, event: Event):
"""
说明:
检测是否满足超级用户权限,是否被ban等
参数:
:param plugin_name: 模块名
:param bot: bot
:param event: event
"""
try:
if (
(
not isinstance(event, MessageEvent)
and plugin_name not in other_limit_plugins
)
or await BanUser.is_ban(event.user_id)
and str(event.user_id) not in bot.config.superusers
) or (
str(event.user_id) in bot.config.superusers
and plugins2settings_manager.get_plugin_data(plugin_name)
and not plugins2settings_manager.get_plugin_data(
plugin_name
).limit_superuser
):
raise IsSuperuserException()
_plugin = nonebot.plugin.get_plugin(plugin_name)
_module = _plugin.module
_plugin_name = _module.__getattribute__("__zx_plugin_name__")
if (
"[superuser]" in _plugin_name.lower()
and str(event.user_id) in bot.config.superusers
):
raise IsSuperuserException()
except AttributeError:
pass
async def auth_cost(self, plugin_name: str, bot: Bot, event: Event) -> int:
"""
说明:
检测是否满足金币条件
参数:
:param plugin_name: 模块名
:param bot: bot
:param event: event
"""
cost_gold = 0
if isinstance(event, GroupMessageEvent) and (
psm := plugins2settings_manager.get_plugin_data(plugin_name)
):
if psm.cost_gold > 0:
if (
await BagUser.get_gold(event.user_id, event.group_id)
< psm.cost_gold
):
await send_msg(f"金币不足..该功能需要{psm.cost_gold}金币..", bot, event)
raise IgnoredException(f"{plugin_name} 金币限制...")
# 当插件不阻塞超级用户时,超级用户提前扣除金币
if (
str(event.user_id) in bot.config.superusers
and not psm.limit_superuser
):
await BagUser.spend_gold(
event.user_id, event.group_id, psm.cost_gold
)
await UserShopGoldLog.create(
user_qq=event.user_id,
group_id=event.group_id,
type=2,
name=plugin_name,
num=1,
spend_gold=psm.cost_gold,
)
cost_gold = psm.cost_gold
return cost_gold
-36
View File
@@ -1,36 +0,0 @@
from typing import Optional
from nonebot.adapters.onebot.v11 import (
Bot,
MessageEvent,
Event,
)
from nonebot.matcher import Matcher
from nonebot.message import run_preprocessor, run_postprocessor
from nonebot.typing import T_State
from ._utils import (
set_block_limit_false,
AuthChecker,
)
# # 权限检测
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: Event):
await AuthChecker().auth(matcher, bot, event)
# 解除命令block阻塞
@run_postprocessor
async def _(
matcher: Matcher,
exception: Optional[Exception],
bot: Bot,
event: Event,
state: T_State,
):
if not isinstance(event, MessageEvent) and matcher.plugin_name != "poke":
return
module = matcher.plugin_name
set_block_limit_false(event, module)
-98
View File
@@ -1,98 +0,0 @@
from nonebot.adapters.onebot.v11 import (
ActionFailed,
Bot,
Event,
GroupMessageEvent,
MessageEvent,
PokeNotifyEvent,
)
from nonebot.matcher import Matcher
from nonebot.message import IgnoredException, run_preprocessor
from nonebot.typing import T_State
from configs.config import Config
from models.ban_user import BanUser
from utils.message_builder import at
from utils.utils import FreqLimiter, is_number, static_flmt
from ._utils import ignore_rst_module, other_limit_plugins
Config.add_plugin_config(
"hook",
"BAN_RESULT",
"才不会给你发消息.",
help_="对被ban用户发送的消息",
)
_flmt = FreqLimiter(300)
# 检查是否被ban
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
if hasattr(event, "user_id") and (
(
(isinstance(event, MessageEvent) or isinstance(event, PokeNotifyEvent))
and matcher.priority not in [1, 999]
)
or matcher.plugin_name in other_limit_plugins
):
try:
if (
await BanUser.is_super_ban(event.user_id)
and str(event.user_id) not in bot.config.superusers
):
raise IgnoredException("用户处于超级黑名单中")
except AttributeError:
pass
if (
await BanUser.is_ban(event.user_id)
and str(event.user_id) not in bot.config.superusers
):
time = await BanUser.check_ban_time(event.user_id)
if isinstance(time, int):
time = abs(int(time))
if time < 60:
time = str(time) + " 秒"
else:
time = str(int(time / 60)) + " 分钟"
else:
time = str(time) + " 分钟"
if isinstance(event, GroupMessageEvent):
if not static_flmt.check(event.user_id):
raise IgnoredException("用户处于黑名单中")
static_flmt.start_cd(event.user_id)
if matcher.priority != 999:
try:
ban_result = Config.get_config("hook", "BAN_RESULT")
if (
ban_result
and _flmt.check(event.user_id)
and matcher.plugin_name not in ignore_rst_module
):
_flmt.start_cd(event.user_id)
await bot.send_group_msg(
group_id=event.group_id,
message=at(event.user_id)
+ ban_result
+ f" 在..在 {time} 后才会理你喔",
)
except ActionFailed:
pass
else:
if not static_flmt.check(event.user_id):
raise IgnoredException("用户处于黑名单中")
static_flmt.start_cd(event.user_id)
if matcher.priority != 999:
try:
ban_result = Config.get_config("hook", "BAN_RESULT")
if ban_result and matcher.plugin_name not in ignore_rst_module:
await bot.send_private_msg(
user_id=event.user_id,
message=at(event.user_id)
+ ban_result
+ f" 在..在 {time}后才会理你喔",
)
except ActionFailed:
pass
raise IgnoredException("用户处于黑名单中")
-54
View File
@@ -1,54 +0,0 @@
from nonebot.matcher import Matcher
from nonebot.message import run_preprocessor, IgnoredException
from nonebot.typing import T_State
from nonebot.adapters.onebot.v11 import (
Bot,
ActionFailed,
MessageEvent,
GroupMessageEvent,
)
from configs.config import Config
from models.ban_user import BanUser
from utils.utils import BanCheckLimiter
from utils.message_builder import at
from services.log import logger
_blmt = BanCheckLimiter(
Config.get_config("hook", "MALICIOUS_CHECK_TIME"),
Config.get_config("hook", "MALICIOUS_BAN_COUNT"),
)
# 恶意触发命令检测
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: GroupMessageEvent, state: T_State):
if not isinstance(event, MessageEvent):
return
if matcher.type == "message" and matcher.priority not in [1, 999]:
if state["_prefix"]["raw_command"]:
if _blmt.check(f'{event.user_id}{state["_prefix"]["raw_command"]}'):
await BanUser.ban(
event.user_id,
9,
Config.get_config("hook", "MALICIOUS_BAN_TIME") * 60,
)
logger.info(f"USER {event.user_id} 触发了恶意触发检测")
if isinstance(event, GroupMessageEvent):
try:
await bot.send_group_msg(
group_id=event.group_id,
message=at(event.user_id) + "检测到恶意触发命令,您将被封禁 30 分钟",
)
except ActionFailed:
pass
else:
try:
await bot.send_private_msg(
user_id=event.user_id,
message=at(event.user_id) + "检测到恶意触发命令,您将被封禁 30 分钟",
)
except ActionFailed:
pass
raise IgnoredException("检测到恶意触发命令")
_blmt.add(f'{event.user_id}{state["_prefix"]["raw_command"]}')
-43
View File
@@ -1,43 +0,0 @@
from nonebot.matcher import Matcher
from nonebot.message import run_preprocessor, IgnoredException
from nonebot.typing import T_State
from ._utils import status_message_manager
from nonebot.adapters.onebot.v11 import (
Bot,
MessageEvent,
PrivateMessageEvent,
GroupMessageEvent,
)
# 为什么AI会自己和自己聊天
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: PrivateMessageEvent, state: T_State):
if not isinstance(event, MessageEvent):
return
if event.user_id == int(bot.self_id):
raise IgnoredException("为什么AI会自己和自己聊天")
# 有命令就别说话了
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
if not isinstance(event, MessageEvent):
return
if matcher.type == "message" and matcher.plugin_name == "ai":
if (
isinstance(event, GroupMessageEvent)
and not status_message_manager.check(event.group_id)
):
status_message_manager.delete(event.group_id)
raise IgnoredException("有命令就别说话了")
elif (
isinstance(event, PrivateMessageEvent)
and not status_message_manager.check(event.user_id)
):
status_message_manager.delete(event.user_id)
raise IgnoredException("有命令就别说话了")
-56
View File
@@ -1,56 +0,0 @@
from nonebot.exception import MockApiException
from nonebot.adapters.onebot.v11 import Bot, Message
from utils.manager import group_manager
from services.log import logger
from typing import Dict, Any
import re
@Bot.on_calling_api
async def _(bot: Bot, api: str, data: Dict[str, Any]):
r = None
task = None
group_id = None
try:
if (
(
(api == "send_msg" and data.get("message_type") == "group")
or api == "send_group_msg"
)
and (
(
r := re.search(
"^\[\[_task\|(.*)]]",
data["message"].strip()
if isinstance(data["message"], str)
else str(data["message"]["text"]).strip(),
)
)
or (
r := re.search(
"^&#91;&#91;_task\|(.*)&#93;&#93;",
data["message"].strip()
if isinstance(data["message"], str)
else str(data["message"]["text"]).strip(),
)
)
)
and r.group(1) in group_manager.get_task_data().keys()
):
task = r.group(1)
group_id = data["group_id"]
except Exception as e:
logger.error(f"TaskHook ERROR {type(e)}:{e}")
else:
if task and group_id:
if (
group_manager.get_group_level(group_id) < 0
or not group_manager.check_task_status(task, group_id)
):
raise MockApiException(f"被动技能 {task} 处于关闭状态...")
else:
msg = str(data["message"]).strip()
msg = msg.replace(f"&#91;&#91;_task|{task}&#93;&#93;", "").replace(
f"[[_task|{task}]]", ""
)
data["message"] = Message(msg)
@@ -1,28 +0,0 @@
from nonebot.matcher import Matcher
from nonebot.message import run_postprocessor
from typing import Optional
from nonebot.typing import T_State
from nonebot.adapters.onebot.v11 import Bot, Event
from utils.manager import withdraw_message_manager
import asyncio
# 消息撤回
@run_postprocessor
async def _(
matcher: Matcher,
exception: Optional[Exception],
bot: Bot,
event: Event,
state: T_State,
):
tasks = []
for id_, time in withdraw_message_manager.data:
tasks.append(asyncio.ensure_future(_withdraw_message(bot, id_, time)))
withdraw_message_manager.remove((id_, time))
await asyncio.gather(*tasks)
async def _withdraw_message(bot: Bot, id_: int, time: int):
await asyncio.sleep(time)
await bot.delete_msg(message_id=id_)
@@ -1,55 +0,0 @@
import nonebot
from nonebot import Driver
from nonebot.adapters.onebot.v11 import Bot
from configs.path_config import DATA_PATH
from services.log import logger
from .check_plugin_status import check_plugin_status
from .init import init
from .init_none_plugin_count_manager import init_none_plugin_count_manager
from .init_plugin_info import init_plugin_info
from .init_plugins_config import init_plugins_config
from .init_plugins_data import init_plugins_data, plugins_manager
from .init_plugins_limit import (
init_plugins_block_limit,
init_plugins_cd_limit,
init_plugins_count_limit,
)
from .init_plugins_resources import init_plugins_resources
from .init_plugins_settings import init_plugins_settings
__zx_plugin_name__ = "初始化插件数据 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
driver: Driver = nonebot.get_driver()
@driver.on_startup
async def _():
"""
初始化数据
"""
config_file = DATA_PATH / "configs" / "plugins2config.yaml"
_flag = not config_file.exists()
init()
init_plugin_info()
init_plugins_settings()
init_plugins_cd_limit()
init_plugins_block_limit()
init_plugins_count_limit()
init_plugins_data()
init_plugins_config()
init_plugins_resources()
init_none_plugin_count_manager()
if _flag:
raise Exception("首次运行,已在configs目录下生成配置文件config.yaml,修改后重启即可...")
logger.info("初始化数据完成...")
@driver.on_bot_connect
async def _(bot: Bot):
# await init_group_manager()
await check_plugin_status(bot)
@@ -1,18 +0,0 @@
from utils.manager import plugins_manager
from nonebot.adapters.onebot.v11 import Bot
async def check_plugin_status(bot: Bot):
"""
遍历查看插件加载情况
"""
msg = ""
for plugin in plugins_manager.keys():
data = plugins_manager.get(plugin)
if data.error:
msg += f'{plugin}:{data.plugin_name}\n'
if msg and bot.config.superusers:
msg = "以下插件加载失败..\n" + msg
await bot.send_private_msg(
user_id=int(list(bot.config.superusers)[0]), message=msg.strip()
)
-14
View File
@@ -1,14 +0,0 @@
from utils.manager import plugins2settings_manager
def init():
if plugins2settings_manager.get("update_pic"):
plugins2settings_manager["update_picture"] = plugins2settings_manager["update_pic"]
plugins2settings_manager.delete("update_pic")
if plugins2settings_manager.get("white2black_img"):
plugins2settings_manager["white2black_image"] = plugins2settings_manager["white2black_img"]
plugins2settings_manager.delete("white2black_img")
if plugins2settings_manager.get("send_img"):
plugins2settings_manager["send_image"] = plugins2settings_manager["send_img"]
plugins2settings_manager.delete("send_img")
@@ -1,62 +0,0 @@
from utils.manager import (
none_plugin_count_manager,
plugins2count_manager,
plugins2cd_manager,
plugins2settings_manager,
plugins2block_manager,
plugins_manager,
)
from services.log import logger
from utils.utils import get_matchers
try:
import ujson as json
except ModuleNotFoundError:
import json
def init_none_plugin_count_manager():
"""
清除已删除插件数据
"""
modules = [x.plugin_name for x in get_matchers(True)]
plugins_manager_list = list(plugins_manager.keys())
for module in plugins_manager_list:
try:
if module not in modules or none_plugin_count_manager.check(module):
try:
plugin_name = plugins_manager.get(module).plugin_name
except (AttributeError, KeyError):
plugin_name = ""
if none_plugin_count_manager.check(module):
try:
plugins2settings_manager.delete(module)
plugins2count_manager.delete(module)
plugins2cd_manager.delete(module)
plugins2block_manager.delete(module)
plugins_manager.delete(module)
plugins_manager.save()
# resources_manager.remove_resource(module)
none_plugin_count_manager.delete(module)
logger.info(f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据...")
except Exception as e:
logger.exception(
f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据失败...{type(e)}:{e}"
)
else:
none_plugin_count_manager.add_count(module)
logger.info(
f"{module}:{plugin_name} 插件疑似已删除,"
f"加载{none_plugin_count_manager._max_count}次失败后将清除对应插件数据,"
f"当前次数:{none_plugin_count_manager.get(module)}"
)
else:
none_plugin_count_manager.reset(module)
except Exception as e:
logger.error(f"清除插件数据错误 {type(e)}:{e}")
plugins2settings_manager.save()
plugins2count_manager.save()
plugins2cd_manager.save()
plugins2block_manager.save()
plugins_manager.save()
none_plugin_count_manager.save()
@@ -1,147 +0,0 @@
import random
from types import ModuleType
from typing import Any, Dict
from configs.config import Config
from services import logger
from utils.manager import (
plugin_data_manager,
plugins2block_manager,
plugins2cd_manager,
plugins2count_manager,
plugins2settings_manager,
plugins_manager,
)
from utils.manager.models import (
Plugin,
PluginBlock,
PluginCd,
PluginCount,
PluginData,
PluginSetting,
PluginType,
)
from utils.utils import get_matchers
def get_attr(module: ModuleType, name: str, default: Any = None) -> Any:
"""
说明:
获取属性
参数:
:param module: module
:param name: name
:param default: default
"""
return getattr(module, name, None) or default
def init_plugin_info():
for matcher in [x for x in get_matchers(True)]:
try:
if (plugin := matcher.plugin) and matcher.plugin_name:
metadata = plugin.metadata
extra = metadata.extra if metadata else {}
if hasattr(plugin, "module"):
module = plugin.module
plugin_model = matcher.plugin_name
plugin_name = (
metadata.name
if metadata and metadata.name
else get_attr(module, "__zx_plugin_name__", matcher.plugin_name)
)
if not plugin_name:
logger.warning(f"配置文件 模块:{plugin_model} 获取 plugin_name 失败...")
continue
if "[Admin]" in plugin_name:
plugin_type = PluginType.ADMIN
plugin_name = plugin_name.replace("[Admin]", "").strip()
elif "[Hidden]" in plugin_name:
plugin_type = PluginType.HIDDEN
plugin_name = plugin_name.replace("[Hidden]", "").strip()
elif "[Superuser]" in plugin_name:
plugin_type = PluginType.SUPERUSER
plugin_name = plugin_name.replace("[Superuser]", "").strip()
else:
plugin_type = PluginType.NORMAL
plugin_usage = (
metadata.usage
if metadata and metadata.usage
else get_attr(module, "__plugin_usage__")
)
plugin_des = (
metadata.description
if metadata and metadata.description
else get_attr(module, "__plugin_des__")
)
menu_type = get_attr(module, "__plugin_type__") or ("normal",)
plugin_setting = get_attr(module, "__plugin_settings__")
if plugin_setting:
plugin_setting = PluginSetting(**plugin_setting)
plugin_setting.plugin_type = menu_type
plugin_superuser_usage = get_attr(module, "__plugin_super_usage__")
plugin_task = get_attr(module, "__plugin_task__")
plugin_version = extra.get("__plugin_version__") or get_attr(
module, "__plugin_version__"
)
plugin_author = extra.get("__plugin_author__") or get_attr(
module, "__plugin_author__"
)
plugin_cd = get_attr(module, "__plugin_cd_limit__")
if plugin_cd:
plugin_cd = PluginCd(**plugin_cd)
plugin_block = get_attr(module, "__plugin_block_limit__")
if plugin_block:
plugin_block = PluginBlock(**plugin_block)
plugin_count = get_attr(module, "__plugin_count_limit__")
if plugin_count:
plugin_count = PluginCount(**plugin_count)
plugin_resources = get_attr(module, "__plugin_resources__")
plugin_configs = get_attr(module, "__plugin_configs__")
if settings := plugins2settings_manager.get(plugin_model):
plugin_setting = settings
if plugin_cd_limit := plugins2cd_manager.get(plugin_model):
plugin_cd = plugin_cd_limit
if plugin_block_limit := plugins2block_manager.get(plugin_model):
plugin_block = plugin_block_limit
if plugin_count_limit := plugins2count_manager.get(plugin_model):
plugin_count = plugin_count_limit
if plugin_cfg := Config.get(plugin_model):
if plugin_configs:
for config_name in plugin_configs:
config: Dict[str, Any] = plugin_configs[config_name] # type: ignore
Config.add_plugin_config(
plugin_model,
config_name,
config.get("value"),
help_=config.get("help"),
default_value=config.get("default_value"),
type=config.get("type"),
)
plugin_configs = plugin_cfg.configs
plugin_status = plugins_manager.get(plugin_model)
if not plugin_status:
plugin_status = Plugin(plugin_name=plugin_model)
plugin_status.author = plugin_author
plugin_status.version = plugin_version
plugin_data = PluginData(
model=plugin_model,
name=plugin_name.strip(),
plugin_type=plugin_type,
usage=plugin_usage,
superuser_usage=plugin_superuser_usage,
des=plugin_des,
task=plugin_task,
menu_type=menu_type,
plugin_setting=plugin_setting,
plugin_cd=plugin_cd,
plugin_block=plugin_block,
plugin_count=plugin_count,
plugin_resources=plugin_resources,
plugin_configs=plugin_configs, # type: ignore
plugin_status=plugin_status,
)
plugin_data_manager.add_plugin_info(plugin_data)
except Exception as e:
logger.error(f"构造插件数据失败 {matcher.plugin_name}", e=e)
@@ -1,173 +0,0 @@
from pathlib import Path
from ruamel import yaml
from ruamel.yaml import YAML, round_trip_dump, round_trip_load
from configs.config import Config
from configs.path_config import DATA_PATH
from services.log import logger
from utils.manager import admin_manager, plugin_data_manager, plugins_manager
from utils.text_utils import prompt2cn
from utils.utils import get_matchers
_yaml = YAML(typ="safe")
def init_plugins_config():
"""
初始化插件数据配置
"""
plugins2config_file = DATA_PATH / "configs" / "plugins2config.yaml"
_data = Config.get_data()
# 优先使用 metadata 数据
for matcher in get_matchers(True):
if matcher.plugin_name:
if plugin_data := plugin_data_manager.get(matcher.plugin_name):
# 插件配置版本更新或为Version为None或不在存储配置内,当使用metadata时,必定更新
version = plugin_data.plugin_status.version
config = _data.get(matcher.plugin_name)
plugin = plugins_manager.get(matcher.plugin_name)
if plugin_data.plugin_configs and (
isinstance(version, str)
or (
version is None
or (
config
and config.configs.keys()
!= plugin_data.plugin_configs.keys()
)
or version > int(plugin.version or 0)
or matcher.plugin_name not in _data.keys()
)
):
plugin_configs = plugin_data.plugin_configs
for key in plugin_configs:
if isinstance(plugin_data.plugin_configs[key], dict):
Config.add_plugin_config(
matcher.plugin_name,
key,
plugin_configs[key].get("value"),
help_=plugin_configs[key].get("help"),
default_value=plugin_configs[key].get("default_value"),
_override=True,
type=plugin_configs[key].get("type"),
)
else:
config = plugin_configs[key]
Config.add_plugin_config(
matcher.plugin_name,
key,
config.value,
name=config.name,
help_=config.help,
default_value=config.default_value,
_override=True,
type=config.type,
)
elif plugin_configs := _data.get(matcher.plugin_name):
for key in plugin_configs.configs:
Config.add_plugin_config(
matcher.plugin_name,
key,
plugin_configs.configs[key].value,
help_=plugin_configs.configs[key].help,
default_value=plugin_configs.configs[key].default_value,
_override=True,
type=plugin_configs.configs[key].type,
)
if not Config.is_empty():
Config.save()
_data = round_trip_load(open(plugins2config_file, encoding="utf8"))
for plugin in _data.keys():
try:
plugin_name = plugins_manager.get(plugin).plugin_name
except (AttributeError, TypeError):
plugin_name = plugin
_data[plugin].yaml_set_start_comment(plugin_name, indent=2)
# 初始化未设置的管理员权限等级
for k, v in Config.get_admin_level_data():
admin_manager.set_admin_level(k, v)
# 存完插件基本设置
with open(plugins2config_file, "w", encoding="utf8") as wf:
round_trip_dump(
_data, wf, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True
)
_replace_config()
def _replace_config():
"""
说明:
定时任务加载的配置读取替换
"""
# 再开始读取用户配置
user_config_file = Path() / "configs" / "config.yaml"
_data = {}
_tmp_data = {}
if user_config_file.exists():
with open(user_config_file, "r", encoding="utf8") as f:
_data = _yaml.load(f)
# 数据替换
for plugin in Config.keys():
_tmp_data[plugin] = {}
for k in Config[plugin].configs.keys():
try:
if _data.get(plugin) and k in _data[plugin].keys():
Config.set_config(plugin, k, _data[plugin][k])
if level2module := Config.get_level2module(plugin, k):
try:
admin_manager.set_admin_level(
level2module, _data[plugin][k]
)
except KeyError:
logger.warning(
f"{level2module} 设置权限等级失败:{_data[plugin][k]}"
)
_tmp_data[plugin][k] = Config.get_config(plugin, k)
except AttributeError as e:
raise AttributeError(
f"{e}\n" + prompt2cn("可能为config.yaml配置文件填写不规范", 46)
)
Config.save()
temp_file = Path() / "configs" / "temp_config.yaml"
try:
with open(temp_file, "w", encoding="utf8") as wf:
yaml.dump(_tmp_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True)
with open(temp_file, "r", encoding="utf8") as rf:
_data = round_trip_load(rf)
# 添加注释
for plugin in _data.keys():
rst = ""
plugin_name = None
try:
if config_group := Config.get(plugin):
for key in list(config_group.configs.keys()):
try:
if config := config_group.configs[key]:
if config.name:
plugin_name = config.name
except AttributeError:
pass
except (KeyError, AttributeError):
plugin_name = None
if not plugin_name:
try:
plugin_name = plugins_manager.get(plugin).plugin_name
except (AttributeError, TypeError):
plugin_name = plugin
plugin_name = (
plugin_name.replace("[Hidden]", "")
.replace("[Superuser]", "")
.replace("[Admin]", "")
.strip()
)
rst += plugin_name + "\n"
for k in _data[plugin].keys():
rst += f"{k}: {Config[plugin].configs[k].help}" + "\n"
_data[plugin].yaml_set_start_comment(rst[:-1], indent=2)
with open(Path() / "configs" / "config.yaml", "w", encoding="utf8") as wf:
round_trip_dump(_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True)
except Exception as e:
logger.error(f"生成简易配置注释错误 {type(e)}:{e}")
if temp_file.exists():
temp_file.unlink()
@@ -1,57 +0,0 @@
from ruamel.yaml import YAML
from utils.manager import plugins_manager, plugin_data_manager
from utils.utils import get_matchers
from services.log import logger
try:
import ujson as json
except ModuleNotFoundError:
import json
_yaml = YAML(typ="safe")
def init_plugins_data():
"""
初始化插件数据信息
"""
for matcher in get_matchers(True):
_plugin = matcher.plugin
if not _plugin:
continue
try:
_module = _plugin.module
except AttributeError:
if matcher.plugin_name not in plugins_manager.keys():
plugins_manager.add_plugin_data(
matcher.plugin_name, matcher.plugin_name, error=True
)
else:
plugins_manager[matcher.plugin_name].error = True
else:
if plugin_data := plugin_data_manager.get(matcher.plugin_name):
try:
plugin_version = plugin_data.plugin_status.version
plugin_name = plugin_data.name
plugin_author = plugin_data.plugin_status.author
if matcher.plugin_name in plugins_manager.keys():
plugins_manager[matcher.plugin_name].error = False
if matcher.plugin_name not in plugins_manager.keys():
plugins_manager.add_plugin_data(
matcher.plugin_name,
plugin_name=plugin_name,
author=plugin_author,
version=plugin_version,
)
elif isinstance(plugin_version, str) or plugins_manager[matcher.plugin_name].version is None or (
plugin_version is not None
and plugin_version > float(plugins_manager[matcher.plugin_name].version)
):
plugins_manager[matcher.plugin_name].plugin_name = plugin_name
plugins_manager[matcher.plugin_name].author = plugin_author
plugins_manager[matcher.plugin_name].version = plugin_version
except Exception as e:
logger.error(f"插件数据 {matcher.plugin_name} 加载发生错误 {type(e)}:{e}")
plugins_manager.save()
@@ -1,64 +0,0 @@
from utils.manager import (
plugins2cd_manager,
plugins2block_manager,
plugins2count_manager,
plugin_data_manager,
)
from utils.utils import get_matchers
from configs.path_config import DATA_PATH
def init_plugins_cd_limit():
"""
加载 cd 限制
"""
plugins2cd_file = DATA_PATH / "configs" / "plugins2cd.yaml"
plugins2cd_file.parent.mkdir(exist_ok=True, parents=True)
for matcher in get_matchers(True):
if not plugins2cd_manager.get_plugin_cd_data(matcher.plugin_name) and (
plugin_data := plugin_data_manager.get(matcher.plugin_name)
):
if plugin_data.plugin_cd:
plugins2cd_manager.add_cd_limit(
matcher.plugin_name, plugin_data.plugin_cd
)
if not plugins2cd_manager.keys():
plugins2cd_manager.add_cd_limit("这是一个示例")
plugins2cd_manager.save()
plugins2cd_manager.reload_cd_limit()
def init_plugins_block_limit():
"""
加载阻塞限制
"""
for matcher in get_matchers(True):
if not plugins2block_manager.get_plugin_block_data(matcher.plugin_name) and (
plugin_data := plugin_data_manager.get(matcher.plugin_name)
):
if plugin_data.plugin_block:
plugins2block_manager.add_block_limit(
matcher.plugin_name, plugin_data.plugin_block
)
if not plugins2block_manager.keys():
plugins2block_manager.add_block_limit("这是一个示例")
plugins2block_manager.save()
plugins2block_manager.reload_block_limit()
def init_plugins_count_limit():
"""
加载次数限制
"""
for matcher in get_matchers(True):
if not plugins2count_manager.get_plugin_count_data(matcher.plugin_name) and (
plugin_data := plugin_data_manager.get(matcher.plugin_name)
):
if plugin_data.plugin_count:
plugins2count_manager.add_count_limit(
matcher.plugin_name, plugin_data.plugin_count
)
if not plugins2count_manager.keys():
plugins2count_manager.add_count_limit("这是一个示例")
plugins2count_manager.save()
plugins2count_manager.reload_count_limit()
@@ -1,25 +0,0 @@
from utils.manager import resources_manager, plugin_data_manager
from utils.utils import get_matchers
from services.log import logger
from pathlib import Path
def init_plugins_resources():
"""
资源文件路径的移动
"""
for matcher in get_matchers(True):
if plugin_data := plugin_data_manager.get(matcher.plugin_name):
try:
_module = matcher.plugin.module
except AttributeError:
logger.warning(f"插件 {matcher.plugin_name} 加载失败...,资源控制未加载...")
else:
if resources := plugin_data.plugin_resources:
path = Path(_module.__getattribute__("__file__")).parent
for resource in resources.keys():
resources_manager.add_resource(
matcher.plugin_name, path / resource, resources[resource]
)
resources_manager.save()
resources_manager.start_move()
@@ -1,44 +0,0 @@
from utils.manager import plugins2settings_manager, admin_manager, plugin_data_manager
from services.log import logger
from utils.manager.models import PluginType
from utils.utils import get_matchers
import nonebot
def init_plugins_settings():
"""
初始化插件设置,从插件中获取 __zx_plugin_name__,__plugin_cmd__,__plugin_settings__
"""
# for x in plugins2settings_manager.keys():
# try:
# _plugin = nonebot.plugin.get_plugin(x)
# _module = _plugin.module
# _module.__getattribute__("__zx_plugin_name__")
# except (KeyError, AttributeError) as e:
# logger.warning(f"配置文件 模块:{x} 获取 plugin_name 失败...{e}")
for matcher in get_matchers(True):
try:
if matcher.plugin_name not in plugins2settings_manager.keys():
_plugin = matcher.plugin
try:
_module = _plugin.module
except AttributeError:
logger.warning(f"插件 {matcher.plugin_name} 加载失败...,插件控制未加载.")
else:
if plugin_data := plugin_data_manager.get(matcher.plugin_name):
if plugin_settings := plugin_data.plugin_setting:
if (name := _module.__getattribute__("__zx_plugin_name__")) not in plugin_settings.cmd:
plugin_settings.cmd.append(name)
# 管理员命令
if plugin_data.plugin_type == PluginType.ADMIN:
admin_manager.add_admin_plugin_settings(
matcher.plugin_name, plugin_settings.cmd, plugin_settings.level
)
else:
plugins2settings_manager.add_plugin_settings(
matcher.plugin_name, plugin_settings
)
except Exception as e:
logger.error(f'{matcher.plugin_name} 初始化 plugin_settings 发生错误 {type(e)}:{e}')
plugins2settings_manager.save()
logger.info(f"已成功加载 {len(plugins2settings_manager.get_data())} 个非限制插件.")
-166
View File
@@ -1,166 +0,0 @@
import asyncio
import re
import time
from datetime import datetime
from nonebot import on_message, on_request
from nonebot.adapters.onebot.v11 import (
ActionFailed,
Bot,
FriendRequestEvent,
GroupRequestEvent,
MessageEvent,
)
from configs.config import NICKNAME, Config
from models.friend_user import FriendUser
from models.group_info import GroupInfo
from services.log import logger
from utils.manager import requests_manager
from utils.utils import scheduler
from .utils import time_manager
__zx_plugin_name__ = "好友群聊处理请求 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_configs__ = {
"AUTO_ADD_FRIEND": {
"value": False,
"help": "是否自动同意好友添加",
"default_value": False,
"type": bool,
}
}
friend_req = on_request(priority=5, block=True)
group_req = on_request(priority=5, block=True)
x = on_message(priority=999, block=False, rule=lambda: False)
@friend_req.handle()
async def _(bot: Bot, event: FriendRequestEvent):
if time_manager.add_user_request(event.user_id):
logger.debug(f"收录 用户[{event.user_id}] 好友请求", "好友请求")
user = await bot.get_stranger_info(user_id=event.user_id)
nickname = user["nickname"]
sex = user["sex"]
age = str(user["age"])
comment = event.comment
await bot.send_private_msg(
user_id=int(list(bot.config.superusers)[0]),
message=f"*****一份好友申请*****\n"
f"昵称:{nickname}({event.user_id})\n"
f"自动同意:{'√' if Config.get_config('invite_manager', 'AUTO_ADD_FRIEND') else '×'}\n"
f"日期:{str(datetime.now()).split('.')[0]}\n"
f"备注:{event.comment}",
)
if Config.get_config("invite_manager", "AUTO_ADD_FRIEND"):
logger.debug(f"已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id)
await bot.set_friend_add_request(flag=event.flag, approve=True)
await FriendUser.create(user_id=user["user_id"], user_name=user["nickname"])
else:
requests_manager.add_request(
event.user_id,
"private",
event.flag,
nickname=nickname,
sex=sex,
age=age,
comment=comment,
)
else:
logger.debug(f"好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id)
@group_req.handle()
async def _(bot: Bot, event: GroupRequestEvent):
# 邀请
if event.sub_type == "invite":
if str(event.user_id) in bot.config.superusers:
try:
logger.debug(
f"超级用户自动同意加入群聊", "群聊请求", event.user_id, target=event.group_id
)
await bot.set_group_add_request(
flag=event.flag, sub_type="invite", approve=True
)
group_info = await bot.get_group_info(group_id=event.group_id)
await GroupInfo.update_or_create(
group_id=group_info["group_id"],
defaults={
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
"group_flag": 1,
},
)
except ActionFailed as e:
logger.error(
"超级用户自动同意加入群聊发生错误",
"群聊请求",
event.user_id,
target=event.group_id,
e=e,
)
else:
if time_manager.add_group_request(event.user_id, event.group_id):
logger.debug(
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", "群聊请求"
)
user = await bot.get_stranger_info(user_id=event.user_id)
sex = user["sex"]
age = str(user["age"])
nickname = await FriendUser.get_user_name(event.user_id)
await bot.send_private_msg(
user_id=int(list(bot.config.superusers)[0]),
message=f"*****一份入群申请*****\n"
f"申请人:{nickname}({event.user_id})\n"
f"群聊:{event.group_id}\n"
f"邀请日期:{datetime.now().replace(microsecond=0)}",
)
await bot.send_private_msg(
user_id=event.user_id,
message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n"
"请确保已经群主或群管理沟通过!\n"
"等待管理员处理吧!",
)
requests_manager.add_request(
event.user_id,
"group",
event.flag,
invite_group=event.group_id,
nickname=nickname,
sex=sex,
age=age,
)
else:
logger.debug(
f"群聊请求五分钟内重复, 已忽略",
"群聊请求",
target=f"{event.user_id}:{event.group_id}",
)
@x.handle()
async def _(event: MessageEvent):
await asyncio.sleep(0.1)
r = re.search(r'groupcode="(.*?)"', str(event.get_message()))
if r:
group_id = int(r.group(1))
else:
return
r = re.search(r'groupname="(.*?)"', str(event.get_message()))
if r:
group_name = r.group(1)
else:
group_name = "None"
requests_manager.set_group_name(group_name, group_id)
@scheduler.scheduled_job(
"interval",
minutes=5,
)
async def _():
time_manager.clear()
-87
View File
@@ -1,87 +0,0 @@
import time
from dataclasses import dataclass
from typing import Dict
@dataclass
class PrivateRequest:
"""
好友请求
"""
user_id: int
time: float = time.time()
@dataclass
class GroupRequest:
"""
群聊请求
"""
user_id: int
group_id: int
time: float = time.time()
class RequestTimeManage:
"""
过滤五分钟以内的重复请求
"""
def __init__(self):
self._group: Dict[str, GroupRequest] = {}
self._user: Dict[int, PrivateRequest] = {}
def add_user_request(self, user_id: int) -> bool:
"""
添加请求时间
Args:
user_id (int): 用户id
Returns:
bool: 是否满足时间
"""
if user := self._user.get(user_id):
if time.time() - user.time < 60 * 5:
return False
self._user[user_id] = PrivateRequest(user_id)
return True
def add_group_request(self, user_id: int, group_id: int) -> bool:
"""
添加请求时间
Args:
user_id (int): 用户id
group_id (int): 邀请群聊
Returns:
bool: 是否满足时间
"""
key = f"{user_id}:{group_id}"
if group := self._group.get(key):
if time.time() - group.time < 60 * 5:
return False
self._group[key] = GroupRequest(user_id=user_id, group_id=group_id)
return True
def clear(self):
"""
清理过期五分钟请求
"""
now = time.time()
for user_id in self._user:
if now - self._user[user_id].time < 60 * 5:
del self._user[user_id]
for key in self._group:
if now - self._group[key].time < 60 * 5:
del self._group[key]
time_manager = RequestTimeManage()
-207
View File
@@ -1,207 +0,0 @@
import random
from typing import Any, List, Tuple
from nonebot import on_command, on_regex
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
from nonebot.internal.matcher import Matcher
from nonebot.internal.params import Depends
from nonebot.params import CommandArg, RegexGroup
from nonebot.rule import to_me
from configs.config import NICKNAME
from models.ban_user import BanUser
from models.friend_user import FriendUser
from models.group_member_info import GroupInfoUser
from services.log import logger
from utils.depends import GetConfig
__zx_plugin_name__ = "昵称系统"
__plugin_usage__ = f"""
usage:
个人昵称,将替换真寻称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称
指令:
以后叫我 [昵称]: 设置当前群聊/私聊的昵称
全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称
{NICKNAME}我是谁
""".strip()
__plugin_des__ = "区区昵称,才不想叫呢!"
__plugin_cmd__ = ["以后叫我 [昵称]", f"{NICKNAME}我是谁", "全局昵称设置 [昵称]"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["昵称"],
}
__plugin_configs__ = {
"BLACK_WORD": {
"value": ["爸", "爹", "爷", "父"],
"help": "昵称所屏蔽的关键词,已设置的昵称会被替换为 *,未设置的昵称会在设置时提示",
"default_value": None,
"type": List[str],
}
}
nickname = on_regex(
"(?:以后)?(?:叫我|请叫我|称呼我)(.*)",
rule=to_me(),
priority=5,
block=True,
)
my_nickname = on_command(
"我叫什么", aliases={"我是谁", "我的名字"}, rule=to_me(), priority=5, block=True
)
global_nickname = on_regex("设置全局昵称(.*)", rule=to_me(), priority=5, block=True)
cancel_nickname = on_command("取消昵称", rule=to_me(), priority=5, block=True)
def CheckNickname():
"""
说明:
检查名称是否合法
"""
async def dependency(
bot: Bot,
matcher: Matcher,
event: MessageEvent,
reg_group: Tuple[Any, ...] = RegexGroup(),
black_word: Any = GetConfig(config="BLACK_WORD"),
):
(msg,) = reg_group
logger.debug(f"昵称检查: {msg}", "昵称设置", event.user_id)
if not msg:
await matcher.finish("叫你空白?叫你虚空?叫你无名??", at_sender=True)
if str(event.user_id) in bot.config.superusers:
logger.debug(f"超级用户设置昵称, 跳过合法检测: {msg}", "昵称设置", event.user_id)
return
if len(msg) > 20:
await nickname.finish("昵称可不能超过20个字!", at_sender=True)
if msg in bot.config.nickname:
await nickname.finish("笨蛋!休想占用我的名字!#", at_sender=True)
if black_word:
for x in msg:
if x in black_word:
logger.debug("昵称设置禁止字符: [{x}]", "昵称设置", event.user_id)
await nickname.finish(f"字符 [{x}] 为禁止字符!", at_sender=True)
for word in black_word:
if word in msg:
logger.debug("昵称设置禁止字符: [{word}]", "昵称设置", event.user_id)
await nickname.finish(f"字符 [{word}] 为禁止字符!", at_sender=True)
return Depends(dependency)
@global_nickname.handle(parameterless=[CheckNickname()])
async def _(
event: MessageEvent,
reg_group: Tuple[Any, ...] = RegexGroup(),
):
(msg,) = reg_group
await FriendUser.set_user_nickname(event.user_id, msg)
await GroupInfoUser.filter(user_qq=event.user_id).update(nickname=msg)
logger.info(f"设置全局昵称成功: {msg}", "设置全局昵称", event.user_id)
await global_nickname.send(f"设置全局昵称成功!亲爱的{msg}")
@nickname.handle(parameterless=[CheckNickname()])
async def _(
event: MessageEvent,
reg_group: Tuple[Any, ...] = RegexGroup(),
):
(msg,) = reg_group
if isinstance(event, GroupMessageEvent):
await GroupInfoUser.set_user_nickname(event.user_id, event.group_id, msg)
if len(msg) < 5:
if random.random() < 0.3:
msg = "~".join(msg)
await nickname.send(
random.choice(
[
f"好啦好啦,我知道啦,{msg},以后就这么叫你吧",
f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}",
f"好突然,突然要叫你昵称什么的...{msg}..",
f"{NICKNAME}会好好记住{msg}的,放心吧",
f"好..好.,那窝以后就叫你{msg}了.",
]
)
)
logger.info(f"设置群昵称成功: {msg}", "昵称设置", event.user_id, event.group_id)
else:
await FriendUser.set_user_nickname(event.user_id, msg)
await nickname.send(
random.choice(
[
f"好啦好啦,我知道啦,{msg},以后就这么叫你吧",
f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}",
f"好突然,突然要叫你昵称什么的...{msg}..",
f"{NICKNAME}会好好记住{msg}的,放心吧",
f"好..好.,那窝以后就叫你{msg}了.",
]
)
)
logger.info(f"设置私聊昵称成功: {msg}", "昵称设置", event.user_id)
@my_nickname.handle()
async def _(event: MessageEvent):
nickname_ = None
card = None
if isinstance(event, GroupMessageEvent):
nickname_ = await GroupInfoUser.get_user_nickname(event.user_id, event.group_id)
card = event.sender.card or event.sender.nickname
else:
nickname_ = await FriendUser.get_user_nickname(event.user_id)
card = event.sender.nickname
if nickname_:
await my_nickname.send(
random.choice(
[
f"我肯定记得你啊,你是{nickname_}啊",
f"我不会忘记你的,你也不要忘记我!{nickname_}",
f"哼哼,{NICKNAME}记忆力可是很好的,{nickname_}",
f"嗯?你是失忆了嘛...{nickname_}..",
f"不要小看{NICKNAME}的记忆力啊!笨蛋{nickname_}!QAQ",
f"哎?{nickname_}..怎么了吗..突然这样问..",
]
)
)
else:
await my_nickname.send(
random.choice(
["没..没有昵称嘛,{}", "啊,你是{}啊,我想叫你的昵称!", "是{}啊,有什么事吗?", "你是{}?"]
).format(card)
)
@cancel_nickname.handle()
async def _(event: MessageEvent):
nickname_ = None
if isinstance(event, GroupMessageEvent):
nickname_ = await GroupInfoUser.get_user_nickname(event.user_id, event.group_id)
else:
nickname_ = await FriendUser.get_user_nickname(event.user_id)
if nickname_:
await cancel_nickname.send(
random.choice(
[
f"呜..{NICKNAME}睡一觉就会忘记的..和梦一样..{nickname_}",
f"窝知道了..{nickname_}..",
f"是{NICKNAME}哪里做的不好嘛..好吧..晚安{nickname_}",
f"呃,{nickname_},下次我绝对绝对绝对不会再忘记你!",
f"可..可恶!{nickname_}!太可恶了!呜",
]
)
)
if isinstance(event, GroupMessageEvent):
await GroupInfoUser.set_user_nickname(event.user_id, event.group_id, "")
else:
await FriendUser.set_user_nickname(event.user_id, "")
await BanUser.ban(event.user_id, 9, 60)
else:
await cancel_nickname.send("你在做梦吗?你没有昵称啊", at_sender=True)
-75
View File
@@ -1,75 +0,0 @@
from nonebot import on_command, on_regex
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from services.log import logger
from utils.message_builder import image
from .data_source import (
download_json,
install_plugin,
show_plugin_repo,
uninstall_plugin,
)
__zx_plugin_name__ = "插件商店 [Superuser]"
__plugin_usage__ = """
usage:
下载安装插件
指令:
查看插件仓库
更新插件仓库
安装插件 [name/id] (重新安装等同于更新)
卸载插件 [name/id]
""".strip()
__plugin_des__ = "从真寻适配仓库中下载插件"
__plugin_cmd__ = ["查看插件仓库", "更新插件仓库", "安装插件 [name/id]", "卸载插件 [name/id]"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
show_repo = on_regex("^查看插件仓库$", priority=1, block=True, permission=SUPERUSER)
update_repo = on_regex("^更新插件仓库$", priority=1, block=True, permission=SUPERUSER)
install_plugin_matcher = on_command(
"安装插件", priority=1, block=True, permission=SUPERUSER
)
uninstall_plugin_matcher = on_command(
"卸载插件", priority=1, block=True, permission=SUPERUSER
)
@install_plugin_matcher.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
name = arg.extract_plain_text().strip()
msg = await install_plugin(name)
await install_plugin_matcher.send(msg)
logger.info(f"安装插件: {name}", "安装插件", event.user_id)
@uninstall_plugin_matcher.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
name = arg.extract_plain_text().strip()
msg = await uninstall_plugin(name)
await install_plugin_matcher.send(msg)
logger.info(f"卸载插件: {name}", "卸载插件", event.user_id)
@update_repo.handle()
async def _(bot: Bot, event: MessageEvent):
code = await download_json()
if code == 200:
await update_repo.finish("更新插件仓库信息成功!")
await update_repo.send("更新插件仓库信息失败!")
logger.info("更新插件仓库信息", "更新插件仓库信息", event.user_id)
@show_repo.handle()
async def _(bot: Bot, event: MessageEvent):
msg = await show_plugin_repo()
if isinstance(msg, int):
await show_repo.finish("文件下载失败或解压失败..")
await show_repo.send(image(msg))
logger.info("查看插件仓库", "查看插件仓库", event.user_id)
-201
View File
@@ -1,201 +0,0 @@
import os
import shutil
import zipfile
from pathlib import Path
from typing import Tuple, Union
import ujson as json
from configs.path_config import DATA_PATH, TEMP_PATH
from services import logger
from utils.http_utils import AsyncHttpx
from utils.image_utils import BuildImage, text2image
from utils.manager import plugins_manager
from utils.utils import is_number
path = DATA_PATH / "plugin_shop"
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
repo_json_url = "https://github.com/zhenxun-org/nonebot_plugins_zhenxun_bot/archive/refs/heads/index.zip"
repo_zip_path = TEMP_PATH / "plugin_repo_json.zip"
plugin_json = path / "zhenxun_plugins.json"
extensive_plugin_path = Path() / "extensive_plugin"
path = DATA_PATH / "plugin_shop"
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
if not extensive_plugin_path.exists():
extensive_plugin_path.mkdir(parents=True, exist_ok=True)
data = {}
async def install_plugin(name: str) -> str:
"""
安装插件
:param name: 插件名或下标
"""
try:
if is_number(name):
name, code = await get_plugin_name(int(name))
if code != 200:
return name
plugin_url = data[name]["download_url"]
url = (await AsyncHttpx.get(plugin_url)).headers.get("Location")
zip_file = TEMP_PATH / f"{name}.zip"
if zip_file.exists():
zip_file.unlink()
if await AsyncHttpx.download_file(url, zip_file):
logger.debug("开始解压插件压缩包...", "安装插件", target=name)
# 解压
zf = zipfile.ZipFile(zip_file, "r")
extract_path = TEMP_PATH / f"{name}"
if extract_path.exists():
shutil.rmtree(extract_path.absolute(), ignore_errors=True)
extract_path.mkdir(exist_ok=True, parents=True)
for file in zf.namelist():
zf.extract(file, extract_path)
zf.close()
logger.debug("解压插件压缩包完成...", "安装插件", target=name)
logger.debug("开始移动插件文件夹...", "安装插件", target=name)
if (extensive_plugin_path / f"{name}").exists():
logger.debug(
"extensive_plugin目录下文件夹已存在,删除该目录插件文件夹...", "安装插件", target=name
)
shutil.rmtree(
(extensive_plugin_path / f"{name}").absolute(), ignore_errors=True
)
extract_path.rename(extensive_plugin_path / f"{name}")
prompt = ""
if "pyproject.toml" in os.listdir(extensive_plugin_path / f"{name}"):
prompt = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。"
os.system(
f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'pyproject.toml').absolute()}"
)
elif "requirements.txt" in os.listdir(extensive_plugin_path / f"{name}"):
prompt = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。"
os.system(
f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'requirements.txt').absolute()}"
)
with open(extensive_plugin_path / f"{name}" / "plugin_info.json", "w") as f:
json.dump(data[name], f, ensure_ascii=False, indent=4)
logger.debug("移动插件文件夹完成...", "安装插件", target=name)
logger.info(f"成功安装插件 {name} 成功!\n{prompt}", "安装插件", target=name)
return f"成功安装插件 {name},请重启真寻!"
except Exception as e:
logger.error(f"安装插失败", "安装插件", target=name, e=e)
return f"安装插件 {name} 失败 {type(e)}:{e}"
async def uninstall_plugin(name: str) -> str:
"""
删除插件
:param name: 插件名或下标
"""
try:
if is_number(name):
name, code = await get_plugin_name(int(name))
if code != 200:
return name
if name not in os.listdir(extensive_plugin_path):
return f"未安装 {name} 插件!"
shutil.rmtree((extensive_plugin_path / name).absolute(), ignore_errors=True)
logger.info(f"插件 {name} 删除成功!")
return f"插件 {name} 删除成功!"
except Exception as e:
logger.error(f"删除插件 {name} 失败 {type(e)}:{e}")
return f"删除插件 {name} 失败 {type(e)}:{e}"
async def show_plugin_repo() -> Union[int, str]:
"""
获取插件仓库数据并格式化
"""
if not plugin_json.exists():
code = await download_json()
if code != 200:
return code
plugin_info = json.load(open(plugin_json, "r", encoding="utf8"))
plugins_data = plugins_manager.get_data()
load_plugin_list = plugins_data.keys()
image_list = []
w, h = 0, 0
line_height = 10
for i, key in enumerate(plugin_info.keys()):
data[key] = {
"名称": plugin_info[key]["plugin_name"],
"模块": key,
"作者": plugin_info[key]["author"],
"版本": plugin_info[key]["version"],
"简介": plugin_info[key]["introduction"],
"download_url": plugin_info[key]["download_url"],
"github_url": plugin_info[key]["github_url"],
}
status = ""
version = ""
if key in load_plugin_list:
status = "<f font_color=#1a7e30>[已安装]</f>"
version = f"<f font_color=#1a7e30>[{plugins_data[key].version}]</f>"
s = (
f'id:{i+1}\n名称:{plugin_info[key]["plugin_name"]}'
f" \t\t{status}\n"
f"模块:{key}\n"
f'作者:{plugin_info[key]["author"]}\n'
f'版本:{plugin_info[key]["version"]} \t\t{version}\n'
f'简介:{plugin_info[key]["introduction"]}\n'
f"-------------------"
)
img = await text2image(s, font_size=20, color="#f9f6f2")
w = w if w > img.w else img.w
h += img.h + line_height
image_list.append(img)
A = BuildImage(w + 50, h + 50, color="#f9f6f2")
cur_h = 25
for img in image_list:
await A.apaste(img, (25, cur_h))
cur_h += img.h + line_height
return A.pic2bs4()
async def download_json() -> int:
"""
下载插件库json文件
"""
try:
url = (await AsyncHttpx.get(repo_json_url)).headers.get("Location")
if repo_zip_path.exists():
repo_zip_path.unlink()
if await AsyncHttpx.download_file(url, repo_zip_path):
zf = zipfile.ZipFile(repo_zip_path, "r")
extract_path = path / "temp"
for file in zf.namelist():
zf.extract(file, extract_path)
zf.close()
if plugin_json.exists():
plugin_json.unlink()
(
extract_path
/ "nonebot_plugins_zhenxun_bot-index"
/ "zhenxun_plugins.json"
).rename(plugin_json)
shutil.rmtree(extract_path.absolute(), ignore_errors=True)
return 200
except Exception as e:
logger.error(f"下载插件库压缩包失败或解压失败 {type(e)}:{e}")
return 999
async def get_plugin_name(name: int) -> Tuple[str, int]:
"""
通过下标获取插件名
:param name: 下标
"""
name = int(name)
if not data:
await show_plugin_repo()
if name < 1 or name > len(data.keys()):
return "下标超过上下限!", 999
name = list(data.keys())[name - 1]
return name, 200
-213
View File
@@ -1,213 +0,0 @@
import random
from asyncio.exceptions import TimeoutError
import nonebot
from nonebot.adapters.onebot.v11 import Bot
from nonebot.drivers import Driver
from configs.path_config import TEXT_PATH
from models.bag_user import BagUser
from models.group_info import GroupInfo
from services.log import logger
from utils.http_utils import AsyncHttpx
from utils.utils import GDict, scheduler
try:
import ujson as json
except ModuleNotFoundError:
import json
driver: Driver = nonebot.get_driver()
@driver.on_startup
async def update_city():
"""
部分插件需要中国省份城市
这里直接更新,避免插件内代码重复
"""
china_city = TEXT_PATH / "china_city.json"
data = {}
if not china_city.exists():
try:
logger.debug("开始更新城市列表...")
res = await AsyncHttpx.get(
"http://www.weather.com.cn/data/city3jdata/china.html", timeout=5
)
res.encoding = "utf8"
provinces_data = json.loads(res.text)
for province in provinces_data.keys():
data[provinces_data[province]] = []
res = await AsyncHttpx.get(
f"http://www.weather.com.cn/data/city3jdata/provshi/{province}.html",
timeout=5,
)
res.encoding = "utf8"
city_data = json.loads(res.text)
for city in city_data.keys():
data[provinces_data[province]].append(city_data[city])
with open(china_city, "w", encoding="utf8") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
logger.info("自动更新城市列表完成.....")
except TimeoutError as e:
logger.warning("自动更新城市列表超时...", e=e)
except ValueError as e:
logger.warning("自动城市列表失败.....", e=e)
except Exception as e:
logger.error(f"自动城市列表未知错误", e=e)
# @driver.on_startup
# async def _():
# """
# 数据库表结构变换
# """
# _flag = []
# sql_str = [
# (
# "ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;",
# "group_info",
# ), # group_info表添加一个group_flag
# (
# "ALTER TABLE bag_users rename belonging_group To group_id;",
# "bag_users",
# ), # 将 bag_users 的 belonging_group 改为 group_id
# (
# "ALTER TABLE group_info_users rename belonging_group To group_id;",
# "group_info_users",
# ),
# (
# "ALTER TABLE sign_group_users rename belonging_group To group_id;",
# "sign_group_users",
# ),
# (
# "ALTER TABLE open_cases_users rename belonging_group To group_id;",
# "open_cases_users",
# ),
# (
# "ALTER TABLE bag_users ADD property json NOT NULL DEFAULT '{}';",
# "bag_users",
# ), # bag_users 新增字段 property 替代 props
# (
# "ALTER TABLE genshin ADD auto_sign_time timestamp with time zone;",
# "genshin",
# ), # 新增原神自动签到字段
# (
# "ALTER TABLE genshin ADD resin_remind boolean DEFAULT False;",
# "genshin",
# ), # 新增原神自动签到字段
# (
# "ALTER TABLE genshin ADD resin_recovery_time timestamp with time zone;",
# "genshin",
# ), # 新增原神自动签到字段
# ("ALTER TABLE genshin ADD bind_group Integer;", "genshin"), # 新增原神群号绑定字段
# (
# "ALTER TABLE genshin ADD login_ticket VARCHAR(255) DEFAULT '';",
# "genshin",
# ), # 新增米游社login_ticket绑定字段
# (
# "ALTER TABLE genshin ADD stuid VARCHAR(255) DEFAULT '';",
# "genshin",
# ), # 新增米游社stuid绑定字段
# (
# "ALTER TABLE genshin ADD stoken VARCHAR(255) DEFAULT '';",
# "genshin",
# ), # 新增米游社stoken绑定字段
# ("ALTER TABLE chat_history ADD plain_text Text;", "chat_history"), # 新增纯文本
# (
# "ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;",
# "goods_info",
# ), # 新增纯文本
# (
# "ALTER TABLE goods_info ADD daily_purchase_limit Json DEFAULT '{}';",
# "goods_info",
# ), # 新增纯文本
# ]
# for sql in sql_str + GDict.get("run_sql", []):
# try:
# if isinstance(sql, str):
# flag = f"{random.randint(1, 10000)}"
# else:
# flag = sql[1]
# sql = sql[0]
# query = db.text(sql)
# await db.first(query)
# logger.info(f"完成sql操作:{sql}")
# _flag.append(flag)
# except (DuplicateColumnError, UndefinedColumnError):
# pass
# except PostgresSyntaxError:
# logger.error(f"语法错误:执行sql失败:{sql}")
# bag_user 将文本转为字典格式
# await __database_script(_flag)
@driver.on_bot_connect
async def _(bot: Bot):
"""
版本某些需要的变换
"""
# 清空不存在的群聊信息,并将已所有已存在的群聊group_flag设置为1(认证所有已存在的群)
if not await GroupInfo.get_or_none(group_id=114514):
# 标识符,该功能只需执行一次
await GroupInfo.create(
group_id=114514,
group_name="114514",
max_member_count=114514,
member_count=114514,
group_flag=1,
)
group_list = await bot.get_group_list()
group_list = [g["group_id"] for g in group_list]
_gl = [x.group_id for x in await GroupInfo.all()]
if 114514 in _gl:
_gl.remove(114514)
for group_id in _gl:
if group_id in group_list:
if group := await GroupInfo.get_or_none(group_id=group_id):
group.group_flag = 1
await group.save(update_fields=["group_flag"])
else:
group_info = await bot.get_group_info(group_id=group_id)
await GroupInfo.create(
group_id=group_info["group_id"],
group_name=group_info["group_name"],
max_member_count=group_info["max_member_count"],
member_count=group_info["member_count"],
group_flag=1,
)
logger.info(f"已添加群认证...", group_id=group_id)
else:
await GroupInfo.filter(group_id=group_id).delete()
logger.info(f"移除不存在的群聊信息", group_id=group_id)
# async def __database_script(_flag: List[str]):
# # bag_user 将文本转为字典格式
# if "bag_users" in _flag:
# for x in await BagUser.get_all_users():
# props = {}
# if x.props:
# for prop in [p for p in x.props.split(",") if p]:
# if props.get(prop):
# props[prop] += 1
# else:
# props[prop] = 1
# logger.info(
# f"__database_script USER {x.user_qq} GROUP {x.group_id} 更新数据 {props}"
# )
# await x.update(
# property=props,
# props="",
# ).apply()
# 自动更新城市列表
@scheduler.scheduled_job(
"cron",
hour=6,
minute=1,
)
async def _():
await update_city()
-27
View File
@@ -1,27 +0,0 @@
from pathlib import Path
import nonebot
from nonebot.drivers import Driver
from configs.config import Config
from utils.decorator.shop import shop_register
driver: Driver = nonebot.get_driver()
Config.add_plugin_config(
"shop",
"IMPORT_DEFAULT_SHOP_GOODS",
True,
help_="导入商店自带的三个商品",
default_value=True,
type=bool,
)
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
@driver.on_bot_connect
async def _():
await shop_register.load_register()
-109
View File
@@ -1,109 +0,0 @@
import time
from nonebot import on_command
from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message
from nonebot.adapters.onebot.v11.permission import GROUP
from nonebot.params import CommandArg
from models.bag_user import BagUser
from models.goods_info import GoodsInfo
from models.user_shop_gold_log import UserShopGoldLog
from services.log import logger
from utils.utils import is_number
__zx_plugin_name__ = "商店 - 购买道具"
__plugin_usage__ = """
usage:
购买道具
指令:
购买 [序号或名称] ?[数量=1]
示例:购买 好感双倍加持卡Ⅰ
示例:购买 1 4
""".strip()
__plugin_des__ = "商店 - 购买道具"
__plugin_cmd__ = ["购买 [序号或名称] ?[数量=1]"]
__plugin_type__ = ("商店",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["商店", "购买道具"],
}
__plugin_cd_limit__ = {"cd": 3}
buy = on_command("购买", aliases={"购买道具"}, priority=5, block=True, permission=GROUP)
@buy.handle()
async def _(event: GroupMessageEvent, arg: Message = CommandArg()):
goods = None
if arg.extract_plain_text().strip() in ["神秘药水"]:
await buy.finish("你们看看就好啦,这是不可能卖给你们的~", at_sender=True)
goods_list = [
x
for x in await GoodsInfo.get_all_goods()
if x.goods_limit_time > time.time() or x.goods_limit_time == 0
]
goods_name_list = [x.goods_name for x in goods_list]
msg = arg.extract_plain_text().strip().split()
num = 1
if len(msg) > 1:
if is_number(msg[1]) and int(msg[1]) > 0:
num = int(msg[1])
else:
await buy.finish("购买的数量要是数字且大于0!", at_sender=True)
if is_number(msg[0]):
msg = int(msg[0])
if msg > len(goods_name_list) or msg < 1:
await buy.finish("请输入正确的商品id!", at_sender=True)
goods = goods_list[msg - 1]
else:
if msg[0] in goods_name_list:
for i in range(len(goods_name_list)):
if msg[0] == goods_name_list[i]:
goods = goods_list[i]
break
else:
await buy.finish("请输入正确的商品名称!")
else:
await buy.finish("请输入正确的商品名称!", at_sender=True)
if (
await BagUser.get_gold(event.user_id, event.group_id)
) < goods.goods_price * num * goods.goods_discount:
await buy.finish("您的金币好像不太够哦", at_sender=True)
flag, n = await GoodsInfo.check_user_daily_purchase(
goods, event.user_id, event.group_id, num
)
if flag:
await buy.finish(f"该次购买将超过每日次数限制,目前该道具还可以购买{n}次哦", at_sender=True)
spend_gold = int(goods.goods_discount * goods.goods_price * num)
await BagUser.spend_gold(event.user_id, event.group_id, spend_gold)
await BagUser.add_property(event.user_id, event.group_id, goods.goods_name, num)
await GoodsInfo.add_user_daily_purchase(goods, event.user_id, event.group_id, num)
await buy.send(
f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 成功!",
at_sender=True,
)
logger.info(
f"花费 {goods.goods_price*num} 金币购买 {goods.goods_name} ×{num} 成功!",
"购买道具",
event.user_id,
event.group_id,
)
await UserShopGoldLog.create(
user_qq=event.user_id,
group_id=event.group_id,
type=0,
name=goods.goods_name,
num=num,
spend_gold=goods.goods_price * num * goods.goods_discount,
)
# else:
# await buy.send(f"{goods.goods_name} 购买失败!", at_sender=True)
# logger.info(
# f"USER {event.user_id} GROUP {event.group_id} "
# f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 失败!"
# )
-62
View File
@@ -1,62 +0,0 @@
from nonebot import on_command
from nonebot.adapters.onebot.v11 import ActionFailed, GroupMessageEvent, Message
from nonebot.adapters.onebot.v11.permission import GROUP
from nonebot.params import CommandArg
from models.bag_user import BagUser
from utils.data_utils import init_rank
from utils.image_utils import text2image
from utils.message_builder import image
from utils.utils import is_number
__zx_plugin_name__ = "商店 - 我的金币"
__plugin_usage__ = """
usage:
我的金币
指令:
我的金币
""".strip()
__plugin_des__ = "商店 - 我的金币"
__plugin_cmd__ = ["我的金币"]
__plugin_type__ = ("商店",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["商店", "我的金币"],
}
my_gold = on_command("我的金币", priority=5, block=True, permission=GROUP)
gold_rank = on_command("金币排行", priority=5, block=True, permission=GROUP)
@my_gold.handle()
async def _(event: GroupMessageEvent):
msg = await BagUser.get_user_total_gold(event.user_id, event.group_id)
try:
await my_gold.send(msg)
except ActionFailed:
await my_gold.send(
image(b64=(await text2image(msg, color="#f9f6f2", padding=10)).pic2bs4())
)
@gold_rank.handle()
async def _(event: GroupMessageEvent, arg: Message = CommandArg()):
num = arg.extract_plain_text().strip()
if is_number(num) and 51 > int(num) > 10:
num = int(num)
else:
num = 10
all_users = await BagUser.filter(group_id=event.group_id)
all_user_id = [user.user_qq for user in all_users]
all_user_data = [user.gold for user in all_users]
rank_image = await init_rank(
"金币排行", all_user_id, all_user_data, event.group_id, num
)
if rank_image:
await gold_rank.finish(image(b64=rank_image.pic2bs4()))
-41
View File
@@ -1,41 +0,0 @@
from nonebot import on_command
from nonebot.adapters.onebot.v11 import GroupMessageEvent
from nonebot.adapters.onebot.v11.permission import GROUP
from models.bag_user import BagUser
from services.log import logger
from utils.message_builder import image
from ._data_source import create_bag_image
__zx_plugin_name__ = "商店 - 我的道具"
__plugin_usage__ = """
usage:
我的道具
指令:
我的道具
""".strip()
__plugin_des__ = "商店 - 我的道具"
__plugin_cmd__ = ["我的道具"]
__plugin_type__ = ("商店",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["商店", "我的道具"],
}
my_props = on_command("我的道具", priority=5, block=True, permission=GROUP)
@my_props.handle()
async def _(event: GroupMessageEvent):
props = await BagUser.get_property(event.user_id, event.group_id)
if props:
await my_props.send(image(b64=await create_bag_image(props)))
logger.info(f"USER {event.user_id} GROUP {event.group_id} 查看我的道具")
else:
await my_props.finish("您的背包里没有任何的道具噢~", at_sender=True)
-121
View File
@@ -1,121 +0,0 @@
from typing import Dict, List, Optional
from models.bag_user import BagUser
from models.goods_info import GoodsInfo
from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
icon_path = IMAGE_PATH / "shop_icon"
async def create_bag_image(props: Dict[str, int]):
"""
说明:
创建背包道具图片
参数:
:param props: 道具仓库字典
"""
goods_list = await GoodsInfo.get_all_goods()
active_props = await _init_prop(props, [x for x in goods_list if not x.is_passive])
passive_props = await _init_prop(props, [x for x in goods_list if x.is_passive])
active_w = 0
active_h = 0
passive_w = 0
passive_h = 0
if active_props:
img = BuildImage(
active_props.w,
active_props.h + 70,
font="CJGaoDeGuo.otf",
font_size=30,
color="#f9f6f2",
)
await img.apaste(active_props, (0, 70))
await img.atext((0, 30), "主动道具")
active_props = img
active_w = img.w
active_h = img.h
if passive_props:
img = BuildImage(
passive_props.w,
passive_props.h + 70,
font="CJGaoDeGuo.otf",
font_size=30,
color="#f9f6f2",
)
await img.apaste(passive_props, (0, 70))
await img.atext((0, 30), "被动道具")
passive_props = img
passive_w = img.w
passive_h = img.h
A = BuildImage(
active_w + passive_w + 100,
max(active_h, passive_h) + 60,
font="CJGaoDeGuo.otf",
font_size=30,
color="#f9f6f2",
)
curr_w = 50
if active_props:
await A.apaste(active_props, (curr_w, 0))
curr_w += active_props.w + 10
if passive_props:
await A.apaste(passive_props, (curr_w, 0))
if active_props and passive_props:
await A.aline(
(active_props.w + 45, 70, active_props.w + 45, A.h - 20), fill=(0, 0, 0)
)
return A.pic2bs4()
async def _init_prop(
props: Dict[str, int], _props: List[GoodsInfo]
) -> Optional[BuildImage]:
"""
说明:
构造道具列表图片
参数:
:param props: 道具仓库字典
:param _props: 道具列表
"""
active_name = [x.goods_name for x in _props]
name_list = [x for x in props.keys() if x in active_name]
if not name_list:
return None
temp_img = BuildImage(0, 0, font_size=20)
image_list = []
num_list = []
for i, name in enumerate(name_list):
img = BuildImage(
temp_img.getsize(name)[0] + 50,
30,
font="msyh.ttf",
font_size=20,
color="#f9f6f2",
)
await img.atext((30, 5), f"{i + 1}.{name}")
goods = [x for x in _props if x.goods_name == name][0]
if goods.icon and (icon_path / goods.icon).exists():
icon = BuildImage(30, 30, background=icon_path / goods.icon)
await img.apaste(icon, alpha=True)
image_list.append(img)
num_list.append(
BuildImage(
30, 30, font_size=20, font="msyh.ttf", plain_text=f"×{props[name]}"
)
)
max_w = 0
num_max_w = 0
h = 0
for img, num in zip(image_list, num_list):
h += img.h
max_w = max_w if max_w > img.w else img.w
num_max_w = num_max_w if num_max_w > num.w else num.w
A = BuildImage(max_w + num_max_w + 30, h, color="#f9f6f2")
curr_h = 0
for img, num in zip(image_list, num_list):
await A.apaste(img, (0, curr_h))
await A.apaste(num, (max_w + 20, curr_h + 5), True)
curr_h += img.h
return A
-162
View File
@@ -1,162 +0,0 @@
import os
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Message, MessageEvent
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from configs.path_config import IMAGE_PATH
from models.bag_user import BagUser
from services.log import logger
from utils.message_builder import image
from utils.utils import is_number, scheduler
from .data_source import (
GoodsInfo,
create_shop_help,
delete_goods,
parse_goods_info,
register_goods,
update_goods,
)
__zx_plugin_name__ = "商店"
__plugin_usage__ = """
usage:
商店项目,这可不是奸商
指令:
商店
""".strip()
__plugin_superuser_usage__ = """
usage:
商品操作
指令:
添加商品 name:[名称] price:[价格] des:[描述] ?discount:[折扣](小数) ?limit_time:[限时时间](小时)
删除商品 [名称或序号]
修改商品 name:[名称或序号] price:[价格] des:[描述] discount:[折扣] limit_time:[限时]
示例:添加商品 name:萝莉酒杯 price:9999 des:普通的酒杯,但是里面.. discount:0.4 limit_time:90
示例:添加商品 name:可疑的药 price:5 des:效果未知
示例:删除商品 2
示例:修改商品 name:1 price:900 修改序号为1的商品的价格为900
* 修改商品只需添加需要值即可 *
""".strip()
__plugin_des__ = "商店系统[金币回收计划]"
__plugin_cmd__ = [
"商店",
"添加商品 name:[名称] price:[价格] des:[描述] ?discount:[折扣](小数) ?limit_time:[限时时间](小时)) [_superuser]",
"删除商品 [名称或序号] [_superuser]",
"修改商品 name:[名称或序号] price:[价格] des:[描述] discount:[折扣] limit_time:[限时] [_superuser]",
]
__plugin_type__ = ("商店",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["商店"],
}
__plugin_block_limit__ = {"limit_type": "group"}
shop_help = on_command("商店", priority=5, block=True)
shop_add_goods = on_command("添加商品", priority=5, permission=SUPERUSER, block=True)
shop_del_goods = on_command("删除商品", priority=5, permission=SUPERUSER, block=True)
shop_update_goods = on_command("修改商品", priority=5, permission=SUPERUSER, block=True)
@shop_help.handle()
async def _():
await shop_help.send(image(b64=await create_shop_help()))
@shop_add_goods.handle()
async def _(event: MessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip()
if msg:
data = parse_goods_info(msg)
if isinstance(data, str):
await shop_add_goods.finish(data)
if not data.get("name") or not data.get("price") or not data.get("des"):
await shop_add_goods.finish("name:price:des 参数不可缺少!")
if await register_goods(**data):
await shop_add_goods.send(
f"添加商品 {data['name']} 成功!\n"
f"名称:{data['name']}\n"
f"价格:{data['price']}金币\n"
f"简介:{data['des']}\n"
f"折扣:{data.get('discount')}\n"
f"限时:{data.get('limit_time')}",
at_sender=True,
)
logger.info(f"USER {event.user_id} 添加商品 {msg} 成功")
else:
await shop_add_goods.send(f"添加商品 {msg} 失败了...", at_sender=True)
logger.warning(f"USER {event.user_id} 添加商品 {msg} 失败")
@shop_del_goods.handle()
async def _(event: MessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip()
if msg:
name = ""
id_ = 0
if is_number(msg):
id_ = int(msg)
else:
name = msg
rst, goods_name, code = await delete_goods(name, id_)
if code == 200:
await shop_del_goods.send(f"删除商品 {goods_name} 成功了...", at_sender=True)
if os.path.exists(f"{IMAGE_PATH}/shop_help.png"):
os.remove(f"{IMAGE_PATH}/shop_help.png")
logger.info(f"USER {event.user_id} 删除商品 {goods_name} 成功")
else:
await shop_del_goods.send(f"删除商品 {goods_name} 失败了...", at_sender=True)
logger.info(f"USER {event.user_id} 删除商品 {goods_name} 失败")
@shop_update_goods.handle()
async def _(event: MessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip()
if msg:
data = parse_goods_info(msg)
if isinstance(data, str):
await shop_add_goods.finish(data)
if not data.get("name"):
await shop_add_goods.finish("name 参数不可缺少!")
flag, name, text = await update_goods(**data)
if flag:
await shop_update_goods.send(f"修改商品 {name} 成功了...\n{text}", at_sender=True)
logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 成功")
else:
await shop_update_goods.send(name, at_sender=True)
logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 失败")
@scheduler.scheduled_job(
"cron",
hour=0,
minute=0,
)
async def _():
try:
await GoodsInfo.all().update(daily_purchase_limit={})
logger.info("商品每日限购次数重置成功...")
except Exception as e:
logger.error(f"商品每日限购次数重置出错 {type(e)}:{e}")
@scheduler.scheduled_job(
"cron",
hour=0,
minute=1,
)
async def _():
try:
await BagUser.all().update(get_today_gold=0, spend_today_gold=0)
except Exception as e:
logger.error(f"重置每日金币", "定时任务", e=e)
@@ -1,421 +0,0 @@
import time
from typing import Optional, Tuple, Union
from PIL import Image
from configs.path_config import IMAGE_PATH
from models.goods_info import GoodsInfo
from utils.image_utils import BuildImage, text2image
from utils.utils import GDict, is_number
icon_path = IMAGE_PATH / "shop_icon"
GDict["run_sql"].append("ALTER TABLE goods_info ADD is_passive boolean DEFAULT False;")
GDict["run_sql"].append("ALTER TABLE goods_info ADD icon VARCHAR(255);")
# 创建商店界面
async def create_shop_help() -> str:
"""
制作商店图片
:return: 图片base64
"""
goods_lst = await GoodsInfo.get_all_goods()
_dc = {}
font_h = BuildImage(0, 0).getsize("正")[1]
h = 10
_list = []
for goods in goods_lst:
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time:
_list.append(goods)
# A = BuildImage(1100, h, color="#f9f6f2")
total_n = 0
image_list = []
for idx, goods in enumerate(_list):
name_image = BuildImage(
580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf"
)
await name_image.atext(
(15, 0), f"{idx + 1}.{goods.goods_name}", center_type="by_height"
)
await name_image.aline((380, -5, 280, 45), "#a29ad6", 5)
await name_image.atext((390, 0), "售价:", center_type="by_height")
if goods.goods_discount != 1:
discount_price = int(goods.goods_discount * goods.goods_price)
old_price_image = BuildImage(
0,
0,
plain_text=str(goods.goods_price),
font_color=(194, 194, 194),
font="CJGaoDeGuo.otf",
font_size=15,
)
await old_price_image.aline(
(
0,
int(old_price_image.h / 2),
old_price_image.w + 1,
int(old_price_image.h / 2),
),
(0, 0, 0),
)
await name_image.apaste(old_price_image, (440, 0), True)
await name_image.atext((440, 15), str(discount_price), (255, 255, 255))
else:
await name_image.atext(
(440, 0),
str(goods.goods_price),
(255, 255, 255),
center_type="by_height",
)
await name_image.atext(
(
440
+ BuildImage(0, 0, plain_text=str(goods.goods_price), font_size=25).w,
0,
),
f" 金币",
center_type="by_height",
)
des_image = None
font_img = BuildImage(
600, 80, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf"
)
p = font_img.getsize("简介:")[0] + 20
if goods.goods_description:
des_list = goods.goods_description.split("\n")
desc = ""
for des in des_list:
if font_img.getsize(des)[0] > font_img.w - p - 20:
msg = ""
tmp = ""
for i in range(len(des)):
if font_img.getsize(tmp)[0] < font_img.w - p - 20:
tmp += des[i]
else:
msg += tmp + "\n"
tmp = des[i]
desc += msg
if tmp:
desc += tmp
else:
desc += des + "\n"
if desc[-1] == "\n":
desc = desc[:-1]
des_image = await text2image(desc, color="#a29ad6")
goods_image = BuildImage(
600,
(50 + des_image.h) if des_image else 50,
font_size=20,
color="#a29ad6",
font="CJGaoDeGuo.otf",
)
if des_image:
await goods_image.atext((15, 50), "简介:")
await goods_image.apaste(des_image, (p, 50))
await name_image.acircle_corner(5)
await goods_image.apaste(name_image, (0, 5), True, center_type="by_width")
await goods_image.acircle_corner(20)
bk = BuildImage(
1180,
(50 + des_image.h) if des_image else 50,
font_size=15,
color="#f9f6f2",
font="CJGaoDeGuo.otf",
)
if goods.icon and (icon_path / goods.icon).exists():
icon = BuildImage(70, 70, background=icon_path / goods.icon)
await bk.apaste(icon)
await bk.apaste(goods_image, (70, 0), alpha=True)
n = 0
_w = 650
# 添加限时图标和时间
if goods.goods_limit_time > 0:
n += 140
_limit_time_logo = BuildImage(
40, 40, background=f"{IMAGE_PATH}/other/time.png"
)
await bk.apaste(_limit_time_logo, (_w + 50, 0), True)
await bk.apaste(
BuildImage(0, 0, plain_text="限时!", font_size=23, font="CJGaoDeGuo.otf"),
(_w + 90, 10),
True,
)
limit_time = time.strftime(
"%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time)
).split()
y_m_d = limit_time[0]
_h_m = limit_time[1].split(":")
h_m = _h_m[0] + "时 " + _h_m[1] + "分"
await bk.atext((_w + 55, 38), str(y_m_d))
await bk.atext((_w + 65, 57), str(h_m))
_w += 140
if goods.goods_discount != 1:
n += 140
_discount_logo = BuildImage(
30, 30, background=f"{IMAGE_PATH}/other/discount.png"
)
await bk.apaste(_discount_logo, (_w + 50, 10), True)
await bk.apaste(
BuildImage(0, 0, plain_text="折扣!", font_size=23, font="CJGaoDeGuo.otf"),
(_w + 90, 15),
True,
)
await bk.apaste(
BuildImage(
0,
0,
plain_text=f"{10 * goods.goods_discount:.1f} 折",
font_size=30,
font="CJGaoDeGuo.otf",
font_color=(85, 156, 75),
),
(_w + 50, 44),
True,
)
_w += 140
if goods.daily_limit != 0:
n += 140
_daily_limit_logo = BuildImage(
35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png"
)
await bk.apaste(_daily_limit_logo, (_w + 50, 10), True)
await bk.apaste(
BuildImage(0, 0, plain_text="限购!", font_size=23, font="CJGaoDeGuo.otf"),
(_w + 90, 20),
True,
)
await bk.apaste(
BuildImage(
0,
0,
plain_text=f"{goods.daily_limit}",
font_size=30,
font="CJGaoDeGuo.otf",
),
(_w + 72, 45),
True,
)
if total_n < n:
total_n = n
if n:
await bk.aline((650, -1, 650 + n, -1), "#a29ad6", 5)
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
# 添加限时图标和时间
image_list.append(bk)
# await A.apaste(bk, (0, current_h), True)
# current_h += 90
h = 0
current_h = 0
for img in image_list:
h += img.h + 10
A = BuildImage(1100, h, color="#f9f6f2")
for img in image_list:
await A.apaste(img, (0, current_h), True)
current_h += img.h + 10
w = 950
if total_n:
w += total_n
h = A.h + 230 + 100
h = 1000 if h < 1000 else h
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
zx_img = BuildImage(0, 0, background=f"{IMAGE_PATH}/zhenxun/toukan_3.png")
zx_img.transpose(Image.FLIP_LEFT_RIGHT)
zx_img.replace_color_tran(((240, 240, 240), (255, 255, 255)), (249, 246, 242))
await shop.apaste(zx_img, (0, 100))
shop.paste(A, (20 + zx_img.w, 230))
await shop.apaste(shop_logo, (450, 30), True)
shop.text(
(int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2), 170),
"注【通过 序号 或者 商品名称 购买】",
)
shop.text((20 + zx_img.w, h - 100), "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~")
return shop.pic2bs4()
async def register_goods(
name: str,
price: int,
des: str,
discount: Optional[float] = 1,
limit_time: Optional[int] = 0,
daily_limit: Optional[int] = 0,
is_passive: Optional[bool] = False,
icon: Optional[str] = None,
) -> bool:
"""
添加商品
例如: 折扣:可选参数↓ 限时时间:可选,单位为小时
添加商品 name:萝莉酒杯 price:9999 des:普通的酒杯,但是里面.. discount:0.4 limit_time:90
添加商品 name:可疑的药 price:5 des:效果未知
:param name: 商品名称
:param price: 商品价格
:param des: 商品简介
:param discount: 商品折扣
:param limit_time: 商品限时销售时间,单位为小时
:param daily_limit: 每日购买次数限制
:param is_passive: 是否为被动
:param icon: 图标
:return: 是否添加成功
"""
if not await GoodsInfo.get_or_none(goods_name=name):
limit_time_ = float(limit_time) if limit_time else limit_time
discount = discount if discount is not None else 1
limit_time_ = (
int(time.time() + limit_time_ * 60 * 60)
if limit_time_ is not None and limit_time_ != 0
else 0
)
await GoodsInfo.create(
goods_name=name,
goods_price=int(price),
goods_description=des,
goods_discount=float(discount),
goods_limit_time=limit_time_,
daily_limit=daily_limit,
is_passive=is_passive,
icon=icon,
)
return True
return False
# 删除商品
async def delete_goods(name: str, id_: int) -> Tuple[str, str, int]:
"""
删除商品
:param name: 商品名称
:param id_: 商品id
:return: 删除状况
"""
goods_lst = await GoodsInfo.get_all_goods()
if id_:
if id_ < 1 or id_ > len(goods_lst):
return "序号错误,没有该序号商品...", "", 999
goods_name = goods_lst[id_ - 1].goods_name
if await GoodsInfo.delete_goods(goods_name):
return f"删除商品 {goods_name} 成功!", goods_name, 200
else:
return f"删除商品 {goods_name} 失败!", goods_name, 999
if name:
if await GoodsInfo.delete_goods(name):
return f"删除商品 {name} 成功!", name, 200
else:
return f"删除商品 {name} 失败!", name, 999
return "获取商品失败", "", 999
# 更新商品信息
async def update_goods(**kwargs) -> Tuple[bool, str, str]:
"""
更新商品信息
:param kwargs: kwargs
:return: 更新状况
"""
if kwargs:
goods_lst = await GoodsInfo.get_all_goods()
if is_number(kwargs["name"]):
if int(kwargs["name"]) < 1 or int(kwargs["name"]) > len(goods_lst):
return False, "序号错误,没有该序号的商品...", ""
goods = goods_lst[int(kwargs["name"]) - 1]
else:
goods = await GoodsInfo.filter(goods_name=kwargs["name"]).first()
if not goods:
return False, "名称错误,没有该名称的商品...", ""
name: str = goods.goods_name
price = goods.goods_price
des = goods.goods_description
discount = goods.goods_discount
limit_time = goods.goods_limit_time
daily_limit = goods.daily_limit
is_passive = goods.is_passive
new_time = 0
tmp = ""
if kwargs.get("price"):
tmp += f'价格:{price} --> {kwargs["price"]}\n'
price = kwargs["price"]
if kwargs.get("des"):
tmp += f'描述:{des} --> {kwargs["des"]}\n'
des = kwargs["des"]
if kwargs.get("discount"):
tmp += f'折扣:{discount} --> {kwargs["discount"]}\n'
discount = kwargs["discount"]
if kwargs.get("limit_time"):
kwargs["limit_time"] = float(kwargs["limit_time"])
new_time = (
time.strftime(
"%Y-%m-%d %H:%M:%S",
time.localtime(time.time() + kwargs["limit_time"] * 60 * 60),
)
if kwargs["limit_time"] != 0
else 0
)
tmp += f"限时至: {new_time}\n" if new_time else "取消了限时\n"
limit_time = kwargs["limit_time"]
if kwargs.get("daily_limit"):
tmp += (
f'每日购买限制:{daily_limit} --> {kwargs["daily_limit"]}\n'
if daily_limit
else "取消了购买限制\n"
)
daily_limit = int(kwargs["daily_limit"])
if kwargs.get("is_passive"):
tmp += f'被动道具:{is_passive} --> {kwargs["is_passive"]}\n'
des = kwargs["is_passive"]
await GoodsInfo.update_goods(
name,
int(price),
des,
float(discount),
int(
time.time() + limit_time * 60 * 60
if limit_time != 0 and new_time
else 0
),
daily_limit,
is_passive,
)
return (
True,
name,
tmp[:-1],
)
def parse_goods_info(msg: str) -> Union[dict, str]:
"""
解析格式数据
:param msg: 消息
:return: 解析完毕的数据data
"""
if "name:" not in msg:
return "必须指定修改的商品名称或序号!"
data = {}
for x in msg.split():
sp = x.split(":", maxsplit=1)
if str(sp[1]).strip():
sp[1] = sp[1].strip()
if sp[0] == "name":
data["name"] = sp[1]
elif sp[0] == "price":
if not is_number(sp[1]) or int(sp[1]) < 0:
return "price参数不合法,必须大于等于0!"
data["price"] = sp[1]
elif sp[0] == "des":
data["des"] = sp[1]
elif sp[0] == "discount":
if not is_number(sp[1]) or float(sp[1]) < 0:
return "discount参数不合法,必须大于0!"
data["discount"] = sp[1]
elif sp[0] == "limit_time":
if not is_number(sp[1]) or float(sp[1]) < 0:
return "limit_time参数不合法,必须为数字且大于0!"
data["limit_time"] = sp[1]
elif sp[0] == "daily_limit":
if not is_number(sp[1]) or float(sp[1]) < 0:
return "daily_limit参数不合法,必须为数字且大于0!"
data["daily_limit"] = sp[1]
return data
-117
View File
@@ -1,117 +0,0 @@
from typing import Any, Tuple
from nonebot import on_command, on_regex
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message
from nonebot.adapters.onebot.v11.permission import GROUP
from nonebot.params import CommandArg, RegexGroup
from models.bag_user import BagUser
from models.user_shop_gold_log import UserShopGoldLog
from services.log import logger
from utils.decorator.shop import NotMeetUseConditionsException
from utils.utils import is_number
from .data_source import build_params, effect, func_manager, register_use
__zx_plugin_name__ = "商店 - 使用道具"
__plugin_usage__ = """
usage:
普通的使用道具
指令:
使用道具 [序号或道具名称] ?[数量]=1 ?[其他信息]
示例:使用道具好感度双倍加持卡 使用道具好感度双倍加持卡
示例:使用道具1 使用第一个道具
示例:使用道具1 10 使用10个第一个道具
示例:使用道具1 1 来点色图 使用第一个道具并附带信息
* 序号以 ”我的道具“ 为准 *
""".strip()
__plugin_des__ = "商店 - 使用道具"
__plugin_cmd__ = ["使用道具 [序号或道具名称]"]
__plugin_type__ = ("商店",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["商店", "使用道具"],
}
use_props = on_command(r"使用道具", priority=5, block=True, permission=GROUP)
@use_props.handle()
async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text()
num = 1
text = ""
prop_n = None
index = None
split = msg.split()
if size := len(split):
if size == 1:
prop_n = split[0].strip()
index = 1
if size > 1 and is_number(split[1].strip()):
prop_n = split[0].strip()
num = int(split[1].strip())
index = 2
else:
await use_props.finish("缺少参数,请查看帮助", at_sender=True)
if index:
text = " ".join(split[index:])
property_ = await BagUser.get_property(event.user_id, event.group_id, True)
if property_:
name = None
if prop_n and is_number(prop_n):
if 0 < int(prop_n) <= len(property_):
name = list(property_.keys())[int(prop_n) - 1]
else:
await use_props.finish("仔细看看自己的道具仓库有没有这个道具?", at_sender=True)
else:
if prop_n not in property_.keys():
await use_props.finish("道具名称错误!", at_sender=True)
name = prop_n
if not name:
await use_props.finish("未获取到道具名称", at_sender=True)
_user_prop_count = property_[name]
if num > _user_prop_count:
await use_props.finish(f"道具数量不足,无法使用{num}次!")
if num > (n := func_manager.get_max_num_limit(name)):
await use_props.finish(f"该道具单次只能使用 {n} 个!")
try:
model, kwargs = build_params(bot, event, name, num, text)
except KeyError:
logger.warning(f"{name} 未注册使用函数")
await use_props.finish(f"{name} 未注册使用方法")
else:
try:
await func_manager.run_handle(
type_="before_handle", param=model, **kwargs
)
except NotMeetUseConditionsException as e:
await use_props.finish(e.get_info(), at_sender=True)
if await BagUser.delete_property(event.user_id, event.group_id, name, num):
if func_manager.check_send_success_message(name):
await use_props.send(f"使用道具 {name} {num} 次成功!", at_sender=True)
if msg := await effect(bot, event, name, num, text, event.message):
await use_props.send(msg, at_sender=True)
logger.info(
f"USER {event.user_id} GROUP {event.group_id} 使用道具 {name} {num} 次成功"
)
await UserShopGoldLog.create(
user_qq=event.user_id,
group_id=event.group_id,
type=1,
name=name,
num=num,
)
else:
await use_props.send(f"使用道具 {name} {num} 次失败!", at_sender=True)
logger.info(
f"USER {event.user_id} GROUP {event.group_id} 使用道具 {name} {num} 次失败"
)
await func_manager.run_handle(type_="after_handle", param=model, **kwargs)
else:
await use_props.send("您的背包里没有任何的道具噢", at_sender=True)
-235
View File
@@ -1,235 +0,0 @@
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageSegment, Message
from services.log import logger
from nonebot.adapters.onebot.v11 import Bot
from pydantic import create_model
from utils.models import ShopParam
from typing import Optional, Union, Callable, List, Tuple, Dict, Any
from types import MappingProxyType
import inspect
import asyncio
class GoodsUseFuncManager:
def __init__(self):
self._data = {}
def register_use_before_handle(self, goods_name: str, fun_list: List[Callable]):
"""
说明:
注册商品使用前函数
参数:
:param goods_name: 商品名称
:param fun_list: 函数列表
"""
if fun_list:
self._data[goods_name]["before_handle"] = fun_list
logger.info(f"register_use_before_handle 成功注册商品:{goods_name} 的{len(fun_list)}个使用前函数")
def register_use_after_handle(self, goods_name: str, fun_list: List[Callable]):
"""
说明:
注册商品使用后函数
参数:
:param goods_name: 商品名称
:param fun_list: 函数列表
"""
if fun_list:
self._data[goods_name]["after_handle"] = fun_list
logger.info(f"register_use_after_handle 成功注册商品:{goods_name} 的{len(fun_list)}个使用后函数")
def register_use(self, goods_name: str, **kwargs):
"""
注册商品使用方法
:param goods_name: 商品名称
:param kwargs: kwargs
"""
self._data[goods_name] = kwargs
def exists(self, goods_name: str) -> bool:
"""
判断商品使用方法是否被注册
:param goods_name: 商品名称
"""
return bool(self._data.get(goods_name))
def get_max_num_limit(self, goods_name: str) -> int:
"""
获取单次商品使用数量
:param goods_name: 商品名称
"""
if self.exists(goods_name):
return self._data[goods_name]["kwargs"]["max_num_limit"]
return 1
def _parse_args(self, args_: MappingProxyType, param: ShopParam, **kwargs):
param_list_ = []
_bot = param.bot
param.bot = None
param_json = param.dict()
param_json["bot"] = _bot
for par in args_.keys():
if par in ["shop_param"]:
param_list_.append(param)
elif par not in ["args", "kwargs"]:
param_list_.append(param_json.get(par))
if kwargs.get(par) is not None:
del kwargs[par]
return param_list_
async def use(
self, param: ShopParam, **kwargs
) -> Optional[Union[str, MessageSegment]]:
"""
使用道具
:param param: BaseModel
:param kwargs: kwargs
"""
goods_name = param.goods_name
if self.exists(goods_name):
# 使用方法
args = inspect.signature(self._data[goods_name]["func"]).parameters
if args and list(args.keys())[0] != "kwargs":
if asyncio.iscoroutinefunction(self._data[goods_name]["func"]):
return await self._data[goods_name]["func"](
*self._parse_args(args, param, **kwargs)
)
else:
return self._data[goods_name]["func"](
*self._parse_args(args, param, **kwargs)
)
else:
if asyncio.iscoroutinefunction(self._data[goods_name]["func"]):
return await self._data[goods_name]["func"](
**kwargs,
)
else:
return self._data[goods_name]["func"](
**kwargs,
)
async def run_handle(self, goods_name: str, type_: str, param: ShopParam, **kwargs):
if self._data.get(goods_name) and self._data[goods_name].get(type_):
for func in self._data[goods_name].get(type_):
args = inspect.signature(func).parameters
if args and list(args.keys())[0] != "kwargs":
if asyncio.iscoroutinefunction(func):
await func(*self._parse_args(args, param, **kwargs))
else:
func(*self._parse_args(args, param, **kwargs))
else:
if asyncio.iscoroutinefunction(func):
await func(**kwargs)
else:
func(**kwargs)
def check_send_success_message(self, goods_name: str) -> bool:
"""
检查是否发送使用成功信息
:param goods_name: 商品名称
"""
if self.exists(goods_name):
return bool(self._data[goods_name]["kwargs"]["send_success_msg"])
return False
def get_kwargs(self, goods_name: str) -> dict:
"""
获取商品使用方法的kwargs
:param goods_name: 商品名称
"""
if self.exists(goods_name):
return self._data[goods_name]["kwargs"]
return {}
def init_model(self, goods_name: str, bot: Bot, event: GroupMessageEvent, num: int, text: str):
return self._data[goods_name]["model"](
**{
"goods_name": goods_name,
"bot": bot,
"event": event,
"user_id": event.user_id,
"group_id": event.group_id,
"num": num,
"message": event.message,
"text": text
}
)
func_manager = GoodsUseFuncManager()
def build_params(
bot: Bot, event: GroupMessageEvent, goods_name: str, num: int, text: str
) -> Tuple[ShopParam, Dict[str, Any]]:
"""
说明:
构造参数
参数:
:param bot: bot
:param event: event
:param goods_name: 商品名称
:param num: 数量
:param text: 其他信息
"""
_kwargs = func_manager.get_kwargs(goods_name)
return (
func_manager.init_model(goods_name, bot, event, num, text),
{
**_kwargs,
"_bot": bot,
"event": event,
"group_id": event.group_id,
"user_id": event.user_id,
"num": num,
"text": text,
"message": event.message,
"goods_name": goods_name,
},
)
async def effect(
bot: Bot, event: GroupMessageEvent, goods_name: str, num: int, text: str, message: Message
) -> Optional[Union[str, MessageSegment]]:
"""
商品生效
:param bot: Bot
:param event: GroupMessageEvent
:param goods_name: 商品名称
:param num: 使用数量
:param text: 其他信息
:param message: Message
:return: 使用是否成功
"""
# 优先使用注册的商品插件
# try:
if func_manager.exists(goods_name):
_kwargs = func_manager.get_kwargs(goods_name)
model, kwargs = build_params(bot, event, goods_name, num, text)
return await func_manager.use(model, **kwargs)
# except Exception as e:
# logger.error(f"use 商品生效函数effect 发生错误 {type(e)}:{e}")
return None
def register_use(goods_name: str, func: Callable, **kwargs):
"""
注册商品使用方法
:param goods_name: 商品名称
:param func: 使用函数
:param kwargs: kwargs
"""
if func_manager.exists(goods_name):
raise ValueError("该商品使用函数已被注册!")
# 发送使用成功信息
kwargs["send_success_msg"] = kwargs.get("send_success_msg", True)
kwargs["max_num_limit"] = kwargs.get("max_num_limit", 1)
func_manager.register_use(
goods_name,
**{
"func": func,
"model": create_model(f"{goods_name}_model", __base__=ShopParam, **kwargs),
"kwargs": kwargs,
},
)
logger.info(f"register_use 成功注册商品:{goods_name} 的使用函数")
-5
View File
@@ -1,5 +0,0 @@
from pathlib import Path
import nonebot
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
-158
View File
@@ -1,158 +0,0 @@
from typing import Tuple
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent
from nonebot.params import Command, CommandArg
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from models.group_info import GroupInfo
from services.log import logger
from utils.depends import OneCommand
from utils.manager import requests_manager
from utils.message_builder import image
from utils.utils import is_number
__zx_plugin_name__ = "显示所有好友群组 [Superuser]"
__plugin_usage__ = """
usage:
显示所有好友群组
指令:
查看所有好友/查看所有群组
同意好友请求 [id]
拒绝好友请求 [id]
同意群聊请求 [id]
拒绝群聊请求 [id]
查看所有请求
清空所有请求
""".strip()
__plugin_des__ = "显示所有好友群组"
__plugin_cmd__ = [
"查看所有好友/查看所有群组",
"同意好友请求 [id]",
"拒绝好友请求 [id]",
"同意群聊请求 [id]",
"拒绝群聊请求 [id]",
"查看所有请求",
"清空所有请求",
]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
cls_group = on_command(
"查看所有群组", rule=to_me(), permission=SUPERUSER, priority=1, block=True
)
cls_friend = on_command(
"查看所有好友", rule=to_me(), permission=SUPERUSER, priority=1, block=True
)
friend_handle = on_command(
"同意好友请求", aliases={"拒绝好友请求"}, permission=SUPERUSER, priority=1, block=True
)
group_handle = on_command(
"同意群聊请求", aliases={"拒绝群聊请求"}, permission=SUPERUSER, priority=1, block=True
)
clear_request = on_command("清空所有请求", permission=SUPERUSER, priority=1, block=True)
cls_request = on_command("查看所有请求", permission=SUPERUSER, priority=1, block=True)
@cls_group.handle()
async def _(bot: Bot):
gl = await bot.get_group_list()
msg = ["{group_id} {group_name}".format_map(g) for g in gl]
msg = "\n".join(msg)
msg = f"bot:{bot.self_id}\n| 群号 | 群名 | 共{len(gl)}个群\n" + msg
await cls_group.send(msg)
@cls_friend.handle()
async def _(bot: Bot):
gl = await bot.get_friend_list()
msg = ["{user_id} {nickname}".format_map(g) for g in gl]
msg = "\n".join(msg)
msg = f"| QQ号 | 昵称 | 共{len(gl)}个好友\n" + msg
await cls_friend.send(msg)
@friend_handle.handle()
async def _(bot: Bot, cmd: str = OneCommand(), arg: Message = CommandArg()):
id_ = arg.extract_plain_text().strip()
if is_number(id_):
id_ = int(id_)
if cmd[:2] == "同意":
flag = await requests_manager.approve(bot, id_, "private")
else:
flag = await requests_manager.refused(bot, id_, "private")
if flag == 1:
await friend_handle.send(f"{cmd[:2]}好友请求失败,该请求已失效..")
requests_manager.delete_request(id_, "private")
elif flag == 2:
await friend_handle.send(f"{cmd[:2]}好友请求失败,未找到此id的请求..")
else:
await friend_handle.send(f"{cmd[:2]}好友请求成功!")
else:
await friend_handle.send("id必须为纯数字!")
@group_handle.handle()
async def _(
bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg()
):
id_ = arg.extract_plain_text().strip()
flag = None
if is_number(id_):
id_ = int(id_)
if cmd[:2] == "同意":
rid = requests_manager.get_group_id(id_)
if rid:
if group := await GroupInfo.get_or_none(group_id=rid):
group.group_flag = 1
await group.save(update_fields=["group_flag"])
else:
group_info = await bot.get_group_info(group_id=rid)
await GroupInfo.create(
group_id=rid,
group_name=group_info["group_name"],
max_member_count=group_info["max_member_count"],
member_count=group_info["member_count"],
group_flag=1,
)
flag = await requests_manager.approve(bot, id_, "group")
else:
await group_handle.send("同意群聊请求失败,未找到此id的请求..")
logger.info("同意群聊请求失败,未找到此id的请求..", cmd, event.user_id)
else:
flag = await requests_manager.refused(bot, id_, "group")
if flag == 1:
await group_handle.send(f"{cmd[:2]}群聊请求失败,该请求已失效..")
requests_manager.delete_request(id_, "group")
elif flag == 2:
await group_handle.send(f"{cmd[:2]}群聊请求失败,未找到此id的请求..")
else:
await group_handle.send(f"{cmd[:2]}群聊请求成功!")
else:
await group_handle.send("id必须为纯数字!")
@cls_request.handle()
async def _():
_str = ""
for type_ in ["private", "group"]:
msg = await requests_manager.show(type_)
if msg:
_str += image(msg)
else:
_str += "没有任何好友请求.." if type_ == "private" else "没有任何群聊请求.."
if type_ == "private":
_str += "\n--------------------\n"
await cls_request.send(Message(_str))
@clear_request.handle()
async def _():
requests_manager.clear()
await clear_request.send("已清空所有好友/群聊请求..")
-75
View File
@@ -1,75 +0,0 @@
import asyncio
import os
import time
from nonebot import on_command
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from configs.path_config import TEMP_PATH
from services.log import logger
from utils.manager import resources_manager
from utils.utils import scheduler
__zx_plugin_name__ = "清理临时数据 [Superuser]"
__plugin_usage__ = """
usage:
清理临时数据
指令:
清理临时数据
""".strip()
__plugin_des__ = "清理临时数据"
__plugin_cmd__ = [
"清理临时数据",
]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
clear_data = on_command(
"清理临时数据", rule=to_me(), permission=SUPERUSER, priority=1, block=True
)
resources_manager.add_temp_dir(TEMP_PATH)
@clear_data.handle()
async def _():
await clear_data.send("开始清理临时数据....")
size = await asyncio.get_event_loop().run_in_executor(None, _clear_data)
await clear_data.send("共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024))
logger.info("清理临时数据完成," + "共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024))
def _clear_data() -> float:
logger.debug("开始清理临时文件...")
size = 0
dir_list = [dir_ for dir_ in resources_manager.get_temp_data_dir() if dir_.exists()]
for dir_ in dir_list:
logger.debug(f"尝试清理文件夹: {dir_.absolute()}", "清理临时数据")
dir_size = 0
for file in os.listdir(dir_):
file = dir_ / file
if file.is_file():
try:
if time.time() - os.path.getatime(file) > 10:
file_size = os.path.getsize(file)
file.unlink()
size += file_size
dir_size += file_size
logger.debug(f"移除临时文件: {file.absolute()}", "清理临时数据")
except Exception as e:
logger.error(f"清理临时数据错误,临时文件夹: {dir_.absolute()}...", "清理临时数据", e=e)
logger.debug("清理临时文件夹大小: {:.2f}MB".format(size / 1024 / 1024), "清理临时数据")
return float(size)
@scheduler.scheduled_job(
"cron",
hour=1,
minute=1,
)
async def _():
size = await asyncio.get_event_loop().run_in_executor(None, _clear_data)
logger.info("自动清理临时数据完成," + "共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024))
-92
View File
@@ -1,92 +0,0 @@
import asyncio
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from tortoise import Tortoise
from services.db_context import TestSQL
from services.log import logger
from utils.message_builder import custom_forward_msg
__zx_plugin_name__ = "执行sql [Superuser]"
__plugin_usage__ = """
usage:
执行一段sql语句
指令:
exec [sql语句] ([查询页数,19条/页])
查看所有表
""".strip()
__plugin_des__ = "执行一段sql语句"
__plugin_cmd__ = ["exec [sql语句]", "查看所有表"]
__plugin_version__ = 0.2
__plugin_author__ = "HibiKier"
exec_ = on_command("exec", rule=to_me(), permission=SUPERUSER, priority=1, block=True)
tables = on_command("查看所有表", rule=to_me(), permission=SUPERUSER, priority=1, block=True)
@exec_.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
sql = arg.extract_plain_text().strip()
if not sql:
await exec_.finish("未接受到sql语句")
db = Tortoise.get_connection("default")
# try:
# 判断是否为SELECT语句
if sql.lower().startswith("select"):
pass
# # 分割语句
try:
page = int(sql.split(" ")[-1]) - 1
sql_list = sql.split(" ")[:-1]
except ValueError:
page = 0
sql_list = sql.split(" ")
# 拼接语句
sql = " ".join(sql_list)
res = await db.execute_query_dict(sql)
msg_list = [f"第{page+1}页查询结果:"]
# logger.info(res)
# 获取所有字段
keys = res[0].keys()
# 每页10条
for i in res[page * 10 : (page + 1) * 10]:
msg = ""
for key in keys:
msg += f"{key}: {i[key]}\n"
msg += f"第{page+1}页第{res.index(i)+1}条"
msg_list.append(msg)
# 检查是私聊还是群聊
if isinstance(event, GroupMessageEvent):
forward_msg_list = custom_forward_msg(msg_list, bot.self_id)
await bot.send_group_forward_msg(
group_id=event.group_id, messages=forward_msg_list
)
else:
for msg in msg_list:
await exec_.send(msg)
await asyncio.sleep(0.2)
return
else:
await TestSQL.raw(sql)
await exec_.send("执行 sql 语句成功.")
# except Exception as e:
# await exec_.send(f"执行 sql 语句失败 {type(e)}:{e}")
# logger.error(f"执行 sql 语句失败 {type(e)}:{e}")
@tables.handle()
async def _(bot: Bot, event: MessageEvent):
# 获取所有表
db = Tortoise.get_connection("default")
query = await db.execute_query_dict(
"select tablename from pg_tables where schemaname = 'public'"
)
msg = "数据库中的所有表名:\n"
for tablename in query:
msg += str(tablename["tablename"]) + "\n"
await tables.finish(msg)
-244
View File
@@ -1,244 +0,0 @@
from typing import Tuple
from nonebot import on_command, on_regex
from nonebot.adapters.onebot.v11 import (
GROUP,
Bot,
GroupMessageEvent,
Message,
MessageEvent,
)
from nonebot.params import Command, CommandArg
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from configs.config import NICKNAME
from models.group_info import GroupInfo
from services.log import logger
from utils.depends import OneCommand
from utils.image_utils import text2image
from utils.manager import group_manager, plugins2settings_manager
from utils.message_builder import image
from utils.utils import is_number
__zx_plugin_name__ = "管理群操作 [Superuser]"
__plugin_usage__ = """
usage:
群权限 | 群白名单 | 退出群 操作
退群,添加/删除群白名单,添加/删除群认证,当在群聊中这五个命令且没有指定群号时,默认指定当前群聊
指令:
退群 ?[group_id]
修改群权限 [group_id] [等级]
修改群权限 [等级]: 该命令仅在群聊时生效,默认修改当前群聊
添加群白名单 ?*[group_id]
删除群白名单 ?*[group_id]
添加群认证 ?*[group_id]
删除群认证 ?*[group_id]
查看群白名单
""".strip()
__plugin_des__ = "管理群操作"
__plugin_cmd__ = [
"退群 [group_id]",
"修改群权限 [group_id] [等级]",
"添加群白名单 *[group_id]",
"删除群白名单 *[group_id]",
"添加群认证 *[group_id]",
"删除群认证 *[group_id]",
"查看群白名单",
]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
del_group = on_command("退群", rule=to_me(), permission=SUPERUSER, priority=1, block=True)
add_group_level = on_command("修改群权限", priority=1, permission=SUPERUSER, block=True)
my_group_level = on_command(
"查看群权限", aliases={"群权限"}, priority=5, permission=GROUP, block=True
)
what_up_group_level = on_regex(
"(:?提高|提升|升高|增加|加上).*?群权限",
rule=to_me(),
priority=5,
permission=GROUP,
block=True,
)
manager_group_whitelist = on_command(
"添加群白名单", aliases={"删除群白名单"}, priority=1, permission=SUPERUSER, block=True
)
show_group_whitelist = on_command(
"查看群白名单", priority=1, permission=SUPERUSER, block=True
)
group_auth = on_command(
"添加群认证", aliases={"删除群认证"}, priority=1, permission=SUPERUSER, block=True
)
@del_group.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
group_id = arg.extract_plain_text().strip()
if not group_id and isinstance(event, GroupMessageEvent):
group_id = event.group_id
if group_id:
if is_number(group_id):
group_list = [x["group_id"] for x in await bot.get_group_list()]
group_id = int(group_id)
if group_id not in group_list:
logger.debug("群聊不存在", "退群", event.user_id, target=group_id)
await del_group.finish(f"{NICKNAME}未在该群聊中...")
try:
await bot.set_group_leave(group_id=group_id)
logger.info(f"{NICKNAME}退出群聊成功", "退群", event.user_id, target=group_id)
await del_group.send(f"退出群聊 {group_id} 成功", at_sender=True)
group_manager.delete_group(group_id)
await GroupInfo.filter(group_id=group_id).delete()
except Exception as e:
logger.error(f"退出群聊失败", "退群", event.user_id, target=group_id, e=e)
await del_group.send(f"退出群聊 {group_id} 失败", at_sender=True)
else:
await del_group.send(f"请输入正确的群号", at_sender=True)
else:
await del_group.send(f"请输入群号", at_sender=True)
@add_group_level.handle()
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip()
msg = msg.split()
group_id = 0
level = 0
if isinstance(event, GroupMessageEvent) and len(msg) == 1:
msg = [event.group_id, msg[0]]
if not msg:
await add_group_level.finish("缺失参数...")
if len(msg) < 2:
await add_group_level.finish("缺失参数...")
if is_number(msg[0]) and is_number(msg[1]):
group_id = int(msg[0])
level = int(msg[1])
else:
await add_group_level.finish("参数错误...群号和等级必须是数字..")
old_level = group_manager.get_group_level(group_id)
group_manager.set_group_level(group_id, level)
await add_group_level.send("修改成功...", at_sender=True)
if level > -1:
await bot.send_group_msg(
group_id=group_id, message=f"管理员修改了此群权限:{old_level} -> {level}"
)
logger.info(f"修改群权限:{level}", "修改群权限", event.user_id, target=group_id)
@my_group_level.handle()
async def _(event: GroupMessageEvent):
level = group_manager.get_group_level(event.group_id)
tmp = ""
data = plugins2settings_manager.get_data()
for module in data:
if data[module].level > level:
plugin_name = data[module].cmd[0]
if plugin_name == "pixiv":
plugin_name = "搜图 p站排行"
tmp += f"{plugin_name}\n"
if not tmp:
await my_group_level.finish(f"当前群权限:{level}")
await my_group_level.finish(
f"当前群权限:{level}\n目前无法使用的功能:\n"
+ image(await text2image(tmp, padding=10, color="#f9f6f2"))
)
@what_up_group_level.handle()
async def _():
await what_up_group_level.finish(
f"[此功能用于防止内鬼,如果引起不便那真是抱歉了]\n" f"目前提高群权限的方法:\n" f"\t1.超级管理员修改权限"
)
@manager_group_whitelist.handle()
async def _(
bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg()
):
msg = arg.extract_plain_text().strip().split()
if not msg and isinstance(event, GroupMessageEvent):
msg = [event.group_id]
if not msg:
await manager_group_whitelist.finish("请输入群号")
all_group = [g["group_id"] for g in await bot.get_group_list()]
error_group = []
group_list = []
for group_id in msg:
if is_number(group_id) and int(group_id) in all_group:
group_list.append(int(group_id))
else:
logger.debug(f"群号不合法或不存在", cmd, target=group_id)
error_group.append(group_id)
if group_list:
for group_id in group_list:
if cmd in ["添加群白名单"]:
group_manager.add_group_white_list(group_id)
else:
group_manager.delete_group_white_list(group_id)
group_list = [str(x) for x in group_list]
await manager_group_whitelist.send("已成功将 " + "\n".join(group_list) + " " + cmd)
group_manager.save()
if error_group:
await manager_group_whitelist.send("以下群聊不合法或不存在:\n" + "\n".join(error_group))
@show_group_whitelist.handle()
async def _():
group = [str(g) for g in group_manager.get_group_white_list()]
if not group:
await show_group_whitelist.finish("没有任何群在群白名单...")
await show_group_whitelist.send("目前的群白名单:\n" + "\n".join(group))
@group_auth.handle()
async def _(
bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg()
):
msg = arg.extract_plain_text().strip().split()
if isinstance(event, GroupMessageEvent) and not msg:
msg = [event.group_id]
if not msg:
await manager_group_whitelist.finish("请输入群号")
error_group = []
all_group = [g["group_id"] for g in await bot.get_group_list()]
for group_id in msg:
group_id = int(group_id)
if is_number(group_id) and group_id in all_group:
if cmd[:2] == "添加":
try:
group_info = await bot.get_group_info(group_id=group_id)
await GroupInfo.update_or_create(
group_id=group_info["group_id"],
defaults={
"group_flag": 1,
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
},
)
except Exception as e:
await group_auth.send(f"添加群认证 {group_id} 发生错误!")
logger.error(f"添加群认证发生错误", cmd, target=group_id, e=e)
else:
await group_auth.send(f"已为 {group_id} {cmd[:2]}群认证..")
logger.info(f"添加群认证成功", cmd, target=group_id)
else:
if group := await GroupInfo.filter(group_id=group_id).first():
await group.update_or_create(
group_id=group_id, defaults={"group_flag": 0}
)
await group_auth.send(f"已删除 {group_id} 群认证..")
logger.info(f"删除群认证成功", cmd, target=group_id)
else:
await group_auth.send(f"未查找到群聊: {group_id}")
logger.info(f"未找到群聊", cmd, target=group_id)
else:
logger.debug(f"群号不合法或不存在", cmd, target=group_id)
error_group.append(str(group_id))
if error_group:
await manager_group_whitelist.send("以下群聊不合法或不存在:\n" + "\n".join(error_group))
-64
View File
@@ -1,64 +0,0 @@
from nonebot import on_command
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from configs.config import Config
from services.log import logger
from utils.manager import (
group_manager,
plugins2block_manager,
plugins2cd_manager,
plugins2settings_manager,
)
from utils.utils import scheduler
__zx_plugin_name__ = "重载配置 [Superuser]"
__plugin_usage__ = """
usage:
重载配置
plugins2settings,
plugins2cd
plugins2block
group_manager
指令:
重载配置
""".strip()
__plugin_des__ = "重载配置"
__plugin_cmd__ = [
"重载配置",
]
__plugin_version__ = 0.2
__plugin_author__ = "HibiKier"
__plugin_configs__ = {
"AUTO_RELOAD": {"value": False, "help": "自动重载配置文件", "default_value": False, "type": bool},
"AUTO_RELOAD_TIME": {"value": 180, "help": "控制自动重载配置文件时长", "default_value": 180, "type": int},
}
reload_plugins_manager = on_command(
"重载配置", rule=to_me(), permission=SUPERUSER, priority=1, block=True
)
@reload_plugins_manager.handle()
async def _():
plugins2settings_manager.reload()
plugins2cd_manager.reload()
plugins2block_manager.reload()
group_manager.reload()
Config.reload()
await reload_plugins_manager.send("重载完成...")
@scheduler.scheduled_job(
"interval",
seconds=Config.get_config("reload_setting", "AUTO_RELOAD_TIME", 180),
)
async def _():
if Config.get_config("reload_setting", "AUTO_RELOAD"):
plugins2settings_manager.reload()
plugins2cd_manager.reload()
plugins2block_manager.reload()
group_manager.reload()
Config.reload()
logger.debug("已自动重载所有配置文件...")
@@ -1,114 +0,0 @@
from typing import List, Tuple
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
from nonebot.exception import ActionFailed
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from models.level_user import LevelUser
from services.log import logger
from utils.depends import AtList, OneCommand
from utils.message_builder import at
from utils.utils import is_number
__zx_plugin_name__ = "用户权限管理 [Superuser]"
__plugin_usage__ = """
usage:
增删改用户的权限
指令:
添加权限 [at] [权限]
添加权限 [qq] [group_id] [权限]
删除权限 [at]
删除权限 [qq] [group_id]
""".strip()
__plugin_des__ = "增删改用户的权限"
__plugin_cmd__ = [
"添加权限 [at] [权限]",
"添加权限 [qq] [group_id] [权限]",
"删除权限 [at]",
"删除权限 [qq] [group_id]",
]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
super_cmd = on_command(
"添加管理",
aliases={"删除管理", "添加权限", "删除权限"},
priority=1,
permission=SUPERUSER,
block=True,
)
@super_cmd.handle()
async def _(
bot: Bot,
event: MessageEvent,
cmd: str = OneCommand(),
arg: Message = CommandArg(),
at_list: List[int] = AtList(),
):
group_id = event.group_id if isinstance(event, GroupMessageEvent) else -1
level = None
args = arg.extract_plain_text().strip().split()
flag = 2
qq = None
try:
if at_list:
qq = at_list[0]
if cmd[:2] == "添加" and args and is_number(args[0]):
level = int(args[0])
else:
if cmd[:2] == "添加":
if (
len(args) > 2
and is_number(args[0])
and is_number(args[1])
and is_number(args[2])
):
qq = int(args[0])
group_id = int(args[1])
level = int(args[2])
else:
if len(args) > 1 and is_number(args[0]) and is_number(args[1]):
qq = int(args[0])
group_id = int(args[1])
flag = 1
level = -1 if cmd[:2] == "删除" else level
if group_id == -1 or not level or not qq:
raise IndexError()
except IndexError:
await super_cmd.finish(__plugin_usage__)
if not qq:
await super_cmd.finish("未指定对象...")
try:
if cmd[:2] == "添加":
await LevelUser.set_level(qq, group_id, level, 1)
result = f"设置权限成功, 权限: {level}"
else:
if await LevelUser.delete_level(qq, group_id):
result = "删除管理成功!"
else:
result = "该账号无管理权限!"
if flag == 2:
await super_cmd.send(result)
elif flag == 1:
try:
await bot.send_group_msg(
group_id=group_id,
message=Message(
f"{at(qq)}管理员修改了你的权限"
f"\n--------\n你当前的权限等级:{level if level != -1 else 0}"
),
)
except ActionFailed:
pass
await super_cmd.send("修改成功")
logger.info(
f"修改权限: {level if level != -1 else 0}", cmd, event.user_id, group_id, qq
)
except Exception as e:
await super_cmd.send("执行指令失败!")
logger.error(f"执行指令失败", cmd, event.user_id, group_id, qq, e=e)
@@ -1,77 +0,0 @@
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from models.friend_user import FriendUser
from models.group_info import GroupInfo
from services.log import logger
__zx_plugin_name__ = "更新群/好友信息 [Superuser]"
__plugin_usage__ = """
usage:
更新群/好友信息
指令:
更新群信息
更新好友信息
""".strip()
__plugin_des__ = "更新群/好友信息"
__plugin_cmd__ = [
"更新群信息",
"更新好友信息",
]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
update_group_info = on_command(
"更新群信息", rule=to_me(), permission=SUPERUSER, priority=1, block=True
)
update_friend_info = on_command(
"更新好友信息", rule=to_me(), permission=SUPERUSER, priority=1, block=True
)
@update_group_info.handle()
async def _(bot: Bot, event: MessageEvent):
gl = await bot.get_group_list()
gl = [g["group_id"] for g in gl]
num = 0
for g in gl:
try:
group_info = await bot.get_group_info(group_id=g)
await GroupInfo.update_or_create(
group_id=group_info["group_id"],
defaults={
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
},
)
num += 1
logger.debug(
"群聊信息更新成功", "更新群信息", event.user_id, target=group_info["group_id"]
)
except Exception as e:
logger.error(f"更新群聊信息失败", "更新群信息", event.user_id, target=g, e=e)
await update_group_info.send(f"成功更新了 {len(gl)} 个群的信息")
logger.info(f"更新群聊信息完成,共更新了 {len(gl)} 个群的信息", "更新群信息", event.user_id)
@update_friend_info.handle()
async def _(bot: Bot, event: MessageEvent):
num = 0
error_list = []
fl = await bot.get_friend_list()
for f in fl:
try:
await FriendUser.update_or_create(
user_id=f["user_id"], defaults={"nickname": f["nickname"]}
)
logger.debug(f"更新好友信息成功", "更新好友信息", event.user_id, target=f["user_id"])
num += 1
except Exception as e:
logger.error(f"更新好友信息失败", "更新好友信息", event.user_id, target=f["user_id"], e=e)
await update_friend_info.send(f"成功更新了 {num} 个好友的信息")
if error_list:
await update_friend_info.send(f"以下好友更新失败:\n" + "\n".join(error_list))
logger.info(f"更新好友信息完成,共更新了 {num} 个群的信息", "更新好友信息", event.user_id)
-24
View File
@@ -1,24 +0,0 @@
from nonebot import on_command
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from utils.message_builder import image
from .data_source import SUPERUSER_HELP_IMAGE, create_help_image
__zx_plugin_name__ = "超级用户帮助 [Superuser]"
if SUPERUSER_HELP_IMAGE.exists():
SUPERUSER_HELP_IMAGE.unlink()
super_help = on_command(
"超级用户帮助", rule=to_me(), priority=1, permission=SUPERUSER, block=True
)
@super_help.handle()
async def _():
if not SUPERUSER_HELP_IMAGE.exists():
await create_help_image()
await super_help.finish(image(SUPERUSER_HELP_IMAGE))
-84
View File
@@ -1,84 +0,0 @@
import nonebot
from configs.path_config import IMAGE_PATH
from nonebot import Driver
from services.log import logger
from utils.image_template import help_template
from utils.image_utils import (BuildImage, build_sort_image, group_image,
text2image)
from utils.manager import plugin_data_manager
from utils.manager.models import PluginType
driver: Driver = nonebot.get_driver()
SUPERUSER_HELP_IMAGE = IMAGE_PATH / "superuser_help.png"
@driver.on_bot_connect
async def create_help_image():
"""
创建超级用户帮助图片
"""
if SUPERUSER_HELP_IMAGE.exists():
return
plugin_data_ = plugin_data_manager.get_data()
image_list = []
task_list = []
for plugin_data in [
plugin_data_[x]
for x in plugin_data_
if plugin_data_[x].name != "超级用户帮助 [Superuser]"
]:
try:
if plugin_data.plugin_type in [PluginType.SUPERUSER, PluginType.ADMIN]:
usage = None
if (
plugin_data.plugin_type == PluginType.SUPERUSER
and plugin_data.usage
):
usage = await text2image(
plugin_data.usage, padding=5, color=(204, 196, 151)
)
if plugin_data.superuser_usage:
usage = await text2image(
plugin_data.superuser_usage, padding=5, color=(204, 196, 151)
)
if usage:
await usage.acircle_corner()
image = await help_template(plugin_data.name, usage)
image_list.append(image)
if plugin_data.task:
for x in plugin_data.task.keys():
task_list.append(plugin_data.task[x])
except Exception as e:
logger.warning(
f"获取超级用户插件 {plugin_data.model}: {plugin_data.name} 设置失败... {type(e)}:{e}"
)
task_str = "\n".join(task_list)
task_str = "通过私聊 开启被动/关闭被动 + [被动名称] 来控制全局被动\n----------\n" + task_str
task_image = await text2image(
task_str, padding=5, color=(204, 196, 151)
)
task_image = await help_template("被动任务", task_image)
image_list.append(task_image)
image_group, _ = group_image(image_list)
A = await build_sort_image(image_group, color="#f9f6f2", padding_top=180)
await A.apaste(
BuildImage(0, 0, font="CJGaoDeGuo.otf", plain_text="超级用户帮助", font_size=50),
(50, 30),
True,
)
await A.apaste(
BuildImage(
0,
0,
font="CJGaoDeGuo.otf",
plain_text="注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数",
font_size=30,
font_color="red",
),
(50, 90),
True,
)
await A.asave(SUPERUSER_HELP_IMAGE)
logger.info(f"已成功加载 {len(image_list)} 条超级用户命令")
-32
View File
@@ -1,32 +0,0 @@
from nonebot import on_command
from utils.message_builder import image
__zx_plugin_name__ = "更新信息"
__plugin_usage__ = """
usage:
更新信息
指令:
更新信息
""".strip()
__plugin_des__ = "当前版本的更新信息"
__plugin_cmd__ = ["更新信息"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["更新信息"],
}
update_info = on_command("更新信息", aliases={"更新日志"}, priority=5, block=True)
@update_info.handle()
async def _():
if img := image("update_info.png"):
await update_info.finish(image("update_info.png"))
else:
await update_info.finish("目前没有更新信息哦")
-21
View File
@@ -1,21 +0,0 @@
import nonebot
from nonebot.adapters.onebot.v11 import Adapter
from services.db_context import init, disconnect
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
config = driver.config
driver.on_startup(init)
driver.on_shutdown(disconnect)
# 优先加载定时任务
nonebot.load_plugin("nonebot_plugin_apscheduler")
nonebot.load_plugins("basic_plugins")
nonebot.load_plugins("plugins")
nonebot.load_plugins("extensive_plugin")
# 最后加载权限控制
nonebot.load_plugins("basic_plugins/hooks")
if __name__ == "__main__":
nonebot.run()
-36
View File
@@ -1,36 +0,0 @@
import platform
from pathlib import Path
from typing import Optional
from .utils import ConfigsManager
if platform.system() == "Linux":
import os
hostip = (
os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'")
.read()
.replace("\n", "")
)
# 回复消息名称
NICKNAME: str = "小真寻"
# 数据库(必要)
# 如果填写了bind就不需要再填写后面的字段了#)
# 示例:"bind": "postgres://user:password@127.0.0.1:5432/database"
bind: str = "" # 数据库连接链接
sql_name: str = "postgres"
user: str = "" # 数据用户名
password: str = "" # 数据库密码
address: str = "" # 数据库地址
port: str = "" # 数据库端口
database: str = "" # 数据库名称
# 代理,例如 "http://127.0.0.1:7890"
# 如果是WLS 可以 f"http://{hostip}:7890" 使用寄主机的代理
SYSTEM_PROXY: Optional[str] = None # 全局代理
Config = ConfigsManager(Path() / "data" / "configs" / "plugins2config.yaml")
-47
View File
@@ -1,47 +0,0 @@
from pathlib import Path
import os
# 图片路径
IMAGE_PATH = Path() / "resources" / "image"
# 语音路径
RECORD_PATH = Path() / "resources" / "record"
# 文本路径
TEXT_PATH = Path() / "resources" / "text"
# 日志路径
LOG_PATH = Path() / "log"
# 字体路径
FONT_PATH = Path() / "resources" / "font"
# 数据路径
DATA_PATH = Path() / "data"
# 临时数据路径
TEMP_PATH = Path() / "resources" / "temp"
# 网页模板路径
TEMPLATE_PATH = Path() / "resources" / "template"
def load_path():
old_img_dir = Path() / "resources" / "img"
if not IMAGE_PATH.exists() and old_img_dir.exists():
os.rename(old_img_dir, IMAGE_PATH)
old_voice_dir = Path() / "resources" / "voice"
if not RECORD_PATH.exists() and old_voice_dir.exists():
os.rename(old_voice_dir, RECORD_PATH)
old_ttf_dir = Path() / "resources" / "ttf"
if not FONT_PATH.exists() and old_ttf_dir.exists():
os.rename(old_ttf_dir, FONT_PATH)
old_txt_dir = Path() / "resources" / "txt"
if not TEXT_PATH.exists() and old_txt_dir.exists():
os.rename(old_txt_dir, TEXT_PATH)
IMAGE_PATH.mkdir(parents=True, exist_ok=True)
RECORD_PATH.mkdir(parents=True, exist_ok=True)
TEXT_PATH.mkdir(parents=True, exist_ok=True)
LOG_PATH.mkdir(parents=True, exist_ok=True)
FONT_PATH.mkdir(parents=True, exist_ok=True)
DATA_PATH.mkdir(parents=True, exist_ok=True)
TEMP_PATH.mkdir(parents=True, exist_ok=True)
load_path()
-323
View File
@@ -1,323 +0,0 @@
import copy
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Type, Union
import cattrs
from pydantic import BaseModel
from ruamel import yaml
from ruamel.yaml import YAML
from ruamel.yaml.scanner import ScannerError
from services.log import logger
class Config(BaseModel):
"""
配置项
"""
value: Any
"""配置项值"""
name: Optional[str]
"""插件名称"""
help: Optional[str]
"""配置注解"""
default_value: Optional[Any] = None
"""默认值"""
level_module: Optional[str]
"""受权限模块"""
type: Any = None
"""参数类型"""
arg_parser: Optional[Callable] = None
"""参数解析"""
class ConfigGroup(BaseModel):
"""
配置组
"""
module: str
"""模块名"""
configs: Dict[str, Config] = {}
"""配置项列表"""
class NoSuchConfig(Exception):
pass
class ConfigsManager:
"""
插件配置 与 资源 管理器
"""
def __init__(self, file: Path):
self._data: Dict[str, ConfigGroup] = {}
self._simple_data: dict = {}
self._admin_level_data = []
self._simple_file = Path() / "configs" / "config.yaml"
_yaml = YAML()
if file:
file.parent.mkdir(exist_ok=True, parents=True)
self.file = file
self.load_data()
if self._simple_file.exists():
try:
with open(self._simple_file, "r", encoding="utf8") as f:
self._simple_data = _yaml.load(f)
except ScannerError as e:
raise ScannerError(
f"{e}\n**********************************************\n"
f"****** 可能为config.yaml配置文件填写不规范 ******\n"
f"**********************************************"
)
def add_plugin_config(
self,
module: str,
key: str,
value: Optional[Any],
*,
name: Optional[str] = None,
help_: Optional[str] = None,
default_value: Optional[Any] = None,
type: Optional[Type] = None,
arg_parser: Optional[Callable] = None,
_override: bool = False,
):
"""
为插件添加一个配置,不会被覆盖,只有第一个生效
:param module: 模块
:param key: 键
:param value: 值
:param name: 插件名称
:param help_: 配置注解
:param default_value: 默认值
:param _override: 强制覆盖值
"""
if not module or not key:
raise ValueError("add_plugin_config: module和key不能为为空")
if module in self._data and (config := self._data[module].configs.get(key)):
config.help = help_
config.arg_parser = arg_parser
config.type = type
if _override:
config.value = value
config.name = name
config.default_value = default_value
else:
_module = None
if ":" in module:
module_split = module.split(":")
if len(module_split) < 2:
raise ValueError(f"module: {module} 填写错误")
_module = module_split[-1]
module = module_split[0]
if "[LEVEL]" in key and _module:
key = key.replace("[LEVEL]", "").strip()
self._admin_level_data.append((_module, value))
key = key.upper()
if not self._data.get(module):
self._data[module] = ConfigGroup(module=module)
self._data[module].configs[key] = Config(
value=value,
name=name,
help=help_,
default_value=default_value,
level_module=_module,
type=type,
)
def set_config(
self,
module: str,
key: str,
value: Any,
auto_save: bool = False,
save_simple_data: bool = True,
):
"""
设置配置值
:param module: 模块名
:param key: 配置名称
:param value: 值
:param auto_save: 自动保存
:param save_simple_data: 保存至config.yaml
"""
if module in self._data:
if (
self._data[module].configs.get(key)
and self._data[module].configs[key] != value
):
self._data[module].configs[key].value = value
self._simple_data[module][key] = value
if auto_save:
self.save(save_simple_data=save_simple_data)
def get_config(
self, module: str, key: str, default: Optional[Any] = None
) -> Optional[Any]:
"""
获取指定配置值
:param module: 模块名
:param key: 配置名称
:param default: 没有key值内容的默认返回值
"""
logger.debug(
f"尝试获取配置 MODULE: [<u><y>{module}</y></u>] | KEY: [<u><y>{key}</y></u>]"
)
key = key.upper()
value = None
if module in self._data.keys():
config = self._data[module].configs.get(key)
if not config:
config = self._data[module].configs.get(f"{key} [LEVEL]")
if not config:
raise NoSuchConfig(f"未查询到配置项 MODULE: [ {module} ] | KEY: [ {key} ]")
if config.arg_parser:
value = config.arg_parser(value or config.default_value)
else:
try:
if config.value is not None:
value = (
cattrs.structure(config.value, config.type)
if config.type
else config.value
)
else:
if config.default_value is not None:
value = (
cattrs.structure(config.default_value, config.type)
if config.type
else config.default_value
)
except Exception as e:
logger.warning(
f"配置项类型转换 MODULE: [<u><y>{module}</y></u>] | KEY: [<u><y>{key}</y></u>]",
e=e,
)
value = config.value or config.default_value
if value is None:
value = default
logger.debug(
f"获取配置 MODULE: [<u><y>{module}</y></u>] | KEY: [<u><y>{key}</y></u>] -> [<u><c>{value}</c></u>]"
)
return value
def get_level2module(self, module: str, key: str) -> Optional[str]:
"""
获取指定key所绑定的module,一般为权限等级
:param module: 模块名
:param key: 配置名称
:return:
"""
if self._data.get(module) is not None:
if config := self._data[module].configs.get(key):
return config.level_module
def get(self, key: str) -> Optional[ConfigGroup]:
"""
获取插件配置数据
:param key: 名称
"""
return self._data.get(key)
def save(
self, path: Optional[Union[str, Path]] = None, save_simple_data: bool = False
):
"""
保存数据
:param path: 路径
:param save_simple_data: 同时保存至config.yaml
"""
if save_simple_data:
with open(self._simple_file, "w", encoding="utf8") as f:
yaml.dump(
self._simple_data,
f,
indent=2,
Dumper=yaml.RoundTripDumper,
allow_unicode=True,
)
path = path or self.file
data = {}
for module in self._data:
data[module] = {}
for config in self._data[module].configs:
value = self._data[module].configs[config].dict()
del value["type"]
data[module][config] = value
with open(path, "w", encoding="utf8") as f:
yaml.dump(
data, f, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True
)
def reload(self):
"""
重新加载配置文件
"""
_yaml = YAML()
if self._simple_file.exists():
with open(self._simple_file, "r", encoding="utf8") as f:
self._simple_data = _yaml.load(f)
for key in self._simple_data.keys():
for k in self._simple_data[key].keys():
self._data[key].configs[k].value = self._simple_data[key][k]
self.save()
def load_data(self):
"""
加载数据
Raises:
ValueError: _description_
"""
if self.file.exists():
_yaml = YAML()
with open(self.file, "r", encoding="utf8") as f:
temp_data = _yaml.load(f)
if not temp_data:
self.file.unlink()
raise ValueError(
"配置文件为空!\n"
"***********************************************************\n"
"****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n"
"***********************************************************"
)
count = 0
for module in temp_data:
config_group = ConfigGroup(module=module)
for config in temp_data[module]:
config_group.configs[config] = Config(**temp_data[module][config])
count += 1
self._data[module] = config_group
logger.info(
f"加载配置完成,共加载 <u><y>{len(temp_data)}</y></u> 个配置组及对应 <u><y>{count}</y></u> 个配置项"
)
def get_admin_level_data(self):
"""
获取管理插件等级
"""
return self._admin_level_data
def get_data(self) -> Dict[str, ConfigGroup]:
return copy.deepcopy(self._data)
def is_empty(self) -> bool:
return not bool(self._data)
def keys(self):
return self._data.keys()
def __str__(self):
return str(self._data)
def __setitem__(self, key, value):
self._data[key] = value
def __getitem__(self, key):
return self._data[key]
-1889
View File
File diff suppressed because it is too large Load Diff
-66
View File
@@ -1,66 +0,0 @@
{
"抽卡":{
"buff": ["欧气满满,十连出金","出金不歪"],
"debuff": ["武器大师","保底出金","金色会是痛苦大剑"]
},
"刷世界boss":{
"buff": ["双攻双爆角斗士"],
"debuff": ["只有保底材料","贪生怕死角斗士"]
},
"刷风本":{
"buff": ["会有极品猎人套","会掉真正的少女心","治疗加成少女头"],
"debuff": ["勇往直前少女心","少女飘摇的杀意","少女暴怒的容颜"]
},
"刷火本":{
"buff": ["魔女帽子火伤杯","暴伤魔女帽!","火伤魔女心!"],
"debuff": ["幡 然 醒 悟","这么阴间的地方真的会有魔女套吗?","不务正业火魔女","会匹配到3个卢姥爷"]
},
"刷岩本":{
"buff": ["悠久的磐岩伴你左右","岩神的庇护常在"],
"debuff": ["防御流星杯,你值得拥有"]
},
"刷宗室":{
"buff": ["物理伤害骑士道,元素精通宗室套"],
"debuff": ["贪生怕死骑士道,物理伤害宗室杯"]
},
"刷冰本":{
"buff": ["双暴词条概率up"],
"debuff": ["防御力船帽,无人可及"]
},
"刷雷本":{
"buff": ["愿雷鸟伴你左右"],
"debuff": ["来表演一个只掉平雷套的绝活","风神忽悠雷凶兆"]
},
"锄大地":{
"buff": ["会掉一大堆紫色材料"],
"debuff": ["深渊法师爱你哟","会被冰水法控到死"]
},
"挖矿":{
"buff": ["开矿出双材料"],
"debuff": ["去别人世界会被拒"]
},
"刷天赋本":{
"buff": ["金色!我看到了金色的书!"],
"debuff": ["2蓝2绿不会变"]
},
"刷突破材料":{
"buff": ["金色!我看到了金色的材料!"],
"debuff": ["2蓝2绿不会变"]
},
"升级圣遗物":{
"buff": ["稀有词条跳跳跳","会双爆拉满"],
"debuff": ["女 仆 狂 喜","无中生有防御力","生命拉满","完美避开双爆"]
},
"打风魔龙":{
"buff": ["看我一箭一个风魔鸡","今天特瓦林可以给想要的突破材料","5金加原胚!"],
"debuff": ["会不小心掉下平台","不小心被地板烫死了"]
},
"打狼王":{
"buff": ["今天安德琉斯的心情不错,可以py一下","5金加原胚!"],
"debuff": ["狼尾巴*1"]
},
"打公子":{
"buff": ["今天可以和公子py想要的突破材料","5金加原胚!"],
"debuff": ["要角没有!要命一条!"]
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

-169
View File
@@ -1,169 +0,0 @@
from typing import Dict
from tortoise import fields
from services.db_context import Model
from .goods_info import GoodsInfo
class BagUser(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_qq = fields.BigIntField()
"""用户id"""
group_id = fields.BigIntField()
"""群聊id"""
gold = fields.IntField(default=100)
"""金币数量"""
spend_total_gold = fields.IntField(default=0)
"""花费金币总数"""
get_total_gold = fields.IntField(default=0)
"""获取金币总数"""
get_today_gold = fields.IntField(default=0)
"""今日获取金币"""
spend_today_gold = fields.IntField(default=0)
"""今日获取金币"""
property: Dict[str, int] = fields.JSONField(default={})
"""道具"""
class Meta:
table = "bag_users"
table_description = "用户道具数据表"
unique_together = ("user_qq", "group_id")
@classmethod
async def get_user_total_gold(cls, user_qq: int, group_id: int) -> str:
"""
说明:
获取金币概况
参数:
:param user_qq: qq号
:param group_id: 所在群号
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
return (
f"当前金币:{user.gold}\n今日获取金币:{user.get_today_gold}\n今日花费金币:{user.spend_today_gold}"
f"\n今日收益:{user.get_today_gold - user.spend_today_gold}"
f"\n总赚取金币:{user.get_total_gold}\n总花费金币:{user.spend_total_gold}"
)
@classmethod
async def get_gold(cls, user_qq: int, group_id: int) -> int:
"""
说明:
获取当前金币
参数:
:param user_qq: qq号
:param group_id: 所在群号
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
return user.gold
@classmethod
async def get_property(
cls, user_qq: int, group_id: int, only_active: bool = False
) -> Dict[str, int]:
"""
说明:
获取当前道具
参数:
:param user_qq: qq号
:param group_id: 所在群号
:param only_active: 仅仅获取主动使用的道具
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
if only_active and user.property:
data = {}
name_list = [
x.goods_name
for x in await GoodsInfo.get_all_goods()
if not x.is_passive
]
for key in [x for x in user.property if x in name_list]:
data[key] = user.property[key]
return data
return user.property
@classmethod
async def add_gold(cls, user_qq: int, group_id: int, num: int):
"""
说明:
增加金币
参数:
:param user_qq: qq号
:param group_id: 所在群号
:param num: 金币数量
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
user.gold = user.gold + num
user.get_total_gold = user.get_total_gold + num
user.get_today_gold = user.get_today_gold + num
await user.save(update_fields=["gold", "get_today_gold", "get_total_gold"])
@classmethod
async def spend_gold(cls, user_qq: int, group_id: int, num: int):
"""
说明:
花费金币
参数:
:param user_qq: qq号
:param group_id: 所在群号
:param num: 金币数量
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
user.gold = user.gold - num
user.spend_total_gold = user.spend_total_gold + num
user.spend_today_gold = user.spend_today_gold + num
await user.save(update_fields=["gold", "spend_total_gold", "spend_today_gold"])
@classmethod
async def add_property(cls, user_qq: int, group_id: int, name: str, num: int = 1):
"""
说明:
增加道具
参数:
:param user_qq: qq号
:param group_id: 所在群号
:param name: 道具名称
:param num: 道具数量
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
property_ = user.property
if property_.get(name) is None:
property_[name] = 0
property_[name] += num
user.property = property_
await user.save(update_fields=["property"])
@classmethod
async def delete_property(
cls, user_qq: int, group_id: int, name: str, num: int = 1
) -> bool:
"""
说明:
使用/删除 道具
参数:
:param user_qq: qq号
:param group_id: 所在群号
:param name: 道具名称
:param num: 使用个数
"""
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
property_ = user.property
if name in property_:
if (n := property_.get(name, 0)) < num:
return False
if n == num:
del property_[name]
else:
property_[name] -= num
await user.save(update_fields=["property"])
return True
return False
@classmethod
async def _run_script(cls):
await cls.raw("ALTER TABLE bag_users DROP props;")
"""删除 props 字段"""
-124
View File
@@ -1,124 +0,0 @@
import time
from typing import Union
from tortoise import fields
from services.db_context import Model
from services.log import logger
class BanUser(Model):
user_qq = fields.IntField(pk=True)
"""用户id"""
ban_level = fields.IntField()
"""使用ban命令的用户等级"""
ban_time = fields.BigIntField()
"""ban开始的时间"""
duration = fields.BigIntField()
"""ban时长"""
class Meta:
table = "ban_users"
table_description = ".ban/b了 封禁人员数据表"
@classmethod
async def check_ban_level(cls, user_qq: int, level: int) -> bool:
"""
说明:
检测ban掉目标的用户与unban用户的权限等级大小
参数:
:param user_qq: unban用户的qq号
:param level: ban掉目标用户的权限等级
"""
user = await cls.filter(user_qq=user_qq).first()
if user:
logger.debug(
f"检测用户被ban等级,user_level: {user.ban_level},level: {level}",
target=user_qq,
)
return bool(user and user.ban_level > level)
return False
@classmethod
async def check_ban_time(cls, user_qq: int) -> Union[str, int]:
"""
说明:
检测用户被ban时长
参数:
:param user_qq: qq号
"""
logger.debug(f"获取用户ban时长", target=user_qq)
if user := await cls.filter(user_qq=user_qq).first():
if (
time.time() - (user.ban_time + user.duration) > 0
and user.duration != -1
):
return ""
if user.duration == -1:
return "∞"
return int(time.time() - user.ban_time - user.duration)
return ""
@classmethod
async def is_ban(cls, user_qq: int) -> bool:
"""
说明:
判断用户是否被ban
参数:
:param user_qq: qq号
"""
logger.debug(f"检测是否被ban", target=user_qq)
if await cls.check_ban_time(user_qq):
return True
else:
await cls.unban(user_qq)
return False
@classmethod
async def is_super_ban(cls, user_qq: int) -> bool:
"""
说明:
判断用户是否被超级用户ban / b了
参数:
:param user_qq: qq号
"""
logger.debug(f"检测是否被超级用户权限封禁", target=user_qq)
if user := await cls.filter(user_qq=user_qq).first():
if user.ban_level == 10:
return True
return False
@classmethod
async def ban(cls, user_qq: int, ban_level: int, duration: int):
"""
说明:
ban掉目标用户
参数:
:param user_qq: 目标用户qq号
:param ban_level: 使用ban命令用户的权限
:param duration: ban时长,秒
"""
logger.debug(f"封禁用户,等级:{ban_level},时长: {duration}", target=user_qq)
if await cls.filter(user_qq=user_qq).first():
await cls.unban(user_qq)
await cls.create(
user_qq=user_qq,
ban_level=ban_level,
ban_time=time.time(),
duration=duration,
)
@classmethod
async def unban(cls, user_qq: int) -> bool:
"""
说明:
unban用户
参数:
:param user_qq: qq号
"""
if user := await cls.filter(user_qq=user_qq).first():
logger.debug("解除封禁", target=user_qq)
await user.delete()
return True
return False
-117
View File
@@ -1,117 +0,0 @@
from datetime import datetime, timedelta
from typing import Any, List, Literal, Optional, Tuple, Union
from tortoise import fields
from tortoise.functions import Count
from services.db_context import Model
class ChatHistory(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_qq = fields.BigIntField()
"""用户id"""
group_id = fields.BigIntField(null=True)
"""群聊id"""
text = fields.TextField(null=True)
"""文本内容"""
plain_text = fields.TextField(null=True)
"""纯文本"""
create_time = fields.DatetimeField(auto_now_add=True)
"""创建时间"""
class Meta:
table = "chat_history"
table_description = "聊天记录数据表"
@classmethod
async def get_group_msg_rank(
cls,
gid: int,
limit: int = 10,
order: str = "DESC",
date_scope: Optional[Tuple[datetime, datetime]] = None,
) -> List["ChatHistory"]:
"""
说明:
获取排行数据
参数:
:param gid: 群号
:param limit: 获取数量
:param order: 排序类型,desc,des
:param date_scope: 日期范围
"""
o = "-" if order == "DESC" else ""
query = cls.filter(group_id=gid)
if date_scope:
query = query.filter(create_time__range=date_scope)
return list(
await query.annotate(count=Count("user_qq"))
.order_by(o + "count")
.group_by("user_qq")
.limit(limit)
.values_list("user_qq", "count")
) # type: ignore
@classmethod
async def get_group_first_msg_datetime(cls, group_id: int) -> Optional[datetime]:
"""
说明:
获取群第一条记录消息时间
参数:
:param group_id: 群聊id
"""
if (
message := await cls.filter(group_id=group_id)
.order_by("create_time")
.first()
):
return message.create_time
@classmethod
async def get_message(
cls,
uid: Optional[int],
gid: Optional[int],
type_: Literal["user", "group"],
msg_type: Optional[Literal["private", "group"]] = None,
days: Optional[Union[int, Tuple[datetime, datetime]]] = None,
) -> List["ChatHistory"]:
"""
说明:
获取消息查询query
参数:
:param uid: 用户qq
:param gid: 群号
:param type_: 类型,私聊或群聊
:param msg_type: 消息类型,用户或群聊
:param days: 限制日期
"""
if type_ == "user":
query = cls.filter(user_qq=uid)
if msg_type == "private":
query = query.filter(group_id__isnull=True)
elif msg_type == "group":
query = query.filter(group_id__not_isnull=True)
else:
query = cls.filter(group_id=gid)
if uid:
query = query.filter(user_qq=uid)
if days:
if isinstance(days, int):
query = query.filter(
create_time__gte=datetime.now() - timedelta(days=days)
)
elif isinstance(days, tuple):
query = query.filter(create_time__range=days)
return await query.all() # type: ignore
@classmethod
async def _run_script(cls):
return [
"alter table chat_history alter group_id drop not null;", # 允许 group_id 为空
"alter table chat_history alter text drop not null;", # 允许 text 为空
"alter table chat_history alter plain_text drop not null;", # 允许 plain_text 为空
]
-60
View File
@@ -1,60 +0,0 @@
from tortoise import fields
from configs.config import Config
from services.db_context import Model
class FriendUser(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_id = fields.BigIntField(unique=True)
"""用户id"""
user_name = fields.CharField(max_length=255, default="")
"""用户名称"""
nickname = fields.CharField(max_length=255, null=True)
"""私聊下自定义昵称"""
class Meta:
table = "friend_users"
table_description = "好友信息数据表"
@classmethod
async def get_user_name(cls, user_id: int) -> str:
"""
说明:
获取好友用户名称
参数:
:param user_id: qq号
"""
if user := await cls.get_or_none(user_id=user_id):
return user.user_name
return ""
@classmethod
async def get_user_nickname(cls, user_id: int) -> str:
"""
说明:
获取用户昵称
参数:
:param user_id: qq号
"""
if user := await cls.get_or_none(user_id=user_id):
if user.nickname:
_tmp = ""
if black_word := Config.get_config("nickname", "BLACK_WORD"):
for x in user.nickname:
_tmp += "*" if x in black_word else x
return _tmp
return ""
@classmethod
async def set_user_nickname(cls, user_id: int, nickname: str):
"""
说明:
设置用户昵称
参数:
:param user_id: qq号
:param nickname: 昵称
"""
await cls.update_or_create(user_id=user_id, defaults={"nickname": nickname})
-196
View File
@@ -1,196 +0,0 @@
from typing import Dict, List, Optional, Tuple
from tortoise import fields
from services.db_context import Model
class GoodsInfo(Model):
__tablename__ = "goods_info"
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
goods_name = fields.CharField(255, unique=True)
"""商品名称"""
goods_price = fields.IntField()
"""价格"""
goods_description = fields.TextField()
"""描述"""
goods_discount = fields.FloatField(default=1)
"""折扣"""
goods_limit_time = fields.BigIntField(default=0)
"""限时"""
daily_limit = fields.IntField(default=0)
"""每日限购"""
daily_purchase_limit: Dict[str, Dict[str, int]] = fields.JSONField(default={})
"""用户限购记录"""
is_passive = fields.BooleanField(default=False)
"""是否为被动道具"""
icon = fields.TextField(null=True)
"""图标路径"""
class Meta:
table = "goods_info"
table_description = "商品数据表"
@classmethod
async def add_goods(
cls,
goods_name: str,
goods_price: int,
goods_description: str,
goods_discount: float = 1,
goods_limit_time: int = 0,
daily_limit: int = 0,
is_passive: bool = False,
icon: Optional[str] = None,
):
"""
说明:
添加商品
参数:
:param goods_name: 商品名称
:param goods_price: 商品价格
:param goods_description: 商品简介
:param goods_discount: 商品折扣
:param goods_limit_time: 商品限时
:param daily_limit: 每日购买限制
:param is_passive: 是否为被动道具
:param icon: 图标
"""
if not await cls.filter(goods_name=goods_name).first():
await cls.create(
goods_name=goods_name,
goods_price=goods_price,
goods_description=goods_description,
goods_discount=goods_discount,
goods_limit_time=goods_limit_time,
daily_limit=daily_limit,
is_passive=is_passive,
icon=icon,
)
@classmethod
async def delete_goods(cls, goods_name: str) -> bool:
"""
说明:
删除商品
参数:
:param goods_name: 商品名称
"""
if goods := await cls.get_or_none(goods_name=goods_name):
await goods.delete()
return True
return False
@classmethod
async def update_goods(
cls,
goods_name: str,
goods_price: Optional[int] = None,
goods_description: Optional[str] = None,
goods_discount: Optional[float] = None,
goods_limit_time: Optional[int] = None,
daily_limit: Optional[int] = None,
is_passive: Optional[bool] = None,
icon: Optional[str] = None,
):
"""
说明:
更新商品信息
参数:
:param goods_name: 商品名称
:param goods_price: 商品价格
:param goods_description: 商品简介
:param goods_discount: 商品折扣
:param goods_limit_time: 商品限时时间
:param daily_limit: 每日次数限制
:param is_passive: 是否为被动
:param icon: 图标
"""
if goods := await cls.get_or_none(goods_name=goods_name):
await cls.update_or_create(
goods_name=goods_name,
defaults={
"goods_price": goods_price or goods.goods_price,
"goods_description": goods_description or goods.goods_description,
"goods_discount": goods_discount or goods.goods_discount,
"goods_limit_time": goods_limit_time
if goods_limit_time is not None
else goods.goods_limit_time,
"daily_limit": daily_limit
if daily_limit is not None
else goods.daily_limit,
"is_passive": is_passive
if is_passive is not None
else goods.is_passive,
"icon": icon or goods.icon,
},
)
@classmethod
async def get_all_goods(cls) -> List["GoodsInfo"]:
"""
说明:
获得全部有序商品对象
"""
query = await cls.all()
id_lst = [x.id for x in query]
goods_lst = []
for _ in range(len(query)):
min_id = min(id_lst)
goods_lst.append([x for x in query if x.id == min_id][0])
id_lst.remove(min_id)
return goods_lst
@classmethod
async def add_user_daily_purchase(
cls, goods: "GoodsInfo", user_id_: int, group_id_: int, num: int = 1
):
"""
说明:
添加用户明日购买限制
参数:
:param goods: 商品
:param user_id: 用户id
:param group_id: 群号
:param num: 数量
"""
user_id = str(user_id_)
group_id = str(group_id_)
if goods and goods.daily_limit and goods.daily_limit > 0:
if not goods.daily_purchase_limit.get(group_id):
goods.daily_purchase_limit[group_id] = {}
if not goods.daily_purchase_limit[group_id].get(user_id):
goods.daily_purchase_limit[group_id][user_id] = 0
goods.daily_purchase_limit[group_id][user_id] += num
await goods.save(update_fields=["daily_purchase_limit"])
@classmethod
async def check_user_daily_purchase(
cls, goods: "GoodsInfo", user_id_: int, group_id_: int, num: int = 1
) -> Tuple[bool, int]:
"""
说明:
检测用户每日购买上限
参数:
:param goods: 商品
:param user_id: 用户id
:param group_id: 群号
:param num: 数量
"""
user_id = str(user_id_)
group_id = str(group_id_)
if goods and goods.daily_limit > 0:
if (
not goods.daily_limit
or not goods.daily_purchase_limit.get(group_id)
or not goods.daily_purchase_limit[group_id].get(user_id)
):
return goods.daily_limit - num < 0, goods.daily_limit
if goods.daily_purchase_limit[group_id][user_id] + num > goods.daily_limit:
return (
True,
goods.daily_limit - goods.daily_purchase_limit[group_id][user_id],
)
return False, 0
-24
View File
@@ -1,24 +0,0 @@
from typing import List, Optional
from tortoise import fields
from services.db_context import Model
from services.log import logger
class GroupInfo(Model):
group_id = fields.BigIntField(pk=True)
"""群聊id"""
group_name = fields.TextField(default="")
"""群聊名称"""
max_member_count = fields.IntField(default=0)
"""最大人数"""
member_count = fields.IntField(default=0)
"""当前人数"""
group_flag: int = fields.IntField(default=0)
"""群认证标记"""
class Meta:
table = "group_info"
table_description = "群聊信息表"
-117
View File
@@ -1,117 +0,0 @@
from datetime import datetime
from typing import List, Optional, Set
from tortoise import fields
from configs.config import Config
from services.db_context import Model
from services.log import logger
class GroupInfoUser(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_qq = fields.BigIntField()
"""用户id"""
user_name = fields.CharField(255, default="")
"""用户昵称"""
group_id = fields.BigIntField()
"""群聊id"""
user_join_time: datetime = fields.DatetimeField(null=True)
"""用户入群时间"""
nickname = fields.CharField(255, null=True)
"""群聊昵称"""
uid = fields.BigIntField(null=True)
"""用户uid"""
class Meta:
table = "group_info_users"
table_description = "群员信息数据表"
unique_together = ("user_qq", "group_id")
@classmethod
async def get_group_member_id_list(cls, group_id: int) -> Set[int]:
"""
说明:
获取该群所有用户qq
参数:
:param group_id: 群号
"""
return set(
await cls.filter(group_id=group_id).values_list("user_qq", flat=True)
) # type: ignore
@classmethod
async def set_user_nickname(cls, user_qq: int, group_id: int, nickname: str):
"""
说明:
设置群员在该群内的昵称
参数:
:param user_qq: qq号
:param group_id: 群号
:param nickname: 昵称
"""
await cls.update_or_create(
user_qq=user_qq,
group_id=group_id,
defaults={"nickname": nickname},
)
@classmethod
async def get_user_all_group(cls, user_qq: int) -> List[int]:
"""
说明:
获取该用户所在的所有群聊
参数:
:param user_qq: 用户qq
"""
return list(
await cls.filter(user_qq=user_qq).values_list("group_id", flat=True)
) # type: ignore
@classmethod
async def get_user_nickname(cls, user_qq: int, group_id: int) -> str:
"""
说明:
获取用户在该群的昵称
参数:
:param user_qq: qq号
:param group_id: 群号
"""
if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id):
if user.nickname:
nickname = ""
if black_word := Config.get_config("nickname", "BLACK_WORD"):
for x in user.nickname:
nickname += "*" if x in black_word else x
return nickname
return user.nickname
return ""
@classmethod
async def get_group_member_uid(cls, user_qq: int, group_id: int) -> Optional[int]:
logger.debug(
f"GroupInfoUser 尝试获取 用户[<u><e>{user_qq}</e></u>] 群聊[<u><e>{group_id}</e></u>] UID"
)
user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id)
_max_uid_user, _ = await cls.get_or_create(user_qq=114514, group_id=114514)
_max_uid = _max_uid_user.uid
if not user.uid:
all_user = await cls.filter(user_qq=user_qq).all()
for x in all_user:
if x.uid:
return x.uid
user.uid = _max_uid + 1
_max_uid_user.uid = _max_uid + 1
await cls.bulk_update([user, _max_uid_user], ["uid"])
logger.debug(
f"GroupInfoUser 获取 用户[<u><e>{user_qq}</e></u>] 群聊[<u><e>{group_id}</e></u>] UID: {user.uid}"
)
return user.uid
@classmethod
async def _run_script(cls):
return [
"alter table group_info_users alter user_join_time drop not null;" # 允许 user_join_time 为空
]
-100
View File
@@ -1,100 +0,0 @@
from tortoise import fields
from services.db_context import Model
class LevelUser(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_qq = fields.BigIntField()
"""用户id"""
group_id = fields.BigIntField()
"""群聊id"""
user_level = fields.BigIntField()
"""用户权限等级"""
group_flag = fields.IntField(default=0)
"""特殊标记,是否随群管理员变更而设置权限"""
class Meta:
table = "level_users"
table_description = "用户权限数据库"
unique_together = ("user_qq", "group_id")
@classmethod
async def get_user_level(cls, user_qq: int, group_id: int) -> int:
"""
说明:
获取用户在群内的等级
参数:
:param user_qq: qq号
:param group_id: 群号
"""
if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id):
return user.user_level
return -1
@classmethod
async def set_level(
cls, user_qq: int, group_id: int, level: int, group_flag: int = 0
):
"""
说明:
设置用户在群内的权限
参数:
:param user_qq: qq号
:param group_id: 群号
:param level: 权限等级
:param group_flag: 是否被自动更新刷新权限 0:是,1:否
"""
await cls.update_or_create(
user_qq=user_qq,
group_id=group_id,
defaults={"user_level": level, "group_flag": group_flag},
)
@classmethod
async def delete_level(cls, user_qq: int, group_id: int) -> bool:
"""
说明:
删除用户权限
参数:
:param user_qq: qq号
:param group_id: 群号
"""
if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id):
await user.delete()
return True
return False
@classmethod
async def check_level(cls, user_qq: int, group_id: int, level: int) -> bool:
"""
说明:
检查用户权限等级是否大于 level
参数:
:param user_qq: qq号
:param group_id: 群号
:param level: 权限等级
"""
if group_id:
if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id):
return user.user_level >= level
else:
user_list = await cls.filter(user_qq=user_qq).all()
user = max(user_list, key=lambda x: x.user_level)
return user.user_level >= level
return False
@classmethod
async def is_group_flag(cls, user_qq: int, group_id: int) -> bool:
"""
说明:
检测是否会被自动更新刷新权限
参数:
:param user_qq: qq号
:param group_id: 群号
"""
if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id):
return user.group_flag == 1
return False
-71
View File
@@ -1,71 +0,0 @@
from datetime import datetime
from typing import List, Literal, Optional, Tuple
from tortoise import fields
from services.db_context import Model
class SignGroupUser(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_qq = fields.BigIntField()
"""用户id"""
group_id = fields.BigIntField()
"""群聊id"""
checkin_count = fields.IntField(default=0)
"""签到次数"""
checkin_time_last = fields.DatetimeField(auto_now=True)
"""最后签到时间"""
impression = fields.DecimalField(10, 3, default=0)
"""好感度"""
add_probability = fields.DecimalField(10, 3, default=0)
"""双倍签到增加概率"""
specify_probability = fields.DecimalField(10, 3, default=0)
"""使用指定双倍概率"""
# specify_probability = fields.DecimalField(10, 3, default=0)
class Meta:
table = "sign_group_users"
table_description = "群员签到数据表"
unique_together = ("user_qq", "group_id")
@classmethod
async def sign(cls, user: "SignGroupUser", impression: float):
"""
说明:
签到
说明:
:param user: 用户
:param impression: 增加的好感度
"""
user.checkin_count = user.checkin_count + 1
user.add_probability = 0
user.specify_probability = 0
user.impression = float(user.impression) + impression
await user.save()
@classmethod
async def get_all_impression(
cls, group_id: Optional[int]
) -> Tuple[List[int], List[int], List[float]]:
"""
说明:
获取该群所有用户 id 及对应 好感度
参数:
:param group_id: 群号
"""
if group_id:
query = cls.filter(group_id=group_id)
else:
query = cls
value_list = await query.all().values_list("user_qq", "group_id", "impression") # type: ignore
qq_list = []
group_list = []
impression_list = []
for value in value_list:
qq_list.append(value[0])
group_list.append(value[1])
impression_list.append(float(value[2]))
return qq_list, impression_list, group_list
-29
View File
@@ -1,29 +0,0 @@
from datetime import datetime
from tortoise import fields
from services.db_context import Model
class UserShopGoldLog(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
"""自增id"""
user_qq = fields.BigIntField()
"""用户id"""
group_id = fields.BigIntField()
"""群聊id"""
type = fields.IntField()
"""金币使用类型 0: 购买, 1: 使用, 2: 插件"""
name = fields.CharField(255)
"""商品/插件 名称"""
spend_gold = fields.IntField(default=0)
"""花费金币"""
num = fields.IntField()
"""数量"""
create_time = fields.DatetimeField(auto_now_add=True)
"""创建时间"""
class Meta:
table = "user_shop_gold_log"
table_description = "金币使用日志表"
View File
-43
View File
@@ -1,43 +0,0 @@
from nonebot import on_regex
from nonebot.rule import to_me
from pathlib import Path
__zx_plugin_name__ = "关于"
__plugin_usage__ = """
usage:
想要更加了解真寻吗
指令:
关于
""".strip()
__plugin_des__ = "想要更加了解真寻吗"
__plugin_cmd__ = ["关于"]
__plugin_version__ = 0.1
__plugin_type__ = ("其他",)
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 1,
"default_status": True,
"limit_superuser": False,
"cmd": ["关于"],
}
about = on_regex("^关于$", priority=5, block=True, rule=to_me())
@about.handle()
async def _():
ver_file = Path() / '__version__'
version = None
if ver_file.exists():
with open(ver_file, 'r', encoding='utf8') as f:
version = f.read().split(':')[-1].strip()
msg = f"""
『绪山真寻Bot』
版本:{version}
简介:基于Nonebot2与go-cqhttp开发,是一个非常可爱的Bot呀,希望与大家要好好相处
项目地址:https://github.com/HibiKier/zhenxun_bot
文档地址:https://hibikier.github.io/zhenxun_bot/
""".strip()
await about.send(msg)

Some files were not shown because too many files have changed in this diff Show More