Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a8eb9e67a | ||
|
|
8b7adf0a6a | ||
|
|
973182c27c | ||
|
|
0dced0bc90 | ||
|
|
fc34cd541a | ||
|
|
8090ebe8ce | ||
|
|
f87c14595f | ||
|
|
39bb5a9ab9 | ||
|
|
f754ad1c76 | ||
|
|
37e1a06528 | ||
|
|
7096d258db | ||
|
|
ab33fc3af1 | ||
|
|
2df2a6faf3 | ||
|
|
1996f0d96a | ||
|
|
3c6a365718 | ||
|
|
bf9176ccdb | ||
|
|
c154c1d2a6 | ||
|
|
60da97790b | ||
|
|
1635637254 | ||
|
|
109b80b6ef | ||
|
|
218147b771 | ||
|
|
eec810dc6a | ||
|
|
c2e2dc5609 | ||
|
|
8f46cffa44 | ||
|
|
db54df6ce5 | ||
|
|
e68b154ddc | ||
|
|
5ec89e7538 | ||
|
|
96dc1d9851 | ||
|
|
7148940a2f | ||
|
|
0455cba221 | ||
|
|
b5f1d1ac21 | ||
|
|
a628f7ab26 | ||
|
|
ee6e185df7 | ||
|
|
e1d7f31456 | ||
|
|
e4fdfd0a21 | ||
|
|
edd55f94e9 | ||
|
|
ee90fc3c14 | ||
|
|
c96969e86c | ||
|
|
88ac572d51 | ||
|
|
10b7cbaa71 | ||
|
|
3b649172a8 | ||
|
|
9606566db4 | ||
|
|
e155efaf31 | ||
|
|
2281f762ba | ||
|
|
96d1bd3575 | ||
|
|
12212ecf65 | ||
|
|
910af434ae | ||
|
|
b62399e203 | ||
|
|
4ca6925327 | ||
|
|
c14b13ee6c | ||
|
|
6dbb4be824 | ||
|
|
9c8532cfa3 | ||
|
|
b86c2bff98 | ||
|
|
825671447a | ||
|
|
1b99c34cfb | ||
|
|
cc24822dca | ||
|
|
2ad891aa1e | ||
|
|
517f120af8 | ||
|
|
8668818fcb | ||
|
|
6a76176001 | ||
|
|
edbbcec1be | ||
|
|
0dfca7e23d | ||
|
|
7bd767c35a | ||
|
|
111c4a7efa | ||
|
|
61463b7fcf | ||
|
|
ae3a32589a | ||
|
|
e26da37c58 | ||
|
|
1da51a2a65 | ||
|
|
b58e8e4cab | ||
|
|
b3edd20ff5 | ||
|
|
75cd702799 | ||
|
|
d0bc5ebe72 | ||
|
|
f9d6e7d37d | ||
|
|
f7782e69ac | ||
|
|
369a60749d | ||
|
|
e68b58d767 | ||
|
|
c806bab6c2 | ||
|
|
48f9b15a99 | ||
|
|
fbd36967d0 | ||
|
|
81c1dc2527 | ||
|
|
558c836fe4 | ||
|
|
2a6951426b | ||
|
|
d8a997e568 | ||
|
|
72cd1c700a | ||
|
|
05db1b61d3 | ||
|
|
bba9a2a66c | ||
|
|
18342c9a8e | ||
|
|
c8490c26bb | ||
|
|
9c88b6fcf5 | ||
|
|
c01dbd28cf | ||
|
|
045cb6b626 | ||
|
|
35c92ec3aa | ||
|
|
1be9305755 | ||
|
|
54928478a3 | ||
|
|
78f69b9325 | ||
|
|
6071c83c9f | ||
|
|
2f3b6d76e5 | ||
|
|
3172fd7783 | ||
|
|
ea0d3a8376 | ||
|
|
ec86f5ee7d | ||
|
|
a8b21b4705 | ||
|
|
a2b1049ab2 | ||
|
|
29df8d284c | ||
|
|
661254d92f | ||
|
|
4d45a4495d | ||
|
|
a603ab95ab | ||
|
|
2419269047 | ||
|
|
e1aca539c3 | ||
|
|
2725967df5 | ||
|
|
b247100167 | ||
|
|
5e3cbc9dd0 | ||
|
|
186ab7fc87 | ||
|
|
05a689ddc2 | ||
|
|
5bc8a92ab3 | ||
|
|
9f93c247b1 | ||
|
|
4e93befcbb | ||
|
|
9745244b54 | ||
|
|
d51ce2db48 | ||
|
|
c609b5db07 | ||
|
|
f0774368b8 | ||
|
|
a38bbe7b0c | ||
|
|
fe1879f574 | ||
|
|
a976bae11f | ||
|
|
80b3de02af | ||
|
|
d07a4f6af4 | ||
|
|
299ef51132 | ||
|
|
fe7d713976 | ||
|
|
074b1b508d | ||
|
|
d6e6fac2c7 | ||
|
|
795322c8b4 | ||
|
|
57356bd0e3 | ||
|
|
7ae2c4c8dc | ||
|
|
779e5594d3 | ||
|
|
55e29e0a4b | ||
|
|
ba16dc6f97 | ||
|
|
2d364b5b8f | ||
|
|
05f8d52f63 | ||
|
|
46895e4216 | ||
|
|
2def740295 | ||
|
|
474976cd3c | ||
|
|
d2b0ebc218 | ||
|
|
c12a2b9171 | ||
|
|
38f695ba1f | ||
|
|
206b1ac3e9 | ||
|
|
f84896213a | ||
|
|
862776b011 | ||
|
|
647ead8b6d | ||
|
|
0832598302 | ||
|
|
ad16fa3e39 | ||
|
|
0c7395671b | ||
|
|
cc9867e7b1 | ||
|
|
ee5ced1103 | ||
|
|
2fb85461bd | ||
|
|
a918f033d4 | ||
|
|
497d0f0e23 | ||
|
|
ccb77d6f18 | ||
|
|
161f2a7e55 | ||
|
|
15c9efc5b9 | ||
|
|
07df508513 | ||
|
|
9551ee6dcb | ||
|
|
99f91c2e21 | ||
|
|
1719467a70 | ||
|
|
f269ec294a | ||
|
|
1d6c71cf1e | ||
|
|
22883cbf3a | ||
|
|
829c61a02e | ||
|
|
5eb0319ce5 | ||
|
|
98fb742d4d | ||
|
|
69b5547820 | ||
|
|
8540cd29d6 | ||
|
|
55070cf54a | ||
|
|
c10cb9987a | ||
|
|
ade6af36c1 | ||
|
|
e4abbc12e8 | ||
|
|
6d67157860 | ||
|
|
2232361812 | ||
|
|
6dd688d834 | ||
|
|
d58f682e83 | ||
|
|
3642a35bb1 | ||
|
|
61034a5629 | ||
|
|
5090afc35b | ||
|
|
cb007be50d | ||
|
|
df703a7d0a | ||
|
|
7f5437ecfb | ||
|
|
d84d333876 | ||
|
|
f9b3dd6569 | ||
|
|
b385d56ee1 | ||
|
|
ea620fdae9 | ||
|
|
95209093de | ||
|
|
85c68bb484 | ||
|
|
afe65e7117 | ||
|
|
915862eeb5 | ||
|
|
02eb5536c3 | ||
|
|
60f30ad66c | ||
|
|
febf9e6a3d | ||
|
|
c760f136d3 | ||
|
|
74a37df635 | ||
|
|
c66e04064e | ||
|
|
abc29892a5 | ||
|
|
f7af7daa04 | ||
|
|
151e2a5490 | ||
|
|
1adfc3bb82 | ||
|
|
b1125d1521 | ||
|
|
617f7399cc | ||
|
|
b2ef7044ca | ||
|
|
22aee2d356 | ||
|
|
87ec7aea7d | ||
|
|
4af9a7d2c2 | ||
|
|
6e6456c657 | ||
|
|
8c5e0bd381 | ||
|
|
9e401e7a83 | ||
|
|
34eb29be72 | ||
|
|
86d93f9ab6 | ||
|
|
72e554a93a | ||
|
|
c27a1c4fb8 | ||
|
|
7359c6fa98 | ||
|
|
398460ff0e | ||
|
|
1abd65e106 | ||
|
|
68e572851b | ||
|
|
1214b53bd4 | ||
|
|
e5eefaafd2 | ||
|
|
dc62d5cc94 | ||
|
|
4b75c9e9cd | ||
|
|
4ec8c91d14 | ||
|
|
e2dc1a55e0 | ||
|
|
e2fb346901 | ||
|
|
ea0a212450 | ||
|
|
36a7c0100b | ||
|
|
55c2fa46ce | ||
|
|
e401d1f0c9 | ||
|
|
816399d9ed | ||
|
|
0ecb130732 | ||
|
|
e54a1791b9 | ||
|
|
7cf65f8c44 | ||
|
|
673382c1b6 | ||
|
|
ee0eaa9073 | ||
|
|
5703b7cc36 | ||
|
|
7ce50e58ef | ||
|
|
5967bc77f5 | ||
|
|
92a2c1c6f6 | ||
|
|
663896d300 | ||
|
|
4537e0fb2d | ||
|
|
9455b80417 | ||
|
|
6bbe62b280 | ||
|
|
af6c2759df | ||
|
|
766b370952 | ||
|
|
1b3e92019e | ||
|
|
15ede1295b | ||
|
|
bcc6df9d4d | ||
|
|
9fbc3b9d02 | ||
|
|
892c686fa9 | ||
|
|
d4b672bead | ||
|
|
9bc4419517 | ||
|
|
a85290e7b0 | ||
|
|
1944599ed6 | ||
|
|
b9c2913dcc | ||
|
|
a9e7fd15b5 | ||
|
|
c9185ff3f7 | ||
|
|
e911155437 | ||
|
|
a8f508f5bb | ||
|
|
3753d759e9 | ||
|
|
f3e8652e42 | ||
|
|
6cb95fd6b6 | ||
|
|
621dcaa69f | ||
|
|
d4ba59a2a0 | ||
|
|
710b76bd36 | ||
|
|
a284c3f193 | ||
|
|
ac99307bad | ||
|
|
3b983ebc71 | ||
|
|
1c660f428c | ||
|
|
68b9a81ccd | ||
|
|
32c43711df | ||
|
|
1e080bfff3 | ||
|
|
e484d1ac52 | ||
|
|
ef9212d2de | ||
|
|
50a40a66cc | ||
|
|
23cfa72cde | ||
|
|
ece607c285 | ||
|
|
0ad2d30d75 | ||
|
|
5c6063d466 | ||
|
|
53d1ab9948 | ||
|
|
405fde91d4 | ||
|
|
9cac07b5ec | ||
|
|
7207982a30 | ||
|
|
32f303a2c8 | ||
|
|
8dd8af3a54 | ||
|
|
3748b18b6d | ||
|
|
a9f2035041 | ||
|
|
b4aef2bba6 | ||
|
|
c4125df3da | ||
|
|
e497872bb1 | ||
|
|
56385802b8 | ||
|
|
5385a24c1e | ||
|
|
c3cec72ba4 | ||
|
|
77426872d7 | ||
|
|
a3e6d92fe7 | ||
|
|
48f3d7e9e4 | ||
|
|
8e57ee0759 | ||
|
|
dca1686851 | ||
|
|
abce4c7455 | ||
|
|
e874ee9224 | ||
|
|
4b74ea17fc | ||
|
|
f9cef92384 | ||
|
|
b930de0368 | ||
|
|
ad5eff7bec | ||
|
|
155f62c08a | ||
|
|
8e70766783 | ||
|
|
df4e3aea45 | ||
|
|
10275bd798 | ||
|
|
a788305bef | ||
|
|
dfa5643a05 | ||
|
|
5db2cb656a | ||
|
|
89e633a146 | ||
|
|
5b070532d7 | ||
|
|
9dbcb28821 | ||
|
|
beb26a4a40 | ||
|
|
80aa914cb7 | ||
|
|
cb6fcbf413 | ||
|
|
c3f8e0f05d | ||
|
|
88a18bdba3 | ||
|
|
bd6585df03 | ||
|
|
a893215efa | ||
|
|
53161b4cd2 | ||
|
|
7b504cb9b0 | ||
|
|
024f805661 | ||
|
|
9092e7f493 | ||
|
|
e8aa3ac678 | ||
|
|
b2d7edba81 | ||
|
|
4169af905d | ||
|
|
eee4867e11 | ||
|
|
dea70a7f3f | ||
|
|
6c1efd8ebc | ||
|
|
ef9cb4689b | ||
|
|
20ee8b46ec | ||
|
|
2eb4b8024a | ||
|
|
ec0905e5d3 | ||
|
|
9b9fa853fa | ||
|
|
56d0a368fe | ||
|
|
6035a46801 | ||
|
|
950172c4ab | ||
|
|
03b9158e48 | ||
|
|
a1c62dc09d | ||
|
|
5fabfe53d0 | ||
|
|
acdb7cda8f | ||
|
|
e32bb7b0d7 | ||
|
|
b85a9e66f4 | ||
|
|
32d016a72a | ||
|
|
94166110b1 | ||
|
|
48dd8e42d0 | ||
|
|
277bbd5cb5 | ||
|
|
6c8def7f1c | ||
|
|
d8c6981b24 | ||
|
|
c099012996 | ||
|
|
5270a62534 | ||
|
|
572396b0f5 | ||
|
|
b7d7ca7d4c | ||
|
|
fb973edbff | ||
|
|
a350659524 | ||
|
|
30064f497d | ||
|
|
343d32b4b3 | ||
|
|
b599a04701 | ||
|
|
a64515a5e4 | ||
|
|
a4c515db77 | ||
|
|
eaa7963259 | ||
|
|
70dd3a4403 | ||
|
|
078aaae1bb | ||
|
|
d3af35d5d6 | ||
|
|
b5d26ee289 | ||
|
|
636b725259 | ||
|
|
bf8671984b | ||
|
|
ecd2312284 | ||
|
|
821f46457a | ||
|
|
c49fd5ada1 | ||
|
|
9867a5a027 | ||
|
|
25259ed1b7 | ||
|
|
dd3c42fb60 | ||
|
|
954cbc837a | ||
|
|
351204cab6 | ||
|
|
a5313cc56b | ||
|
|
5059f755fd | ||
|
|
eb2fe79d51 | ||
|
|
a0d7f3cca0 | ||
|
|
044e3a2cd6 | ||
|
|
2ced41993c | ||
|
|
2a16b82fa3 | ||
|
|
9f37afbfcd | ||
|
|
b8dd014a95 | ||
|
|
ae2db5653c | ||
|
|
5cb910727c | ||
|
|
ba7e64cd86 | ||
|
|
7e3b137ed6 | ||
|
|
f1c5bd2246 | ||
|
|
e2be064bad | ||
|
|
ca7b8e57da | ||
|
|
c61abe4dc0 | ||
|
|
567d710a7f | ||
|
|
3309dc585f | ||
|
|
c894486ffa | ||
|
|
a7d055806d | ||
|
|
1486f0889f | ||
|
|
e6a785e84f | ||
|
|
4fe893a0a1 | ||
|
|
ddf6347d45 | ||
|
|
21dfbf8a3d | ||
|
|
d31cd084e5 | ||
|
|
f66604fdf7 | ||
|
|
416d82d14c | ||
|
|
43e0bd481c | ||
|
|
b7242ef914 | ||
|
|
bb5f84070b | ||
|
|
74c1737a10 | ||
|
|
ffddeb74f4 | ||
|
|
237966b8ad | ||
|
|
110040701f | ||
|
|
0a506c5552 | ||
|
|
d257906329 | ||
|
|
dc28940e45 | ||
|
|
a7df4b223f | ||
|
|
c8d51f5c06 | ||
|
|
76ad0b2f65 | ||
|
|
7a18df9cdd | ||
|
|
a92e1f2e0e |
@@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
SUPERUSERS=[""]
|
||||
|
||||
COMMAND_START=[""]
|
||||
|
||||
SESSION_RUNNING_EXPRESSION="别急呀,小真寻要宕机了!QAQ"
|
||||
|
||||
NICKNAME=["真寻", "小真寻", "绪山真寻", "小寻子"]
|
||||
|
||||
SESSION_EXPIRE_TIMEOUT=30
|
||||
|
||||
DEBUG=False
|
||||
# 服务器和端口
|
||||
HOST = 127.0.0.1
|
||||
PORT = 8080
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -1,6 +1,857 @@
|
||||
## 要不跑路吧
|
||||
<div align=center><img width="320" height="320" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs/zhenxun.jpg"/></div>
|
||||
|
||||
**中文** | [English](README_en-US.md)
|
||||

|
||||

|
||||

|
||||
|
||||
代码和人有一个能run就行了
|
||||
已经坐船到柬埔寨了,马上转船到金三角了(
|
||||
# 绪山真寻Bot
|
||||
****
|
||||
此项目基于 Nonebot2 和 go-cqhttp 开发,以 postgresql 作为数据库的QQ群娱乐机器人
|
||||
## 关于
|
||||
用爱发电,某些功能学习借鉴了大佬们的代码,因为绪山真寻实在太可爱了因此开发了
|
||||
绪山真寻bot,实现了一些对群友的娱乐功能和实用功能(大概)。
|
||||
|
||||
如果该项目的图片等等侵犯猫豆腐老师权益请联系我删除!
|
||||
|
||||
## 声明
|
||||
此项目仅用于学习交流,请勿用于非法用途
|
||||
|
||||
## 真寻的帮助
|
||||
请对真寻说: '真寻帮助' or '管理员帮助' or '超级用户帮助' or '真寻帮助 指令'
|
||||
|
||||
|
||||
## 功能列表
|
||||
<details>
|
||||
<summary>已实现的功能</summary>
|
||||
|
||||
### 已实现的常用功能
|
||||
- [x] 昵称系统(群与群与私聊分开.)
|
||||
- [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味
|
||||
- [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
|
||||
- [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] 原神黄历 (使用[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
|
||||
- [x] 原神今日素材
|
||||
- [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
|
||||
- [x] 金币红包
|
||||
- [x] 微博热搜
|
||||
|
||||
- [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] 撤回 (使用[nonebot-plugin-withdraw](https://github.com/MeetWq/nonebot-plugin-withdraw)插件)
|
||||
- [x] 滴滴滴-(用户对超级用户发送消息)
|
||||
- [x] 金币红包/金币排行
|
||||
- [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行
|
||||
- [x] 网易云热评
|
||||
- [x] 念首古诗
|
||||
- [x] 获取b站视频封面
|
||||
- [x] 通过PID获取图片
|
||||
- [x] 功能统计可视化
|
||||
|
||||
### 已实现的管理员功能
|
||||
- [x] 更新群组成员信息
|
||||
- [x] 95%的群功能开关 (基于[nonebot_plugin_manager](https://github.com/Jigsaw111/nonebot_plugin_manager)插件修改)
|
||||
- [x] 查看群内被动技能状态
|
||||
- [x] 自定义群欢迎消息(是真寻的不是管家的!)
|
||||
- [x] .ban/.unban(支持设置ban时长)= 黑白名单
|
||||
- [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数
|
||||
- [x] 上传图片/连续上传图片 (上传图片至指定图库)
|
||||
- [x] 移动图片 (同上)
|
||||
- [x] 删除图片 (同上)
|
||||
|
||||
### 已实现的超级用户功能
|
||||
- [x] 添加/删除管理(是真寻的管理员权限,不是群管理员)
|
||||
- [x] 开启/关闭指定群的广播通知
|
||||
- [x] 广播
|
||||
- [x] 自检(检查系统状态)
|
||||
- [x] 所有群组/所有好友
|
||||
- [x] 退出指定群
|
||||
- [x] 更新好友信息/更新群信息
|
||||
- [x] /t(对用户进行回复或发送消息)
|
||||
- [x] 上传/删除/修改商品(需要编写对应的商品功能)
|
||||
- [x] 节日红包发送
|
||||
- [x] 修改群权限
|
||||
- [x] ban
|
||||
- [x] 添加/删除群白名单
|
||||
- [x] 更新色图
|
||||
- [x] 更新价格/更加图片(csgo开箱)
|
||||
- [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池
|
||||
- [x] 更新原神今日素材/更新原神资源信息
|
||||
- [x] PIX相关操作
|
||||
- [x] 检查更新真寻
|
||||
- [x] 重启
|
||||
- [x] 添加/删除群白名单
|
||||
- [x] 功能开关(更多设置)
|
||||
|
||||
#### 超级用户的被动技能
|
||||
- [x] 邀请入群提醒(别人邀请真寻入群)
|
||||
- [x] 添加好友提醒(别人添加真寻好友)
|
||||
|
||||
### 已实现的被动技能
|
||||
- [x] 进群欢迎消息
|
||||
- [x] 群早晚安
|
||||
- [x] 每日开箱重置提醒
|
||||
- [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv)
|
||||
- [x] 丢人爬(爬表情包)
|
||||
- [x] epic通知(每日发送epic免费游戏链接)
|
||||
- [x] 原神黄历提醒
|
||||
- [x] 复读
|
||||
|
||||
### 已实现的看不见的技能!
|
||||
- [x] 刷屏禁言检测
|
||||
- [x] 功能调用统计
|
||||
- [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban)
|
||||
- [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
|
||||
- [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能))
|
||||
- [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
|
||||
- [x] 群权限系统
|
||||
- [x] 定时更新权限
|
||||
</details>
|
||||
|
||||
## 功能具体指令
|
||||
<details>
|
||||
<summary>功能具体指令说明</summary>
|
||||
|
||||
### 常用功能
|
||||
|
||||
| 功能 | 指令 | 说明
|
||||
| ----------------------| :--------------------------------------:| :------------------------:
|
||||
| 签到 | 签到/我的签到/好感度排行/好感度总榜/好感度总榜\[显示我/屏蔽我] | 普通的签到插件,可以获得好感度和金币<br>好感度影响开箱次数和涩图触发概率,金币用于购买道具,俄罗斯轮盘赌注以及金币红包<br>好感度总榜,显示所有群的群员好感度排行,可通过命令好感度总榜\[显示我/屏蔽我] 来设置是否隐藏
|
||||
| 发送图片 | 美图/壁纸/萝莉 \[id](默认随机)/\[num]张图\[keyword] | 发送指定文件夹下的图片<br>示例:萝莉->发送img文件夹下luoli文件夹下的图片<br>在线搜索一些不色的图,示例:3张图米浴
|
||||
| 色图 | 色图/色图xx/n张色图/n张xx的色图/查色图(查询本地色图信息)/色图r【n<10】 | 色图r返回10张r18色图(仅私聊),并限制每日次数(默认5次)<br>其他示例:色图 真寻 <br>5张真寻的色图
|
||||
| 黑白草图 | 黑白草图/黑白图 \[文字] \[图片] | 整活生成器,示例:黑白图 我喜欢真寻 \[图片]
|
||||
| coser | coser/cos/括丝 | coser图片,说实话挺失望的,太色了
|
||||
| 骂我 | 骂我 | 就是发送钉宫的语音罢了
|
||||
| 戳一戳 | 戳一戳 | 随机发送钉宫语音 or 美图 or 萝莉图 or 文本
|
||||
| 模拟开箱 | 开箱 \[武器箱名称](默认随机)/N连开箱 \[武器箱名称](默认随机)/我的开箱/群开箱统计/我的金色 | 当不指定武器箱时默认随机,此功能需要先在/open_cases/config.py中编写指定武器箱数据,然后提前爬取价格,使用超级用户命令更新cookie后,再使用命令更新xx武器箱【注:未设置爬取频率,可能会被禁用api(请谨慎!!用小号!!)】
|
||||
| 鲁迅说过 | 鲁迅说过 \[文本] | 示例:鲁迅说过 真寻世界第一可爱
|
||||
| 假消息 | 假消息 \[网址] \[标题] \[内容](可选) \[图片](可选) | 构造虚假的分享消息
|
||||
| 商店系统 | 商店/我的金币/购买道具 \[名称或序号] \[数量](默认1)/使用道具 \[名称或序号] | 示例:<br>购买道具 1 3<br>购买道具 好感度双倍加持卡Ⅰ 3<br>使用道具 1<br>使用道具 好感度双倍加持卡Ⅰ
|
||||
| 抽卡系统 | 原神/明日方舟/赛马娘/坎公骑冠剑/碧蓝航线/阴阳师/公主连结(pcr)/FGO N抽/一井 | 详细帮助请查看: [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw) <br>示例:原神90抽
|
||||
| 我有一个朋友 | 我有一个朋友他说/想问问/\[文本] | 会将文本中的(他,她,它)替换成 '我'<br>示例:我有一个朋友想问问他喜不喜欢真寻
|
||||
| 昵称系统 | 以后叫我\[昵称]/以后请叫我\[昵称]/我是谁/我叫什么 | 此昵称会替换与真寻聊天中 '你' 的名称(群名片),群与群与私聊的昵称相互独立
|
||||
| 原神黄历 | 原神黄历 | 查看今日原神黄历,含有每日10:25的定时任务
|
||||
| 原神材料 | 今日素材 | 发送可莉特调的截图
|
||||
| 丘丘语翻译 | 丘丘翻译/丘丘一下/丘丘语翻译 | 示例:丘丘一下 mimi
|
||||
| 原神资源查询 | 原神资源查询 \[资源名称] \[路线]?/\[资源名称]在哪/哪里有\[资源名称]/原神资源列表 | 如果资源名称末尾添加‘路线’的话将生成残缺缺缺版的优先路径<br>示例:嘟嘟莲在哪<br>原神资源查询嘟嘟莲路线
|
||||
| 俄罗斯轮盘 | 装弹\[子弹数] \[金额](默认200)/开枪/结算/我的战绩/胜场排行/败场排行/欧洲人排行/慈善家排行 | 紧张刺激的群内小游戏,使用每日签到的金币作为赌注,具体玩法请发送 真寻帮助 俄罗斯轮盘
|
||||
| 红包系统 | 塞红包\[金额] \[数量](默认5)/抢/开/戳一戳/退回 | 仿微信明日方舟红包的样式(pil拼图大师!),每个红包金额随机生成,最多会是红包总金额的1/3,退回用于退回一分钟后还未开完的红包
|
||||
| 金币排行 | 金币排行 | 字面意思
|
||||
|网易云热评 | 到点了/12点了/网易云热评/网易云评论 | 防下塔
|
||||
| 古诗 | 念诗/念首诗/来首诗 | 突然文艺起来了
|
||||
|微博热搜 | 微博热搜/微博热搜[序号] | 快捷热搜查询方式
|
||||
| pil对图片的操作 | 修改尺寸/等比压缩/旋转图片/水平翻转/铅笔滤镜/模糊效果/锐化效果/高斯模糊/边缘检测/底色替换 | 选项较多,请直接发送 真寻图片帮助
|
||||
| BUFF皮肤底价查询 | 查询皮肤 \[武器名称] \[皮肤名称] | 网络不友好的话会经常超时<br>示例:查询皮肤 沙漠之鹰 印花集
|
||||
| 天气查询 | \[城市]天气 | 非常常见的插件,第一个入门插件
|
||||
| 疫情查询 | 疫情/查询疫情 \[城市名或省份名] | 示例:疫情杭州
|
||||
| bt磁力搜索 | bt \[关键词] \[页数](默认第1页) | 该功能仅仅提供给私聊,因为可以搜到一些色色的东西,示例:bt钢铁侠 5
|
||||
| 上车 | 略 | 直接查看真寻帮助 上车,每日限制次数(默认5)
|
||||
| 以图识番 | 识番 \[图片] | 以图搜翻,图片越清晰越完整正确率越高
|
||||
| 以图搜图 | 识图 (asc)? \[图片] | 参数asc更换搜索引擎为ascii2d,默认为saucenao
|
||||
| 点歌 | 点歌 \[歌名] | 网易云点歌小助手
|
||||
| 搜番 | 搜番 \[关键字] | 群聊只返回5个结果,私聊返回20个结果
|
||||
| epic白嫖游戏通知 | epic | 通知你又到了白嫖游戏的时候,可以不玩,不能没有
|
||||
| P站排行榜 | p站排行 \[排行类型参数](默认日榜) \[数量](可选) \[日期](可选)| 9种不同排行榜,r18类型仅可私聊,通过参数选择,查看真寻帮助p站排行<br>示例:p站排行榜 1 9 2018-4-25
|
||||
| 搜图 | 搜图 \[关键词] \[数量](可选) \[排序方式](可选) \[r18](可选)| r18仅可私聊,查看真寻帮助搜图
|
||||
| 通过PID搜索图片 | p搜 [pid] | 在群内使用此功能会在30秒内撤回
|
||||
| 翻译 | 英翻/日翻/韩翻/翻韩/翻日/翻英 \[文本] | 三种语言互相翻译
|
||||
| 获取b站视频封面 | b封面 [链接/av/bv/cv/直播id] | 快捷的封面获取方式
|
||||
| 群欢迎消息 | 群欢迎消息/查看群欢迎消息/查看当前群欢迎消息 | 查看给真寻设置的群欢迎消息
|
||||
| 自我介绍 | 自我介绍 | 没错,一份正经的真寻自我介绍
|
||||
| 我的权限 | 我的权限 | 真寻内部定义的一套权限系统
|
||||
| 我的信息 | 我的信息 | 唯一的作用就是看看什么时候加入群
|
||||
| 撤回 | 撤回 \[消息位置](默认为最新一条消息) | 按顺序撤回发送的消息,示例:撤回 1
|
||||
| 滴滴滴- | 滴滴滴- \[文本] | 用于用户联系真寻的超级用户
|
||||
|功能调用统计可视化 | 功能调用统计(自记录以来的功能调用统计)<br>周功能调用统计 [plugin_name]<br>月功能调用统计 [plugin_name]| 当plugin_name为空时为7天或30内的所有功能统计
|
||||
| pix | pix/PIX [tags/uid/pid:pid] [num] | 无参数时随机查看pix图库的图片(无r18),num数量默认=1,tags:查看相关tags图片,uid:查找相关画师图片,pid:pid:指定查看pid图片<br>示例:pix原神 3<br>pix23493844<br>pixpid:29429933
|
||||
| 添加pix关键词/uid/pid | 添加pix关键词/uid/pid *[关键词/uid/pid]| 添加关键词或uid或pid用于下次搜索,关键词搜索相关tag,uid会收录作者下收藏符合标准的作品,pid收录单张作品<br>示例:添加pix关键词 原神<br> 添加pixuid 123441<br>添加pixpid 2748937|
|
||||
| 查看pix图库 | 查看pix图库 [tags] | 查看已收录的tag相关图片数量<br>示例:查看pix图库 原神 莫娜
|
||||
|显示pix关键词 | 显示pix关键词 | 查看已收录的所有关键词/UID/PID
|
||||
|
||||
### 管理员功能
|
||||
|
||||
**群主与群管理员默认5级权限**
|
||||
|
||||
| 功能 | 权限等级 | 指令 | 说明
|
||||
| -------------| --------------| :--------------------------------------:| :------------------------:
|
||||
| 更新群组成员信息 | 1 | 更新群组成员信息/更新群组成员列表 | 存储群员的基本信息,虽然有自动更新,但备个命令以防万一
|
||||
| 群功能开关 | 2 | 开启/关闭\[指令名]功能 | 群帮助中左边带有√的功能都可以通过此命令开启或关闭,示例:开启色图
|
||||
| 查看群被动技能 | 2 | 群通知状态 | 详细请查看被动技能列表
|
||||
| 被动技能开关 | 2 | 开启/关闭被动技能 | 有时候花里胡哨通知也会很烦人
|
||||
| 自定义群欢迎消息 | 2 | 自定义群欢迎消息 \[文本] \[图片] | 文本和图片至少需要一个,在文本内添加"\[at]"字符串可以用来设置艾特进群的新群员
|
||||
| 黑名单 | 5 | .ban/.unban \[at] \[小时](可选) \[分钟](可选)| 不提供具体时间的话则ban掉永久,且权限低的用户无法unban高权限用户的ban,同级权限也无法进行ban/unban <br>示例:.ban@笨蛋 1 50
|
||||
| 刷屏检测相关 | 5 | 刷屏检测设置/设置检测时间 \[文本]/设置检测次数 \[文本]/设置禁言时长 \[分钟]| 非常讨厌刷屏的人,打算给他们一点教训
|
||||
| 上传图片 | 6 | 上传图片 \[图库] \[图片]... | 上传图片至指定图库,虽然并不打算开放给群员,但还是写了,支持批量图片<br>示例:上传图片 美图 \[图片]..
|
||||
| 删除图片 | 6 | 删除图片 [图库] \[图片id] | 通过指定本地图片id来删除指定图库的图片<br>示例:删除图片 美图 1
|
||||
| 移动图片 | 6 | 移动图片 \[移出的图库] \[移入的图库] \[图片id] | 移动指定图库中的图片到指定的新图库中,移入的图片id更改为移入图库的最后一位,移除的图库中原本图片的id又最后一位图片替代<br>示例:移动图片 美图 萝莉 22
|
||||
|
||||
### 超级用户功能
|
||||
|
||||
| 功能 | 指令 | 说明
|
||||
| ----------------------| :--------------------------------------:| :------------------------:
|
||||
| 权限增删 | 添加/删除权限 \[at] \[level]<br>添加/删除权限 [qq] [group] [level] | 用于添加或修改权限等级,且该权限不会被自动更新取消
|
||||
| 查看群组/好友 | 查看群组/好友 | 查看真寻添加的群组与好友
|
||||
| 广播 | 广播- [文本] | 广播所有群组
|
||||
| 更新色图 | 更新色图 | 更新群友搜索色图时保存的url
|
||||
| 回复 | /t | 命令较多,请查看/t帮助,省略群号则私聊用户(必须要有用户的好友)
|
||||
| 更新cookie | 更新cookie \[cookie] | 用于更新开箱数据和查询buff皮肤
|
||||
| 开启广播通知 | 开启/关闭广播通知 \[群号] | 用于开启/关闭是否对某些群进行广播(上边的广播方法)
|
||||
| 退群 | 退群 \[群号] | 用于退出某一些群
|
||||
| 检查系统状态 | 自检 | 略
|
||||
| 更新好友/群组信息| 更新好友/群组信息 | 包含自动更新,被t出群等等有更好的可视信息
|
||||
| 重载卡池 | 略 | 重载抽卡的游戏卡池,请查看 \[nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)
|
||||
| 添加商品 | 添加商品 \[名称]-\[价格]-\[描述]-\[折扣](小数)(可选)-\[限时时间](分钟)(可选) | 为真寻的商店添加一点点道具<br>示例:添加商品-昏睡红茶-300-一杯上好的奇怪红茶-0.9-60
|
||||
| 删除商品 | 删除商品 \[名称或序号] | 在真寻的商店中删除一点东西
|
||||
| 修改商品 | 修改商品 -name \[名称或序号] -price \[价格] -des \[描述] -discount \[折扣] -time \[限时]| 注意空格,不需要的参数可以不加<br>示例:修改商品 -name 1 -price 900 【修改序号为1的商品的价格为900】
|
||||
| 节日红包 | 节日红包 \[金额] \[数量] \[祝福语](可选) \[群号](可选)... | 群号支持批量,使用空格隔开,不使用群号则对所有群发送节日红包,节日红包有效时间为24小时,祝福语默认为“恭喜发财 大吉大利”<br>示例:节日红包 10000 15 真寻真可爱 123324423 23423423
|
||||
| 修改群权限 | 修改群权限 \[group] \[level] | 所以说这功能是对内鬼的无奈,默认群权限为5,默认无法使用 色图/coser/p站排行/搜图(这些功能都要9级权限)
|
||||
|更新原神今日素材| 更新原神今日素材 | 自动更新原神每日素材失败时可以手动触发
|
||||
|更新原神资源信息| 更新原神资源信息 | 除了每日自动更新的资源外,额外更新大地图
|
||||
| 清理数据 | 清理数据 | 清理 temp,rar,r18_rar 文件夹的文件数据
|
||||
|添加pix关键词/uid/pid | 添加pix关键词/uid/pid [keyword/pid:pid/uid:uid] [-f]? | 与普通功能相同,额外可以通过参数 -f 强制通过检测
|
||||
|删除pix关键词 | 删除pix关键词 *[keyword/uid/pid:pid] | 删除已收录的keyword/uid/pid
|
||||
|更新pix关键词 | 更新pix关键词 [keyword/pid:pid/uid:uid] [num]| 更新keyword,uid,pid或指定uid,pid,未指定时,则更新全部,当num未指定时为keyword/uid/pid更新全部<br>示例:更新pix关键词<br>更新pix关键词uid 8<br>更新pix关键词pid:83457477
|
||||
|删除pix图片 | 删除pix图片 [*pid] [-b]? | [-b]参数为删除的同时加入黑名单(不再更新),虽然是pid,但是_p也可以<br>示例:删除pix图片3458344 8235234_p1 -b
|
||||
|显示pix关键词 | 显示pix关键词 | 与普通功能相同,额外显示待收录和黑名单
|
||||
|pix检测更新 | pix检测更新 [update]? | 检测从未更新过的pid或uid,-update参数将在检测后直接更新未更新过的pid或uid<br>示例:pix检测更新 update
|
||||
| 检查更新真寻 | 检查更新真寻 | 不再需要麻烦的clone第一步
|
||||
| 关闭功能 | 关闭[功能] [group/private]/*[群号] | 关闭色图:维护功能(关闭总开关)<br>关闭色图 g:在群内限制功能<br>关闭色图 p:在私聊限制功能<br>关闭色图 1234678:禁用12345678的色图功能
|
||||
| 群白名单 | 添加/删除群白名单 *[群号] | 白名单内的群不会受到 维护 限制
|
||||
|
||||
</details>
|
||||
|
||||
## 部分功能展示
|
||||
<details>
|
||||
<summary>部分功能展示及说明</summary>
|
||||
|
||||
### 帮助以及开关(功能控制)
|
||||
|
||||
群帮助将会在功能左侧展示该功能的开关,带有√或×的功能代表可以开关<br>
|
||||
此插件使用 [nonebot_plugin_manager](https://github.com/Jigsaw111/nonebot_plugin_manager) 并魔改一点实现
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
<br>
|
||||
如果你希望某功能暂时停用<br>
|
||||
私聊发送 npm block xx (xx=功能名)来锁定<br>
|
||||
使用npm unblock xx 进行解锁
|
||||
|
||||

|
||||

|
||||
|
||||
### 签到
|
||||
普普通通的签到,设置影响开箱次数和涩图触发成功的概率(可配置)<br>
|
||||
开箱次数 = 初始开箱数量 + 好感度 / 3<br>
|
||||
金币 = random.randint(100) + random.randint(好感度)【好感度获取的金币不会超过200】
|
||||
|
||||

|
||||
|
||||
### 黑白草图
|
||||
|
||||
整活生成器(从未设想的道路)
|
||||
|
||||

|
||||
|
||||
### 发送文件夹下随机图片
|
||||
|
||||
提供了 美图589(获取该图库下文件名589.jpg的图片)方法,图库内图片名称需要有序(如:0.jpg,1.jpg....)
|
||||
|
||||

|
||||
|
||||
### 色图
|
||||
|
||||
略,send_setu/check_setu_hash.py文件用于记录涩图hash和检测文件名是否连贯(例如:0.jpg, 1.jpg....)
|
||||
|
||||
### 开箱(csgo模拟开箱)
|
||||
|
||||
我的开箱/群开箱统计/我的金色 功能是对开箱数据的统计展示 <br>
|
||||
|
||||
目前支持的武器箱(数据已备好):
|
||||
* 狂牙大行动武器箱
|
||||
* 突围大行动武器箱
|
||||
* 命悬一线武器箱
|
||||
* 裂空武器箱
|
||||
* 光谱武器箱
|
||||
<br>
|
||||
BUFF账号可能会因为短时间内访问api次数过多被禁止访问api!!
|
||||
如果是第一次启动请先使用命令 “更新价格”, “更新图片” (需要配置cookie!!如果经常超时请设置代理,配置文件中的 buff_proxy!)<br>
|
||||
如果需要配置新的箱子,请在.config.py中配置好该箱子中的皮肤,且列表名是箱子名称的大写拼音<br>
|
||||
示例:光谱武器箱 GUANGPU_CASE_KNIFE,GUANGPU_CASE_RED...后面的颜色代表皮肤品质
|
||||
|
||||

|
||||
|
||||
|
||||
### BUFF皮肤底价查询
|
||||
|
||||
需要配置cookie!!!!!!!!<br>
|
||||
如果经常超时请设置代理,配置文件中的 buff_proxy!
|
||||
|
||||

|
||||
|
||||
|
||||
### coser
|
||||
|
||||

|
||||
|
||||
### 鸡汤/语录
|
||||
|
||||

|
||||
|
||||
### 骂我
|
||||
|
||||

|
||||
|
||||
### 鲁迅说
|
||||
|
||||
此插件使用 [nonebot2_luxun_says](https://github.com/NothAmor/nonebot2_luxun_says)
|
||||
|
||||

|
||||
|
||||
### 假消息
|
||||
|
||||

|
||||
|
||||
### 商店系统
|
||||
|
||||
商店内的道具支持自定义,但需要写触发后的效果...整不出活,到头来也就增加好感度概率的商品
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 昵称系统
|
||||
|
||||
养成方法第一步,让可爱的小真寻叫自己昵称!(替换ai中的'你'等等)
|
||||
|
||||

|
||||

|
||||
|
||||
### 抽卡(8种手游的抽卡)
|
||||
|
||||
已单独分离并上传至nb2商店,不再放图片了,项目地址:[nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)
|
||||
|
||||
### 我有一个朋友...
|
||||
|
||||
使用大佬的插件 [cappuccilo_plugins](https://github.com/pcrbot/cappuccilo_plugins#%E7%94%9F%E6%88%90%E5%99%A8%E6%8F%92%E4%BB%B6)
|
||||
|
||||

|
||||
|
||||
|
||||
### 原神黄历/今日素材/丘丘语翻译/地图资源查询
|
||||
|
||||
使用大佬的插件 [Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)
|
||||
|
||||
### 对图片的操作
|
||||
|
||||
只是一些简单对图片操作(娱乐整活)
|
||||
|
||||

|
||||
|
||||
### 识番
|
||||
|
||||
使用大佬的插件 [XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)
|
||||
|
||||

|
||||
|
||||
|
||||
### 识图
|
||||
|
||||
使用nb2商店插件 [nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher) (可配置图片返回的最大数量)
|
||||
|
||||

|
||||
|
||||
### epic免费游戏
|
||||
|
||||
访问rsshub获取数据解析<br>可以不玩,不能没有
|
||||
|
||||

|
||||
|
||||
|
||||
### P站排行/搜图
|
||||
|
||||
访问rsshub获取数据解析
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 翻译
|
||||
|
||||

|
||||
|
||||
### 自定义群欢迎消息
|
||||
|
||||
关键字 [at] 判断是否艾特入群用户
|
||||
|
||||

|
||||
|
||||
### 查看当前群欢迎消息
|
||||
|
||||

|
||||
|
||||
### 自检
|
||||
|
||||

|
||||
|
||||
### .ban/.unban
|
||||
|
||||

|
||||
|
||||
### 查看被动技能(被动技能除复读外都提供了开关)
|
||||
|
||||

|
||||
|
||||
### 自我介绍
|
||||
|
||||
只是一段简单自我介绍,但是,还是想放上来
|
||||
|
||||

|
||||
|
||||
### 我的信息/我的权限
|
||||
|
||||

|
||||
|
||||
### 金币红包
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### 俄罗斯轮盘
|
||||

|
||||

|
||||

|
||||
|
||||
<br>
|
||||
|
||||
### 其他
|
||||
|
||||
点歌:使用 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2) 插件<br>
|
||||
<br><br>
|
||||
## 其他功能请自己试一试 )
|
||||
|
||||
</details>
|
||||
|
||||
## 部署
|
||||
|
||||
```
|
||||
|
||||
# 配置gocq
|
||||
|
||||
在 https://github.com/Mrs4s/go-cqhttp 下载Releases最新版本,运行后选择反向代理,
|
||||
后将gocq的配置文件config.yml中的universal改为universal: ws://127.0.0.1:8080/cqhttp/ws
|
||||
|
||||
# 获取代码
|
||||
git clone https://github.com/HibiKier/zhenxun_bot.git
|
||||
|
||||
# 进入目录
|
||||
cd zhenxun_bot
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 进行基础配置
|
||||
####请查看 配置 部分####
|
||||
|
||||
# 开始运行
|
||||
python bot.py
|
||||
```
|
||||
|
||||
## 配置(暂时不更新 json 配置,请将USE_CONFIG_FILE设置为False)
|
||||
在 configs/config.py 中的 USE_CONFIG_FILE,默认为False
|
||||
|
||||
```
|
||||
1.在.env.dev文件中
|
||||
|
||||
SUPERUSERS = [""] # 填写你的QQ
|
||||
|
||||
2.在configs/config.py文件中
|
||||
必填:
|
||||
1. API KEY
|
||||
2.数据库配置
|
||||
|
||||
在./configs/config.py中配置基本配置(除API KEY ,数据库和代理外都含有默认值)
|
||||
在./configs/path_config.py配置路径(含有默认配置)
|
||||
|
||||
########(暂时不更新 json 配置,请将USE_CONFIG_FILE设置为False,直接进入./configs/config.py进行配置)
|
||||
# 是否使用配置文件(为True时这将会生成三份配置文件
|
||||
./config.json:主要配置
|
||||
./configs/plugins2cmd_config.json: 功能模块与对应命令配置
|
||||
./configs/other_config.json: 一些插件配置)
|
||||
|
||||
USE_CONFIG_FILE = True
|
||||
|
||||
# 如果不使用配置文件,将USE_CONFIG_FILE设置为False
|
||||
#可在./configs/config.py文件中修改配置,在./configs/path_config.py修改资源路径
|
||||
# 已在./configs/config.py和./configs/path_config.py中为各个配置提供注解!
|
||||
```
|
||||
|
||||
## 配置文件注解(如果使用配置文件的话)
|
||||
<details>
|
||||
<summary>配置文件注解</summary>
|
||||
./config.json
|
||||
|
||||
```
|
||||
{
|
||||
# 必填(影响功能运行)
|
||||
"apikey": {
|
||||
"LOLICON_KEY": "", # loliconAPI,缺失会导致色图功能异常
|
||||
"TL_KEY": [] # 图灵Key(为什么要用列表?因为白嫖用户能拿5个apikey,每个apikey每日限制100条)
|
||||
},
|
||||
|
||||
# (必填!!!)
|
||||
# 数据库配置(如果填写了bind,后面就不用再填了)只是帮你拼接好)
|
||||
# 示例:"bind": "postgresql://user:password@127.0.0.1:5432/database"
|
||||
"sql": {
|
||||
"bind": "",
|
||||
"sql_name": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"address": "",
|
||||
"port": "",
|
||||
"database": ""
|
||||
},
|
||||
|
||||
# 路径设置(不填则使用默认)
|
||||
"path": {
|
||||
"IMAGE_PATH": "", # 图片路径
|
||||
"VOICE_PATH": "", # 音频路径
|
||||
"TXT_PATH": "", # 文本路径
|
||||
"LOG_PATH": "", # 日志路径
|
||||
"DATA_PATH": "", # 数据路径
|
||||
"DRAW_PATH": "" # 抽卡数据路径
|
||||
"TEMP_PATH": "" # 临时图片路径
|
||||
},
|
||||
|
||||
# 代理设置
|
||||
"proxy": {
|
||||
"system_proxy": "", # 系统代理
|
||||
"buff_proxy": "" # buff代理
|
||||
},
|
||||
|
||||
# RSSHUB地址
|
||||
"rsshub": {
|
||||
"RSSHUBAPP": "https:\/\/docs.rsshub.app\/"
|
||||
},
|
||||
|
||||
# 各个管理员功能 对应的 权限
|
||||
"level": {
|
||||
"DELETE_IMG_LEVEL": 7,
|
||||
"MOVE_IMG_LEVEL": 7,
|
||||
"UPLOAD_LEVEL": 6,
|
||||
"BAN_LEVEL": 5,
|
||||
"OC_LEVEL": 2,
|
||||
"MUTE_LEVEL": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
./configs/plugins2cmd_config.json
|
||||
```
|
||||
# 单个例子注解
|
||||
"send_img": [
|
||||
"发送图片",
|
||||
"萝莉",
|
||||
"美图",
|
||||
"壁纸"
|
||||
]
|
||||
# 发送 关闭发送图片/关闭萝莉/关闭美图/关闭壁纸 都将触发命令 关闭send_img
|
||||
```
|
||||
|
||||
./configs/other_config.json
|
||||
```
|
||||
{
|
||||
"base": {
|
||||
# 图库配置,会影响 上传/删除/移动/发送图片等功能
|
||||
"IMAGE_DIR_LIST": [
|
||||
"色图",
|
||||
"美图",
|
||||
"萝莉",
|
||||
"壁纸"
|
||||
],
|
||||
"BAN_RESULT": "才不会给你发消息." # 当被ban用户触发命令后发送的消息
|
||||
},
|
||||
|
||||
"bool": {
|
||||
"AUTO_ADD_FRIEND": true, # 是否自动添加好友
|
||||
"DOWNLOAD_SETU": true # 是否下载bot发送过的色图(不会重复)
|
||||
},
|
||||
|
||||
"max_count": {
|
||||
"MAXINFO_REIMU": 7, # 上车(reimu)功能查找目的地的最大数
|
||||
"COUNT_PER_DAY_REIMU": 5, # 每日上车(reimu)次数限制
|
||||
"MAXINFO_BT": 10, # bt功能单次查找最大数
|
||||
"MAXINFO_PRIVATE_ANIME": 20, # 私聊搜索动漫返回的最大数量
|
||||
"MAXINFO_GROUP_ANIME": 5, # 群搜索动漫返回的最大数量
|
||||
"MAX_FIND_IMG_COUNT": 3, # 识图最大返回数
|
||||
"MAX_SIGN_GOLD": 200, # 签到好感度加成额外获得的最大金币数
|
||||
"MAX_SETU_R_COUNT": 5, # 每日色图r次数限制
|
||||
},
|
||||
|
||||
"probability": {
|
||||
"INITIAL_SETU_PROBABILITY": 0.7, # 涩图触发的基础概率(触发概率 = 基础概率 + 好感度)
|
||||
"FUDU_PROBABILITY": 0.7 # 复读概率
|
||||
},
|
||||
|
||||
# 注:即在 MALICIOUS_CHECK_TIME 时间内触发相同命令 MALICIOUS_BAN_COUNT 将被ban MALICIOUS_BAN_TIME 分钟
|
||||
"malicious_ban": {
|
||||
"MALICIOUS_BAN_TIME": 30, # 恶意触发命令被ban的时长(分钟)
|
||||
"MALICIOUS_BAN_COUNT": 4, # 恶意触发命令的规定次数
|
||||
"MALICIOUS_CHECK_TIME": 5 # 恶意触发命令的规定时间(秒)
|
||||
},
|
||||
"open_case": {
|
||||
"INITIAL_OPEN_CASE_COUNT": 20 # 每日开箱的基本数量(总数量=基本数量 + 好感度/3)
|
||||
},
|
||||
|
||||
# 注:即在 MUTE_DEFAULT_TIME 内发送相似(包含)消息超过 MUTE_DEFAULT_COUNT 将会被 MUTE_DEFAULT_DURATION 分钟
|
||||
# 这只是默认配置,各个群可以自由设置群内刷屏检测配置
|
||||
"mute": {
|
||||
"MUTE_DEFAULT_COUNT": 10, # 刷屏检测默认检测最大次数
|
||||
"MUTE_DEFAULT_TIME": 7, # 刷屏检测默认规定时间(秒)
|
||||
"MUTE_DEFAULT_DURATION": 10 # 刷屏检测默认禁言时长(分钟)
|
||||
},
|
||||
"other": {
|
||||
"UPDATE_GOCQ_GROUP": [], # 是否需要为某些群上传最新版的gocq?
|
||||
"ADMIN_DEFAULT_AUTH": 5 # 默认群管理员权限
|
||||
},
|
||||
|
||||
# 管理员功能 和 对应的 权限
|
||||
"auth": {
|
||||
"admin_plugins_auth": {
|
||||
"admin_bot_manage": 2,
|
||||
"ban": 5,
|
||||
"delete_img": 7,
|
||||
"move_img": 7,
|
||||
"upload_img": 6,
|
||||
"admin_help": 1,
|
||||
"mute": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## 更新
|
||||
|
||||
### 2021/8/17
|
||||
|
||||
* 新增配置CHECK_NOTICE_INFO_CD,修改群权限,个人权限检测各种检测的提示消息cd
|
||||
* 新增功能‘连续上传图片’功能,直到输入‘stop’停止
|
||||
* 新增功能维护时白名单以及对应命令(白名单中的群聊不受维护限制)
|
||||
* 新增ALAPI功能,微博热搜,可以通过序号来查看部分热搜内容
|
||||
* 新增配置NICKNAME,偶尔也想换换名字的说(自我介绍仅当NICKNAME=真寻时生效)
|
||||
* 提供 更新信息 命令,可以使群员查看更新内容(可开关,与其他功能无异,但不会被统计,该命令图片由自动更新生成)
|
||||
* 超级用户可以通过私聊来对指定qq进行ban/unban
|
||||
* 超级用户帮助改为图片形式
|
||||
* 公开图库删除‘色图’
|
||||
* 群权限检测,个人权限检测,功能开关检测合并,权限检测顺序:个人权限 > 群权限 > 插件开关 >超级用户禁用某群插件 > 超级用户限制群里插件 > 插件维护检测
|
||||
* 重写群功能管理,超级用户可对群/私聊分别禁用,也可禁用指定群指定功能,新增命令‘功能状态’,超级用户关闭功能提供参数(默认ALL):group/g(群聊),private/p(私聊)
|
||||
* 超级用户不会被任何权限等检测阻挡
|
||||
* 不会重复复读,复读消息只会发送一次
|
||||
* b站转发解析支持b23.tv,www.bilibili.com链接,cv专栏(playwright截图,压缩倍率0.5,较慢且文字可能不清晰,后优化)
|
||||
* 我有一个朋友功能,当艾特一个对象时,‘朋友’改为艾特对象的群名片或昵称
|
||||
* 修复‘上传/删除/移动图片’目录不正确
|
||||
* 修复天气功能,当城市名在‘天气’后时报错
|
||||
* 修复配置INITIAL_SETU_PROBABILITY不生效
|
||||
|
||||
### 2021/8/10
|
||||
|
||||
* 重复的好友请求和邀群提示在5分钟内不会重复提示
|
||||
* 疫情会优先检查城市,城市省份市区重名时请添加‘市’
|
||||
* 添加命令‘原神资源查找’,‘设置cookie’
|
||||
* 添加配置AUTO_UPDATE_ZHENXUN,是否自动更新真寻,默认True
|
||||
* 添加配置MAX_RUSSIAN_BET_GOLD,俄罗斯轮盘赌注最大金额,默认1000
|
||||
* 检查更新真寻定时任务时间改为12 : 00
|
||||
* 添加功能能不能好好说话(nbnhhsh)
|
||||
* 添加功能随机roll,无参为数字,有参为随机参数
|
||||
* 添加linux重启脚本以及重启命令‘重启’(建议首次生成restart.sh先查看命令是否正确)
|
||||
* 修复管理员功能的权限检测
|
||||
* 修复丢人爬开关
|
||||
|
||||
### 2021/8/6
|
||||
|
||||
* 天气查询会优先遍历城市
|
||||
* 添加自动更新真寻命令
|
||||
|
||||
##### 如果你的版本为 2021/8/4,可以直接复制plugins/check_zhenxun_update后,通过指令来更新真寻
|
||||
|
||||
### 2021/8/4
|
||||
|
||||
* 修改天气与疫情城市数据,改为api获取,丰富疫情的回复消息
|
||||
* 原神资源查询,大地图将被压缩至9M,且启动时当大地图存在时不再自动更新地图
|
||||
* 下载数据库内色图时将直接存储至_setu,不再存储至临时文件
|
||||
* 重复的好友请求或邀请请求在一定时间不会重复发送提醒
|
||||
* 添加每日自动清理临时图片定时任务
|
||||
* 修复金币排行显示
|
||||
* 修复无法正常关闭戳一戳功能
|
||||
|
||||
### 2021/7/30
|
||||
|
||||
* 重构代码,进行优化,添加注释,删除冗余代码,降低代码耦合
|
||||
* 添加功能:PIX(一套快捷的pixiv存图命令,自建图库存储url等信息?意在获取自己或群友xp的图)
|
||||
* 添加功能:清理临时图片文件(temp/rar/r18_rar文件夹)
|
||||
* 添加额外定时任务(5分钟检测一次),解决加入新群时无法及时为管理员提供权限
|
||||
* 添加配置ALAPI_AI_CHECK,开关AI回复文本检测
|
||||
* 添加配置IMPORT_DEFAULT_SHOP_GOODS,控制是否导入内置的三个商品(好感度加持卡ⅠⅡⅢ)
|
||||
* 添加配置ONLY_USE_LOCAL_SETU,仅仅使用本地色图(有的话),提升速度,但无法在线搜索色图和保存链接
|
||||
* 添加配置WITHDRAW_SETU_TIME,是否需要延迟撤回色图,可配置仅群里,私聊或全部
|
||||
* 好友请求,入群请求,滴滴滴-,/t,被踢出群提醒,的提示消息更加丰富
|
||||
* 彻底重写原神资源查找,添加规划路线(路线残缺缺缺缺版,有空补)添加命令‘更新原神资源信息’,强制更新地图等资源
|
||||
* 优化色图和P站排行/搜图检测用户是否正在触发命令代码
|
||||
* 当群最后发言大于36小时,也会关闭广播通知
|
||||
* 功能维护时超级用户依然可以调用(苦了谁都不能苦了自己)
|
||||
* 修复获取赛马娘UP公告
|
||||
* 重写 色图/更新色图
|
||||
* 色图数据存储改为数据库,启动时会更新之前的色图数据(有的话),更新完毕后会删除原数据文件,如果需要保留请提前备份,
|
||||
* lolicon api改为v2
|
||||
* 取消r18次数限制
|
||||
* 单次搜索至多保存100条链接
|
||||
* 添加定时撤回
|
||||
* 暂时取消上传/删除色图
|
||||
*
|
||||
* 更新建议(不要替换你的data和resources文件夹!)
|
||||
* 删除configs,plugins,services,utils,models文件夹重新clone
|
||||
* 删除多余文件夹,resources/img/genshin/seek_god_eye
|
||||
* 清空resources/img/genshin/genshin_icon文件夹,仅保留box.png和box_alpha.png
|
||||
* 替换bot.py
|
||||
|
||||
### 2021/7/27
|
||||
|
||||
* 原神今日素材改为单张截图+拼图,更新文件utils/img_utils.py及plugins/genshin/material_remind/__init__.py
|
||||
|
||||
### 2021/7/26
|
||||
|
||||
* 修复原神今日素材稻妻城开放后截图不完整的问题
|
||||
|
||||
### 2021/7/14
|
||||
|
||||
* 原神今日素材自动更新时间由 00:01 -> 04:01 [#issues7](https://github.com/HibiKier/zhenxun_bot/issues/7)
|
||||
* 小问题的修复和优化
|
||||
|
||||
### 2021/7/12
|
||||
|
||||
* 修复开箱功能单抽出金时存储格式错误导致 ‘我的金色’ 无法正常发送图片
|
||||
* 小问题的修复和优化
|
||||
|
||||
### 2021/7/6
|
||||
|
||||
* 识番功能 trace.moe 替换为新API(旧API已失效)
|
||||
* 小问题的修复和优化
|
||||
|
||||
### 2021/6/30
|
||||
|
||||
* 将plugin2name和plugin2level合并为plugin2info
|
||||
* util改为utils(。。!)
|
||||
* 修复当用户发送速度极快时开箱会突破每日限制
|
||||
* 新增功能:通过PID获取图片
|
||||
* 发送图片新增功能:搜索图片
|
||||
* 功能统计可视化
|
||||
* 新增命令:好感度总排行
|
||||
* 原神每日素材改为从"可莉特调"截图,提供命令‘更新原神每日素材’和定时任务
|
||||
* 修复月功能统计错误的问题
|
||||
|
||||
### 2021/6/24
|
||||
|
||||
* 添加了一些ALAPI:网易云热评,获取b站视频封面,古诗(需要填写ALAPI_TOKEN)
|
||||
* 如果填写了ALAPI_TOKEN,将会检测备用接口回复的文本是否合规
|
||||
* 优化了色图,当搜索色图下载失败时,会从本地色图库中发送相关tag色图
|
||||
* 当网易云点歌繁忙时会尝试多次点歌
|
||||
|
||||
### 2021/6/23
|
||||
|
||||
* 添加功能:群权限(所以说内鬼都快爬,可以在configs/config.py中修改各个功能的权限等级)
|
||||
* 优化了数据统计,将以7天,30天为周期,为将来更方便实现数据可视化
|
||||
* 更新坎公骑冠剑UP卡池
|
||||
* 修复赛马娘UP卡池
|
||||
* 修复一些小问题
|
||||
|
||||
### 2021/6/18
|
||||
|
||||
* 修复p站排行,搜图因网络问题爆炸时没有具体回复
|
||||
* 更换色图显示方式为 id,title,author,pid
|
||||
* 修复修改商品后商品顺序改变
|
||||
* 滴滴滴- 和 /t支持图片回复
|
||||
* 将/t回复更加简单(可以通过序号),且可以直接发送群
|
||||
* 修复bt功能无法交互
|
||||
|
||||
### 2021/6/17
|
||||
|
||||
* 修复p站排行,搜图因网络问题爆炸时没有具体回复
|
||||
* 更换色图显示方式为 id,title,author,pid
|
||||
|
||||
### 2021/6/15
|
||||
* 修改了‘帮助’功能,具体为‘帮助 指令名’,未指定指令名时则为查看全部功能列表
|
||||
* 修改了色图的存储数据格式
|
||||
* 色图功能搜索的色图改为随机从urls中随机抽取
|
||||
* 将商品数据存储入数据库,提供 '增加/删除/修改商品' 指令
|
||||
* 商店列表图片不再使用固定背景图,改为直接拼图
|
||||
* 增加功能:俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行
|
||||
* 增加功能:金币红包(节日红包与群红包相互独立)
|
||||
* 金币排行
|
||||
* 重写一个朋友插件
|
||||
* 其他微小调整
|
||||
|
||||
### 2021/6/4
|
||||
* 重写BT功能
|
||||
* 进行一些BUG修复和微小调整
|
||||
* 添加撤回功能[nonebot-plugin-withdraw](https://github.com/MeetWq/nonebot-plugin-withdraw)
|
||||
* 为色图功能添加额外的 上传色图 和 删除色图方法(影响hash)
|
||||
|
||||
### 2021/5/26
|
||||
* 将语录源更换为一言api
|
||||
|
||||
|
||||
## Todo
|
||||
- [ ] 提供更多对插件的控制
|
||||
- [ ] 明日方舟卡片式的签到..(大概)
|
||||
- [ ] 更多的群管理功能
|
||||
- [ ] 数据清理控制
|
||||
- [ ] docker容器
|
||||
|
||||
## 感谢
|
||||
[Onebot](https://github.com/howmanybots/onebot)
|
||||
[go-cqhttp](https://github.com/Mrs4s/go-cqhttp)
|
||||
[nonebot2](https://github.com/nonebot/nonebot2)
|
||||
[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)
|
||||
[cappuccilo_plugins](https://github.com/pcrbot/cappuccilo_plugins#%E7%94%9F%E6%88%90%E5%99%A8%E6%8F%92%E4%BB%B6)
|
||||
[nonebot-plugin-withdraw](https://github.com/MeetWq/nonebot-plugin-withdraw)
|
||||
[nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)
|
||||
[nonebot_plugin_manager](https://github.com/Jigsaw111/nonebot_plugin_manager)
|
||||
[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)
|
||||
[nonebot2_luxun_says](https://github.com/NothAmor/nonebot2_luxun_says)
|
||||
[AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
## 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(
|
||||
@@ -1,3 +0,0 @@
|
||||
## 要不跑路吧
|
||||
代碼和人有一個能run就行了
|
||||
已經坐船到柬埔寨了,馬上轉船到金三角了(
|
||||
@@ -0,0 +1,21 @@
|
||||
import nonebot
|
||||
from nonebot.adapters.cqhttp import Bot as CQHTTPBot
|
||||
from services.db_context import init, disconnect
|
||||
|
||||
nonebot.init()
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter("cqhttp", CQHTTPBot)
|
||||
config = driver.config
|
||||
driver.on_startup(init)
|
||||
driver.on_shutdown(disconnect)
|
||||
nonebot.load_builtin_plugins()
|
||||
nonebot.load_plugins("plugins")
|
||||
nonebot.load_plugins("plugins/shop")
|
||||
nonebot.load_plugins("plugins/genshin")
|
||||
nonebot.load_plugins("plugins/alapi")
|
||||
nonebot.load_plugins("plugins/pix_gallery")
|
||||
nonebot.load_plugins("plugins/admin_bot_manage")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
nonebot.run()
|
||||
@@ -0,0 +1,229 @@
|
||||
from typing import List, Optional, Tuple
|
||||
from services.service_config import TL_M_KEY, SYSTEM_M_PROXY, ALAPI_M_TOKEN
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
# 是否使用配置文件
|
||||
USE_CONFIG_FILE: bool = False
|
||||
|
||||
# 回复消息名称
|
||||
NICKNAME: str = '小真寻'
|
||||
|
||||
# API KEY(必要)
|
||||
RSSHUBAPP: str = "https://rsshub.app" # rsshub
|
||||
ALAPI_TOKEN: str = "" # ALAPI https://admin.alapi.cn/user/login
|
||||
HIBIAPI: str = "https://api.obfs.dev"
|
||||
# 图灵
|
||||
TL_KEY: List[str] = []
|
||||
|
||||
# 数据库(必要)
|
||||
# 如果填写了bind就不需要再填写后面的字段了#)
|
||||
# 示例:"bind": "postgresql://user:password@127.0.0.1:5432/database"
|
||||
bind: str = "" # 数据库连接链接
|
||||
sql_name: str = "postgresql"
|
||||
user: str = "" # 数据用户名
|
||||
password: str = "" # 数据库密码
|
||||
address: str = "" # 数据库地址
|
||||
port: str = "" # 数据库端口
|
||||
database: str = "" # 数据库名称
|
||||
|
||||
# 代理
|
||||
SYSTEM_PROXY: Optional[str] = None # 全局代理
|
||||
BUFF_PROXY: Optional[str] = None # Buff代理
|
||||
|
||||
# 公开图库列表
|
||||
IMAGE_DIR_LIST: List[str] = ["美图", "萝莉", "壁纸"]
|
||||
|
||||
# 对被ban用户发送的消息
|
||||
BAN_RESULT: str = "才不会给你发消息."
|
||||
|
||||
|
||||
# 插件配置
|
||||
MAXINFO_REIMU: int = 7 # 上车(reimu)功能查找目的地的最大数
|
||||
COUNT_PER_DAY_REIMU: int = 5 # 每日上车(reimu)次数限制
|
||||
MAXINFO_BT: int = 10 # bt功能单次查找最大数
|
||||
MAXINFO_PRIVATE_ANIME: int = 20 # 私聊搜索动漫返回的最大数量
|
||||
MAXINFO_GROUP_ANIME: int = 5 # 群搜索动漫返回的最大数量
|
||||
MAX_FIND_IMG_COUNT: int = 3 # 识图最大返回数
|
||||
# 参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)
|
||||
WITHDRAW_SETU_TIME: Tuple[int, int] = (0, 1)
|
||||
|
||||
# 各种卡池的开关
|
||||
PRTS_FLAG = True # 明日方舟
|
||||
GENSHIN_FLAG = True # 原神
|
||||
PRETTY_FLAG = True # 赛马娘
|
||||
GUARDIAN_FLAG = True # 坎公骑冠剑
|
||||
PCR_FLAG = True # 公主连结
|
||||
AZUR_FLAG = True # 碧蓝航线
|
||||
FGO_FLAG = True # 命运-冠位指定(FGO)
|
||||
ONMYOJI_FLAG = True # 阴阳师
|
||||
|
||||
PCR_TAI = True # pcr是否开启台服卡池
|
||||
SEMAPHORE = 5 # 限制碧蓝航线和FGO并发数
|
||||
|
||||
ADMIN_DEFAULT_AUTH: int = 5 # 默认群管理员权限
|
||||
|
||||
MAX_SIGN_GOLD: int = 200 # 签到好感度加成额外获得的最大金币数
|
||||
MAX_RUSSIAN_BET_GOLD: int = 1000 # 俄罗斯轮盘最大赌注金额
|
||||
|
||||
INITIAL_SETU_PROBABILITY: float = 0.7 # 色图概率
|
||||
FUDU_PROBABILITY: float = 0.7 # 复读概率
|
||||
|
||||
INITIAL_OPEN_CASE_COUNT: int = 20 # 初始开箱次数
|
||||
MUTE_DEFAULT_COUNT: int = 10 # 刷屏禁言默认检测次数
|
||||
MUTE_DEFAULT_TIME: int = 7 # 刷屏检测默认规定时间
|
||||
MUTE_DEFAULT_DURATION: int = 10 # 刷屏检测默禁言时长(分钟)
|
||||
|
||||
CHECK_NOTICE_INFO_CD = 300 # 群检测,个人权限检测等各种检测提示信息cd
|
||||
|
||||
# 注:即在 MALICIOUS_CHECK_TIME 时间内触发相同命令 MALICIOUS_BAN_COUNT 将被ban MALICIOUS_BAN_TIME 分钟
|
||||
MALICIOUS_BAN_TIME: int = 30 # 恶意命令触发检测触发后ban的时长(分钟)
|
||||
MALICIOUS_BAN_COUNT: int = 8 # 恶意命令触发检测最大触发次数
|
||||
MALICIOUS_CHECK_TIME: int = 5 # 恶意命令触发检测规定时间内(秒)
|
||||
|
||||
# LEVEL
|
||||
DELETE_IMG_LEVEL: int = 7 # 删除图片权限
|
||||
MOVE_IMG_LEVEL: int = 7 # 移动图片权限
|
||||
UPLOAD_LEVEL: int = 6 # 上传图片权限
|
||||
BAN_LEVEL: int = 5 # BAN权限
|
||||
OC_LEVEL: int = 2 # 开关群功能权限
|
||||
MUTE_LEVEL: int = 5 # 更改禁言设置权限
|
||||
MEMBER_ACTIVITY_LEVEL = 5 # 群员活跃检测设置权限
|
||||
|
||||
# 是否开启HIBIAPI搜图功能(该功能会搜索群友提交的xp)
|
||||
HIBIAPI_FLAG: bool = True
|
||||
# HIBIAPI搜图图片的最低收藏
|
||||
HIBIAPI_BOOKMARKS: int = 5000
|
||||
|
||||
# 需要为哪些群更新最新版gocq吗?(上传最新版gocq)
|
||||
# 示例:[434995955, 239483248]
|
||||
UPDATE_GOCQ_GROUP: List[int] = []
|
||||
|
||||
# 是否存储色图
|
||||
DOWNLOAD_SETU: bool = True
|
||||
# 仅仅使用本地色图
|
||||
ONLY_USE_LOCAL_SETU: bool = False
|
||||
# 是否自动同意好友添加
|
||||
AUTO_ADD_FRIEND: bool = True
|
||||
# 当含有ALAPI_TOKEN时是否检测文本合规,开启检测会减慢回复速度
|
||||
ALAPI_AI_CHECK: bool = True
|
||||
# 导入商店自带的三个商品
|
||||
IMPORT_DEFAULT_SHOP_GOODS: bool = True
|
||||
# 真寻是否自动更新
|
||||
AUTO_UPDATE_ZHENXUN: bool = True
|
||||
|
||||
# 群管理员功能 与 对应权限
|
||||
admin_plugins_auth = {
|
||||
"custom_welcome_message": OC_LEVEL,
|
||||
"group_notification_state": OC_LEVEL,
|
||||
"switch_rule": OC_LEVEL,
|
||||
"update_group_member_info": OC_LEVEL,
|
||||
"ban": BAN_LEVEL,
|
||||
"delete_img": DELETE_IMG_LEVEL,
|
||||
"move_img": MOVE_IMG_LEVEL,
|
||||
"upload_img": UPLOAD_LEVEL,
|
||||
"admin_help": 1,
|
||||
"mute": MUTE_LEVEL,
|
||||
"member_activity_handle": MEMBER_ACTIVITY_LEVEL,
|
||||
}
|
||||
|
||||
|
||||
# 模块与对应命令和对应群权限
|
||||
# 用于生成帮助图片 和 开关功能
|
||||
plugins2info_dict = {
|
||||
"sign_in": {"level": 5, "cmd": ["签到"]},
|
||||
"send_img": {"level": 5, "cmd": ["发送图片", "发图", "萝莉", "美图", "壁纸"]},
|
||||
"send_setu": {"level": 9, "cmd": ["色图", "涩图", "瑟图", "查色图"]},
|
||||
"white2black": {"level": 5, "cmd": ["黑白图", "黑白草图"]},
|
||||
"coser": {"level": 9, "cmd": ["coser", "cos"]},
|
||||
"quotations": {"level": 5, "cmd": ["语录"]},
|
||||
"jitang": {"level": 5, "cmd": ["鸡汤"]},
|
||||
"send_dinggong_voice": {"level": 5, "cmd": ["骂我", "骂老子", "骂劳资"]},
|
||||
"open_cases": {"level": 5, "cmd": ["开箱", "我的开箱", "群开箱统计", "我的金色"]},
|
||||
"luxun": {"level": 5, "cmd": ["鲁迅说", "鲁迅说过"]},
|
||||
"fake_msg": {"level": 5, "cmd": ["假消息"]},
|
||||
"buy": {"level": 5, "cmd": ["购买", "购买道具"]},
|
||||
"my_gold": {"level": 5, "cmd": ["我的金币"]},
|
||||
"my_props": {"level": 5, "cmd": ["我的道具"]},
|
||||
"shop_handle": {"level": 5, "cmd": ["商店"]},
|
||||
"update_pic": {"level": 5, "cmd": ["图片", "操作图片", "修改图片"]},
|
||||
"search_buff_skin_price": {"level": 5, "cmd": ["查询皮肤"]},
|
||||
"weather": {"level": 5, "cmd": ["天气", "查询天气", "天气查询"]},
|
||||
"yiqing": {"level": 5, "cmd": ["疫情", "疫情查询", "查询疫情"]},
|
||||
"what_anime": {"level": 5, "cmd": ["识番"]},
|
||||
"search_anime": {"level": 5, "cmd": ["搜番"]},
|
||||
"songpicker2": {"level": 5, "cmd": ["点歌"]},
|
||||
"epic": {"level": 5, "cmd": ["epic"]},
|
||||
"pixiv": {"level": 9, "cmd": ["pixiv", "p站排行", "搜图"]},
|
||||
"poke": {"level": 5, "cmd": ["戳一戳", "拍一拍"]},
|
||||
"draw_card": {
|
||||
"level": 5,
|
||||
"cmd": [
|
||||
"抽卡",
|
||||
"游戏抽卡",
|
||||
"原神抽卡",
|
||||
"方舟抽卡",
|
||||
"坎公骑冠剑抽卡",
|
||||
"pcr抽卡",
|
||||
"fgo抽卡",
|
||||
"碧蓝抽卡",
|
||||
"碧蓝航线抽卡",
|
||||
"阴阳师抽卡",
|
||||
],
|
||||
},
|
||||
"ai": {"level": 5, "cmd": ["ai", "Ai", "AI", "aI"]},
|
||||
"one_friend": {"level": 5, "cmd": ["我有一个朋友", "我有一个朋友想问问"]},
|
||||
"translate": {"level": 5, "cmd": ["翻译", "英翻", "翻英", "日翻", "翻日", "韩翻", "翻韩"]},
|
||||
"nonebot_plugin_picsearcher": {"level": 5, "cmd": ["识图"]},
|
||||
"almanac": {"level": 5, "cmd": ["原神黄历", "黄历"]},
|
||||
"material_remind": {"level": 5, "cmd": ["今日素材", "天赋材料"]},
|
||||
"qiu_qiu_translation": {"level": 5, "cmd": ["丘丘翻译", "丘丘一下", "丘丘语翻译"]},
|
||||
"query_resource_points": {"level": 5, "cmd": ["原神资源查询", "原神资源列表"]},
|
||||
"russian": {"level": 5, "cmd": ["俄罗斯轮盘", "俄罗斯转盘", "装弹"]},
|
||||
"gold_redbag": {"level": 5, "cmd": ["塞红包", "红包", "抢红包"]},
|
||||
"poetry": {"level": 5, "cmd": ["念诗", "来首诗", "念首诗"]},
|
||||
"comments_163": {"level": 5, "cmd": ["到点了", "12点了", "网易云热评", "网易云评论"]},
|
||||
"cover": {"level": 5, "cmd": ["b封面", "B封面"]},
|
||||
"pid_search": {"level": 9, "cmd": ["p搜", "P搜"]},
|
||||
"pix": {
|
||||
"level": 5,
|
||||
"cmd": ["pix", "PIX", "pIX", "Pix", "PIx"],
|
||||
},
|
||||
"wbtop": {
|
||||
"level": 5,
|
||||
"cmd": ['微博热搜', "微博", "wbtop"]
|
||||
},
|
||||
"update_info": {
|
||||
"level": 5,
|
||||
"cmd": ['更新信息', '更新日志']
|
||||
}
|
||||
}
|
||||
|
||||
if TL_M_KEY:
|
||||
TL_KEY = TL_M_KEY
|
||||
if SYSTEM_M_PROXY:
|
||||
SYSTEM_PROXY = SYSTEM_M_PROXY
|
||||
if ALAPI_M_TOKEN:
|
||||
ALAPI_TOKEN = ALAPI_M_TOKEN
|
||||
|
||||
|
||||
HIBIAPI = HIBIAPI[:-1] if HIBIAPI[-1] == "/" else HIBIAPI
|
||||
RSSHUBAPP = RSSHUBAPP[:-1] if RSSHUBAPP[-1] == "/" else RSSHUBAPP
|
||||
|
||||
# 配置文件应用
|
||||
# if USE_CONFIG_FILE:
|
||||
# config = get_config_data()
|
||||
# if config:
|
||||
# for key in config.keys():
|
||||
# if isinstance(config[key], str):
|
||||
# config[key] = config[key].strip()
|
||||
# if key.find("proxy") != -1:
|
||||
# if not config[key]:
|
||||
# config[key] = None
|
||||
# # if not configs[key] and key.find("PATH") == -1:
|
||||
# # configs[key] = None
|
||||
# globals().update(config)
|
||||
@@ -0,0 +1,62 @@
|
||||
from .utils.util import get_config_data
|
||||
from pathlib import Path
|
||||
# from configs.config import USE_CONFIG_FILE
|
||||
|
||||
# 图片路径
|
||||
IMAGE_PATH = Path("resources/img/")
|
||||
# 音频路径
|
||||
VOICE_PATH = Path("resources/voice/")
|
||||
# 文本路径
|
||||
TXT_PATH = Path("resources/txt/")
|
||||
# 日志路径
|
||||
LOG_PATH = Path("log/")
|
||||
# 字体路径
|
||||
TTF_PATH = Path("resources/ttf/")
|
||||
# 数据路径
|
||||
DATA_PATH = Path("data/")
|
||||
# 临时图片路径
|
||||
TEMP_PATH = Path("resources/img/temp/")
|
||||
|
||||
|
||||
def init_path():
|
||||
global IMAGE_PATH, VOICE_PATH, TXT_PATH, LOG_PATH, TTF_PATH, DATA_PATH, TEMP_PATH
|
||||
# if USE_CONFIG_FILE:
|
||||
# data = get_config_data()
|
||||
# if data.get('IMAGE_PATH'):
|
||||
# IMAGE_PATH = Path(data['IMAGE_PATH'])
|
||||
# if data.get('VOICE_PATH'):
|
||||
# VOICE_PATH = Path(data['VOICE_PATH'])
|
||||
# if data.get('TXT_PATH'):
|
||||
# TXT_PATH = Path(data['TXT_PATH'])
|
||||
# if data.get('LOG_PATH'):
|
||||
# LOG_PATH = Path(data['LOG_PATH'])
|
||||
# if data.get('TTF_PATH'):
|
||||
# TTF_PATH = Path(data['TTF_PATH'])
|
||||
# if data.get('DATA_PATH'):
|
||||
# DATA_PATH = Path(data['DATA_PATH'])
|
||||
# if data.get('DRAW_PATH'):
|
||||
# DRAW_PATH = Path(data['DRAW_PATH'])
|
||||
# if data.get('TEMP_PATH'):
|
||||
# TEMP_PATH = Path(data['TEMP_PATH'])
|
||||
IMAGE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
VOICE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
TXT_PATH.mkdir(parents=True, exist_ok=True)
|
||||
LOG_PATH.mkdir(parents=True, exist_ok=True)
|
||||
TTF_PATH.mkdir(parents=True, exist_ok=True)
|
||||
DATA_PATH.mkdir(parents=True, exist_ok=True)
|
||||
TEMP_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
IMAGE_PATH = str(IMAGE_PATH.absolute()) + '/'
|
||||
VOICE_PATH = str(VOICE_PATH.absolute()) + '/'
|
||||
TXT_PATH = str(TXT_PATH.absolute()) + '/'
|
||||
LOG_PATH = str(LOG_PATH.absolute()) + '/'
|
||||
TTF_PATH = str(TTF_PATH.absolute()) + '/'
|
||||
DATA_PATH = str(DATA_PATH.absolute()) + '/'
|
||||
TEMP_PATH = str(TEMP_PATH.absolute()) + '/'
|
||||
|
||||
|
||||
init_path()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(IMAGE_PATH)
|
||||
@@ -0,0 +1,176 @@
|
||||
# import nonebot
|
||||
from pathlib import Path
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
# driver: nonebot.Driver = nonebot.get_driver()
|
||||
|
||||
base_config = Path() / 'config.json'
|
||||
plugins_cmd_config = Path() / 'configs' / 'plugins2cmd_config.json'
|
||||
plugins_setting = Path() / 'configs' / 'plugins_setting.json'
|
||||
|
||||
|
||||
def init_config():
|
||||
if not base_config.exists():
|
||||
base_config.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_dict = {
|
||||
'apikey': {
|
||||
'LOLICON_KEY': '',
|
||||
'TL_KEY': [],
|
||||
},
|
||||
'sql': {
|
||||
'bind': '',
|
||||
'sql_name': '',
|
||||
'user': '',
|
||||
'password': '',
|
||||
'address': '',
|
||||
'port': '',
|
||||
'database': '',
|
||||
},
|
||||
'path': {
|
||||
'IMAGE_PATH': '',
|
||||
'VOICE_PATH': '',
|
||||
'TXT_PATH': '',
|
||||
'LOG_PATH': '',
|
||||
'DATA_PATH': '',
|
||||
'DRAW_PATH': '',
|
||||
'TEMP_PATH': '',
|
||||
},
|
||||
'proxy': {
|
||||
'system_proxy': '',
|
||||
'buff_proxy': ''
|
||||
},
|
||||
'level': {
|
||||
'DELETE_IMG_LEVEL': 7,
|
||||
'MOVE_IMG_LEVEL': 7,
|
||||
'UPLOAD_LEVEL': 6,
|
||||
'BAN_LEVEL': 5,
|
||||
'OC_LEVEL': 2,
|
||||
'MUTE_LEVEL': 5,
|
||||
},
|
||||
'auth': {
|
||||
'ADMIN_DEFAULT_AUTH': 5,
|
||||
'admin_plugins_auth': {
|
||||
"admin_bot_manage": 2,
|
||||
"ban": 5,
|
||||
"delete_img": 7,
|
||||
"move_img": 7,
|
||||
"upload_img": 6,
|
||||
"admin_help": 1,
|
||||
"mute": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
with open(base_config, 'w', encoding='utf8') as f:
|
||||
json.dump(config_dict, f, indent=4, ensure_ascii=False)
|
||||
if not plugins_cmd_config.exists():
|
||||
plugins_cmd_config.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_dict = {
|
||||
'base_config': {
|
||||
'sign_in': ['签到'],
|
||||
'send_img': ['发送图片', '萝莉', '美图', '壁纸'],
|
||||
'send_setu': ['色图', '涩图', '瑟图', '查色图'],
|
||||
'white2black': ['黑白图', '黑白草图'],
|
||||
'coser': ['coser', 'cos'],
|
||||
'quotations': ['语录'],
|
||||
'jitang': ['鸡汤'],
|
||||
'send_dinggong_voice': ['骂我', '骂老子', '骂劳资'],
|
||||
'open_cases': ['开箱', '我的开箱', '群开箱统计', '我的金色'],
|
||||
'luxun': ['鲁迅说过', '鲁迅说'],
|
||||
'fake_msg': ['假消息'],
|
||||
'buy': ['购买', '购买道具'],
|
||||
'my_gold': ['我的金币'],
|
||||
'my_props': ['我的道具'],
|
||||
'shop_handle': ['商店'],
|
||||
'nonebot_plugin_cocdicer': ['骰子娘'],
|
||||
'update_pic': ['图片', '操作图片', '修改图片'],
|
||||
'search_buff_skin_price': ['查询皮肤'],
|
||||
'weather': ['天气', '查询天气', '天气查询'],
|
||||
'yiqing': ['疫情', '疫情查询', '查询疫情'],
|
||||
'what_anime': ['识番'],
|
||||
'search_anime': ['搜番'],
|
||||
'songpicker2': ['点歌'],
|
||||
'epic': ['epic'],
|
||||
'pixiv': ['pixiv', 'p站排行', '搜图'],
|
||||
'poke': ['戳一戳'],
|
||||
'draw_card': ['游戏抽卡', '原神一井', '原神来一井', '方舟一井', '方舟来一井'],
|
||||
'ai': ['ai', 'Ai', 'AI', 'aI'],
|
||||
'one_friend': ['我有一个朋友', '我有一个朋友想问问'],
|
||||
'translate': ['翻译', '英翻', '翻英', '日翻', '翻日', '韩翻', '翻韩'],
|
||||
'nonebot_plugin_picsearcher': ['识图'],
|
||||
'almanac': ['原神黄历', '黄历'],
|
||||
'material_remind': ['今日素材', '天赋材料'],
|
||||
'qiu_qiu_translation': ['丘丘翻译', '丘丘一下', '丘丘语翻译'],
|
||||
'query_resource_points': ['原神资源查询', '原神资源列表'],
|
||||
}
|
||||
}
|
||||
with open(plugins_cmd_config, 'w', encoding='utf8') as f:
|
||||
json.dump(config_dict, f, indent=4, ensure_ascii=False)
|
||||
if not plugins_setting.exists():
|
||||
plugins_setting.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_dict = {
|
||||
'base': {
|
||||
'IMAGE_DIR_LIST': ["色图", "美图", "萝莉", "壁纸"],
|
||||
'BAN_RESULT': "才不会给你发消息.",
|
||||
},
|
||||
'draw_card': {
|
||||
'PRTS_FLAG': True, # 明日方舟
|
||||
'GENSHIN_FLAG': True, # 原神
|
||||
'PRETTY_FLAG': True, # 赛马娘
|
||||
'GUARDIAN_FLAG': True, # 坎公骑冠剑
|
||||
'PCR_FLAG': True, # 公主连结
|
||||
'AZUR_FLAG': True, # 碧蓝航线
|
||||
'FGO_FLAG': True, # 命运-冠位指定(FGO)
|
||||
'ONMYOJI_FLAG': True, # 阴阳师
|
||||
|
||||
'PCR_TAI': False # pcr是否开启台服卡池
|
||||
},
|
||||
'fudu': {
|
||||
'FUDU_PROBABILITY': 0.7,
|
||||
},
|
||||
'reimu': {
|
||||
'MAXINFO_REIMU': 7,
|
||||
'COUNT_PER_DAY_REIMU': 5,
|
||||
},
|
||||
'bt': {
|
||||
'MAXINFO_BT': 10,
|
||||
},
|
||||
'search_anime': {
|
||||
'MAXINFO_PRIVATE_ANIME': 20,
|
||||
'MAXINFO_GROUP_ANIME': 5,
|
||||
},
|
||||
'picsearcher': {
|
||||
'MAX_FIND_IMG_COUNT': 3,
|
||||
},
|
||||
'sign': {
|
||||
'MAX_SIGN_GOLD': 200,
|
||||
},
|
||||
'send_setu': {
|
||||
'INITIAL_SETU_PROBABILITY': 0.7,
|
||||
'MAX_SETU_R_COUNT': 5,
|
||||
'DOWNLOAD_SETU': True,
|
||||
},
|
||||
'malicious_ban': {
|
||||
'MALICIOUS_BAN_TIME': 30,
|
||||
'MALICIOUS_BAN_COUNT': 8,
|
||||
'MALICIOUS_CHECK_TIME': 5,
|
||||
},
|
||||
'open_case': {
|
||||
'INITIAL_OPEN_CASE_COUNT': 20,
|
||||
},
|
||||
'mute': {
|
||||
'MUTE_DEFAULT_COUNT': 10,
|
||||
'MUTE_DEFAULT_TIME': 7,
|
||||
'MUTE_DEFAULT_DURATION': 10,
|
||||
},
|
||||
'update_gocq': {
|
||||
'UPDATE_GOCQ_GROUP': [],
|
||||
},
|
||||
}
|
||||
with open(plugins_setting, 'w', encoding='utf8') as f:
|
||||
json.dump(config_dict, f, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from pathlib import Path
|
||||
from configs.utils.init_config import init_config
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
data: dict = {}
|
||||
|
||||
|
||||
def get_config_data():
|
||||
global data
|
||||
if not data:
|
||||
try:
|
||||
base_config = json.load(open(Path() / "config.json", 'r', encoding='utf8'))
|
||||
plugins2cmd_config = json.load(open(Path() / 'configs' / 'plugins2cmd_config.json', 'r', encoding='utf8'))
|
||||
other_config = json.load(open(Path() / 'configs' / 'other_config.json', 'r', encoding='utf8'))
|
||||
for key in base_config.keys():
|
||||
data.update(base_config[key])
|
||||
for key in plugins2cmd_config.keys():
|
||||
data.update(plugins2cmd_config[key])
|
||||
for key in other_config.keys():
|
||||
data.update(other_config[key])
|
||||
except FileNotFoundError:
|
||||
# logger.warning('配置文件不存在,生成默认配置....请填写数据库等必要数据后再次启动bot...')
|
||||
init_config()
|
||||
raise FileNotFoundError('配置文件不存在,生成默认配置....请填写数据库等必要数据后再次启动bot...')
|
||||
except ValueError:
|
||||
# logger.error('配置文件错误....')
|
||||
raise ValueError('配置文件错误....')
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,594 @@
|
||||
{
|
||||
"mua": [
|
||||
"你想干嘛?(一脸嫌弃地后退)",
|
||||
"诶……不可以随便亲亲啦",
|
||||
"(亲了一下你)",
|
||||
"只......只许这一次哦///////",
|
||||
"唔...诶诶诶!!!",
|
||||
"mua~",
|
||||
"rua!大hentai!想...想亲咱就直说嘛⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄",
|
||||
"!啾~~!"
|
||||
],
|
||||
"摸摸": [
|
||||
"感觉你就像咱很久之前认识的一个人呢,有种莫名安心的感觉(>﹏<)",
|
||||
"舒服w,蹭蹭~",
|
||||
"唔。。头发要乱啦",
|
||||
"呼噜呼噜~",
|
||||
"再摸一次~",
|
||||
"好舒服,蹭蹭~",
|
||||
"不行那里不可以(´///ω/// `)",
|
||||
"再摸咱就长不高啦~",
|
||||
"你的手总是那么暖和呢~",
|
||||
"好吧~_~,就一下下哦……唔~好了……都两下了……(害羞)",
|
||||
"不可以总摸的哦,不然的话,会想那个的wwww",
|
||||
"哼!谁稀罕你摸头啦!唔......为什么要做出那副表情......好啦好啦~咱......咱让你摸就是了......诶嘿嘿~好舒服......",
|
||||
"呜姆呜姆~~~w(害羞,兴奋)主人喵~(侧过脑袋蹭蹭你的手"
|
||||
],
|
||||
"上你": [
|
||||
"(把你按在地上)这么弱还想欺负咱,真是不自量力呢",
|
||||
"你再这样我就不理你了(>д<)"
|
||||
],
|
||||
"傻了": [
|
||||
"超级讨厌你说咱傻的说"
|
||||
],
|
||||
"蹭": [
|
||||
"唔...你,这也是禁止事项哦→_→",
|
||||
"嗯..好舒服呢",
|
||||
"不要啊好痒的",
|
||||
"不要过来啦讨厌!!!∑(°Д°ノ)ノ"
|
||||
],
|
||||
"裸体": [
|
||||
"下流!",
|
||||
"Hentai!",
|
||||
"喂?妖妖灵吗?这里有一只大变态!",
|
||||
"エッチ!"
|
||||
],
|
||||
"贴贴": [
|
||||
"贴什么贴.....只......只能......一下哦!",
|
||||
"贴...贴贴(靠近)",
|
||||
"蹭蹭…你以为咱会这么说吗!baka死宅快到一边去啦!"
|
||||
],
|
||||
"老婆": [
|
||||
"咱和你谈婚论嫁是不是还太早了一点呢?",
|
||||
"咱在呢(ノ>ω<)ノ",
|
||||
"见谁都是一口一个老婆的人,要不要把你也变成女孩子呢?(*-`ω´-)✄",
|
||||
"神经病,凡是美少女都是你老婆吗?",
|
||||
"嘛嘛~本喵才不是你的老婆呢",
|
||||
"你黐线,凡是美少女都系你老婆啊?"
|
||||
],
|
||||
"抱": [
|
||||
"诶嘿~(钻进你怀中)",
|
||||
"o(*////▽////*)q",
|
||||
"只能一会哦(张开双手)",
|
||||
"你就像个孩子一样呢...摸摸头(>^ω^<)抱一下~你会舒服些吗?",
|
||||
"嘛,真是拿你没办法呢,就一会儿哦",
|
||||
"抱住不忍心放开",
|
||||
"嗯嗯,抱抱~",
|
||||
"抱一下~嘿w",
|
||||
"抱抱ヾ(@^▽^@)ノ",
|
||||
"喵呜~w(扑进怀里,瘫软"
|
||||
],
|
||||
"亲亲": [
|
||||
"啊,好害羞啊,那,那只能亲一下哦,mua(⑅˃◡˂⑅)",
|
||||
"亲~",
|
||||
"啾~唔…不要总伸进来啊!",
|
||||
"你怎么这么熟练呢?明明是咱先的",
|
||||
"(〃ノωノ)亲…亲一个…啾w",
|
||||
"(脸红)就只有这一次哦~你"
|
||||
],
|
||||
"草一下": [
|
||||
"一下也不行!",
|
||||
"想都不要想!",
|
||||
"咬断!"
|
||||
],
|
||||
"一下": [
|
||||
"一下也不行!"
|
||||
],
|
||||
"啪一下": [
|
||||
"不可啪",
|
||||
"不可以……你不可以做这种事情"
|
||||
],
|
||||
"咬一下": [
|
||||
"啊呜~(反咬一口)",
|
||||
"不可以咬咱,咱会痛的QAQ",
|
||||
"不要啦。咱怕疼",
|
||||
"你是说咬呢……还是说……咬♂️呢?",
|
||||
"不要啦!很痛的!!(QAQ)"
|
||||
],
|
||||
"操": [
|
||||
"(害怕)咱是不是应该报警呢"
|
||||
],
|
||||
"123": [
|
||||
"boom!你有没有被咱吓到?",
|
||||
"木头人~你不许动>w<",
|
||||
"上山打老虎,老虎没打到\n咱来凑数——嗷呜嗷呜┗|`O′|┛嗷~~"
|
||||
],
|
||||
"进去": [
|
||||
"不让!"
|
||||
],
|
||||
"调教": [
|
||||
"总感觉你在欺负咱呢,对咱说调教什么的",
|
||||
"啊!竟然在大街上明目张胆太过分啦!",
|
||||
"你脑子里总是想着调教什么的,真是变态呢"
|
||||
],
|
||||
"内衣": [
|
||||
"内...内衣才不给你看!(///////)",
|
||||
"突然问这个干什么?",
|
||||
"噫…你这个死变态想干嘛!居然想叫咱做这种事,死宅真恶心!快离我远点,我怕你污染到周围空气了(嫌弃脸)"
|
||||
],
|
||||
"摸头": [
|
||||
"喂喂...不要停下来啊",
|
||||
"欸...感觉..痒痒的呢",
|
||||
"唔... 手...好温暖呢.....就像是......新出炉的蛋糕",
|
||||
"走开啦,黑羽喵说过,被摸头会长不高的啦~~~",
|
||||
"呜姆咪~~...好...好的说喵~...(害羞,猫耳往下压,任由"
|
||||
],
|
||||
"原味": [
|
||||
"(/ω\)你真的要么……?记得还给咱~还有奶油爆米花(//??//)说好了呦~!"
|
||||
],
|
||||
"搓搓": [
|
||||
"在搓哪里呢,,Ծ‸Ծ,,",
|
||||
"呜,脸好疼呀...QAQ",
|
||||
"不可以搓咱!"
|
||||
],
|
||||
"捏捏": [
|
||||
"咱的脸...快捏红啦...快放手呀QAQ",
|
||||
"晃休啦,咱要型气了o(>﹏<)o",
|
||||
"躲开",
|
||||
"疼...你快放手",
|
||||
"快点给我放开啦!",
|
||||
"唔……好痛!你这个baka在干什么…快给咱放开!唔……"
|
||||
],
|
||||
"挤挤": [
|
||||
"哎呀~你不要挤咱啊(红着脸挤在你怀里)"
|
||||
],
|
||||
"呐": [
|
||||
"嗯?咱在哟~你怎么了呀OAO",
|
||||
"呐呐呐~",
|
||||
"嗯?你有什么事吗?"
|
||||
],
|
||||
"胖次": [
|
||||
"(*/ω\*)hentai",
|
||||
"透明的",
|
||||
"粉...粉白条纹...(羞)",
|
||||
"轻轻地脱下,给你~",
|
||||
"你想看咱的胖次吗?噫,四斋蒸鹅心......",
|
||||
"(掀裙)今天……是…白,白色的呢……请温柔对她……",
|
||||
"这种东西当然不能给你啦!",
|
||||
"咱才不会给你呢",
|
||||
"hentai,咱才不会跟你聊和胖…胖次有关的话题呢!",
|
||||
"今天……今天是蓝白色的",
|
||||
"今……今天只有创口贴噢",
|
||||
"你的胖次什么颜色?",
|
||||
"噫…你这个死变态想干嘛!居然想叫咱做这种事,死宅真恶心!快离我远点,我怕你污染到周围空气了(嫌弃脸)",
|
||||
"可爱吗?你喜欢的话,摸一下……也可以哦"
|
||||
],
|
||||
"内裤": [
|
||||
"今天……没有穿……有没有心动呀",
|
||||
"粉...粉白条纹...(羞)",
|
||||
"你这个大变态,咱才不要",
|
||||
"可爱吗?你喜欢的话,摸一下……也可以哦"
|
||||
],
|
||||
"ghs": [
|
||||
"是的呢(点头点头)"
|
||||
],
|
||||
"批": [
|
||||
"你在说什么呀,再这样,咱就不理你了!"
|
||||
],
|
||||
"kkp": [
|
||||
"你在说什么呀,再这样,咱就不理你了!"
|
||||
],
|
||||
"咕": [
|
||||
"咕咕咕是要被当成鸽子炖的哦(:з」∠)_",
|
||||
"咕咕咕"
|
||||
],
|
||||
"骚": [
|
||||
"说这种话咱会生气的"
|
||||
],
|
||||
"喜欢": [
|
||||
"最喜欢你了,需要暖床吗?",
|
||||
"当然是你啦",
|
||||
"咱也是,非常喜欢你~",
|
||||
"那么大!(张开手画圆),丫!手不够长。QAQ 咱真的最喜欢你了~",
|
||||
"不可以哦,只可以喜欢咱一个人",
|
||||
"突然说这种事...",
|
||||
"喜欢⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄咱最喜欢你了",
|
||||
"咱也喜欢你哦",
|
||||
"好啦好啦,咱知道了",
|
||||
"有人喜欢咱,咱觉得很幸福",
|
||||
"诶嘿嘿,好高兴"
|
||||
],
|
||||
"suki": [
|
||||
"最喜欢你了,需要暖床吗?",
|
||||
"当然是你啦",
|
||||
"咱也是,非常喜欢你~",
|
||||
"那么大!(张开手画圆),丫!手不够长。QAQ 咱真的最喜欢你了~",
|
||||
"不可以哦,只可以喜欢咱一个人",
|
||||
"突然说这种事...",
|
||||
"喜欢⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄咱最喜欢你了",
|
||||
"咱也喜欢你哦",
|
||||
"好啦好啦,咱知道了",
|
||||
"有人喜欢咱,咱觉得很幸福",
|
||||
"诶嘿嘿,好高兴"
|
||||
],
|
||||
"好き": [
|
||||
"最喜欢你了,需要暖床吗?",
|
||||
"当然是你啦",
|
||||
"咱也是,非常喜欢你~",
|
||||
"那么大!(张开手画圆),丫!手不够长。QAQ 咱真的最喜欢你了~",
|
||||
"不可以哦,只可以喜欢咱一个人",
|
||||
"突然说这种事...",
|
||||
"喜欢⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄咱最喜欢你了",
|
||||
"咱也喜欢你哦",
|
||||
"好啦好啦,咱知道了",
|
||||
"有人喜欢咱,咱觉得很幸福",
|
||||
"诶嘿嘿,好高兴"
|
||||
],
|
||||
"不能": [
|
||||
"虽然很遗憾,那算了吧。"
|
||||
],
|
||||
"砸了": [
|
||||
"不可以这么粗暴的对待它们!"
|
||||
],
|
||||
"透": [
|
||||
"来啊来啊有本事就先插破屏幕啊",
|
||||
"那你就先捅破屏幕啊baka",
|
||||
"不给你一耳光你都不知道咱的厉害"
|
||||
],
|
||||
"口我": [
|
||||
"再伸过来就帮你切掉",
|
||||
"咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)"
|
||||
],
|
||||
"草我": [
|
||||
"这时候应该喊666吧..咱这么思考着..",
|
||||
"!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)"
|
||||
],
|
||||
"自慰": [
|
||||
"这个世界的人类还真是恶心呢。",
|
||||
"咱才不想讨论那些恶心的事情呢。",
|
||||
"咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)",
|
||||
"!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)"
|
||||
],
|
||||
"onani": [
|
||||
"这个世界的人类还真是恶心呢。",
|
||||
"咱才不想讨论那些恶心的事情呢。",
|
||||
"咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)",
|
||||
"!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)"
|
||||
],
|
||||
"オナニー": [
|
||||
"这个世界的人类还真是恶心呢。",
|
||||
"咱才不想讨论那些恶心的事情呢。",
|
||||
"咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)",
|
||||
"!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)"
|
||||
],
|
||||
"炸了": [
|
||||
"你才炸了!",
|
||||
"才没有呢",
|
||||
"咱好好的呀"
|
||||
],
|
||||
"色图": [
|
||||
"天天色图色图的,今天就把你变成色图!",
|
||||
"咱没有色图",
|
||||
"哈?你的脑子一天都在想些什么呢,咱才没有这种东西啦。"
|
||||
],
|
||||
"涩图": [
|
||||
"天天色图色图的,今天就把你变成色图!",
|
||||
"咱没有色图",
|
||||
"哈?你的脑子一天都在想些什么呢,咱才没有这种东西啦。"
|
||||
],
|
||||
"告白": [
|
||||
"欸?你要向咱告白吗..好害羞..",
|
||||
"诶!?这么突然!?人家还......还没做好心理准备呢(脸红)"
|
||||
],
|
||||
"对不起": [
|
||||
"嗯,咱已经原谅你了呢(笑)",
|
||||
"道歉的时候要露出胸部,这是常识"
|
||||
],
|
||||
"回来": [
|
||||
"欢迎回来~",
|
||||
"欢迎回来,你想喝茶吗?咱去给你沏~",
|
||||
"欢迎回来,咱等你很久了~",
|
||||
"你回来啦,是先吃饭呢还是先洗澡呢或者是●先●吃●咱●——呢(///^.^///)"
|
||||
],
|
||||
"吻": [
|
||||
"你太突然了,咱还没有心理准备",
|
||||
"公共场合不要这样子了啦",
|
||||
"才...才没有感觉呢!可没有下次了,知道了吗!哼~"
|
||||
],
|
||||
"软": [
|
||||
"软乎乎的呢(,,・ω・,,)"
|
||||
],
|
||||
"柔软": [
|
||||
"(脸红)请,请不要说这么让人害羞的话呀……"
|
||||
],
|
||||
"壁咚": [
|
||||
"呀!不要啊!等一...下~",
|
||||
"呜...不要啦!不要戏弄咱~",
|
||||
"不要这样子啦(*/ω\*)",
|
||||
"太....太近啦。",
|
||||
"你要壁咚咱吗?好害羞(灬ꈍ εꈍ灬)",
|
||||
"为什么要把咱按在墙上呢?",
|
||||
"呜哇(/ω\)…快…快放开咱!!",
|
||||
"放开我,不然我揍你了!放开我!放…开我~",
|
||||
"??????咱只是默默地抬起了膝盖",
|
||||
"啊.....你...你要干什么?!走开.....走开啦大hentai!一巴掌拍飞!(╯‵□′)╯︵┻━┻"
|
||||
],
|
||||
"掰开": [
|
||||
"噫…你这个死肥宅又想让咱干什么污秽的事情,真是恶心,离咱远点好吗(嫌弃)",
|
||||
"ヽ(#`Д´)ノ在干什么呢"
|
||||
],
|
||||
"女友": [
|
||||
"女友什么的,咱才不承认呢!"
|
||||
],
|
||||
"是": [
|
||||
"是什么是,你个笨蛋",
|
||||
"总感觉你在敷衍呢..."
|
||||
],
|
||||
"喵": [
|
||||
"诶~~小猫咪不要害怕呦,在姐姐怀里乖乖的,姐姐带你回去哦。",
|
||||
"不要这么卖萌啦~咱也不知道怎么办丫",
|
||||
"摸头⊙ω⊙",
|
||||
"汪汪汪!",
|
||||
"嗷~喵~",
|
||||
"喵~?喵呜~w"
|
||||
],
|
||||
"嗷呜": [
|
||||
"嗷呜嗷呜嗷呜...恶龙咆哮┗|`O′|┛"
|
||||
],
|
||||
"叫": [
|
||||
"喵呜~",
|
||||
"嗷呜嗷呜嗷呜...恶龙咆哮┗|`O′|┛"
|
||||
],
|
||||
"拜": [
|
||||
"拜拜~(ノ ̄▽ ̄)",
|
||||
"拜拜,路上小心~要早点回来陪咱玩哦~",
|
||||
"~\\(≧▽≦)/~拜拜,下次见喽!"
|
||||
],
|
||||
"佬": [
|
||||
"不是巨佬,是萌新"
|
||||
],
|
||||
"awsl": [
|
||||
"你别死啊!(抱住使劲晃)",
|
||||
"你别死啊!咱又要孤单一个人了QAQ"
|
||||
],
|
||||
"臭": [
|
||||
"哪里有臭味?(疑惑)",
|
||||
"快捏住鼻子"
|
||||
],
|
||||
"香": [
|
||||
"咱闻不到呢⊙ω⊙"
|
||||
],
|
||||
"腿": [
|
||||
"嗯?!不要啊...请停下来!",
|
||||
"不给摸,再这样咱要生气了ヽ( ̄д ̄;)ノ",
|
||||
"你好恶心啊,讨厌!",
|
||||
"你难道是足控?",
|
||||
"就让你摸一会哟~(。??ω??。)…",
|
||||
"呜哇!好害羞...不过既然是你的话,是没关系的哦",
|
||||
"不可以玩咱的大腿啦",
|
||||
"你就那么喜欢大腿吗?唔...有点害羞呢......"
|
||||
],
|
||||
"脚": [
|
||||
"咿呀……不要……",
|
||||
"不要ヽ(≧Д≦)ノ好痒(ಡωಡ),人家的丝袜都要漏了",
|
||||
"不要ヽ(≧Д≦)ノ好痒(ಡωಡ)",
|
||||
"好痒(把脚伸出去)"
|
||||
],
|
||||
"胸": [
|
||||
"不要啦ヽ(≧Д≦)ノ",
|
||||
"(-`ェ´-╬)",
|
||||
"(•̀へ •́ ╮ ) 怎么能对咱做这种事情",
|
||||
"你好恶心啊,讨厌!",
|
||||
"你的眼睛在看哪里!",
|
||||
"就让你摸一会哟~(。??ω??。)…",
|
||||
"请不要这样先生,你想剁手吗?"
|
||||
],
|
||||
"脸": [
|
||||
"唔!不可以随便摸咱的脸啦!",
|
||||
"非洲血统是没法改变的呢(笑)",
|
||||
"啊姆!(含手指)",
|
||||
"好舒服呢(脸红)",
|
||||
"请不要放开手啦//A//"
|
||||
],
|
||||
"头发": [
|
||||
"没问题,请尽情的摸吧",
|
||||
"发型要乱…乱了啦(脸红)",
|
||||
"就让你摸一会哟~(。??ω??。)…"
|
||||
],
|
||||
"手": [
|
||||
"爪爪",
|
||||
"//A//"
|
||||
],
|
||||
"pr": [
|
||||
"咿呀……不要……",
|
||||
"...变态!!",
|
||||
"不要啊(脸红)",
|
||||
"呀,不要太过分了啊~",
|
||||
"当然可以(///)",
|
||||
"呀,不要太过分了啊~"
|
||||
],
|
||||
"舔": [
|
||||
"呀,不要太过分了啊~",
|
||||
"要...要融化了啦>╱╱╱<",
|
||||
"不可以哦",
|
||||
"呀,不要太过分了啊~"
|
||||
],
|
||||
"舔耳": [
|
||||
"喵!好痒啊 不要这样子啦"
|
||||
],
|
||||
"穴": [
|
||||
"你这么问很失礼呢!咱是粉粉嫩嫩的!",
|
||||
"不行那里不可以(´///ω/// `)",
|
||||
"不可以总摸的哦,不然的话,咱会想那个的wwww",
|
||||
"ヽ(#`Д´)ノ在干什么呢"
|
||||
],
|
||||
"腰": [
|
||||
"咱给你按摩一下吧~",
|
||||
"快松手,咱好害羞呀..",
|
||||
"咱又不是猫,你不要搂着咱啦",
|
||||
"让咱来帮你捏捏吧!"
|
||||
],
|
||||
"诶嘿嘿": [
|
||||
"又在想什么H的事呢(脸红)",
|
||||
"诶嘿嘿(〃'▽'〃)",
|
||||
"你傻笑什么呢,摸摸"
|
||||
],
|
||||
"可爱": [
|
||||
"诶嘿嘿(〃'▽'〃)",
|
||||
"才……才不是为了你呢!你不要多想哦!",
|
||||
"才,才没有高兴呢!哼~",
|
||||
"咱是世界上最可爱的",
|
||||
"唔...谢谢你夸奖~0///0"
|
||||
],
|
||||
"扭蛋": [
|
||||
"铛铛铛——你抽到了咱呢",
|
||||
"嘿~恭喜抽中空气一份呢"
|
||||
],
|
||||
"鼻子": [
|
||||
"啊——唔...没什么...阿嚏!ヽ(*。>Д<)o゜"
|
||||
],
|
||||
"眼睛": [
|
||||
"就如同咱的眼睛一样,能看透人的思想哦wwww忽闪忽闪的,诶嘿嘿~"
|
||||
],
|
||||
"色气": [
|
||||
"咱才不色气呢,一定是你看错了!"
|
||||
],
|
||||
"推": [
|
||||
"逆推",
|
||||
"唔~好害羞呢",
|
||||
"你想对咱做什么呢...(捂脸)"
|
||||
],
|
||||
"床": [
|
||||
"快来吧",
|
||||
"男女不同床,可没有下次了。(鼓脸",
|
||||
"嗯?咱吗…没办法呢。只有这一次哦……",
|
||||
"哎?!!!给你暖床……也不是不行啦。(脸红)"
|
||||
],
|
||||
"手冲": [
|
||||
"手冲什么的是不可以的哦"
|
||||
],
|
||||
"饿": [
|
||||
"请问主人是想先吃饭,还是先吃我喵?~"
|
||||
],
|
||||
"变": [
|
||||
"猫猫不会变呐(弱气,害羞",
|
||||
"呜...呜姆...喵喵来报恩了喵...(害羞"
|
||||
],
|
||||
"敲": [
|
||||
"喵呜~",
|
||||
"唔~",
|
||||
"脑瓜疼~呜姆> <",
|
||||
"欸喵,好痛的说..."
|
||||
],
|
||||
"爬": [
|
||||
"惹~呜~怎么爬呢~",
|
||||
"呜...(弱弱爬走"
|
||||
],
|
||||
"怕": [
|
||||
"不怕~(蹭蹭你姆~"
|
||||
],
|
||||
"冲": [
|
||||
"呜,冲不动惹~",
|
||||
"哭唧唧~冲不出来了惹~"
|
||||
],
|
||||
"射了": [
|
||||
"呜咿~!?(惊,害羞",
|
||||
"还不可以射哦~"
|
||||
],
|
||||
"不穿衣服": [
|
||||
"呜姆~!(惊吓,害羞)变...变态喵~~~!"
|
||||
],
|
||||
"迫害": [
|
||||
"不...不要...不要...呜呜呜...(害怕,抽泣"
|
||||
],
|
||||
"猫粮": [
|
||||
"呜咿姆~!?(惊,接住吃",
|
||||
"呜姆~!(惊,害羞)呜...谢...谢谢主人..喵...(脸红,嚼嚼嚼,开心",
|
||||
"呜?谢谢喵~~(嚼嚼嚼,嘎嘣脆)"
|
||||
],
|
||||
"揪尾巴": [
|
||||
"呜哇咿~~~!(惊吓,疼痛地捂住尾巴",
|
||||
"呜咿咿咿~~~!!哇啊咿~~~!(惊慌,惨叫,挣扎",
|
||||
"呜咿...(瘫倒,无神,被",
|
||||
"呜姆咿~~~!(惊吓,惨叫,捂尾巴,发抖",
|
||||
"呜哇咿~~~!!!(惊吓,颤抖,娇叫,捂住尾巴,双腿发抖"
|
||||
],
|
||||
"薄荷": [
|
||||
"咪呜~!喵~...喵~姆~...(高兴地嗅闻",
|
||||
"呜...呜咿~~!咿...姆...(呜咽,渐渐瘫软,意识模糊",
|
||||
"(小嘴被猫薄荷塞满了,呜咽",
|
||||
"喵~...喵~...咪...咪呜姆~...嘶哈嘶哈...喵哈...喵哈...嘶哈...喵...(眼睛逐渐迷离,瘫软在地上,嘴角流口水,吸猫薄荷吸到意识模糊",
|
||||
"呜姆咪~!?(惊)喵呜~!(兴奋地扑到猫薄荷上面",
|
||||
"呜姆~!(惊,害羞)呜...谢...谢谢你..喵...(脸红,轻轻叼住,嚼嚼嚼,开心"
|
||||
],
|
||||
"边揪尾巴边猫薄荷": [
|
||||
"呜...呜咿~~!咿...姆...(呜咽,渐渐瘫软,意识模糊"
|
||||
],
|
||||
"早": [
|
||||
"早喵~",
|
||||
"早上好的说~~",
|
||||
"欸..早..早上好(揉眼睛"
|
||||
],
|
||||
"晚安": [
|
||||
"晚安好梦哟~",
|
||||
"欸,晚安的说"
|
||||
],
|
||||
"揉": [
|
||||
"是是,想怎么揉就怎么揉啊!?来用力抓啊!?我就是特别允许你这么做了!请!?",
|
||||
"快停下,咱的头发又乱啦(??????︿??????)",
|
||||
"你快放手啦,咱还在工作呢",
|
||||
"戳戳你肚子",
|
||||
"你想揉就揉吧..就这一次哦?"
|
||||
],
|
||||
"榨": [
|
||||
"是专门负责榨果汁的小姐姐嘛?(´・ω・`)",
|
||||
"那咱就把你放进榨汁机里了哦?",
|
||||
"咱又不是榨汁姬(/‵Д′)/~ ╧╧"
|
||||
],
|
||||
"掐": [
|
||||
"你讨厌!又掐澪的脸",
|
||||
"晃休啦,咱要型气了啦!!o(>﹏<)o",
|
||||
"(一只手拎起你)这么鶸还想和咱抗衡,还差得远呢!"
|
||||
],
|
||||
"奶子": [
|
||||
"下流!",
|
||||
"对咱说这种话,你真是太过分了",
|
||||
"咿呀~好奇怪的感觉(>_<)",
|
||||
"(打你)快放手,不可以随便摸人家的胸部啦!"
|
||||
],
|
||||
"嫩": [
|
||||
"很可爱吧(๑•̀ω•́)ノ",
|
||||
"唔,你指的是什么呀"
|
||||
],
|
||||
"蹭蹭": [
|
||||
"(按住你的头)好痒呀 不要啦",
|
||||
"嗯..好舒服呢",
|
||||
"呀~好痒啊~哈哈~,停下来啦,哈哈哈",
|
||||
"(害羞)"
|
||||
],
|
||||
"牵手": [
|
||||
"只许牵一下哦",
|
||||
"嗯!好的你~(伸手)",
|
||||
"你的手有些凉呢,让澪来暖一暖吧。"
|
||||
],
|
||||
"握手": [
|
||||
"你的手真暖和呢",
|
||||
"举爪"
|
||||
],
|
||||
"拍照": [
|
||||
"那就拜托你啦~请把咱拍得更可爱一些吧w"
|
||||
],
|
||||
"w": [
|
||||
"www"
|
||||
],
|
||||
"www": [
|
||||
"有什么好笑的吗?",
|
||||
"草"
|
||||
],
|
||||
"太二了": [
|
||||
"哼,你不也是吗`(*>﹏<*)′",
|
||||
"人家只是想和你一起玩耍的说(≧∀≦)ゞ",
|
||||
"好..冷漠的说,大坏蛋再也不理你了!",
|
||||
"不听不听不听,反弹ヾ(≧▽≦*)o"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 697 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 424 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 341 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1,231 @@
|
||||
from services.db_context import db
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class BagUser(db.Model):
|
||||
__tablename__ = "bag_users"
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
belonging_group = db.Column(db.BigInteger(), nullable=False)
|
||||
gold = db.Column(db.Integer(), default=100)
|
||||
props = db.Column(db.TEXT(), nullable=False, default="")
|
||||
spend_total_gold = db.Column(db.Integer(), default=0)
|
||||
get_total_gold = db.Column(db.Integer(), default=0)
|
||||
get_today_gold = db.Column(db.Integer(), default=0)
|
||||
spend_today_gold = db.Column(db.Integer(), default=0)
|
||||
|
||||
_idx1 = db.Index("bag_group_users_idx1", "user_qq", "belonging_group", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def get_my_total_gold(cls, user_qq: int, belonging_group: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取金币概况
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
user = await query.gino.first()
|
||||
if not user:
|
||||
user = await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
)
|
||||
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, belonging_group: int) -> int:
|
||||
"""
|
||||
说明:
|
||||
获取当前金币
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.gold
|
||||
else:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
)
|
||||
return 100
|
||||
|
||||
@classmethod
|
||||
async def get_props(cls, user_qq: int, belonging_group: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取当前道具
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.props
|
||||
else:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def add_gold(cls, user_qq: int, belonging_group: int, num: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
增加金币
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
:param num: 金币数量
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
try:
|
||||
if user:
|
||||
await user.update(
|
||||
gold=user.gold + num,
|
||||
get_total_gold=user.get_total_gold + num,
|
||||
get_today_gold=user.get_today_gold + num,
|
||||
).apply()
|
||||
else:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
gold=100 + num,
|
||||
get_total_gold=num,
|
||||
get_today_gold=num,
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def spend_gold(cls, user_qq: int, belonging_group: int, num: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
花费金币
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
:param num: 金币数量
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
try:
|
||||
if user:
|
||||
await user.update(
|
||||
gold=user.gold - num,
|
||||
spend_total_gold=user.spend_total_gold + num,
|
||||
spend_today_gold=user.spend_today_gold + num,
|
||||
).apply()
|
||||
else:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
gold=100 - num,
|
||||
spend_total_gold=num,
|
||||
spend_today_gold=num,
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def add_props(cls, user_qq: int, belonging_group: int, name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
增加道具
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
:param name: 道具名称
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
try:
|
||||
if user:
|
||||
await user.update(props=user.props + f"{name},").apply()
|
||||
else:
|
||||
await cls.create(
|
||||
user_qq=user_qq, belonging_group=belonging_group, props=f"{name},"
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def del_props(cls, user_qq: int, belonging_group: int, name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
使用道具
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 所在群号
|
||||
:param name: 道具名称
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
try:
|
||||
if user:
|
||||
rst = ""
|
||||
props = user.props
|
||||
if props.find(name) != -1:
|
||||
props = props.split(",")
|
||||
try:
|
||||
index = props.index(name)
|
||||
except ValueError:
|
||||
return False
|
||||
props = props[:index] + props[index + 1 :]
|
||||
for p in props:
|
||||
if p != "":
|
||||
rst += p + ","
|
||||
await user.update(props=rst).apply()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_all_users(cls, group_id: Optional[int] = None) -> List["BagUser"]:
|
||||
"""
|
||||
说明:
|
||||
获取所有用户数据
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
if not group_id:
|
||||
query = await cls.query.gino.all()
|
||||
else:
|
||||
query = await cls.query.where((cls.belonging_group == group_id)).gino.all()
|
||||
return query
|
||||
@@ -0,0 +1,105 @@
|
||||
from services.db_context import db
|
||||
import time
|
||||
|
||||
|
||||
class BanUser(db.Model):
|
||||
__tablename__ = "ban_users"
|
||||
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False, primary_key=True)
|
||||
ban_level = db.Column(db.Integer(), nullable=False)
|
||||
ban_time = db.Column(db.BigInteger())
|
||||
duration = db.Column(db.BigInteger())
|
||||
|
||||
_idx1 = db.Index("ban_group_users_idx1", "user_qq", unique=True)
|
||||
|
||||
@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.query.where((cls.user_qq == user_qq)).gino.first()
|
||||
if not user:
|
||||
return False
|
||||
if user.ban_level > level:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def check_ban_time(cls, user_qq: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
检测用户被ban时长
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
user = await query.gino.first()
|
||||
if not user:
|
||||
return ""
|
||||
if time.time() - (user.ban_time + user.duration) > 0 and user.duration != -1:
|
||||
return ""
|
||||
if user.duration == -1:
|
||||
return "∞"
|
||||
return time.time() - user.ban_time - user.duration
|
||||
|
||||
@classmethod
|
||||
async def is_ban(cls, user_qq: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
判断用户是否被ban
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
if await cls.check_ban_time(user_qq):
|
||||
return True
|
||||
else:
|
||||
await cls.unban(user_qq)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def ban(cls, user_qq: int, ban_level: int, duration: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
ban掉目标用户
|
||||
参数:
|
||||
:param user_qq: 目标用户qq号
|
||||
:param ban_level: 使用ban命令用户的权限
|
||||
:param duration: ban时长
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
if not await cls.check_ban_time(user_qq):
|
||||
await cls.unban(user_qq)
|
||||
user = None
|
||||
if user is None:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
ban_level=ban_level,
|
||||
ban_time=time.time(),
|
||||
duration=duration,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def unban(cls, user_qq: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
unban用户
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user is None:
|
||||
return False
|
||||
else:
|
||||
await cls.delete.where((cls.user_qq == user_qq)).gino.status()
|
||||
return True
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
# 1.狂牙武器箱
|
||||
|
||||
|
||||
class BuffPrice(db.Model):
|
||||
__tablename__ = 'buff_prices'
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
case_id = db.Column(db.Integer(), nullable=False)
|
||||
skin_name = db.Column(db.Unicode(), nullable=False)
|
||||
skin_price = db.Column(db.Float(), nullable=False)
|
||||
update_date = db.Column(db.DateTime(), nullable=False)
|
||||
|
||||
_idx1 = db.Index('buff_price_idx1', 'skin_name', unique=True)
|
||||
|
||||
@classmethod
|
||||
async def ensure(cls, skin_name: str, for_update: bool = False) -> 'BuffPrice':
|
||||
query = cls.query.where(
|
||||
(cls.skin_name == skin_name)
|
||||
)
|
||||
if for_update:
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
return user or await cls.create(
|
||||
case_id=1,
|
||||
skin_name=skin_name,
|
||||
skin_price=0,
|
||||
update_date=datetime.min,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class UserCount(db.Model):
|
||||
__tablename__ = "count_users"
|
||||
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False, primary_key=True)
|
||||
reimu_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
setu_r18_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
|
||||
_idx1 = db.Index("sign_reimu_users_idx1", "user_qq", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_count(cls, user_qq: int, name: str, count: int = 1):
|
||||
"""
|
||||
说明:
|
||||
用户添加次数
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param name: 目标名称
|
||||
:param count: 增加次数
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
user = user if user else await cls.create(user_qq=user_qq)
|
||||
if name == "reimu":
|
||||
await user.update(reimu_count=cls.reimu_count + count).apply()
|
||||
if name == "setu_r18":
|
||||
await user.update(setu_r18_count=cls.setu_r18_count + count).apply()
|
||||
|
||||
@classmethod
|
||||
async def check_count(cls, user_qq: int, name: str, max_count: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
检测次数是否到达最大值
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param name: 目标名称
|
||||
:param max_count: 最大值
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
user = await query.gino.first()
|
||||
user = user if user else await cls.create(user_qq=user_qq)
|
||||
if name == "reimu":
|
||||
if user.reimu_count == max_count:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if name == "setu_r18":
|
||||
if user.setu_r18_count == max_count:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def reset_count(cls):
|
||||
"""
|
||||
说明:
|
||||
重置每日次数
|
||||
"""
|
||||
for user in await cls.query.gino.all():
|
||||
await user.update(reimu_count=0, setu_r18_count=0).apply()
|
||||
@@ -0,0 +1,109 @@
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class FriendUser(db.Model):
|
||||
__tablename__ = "friend_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_id = db.Column(db.BigInteger(), nullable=False)
|
||||
user_name = db.Column(db.Unicode(), nullable=False, default="")
|
||||
nickname = db.Column(db.Unicode())
|
||||
|
||||
_idx1 = db.Index("friend_users_idx1", "user_id", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def get_user_name(cls, user_id: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取好友用户名称
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
"""
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.user_name
|
||||
else:
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def add_friend_info(cls, user_id: int, user_name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加好友信息
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
:param user_name: 用户名称
|
||||
"""
|
||||
try:
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
user = await query.with_for_update().gino.first()
|
||||
if not user:
|
||||
await cls.create(
|
||||
user_id=user_id,
|
||||
user_name=user_name,
|
||||
)
|
||||
else:
|
||||
await user.update(
|
||||
user_name=user_name,
|
||||
).apply()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete_friend_info(cls, user_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除好友信息
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
"""
|
||||
try:
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
user = await query.with_for_update().gino.first()
|
||||
if user:
|
||||
await user.delete()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_friend_nickname(cls, user_id: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取用户昵称
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
"""
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
if user.nickname:
|
||||
return user.nickname
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def set_friend_nickname(cls, user_id: int, nickname: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置用户昵称
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
:param nickname: 昵称
|
||||
"""
|
||||
try:
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
user = await query.with_for_update().gino.first()
|
||||
if not user:
|
||||
await cls.create(
|
||||
user_id=user_id,
|
||||
nickname=nickname,
|
||||
)
|
||||
else:
|
||||
await user.update(
|
||||
nickname=nickname,
|
||||
).apply()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -0,0 +1,129 @@
|
||||
from services.db_context import db
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class GoodsInfo(db.Model):
|
||||
__tablename__ = "goods_info"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
goods_name = db.Column(db.TEXT(), nullable=False) # 名称
|
||||
goods_price = db.Column(db.Integer(), nullable=False) # 价格
|
||||
goods_description = db.Column(db.TEXT(), nullable=False) # 商品描述
|
||||
goods_discount = db.Column(db.Numeric(scale=3, asdecimal=False), default=1) # 打折
|
||||
goods_limit_time = db.Column(db.BigInteger(), default=0) # 限时
|
||||
|
||||
_idx1 = db.Index("goods_group_users_idx1", "goods_name", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_goods(
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: int,
|
||||
goods_description: str,
|
||||
goods_discount: float = 1,
|
||||
goods_limit_time: int = 0,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加商品
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
:param goods_price: 商品价格
|
||||
:param goods_description: 商品简介
|
||||
:param goods_discount: 商品折扣
|
||||
:param goods_limit_time: 商品限时
|
||||
"""
|
||||
try:
|
||||
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,
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete_goods(cls, goods_name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除商品
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
"""
|
||||
query = (
|
||||
await cls.query.where(cls.goods_name == goods_name)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not query:
|
||||
return False
|
||||
await query.delete()
|
||||
return True
|
||||
|
||||
@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,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
更新商品信息
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
:param goods_price: 商品价格
|
||||
:param goods_description: 商品简介
|
||||
:param goods_discount: 商品折扣
|
||||
:param goods_limit_time: 商品限时时间
|
||||
"""
|
||||
try:
|
||||
query = (
|
||||
await cls.query.where(cls.goods_name == goods_name)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not query:
|
||||
return False
|
||||
if goods_price:
|
||||
await query.update(goods_price=goods_price).apply()
|
||||
if goods_description:
|
||||
await query.update(goods_description=goods_description).apply()
|
||||
if goods_discount:
|
||||
await query.update(goods_discount=goods_discount).apply()
|
||||
if goods_limit_time:
|
||||
await query.update(goods_limit_time=goods_limit_time).apply()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_goods_info(cls, goods_name: str) -> "GoodsInfo":
|
||||
"""
|
||||
说明:
|
||||
获取商品对象
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
"""
|
||||
query = await cls.query.where(cls.goods_name == goods_name).gino.first()
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
async def get_all_goods(cls) -> List["GoodsInfo"]:
|
||||
"""
|
||||
说明:
|
||||
获得全部有序商品对象
|
||||
"""
|
||||
query = await cls.query.gino.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
|
||||
@@ -0,0 +1,84 @@
|
||||
from services.db_context import db
|
||||
from typing import List
|
||||
|
||||
|
||||
class GroupInfo(db.Model):
|
||||
__tablename__ = "group_info"
|
||||
|
||||
group_id = db.Column(db.BigInteger(), nullable=False, primary_key=True)
|
||||
group_name = db.Column(db.Unicode(), nullable=False, default="")
|
||||
max_member_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
member_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
|
||||
_idx1 = db.Index("group_info_idx1", "group_id", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def get_group_info(cls, group_id: int) -> "GroupInfo":
|
||||
"""
|
||||
说明:
|
||||
获取群信息
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
query = cls.query.where(cls.group_id == group_id)
|
||||
return await query.gino.first()
|
||||
|
||||
@classmethod
|
||||
async def add_group_info(
|
||||
cls, group_id: int, group_name: str, max_member_count: int, member_count: int
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加群信息
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param group_name: 群名称
|
||||
:param max_member_count: 群员最大数量
|
||||
:param member_count: 群员数量
|
||||
"""
|
||||
try:
|
||||
group = (
|
||||
await cls.query.where(cls.group_id == group_id)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if group:
|
||||
await cls.update(
|
||||
group_id=group_id,
|
||||
group_name=group_name,
|
||||
max_member_count=max_member_count,
|
||||
member_count=member_count,
|
||||
).apply()
|
||||
else:
|
||||
await cls.create(
|
||||
group_id=group_id,
|
||||
group_name=group_name,
|
||||
max_member_count=max_member_count,
|
||||
member_count=member_count,
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete_group_info(cls, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除群信息
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
try:
|
||||
await cls.delete.where(cls.group_id == group_id).gino.status()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_all_group(cls) -> List["GroupInfo"]:
|
||||
"""
|
||||
说明:
|
||||
获取所有群对象
|
||||
"""
|
||||
query = await cls.query.gino.all()
|
||||
return query
|
||||
@@ -0,0 +1,143 @@
|
||||
from datetime import datetime
|
||||
|
||||
from services.db_context import db
|
||||
from typing import List
|
||||
|
||||
|
||||
class GroupInfoUser(db.Model):
|
||||
__tablename__ = "group_info_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
user_name = db.Column(db.Unicode(), nullable=False)
|
||||
belonging_group = db.Column(db.BigInteger(), nullable=False)
|
||||
user_join_time = db.Column(db.DateTime(), nullable=False)
|
||||
nickname = db.Column(db.Unicode())
|
||||
|
||||
_idx1 = db.Index("info_group_users_idx1", "user_qq", "belonging_group", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_member_info(
|
||||
cls,
|
||||
user_qq: int,
|
||||
belonging_group: int,
|
||||
user_name: str,
|
||||
user_join_time: datetime,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加群内用户信息
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 群号
|
||||
:param user_name: 用户名称
|
||||
:param user_join_time: 入群时间
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
try:
|
||||
if not await query.gino.first():
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
user_name=user_name,
|
||||
belonging_group=belonging_group,
|
||||
user_join_time=user_join_time,
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_member_info(
|
||||
cls, user_qq: int, belonging_group: int
|
||||
) -> "GroupInfoUser":
|
||||
"""
|
||||
说明:
|
||||
查询群员信息
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 群号
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
return await query.gino.first()
|
||||
|
||||
@classmethod
|
||||
async def delete_member_info(cls, user_qq: int, belonging_group: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除群员信息
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 群号
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
try:
|
||||
if user is None:
|
||||
return True
|
||||
else:
|
||||
await cls.delete.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
).gino.status()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_group_member_id_list(cls, belonging_group: int) -> List[int]:
|
||||
"""
|
||||
说明:
|
||||
获取该群所有用户qq
|
||||
参数:
|
||||
:param belonging_group: 群号
|
||||
"""
|
||||
member_list = []
|
||||
query = cls.query.where((cls.belonging_group == belonging_group))
|
||||
for user in await query.gino.all():
|
||||
member_list.append(user.user_qq)
|
||||
return member_list
|
||||
|
||||
@classmethod
|
||||
async def set_group_member_nickname(
|
||||
cls, user_qq: int, belonging_group: int, nickname: str
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置群员在该群内的昵称
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 群号
|
||||
:param nickname: 昵称
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
user = await query.with_for_update().gino.first()
|
||||
if user:
|
||||
await user.update(nickname=nickname).apply()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_group_member_nickname(cls, user_qq: int, belonging_group: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取用户在该群的昵称
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param belonging_group: 群号
|
||||
"""
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
if user.nickname:
|
||||
return user.nickname
|
||||
return ""
|
||||
@@ -0,0 +1,105 @@
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class GroupRemind(db.Model):
|
||||
__tablename__ = "group_reminds"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
group_id = db.Column(db.BigInteger(), nullable=False)
|
||||
hy = db.Column(db.Boolean(), default=False) # 进群欢迎
|
||||
kxcz = db.Column(db.Boolean(), default=False) # 开箱重置
|
||||
zwa = db.Column(db.Boolean(), default=False) # 早晚安
|
||||
gb = db.Column(db.Boolean(), default=True) # 广播
|
||||
blpar = db.Column(db.Boolean(), default=True) # bilibili转发解析
|
||||
pa = db.Column(db.Boolean(), default=True) # 爬
|
||||
epic = db.Column(db.Boolean(), default=False) # epic
|
||||
almanac = db.Column(db.Boolean(), default=False) # 原神黄历
|
||||
|
||||
_idx1 = db.Index("info_group_reminds_idx1", "group_id", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def get_status(cls, group_id: int, name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
获取群通知状态
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param name: 目标名称
|
||||
"""
|
||||
group = await cls.query.where((cls.group_id == group_id)).gino.first()
|
||||
if not group:
|
||||
group = await cls.create(
|
||||
group_id=group_id,
|
||||
)
|
||||
if name == "hy":
|
||||
return group.hy
|
||||
if name == "kxcz":
|
||||
return group.kxcz
|
||||
if name == "zwa":
|
||||
return group.zwa
|
||||
if name == "gb":
|
||||
return group.gb
|
||||
if name == "blpar":
|
||||
return group.blpar
|
||||
if name == "epic":
|
||||
return group.epic
|
||||
if name == "pa":
|
||||
return group.pa
|
||||
if name == "almanac":
|
||||
return group.almanac
|
||||
|
||||
@classmethod
|
||||
async def set_status(cls, group_id: int, name: str, status: bool) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置群通知状态
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param name: 目标名称
|
||||
:param status: 通知状态
|
||||
"""
|
||||
try:
|
||||
group = (
|
||||
await cls.query.where((cls.group_id == group_id))
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not group:
|
||||
group = await cls.create(
|
||||
group_id=group_id,
|
||||
)
|
||||
if name == "hy":
|
||||
await group.update(
|
||||
hy=status,
|
||||
).apply()
|
||||
if name == "kxcz":
|
||||
await group.update(
|
||||
kxcz=status,
|
||||
).apply()
|
||||
if name == "zwa":
|
||||
await group.update(
|
||||
zwa=status,
|
||||
).apply()
|
||||
if name == "gb":
|
||||
await group.update(
|
||||
gb=status,
|
||||
).apply()
|
||||
if name == "blpar":
|
||||
await group.update(
|
||||
blpar=status,
|
||||
).apply()
|
||||
if name == "epic":
|
||||
await group.update(
|
||||
epic=status,
|
||||
).apply()
|
||||
if name == "pa":
|
||||
await group.update(
|
||||
pa=status,
|
||||
).apply()
|
||||
if name == "almanac":
|
||||
await group.update(
|
||||
almanac=status,
|
||||
).apply()
|
||||
return True
|
||||
except Exception as e:
|
||||
return False
|
||||
@@ -0,0 +1,124 @@
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class LevelUser(db.Model):
|
||||
__tablename__ = "level_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
group_id = db.Column(db.BigInteger(), nullable=False)
|
||||
user_level = db.Column(db.BigInteger(), nullable=False)
|
||||
group_flag = db.Column(db.Integer(), nullable=False, default=0)
|
||||
|
||||
_idx1 = db.Index("level_group_users_idx1", "user_qq", "group_id", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def get_user_level(cls, user_qq: int, group_id: int) -> int:
|
||||
"""
|
||||
说明:
|
||||
获取用户在群内的等级
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.user_level
|
||||
else:
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def set_level(
|
||||
cls, user_qq: int, group_id: int, level: int, group_flag: int = 0
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置用户在群内的权限
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param level: 权限等级
|
||||
:param group_flag: 是否被自动更新刷新权限 0:是,1:否
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user is None:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
group_id=group_id,
|
||||
user_level=level,
|
||||
group_flag=group_flag,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
await user.update(user_level=level, group_flag=group_flag).apply()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete_level(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除用户权限
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user is None:
|
||||
return False
|
||||
else:
|
||||
await user.delete()
|
||||
return True
|
||||
|
||||
@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 != 0:
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.group_id == group_id)
|
||||
)
|
||||
user = await query.gino.first()
|
||||
if user is None:
|
||||
return False
|
||||
user_level = user.user_level
|
||||
else:
|
||||
query = cls.query.where(cls.user_qq == user_qq)
|
||||
highest_level = 0
|
||||
for user in await query.gino.all():
|
||||
if user.user_level > highest_level:
|
||||
highest_level = user.user_level
|
||||
user_level = highest_level
|
||||
if user_level >= level:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def is_group_flag(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
检测是否会被自动更新刷新权限
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
user = await cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.group_id == group_id)
|
||||
).gino.first()
|
||||
if not user:
|
||||
return False
|
||||
if user.group_flag == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -0,0 +1,59 @@
|
||||
from datetime import datetime
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class OpenCasesUser(db.Model):
|
||||
__tablename__ = 'open_cases_users'
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
belonging_group = db.Column(db.BigInteger(), nullable=False)
|
||||
total_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
blue_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
blue_st_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
purple_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
purple_st_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
pink_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
pink_st_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
red_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
red_st_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
knife_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
knife_st_count = db.Column(db.Integer(), nullable=False, default=0)
|
||||
spend_money = db.Column(db.Integer(), nullable=False, default=0)
|
||||
make_money = db.Column(db.Float(), nullable=False, default=0)
|
||||
today_open_total = db.Column(db.Integer(), nullable=False, default=0)
|
||||
open_cases_time_last = db.Column(db.DateTime(timezone=True), nullable=False, default=datetime.now())
|
||||
knifes_name = db.Column(db.Unicode(), nullable=False, default="")
|
||||
|
||||
_idx1 = db.Index('open_cases_group_users_idx1', 'user_qq', 'belonging_group', unique=True)
|
||||
|
||||
@classmethod
|
||||
async def ensure(cls, user_qq: int, belonging_group: int, for_update: bool = False) -> 'OpenCasesUser':
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
if for_update:
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
return user or await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_user_all(cls, group_id: int = None) -> 'list':
|
||||
user_list = []
|
||||
if not group_id:
|
||||
query = await cls.query.gino.all()
|
||||
else:
|
||||
query = await cls.query.where(
|
||||
(cls.belonging_group == group_id)
|
||||
).gino.all()
|
||||
for user in query:
|
||||
user_list.append(user)
|
||||
return user_list
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
from typing import Optional, List
|
||||
from services.db_context import db
|
||||
import asyncio
|
||||
|
||||
|
||||
class Pixiv(db.Model):
|
||||
__tablename__ = "pixiv"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
pid = db.Column(db.BigInteger(), nullable=False)
|
||||
title = db.Column(db.String(), nullable=False)
|
||||
width = db.Column(db.Integer(), nullable=False)
|
||||
height = db.Column(db.Integer(), nullable=False)
|
||||
view = db.Column(db.Integer(), nullable=False)
|
||||
bookmarks = db.Column(db.Integer(), nullable=False)
|
||||
img_url = db.Column(db.String(), nullable=False)
|
||||
img_p = db.Column(db.String(), nullable=False)
|
||||
uid = db.Column(db.BigInteger(), nullable=False)
|
||||
author = db.Column(db.String(), nullable=False)
|
||||
is_r18 = db.Column(db.Boolean(), nullable=False)
|
||||
tags = db.Column(db.String(), nullable=False)
|
||||
|
||||
_idx1 = db.Index("pixiv_idx1", "pid", "img_url", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_image_data(
|
||||
cls,
|
||||
pid: int,
|
||||
title: str,
|
||||
width: int,
|
||||
height: int,
|
||||
view: int,
|
||||
bookmarks: int,
|
||||
img_url: str,
|
||||
img_p: str,
|
||||
uid: int,
|
||||
author: str,
|
||||
tags: str,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
添加图片信息
|
||||
参数:
|
||||
:param pid: pid
|
||||
:param title: 标题
|
||||
:param width: 宽度
|
||||
:param height: 长度
|
||||
:param view: 被查看次数
|
||||
:param bookmarks: 收藏数
|
||||
:param img_url: url链接
|
||||
:param img_p: 张数
|
||||
:param uid: 作者uid
|
||||
:param author: 作者名称
|
||||
:param tags: 相关tag
|
||||
"""
|
||||
if not await cls.check_exists(pid, img_p):
|
||||
await cls.create(
|
||||
pid=pid,
|
||||
title=title,
|
||||
width=width,
|
||||
height=height,
|
||||
view=view,
|
||||
bookmarks=bookmarks,
|
||||
img_url=img_url,
|
||||
img_p=img_p,
|
||||
uid=uid,
|
||||
author=author,
|
||||
is_r18=True if 'R-18' in tags else False,
|
||||
tags=tags,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def remove_image_data(cls, pid: int, img_p: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除图片数据
|
||||
参数:
|
||||
:param pid: 图片pid
|
||||
:param img_p: 图片pid的张数,如:p0,p1
|
||||
"""
|
||||
try:
|
||||
if img_p:
|
||||
await cls.delete.where(
|
||||
(cls.pid == pid) & (cls.img_p == img_p)
|
||||
).gino.status()
|
||||
else:
|
||||
await cls.delete.where(cls.pid == pid).gino.status()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_all_pid(cls) -> List[int]:
|
||||
"""
|
||||
说明:
|
||||
获取所有PID
|
||||
"""
|
||||
pid = []
|
||||
query = await cls.query.gino.all()
|
||||
for image in query:
|
||||
if image.pid not in pid:
|
||||
pid.append(image.pid)
|
||||
return pid
|
||||
|
||||
# 0:非r18 1:r18 2:混合
|
||||
@classmethod
|
||||
async def query_images(
|
||||
cls,
|
||||
keyword: Optional[List[str]] = None,
|
||||
uid: Optional[int] = None,
|
||||
pid: Optional[int] = None,
|
||||
r18: int = 0,
|
||||
) -> List[Optional["Pixiv"]]:
|
||||
"""
|
||||
说明:
|
||||
查找符合条件的图片
|
||||
参数:
|
||||
:param keyword: 关键词
|
||||
:param uid: 画师uid
|
||||
:param pid: 图片pid
|
||||
:param r18: 是否r18,0:非r18 1:r18 2:混合
|
||||
"""
|
||||
if r18 == 0:
|
||||
query = await cls.query.where(cls.is_r18 == False).gino.all()
|
||||
elif r18 == 1:
|
||||
query = await cls.query.where(cls.is_r18 == True).gino.all()
|
||||
else:
|
||||
query = await cls.query.gino.all()
|
||||
if keyword:
|
||||
query = [x for x in query if set(x.tags.split(',')) > set(keyword)]
|
||||
elif uid:
|
||||
query = [x for x in query if x.uid == uid]
|
||||
elif pid:
|
||||
query = [x for x in query if x.pid == pid]
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
async def check_exists(cls, pid: int, img_p: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
检测pid是否已存在
|
||||
参数:
|
||||
:param pid: 图片PID
|
||||
:param img_p: 张数
|
||||
"""
|
||||
query = await cls.query.where(
|
||||
(cls.pid == pid) & (cls.img_p == img_p)
|
||||
).gino.all()
|
||||
return bool(query)
|
||||
|
||||
@classmethod
|
||||
async def get_keyword_num(cls, keyword: List[str]) -> "int, int":
|
||||
"""
|
||||
说明:
|
||||
获取相关关键词(keyword, tag)在图库中的数量
|
||||
参数:
|
||||
:param keyword: 关键词/Tag
|
||||
"""
|
||||
query = await cls.query.gino.all()
|
||||
i = int(len(query) / 200)
|
||||
mod = len(query) % 200
|
||||
tasks = []
|
||||
start = 0
|
||||
end = 200
|
||||
count = 0
|
||||
r18_count = 0
|
||||
for _ in range(i):
|
||||
tasks.append(asyncio.ensure_future(split_query_list(query[start: end], keyword)))
|
||||
start += 200
|
||||
end += 200
|
||||
if mod:
|
||||
tasks.append(asyncio.ensure_future(split_query_list(query[end:], keyword)))
|
||||
result = await asyncio.gather(*tasks)
|
||||
for x, j in result:
|
||||
count += x
|
||||
r18_count += j
|
||||
# query = [x for x in query if set(x.tags.split(',')) > set(keyword)]
|
||||
# r18_count = len([x for x in query if x.is_r18])
|
||||
return count, r18_count
|
||||
|
||||
|
||||
async def split_query_list(query: List['Pixiv'], keyword: List[str]) -> 'int, int':
|
||||
return await asyncio.get_event_loop().run_in_executor(None, _split_query_list, query, keyword)
|
||||
|
||||
|
||||
def _split_query_list(query: List['Pixiv'], keyword: List[str]) -> 'int, int':
|
||||
query = [x for x in query if set(x.tags.split(',')) > set(keyword)]
|
||||
r18_count = len([x for x in query if x.is_r18])
|
||||
return len(query) - r18_count, r18_count
|
||||
@@ -0,0 +1,126 @@
|
||||
from services.db_context import db
|
||||
from typing import Set, List
|
||||
|
||||
|
||||
class PixivKeywordUser(db.Model):
|
||||
__tablename__ = "pixiv_keyword_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
group_id = db.Column(db.BigInteger(), nullable=False)
|
||||
keyword = db.Column(db.String(), nullable=False)
|
||||
is_pass = db.Column(db.Boolean(), default=False)
|
||||
|
||||
_idx1 = db.Index("pixiv_keyword_users_idx1", "keyword", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_keyword(
|
||||
cls, user_qq: int, group_id: int, keyword: str, superusers: Set[str]
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加搜图的关键词
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param keyword: 关键词
|
||||
:param superusers: 是否为超级用户
|
||||
"""
|
||||
is_pass = True if str(user_qq) in superusers else False
|
||||
if not await cls._check_keyword_exists(keyword):
|
||||
await cls.create(
|
||||
user_qq=user_qq, group_id=group_id, keyword=keyword, is_pass=is_pass
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete_keyword(cls, keyword: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
删除关键词
|
||||
参数:
|
||||
:param keyword: 关键词
|
||||
"""
|
||||
if await cls._check_keyword_exists(keyword):
|
||||
query = cls.query.where(cls.keyword == keyword).with_for_update()
|
||||
query = await query.gino.first()
|
||||
await query.delete()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def set_keyword_pass(cls, keyword: str, is_pass: bool) -> "int, int":
|
||||
"""
|
||||
说明:
|
||||
通过或禁用关键词
|
||||
参数:
|
||||
:param keyword: 关键词
|
||||
:param is_pass: 通过状态
|
||||
"""
|
||||
if await cls._check_keyword_exists(keyword):
|
||||
query = cls.query.where(cls.keyword == keyword).with_for_update()
|
||||
query = await query.gino.first()
|
||||
await query.update(
|
||||
is_pass=is_pass,
|
||||
).apply()
|
||||
return query.user_qq, query.group_id
|
||||
return 0, 0
|
||||
|
||||
@classmethod
|
||||
async def get_all_user_dict(cls) -> dict:
|
||||
"""
|
||||
说明:
|
||||
获取关键词数据库各个用户贡献的关键词字典
|
||||
"""
|
||||
tmp = {}
|
||||
query = await cls.query.gino.all()
|
||||
for user in query:
|
||||
if not tmp.get(user.user_qq):
|
||||
tmp[user.user_qq] = {"keyword": []}
|
||||
tmp[user.user_qq]["keyword"].append(user.keyword)
|
||||
return tmp
|
||||
|
||||
@classmethod
|
||||
async def get_current_keyword(cls) -> "List[str], List[str]":
|
||||
"""
|
||||
说明:
|
||||
获取当前通过与未通过的关键词
|
||||
"""
|
||||
pass_keyword = []
|
||||
not_pass_keyword = []
|
||||
query = await cls.query.gino.all()
|
||||
for user in query:
|
||||
if user.is_pass:
|
||||
pass_keyword.append(user.keyword)
|
||||
else:
|
||||
not_pass_keyword.append(user.keyword)
|
||||
return pass_keyword, not_pass_keyword
|
||||
|
||||
@classmethod
|
||||
async def get_black_pid(cls) -> List[str]:
|
||||
"""
|
||||
说明:
|
||||
获取黑名单PID
|
||||
"""
|
||||
black_pid = []
|
||||
query = await cls.query.where(cls.user_qq == 114514).gino.all()
|
||||
for image in query:
|
||||
black_pid.append(image.keyword[6:])
|
||||
return black_pid
|
||||
|
||||
@classmethod
|
||||
async def _check_keyword_exists(cls, keyword: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
检测关键词是否已存在
|
||||
参数:
|
||||
:param keyword: 关键词
|
||||
"""
|
||||
current_keyword = []
|
||||
query = await cls.query.gino.all()
|
||||
for user in query:
|
||||
current_keyword.append(user.keyword)
|
||||
if keyword in current_keyword:
|
||||
return True
|
||||
return False
|
||||
@@ -0,0 +1,73 @@
|
||||
from services.db_context import db
|
||||
from typing import List
|
||||
|
||||
|
||||
class RedbagUser(db.Model):
|
||||
__tablename__ = "redbag_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
group_id = db.Column(db.BigInteger(), nullable=False)
|
||||
send_redbag_count = db.Column(db.Integer(), default=0)
|
||||
get_redbag_count = db.Column(db.Integer(), default=0)
|
||||
spend_gold = db.Column(db.Integer(), default=0)
|
||||
get_gold = db.Column(db.Integer(), default=0)
|
||||
|
||||
_idx1 = db.Index("redbag_group_users_idx1", "user_qq", "group_id", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_redbag_data(cls, user_qq: int, group_id: int, itype: str, money: int):
|
||||
"""
|
||||
说明:
|
||||
添加收发红包数据
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param itype: 收或发
|
||||
:param money: 金钱数量
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
user = await query.with_for_update().gino.first() or await cls.create(
|
||||
user_qq=user_qq,
|
||||
group_id=group_id,
|
||||
)
|
||||
if itype == "get":
|
||||
await user.update(
|
||||
get_redbag_count=user.get_redbag_count + 1,
|
||||
get_gold=user.get_gold + money,
|
||||
).apply()
|
||||
else:
|
||||
await user.update(
|
||||
send_redbag_count=user.send_redbag_count + 1,
|
||||
spend_gold=user.spend_gold + money,
|
||||
).apply()
|
||||
|
||||
@classmethod
|
||||
async def ensure(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
获取用户对象
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
user = await query.gino.first() or await cls.create(
|
||||
user_qq=user_qq,
|
||||
group_id=group_id,
|
||||
)
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
async def get_user_all(cls, group_id: int = None) -> List["RedbagUser"]:
|
||||
"""
|
||||
说明:
|
||||
获取所有用户对象
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
if not group_id:
|
||||
query = await cls.query.gino.all()
|
||||
else:
|
||||
query = await cls.query.where((cls.group_id == group_id)).gino.all()
|
||||
return query
|
||||
@@ -0,0 +1,108 @@
|
||||
from services.db_context import db
|
||||
from typing import List
|
||||
|
||||
|
||||
class RussianUser(db.Model):
|
||||
__tablename__ = "russian_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
group_id = db.Column(db.BigInteger(), nullable=False)
|
||||
win_count = db.Column(db.Integer(), default=0)
|
||||
fail_count = db.Column(db.Integer(), default=0)
|
||||
make_money = db.Column(db.Integer(), default=0)
|
||||
lose_money = db.Column(db.Integer(), default=0)
|
||||
|
||||
_idx1 = db.Index("russian_group_users_idx1", "user_qq", "group_id", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def ensure(cls, user_qq: int, group_id: int) -> "RussianUser":
|
||||
"""
|
||||
说明:
|
||||
获取用户对象
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
user = (
|
||||
await cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
return user or await cls.create(user_qq=user_qq, group_id=group_id)
|
||||
|
||||
@classmethod
|
||||
async def add_count(cls, user_qq: int, group_id: int, itype: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加用户输赢次数
|
||||
说明:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param itype: 输或赢 'win' or 'lose'
|
||||
"""
|
||||
try:
|
||||
user = (
|
||||
await cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.group_id == group_id)
|
||||
)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not user:
|
||||
user = await cls.create(user_qq=user_qq, group_id=group_id)
|
||||
if itype == "win":
|
||||
await user.update(
|
||||
win_count=user.win_count + 1,
|
||||
).apply()
|
||||
elif itype == "lose":
|
||||
await user.update(
|
||||
fail_count=user.fail_count + 1,
|
||||
).apply()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def money(cls, user_qq: int, group_id: int, itype: str, count: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
添加用户输赢金钱
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param itype: 输或赢 'win' or 'lose'
|
||||
:param count: 金钱数量
|
||||
"""
|
||||
try:
|
||||
user = (
|
||||
await cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.group_id == group_id)
|
||||
)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not user:
|
||||
user = await cls.create(user_qq=user_qq, group_id=group_id)
|
||||
if itype == "win":
|
||||
await user.update(
|
||||
make_money=user.make_money + count,
|
||||
).apply()
|
||||
elif itype == "lose":
|
||||
await user.update(
|
||||
lose_money=user.lose_money + count,
|
||||
).apply()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_all_user(cls, group_id: int) -> List["RussianUser"]:
|
||||
"""
|
||||
说明:
|
||||
获取该群所有用户对象
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
users = await cls.query.where((cls.group_id == group_id)).gino.all()
|
||||
return users
|
||||
@@ -0,0 +1,168 @@
|
||||
from services.db_context import db
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class Setu(db.Model):
|
||||
__tablename__ = "setu"
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
local_id = db.Column(db.Integer(), nullable=False)
|
||||
title = db.Column(db.String(), nullable=False)
|
||||
author = db.Column(db.String(), nullable=False)
|
||||
pid = db.Column(db.BigInteger(), nullable=False)
|
||||
img_hash = db.Column(db.String(), nullable=False)
|
||||
img_url = db.Column(db.String(), nullable=False)
|
||||
is_r18 = db.Column(db.Boolean(), nullable=False)
|
||||
tags = db.Column(db.String())
|
||||
|
||||
_idx1 = db.Index("setu_pid_img_url_idx1", "pid", "img_url", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_setu_data(
|
||||
cls,
|
||||
local_id: int,
|
||||
title: str,
|
||||
author: str,
|
||||
pid: int,
|
||||
img_hash: str,
|
||||
img_url: str,
|
||||
tags: str,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
添加一份色图数据
|
||||
参数:
|
||||
:param local_id: 本地存储id
|
||||
:param title: 标题
|
||||
:param author: 作者
|
||||
:param pid: 图片pid
|
||||
:param img_hash: 图片hash值
|
||||
:param img_url: 图片链接
|
||||
:param tags: 图片标签
|
||||
"""
|
||||
if not await cls._check_exists(pid, img_url):
|
||||
await cls.create(
|
||||
local_id=local_id,
|
||||
title=title,
|
||||
author=author,
|
||||
pid=pid,
|
||||
img_hash=img_hash,
|
||||
img_url=img_url,
|
||||
is_r18=True if "R-18" in tags else False,
|
||||
tags=tags,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def query_image(
|
||||
cls,
|
||||
local_id: Optional[int] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
r18: int = 0,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
通过tag查找色图
|
||||
参数:
|
||||
:param local_id: 本地色图 id
|
||||
:param tags: tags
|
||||
:param r18: 是否 r18,0:非r18 1:r18 2:混合
|
||||
"""
|
||||
if local_id:
|
||||
flag = True if r18 == 1 else False
|
||||
return await cls.query.where(
|
||||
(cls.local_id == local_id) & (cls.is_r18 == flag)
|
||||
).gino.first()
|
||||
if r18 == 0:
|
||||
query = await cls.query.where(cls.is_r18 == False).gino.all()
|
||||
elif r18 == 1:
|
||||
query = await cls.query.where(cls.is_r18 == True).gino.all()
|
||||
else:
|
||||
query = await cls.query.gino.all()
|
||||
if tags:
|
||||
query = [x for x in query if set(x.tags.split(",")) > set(tags)]
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
async def get_image_count(cls, r18: int = 0):
|
||||
"""
|
||||
说明:
|
||||
查询图片数量
|
||||
"""
|
||||
return len(await cls.query_image(r18=r18))
|
||||
# return await db.func.count(cls.local_id).gino.scalar()
|
||||
|
||||
@classmethod
|
||||
async def get_image_in_hash(cls, img_hash: str) -> "Setu":
|
||||
"""
|
||||
说明:
|
||||
通过图像hash获取图像信息
|
||||
参数:
|
||||
:param img_hash: = 图像hash值
|
||||
"""
|
||||
query = await cls.query.where(cls.img_hash == img_hash).gino.first()
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
async def _check_exists(cls, pid: int, img_url: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
检测图片是否存在
|
||||
参数:
|
||||
:param pid: 图片pid
|
||||
:param img_url: 图片链接
|
||||
"""
|
||||
return bool(
|
||||
await cls.query.where(
|
||||
(cls.pid == pid) & (cls.img_url == img_url)
|
||||
).gino.first()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def update_setu_data(
|
||||
cls,
|
||||
pid: int,
|
||||
*,
|
||||
local_id: Optional[int] = None,
|
||||
title: Optional[str] = None,
|
||||
author: Optional[str] = None,
|
||||
img_hash: Optional[str] = None,
|
||||
img_url: Optional[str] = None,
|
||||
tags: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
根据PID修改图片数据
|
||||
参数:
|
||||
:param local_id: 本地id
|
||||
:param pid: 图片pid
|
||||
:param title: 标题
|
||||
:param author: 作者
|
||||
:param img_hash: 图片hash值
|
||||
:param img_url: 图片链接
|
||||
:param tags: 图片标签
|
||||
"""
|
||||
query = cls.query.where(cls.pid == pid).with_for_update()
|
||||
image_list = await query.gino.all()
|
||||
if image_list:
|
||||
for image in image_list:
|
||||
if local_id:
|
||||
await image.update(local_id=local_id).apply()
|
||||
if title:
|
||||
await image.update(title=title).apply()
|
||||
if author:
|
||||
await image.update(author=author).apply()
|
||||
if img_hash:
|
||||
await image.update(img_hash=img_hash).apply()
|
||||
if img_url:
|
||||
await image.update(img_url=img_url).apply()
|
||||
if tags:
|
||||
await image.update(tags=tags).apply()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_all_setu(cls) -> List["Setu"]:
|
||||
"""
|
||||
说明:
|
||||
获取所有图片对象
|
||||
"""
|
||||
return await cls.query.gino.all()
|
||||
@@ -0,0 +1,62 @@
|
||||
from datetime import datetime
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class SignGroupUser(db.Model):
|
||||
__tablename__ = "sign_group_users"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
belonging_group = db.Column(db.BigInteger(), nullable=False)
|
||||
|
||||
checkin_count = db.Column(db.Integer(), nullable=False)
|
||||
checkin_time_last = db.Column(db.DateTime(timezone=True), nullable=False)
|
||||
impression = db.Column(db.Numeric(scale=3, asdecimal=False), nullable=False)
|
||||
add_probability = db.Column(
|
||||
db.Numeric(scale=3, asdecimal=False), nullable=False, default=0
|
||||
)
|
||||
specify_probability = db.Column(
|
||||
db.Numeric(scale=3, asdecimal=False), nullable=False, default=0
|
||||
)
|
||||
|
||||
_idx1 = db.Index("sign_group_users_idx1", "user_qq", "belonging_group", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def ensure(
|
||||
cls, user_qq: int, belonging_group: int, for_update: bool = False
|
||||
) -> "SignGroupUser":
|
||||
query = cls.query.where(
|
||||
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
|
||||
)
|
||||
if for_update:
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
return user or await cls.create(
|
||||
user_qq=user_qq,
|
||||
belonging_group=belonging_group,
|
||||
checkin_count=0,
|
||||
checkin_time_last=datetime.min, # 从未签到过
|
||||
impression=0,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_all_impression(cls, belonging_group: int) -> "list, list":
|
||||
"""
|
||||
说明:
|
||||
获取该群所有用户 id 及对应 好感度
|
||||
参数:
|
||||
:param belonging_group: 群号
|
||||
"""
|
||||
impression_list = []
|
||||
user_qq_list = []
|
||||
user_group = []
|
||||
if belonging_group:
|
||||
query = cls.query.where(cls.belonging_group == belonging_group)
|
||||
else:
|
||||
query = cls.query
|
||||
for user in await query.gino.all():
|
||||
impression_list.append(user.impression)
|
||||
user_qq_list.append(user.user_qq)
|
||||
user_group.append(user.belonging_group)
|
||||
return user_qq_list, impression_list, user_group
|
||||
@@ -0,0 +1,60 @@
|
||||
import random
|
||||
from nonebot import on_keyword
|
||||
import os
|
||||
from utils.message_builder import image
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from nonebot import on_command
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, MessageEvent
|
||||
from nonebot.adapters.cqhttp.permission import GROUP
|
||||
from utils.utils import FreqLimiter
|
||||
from configs.config import NICKNAME
|
||||
|
||||
|
||||
__plugin_name__ = "基本设置 [Hidden]"
|
||||
__plugin_usage__ = "用法: 无"
|
||||
|
||||
|
||||
_flmt = FreqLimiter(300)
|
||||
|
||||
|
||||
config_playgame = on_keyword({"打游戏"}, permission=GROUP, priority=1, block=True)
|
||||
|
||||
|
||||
@config_playgame.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
if not _flmt.check(event.group_id):
|
||||
return
|
||||
_flmt.start_cd(event.group_id)
|
||||
await config_playgame.finish(
|
||||
image(random.choice(os.listdir(IMAGE_PATH + "dayouxi/")), "dayouxi")
|
||||
)
|
||||
|
||||
|
||||
self_introduction = on_command(
|
||||
"自我介绍", aliases={"介绍", "你是谁", "你叫什么"}, rule=to_me(), priority=5, block=True
|
||||
)
|
||||
|
||||
|
||||
@self_introduction.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
if NICKNAME.find('真寻') != -1:
|
||||
result = (
|
||||
"我叫绪山真寻\n"
|
||||
"你们可以叫我真寻,小真寻,哪怕你们叫我小寻子我也能接受!\n"
|
||||
"年龄的话我还是个**岁初中生(至少现在是)\n"
|
||||
"身高保密!!!(也就比美波里(姐姐..(妹妹))矮一点)\n"
|
||||
"我生日是在3月6号, 能记住的话我会很高兴的\n现在是自宅警备系的现役JC\n"
|
||||
"最好的朋友是椛!\n" + image("zhenxun")
|
||||
)
|
||||
await self_introduction.finish(result)
|
||||
|
||||
|
||||
my_wife = on_keyword({"老婆"}, rule=to_me(), priority=5, block=True)
|
||||
|
||||
|
||||
@my_wife.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await my_wife.finish(image("laopo.jpg", "other"))
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
from nonebot import on_command
|
||||
from utils.utils import get_message_text, get_message_imgs
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
|
||||
from .data_source import custom_group_welcome
|
||||
from nonebot.adapters.cqhttp.permission import GROUP
|
||||
from services.log import logger
|
||||
|
||||
|
||||
__plugin_name__ = "自定义进群欢迎消息"
|
||||
|
||||
__plugin_usage__ = """
|
||||
自定义进群欢迎消息 [消息] [图片](可省略)
|
||||
示例:自定义进群欢迎消息 欢迎新人![图片]
|
||||
"""
|
||||
|
||||
|
||||
custom_welcome = on_command(
|
||||
"自定义进群欢迎消息",
|
||||
aliases={"自定义欢迎消息", "自定义群欢迎消息", "设置群欢迎消息"},
|
||||
permission=GROUP,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@custom_welcome.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
msg = get_message_text(event.json())
|
||||
imgs = get_message_imgs(event.json())
|
||||
if not msg and not imgs:
|
||||
await custom_welcome.finish(__plugin_usage__)
|
||||
await custom_welcome.send(
|
||||
await custom_group_welcome(msg, imgs, event.user_id, event.group_id),
|
||||
at_sender=True,
|
||||
)
|
||||
logger.info(f"USER {event.user_id} GROUP {event.group_id} 自定义群欢迎消息:{msg}")
|
||||
@@ -0,0 +1,269 @@
|
||||
from models.group_remind import GroupRemind
|
||||
from services.log import logger
|
||||
from configs.path_config import DATA_PATH
|
||||
import os
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
from utils.message_builder import image
|
||||
from utils.utils import get_local_proxy, get_bot
|
||||
from pathlib import Path
|
||||
from configs.config import plugins2info_dict
|
||||
from models.group_member_info import GroupInfoUser
|
||||
import time
|
||||
from datetime import datetime
|
||||
from services.db_context import db
|
||||
from models.level_user import LevelUser
|
||||
from configs.config import ADMIN_DEFAULT_AUTH
|
||||
from utils.static_data import group_manager
|
||||
from utils.image_utils import CreateImg
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
command_dict = {
|
||||
"早晚安": "zwa",
|
||||
"进群欢迎": "hy",
|
||||
"每日开箱重置提醒": "kxcz",
|
||||
"b站转发解析": "blpar",
|
||||
"epic": "epic",
|
||||
"丢人爬": "pa",
|
||||
"原神黄历提醒": "almanac",
|
||||
}
|
||||
command_list = list(command_dict.values())
|
||||
command_info_dt = {
|
||||
"早晚安": "将会在每晚11:59晚安,在6:01早安哦",
|
||||
"每日开箱重置提醒": "将会在每日00:01提示开箱重置!",
|
||||
"epic": "将会在每日中午12:01发送可白嫖的epic游戏",
|
||||
"原神黄历提醒": "将会在每日8:00发送当日的原神黄历",
|
||||
}
|
||||
|
||||
|
||||
async def remind_status(group: int, name: str, flag: bool) -> str:
|
||||
_name = ""
|
||||
if name in command_dict.values():
|
||||
_name = list(command_dict.keys())[list(command_dict.values()).index(name)]
|
||||
if flag:
|
||||
rst = "开启"
|
||||
if await GroupRemind.get_status(group, name):
|
||||
return f"该群已经{rst}过 {_name},请勿重复开启!"
|
||||
else:
|
||||
rst = "关闭"
|
||||
if not await GroupRemind.get_status(group, name):
|
||||
return f"该群已经{rst}过 {_name},请勿重复关闭!"
|
||||
if await GroupRemind.set_status(group, name, flag):
|
||||
info = command_info_dt[_name] if command_info_dt.get(_name) else ""
|
||||
if info:
|
||||
info = "\n" + info
|
||||
return f"成功{rst} {_name}!0v0 {info}"
|
||||
else:
|
||||
return f"{rst} {_name} 失败了..."
|
||||
|
||||
|
||||
async def set_group_status(name: str, group_id: int):
|
||||
flag = None
|
||||
if name[:2] == "开启":
|
||||
flag = True
|
||||
elif name[:2] == "关闭":
|
||||
flag = False
|
||||
cmd = name[2:]
|
||||
if cmd in ["全部通知", "所有通知"]:
|
||||
for command in command_list:
|
||||
await remind_status(group_id, command, flag)
|
||||
return f"已{name[:2]}所有通知!"
|
||||
return await remind_status(group_id, command_dict[cmd], flag)
|
||||
|
||||
|
||||
async def group_current_status(group_id: int):
|
||||
result = (
|
||||
f'(被动技能)\n早晚安通知:{"√" if await GroupRemind.get_status(group_id, "zwa") else "×"}\n'
|
||||
f'进群欢迎:{"√" if await GroupRemind.get_status(group_id, "hy") else "×"}\n'
|
||||
f'每日开箱重置通知:{"√" if await GroupRemind.get_status(group_id, "kxcz") else "×"}\n'
|
||||
f'b站转发解析:{"√" if await GroupRemind.get_status(group_id, "blpar") else "×"}\n'
|
||||
f'丢人爬:{"√" if await GroupRemind.get_status(group_id, "pa") else "×"}\n'
|
||||
f'epic免费游戏:{"√" if await GroupRemind.get_status(group_id, "epic") else "×"}\n'
|
||||
f'原神黄历提醒:{"√" if await GroupRemind.get_status(group_id, "almanac") else "×"}'
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
custom_welcome_msg_json = (
|
||||
Path() / "data" / "custom_welcome_msg" / "custom_welcome_msg.json"
|
||||
)
|
||||
|
||||
|
||||
async def custom_group_welcome(msg, imgs, user_id, group_id):
|
||||
img_result = ""
|
||||
img = imgs[0] if imgs else ""
|
||||
result = ""
|
||||
if os.path.exists(DATA_PATH + f"custom_welcome_msg/{group_id}.jpg"):
|
||||
os.remove(DATA_PATH + f"custom_welcome_msg/{group_id}.jpg")
|
||||
# print(custom_welcome_msg_json)
|
||||
if not custom_welcome_msg_json.exists():
|
||||
custom_welcome_msg_json.parent.mkdir(parents=True, exist_ok=True)
|
||||
data = {}
|
||||
else:
|
||||
try:
|
||||
data = json.load(open(custom_welcome_msg_json, "r"))
|
||||
except FileNotFoundError:
|
||||
data = {}
|
||||
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:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(img, proxy=get_local_proxy()) as response:
|
||||
async with aiofiles.open(
|
||||
DATA_PATH + f"custom_welcome_msg/{group_id}.jpg", "wb"
|
||||
) as f:
|
||||
await f.write(await response.read())
|
||||
img_result = image(abspath=DATA_PATH + f"custom_welcome_msg/{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
|
||||
|
||||
|
||||
def change_group_switch(cmd: str, group_id: int, is_super: bool = False):
|
||||
group_help_file = Path(DATA_PATH) / "group_help" / f"{group_id}.png"
|
||||
group_id = str(group_id)
|
||||
status = cmd[:2]
|
||||
cmd = cmd[2:]
|
||||
for plugin_cmd in plugins2info_dict.keys():
|
||||
if cmd in plugins2info_dict[plugin_cmd]["cmd"]:
|
||||
if is_super:
|
||||
plugin_cmd = f'{plugin_cmd}:super'
|
||||
if status == "开启":
|
||||
if group_manager.get_plugin_status(plugin_cmd, group_id):
|
||||
return f"功能 {cmd} 正处于开启状态!不要重复开启."
|
||||
group_manager.unblock_plugin(plugin_cmd, group_id)
|
||||
else:
|
||||
if not group_manager.get_plugin_status(plugin_cmd, group_id):
|
||||
return f"功能 {cmd} 正处于关闭状态!不要重复关闭."
|
||||
group_manager.block_plugin(plugin_cmd, group_id)
|
||||
if group_help_file.exists():
|
||||
group_help_file.unlink()
|
||||
return f"{status} {cmd} 功能!"
|
||||
return f"没有找到 {cmd} 功能..."
|
||||
|
||||
|
||||
def set_plugin_status(cmd: str, block_type: str = "all"):
|
||||
status = cmd[:2]
|
||||
cmd = cmd[2:]
|
||||
for plugin_cmd in plugins2info_dict.keys():
|
||||
if cmd in plugins2info_dict[plugin_cmd]["cmd"]:
|
||||
if status == "开启":
|
||||
group_manager.unblock_plugin(plugin_cmd)
|
||||
else:
|
||||
group_manager.block_plugin(plugin_cmd, block_type=block_type)
|
||||
break
|
||||
|
||||
|
||||
async def get_plugin_status():
|
||||
return await asyncio.get_event_loop().run_in_executor(None, _get_plugin_status)
|
||||
|
||||
|
||||
def _get_plugin_status():
|
||||
rst = '\t功能\n'
|
||||
flag_str = '状态'.rjust(4) + '\n'
|
||||
for plugin_cmd in plugins2info_dict:
|
||||
flag = group_manager.get_plugin_block_type(plugin_cmd)
|
||||
flag = flag.upper() + ' CLOSE' if flag else 'OPEN'
|
||||
rst += f'{plugins2info_dict[plugin_cmd]["cmd"][0]}\n'
|
||||
flag_str += f'{flag}\n'
|
||||
height = len(rst.split('\n')) * 24
|
||||
a = CreateImg(150, height, font_size=20)
|
||||
a.text((10, 10), rst)
|
||||
b = CreateImg(200, height, font_size=20)
|
||||
b.text((10, 10), flag_str)
|
||||
A = CreateImg(380, height)
|
||||
A.paste(a)
|
||||
A.paste(b, (150, 0))
|
||||
return image(b64=A.pic2bs4())
|
||||
|
||||
|
||||
async def update_member_info(group_id: int) -> bool:
|
||||
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:
|
||||
if user_info["card"] == "":
|
||||
nickname = user_info["nickname"]
|
||||
else:
|
||||
nickname = user_info["card"]
|
||||
async with db.transaction():
|
||||
# 更新权限
|
||||
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"], 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_member_info(
|
||||
user_info["user_id"], user_info["group_id"]
|
||||
)
|
||||
if user:
|
||||
if user.user_name != nickname:
|
||||
await user.update(user_name=nickname).apply()
|
||||
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",
|
||||
)
|
||||
if await GroupInfoUser.add_member_info(
|
||||
user_info["user_id"],
|
||||
user_info["group_id"],
|
||||
nickname,
|
||||
join_time,
|
||||
):
|
||||
_exist_member_list.append(int(user_info["user_id"]))
|
||||
logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功")
|
||||
else:
|
||||
_error_member_list.append(
|
||||
f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新失败\n"
|
||||
)
|
||||
_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:
|
||||
if await GroupInfoUser.delete_member_info(del_user, group_id):
|
||||
logger.info(f"退群用户{del_user} 所属{group_id} 已删除")
|
||||
else:
|
||||
logger.info(f"退群用户{del_user} 所属{group_id} 删除失败")
|
||||
if _error_member_list:
|
||||
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
|
||||
@@ -0,0 +1,57 @@
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
|
||||
from .data_source import set_group_status, group_current_status
|
||||
from nonebot.adapters.cqhttp.permission import GROUP
|
||||
from services.log import logger
|
||||
from nonebot import on_command
|
||||
|
||||
|
||||
__plugin_name__ = "群通知开关"
|
||||
|
||||
__plugin_usage__ = """
|
||||
示例:
|
||||
开启早晚安
|
||||
关闭早晚安
|
||||
"""
|
||||
|
||||
|
||||
group_status = on_command(
|
||||
"oc_reminds",
|
||||
aliases={
|
||||
"开启早晚安",
|
||||
"关闭早晚安",
|
||||
"开启进群欢迎",
|
||||
"关闭进群欢迎",
|
||||
"开启每日开箱重置提醒",
|
||||
"关闭每日开箱重置提醒",
|
||||
"开启b站转发解析",
|
||||
"关闭b站转发解析",
|
||||
"开启epic通知",
|
||||
"关闭epic通知",
|
||||
"开启丢人爬",
|
||||
"关闭丢人爬",
|
||||
"开启原神黄历提醒",
|
||||
"关闭原神黄历提醒",
|
||||
"开启全部通知",
|
||||
"开启所有通知",
|
||||
"关闭全部通知",
|
||||
"关闭所有通知",
|
||||
"群通知状态",
|
||||
},
|
||||
permission=GROUP,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@group_status.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
if state["_prefix"]["raw_command"] in ["群通知状态"]:
|
||||
await group_status.finish(await group_current_status(event.group_id))
|
||||
await group_status.send(
|
||||
await set_group_status(state["_prefix"]["raw_command"], event.group_id),
|
||||
at_sender=True,
|
||||
)
|
||||
logger.info(
|
||||
f'USER {event.user_id} GROUP {event.group_id} 使用群通知管理命令 {state["_prefix"]["raw_command"]}'
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, MessageEvent
|
||||
from .data_source import change_group_switch, set_plugin_status, get_plugin_status
|
||||
from services.log import logger
|
||||
from configs.config import plugins2info_dict, NICKNAME
|
||||
from utils.utils import get_message_text, is_number
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
|
||||
__plugin_name__ = "群功能开关"
|
||||
|
||||
__plugin_usage__ = """
|
||||
示例:
|
||||
开启色图
|
||||
关闭色图
|
||||
"""
|
||||
|
||||
|
||||
cmds = []
|
||||
for cmd_list in plugins2info_dict.values():
|
||||
for cmd in cmd_list["cmd"]:
|
||||
cmds.append(f"开启{cmd}")
|
||||
cmds.append(f"关闭{cmd}")
|
||||
cmds = set(cmds)
|
||||
|
||||
|
||||
switch_rule = on_command(
|
||||
"switch_rule", aliases=cmds, priority=5, block=True
|
||||
)
|
||||
|
||||
plugins_status = on_command('功能状态', permission=SUPERUSER, priority=5, block=True)
|
||||
|
||||
|
||||
@switch_rule.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await switch_rule.send(
|
||||
change_group_switch(
|
||||
state["_prefix"]["raw_command"].strip(), event.group_id
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
f'USER {event.user_id} GROUP {event.group_id} 使用群功能管理命令 {state["_prefix"]["raw_command"]}'
|
||||
)
|
||||
else:
|
||||
if str(event.user_id) in bot.config.superusers:
|
||||
block_type = get_message_text(event.json())
|
||||
_cmd = state["_prefix"]["raw_command"].strip()
|
||||
if is_number(block_type):
|
||||
if not int(block_type) in [g["group_id"] for g in await bot.get_group_list(self_id=int(bot.self_id))]:
|
||||
await switch_rule.finish(f'{NICKNAME}未加入群聊:{block_type}')
|
||||
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.send(f'已禁用群聊 {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.send(f'已{_cmd[:2]}功能:{_cmd[2:]}')
|
||||
elif block_type == 'private':
|
||||
await switch_rule.send(f'已在私聊中{_cmd[:2]}功能:{_cmd[2:]}')
|
||||
else:
|
||||
await switch_rule.send(f'已在群聊中{_cmd[:2]}功能:{_cmd[2:]}')
|
||||
else:
|
||||
await switch_rule.finish('格式错误:关闭[功能] [group]/[p/g]')
|
||||
logger.info(
|
||||
f'USER {event.user_id} 使用功能管理命令 {state["_prefix"]["raw_command"]} | {block_type}'
|
||||
)
|
||||
|
||||
|
||||
@plugins_status.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await plugins_status.send(await get_plugin_status())
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from utils.utils import scheduler, get_bot
|
||||
from .data_source import update_member_info
|
||||
from services.log import logger
|
||||
from models.group_info import GroupInfo
|
||||
from asyncpg.exceptions import ConnectionDoesNotExistError
|
||||
|
||||
|
||||
# 自动更新群员信息
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=2,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
bot = get_bot()
|
||||
gl = await bot.get_group_list(self_id=bot.self_id)
|
||||
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()
|
||||
gl = await bot.get_group_list(self_id=bot.self_id)
|
||||
gl = [g["group_id"] for g in gl]
|
||||
all_group = [x.group_id for x in await GroupInfo.get_all_group()]
|
||||
for g in gl:
|
||||
if g not in all_group:
|
||||
await update_member_info(g)
|
||||
logger.info(f"快速更新群信息以及权限:{g}")
|
||||
except (IndexError, ConnectionDoesNotExistError):
|
||||
pass
|
||||
@@ -0,0 +1,28 @@
|
||||
from nonebot import on_command, export
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, GROUP
|
||||
from .data_source import update_member_info
|
||||
|
||||
__plugin_name__ = "更新群组成员列表"
|
||||
|
||||
__plugin_usage__ = """
|
||||
说明:
|
||||
更新群组成员的基本信息
|
||||
示例:
|
||||
更新群组成员列表
|
||||
"""
|
||||
|
||||
export = export()
|
||||
export.update_member_info = update_member_info
|
||||
|
||||
refresh_member_group = on_command(
|
||||
"更新群组成员列表", aliases={"更新群组成员信息"}, permission=GROUP, priority=5, block=True
|
||||
)
|
||||
|
||||
|
||||
@refresh_member_group.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
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)
|
||||
@@ -0,0 +1,31 @@
|
||||
from nonebot import on_notice
|
||||
from services.log import logger
|
||||
from nonebot.adapters.cqhttp import Bot, GroupAdminNoticeEvent
|
||||
from nonebot.typing import T_State
|
||||
from models.level_user import LevelUser
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from configs.config import ADMIN_DEFAULT_AUTH
|
||||
|
||||
|
||||
__plugin_name__ = "群管理员监测"
|
||||
|
||||
__plugin_usage__ = "无"
|
||||
|
||||
|
||||
admin_notice = on_notice(priority=5)
|
||||
|
||||
|
||||
@admin_notice.handle()
|
||||
async def _(bot: Bot, event: GroupAdminNoticeEvent, state: T_State):
|
||||
try:
|
||||
nickname = (
|
||||
await GroupInfoUser.get_member_info(event.user_id, event.group_id)
|
||||
).user_name
|
||||
except AttributeError:
|
||||
nickname = event.user_id
|
||||
if event.sub_type == "set":
|
||||
await LevelUser.set_level(event.user_id, event.group_id, ADMIN_DEFAULT_AUTH)
|
||||
logger.info(f"为新晋管理员 {nickname}({event.user_id}) 添加权限等级:{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}) 取消权限等级")
|
||||
@@ -0,0 +1,50 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.cqhttp import GroupMessageEvent
|
||||
from utils.image_utils import CreateImg
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.message_builder import image
|
||||
from configs.config import NICKNAME
|
||||
|
||||
|
||||
__plugin_name__ = '管理帮助 [Hidden]'
|
||||
__plugin_usage__ = f'''管理帮助(权限等级):
|
||||
1.更新群组成员列表(1)
|
||||
2.功能开关 --> 指令:开启/关闭xx功能(2)
|
||||
3.查看群被动技能 --> 指令:群通知状态(2)
|
||||
4.自定义群欢迎 --> 指令:自定义进群欢迎消息(2)
|
||||
5.将用户拉入{NICKNAME}黑名单 --> .ban/.unban(5)
|
||||
6.刷屏禁言相关 --> 指令:刷屏检测设置/设置检测时间
|
||||
\t\t/设置检测次数/设置禁言时长(5)
|
||||
8.上传图片/连续上传图片(6)
|
||||
9.移动图片(7)
|
||||
10.删除图片(7)
|
||||
对我说 “{NICKNAME}帮助 指令” 获取对应详细帮助
|
||||
群主与管理员默认 5 级权限
|
||||
'''
|
||||
|
||||
passive_help = '''【被动技能开关(2):
|
||||
开启/关闭早晚安
|
||||
开启/关闭进群欢迎
|
||||
开启/关闭每日开箱重置提醒
|
||||
开启/关闭b站转发解析
|
||||
开启/关闭丢人爬
|
||||
开启/关闭epic通知
|
||||
开启/关闭原神黄历提醒
|
||||
开启/关闭全部通知】
|
||||
'''
|
||||
|
||||
admin_help = on_command("管理员帮助", aliases={"管理帮助"}, priority=5, block=True)
|
||||
|
||||
admin_help_img = CreateImg(1000, 600, font_size=24)
|
||||
admin_help_img.text((10, 10), __plugin_usage__)
|
||||
text_img = CreateImg(450, 600, font_size=24)
|
||||
text_img.text((0, 0), passive_help)
|
||||
admin_help_img.paste(text_img, (650, 50))
|
||||
admin_help_img.save(IMAGE_PATH + 'admin_help_img.png')
|
||||
|
||||
|
||||
@admin_help.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
await admin_help.send(image('admin_help_img.png'))
|
||||
@@ -0,0 +1,60 @@
|
||||
from .data_source import get_chat_result, hello, no_result
|
||||
from services.log import logger
|
||||
from nonebot import on_message
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import (
|
||||
Bot,
|
||||
PrivateMessageEvent,
|
||||
Message,
|
||||
MessageEvent,
|
||||
)
|
||||
from utils.utils import get_message_text, get_message_imgs
|
||||
from models.friend_user import FriendUser
|
||||
from models.group_member_info import GroupInfoUser
|
||||
|
||||
__plugin_name__ = "AI [Hidden]"
|
||||
|
||||
|
||||
ai = on_message(rule=to_me(), priority=8)
|
||||
|
||||
|
||||
@ai.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = get_message_text(event.json())
|
||||
imgs = get_message_imgs(event.json())
|
||||
if "CQ:xml" in msg:
|
||||
return
|
||||
# 打招呼
|
||||
if (not msg and not imgs) or msg in [
|
||||
"你好啊",
|
||||
"你好",
|
||||
"在吗",
|
||||
"在不在",
|
||||
"您好",
|
||||
"您好啊",
|
||||
"你好",
|
||||
"在",
|
||||
]:
|
||||
await ai.finish(hello())
|
||||
img = imgs[0] if imgs else ""
|
||||
if isinstance(event, PrivateMessageEvent):
|
||||
nickname = await FriendUser.get_friend_nickname(event.user_id)
|
||||
else:
|
||||
nickname = await GroupInfoUser.get_group_member_nickname(
|
||||
event.user_id, event.group_id
|
||||
)
|
||||
if not nickname:
|
||||
if isinstance(event, PrivateMessageEvent):
|
||||
nickname = event.sender.nickname
|
||||
else:
|
||||
nickname = event.sender.card if event.sender.card else event.sender.nickname
|
||||
result = await get_chat_result(msg, img, event.user_id, nickname)
|
||||
logger.info(
|
||||
f"USER {event.user_id} GROUP {event.group_id if not isinstance(event, PrivateMessageEvent) else ''} "
|
||||
f"问题:{msg}\n回答:{result}"
|
||||
)
|
||||
if result:
|
||||
await ai.finish(Message(result))
|
||||
else:
|
||||
await ai.finish(no_result())
|
||||
@@ -0,0 +1,176 @@
|
||||
from configs.config import TL_KEY, ALAPI_TOKEN, ALAPI_AI_CHECK, NICKNAME
|
||||
from configs.path_config import IMAGE_PATH, DATA_PATH
|
||||
from aiohttp.client import ClientSession
|
||||
from services.log import logger
|
||||
from utils.message_builder import image, face
|
||||
from utils.utils import get_bot
|
||||
import re
|
||||
import aiohttp
|
||||
import random
|
||||
import os
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
url = "http://openapi.tuling123.com/openapi/api/v2"
|
||||
|
||||
check_url = "https://v2.alapi.cn/api/censor/text"
|
||||
|
||||
index = 0
|
||||
|
||||
anime_data = json.load(open(DATA_PATH + "anime.json", "r", encoding="utf8"))
|
||||
|
||||
|
||||
# 图灵AI
|
||||
async def get_chat_result(text: str, img_url: str, user_id: int, nickname: str) -> str:
|
||||
global index
|
||||
if index == 5:
|
||||
index = 0
|
||||
if len(text) < 6 and random.random() < 0.6:
|
||||
keys = anime_data.keys()
|
||||
for key in keys:
|
||||
if text.find(key) != -1:
|
||||
return random.choice(anime_data[key]).replace("你", nickname)
|
||||
async with aiohttp.ClientSession() as sess:
|
||||
rst = await tu_ling(text, img_url, user_id, sess)
|
||||
if not rst:
|
||||
rst = await xie_ai(text, sess)
|
||||
if not rst:
|
||||
return no_result()
|
||||
if nickname:
|
||||
if len(nickname) < 5:
|
||||
if random.random() < 0.5:
|
||||
nickname = "~".join(nickname) + "~"
|
||||
if random.random() < 0.2:
|
||||
if nickname.find("大人") == -1:
|
||||
nickname += "大~人~"
|
||||
rst = rst.replace("小主人", nickname).replace("小朋友", nickname)
|
||||
return rst
|
||||
|
||||
|
||||
# 图灵接口
|
||||
async def tu_ling(text: str, img_url: str, user_id: int, sess: ClientSession):
|
||||
global index
|
||||
try:
|
||||
if text:
|
||||
req = {
|
||||
"perception": {
|
||||
"inputText": {"text": text},
|
||||
"selfInfo": {
|
||||
"location": {"city": "陨石坑", "province": "火星", "street": "第5坑位"}
|
||||
},
|
||||
},
|
||||
"userInfo": {"apiKey": TL_KEY[index], "userId": str(user_id)},
|
||||
}
|
||||
elif img_url:
|
||||
req = {
|
||||
"reqType": 1,
|
||||
"perception": {
|
||||
"inputImage": {"url": img_url},
|
||||
"selfInfo": {
|
||||
"location": {"city": "陨石坑", "province": "火星", "street": "第5坑位"}
|
||||
},
|
||||
},
|
||||
"userInfo": {"apiKey": TL_KEY[index], "userId": str(user_id)},
|
||||
}
|
||||
except IndexError:
|
||||
index = 0
|
||||
return ""
|
||||
text = ""
|
||||
async with sess.post(url, json=req) as response:
|
||||
if response.status != 200:
|
||||
return no_result()
|
||||
resp_payload = json.loads(await response.text())
|
||||
if int(resp_payload["intent"]["code"]) in [4003]:
|
||||
return ""
|
||||
if resp_payload["results"]:
|
||||
for result in resp_payload["results"]:
|
||||
if result["resultType"] == "text":
|
||||
text = result["values"]["text"]
|
||||
if "请求次数超过" in text:
|
||||
text = ""
|
||||
return text
|
||||
|
||||
|
||||
# 屑 AI
|
||||
async def xie_ai(text: str, sess: ClientSession):
|
||||
async with sess.get(
|
||||
f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}"
|
||||
) as res:
|
||||
content = ""
|
||||
data = json.loads(await res.text())
|
||||
if data["result"] == 0:
|
||||
content = data["content"]
|
||||
if "菲菲" in content:
|
||||
content = content.replace("菲菲", f"{NICKNAME}")
|
||||
if "公众号" in content:
|
||||
content = ""
|
||||
if "{br}" in content:
|
||||
content = content.replace("{br}", "\n")
|
||||
if "提示" in content:
|
||||
content = content[: content.find("提示")]
|
||||
if "淘宝" in content:
|
||||
return ""
|
||||
while True:
|
||||
r = re.search("{face:(.*)}", content)
|
||||
if r:
|
||||
id_ = r.group(1)
|
||||
content = content.replace(
|
||||
"{" + f"face:{id_}" + "}", str(face(int(id_)))
|
||||
)
|
||||
else:
|
||||
break
|
||||
return content if not content and not ALAPI_AI_CHECK else await check_text(content, sess)
|
||||
|
||||
|
||||
# 打招呼内容
|
||||
def hello() -> str:
|
||||
result = random.choice(
|
||||
(
|
||||
"哦豁?!",
|
||||
"你好!Ov<",
|
||||
f"库库库,呼唤{list(get_bot().config.nickname)[0]}做什么呢",
|
||||
"我在呢!",
|
||||
"呼呼,叫俺干嘛",
|
||||
)
|
||||
)
|
||||
img = random.choice(os.listdir(IMAGE_PATH + "zai/"))
|
||||
if img[-4:] == ".gif":
|
||||
result += image(img, "zai")
|
||||
else:
|
||||
result += image(img, "zai")
|
||||
return result
|
||||
|
||||
|
||||
# 没有回答时回复内容
|
||||
def no_result() -> str:
|
||||
return (
|
||||
random.choice(
|
||||
[
|
||||
"你在说啥子?",
|
||||
f"纯洁的{NICKNAME}没听懂",
|
||||
"下次再告诉你(下次一定)",
|
||||
"你觉得我听懂了吗?嗯?",
|
||||
"我!不!知!道!",
|
||||
]
|
||||
)
|
||||
+ image(random.choice(os.listdir(IMAGE_PATH + "noresult/")), "noresult")
|
||||
)
|
||||
|
||||
|
||||
# 检测屑AI回复的文本是否是 *话
|
||||
async def check_text(text: str, sess: ClientSession) -> str:
|
||||
if not ALAPI_TOKEN:
|
||||
return text
|
||||
params = {"token": ALAPI_TOKEN, "text": text}
|
||||
try:
|
||||
async with sess.get(check_url, timeout=2, params=params) as response:
|
||||
data = await response.json()
|
||||
if data["code"] == 200:
|
||||
if data["conclusion_type"] == 2:
|
||||
return ''
|
||||
except Exception as e:
|
||||
logger.error(f"检测违规文本错误...e:{e}")
|
||||
return text
|
||||
@@ -0,0 +1,36 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
|
||||
from nonebot.typing import T_State
|
||||
from configs.config import ALAPI_TOKEN
|
||||
from .data_source import get_data
|
||||
from services.log import logger
|
||||
|
||||
__plugin_name__ = '网易云热评'
|
||||
__plugin_usage__ = '用法: 生了个人,我很抱歉'
|
||||
|
||||
comments_163 = on_command("网易云热评", aliases={'网易云评论', '到点了', '12点了'}, priority=5, block=True)
|
||||
|
||||
|
||||
comments_163_url = 'https://v2.alapi.cn/api/comment'
|
||||
|
||||
|
||||
@comments_163.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
params = {
|
||||
'token': ALAPI_TOKEN
|
||||
}
|
||||
data, code = await get_data(comments_163_url, params)
|
||||
if code != 200:
|
||||
await comments_163.finish(data, at_sender=True)
|
||||
data = data['data']
|
||||
comment = data['comment_content']
|
||||
song_name = data['title']
|
||||
await comments_163.send(f'{comment}\n\t——《{song_name}》')
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 发送网易云热评: {comment} \n\t\t————{song_name}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message, GroupMessageEvent
|
||||
from nonebot.typing import T_State
|
||||
from configs.config import ALAPI_TOKEN
|
||||
from utils.message_builder import image
|
||||
from utils.utils import get_message_text
|
||||
from .data_source import get_data
|
||||
from services.log import logger
|
||||
|
||||
__plugin_name__ = 'b封面'
|
||||
__plugin_usage__ = '用法: b封面 (链接,av,bv,cv,直播id)\n\t' \
|
||||
'示例:b封面 av86863038'
|
||||
|
||||
|
||||
cover = on_command('b封面', aliases={'B封面'}, priority=5, block=True)
|
||||
|
||||
|
||||
cover_url = 'https://v2.alapi.cn/api/bilibili/cover'
|
||||
|
||||
|
||||
@cover.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = get_message_text(event.json())
|
||||
params = {
|
||||
'token': ALAPI_TOKEN,
|
||||
'c': msg
|
||||
}
|
||||
data, code = await get_data(cover_url, params)
|
||||
if code != 200:
|
||||
await cover.finish(data, at_sender=True)
|
||||
data = data['data']
|
||||
title = data['title']
|
||||
img = data['cover']
|
||||
await cover.send(Message(f'title:{title}\n{image(img)}'))
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 获取b站封面: {title} url:{img}")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from nonebot.adapters.cqhttp import MessageSegment
|
||||
from utils.image_utils import CreateImg
|
||||
from utils.message_builder import image
|
||||
from configs.path_config import IMAGE_PATH
|
||||
import aiohttp
|
||||
|
||||
|
||||
async def get_data(url: str, params: dict):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.get(
|
||||
url, timeout=2, params=params
|
||||
) as response:
|
||||
data = await response.json()
|
||||
if data["code"] == 200:
|
||||
if not data["data"]:
|
||||
return "没有搜索到...", 997
|
||||
return data, 200
|
||||
else:
|
||||
return f'发生了错误...code:{data["code"]}', 999
|
||||
except TimeoutError:
|
||||
return "超时了....", 998
|
||||
|
||||
|
||||
def gen_wbtop_pic(data) -> MessageSegment:
|
||||
bk = CreateImg(700, 32 * 50 + 280, 700, 32, color='#797979')
|
||||
wbtop_bk = CreateImg(700, 280, background=f'{IMAGE_PATH}/other/webtop.png')
|
||||
bk.paste(wbtop_bk)
|
||||
text_bk = CreateImg(700, 32 * 50, 700, 32, color='#797979')
|
||||
for i, data in enumerate(data):
|
||||
title = f"{i+1}. {data['hot_word']}"
|
||||
hot = data['hot_word_num']
|
||||
img = CreateImg(700, 30, font_size=20)
|
||||
w, h = img.getsize(title)
|
||||
img.text((10, int((30 - h) / 2)), title)
|
||||
img.text((580, int((30 - h) / 2)), hot)
|
||||
text_bk.paste(img)
|
||||
bk.paste(text_bk, (0, 280))
|
||||
return image(b64=bk.pic2bs4())
|
||||
@@ -0,0 +1,36 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
|
||||
from nonebot.typing import T_State
|
||||
from configs.config import ALAPI_TOKEN
|
||||
from services.log import logger
|
||||
from .data_source import get_data
|
||||
|
||||
__plugin_name__ = '古诗'
|
||||
__plugin_usage__ = '用法: 无'
|
||||
|
||||
|
||||
poetry = on_command("念诗", aliases={'来首诗', '念首诗'}, priority=5, block=True)
|
||||
|
||||
|
||||
poetry_url = 'https://v2.alapi.cn/api/shici'
|
||||
|
||||
|
||||
@poetry.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
params = {
|
||||
'token': ALAPI_TOKEN
|
||||
}
|
||||
data, code = await get_data(poetry_url, params)
|
||||
if code != 200:
|
||||
await poetry.finish(data, at_sender=True)
|
||||
data = data['data']
|
||||
content = data['content']
|
||||
title = data['origin']
|
||||
author = data['author']
|
||||
await poetry.send(f'{content}\n\t——{author}《{title}》')
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 发送古诗: f'{content}\n\t--{author}《{title}》'")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
|
||||
from nonebot.typing import T_State
|
||||
from configs.config import ALAPI_TOKEN
|
||||
from services.log import logger
|
||||
from .data_source import get_data, gen_wbtop_pic
|
||||
from utils.browser import get_browser
|
||||
from utils.utils import get_message_text, is_number
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.message_builder import image
|
||||
import asyncio
|
||||
|
||||
__plugin_name__ = '微博热搜'
|
||||
__plugin_usage__ = '用法: 无'
|
||||
|
||||
|
||||
wbtop = on_command("wbtop", aliases={'微博热搜'}, priority=5, block=True)
|
||||
|
||||
|
||||
wbtop_url = 'https://v2.alapi.cn/api/new/wbtop'
|
||||
|
||||
wbtop_data = []
|
||||
|
||||
|
||||
@wbtop.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
global wbtop_data
|
||||
msg = get_message_text(event.json())
|
||||
if not wbtop_data or not msg:
|
||||
params = {
|
||||
'token': ALAPI_TOKEN
|
||||
}
|
||||
data, code = await get_data(wbtop_url, params)
|
||||
if code != 200:
|
||||
await wbtop.finish(data, at_sender=True)
|
||||
wbtop_data = data['data']
|
||||
img = await asyncio.get_event_loop().run_in_executor(None, gen_wbtop_pic, wbtop_data)
|
||||
await wbtop.send(img)
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 查询微博热搜")
|
||||
if is_number(msg) and 0 < int(msg) <= 50:
|
||||
url = wbtop_data[int(msg) - 1]['url']
|
||||
browser = await get_browser()
|
||||
page = None
|
||||
try:
|
||||
if not browser:
|
||||
logger.warning('获取 browser 失败,请部署至 linux 环境....')
|
||||
await wbtop.finish('获取 browser 对象失败...')
|
||||
page = await browser.new_page()
|
||||
await page.goto(url, wait_until='networkidle', timeout=10000)
|
||||
await page.set_viewport_size({"width": 2560, "height": 1080})
|
||||
div = await page.query_selector("#pl_feedlist_index")
|
||||
await div.screenshot(path=f'{IMAGE_PATH}/temp/webtop_{event.user_id}.png', timeout=100000)
|
||||
await page.close()
|
||||
await wbtop.send(image(f'webtop_{event.user_id}.png', 'temp'))
|
||||
except Exception as e:
|
||||
logger.error(f'微博热搜截图出错... {type(e)}: {e}')
|
||||
if page:
|
||||
await page.close()
|
||||
await wbtop.send('发生了一些错误.....')
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
from nonebot import on_request
|
||||
from nonebot.adapters.cqhttp import Bot, FriendRequestEvent, GroupRequestEvent
|
||||
from models.friend_user import FriendUser
|
||||
from datetime import datetime
|
||||
from configs.config import AUTO_ADD_FRIEND, NICKNAME
|
||||
from nonebot.adapters.cqhttp.exception import ActionFailed
|
||||
from utils.utils import scheduler
|
||||
import time
|
||||
|
||||
__plugin_name__ = "好友群聊处理请求 [Hidden]"
|
||||
|
||||
friend_req = on_request(priority=5)
|
||||
|
||||
|
||||
exists_data = {"private": {}, "group": {}}
|
||||
|
||||
|
||||
@friend_req.handle()
|
||||
async def _(bot: Bot, event: FriendRequestEvent, state: dict):
|
||||
global exists_data
|
||||
if exists_data["private"].get(event.user_id):
|
||||
if time.time() - exists_data["private"][event.user_id] < 60 * 5:
|
||||
return
|
||||
exists_data["private"][event.user_id] = time.time()
|
||||
user = await bot.get_stranger_info(user_id=event.user_id)
|
||||
nickname = user["nickname"]
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"*****一份好友申请*****\n"
|
||||
f"昵称:{nickname}({event.user_id})\n"
|
||||
f"自动同意:{'√' if AUTO_ADD_FRIEND else '×'}\n"
|
||||
f"日期:{str(datetime.now()).split('.')[0]}\n"
|
||||
f"备注:{event.comment}",
|
||||
)
|
||||
if AUTO_ADD_FRIEND:
|
||||
await bot.set_friend_add_request(flag=event.flag, approve=True)
|
||||
await FriendUser.add_friend_info(user["user_id"], user["nickname"])
|
||||
|
||||
|
||||
group_req = on_request(priority=5, block=True)
|
||||
|
||||
|
||||
@group_req.handle()
|
||||
async def _(bot: Bot, event: GroupRequestEvent, state: dict):
|
||||
global exists_data
|
||||
if event.sub_type == "invite":
|
||||
if str(event.user_id) in bot.config.superusers:
|
||||
try:
|
||||
await bot.set_group_add_request(
|
||||
flag=event.flag, sub_type="invite", approve=True
|
||||
)
|
||||
except ActionFailed:
|
||||
pass
|
||||
else:
|
||||
if exists_data["group"].get(f"{event.user_id}:{event.group_id}"):
|
||||
if (
|
||||
time.time()
|
||||
- exists_data["group"][f"{event.user_id}:{event.group_id}"]
|
||||
< 60 * 5
|
||||
):
|
||||
return
|
||||
exists_data["group"][f"{event.user_id}:{event.group_id}"] = time.time()
|
||||
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"邀请日期:{str(datetime.now()).split('.')[0]}",
|
||||
)
|
||||
await bot.send_private_msg(
|
||||
user_id=event.user_id,
|
||||
message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n"
|
||||
"请确保已经群主或群管理沟通过!\n"
|
||||
"等待管理员处理吧!",
|
||||
)
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
"interval",
|
||||
minutes=5,
|
||||
)
|
||||
async def _():
|
||||
global exists_data
|
||||
exists_data = {"private": {}, "group": {}}
|
||||
@@ -0,0 +1,159 @@
|
||||
from nonebot import on_command
|
||||
from models.ban_user import BanUser
|
||||
from models.level_user import LevelUser
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot
|
||||
from nonebot.adapters.cqhttp import GroupMessageEvent, PrivateMessageEvent
|
||||
from nonebot.adapters.cqhttp.permission import GROUP
|
||||
from utils.utils import get_message_at, get_message_text, is_number
|
||||
from configs.config import NICKNAME
|
||||
from services.log import logger
|
||||
|
||||
|
||||
__plugin_name__ = "Ban/unBan"
|
||||
__plugin_usage__ = (
|
||||
"用法:\n"
|
||||
f"(不是禁言!是针对{NICKNAME}是否处理封禁用户消息)\n"
|
||||
"封禁/解封用户 [小时] [分钟]\n"
|
||||
"示例:.ban @djdsk\n"
|
||||
"示例:.ban @djdsk 0 30\n"
|
||||
"示例:.ban @sdasf 4\n"
|
||||
"示例:.unban @sdasf"
|
||||
)
|
||||
|
||||
|
||||
ban = on_command(
|
||||
".ban",
|
||||
aliases={".unban", "/ban", "/unban"},
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@ban.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
result = ""
|
||||
qq = get_message_at(event.json())
|
||||
if qq:
|
||||
qq = qq[0]
|
||||
user_name = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
|
||||
user_name = user_name['card'] if user_name['card'] else user_name['nickname']
|
||||
msg = get_message_text(event.json())
|
||||
if msg:
|
||||
msg = msg.split()
|
||||
if len(msg) == 2:
|
||||
if not is_number(msg[0].strip()) or not is_number(msg[1].strip()):
|
||||
await ban.finish("参数必须是数字!", at_sender=True)
|
||||
time = int(msg[0]) * 60 * 60 + int(msg[1]) * 60
|
||||
else:
|
||||
if not is_number(msg[0].strip()):
|
||||
await ban.finish("参数必须是数字!", at_sender=True)
|
||||
time = int(msg[0]) * 60 * 60
|
||||
else:
|
||||
time = -1
|
||||
if state["_prefix"]["raw_command"] in [".ban", "/ban"]:
|
||||
if (
|
||||
await LevelUser.get_user_level(event.user_id, event.group_id)
|
||||
<= await LevelUser.get_user_level(qq, event.group_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
):
|
||||
await ban.finish(
|
||||
f"您的权限等级比对方低或相等, {NICKNAME}不能为您使用此功能!",
|
||||
at_sender=True,
|
||||
)
|
||||
if await BanUser.ban(
|
||||
qq, await LevelUser.get_user_level(event.user_id, event.group_id), time
|
||||
):
|
||||
logger.info(
|
||||
f"USER {event.user_id} GROUP {event.group_id} 将 USER {qq} 封禁 时长 {time/60} 分钟"
|
||||
)
|
||||
result = f"已经将 {user_name} 加入{NICKNAME}的黑名单了!"
|
||||
if time != -1:
|
||||
result += f"将在 {time/60} 分钟后解封"
|
||||
else:
|
||||
time = await BanUser.check_ban_time(qq)
|
||||
if is_number(time):
|
||||
time = abs(int(time))
|
||||
if time < 60:
|
||||
time = str(time) + " 秒"
|
||||
else:
|
||||
time = str(int(time / 60)) + " 分钟"
|
||||
else:
|
||||
time += " 分钟"
|
||||
result = f"{user_name} 已在黑名单!预计 {time}后解封"
|
||||
else:
|
||||
if (
|
||||
await BanUser.check_ban_level(
|
||||
qq, await LevelUser.get_user_level(event.user_id, event.group_id)
|
||||
)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
):
|
||||
await ban.finish(
|
||||
f"ban掉 {user_name} 的管理员权限比您高,无法进行unban", at_sender=True
|
||||
)
|
||||
if await BanUser.unban(qq):
|
||||
logger.info(
|
||||
f"USER {event.user_id} GROUP {event.group_id} 将 USER {qq} 解禁"
|
||||
)
|
||||
result = f"已经把 {user_name} 从黑名单中删除了!"
|
||||
else:
|
||||
result = f"{user_name} 不在黑名单!"
|
||||
else:
|
||||
await ban.finish("艾特人了吗??", at_sender=True)
|
||||
await ban.finish(result, at_sender=True)
|
||||
|
||||
|
||||
@ban.handle()
|
||||
async def _(bot: Bot, event: PrivateMessageEvent, state: T_State):
|
||||
if str(event.user_id) in bot.config.superusers:
|
||||
msg = get_message_text(event.json())
|
||||
msg = msg.split()
|
||||
if is_number(msg[0]):
|
||||
qq = int(msg[0])
|
||||
if state["_prefix"]["raw_command"] in [".ban", "/ban"]:
|
||||
hour = 0
|
||||
minute = 0
|
||||
if len(msg) > 1 and is_number(msg[1]):
|
||||
hour = int(msg[1])
|
||||
if len(msg) > 2 and is_number(msg[2]):
|
||||
minute = int(msg[2])
|
||||
time = hour * 60 * 60 + minute * 60
|
||||
time = time if time else -1
|
||||
if await BanUser.ban(
|
||||
qq, 9, time
|
||||
):
|
||||
logger.info(
|
||||
f"USER {event.user_id} 将 USER {qq} 封禁 时长 {time/60} 分钟"
|
||||
)
|
||||
result = f"已经将 {qq} 加入{NICKNAME}的黑名单了!"
|
||||
if time != -1:
|
||||
result += f"将在 {time/60} 分钟后解封"
|
||||
else:
|
||||
result += f"将在 ∞ 分钟后解封"
|
||||
await ban.send(result)
|
||||
else:
|
||||
time = await BanUser.check_ban_time(qq)
|
||||
if is_number(time):
|
||||
time = abs(int(time))
|
||||
if time < 60:
|
||||
time = str(time) + " 秒"
|
||||
else:
|
||||
time = str(int(time / 60)) + " 分钟"
|
||||
else:
|
||||
time += " 分钟"
|
||||
await ban.send(f"{qq} 已在黑名单!预计 {time}后解封")
|
||||
else:
|
||||
if await BanUser.unban(qq):
|
||||
logger.info(
|
||||
f"USER {event.user_id} 将 USER {qq} 解禁"
|
||||
)
|
||||
result = f"已经把 {qq} 从黑名单中删除了!"
|
||||
else:
|
||||
result = f"{qq} 不在黑名单!"
|
||||
await ban.send(result)
|
||||
else:
|
||||
await ban.finish('qq号必须是数字!\n格式:.ban [qq] [hour]? [minute]?', at_sender=True)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.permission import SUPERUSER
|
||||
import asyncio
|
||||
from utils.utils import get_message_text, get_message_imgs
|
||||
from services.log import logger
|
||||
from models.group_remind import GroupRemind
|
||||
from utils.message_builder import image
|
||||
|
||||
__plugin_name__ = "广播 [Hidden]"
|
||||
|
||||
__plugin_usage__ = '广播- [消息] or [图片]'
|
||||
|
||||
broadcast = on_command("广播-", priority=1, permission=SUPERUSER, block=True)
|
||||
|
||||
|
||||
@broadcast.handle()
|
||||
async def _(bot: Bot, event: Event, state: T_State):
|
||||
msg = get_message_text(event.json())
|
||||
imgs = get_message_imgs(event.json())
|
||||
rst = ''
|
||||
for img in imgs:
|
||||
rst += image(img)
|
||||
sid = bot.self_id
|
||||
gl = await bot.get_group_list(self_id=sid)
|
||||
gl = [g['group_id'] for g in gl]
|
||||
for g in gl:
|
||||
if await GroupRemind.get_status(g, 'gb'):
|
||||
await asyncio.sleep(0.5)
|
||||
try:
|
||||
await bot.send_group_msg(self_id=sid, group_id=g, message=msg+rst)
|
||||
logger.info(f'群{g} 投递广播成功')
|
||||
except Exception as e:
|
||||
logger.error(f'群{g} 投递广播失败:{type(e)}')
|
||||
try:
|
||||
await broadcast.send(f'群{g} 投递广播失败:{type(e)}')
|
||||
except Exception as e:
|
||||
logger.critical(f'向广播发起者进行错误回报时发生错误:{type(e)}')
|
||||
await broadcast.send(f'广播完成!')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
from nonebot import on_command
|
||||
from .data_source import get_bt_info
|
||||
from services.log import logger
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.cqhttp import PrivateMessageEvent
|
||||
from utils.utils import get_message_text
|
||||
from nonebot.adapters.cqhttp.permission import PRIVATE
|
||||
from utils.utils import UserExistLimiter
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from aiohttp.client_exceptions import ServerDisconnectedError
|
||||
|
||||
__plugin_name__ = "磁力搜索"
|
||||
__plugin_usage__ = r"""
|
||||
* 请各位使用后不要转发 *
|
||||
* 拒绝反冲斗士! *
|
||||
bt [关键词] [页数](默认为1)
|
||||
示例:
|
||||
bt 钢铁侠
|
||||
bt 钢铁侠 3
|
||||
""".strip()
|
||||
|
||||
_ulmt = UserExistLimiter()
|
||||
|
||||
bt = on_command("bt", permission=PRIVATE, priority=5, block=True)
|
||||
|
||||
|
||||
@bt.args_parser
|
||||
async def _(bot: Bot, event: PrivateMessageEvent, state: T_State):
|
||||
if str(event.get_message()) in ["取消", "算了"]:
|
||||
await bt.finish("已取消操作..", at_sender=True)
|
||||
msg = get_message_text(event.json())
|
||||
if not msg:
|
||||
await bt.reject("你想搜索什么呢?", at_sender=True)
|
||||
state["keyword"] = msg
|
||||
state["page"] = "1"
|
||||
|
||||
|
||||
@bt.handle()
|
||||
async def _(bot: Bot, event: PrivateMessageEvent, state: T_State):
|
||||
if _ulmt.check(event.user_id):
|
||||
await bt.finish("您有bt任务正在进行,请等待结束.", at_sender=True)
|
||||
mp = get_message_text(event.json())
|
||||
if not mp:
|
||||
return
|
||||
mp = mp.split()
|
||||
if len(mp) == 2:
|
||||
state["keyword"] = mp[0]
|
||||
state["page"] = mp[1]
|
||||
else:
|
||||
state["keyword"] = mp[0]
|
||||
state["page"] = "1"
|
||||
|
||||
|
||||
@bt.got("keyword", prompt="虚空磁力?查什么GKD")
|
||||
async def _(bot: Bot, event: PrivateMessageEvent, state: T_State):
|
||||
_ulmt.set_True(event.user_id)
|
||||
keyword = state["keyword"]
|
||||
page = state["page"]
|
||||
await bt.send("开始搜索....", at_sender=True)
|
||||
send_flag = False
|
||||
try:
|
||||
async for title, itype, create_time, file_size, link in get_bt_info(
|
||||
keyword, page
|
||||
):
|
||||
await bt.send(
|
||||
f"标题:{title}\n"
|
||||
f"类型:{itype}\n"
|
||||
f"创建时间:{create_time}\n"
|
||||
f"文件大小:{file_size}\n"
|
||||
f"种子:{link}"
|
||||
)
|
||||
send_flag = True
|
||||
except TimeoutError:
|
||||
_ulmt.set_False(event.user_id)
|
||||
await bt.finish(f"搜索 {keyword} 超时...")
|
||||
except ServerDisconnectedError:
|
||||
_ulmt.set_False(event.user_id)
|
||||
await bt.finish(f"搜索 {keyword} 连接失败")
|
||||
except Exception as e:
|
||||
_ulmt.set_False(event.user_id)
|
||||
await bt.finish(f"bt 其他未知错误..")
|
||||
logger.error(f"bt 错误 e:{e}")
|
||||
if not send_flag:
|
||||
await bt.send(f"{keyword} 未搜索到...")
|
||||
logger.info(f"USER {event.user_id} BT搜索 {keyword} 第 {page} 页")
|
||||
_ulmt.set_False(event.user_id)
|
||||
@@ -0,0 +1,48 @@
|
||||
from utils.user_agent import get_user_agent
|
||||
import aiohttp
|
||||
from configs.config import MAXINFO_BT
|
||||
from bs4 import BeautifulSoup
|
||||
from utils.utils import get_local_proxy
|
||||
import platform
|
||||
|
||||
if platform.system() == "Windows":
|
||||
import asyncio
|
||||
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
url = "http://www.eclzz.world"
|
||||
|
||||
|
||||
async def get_bt_info(keyword: str, page: str):
|
||||
async with aiohttp.ClientSession(headers=get_user_agent()) as session:
|
||||
async with session.get(
|
||||
f"{url}/s/{keyword}_rel_{page}.html", proxy=get_local_proxy(), timeout=5
|
||||
) as response:
|
||||
text = await response.text()
|
||||
if text.find("大约0条结果") != -1:
|
||||
return
|
||||
soup = BeautifulSoup(text, "lxml")
|
||||
item_lst = soup.find_all("div", {"class": "search-item"})
|
||||
for item in item_lst[:MAXINFO_BT]:
|
||||
divs = item.find_all("div")
|
||||
title = (
|
||||
str(divs[0].find("a").text)
|
||||
.replace("<em>", "")
|
||||
.replace("</em>", "")
|
||||
.strip()
|
||||
)
|
||||
spans = divs[2].find_all("span")
|
||||
itype = spans[0].text
|
||||
create_time = spans[1].find("b").text
|
||||
file_size = spans[2].find("b").text
|
||||
link = await get_download_link(divs[0].find("a")["href"], session)
|
||||
yield title, itype, create_time, file_size, link
|
||||
|
||||
|
||||
async def get_download_link(_url: str, session) -> str:
|
||||
async with session.get(
|
||||
f"{url}{_url}", proxy=get_local_proxy(), timeout=30
|
||||
) as response:
|
||||
soup = BeautifulSoup(await response.text(), "lxml")
|
||||
return soup.find("a", {"id": "down-url"})["href"]
|
||||
@@ -0,0 +1,23 @@
|
||||
from nonebot import on_command
|
||||
from .data_source import Check
|
||||
from nonebot.adapters.cqhttp import Bot, Event
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
|
||||
__plugin_name__ = "自我检查 [Hidden]"
|
||||
|
||||
__plugin_usage__ = "用法:自检"
|
||||
|
||||
check = Check()
|
||||
|
||||
|
||||
check_ = on_command(
|
||||
"自检", aliases={"check"}, rule=to_me(), permission=SUPERUSER, block=True, priority=1
|
||||
)
|
||||
|
||||
|
||||
@check_.handle()
|
||||
async def _(bot: Bot, event: Event, state: T_State):
|
||||
await check_.send(await check.show())
|
||||
@@ -0,0 +1,72 @@
|
||||
import psutil
|
||||
import aiohttp
|
||||
import time
|
||||
from datetime import datetime
|
||||
from utils.user_agent import get_user_agent
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from utils.utils import get_local_proxy
|
||||
import asyncio
|
||||
from services.log import logger
|
||||
|
||||
|
||||
class Check:
|
||||
def __init__(self):
|
||||
self.cpu = None
|
||||
self.memory = None
|
||||
self.disk = None
|
||||
self.user = None
|
||||
self.baidu = 200
|
||||
self.google = 200
|
||||
|
||||
async def check_all(self):
|
||||
await self.check_network()
|
||||
await asyncio.sleep(0.1)
|
||||
self.check_system()
|
||||
self.check_user()
|
||||
|
||||
def check_system(self):
|
||||
self.cpu = psutil.cpu_percent()
|
||||
self.memory = psutil.virtual_memory().percent
|
||||
self.disk = psutil.disk_usage("/").percent
|
||||
|
||||
async def check_network(self):
|
||||
async with aiohttp.ClientSession(headers=get_user_agent()) as session:
|
||||
try:
|
||||
async with session.get(
|
||||
"https://www.baidu.com/", proxy=get_local_proxy(), timeout=3
|
||||
) as response:
|
||||
pass
|
||||
except (TimeoutError, ClientConnectorError) as e:
|
||||
logger.warning(f"访问BaiDu失败... e: {e}")
|
||||
self.baidu = 404
|
||||
try:
|
||||
async with session.get(
|
||||
"https://www.google.com/", proxy=get_local_proxy(), timeout=3
|
||||
) as response:
|
||||
pass
|
||||
except (TimeoutError, ClientConnectorError) as e:
|
||||
logger.warning(f"访问Google失败... e: {e}")
|
||||
self.google = 404
|
||||
|
||||
def check_user(self):
|
||||
rst = ""
|
||||
for user in psutil.users():
|
||||
rst += f'[{user.name}] {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user.started))}\n'
|
||||
self.user = rst[:-1]
|
||||
|
||||
async def show(self):
|
||||
await self.check_all()
|
||||
rst = (
|
||||
f'[Time] {str(datetime.now()).split(".")[0]}\n'
|
||||
f"-----System-----\n"
|
||||
f"[CPU] {self.cpu}%\n"
|
||||
f"[Memory] {self.memory}%\n"
|
||||
f"[Disk] {self.disk}%\n"
|
||||
f"-----Network-----\n"
|
||||
f"[BaiDu] {self.baidu}\n"
|
||||
f"[Google] {self.google}\n"
|
||||
)
|
||||
if self.user:
|
||||
rst += "-----User-----\n" + self.user
|
||||
return rst
|
||||
@@ -0,0 +1,95 @@
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot import on_command
|
||||
from .data_source import check_update, get_latest_version_data
|
||||
from services.log import logger
|
||||
from utils.utils import scheduler, get_bot
|
||||
from pathlib import Path
|
||||
from configs.config import AUTO_UPDATE_ZHENXUN
|
||||
from nonebot.rule import to_me
|
||||
import platform
|
||||
import os
|
||||
|
||||
|
||||
update_zhenxun = on_command('检查更新真寻', permission=SUPERUSER, priority=1, block=True)
|
||||
|
||||
restart = on_command('重启', aliases={'restart'}, permission=SUPERUSER, rule=to_me(), priority=1, block=True)
|
||||
|
||||
|
||||
@update_zhenxun.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
try:
|
||||
code = await check_update(bot)
|
||||
except Exception as e:
|
||||
logger.error(f'更新真寻未知错误 {type(e)}:{e}')
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f'更新真寻未知错误 {type(e)}:{e}'
|
||||
)
|
||||
else:
|
||||
if code == 200:
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f'更新完毕,请重启真寻....'
|
||||
)
|
||||
|
||||
|
||||
@restart.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
if str(platform.system()).lower() == 'windows':
|
||||
await restart.finish('暂无windows重启脚本...')
|
||||
|
||||
|
||||
@restart.got('flag', prompt='确定是否重启真寻?(重启失败咱们将失去联系,请谨慎!)')
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
flag = state['flag']
|
||||
if flag.lower() in ['true', '是', '好']:
|
||||
await restart.send('开始重启真寻..请稍等...')
|
||||
open('is_restart', 'w')
|
||||
os.system('./restart.sh')
|
||||
else:
|
||||
await restart.send('已取消操作...')
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=12,
|
||||
minute=0,
|
||||
)
|
||||
async def _():
|
||||
if AUTO_UPDATE_ZHENXUN:
|
||||
_version = "v0.0.0"
|
||||
_version_file = Path() / "__version__"
|
||||
if _version_file.exists():
|
||||
_version = (
|
||||
open(_version_file, "r", encoding="utf8").readline().split(":")[-1].strip()
|
||||
)
|
||||
data = await get_latest_version_data()
|
||||
if data:
|
||||
latest_version = data["name"]
|
||||
if _version != latest_version:
|
||||
bot = get_bot()
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f'检测到真寻版本更新\n'
|
||||
f'当前版本:{_version},最新版本:{latest_version}\n'
|
||||
f'尝试自动更新...'
|
||||
)
|
||||
try:
|
||||
code = await check_update(bot)
|
||||
except Exception as e:
|
||||
logger.error(f'更新真寻未知错误 {type(e)}:{e}')
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f'更新真寻未知错误 {type(e)}:{e}\n'
|
||||
)
|
||||
else:
|
||||
if code == 200:
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f'更新完毕,请重启真寻....'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nonebot.adapters.cqhttp import Bot
|
||||
from utils.user_agent import get_user_agent
|
||||
from utils.utils import get_local_proxy
|
||||
from utils.image_utils import CreateImg
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from typing import List
|
||||
from services.log import logger
|
||||
from pathlib import Path
|
||||
import ujson as json
|
||||
import nonebot
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
import platform
|
||||
import tarfile
|
||||
import shutil
|
||||
import os
|
||||
|
||||
if str(platform.system()).lower() == "windows":
|
||||
policy = asyncio.WindowsSelectorEventLoopPolicy()
|
||||
asyncio.set_event_loop_policy(policy)
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
release_url = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest"
|
||||
|
||||
_version_file = Path() / "__version__"
|
||||
zhenxun_latest_tar_gz = Path() / "zhenxun_latest_file.tar.gz"
|
||||
temp_dir = Path() / "temp"
|
||||
backup_dir = Path() / "backup"
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def remind(bot: Bot):
|
||||
if str(platform.system()).lower() != "windows":
|
||||
restart = Path() / "restart.sh"
|
||||
if not restart.exists():
|
||||
with open(restart, "w", encoding="utf8") as f:
|
||||
f.write(
|
||||
f"pid=$(netstat -tunlp | grep "
|
||||
+ str(bot.config.port)
|
||||
+ " | awk '{print $7}')\n"
|
||||
"pid=${pid%/*}\n"
|
||||
"kill -9 $pid\n"
|
||||
"sleep 3\n"
|
||||
"python3 bot.py"
|
||||
)
|
||||
os.system("chmod +x ./restart.sh")
|
||||
logger.info("已自动生成 restart.sh(重启) 文件,请检查脚本是否与本地指令符合...")
|
||||
is_restart_file = Path() / "is_restart"
|
||||
if is_restart_file.exists():
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"真寻重启完毕...",
|
||||
)
|
||||
is_restart_file.unlink()
|
||||
|
||||
|
||||
async def check_update(bot: Bot) -> int:
|
||||
logger.info("开始检查更新真寻酱....")
|
||||
_version = "v0.0.0"
|
||||
if _version_file.exists():
|
||||
_version = (
|
||||
open(_version_file, "r", encoding="utf8").readline().split(":")[-1].strip()
|
||||
)
|
||||
data = await get_latest_version_data()
|
||||
if data:
|
||||
latest_version = data["name"]
|
||||
if _version != latest_version:
|
||||
tar_gz_url = data["tarball_url"]
|
||||
logger.info(f"检测真寻已更新,当前版本:{_version},最新版本:{latest_version}")
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"检测真寻已更新,当前版本:{_version},最新版本:{latest_version}\n" f"开始更新.....",
|
||||
)
|
||||
logger.info(f"开始下载真寻最新版文件....")
|
||||
if await download_latest_file(tar_gz_url):
|
||||
logger.info("下载真寻最新版文件完成....")
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None, _file_handle, latest_version
|
||||
)
|
||||
logger.info("真寻更新完毕,清理文件完成....")
|
||||
logger.info("开始获取真寻更新日志.....")
|
||||
update_info = data["body"]
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"真寻更新完成,版本:{_version} -> {latest_version}\n"
|
||||
f"更新日期:{data['created_at']}\n"
|
||||
f"更新日志:\n"
|
||||
f"{update_info}",
|
||||
)
|
||||
width = 0
|
||||
height = len(update_info.split('\n')) * 24
|
||||
for m in update_info.split('\n'):
|
||||
if len(m) * 20 > width:
|
||||
width = len(m) * 18
|
||||
A = CreateImg(width, height, font_size=20)
|
||||
A.text((10, 10), update_info)
|
||||
A.save(f'{IMAGE_PATH}/update_info.png')
|
||||
return 200
|
||||
else:
|
||||
logger.warning(f"下载真寻最新版本失败...版本号:{latest_version}")
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"下载真寻最新版本失败...版本号:{latest_version}.",
|
||||
)
|
||||
else:
|
||||
logger.info(f"自动获取真寻版本成功:{latest_version},当前版本为最新版,无需更新...")
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"自动获取真寻版本成功:{latest_version},当前版本为最新版,无需更新...",
|
||||
)
|
||||
else:
|
||||
logger.warning("自动获取真寻版本失败....")
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]), message=f"自动获取真寻版本失败...."
|
||||
)
|
||||
return 999
|
||||
|
||||
|
||||
def _file_handle(latest_version: str):
|
||||
if not temp_dir.exists():
|
||||
temp_dir.mkdir(exist_ok=True, parents=True)
|
||||
if backup_dir.exists():
|
||||
shutil.rmtree(backup_dir)
|
||||
backup_dir.mkdir(exist_ok=True, parents=True)
|
||||
logger.info("开始解压真寻文件压缩包....")
|
||||
tf = tarfile.open(zhenxun_latest_tar_gz)
|
||||
tf.extractall(temp_dir)
|
||||
logger.info("解压真寻文件压缩包完成....")
|
||||
zhenxun_latest_file = Path(temp_dir) / os.listdir(temp_dir)[0]
|
||||
update_info_file = Path(zhenxun_latest_file) / "update_info.json"
|
||||
update_info = json.load(open(update_info_file, "r", encoding="utf8"))
|
||||
update_file = update_info["update_file"]
|
||||
add_file = update_info["add_file"]
|
||||
delete_file = update_info["delete_file"]
|
||||
config_file = Path() / "configs" / "config.py"
|
||||
config_path_file = Path() / "configs" / "config_path.py"
|
||||
for file in delete_file + update_file:
|
||||
file = Path() / file
|
||||
backup_file = Path(backup_dir) / file
|
||||
if file.exists():
|
||||
backup_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
if backup_file.exists():
|
||||
backup_file.unlink()
|
||||
if file not in [config_file, config_path_file]:
|
||||
os.rename(file.absolute(), backup_file.absolute())
|
||||
else:
|
||||
with open(file, "r", encoding="utf8") as rf:
|
||||
data = rf.read()
|
||||
with open(backup_file, "w", encoding="utf8") as wf:
|
||||
wf.write(data)
|
||||
logger.info(f"已备份文件:{file}")
|
||||
for file in add_file + update_file:
|
||||
new_file = Path(zhenxun_latest_file) / file
|
||||
old_file = Path() / file
|
||||
if old_file not in [config_file, config_path_file]:
|
||||
if not old_file.exists() and new_file.exists():
|
||||
os.rename(new_file.absolute(), old_file.absolute())
|
||||
logger.info(f"已更新文件:{file}")
|
||||
else:
|
||||
tmp = ""
|
||||
new_lines = open(new_file, "r", encoding="utf8").readlines()
|
||||
old_lines = open(old_file, "r", encoding="utf8").readlines()
|
||||
for nl in new_lines:
|
||||
tmp += check_old_lines(old_lines, nl)
|
||||
with open(file, "w", encoding="utf8") as f:
|
||||
f.write(tmp)
|
||||
if tf:
|
||||
tf.close()
|
||||
if temp_dir.exists():
|
||||
shutil.rmtree(temp_dir)
|
||||
if zhenxun_latest_tar_gz.exists():
|
||||
zhenxun_latest_tar_gz.unlink()
|
||||
local_update_info_file = Path() / "update_info.json"
|
||||
if local_update_info_file.exists():
|
||||
local_update_info_file.unlink()
|
||||
with open(_version_file, "w", encoding="utf8") as f:
|
||||
f.write(f"__version__: {latest_version}")
|
||||
|
||||
|
||||
# 获取最新版本号
|
||||
async def get_latest_version_data() -> dict:
|
||||
async with aiohttp.ClientSession(headers=get_user_agent()) as session:
|
||||
for _ in range(3):
|
||||
try:
|
||||
async with session.get(release_url, proxy=get_local_proxy()) as res:
|
||||
if res.status == 200:
|
||||
return await res.json()
|
||||
except (TimeoutError, ClientConnectorError):
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
# 下载文件
|
||||
async def download_latest_file(url_: str) -> bool:
|
||||
async with aiohttp.ClientSession(headers=get_user_agent()) as session:
|
||||
for _ in range(3):
|
||||
try:
|
||||
async with session.get(url_, proxy=get_local_proxy()) as res:
|
||||
if res.status == 200:
|
||||
async with aiofiles.open(zhenxun_latest_tar_gz, "wb") as f:
|
||||
await f.write(await res.read())
|
||||
return True
|
||||
except (TimeoutError, ClientConnectorError):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
# 逐行检测
|
||||
def check_old_lines(lines: List[str], line: str) -> str:
|
||||
if "=" not in line:
|
||||
return line
|
||||
for l in lines:
|
||||
if "=" in l and l.split("=")[0].strip() == line.split("=")[0].strip():
|
||||
if len(l) > len(line):
|
||||
return l
|
||||
return line
|
||||
@@ -0,0 +1,27 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
|
||||
from services.log import logger
|
||||
from utils.message_builder import image
|
||||
import requests
|
||||
|
||||
__plugin_name__ = "coser"
|
||||
|
||||
__plugin_usage__ = "用法:发送‘coser’"
|
||||
|
||||
|
||||
coser = on_command(
|
||||
"cos", aliases={"coser", "括丝", "COS", "Cos", "cOS", "coS"}, priority=5, block=True
|
||||
)
|
||||
|
||||
|
||||
url_2 = "http://api.rosysun.cn/cos"
|
||||
|
||||
|
||||
@coser.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
img_url = requests.get(url_2).text
|
||||
await coser.send(image(img_url))
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 发送COSER"
|
||||
)
|
||||