mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 22:34:19 +02:00
Commited backup
This commit is contained in:
2
hikariatama/ftg/.gitignore
vendored
Normal file
2
hikariatama/ftg/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.stfolder
|
||||
.stignore
|
||||
661
hikariatama/ftg/LICENSE
Normal file
661
hikariatama/ftg/LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
3
hikariatama/ftg/README.md
Normal file
3
hikariatama/ftg/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# FTG\GeekTG modules by @hikariatama
|
||||
This repository recieves late updates. Real-time updates are available at mods.hikariatama.ru
|
||||
Modules, marked with green color are GeekTG only, and will throw in FTG
|
||||
435
hikariatama/ftg/account_switcher.py
Normal file
435
hikariatama/ftg/account_switcher.py
Normal file
@@ -0,0 +1,435 @@
|
||||
__version__ = (2, 0, 1)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/account_switcher_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/account_switcher.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
import typing
|
||||
|
||||
from aiogram.utils.exceptions import ChatNotFound
|
||||
from telethon.tl.functions.account import UpdateProfileRequest
|
||||
from telethon.tl.functions.channels import InviteToChannelRequest
|
||||
from telethon.tl.functions.photos import UploadProfilePhotoRequest
|
||||
from telethon.tl.functions.users import GetFullUserRequest
|
||||
from telethon.tl.types import Message as TelethonMessage
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall, InlineMessage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AccountSwitcherMod(loader.Module):
|
||||
"""Allows you to easily switch between different profiles"""
|
||||
|
||||
strings = {
|
||||
"name": "AccountSwitcher",
|
||||
"account_saved": (
|
||||
"<emoji document_id=5301255387306009369>🌚</emoji> <b><a"
|
||||
' href="https://t.me/c/{}/{}">Account</a> saved!</b>'
|
||||
),
|
||||
"restore_btn": "👆 Restore",
|
||||
"desc": "This chat will handle your saved profiles",
|
||||
"first_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> First name restored\n"
|
||||
),
|
||||
"first_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> First name not saved\n"
|
||||
),
|
||||
"last_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> First name restored\n"
|
||||
),
|
||||
"last_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> First name not saved\n"
|
||||
),
|
||||
"bio_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Bio restored\n"
|
||||
),
|
||||
"bio_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Bio not saved\n"
|
||||
),
|
||||
"data_not_restored": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> First name not"
|
||||
" restored\n<emoji document_id=5312526098750252863>🚫</emoji> Last name not"
|
||||
" restored\n<emoji document_id=5312526098750252863>🚫</emoji> Bio not"
|
||||
" restored\n"
|
||||
),
|
||||
"pfp_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Profile photo restored"
|
||||
),
|
||||
"pfp_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Profile photo not saved"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"account_saved": (
|
||||
"<emoji document_id=5301255387306009369>🌚</emoji> <b><a"
|
||||
' href="https://t.me/c/{}/{}">Аккаунт</a> сохранен!</b>'
|
||||
),
|
||||
"restore_btn": "👆 Восстановить",
|
||||
"desc": "Тут будут появляться сохраненные профили",
|
||||
"first_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Имя восстановлено\n"
|
||||
),
|
||||
"first_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Имя не сохранялось\n"
|
||||
),
|
||||
"last_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Фамилия восстановлена\n"
|
||||
),
|
||||
"last_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Фамилия не сохранялась\n"
|
||||
),
|
||||
"bio_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Био восстановлено\n"
|
||||
),
|
||||
"bio_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Био не сохранялось\n"
|
||||
),
|
||||
"data_not_restored": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> Имя не"
|
||||
" восстановлено\n<emoji document_id=5312526098750252863>🚫</emoji> Фамилия"
|
||||
" не восстановлена\n<emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Био не"
|
||||
" восстановлено\n"
|
||||
),
|
||||
"pfp_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Аватарка восстановлена"
|
||||
),
|
||||
"pfp_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Аватарка не сохранялась"
|
||||
),
|
||||
"_cmd_doc_accsave": "Сохранить аккаунт для последующего использования",
|
||||
"_cls_doc": "Позволяет быстро переключаться между разными аккаунтами",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"account_saved": (
|
||||
"<emoji document_id=5301255387306009369>🌚</emoji> <b><a"
|
||||
' href="https://t.me/c/{}/{}">Konto</a> gespeichert!</b>'
|
||||
),
|
||||
"restore_btn": "👆 Wiederherstellen",
|
||||
"desc": "Dieser Chat wird deine gespeicherten Profile verwalten",
|
||||
"first_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Vorname"
|
||||
" wiederhergestellt.\n"
|
||||
),
|
||||
"first_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Vorname nicht"
|
||||
" gespeichert.\n"
|
||||
),
|
||||
"last_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Nachname"
|
||||
" wiederhergestellt.\n"
|
||||
),
|
||||
"last_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Nachname nicht"
|
||||
" gespeichert.\n"
|
||||
),
|
||||
"bio_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Bio wiederhergestellt.\n"
|
||||
),
|
||||
"bio_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Bio nicht gespeichert.\n"
|
||||
),
|
||||
"data_not_restored": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> Vorname nicht"
|
||||
" wiederhergestellt.\n<emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Nachname nicht wiederhergestellt.\n<emoji"
|
||||
" document_id=5312526098750252863>🚫</emoji> Bio nicht wiederhergestellt.\n"
|
||||
),
|
||||
"pfp_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Profilbild"
|
||||
" wiederhergestellt."
|
||||
),
|
||||
"pfp_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Profilbild nicht"
|
||||
" gespeichert."
|
||||
),
|
||||
"_cmd_doc_accsave": "Speichert das Konto für spätere Verwendung",
|
||||
"_cls_doc": "Ermöglicht es, schnell zwischen verschiedenen Konten zu wechseln",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"account_saved": (
|
||||
"<emoji document_id=5301255387306009369>🌚</emoji> <b><a"
|
||||
' href="https://t.me/c/{}/{}">खाता</a> सहेजा गया!</b>'
|
||||
),
|
||||
"restore_btn": "👆 पुनर्स्थापित करें",
|
||||
"desc": "यह चैट आपके सहेजे गए प्रोफाइल का प्रबंधन करेगा",
|
||||
"first_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> पहला नाम पुनर्स्थापित"
|
||||
" किया गया।\n"
|
||||
),
|
||||
"first_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> पहला नाम सहेजा नहीं गया।\n"
|
||||
),
|
||||
"last_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> अंतिम नाम पुनर्स्थापित"
|
||||
" किया गया।\n"
|
||||
),
|
||||
"last_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> अंतिम नाम सहेजा नहीं गया।\n"
|
||||
),
|
||||
"bio_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> बायो पुनर्स्थापित किया"
|
||||
" गया।\n"
|
||||
),
|
||||
"bio_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> बायो सहेजा नहीं गया।\n"
|
||||
),
|
||||
"data_not_restored": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> पहला नाम पुनर्स्थापित"
|
||||
" नहीं किया गया।\n<emoji document_id=5312526098750252863>🚫</emoji> अंतिम"
|
||||
" नाम पुनर्स्थापित नहीं किया गया।\n<emoji"
|
||||
" document_id=5312526098750252863>🚫</emoji> बायो पुनर्स्थापित नहीं किया"
|
||||
" गया।\n"
|
||||
),
|
||||
"pfp_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> प्रोफ़ाइल चित्र"
|
||||
" पुनर्स्थापित किया गया।"
|
||||
),
|
||||
"pfp_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> प्रोफ़ाइल चित्र सहेजा"
|
||||
" नहीं गया।"
|
||||
),
|
||||
"_cmd_doc_accsave": "भविष्य के उपयोग के लिए खाता सहेजें",
|
||||
"_cls_doc": "विभिन्न खातों के बीच जल्दी से जल्दी बदलने की अनुमति देता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"account_saved": (
|
||||
"<emoji document_id=5301255387306009369>🌚</emoji> <b><a"
|
||||
' href="https://t.me/c/{}/{}">Hisob</a> saqlandi!</b>'
|
||||
),
|
||||
"restore_btn": "👆 Qayta tiklash",
|
||||
"desc": "Bu chat sizning saqlangan profilni boshqaradi",
|
||||
"first_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Nomi qayta tiklandi.\n"
|
||||
),
|
||||
"first_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Nomi saqlanmadi.\n"
|
||||
),
|
||||
"last_name_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Familiya qayta"
|
||||
" tiklandi.\n"
|
||||
),
|
||||
"last_name_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Familiya saqlanmadi.\n"
|
||||
),
|
||||
"bio_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Bio qayta tiklandi.\n"
|
||||
),
|
||||
"bio_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Bio saqlanmadi.\n"
|
||||
),
|
||||
"data_not_restored": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> Nomi qayta"
|
||||
" tiklanmadi.\n<emoji document_id=5312526098750252863>🚫</emoji> Familiya"
|
||||
" qayta tiklanmadi.\n<emoji document_id=5312526098750252863>🚫</emoji> Bio"
|
||||
" qayta tiklanmadi.\n"
|
||||
),
|
||||
"pfp_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> Profil rasmi qayta"
|
||||
" tiklandi."
|
||||
),
|
||||
"pfp_unsaved": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> Profil rasmi saqlanmadi."
|
||||
),
|
||||
"_cmd_doc_accsave": "Keyingi ishlatish uchun hisobni saqlash",
|
||||
"_cls_doc": "Tez-tez turli hisoblarga o'tishga imkon beradi",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._accs_db, is_new = await utils.asset_channel(
|
||||
self._client,
|
||||
"hikka-acc-switcher",
|
||||
self.strings("desc"),
|
||||
silent=True,
|
||||
archive=True,
|
||||
avatar="https://raw.githubusercontent.com/hikariatama/assets/master/hikka-acc-switcher.png",
|
||||
_folder="hikka",
|
||||
)
|
||||
|
||||
self._accs_db_id = int(f"-100{self._accs_db.id}")
|
||||
|
||||
if not is_new:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._client(
|
||||
InviteToChannelRequest(self._accs_db, [self.inline.bot_username])
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("Unable to invite logger to chat. Maybe he's already there?")
|
||||
|
||||
async def _save_acc(
|
||||
self,
|
||||
photo: typing.Optional[bytes],
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
bio: str,
|
||||
no_retry: bool = False,
|
||||
) -> int:
|
||||
info = (
|
||||
f"<code>{utils.escape_html(first_name)}</code> "
|
||||
f"<code>{utils.escape_html(last_name)}</code>\n\n"
|
||||
f"<b>Bio</b>: <code>{utils.escape_html(bio)}</code>\n"
|
||||
)
|
||||
|
||||
try:
|
||||
if photo is not None:
|
||||
photo_io = io.BytesIO(photo)
|
||||
photo_io.name = "pfp.jpg"
|
||||
|
||||
return (
|
||||
await self.inline.bot.send_document(
|
||||
self._accs_db_id,
|
||||
photo_io,
|
||||
caption=info,
|
||||
parse_mode="HTML",
|
||||
reply_markup=self.inline.generate_markup(
|
||||
{"text": self.strings("restore_btn"), "data": "accrest"}
|
||||
),
|
||||
)
|
||||
).message_id
|
||||
else:
|
||||
return (
|
||||
await self.inline.bot.send_message(
|
||||
self._accs_db_id,
|
||||
info,
|
||||
parse_mode="HTML",
|
||||
reply_markup=self.inline.generate_markup(
|
||||
{"text": self.strings("restore_btn"), "data": "accrest"}
|
||||
),
|
||||
)
|
||||
).message_id
|
||||
except ChatNotFound:
|
||||
if no_retry:
|
||||
logger.exception("Can't restore account")
|
||||
return
|
||||
|
||||
await self._client(
|
||||
InviteToChannelRequest(self._accs_db, [self.inline.bot_username])
|
||||
)
|
||||
|
||||
return await self._save_acc(
|
||||
photo,
|
||||
first_name,
|
||||
last_name,
|
||||
bio,
|
||||
no_retry=True,
|
||||
)
|
||||
|
||||
async def accrest_callback_handler(self, call: InlineCall):
|
||||
if call.data != "accrest":
|
||||
return
|
||||
|
||||
await call.answer(await self._restore(call.message), show_alert=True)
|
||||
|
||||
async def accsavecmd(self, message: TelethonMessage):
|
||||
"""Save account for future restoring"""
|
||||
full = await self._client(GetFullUserRequest("me"))
|
||||
acc = await self._client.force_get_entity("me")
|
||||
|
||||
message_id = await self._save_acc(
|
||||
(
|
||||
(await self._client.download_profile_photo(acc, bytes))
|
||||
if full.full_user.profile_photo
|
||||
else None
|
||||
),
|
||||
getattr(acc, "first_name", "None"),
|
||||
getattr(acc, "last_name", "None"),
|
||||
(getattr(full.full_user, "about", "None")),
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message, self.strings("account_saved").format(self._accs_db.id, message_id)
|
||||
)
|
||||
|
||||
async def _restore(
|
||||
self,
|
||||
reply: typing.Union[TelethonMessage, InlineMessage],
|
||||
) -> str:
|
||||
log = ""
|
||||
|
||||
first_name, last_name, bio = list(
|
||||
map(
|
||||
lambda x: x.replace(">", ">")
|
||||
.replace("<", "<")
|
||||
.replace(""", '"')
|
||||
.replace("&", "&"),
|
||||
re.findall(
|
||||
r"<code>(.*?)</code>",
|
||||
getattr(reply, "html_text", reply.text),
|
||||
flags=re.S,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if first_name == "None":
|
||||
first_name = None
|
||||
|
||||
if last_name == "None":
|
||||
last_name = None
|
||||
|
||||
if bio == "None":
|
||||
bio = None
|
||||
|
||||
try:
|
||||
await self._client(UpdateProfileRequest(first_name, last_name, bio))
|
||||
|
||||
log += (
|
||||
self.strings("first_name_restored")
|
||||
if first_name
|
||||
else self.strings("first_name_unsaved")
|
||||
)
|
||||
|
||||
log += (
|
||||
self.strings("last_name_restored")
|
||||
if last_name
|
||||
else self.strings("last_name_unsaved")
|
||||
)
|
||||
|
||||
log += self.strings("bio_restored") if bio else self.strings("bio_unsaved")
|
||||
except Exception:
|
||||
logger.exception("Can't restore account due to")
|
||||
log += self.strings("data_not_restored")
|
||||
|
||||
try:
|
||||
upload = await self._client.upload_file(
|
||||
await self._client.download_file(reply.media, bytes)
|
||||
)
|
||||
await self._client(UploadProfilePhotoRequest(upload))
|
||||
log += self.strings("pfp_restored")
|
||||
except Exception:
|
||||
try:
|
||||
file = io.BytesIO()
|
||||
await reply.document.download(destination_file=file)
|
||||
|
||||
await self._client(
|
||||
UploadProfilePhotoRequest(
|
||||
await self._client.upload_file(file),
|
||||
)
|
||||
)
|
||||
|
||||
log += self.strings("pfp_restored")
|
||||
except Exception:
|
||||
log += self.strings("pfp_unsaved")
|
||||
|
||||
return re.sub(r"\n{2,}", "\n", log)
|
||||
168
hikariatama/ftg/activists.py
Normal file
168
hikariatama/ftg/activists.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/activists_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/activists.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.4.0
|
||||
|
||||
import time
|
||||
import typing
|
||||
|
||||
from telethon.tl.types import Chat, Message, User
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ActivistsMod(loader.Module):
|
||||
"""Looks for the most active users in chat"""
|
||||
|
||||
strings = {
|
||||
"name": "Activists",
|
||||
"searching": (
|
||||
"<emoji document_id=5188311512791393083>🔎</emoji> <b>Looking for the most"
|
||||
" active users in chat...\nThis might take a while.</b>"
|
||||
),
|
||||
"user": (
|
||||
'<emoji document_id=5314541718312328811>👤</emoji> {}. <a href="{}">{}</a>:'
|
||||
" {} messages"
|
||||
),
|
||||
"active": (
|
||||
"<emoji document_id=5312361425409156767>⬆️</emoji> <b>The most active users"
|
||||
" in this chat:</b>\n\n{}\n<i>Request took: {}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"searching": (
|
||||
"<emoji document_id=5188311512791393083>🔎</emoji> <b>Поиск самых активных"
|
||||
" участников чата...\nЭто может занять некоторое время.</b>"
|
||||
),
|
||||
"active": (
|
||||
"<emoji document_id=5312361425409156767>⬆️</emoji> <b>Самые активные"
|
||||
" пользователи в чате:</b>\n\n{}\n<i>Подсчет занял: {}s</i>"
|
||||
),
|
||||
"_cmd_doc_activists": (
|
||||
"[количество] [-m <int>] - Найти наиболее активных пользователей чата"
|
||||
),
|
||||
"_cls_doc": "Ищет наиболее активных пользователей чата",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"searching": (
|
||||
"<emoji document_id=5188311512791393083>🔎</emoji> <b>Suche nach den"
|
||||
" aktivsten Benutzern im Chat...\nDies kann eine Weile dauern.</b>"
|
||||
),
|
||||
"active": (
|
||||
"<emoji document_id=5312361425409156767>⬆️</emoji> <b>Die aktivsten"
|
||||
" Benutzer in diesem Chat:</b>\n\n{}\n<i>Anfrage dauerte: {}s</i>"
|
||||
),
|
||||
"_cmd_doc_activists": (
|
||||
"[Anzahl] [-m <int>] - Finde die aktivsten Benutzer im Chat"
|
||||
),
|
||||
"_cls_doc": "Sucht nach den aktivsten Benutzern im Chat",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"searching": (
|
||||
"<emoji document_id=5188311512791393083>🔎</emoji> <b>चैट में सबसे सक्रिय"
|
||||
" उपयोगकर्ताओं की तलाश कर रहा हूं...\nयह थोड़ा समय लेने सकता है।</b>"
|
||||
),
|
||||
"active": (
|
||||
"<emoji document_id=5312361425409156767>⬆️</emoji> <b>इस चैट में सबसे"
|
||||
" सक्रिय उपयोगकर्ता:</b>\n\n{}\n<i>अनुरोध लिया: {}s</i>"
|
||||
),
|
||||
"_cmd_doc_activists": (
|
||||
"[संख्या] [-m <int>] - चैट में सबसे सक्रिय उपयोगकर्ताओं की तलाश करें"
|
||||
),
|
||||
"_cls_doc": "चैट में सबसे सक्रिय उपयोगकर्ताओं की तलाश करता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"searching": (
|
||||
"<emoji document_id=5188311512791393083>🔎</emoji> <b>Chatdagi eng faol"
|
||||
" foydalanuvchilarni qidirish...\nBu bir necha vaqt olishi mumkin.</b>"
|
||||
),
|
||||
"active": (
|
||||
"<emoji document_id=5312361425409156767>⬆️</emoji> <b>Ushbu chatdagi eng"
|
||||
" faol foydalanuvchilar:</b>\n\n{}\n<i>Talab: {}s</i>"
|
||||
),
|
||||
"_cmd_doc_activists": (
|
||||
"[soni] [-m <int>] - Chatdagi eng faol foydalanuvchilarni qidirish"
|
||||
),
|
||||
"_cls_doc": "Chatdagi eng faol foydalanuvchilarni qidiradi",
|
||||
}
|
||||
|
||||
async def check_admin(
|
||||
self,
|
||||
chat: typing.Union[int, Chat],
|
||||
user_id: typing.Union[int, User],
|
||||
) -> bool:
|
||||
try:
|
||||
return (await self._client.get_perms_cached(chat, user_id)).is_admin
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def activistscmd(self, message: Message):
|
||||
"""[quantity] [-m <int>] - Find top active users in chat"""
|
||||
args = utils.get_args_raw(message)
|
||||
limit = None
|
||||
if "-m" in args:
|
||||
limit = int(
|
||||
"".join([lim for lim in args[args.find("-m") + 2 :] if lim.isdigit()])
|
||||
)
|
||||
args = args[: args.find("-m")].strip()
|
||||
|
||||
quantity = int(args) if args.isdigit() else 15
|
||||
|
||||
message = await utils.answer(message, self.strings("searching"))
|
||||
|
||||
st = time.perf_counter()
|
||||
|
||||
temp = {}
|
||||
async for msg in self._client.iter_messages(message.peer_id, limit=limit):
|
||||
user = getattr(msg, "sender_id", False)
|
||||
if not user:
|
||||
continue
|
||||
|
||||
if user not in temp:
|
||||
temp[user] = 0
|
||||
|
||||
temp[user] += 1
|
||||
|
||||
stats = [
|
||||
user[0]
|
||||
for user in list(
|
||||
sorted(list(temp.items()), key=lambda x: x[1], reverse=True)
|
||||
)
|
||||
]
|
||||
|
||||
top_users = []
|
||||
for u in stats:
|
||||
if len(top_users) >= quantity:
|
||||
break
|
||||
|
||||
if not await self.check_admin(message.peer_id, u):
|
||||
top_users += [(await self._client.get_entity(u), u)]
|
||||
|
||||
top_users_formatted = [
|
||||
self.strings("user").format(
|
||||
i + 1, utils.get_link(user[0]), get_display_name(user[0]), temp[user[1]]
|
||||
)
|
||||
for i, user in enumerate(top_users)
|
||||
]
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("active").format(
|
||||
"\n".join(top_users_formatted), round(time.perf_counter() - st, 2)
|
||||
),
|
||||
)
|
||||
231
hikariatama/ftg/alphabet.py
Normal file
231
hikariatama/ftg/alphabet.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/plasticine/344/hiragana-ma.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/alphabet.jpg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.4.0
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
to_ = [
|
||||
'<emoji document_id="5456128055414103034">😀</emoji>',
|
||||
'<emoji document_id="5456434780503548020">😀</emoji>',
|
||||
'<emoji document_id="5456256891548081456">😀</emoji>',
|
||||
'<emoji document_id="5454330491341643548">😀</emoji>',
|
||||
'<emoji document_id="5456670806136332319">😀</emoji>',
|
||||
'<emoji document_id="5456638048420767252">😀</emoji>',
|
||||
'<emoji document_id="5456546939279514692">😀</emoji>',
|
||||
'<emoji document_id="5454311039434759616">😀</emoji>',
|
||||
'<emoji document_id="5456509650373451167">😀</emoji>',
|
||||
'<emoji document_id="5456623527136336113">😀</emoji>',
|
||||
'<emoji document_id="5456505132067855523">😀</emoji>',
|
||||
'<emoji document_id="5456371910772269309">😀</emoji>',
|
||||
'<emoji document_id="5456140738452528837">😀</emoji>',
|
||||
'<emoji document_id="5453930556871941888">😀</emoji>',
|
||||
'<emoji document_id="5453937347215238994">😀</emoji>',
|
||||
'<emoji document_id="5456502344634079449">😀</emoji>',
|
||||
'<emoji document_id="5456402237536346480">😀</emoji>',
|
||||
'<emoji document_id="5456119517019119748">😀</emoji>',
|
||||
'<emoji document_id="5456490688092838489">😀</emoji>',
|
||||
'<emoji document_id="5456151875302726462">😀</emoji>',
|
||||
'<emoji document_id="5454053289857393595">😀</emoji>',
|
||||
'<emoji document_id="5454338918067479229">😀</emoji>',
|
||||
'<emoji document_id="5454359744363895908">😀</emoji>',
|
||||
'<emoji document_id="5454131191974207370">😀</emoji>',
|
||||
'<emoji document_id="5456480702293877170">😀</emoji>',
|
||||
'<emoji document_id="5454080962331680684">😀</emoji>',
|
||||
'<emoji document_id="5456518863078301519">😀</emoji>',
|
||||
'<emoji document_id="5454347190174490271">😀</emoji>',
|
||||
'<emoji document_id="5453878587767660028">😀</emoji>',
|
||||
'<emoji document_id="5454343273164316651">😀</emoji>',
|
||||
'<emoji document_id="5456437748325948254">😀</emoji>',
|
||||
'<emoji document_id="5454207307384626821">😀</emoji>',
|
||||
'<emoji document_id="5454275588774699252">😀</emoji>',
|
||||
'<emoji document_id="5456128055414103034">😀</emoji>',
|
||||
'<emoji document_id="5456434780503548020">😀</emoji>',
|
||||
'<emoji document_id="5456256891548081456">😀</emoji>',
|
||||
'<emoji document_id="5454330491341643548">😀</emoji>',
|
||||
'<emoji document_id="5456670806136332319">😀</emoji>',
|
||||
'<emoji document_id="5456638048420767252">😀</emoji>',
|
||||
'<emoji document_id="5456546939279514692">😀</emoji>',
|
||||
'<emoji document_id="5454311039434759616">😀</emoji>',
|
||||
'<emoji document_id="5456509650373451167">😀</emoji>',
|
||||
'<emoji document_id="5456623527136336113">😀</emoji>',
|
||||
'<emoji document_id="5456505132067855523">😀</emoji>',
|
||||
'<emoji document_id="5456371910772269309">😀</emoji>',
|
||||
'<emoji document_id="5456140738452528837">😀</emoji>',
|
||||
'<emoji document_id="5453930556871941888">😀</emoji>',
|
||||
'<emoji document_id="5453937347215238994">😀</emoji>',
|
||||
'<emoji document_id="5456502344634079449">😀</emoji>',
|
||||
'<emoji document_id="5456402237536346480">😀</emoji>',
|
||||
'<emoji document_id="5456119517019119748">😀</emoji>',
|
||||
'<emoji document_id="5456490688092838489">😀</emoji>',
|
||||
'<emoji document_id="5456151875302726462">😀</emoji>',
|
||||
'<emoji document_id="5454053289857393595">😀</emoji>',
|
||||
'<emoji document_id="5454338918067479229">😀</emoji>',
|
||||
'<emoji document_id="5454359744363895908">😀</emoji>',
|
||||
'<emoji document_id="5454131191974207370">😀</emoji>',
|
||||
'<emoji document_id="5456480702293877170">😀</emoji>',
|
||||
'<emoji document_id="5454080962331680684">😀</emoji>',
|
||||
'<emoji document_id="5456518863078301519">😀</emoji>',
|
||||
'<emoji document_id="5454347190174490271">😀</emoji>',
|
||||
'<emoji document_id="5453878587767660028">😀</emoji>',
|
||||
'<emoji document_id="5454343273164316651">😀</emoji>',
|
||||
'<emoji document_id="5456437748325948254">😀</emoji>',
|
||||
'<emoji document_id="5454207307384626821">😀</emoji>',
|
||||
'<emoji document_id="5454275588774699252">😀</emoji>',
|
||||
"<emoji document_id=5226734466315067436>🔤</emoji>",
|
||||
"<emoji document_id=5330453760395191684>🔤</emoji>",
|
||||
"<emoji document_id=5330523098347218561>🔤</emoji>",
|
||||
"<emoji document_id=5361630910816984823>🔤</emoji>",
|
||||
"<emoji document_id=5332587336939084375>🔤</emoji>",
|
||||
"<emoji document_id=5330369145244491360>🔤</emoji>",
|
||||
"<emoji document_id=5361861335812416268>🔤</emoji>",
|
||||
"<emoji document_id=5330133162561380231>🔤</emoji>",
|
||||
"<emoji document_id=5381808177547321132>🔤</emoji>",
|
||||
"<emoji document_id=5330383228442258084>🔤</emoji>",
|
||||
"<emoji document_id=5330026574357996347>🔤</emoji>",
|
||||
"<emoji document_id=5332396623211274002>🔤</emoji>",
|
||||
"<emoji document_id=5332321341024508571>🔤</emoji>",
|
||||
"<emoji document_id=5359736027080565026>🔤</emoji>",
|
||||
"<emoji document_id=5361583176550457135>🔤</emoji>",
|
||||
"<emoji document_id=5361909160273255840>🔤</emoji>",
|
||||
"<emoji document_id=5361948540828393629>🔤</emoji>",
|
||||
"<emoji document_id=5332514996804918116>🔤</emoji>",
|
||||
"<emoji document_id=5332807088940785741>🔤</emoji>",
|
||||
"<emoji document_id=5332558333024934589>🔤</emoji>",
|
||||
"<emoji document_id=5330069773139059849>🔤</emoji>",
|
||||
"<emoji document_id=5393117612416703921>🔤</emoji>",
|
||||
"<emoji document_id=5332308237079288987>🔤</emoji>",
|
||||
"<emoji document_id=5332575697577714724>🔤</emoji>",
|
||||
"<emoji document_id=5332648110726323166>🔤</emoji>",
|
||||
"<emoji document_id=5330309934825351007>🔤</emoji>",
|
||||
"<emoji document_id=5226734466315067436>🔤</emoji>",
|
||||
"<emoji document_id=5330453760395191684>🔤</emoji>",
|
||||
"<emoji document_id=5330523098347218561>🔤</emoji>",
|
||||
"<emoji document_id=5361630910816984823>🔤</emoji>",
|
||||
"<emoji document_id=5332587336939084375>🔤</emoji>",
|
||||
"<emoji document_id=5330369145244491360>🔤</emoji>",
|
||||
"<emoji document_id=5361861335812416268>🔤</emoji>",
|
||||
"<emoji document_id=5330133162561380231>🔤</emoji>",
|
||||
"<emoji document_id=5381808177547321132>🔤</emoji>",
|
||||
"<emoji document_id=5330383228442258084>🔤</emoji>",
|
||||
"<emoji document_id=5330026574357996347>🔤</emoji>",
|
||||
"<emoji document_id=5332396623211274002>🔤</emoji>",
|
||||
"<emoji document_id=5332321341024508571>🔤</emoji>",
|
||||
"<emoji document_id=5359736027080565026>🔤</emoji>",
|
||||
"<emoji document_id=5361583176550457135>🔤</emoji>",
|
||||
"<emoji document_id=5361909160273255840>🔤</emoji>",
|
||||
"<emoji document_id=5361948540828393629>🔤</emoji>",
|
||||
"<emoji document_id=5332514996804918116>🔤</emoji>",
|
||||
"<emoji document_id=5332807088940785741>🔤</emoji>",
|
||||
"<emoji document_id=5332558333024934589>🔤</emoji>",
|
||||
"<emoji document_id=5330069773139059849>🔤</emoji>",
|
||||
"<emoji document_id=5393117612416703921>🔤</emoji>",
|
||||
"<emoji document_id=5332308237079288987>🔤</emoji>",
|
||||
"<emoji document_id=5332575697577714724>🔤</emoji>",
|
||||
"<emoji document_id=5332648110726323166>🔤</emoji>",
|
||||
"<emoji document_id=5330309934825351007>🔤</emoji>",
|
||||
"<emoji document_id=5382322671679708881>1️⃣</emoji>",
|
||||
"<emoji document_id=5381990043642502553>2️⃣</emoji>",
|
||||
"<emoji document_id=5381879959335738545>3️⃣</emoji>",
|
||||
"<emoji document_id=5382054253403577563>4️⃣</emoji>",
|
||||
"<emoji document_id=5391197405553107640>5️⃣</emoji>",
|
||||
"<emoji document_id=5390966190283694453>6️⃣</emoji>",
|
||||
"<emoji document_id=5382132232829804982>7️⃣</emoji>",
|
||||
"<emoji document_id=5391038994274329680>8️⃣</emoji>",
|
||||
"<emoji document_id=5391234698754138414>9️⃣</emoji>",
|
||||
"<emoji document_id=5393480373944459905>0️⃣</emoji>",
|
||||
'<emoji document_id="6035271044858645168">📝</emoji>',
|
||||
'<emoji document_id="6034823612345617299">📝</emoji>',
|
||||
'<emoji document_id="6032617102962069967">⭕️</emoji>',
|
||||
'<emoji document_id="6032933036461395383">🛑</emoji>',
|
||||
'<emoji document_id="6033101201610903072">❗️</emoji>',
|
||||
'<emoji document_id="6033056731519519862">❓</emoji>',
|
||||
'<emoji document_id="6032769737509833594">📛</emoji>',
|
||||
]
|
||||
|
||||
from_ = (
|
||||
"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890().,!? "
|
||||
)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Alphabet(loader.Module):
|
||||
"""Replaces your text with custom emojis. Telegram Premium only"""
|
||||
|
||||
strings = {
|
||||
"name": "Alphabet",
|
||||
"no_text": "🚫 <b>Specify text to replace</b>",
|
||||
"premium_only": (
|
||||
"⭐️ This module is available only to Telegram Premium subscribers"
|
||||
),
|
||||
}
|
||||
strings_ru = {
|
||||
"no_text": "🚫 <b>Укажите текст для замены</b>",
|
||||
"premium_only": "⭐️ Этот модуль доступен только для Telegram Premium",
|
||||
"_cmd_doc_a": "Заменить текст на эмодзи",
|
||||
"_cls_doc": "Заменяет текст на кастомные эмодзи. Только для Telegram Premium",
|
||||
}
|
||||
strings_de = {
|
||||
"no_text": "🚫 <b>Gib den Text ein, der ersetzt werden soll</b>",
|
||||
"premium_only": (
|
||||
"⭐️ Dieses Modul ist nur für Telegram Premium-Abonnenten verfügbar"
|
||||
),
|
||||
"_cmd_doc_a": "Ersetze Text durch Emojis",
|
||||
"_cls_doc": (
|
||||
"Ersetzt Text durch benutzerdefinierte Emojis. Nur für Telegram Premium"
|
||||
),
|
||||
}
|
||||
strings_hi = {
|
||||
"no_text": "🚫 <b>बदलने के लिए पाठ निर्दिष्ट करें</b>",
|
||||
"premium_only": "⭐️ यह मॉड्यूल केवल Telegram Premium सदस्यों के लिए उपलब्ध है",
|
||||
"_cmd_doc_a": "पाठ को इमोजी के रूप में बदलें",
|
||||
"_cls_doc": "आपके पाठ को कस्टम इमोजी के रूप में बदलता है। केवल Telegram Premium के लिए",
|
||||
}
|
||||
strings_uz = {
|
||||
"no_text": "🚫 <b>Almashtirish uchun matn belgilang</b>",
|
||||
"premium_only": (
|
||||
"⭐️ Bu modul faqat Telegram Premium obuna bo'lganlar uchun mavjud"
|
||||
),
|
||||
"_cmd_doc_a": "Matnni emoji bilan almashtiring",
|
||||
"_cls_doc": (
|
||||
"Matnni sizning emojiingiz bilan almashtiradi. Faqat Telegram Premium uchun"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
if not (await self._client.get_me()).premium:
|
||||
raise loader.LoadError(self.strings("premium_only"))
|
||||
|
||||
self._from = from_
|
||||
self._to = to_
|
||||
|
||||
async def acmd(self, message: Message):
|
||||
"""<text> - Write text with emojis"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
if not args and not reply:
|
||||
await utils.answer(message, self.strings("no_text"))
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
"".join(
|
||||
to_[from_.index(char)] if char in from_ else char
|
||||
for char in args or reply.raw_text
|
||||
),
|
||||
)
|
||||
114
hikariatama/ftg/aniquotes.py
Normal file
114
hikariatama/ftg/aniquotes.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/aniquotes_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/aniquotes.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from random import choice
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AnimatedQuotesMod(loader.Module):
|
||||
"""Simple module to create animated stickers via bot"""
|
||||
|
||||
strings = {
|
||||
"name": "AnimatedQuotes",
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Provide a text to"
|
||||
" create sticker with</b>"
|
||||
),
|
||||
"processing": (
|
||||
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>Processing...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Укажи текст для"
|
||||
" создания стикера</b>"
|
||||
),
|
||||
"processing": (
|
||||
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>Обработка...</b>"
|
||||
),
|
||||
"_cmd_doc_aniq": "<text> - Создать анимированный стикер",
|
||||
"_cls_doc": "Простенький модуль, который создает анимированные стикеры",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bitte gib einen Text"
|
||||
" an, um einen Sticker zu erstellen</b>"
|
||||
),
|
||||
"processing": (
|
||||
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>Verarbeitung...</b>"
|
||||
),
|
||||
"_cmd_doc_aniq": "<text> - Erstelle einen animierten Sticker",
|
||||
"_cls_doc": "Einfaches Modul, das animierte Sticker erstellt",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>एक टेक्स्ट दें जिसके"
|
||||
" लिए एक स्टिकर बनाना है</b>"
|
||||
),
|
||||
"processing": (
|
||||
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>प्रोसेसिंग...</b>"
|
||||
),
|
||||
"_cmd_doc_aniq": "<text> - एक एनीमेटेड स्टिकर बनाएं",
|
||||
"_cls_doc": "एक एनीमेटेड स्टिकर बनाने के लिए एक सरल मॉड्यूल",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Sticker yaratish"
|
||||
" uchun"
|
||||
" matn kiriting</b>"
|
||||
),
|
||||
"processing": (
|
||||
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>Islenmoqda...</b>"
|
||||
),
|
||||
"_cmd_doc_aniq": "<matn> - Animatsiya stikerni yaratish",
|
||||
"_cls_doc": "Animatsiya stikerni yaratish uchun oddiy modul",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bir metin girin</b>"
|
||||
),
|
||||
"processing": (
|
||||
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>İşleniyor...</b>"
|
||||
),
|
||||
"_cmd_doc_aniq": "<text> - Animasyonlu alıntı oluştur",
|
||||
"_cls_doc": "Animasyonlu stiker oluşturmak için basit bir modül",
|
||||
}
|
||||
|
||||
async def aniqcmd(self, message: Message):
|
||||
"""<text> - Create animated quote"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_text"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("processing"))
|
||||
|
||||
try:
|
||||
query = await self._client.inline_query("@QuotAfBot", args)
|
||||
await message.respond(file=choice(query).document)
|
||||
except Exception as e:
|
||||
await utils.answer(message, str(e))
|
||||
return
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
211
hikariatama/ftg/anisearch.py
Normal file
211
hikariatama/ftg/anisearch.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/anisearch_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/anisearch.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AniSearchMod(loader.Module):
|
||||
"""Searches for anime exact moment by only frame screenshot"""
|
||||
|
||||
strings = {
|
||||
"name": "AniSearch",
|
||||
"404": (
|
||||
"<emoji document_id=5204174553592372633>😢</emoji> <b>I don't know which"
|
||||
" anime it is...</b>"
|
||||
),
|
||||
"searching": (
|
||||
"<emoji document_id=5424885441100782420>👀</emoji> <b>Let me take a"
|
||||
" look...</b>"
|
||||
),
|
||||
"result": (
|
||||
"<emoji document_id=5312017978349331498>😎</emoji> <b>I think, it is..."
|
||||
" </b><code>{}</code><b> episode </b><code>{}</code><b> at</b>"
|
||||
" <code>{}</code>\n<b>I'm sure at {}%</b>"
|
||||
),
|
||||
"media_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Media not found</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"404": (
|
||||
"<emoji document_id=5204174553592372633>😢</emoji> <b>Я не знаю, что это за"
|
||||
" аниме...</b>"
|
||||
),
|
||||
"searching": (
|
||||
"<emoji document_id=5424885441100782420>👀</emoji> <b>Дай глянуть...</b>"
|
||||
),
|
||||
"result": (
|
||||
"<emoji document_id=5312017978349331498>😎</emoji> <b>Я думаю, что это..."
|
||||
" </b><code>{}</code><b> эпизод </b><code>{}</code><b> на</b>"
|
||||
" <code>{}</code>\n<b>Я уверен на {}%</b>"
|
||||
),
|
||||
"media_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Медиа не найдено</b>"
|
||||
),
|
||||
"_cmd_doc_anisearch": "Поиск аниме по скриншоту",
|
||||
"_cls_doc": "Ищет конкретную серию и тайм-код аниме по скриншоту",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"404": (
|
||||
"<emoji document_id=5204174553592372633>😢</emoji> <b>Ich weiß nicht,"
|
||||
" welcher Anime das ist...</b>"
|
||||
),
|
||||
"searching": (
|
||||
"<emoji document_id=5424885441100782420>👀</emoji> <b>Lass mich mal"
|
||||
" schauen...</b>"
|
||||
),
|
||||
"result": (
|
||||
"<emoji document_id=5312017978349331498>😎</emoji> <b>Ich denke, es ist..."
|
||||
" </b><code>{}</code><b> Folge </b><code>{}</code><b> um</b>"
|
||||
" <code>{}</code>\n<b>Ich bin mir zu {}% sicher</b>"
|
||||
),
|
||||
"media_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Medien nicht"
|
||||
" gefunden</b>"
|
||||
),
|
||||
"_cmd_doc_anisearch": "Suche Anime nach einem Screenshot",
|
||||
"_cls_doc": (
|
||||
"Sucht nach einer bestimmten Folge und Zeitstempel eines Anime nach einem"
|
||||
" Screenshot"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"404": (
|
||||
"<emoji document_id=5204174553592372633>😢</emoji> <b>मैं नहीं जानता कि यह"
|
||||
" कौन सी एनीमे है...</b>"
|
||||
),
|
||||
"searching": (
|
||||
"<emoji document_id=5424885441100782420>👀</emoji> <b>मुझे देखने के लिए दें...</b>"
|
||||
),
|
||||
"result": (
|
||||
"<emoji document_id=5312017978349331498>😎</emoji> <b>मैं सोचता हूँ कि..."
|
||||
" </b><code>{}</code><b> एपिसोड </b><code>{}</code><b> में</b>"
|
||||
" <code>{}</code>\n<b>मैं {}% सुनिश्चित हूँ</b>"
|
||||
),
|
||||
"media_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>मीडिया नहीं मिला</b>"
|
||||
),
|
||||
"_cmd_doc_anisearch": "एक स्क्रीनशॉट के लिए एनीमे खोजें",
|
||||
"_cls_doc": "एक स्क्रीनशॉट के लिए एक विशिष्ट एपिसोड और समय-स्टैंप खोजता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"404": (
|
||||
"<emoji document_id=5204174553592372633>😢</emoji> <b>Bu anime haqida"
|
||||
" gapirishim mumkin emas...</b>"
|
||||
),
|
||||
"searching": (
|
||||
"<emoji document_id=5424885441100782420>👀</emoji> <b>Qarashimni ko'rish"
|
||||
" uchun beraman...</b>"
|
||||
),
|
||||
"result": (
|
||||
"<emoji document_id=5312017978349331498>😎</emoji> <b>Aytaman..."
|
||||
" </b><code>{}</code><b> </b><code>{}</code><b> da</b>"
|
||||
" <code>{}</code>\n<b>Menga %{} hisoblanadi</b>"
|
||||
),
|
||||
"media_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Media topilmadi</b>"
|
||||
),
|
||||
"_cmd_doc_anisearch": "Ekran rasmini ishlatib anime qidirish",
|
||||
"_cls_doc": (
|
||||
"Ekran rasmini ishlatib biror animening biror qismi va vaqtini qidiradi"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"404": (
|
||||
"<emoji document_id=5204174553592372633>😢</emoji> <b>Bu anime hakkında"
|
||||
" bilgim yok...</b>"
|
||||
),
|
||||
"searching": (
|
||||
"<emoji document_id=5424885441100782420>👀</emoji> <b>Göz atayım...</b>"
|
||||
),
|
||||
"result": (
|
||||
"<emoji document_id=5312017978349331498>😎</emoji> <b>Sanırım..."
|
||||
" </b><code>{}</code><b> </b><code>{}</code><b> da</b>"
|
||||
" <code>{}</code>\n<b>%{} ihtimalle</b>"
|
||||
),
|
||||
"media_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Medya bulunamadı</b>"
|
||||
),
|
||||
"_cmd_doc_anisearch": "Bir ekran görüntüsü kullanarak anime arama",
|
||||
"_cls_doc": (
|
||||
"Bir ekran görüntüsü kullanarak bir anime serisinin ve zaman damgasının bir"
|
||||
" kısmını arar"
|
||||
),
|
||||
}
|
||||
|
||||
async def anisearchcmd(self, message: Message):
|
||||
"""Search anime by frame"""
|
||||
reply = await message.get_reply_message()
|
||||
if not message.media and (not reply or not reply.media):
|
||||
await utils.answer(message, self.strings("media_not_found"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("searching"))
|
||||
search_result = requests.post(
|
||||
"https://api.trace.moe/search",
|
||||
files={
|
||||
"image": await self._client.download_media(
|
||||
message if message.media else reply,
|
||||
bytes,
|
||||
)
|
||||
},
|
||||
).json()
|
||||
|
||||
if not search_result or not search_result.get("result", False):
|
||||
await utils.answer(message, self.strings("404"))
|
||||
return
|
||||
|
||||
anilist = requests.post(
|
||||
"https://graphql.anilist.co",
|
||||
json={
|
||||
"query": (
|
||||
"query($id: Int) {Media(id: $id, type: ANIME) {id idMal title"
|
||||
" {native romaji english } synonyms isAdult } }"
|
||||
),
|
||||
"variables": {"id": search_result["result"][0]["anilist"]},
|
||||
},
|
||||
).json()
|
||||
|
||||
title = (
|
||||
anilist["data"]["Media"]["title"]["english"]
|
||||
or anilist["data"]["Media"]["title"]["romaji"]
|
||||
or anilist["data"]["Media"]["title"]["native"]
|
||||
)
|
||||
|
||||
if not title:
|
||||
await utils.answer(message, self.strings("media_not_found"))
|
||||
return
|
||||
|
||||
pos = search_result["result"][0]["from"]
|
||||
episode = search_result["result"][0]["episode"]
|
||||
conf = search_result["result"][0]["similarity"]
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("result").format(
|
||||
title,
|
||||
episode,
|
||||
f"{round(pos // 60)}:{round(pos % 60)}",
|
||||
round(conf * 100, 2),
|
||||
),
|
||||
)
|
||||
476
hikariatama/ftg/anything.py
Normal file
476
hikariatama/ftg/anything.py
Normal file
@@ -0,0 +1,476 @@
|
||||
__version__ = (1, 0, 7)
|
||||
|
||||
# ©️ Dan Gazizullin, 2021-2023
|
||||
# This file is a part of Hikka Userbot
|
||||
# Code is licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
|
||||
# 🌐 https://github.com/hikariatama/Hikka
|
||||
# 🔑 https://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||
# + attribution
|
||||
# + non-commercial
|
||||
# + no-derivatives
|
||||
|
||||
# You CANNOT edit this file without direct permission from the author.
|
||||
# You can redistribute this file without any changes.
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/512/artificial-intelligence.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/anything.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.6.2
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
|
||||
import requests
|
||||
from hikkatl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Anything(loader.Module):
|
||||
"""Draws ANYTHING using artificial intelligence. No API key required. 18+ only."""
|
||||
|
||||
strings = {
|
||||
"name": "Anything",
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Arguments are"
|
||||
" required</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Failed to generate"
|
||||
" image</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Drawing {}"
|
||||
" picture(-s)"
|
||||
" using </b><code>{}</code><b>...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Image"
|
||||
" generated!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Model: {model}, CFG: {cfg}, Steps: {steps}, Prompt:"
|
||||
" {prompt}, Negative: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Требуются"
|
||||
" аргументы</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Не удалось"
|
||||
" сгенерировать изображение</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Рисую {}"
|
||||
" картинку(-ок) с помощью </b><code>{}</code><b>...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Изображение"
|
||||
" сгенерировано!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Модель: {model}, CFG: {cfg}, Шаги: {steps}, Запрос:"
|
||||
" {prompt}, Отрицательный запрос: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_es = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Se requieren"
|
||||
" argumentos</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>No se"
|
||||
" pudo generar la imagen</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Dibujando {}"
|
||||
" imagen(-es) usando </b><code>{}</code><b>...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>¡Imagen"
|
||||
" generada!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Modelo: {model}, CFG: {cfg}, Pasos: {steps}, Solicitud:"
|
||||
" {prompt}, Solicitud negativa: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_it = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Sono richiesti"
|
||||
" argomenti</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Impossibile"
|
||||
" generare l'immagine</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Disegno {}"
|
||||
" immagine(-i) usando </b><code>{}</code><b>...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Immagine"
|
||||
" generata!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Modello: {model}, CFG: {cfg}, Passi: {steps}, Richiesta:"
|
||||
" {prompt}, Richiesta negativa: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_fr = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Des arguments"
|
||||
" sont requis</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Impossible"
|
||||
" de générer l'image</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Dessin {}"
|
||||
" image(-s) en utilisant </b><code>{}</code><b>...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Image générée!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Modèle: {model}, CFG: {cfg}, Étapes: {steps}, Demande:"
|
||||
" {prompt}, Demande négative: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Argumente"
|
||||
" werden benötigt</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Konnte das"
|
||||
" Bild nicht generieren</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Zeichne {}"
|
||||
" Bild(-er) mit </b><code>{}</code><b>...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Bild generiert!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Modell: {model}, CFG: {cfg}, Schritte: {steps}, Anfrage:"
|
||||
" {prompt}, Negative Anfrage: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Gerekli"
|
||||
" argümanlar</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Görüntü"
|
||||
" oluşturulamadı</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Çiziyorum {}"
|
||||
" görüntü(-leri) </b><code>{}</code><b> kullanarak...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Görüntü"
|
||||
" oluşturuldu!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Model: {model}, CFG: {cfg}, Adımlar: {steps}, Talep:"
|
||||
" {prompt}, Talep reddedildi: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"args": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Talab qilinadi</b>"
|
||||
),
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Rasm"
|
||||
" yaratib bo'lmadi</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Rasm(-lar)"
|
||||
" chizilmoqda </b><code>{}</code><b> orqali...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Rasm yaratildi!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Model: {model}, CFG: {cfg}, Qadam: {steps}, Talab:"
|
||||
" {prompt}, Talab qilinmadi: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_kk = {
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Талап келеді</b>",
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Сурет"
|
||||
" жасап болмады</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Сурет(-тер)"
|
||||
" жасалуда </b><code>{}</code><b> арқылы...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Сурет жасалды!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Модель: {model}, CFG: {cfg}, Қадам: {steps}, Сұрақ:"
|
||||
" {prompt}, Сұрақ жасалмады: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tt = {
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Талап киләнә</b>",
|
||||
"fail": (
|
||||
"<emoji document_id=5210952531676504517>❌</emoji> <b>Рәсем ясап"
|
||||
" булмады</b>"
|
||||
),
|
||||
"drawing": (
|
||||
"<emoji document_id=5431456208487716895>🎨</emoji> <b>Рәсем(-ләр)"
|
||||
" ясалуда </b><code>{}</code><b> арҗылы...</b>"
|
||||
),
|
||||
"ready": (
|
||||
"<emoji document_id=5398001711786762757>✅</emoji> <b>Рәсем ясалды!</b>{}"
|
||||
),
|
||||
"debug": (
|
||||
"\n\n<i>Модель: {model}, CFG: {cfg}, Адым: {steps}, Сорау:"
|
||||
" {prompt}, Сорау ясалмады: {negative}, {took:.2f}s</i>"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"model",
|
||||
"auto",
|
||||
"Model to use - For anime characters use Anything-4.5",
|
||||
validator=loader.validators.Choice(["auto"]),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"steps",
|
||||
30,
|
||||
"Steps - The higher the number, the more the image will be detailed",
|
||||
validator=loader.validators.Integer(minimum=1, maximum=50),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"cfg",
|
||||
6,
|
||||
(
|
||||
"CFG Scale Factor - The higher the number, the more the image will"
|
||||
" follow the prompt"
|
||||
),
|
||||
validator=loader.validators.Integer(minimum=1, maximum=20),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"sampler",
|
||||
"Euler a",
|
||||
"Sampler used",
|
||||
validator=loader.validators.Choice(
|
||||
["Euler", "Euler a", "Heun", "DPM++ 2M Karras"]
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"debug",
|
||||
False,
|
||||
"Debug mode",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"default_negative",
|
||||
(
|
||||
"(bad_prompt:0.8), multiple persons, multiple views, extra hands,"
|
||||
" ugly, lowres, bad quality, blurry, disfigured, extra limbs,"
|
||||
" missing limbs, deep fried, cheap art, missing fingers, out of"
|
||||
" frame, cropped, bad art, face hidden, text, speech bubble,"
|
||||
" stretched, bad hands, error, extra digit, fewer digits, worst"
|
||||
" quality, low quality, normal quality, mutated, mutation,"
|
||||
" deformed, severed, dismembered, corpse, pubic, poorly drawn,"
|
||||
" (((deformed hands))), (((more than two hands))), (((deformed"
|
||||
" body))), ((((mutant))))"
|
||||
),
|
||||
"Default negative prompt",
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"default_quantity",
|
||||
1,
|
||||
"Default quantity of images to generate",
|
||||
validator=loader.validators.Integer(minimum=1, maximum=10),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self._models = json.loads(
|
||||
re.search(
|
||||
r"VUE_APP_AI_MODELS:'(.*?)',VUE_APP_STATS_STREAMS",
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://app.prodia.com"
|
||||
+ re.search(
|
||||
(
|
||||
r'defer="defer"'
|
||||
r' src="(\/js\/app\.[^.]*?\.js)"><\/script><link'
|
||||
r' href="\/css'
|
||||
),
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://app.prodia.com/",
|
||||
)
|
||||
).text,
|
||||
)[1],
|
||||
)
|
||||
).text,
|
||||
)[1].replace("\\'", "'")
|
||||
)
|
||||
|
||||
self.config._config["model"].validator = loader.validators.Choice(
|
||||
["auto"] + list(self._models.values())
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def draw(self, message: Message):
|
||||
"""<prompt> [-n <int>] [-comp] [-neg <str>]"""
|
||||
if not (args := utils.get_args_raw(message)):
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
negative = ""
|
||||
quantity = self.config["default_quantity"]
|
||||
|
||||
if "-n " in args:
|
||||
quantity = int(args.split("-n ")[1].split()[0])
|
||||
args = args.replace(f"-n {quantity}", "")
|
||||
|
||||
if "-neg" in args:
|
||||
args, negative = args.split("-neg")
|
||||
|
||||
comp = False
|
||||
if "-comp" in args:
|
||||
args = args.replace("-comp", "")
|
||||
comp = True
|
||||
|
||||
args, negative = args.strip(), negative.strip()
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
model = (
|
||||
next(
|
||||
model
|
||||
for model in self._models.values()
|
||||
if model.startswith("anything-v4.5")
|
||||
)
|
||||
if self.config["model"] == "auto"
|
||||
else self.config["model"]
|
||||
)
|
||||
message = await utils.answer(
|
||||
message,
|
||||
self.strings("drawing").format(
|
||||
len(self._models) if comp else quantity,
|
||||
utils.escape_html("many" if comp else model),
|
||||
),
|
||||
)
|
||||
|
||||
images = []
|
||||
|
||||
negative = negative or self.config["default_negative"]
|
||||
m = list(self._models.values())
|
||||
|
||||
start = time.time()
|
||||
|
||||
async def create_job():
|
||||
return (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://arran.fly.dev/generate",
|
||||
params={
|
||||
"prompt": args,
|
||||
"model": m.pop() if comp else model,
|
||||
"negative_prompt": negative,
|
||||
"steps": self.config["steps"],
|
||||
"cfg": self.config["cfg"],
|
||||
"seed": random.randint(0, 1000000),
|
||||
"sampler": self.config["sampler"],
|
||||
"aspect_ratio": "square",
|
||||
},
|
||||
)
|
||||
).json()["job"]
|
||||
|
||||
async def create_job_ex():
|
||||
try:
|
||||
return await create_job()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
await asyncio.sleep(5)
|
||||
return await create_job_ex()
|
||||
|
||||
for _ in range(len(m) if comp else quantity):
|
||||
job = await create_job_ex()
|
||||
|
||||
q = 0
|
||||
while (
|
||||
status := (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://arran.fly.dev/job/{job}",
|
||||
)
|
||||
).json()["status"]
|
||||
) != "succeeded" and q < 20:
|
||||
await asyncio.sleep(5)
|
||||
q += 1
|
||||
|
||||
if status != "succeeded":
|
||||
await utils.answer(message, self.strings("fail"))
|
||||
return
|
||||
|
||||
image = io.BytesIO(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://images.prodia.xyz/{job}.png?download=1",
|
||||
stream=True,
|
||||
)
|
||||
).content
|
||||
)
|
||||
image.name = "hahahahahhaah.png"
|
||||
|
||||
images.append(image)
|
||||
await asyncio.sleep(10)
|
||||
|
||||
await utils.answer_file(
|
||||
message,
|
||||
images,
|
||||
self.strings("ready").format(
|
||||
self.strings("debug").format(
|
||||
model=utils.escape_html("many" if comp else model),
|
||||
cfg=self.config["cfg"],
|
||||
steps=self.config["steps"],
|
||||
prompt=utils.escape_html(args),
|
||||
negative=utils.escape_html(negative),
|
||||
took=time.time() - start,
|
||||
)
|
||||
if self.config["debug"]
|
||||
else ""
|
||||
),
|
||||
)
|
||||
299
hikariatama/ftg/artai.py
Normal file
299
hikariatama/ftg/artai.py
Normal file
@@ -0,0 +1,299 @@
|
||||
__version__ = (1, 1, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
#
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/artai_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/artai.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import io
|
||||
import random
|
||||
from typing import Union
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
|
||||
def base(bytes_: bytes) -> str:
|
||||
return f"data:image/jpeg;base64,{base64.b64encode(bytes_).decode()}"
|
||||
|
||||
|
||||
async def poll(queue_hash: str) -> str:
|
||||
for _ in range(50):
|
||||
answ = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://akhaliq-jojogan.hf.space/api/queue/status/",
|
||||
params={"hash": queue_hash},
|
||||
)
|
||||
|
||||
if answ.json()["status"] == "COMPLETE":
|
||||
return answ.json()["data"]["data"][0]
|
||||
elif answ.json()["status"] != "PENDING":
|
||||
return False
|
||||
|
||||
await asyncio.sleep(3)
|
||||
|
||||
|
||||
async def animefy(image: bytes, engine: str) -> Union[bytes, bool]:
|
||||
file = io.BytesIO(
|
||||
base64.decodebytes(
|
||||
(
|
||||
await poll(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://akhaliq-jojogan.hf.space/api/queue/push/",
|
||||
headers={
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/json",
|
||||
"origin": "https://akhaliq-jojogan.hf.space",
|
||||
"pragma": "no-cache",
|
||||
"referer": (
|
||||
"https://akhaliq-jojogan.hf.space/?__theme=light"
|
||||
),
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"sec-gpc": "1",
|
||||
"user-agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko)"
|
||||
" Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
},
|
||||
json={
|
||||
"action": "predict",
|
||||
"data": [base(image), engine],
|
||||
"fn_index": 0,
|
||||
"session_hash": utils.rand(11).lower(),
|
||||
},
|
||||
)
|
||||
).json()["hash"]
|
||||
)
|
||||
)
|
||||
.split("base64,")[1]
|
||||
.encode()
|
||||
)
|
||||
)
|
||||
file.name = "photo.jpg"
|
||||
return file
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ArtAIMod(loader.Module):
|
||||
"""Ultimate module, which uses AI to draw ppl"""
|
||||
|
||||
paint = "<emoji document_id=5431456208487716895>🎨</emoji>"
|
||||
|
||||
strings = {
|
||||
"name": "ArtAI",
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Reply to a photo"
|
||||
" required</b>"
|
||||
),
|
||||
"pick_engine": "👩🎤 <b>Please, choose engine to process this photo</b>",
|
||||
"uploading": "☁️ <b>Uploading...</b>",
|
||||
"success": (
|
||||
f"{paint} <b>This is nice</b>",
|
||||
f"{paint} <b>Shee-e-esh</b>",
|
||||
f"{paint} <b>I'm the artist, this is my POV!</b>",
|
||||
f"{paint} <b>Do not blame me, I'm the artist</b>",
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Ответь на фото</b>"
|
||||
),
|
||||
"pick_engine": "👩🎤 <b>Выбери движок для обработки этой фотографии</b>",
|
||||
"uploading": "☁️ <b>Загружаю...</b>",
|
||||
"success": (
|
||||
f"{paint} <b>Это классно</b>",
|
||||
f"{paint} <b>Shee-e-esh</b>",
|
||||
f"{paint} <b>Я художник, я так вижу!</b>",
|
||||
f"{paint} <b>Не обвиняй меня, я художник</b>",
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Antworte auf ein"
|
||||
" Foto</b>"
|
||||
),
|
||||
"pick_engine": "👩🎤 <b>Wähle einen Motor, um dieses Foto zu verarbeiten</b>",
|
||||
"uploading": "☁️ <b>Hochladen...</b>",
|
||||
"success": (
|
||||
f"{paint} <b>Das ist schön</b>",
|
||||
f"{paint} <b>Shee-e-esh</b>",
|
||||
f"{paint} <b>Ich bin der Künstler, das ist meine Sicht!</b>",
|
||||
f"{paint} <b>Verurteile mich nicht, ich bin der Künstler</b>",
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Fotoya javob"
|
||||
" bering</b>"
|
||||
),
|
||||
"pick_engine": "👩🎤 <b>Ushbu rasmni ishlash uchun injinani tanlang</b>",
|
||||
"uploading": "☁️ <b>Yuklanmoqda...</b>",
|
||||
"success": (
|
||||
f"{paint} <b>Bu yaxshi</b>",
|
||||
f"{paint} <b>Shee-e-esh</b>",
|
||||
),
|
||||
}
|
||||
|
||||
strings_es = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Responde a una"
|
||||
" foto</b>"
|
||||
),
|
||||
"pick_engine": "👩🎤 <b>Elige un motor para procesar esta foto</b>",
|
||||
"uploading": "☁️ <b>Subiendo...</b>",
|
||||
"success": (
|
||||
f"{paint} <b>Esto es bueno</b>",
|
||||
f"{paint} <b>Shee-e-esh</b>",
|
||||
f"{paint} <b>Soy el artista, esta es mi visión</b>",
|
||||
f"{paint} <b>No me culpes, soy el artista</b>",
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bir fotoğrafa yanıt"
|
||||
" verin</b>"
|
||||
),
|
||||
"pick_engine": "👩🎤 <b>Bu fotoğrafı işlemek için bir motor seçin</b>",
|
||||
"uploading": "☁️ <b>Yükleniyor...</b>",
|
||||
"success": (
|
||||
f"{paint} <b>Bu güzel</b>",
|
||||
f"{paint} <b>Shee-e-esh</b>",
|
||||
f"{paint} <b>Ben sanatçıyım, bu benim bakış açım!</b>",
|
||||
f"{paint} <b>Sana suçlamayın, ben sanatçıyım</b>",
|
||||
),
|
||||
}
|
||||
|
||||
async def artaicmd(self, message: Message):
|
||||
"""<photo> - Create anime art from photo"""
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if not reply or not reply.photo:
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("pick_engine"),
|
||||
reply_markup=self._gen_markup(reply),
|
||||
)
|
||||
|
||||
async def _process_engine(
|
||||
self,
|
||||
call: InlineCall,
|
||||
engine: str,
|
||||
chat_id: int,
|
||||
message_id: int,
|
||||
):
|
||||
await call.edit(self.strings("uploading"))
|
||||
media = await self._client.download_media(
|
||||
(
|
||||
await self._client.get_messages(
|
||||
entity=chat_id,
|
||||
ids=[message_id],
|
||||
limit=1,
|
||||
)
|
||||
)[0],
|
||||
bytes,
|
||||
)
|
||||
|
||||
if engine != "All":
|
||||
await self._client.send_file(
|
||||
chat_id,
|
||||
file=await animefy(media, engine),
|
||||
reply_to=message_id,
|
||||
caption=random.choice(self.strings("success")),
|
||||
)
|
||||
await call.delete()
|
||||
return
|
||||
else:
|
||||
res = []
|
||||
|
||||
statuses = {
|
||||
"JoJo": "⬜️",
|
||||
"Disney": "⬜️",
|
||||
"Jinx": "⬜️",
|
||||
"Caitlyn": "⬜️",
|
||||
"Yasuho": "⬜️",
|
||||
"Arcane Multi": "⬜️",
|
||||
"Art": "⬜️",
|
||||
"Spider-Verse": "⬜️",
|
||||
"Sketch": "⬜️",
|
||||
}
|
||||
|
||||
for engine in statuses:
|
||||
suffix = (
|
||||
lambda: (
|
||||
f"<i>Processing image...</i>\n\n{''.join(statuses.values())}"
|
||||
)
|
||||
)
|
||||
res += [await animefy(media, engine)]
|
||||
statuses[engine] = "🟩"
|
||||
await call.edit(suffix())
|
||||
|
||||
await call.delete()
|
||||
try:
|
||||
await self._client.send_file(
|
||||
chat_id,
|
||||
file=res,
|
||||
reply_to=message_id,
|
||||
caption=random.choice(self.strings("success")),
|
||||
)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def _gen_markup(self, reply: Message) -> list:
|
||||
engines = [
|
||||
"👊 JoJo",
|
||||
"👸 Disney",
|
||||
"🥷 Jinx",
|
||||
"😥 Caitlyn",
|
||||
"👩🎤 Yasuho",
|
||||
"👨🎤 Arcane Multi",
|
||||
"🎨 Art",
|
||||
"🕸 Spider-Verse",
|
||||
"✒️ Sketch",
|
||||
"🎁 All",
|
||||
]
|
||||
|
||||
return utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": engine,
|
||||
"callback": self._process_engine,
|
||||
"args": (
|
||||
engine.split(maxsplit=1)[1],
|
||||
utils.get_chat_id(reply),
|
||||
reply.id,
|
||||
),
|
||||
}
|
||||
for engine in engines
|
||||
],
|
||||
2,
|
||||
)
|
||||
323
hikariatama/ftg/backuper.py
Normal file
323
hikariatama/ftg/backuper.py
Normal file
@@ -0,0 +1,323 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/backuper_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/backuper.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DATA_DIR = (
|
||||
os.path.normpath(os.path.join(utils.get_base_dir(), ".."))
|
||||
if "OKTETO" not in os.environ and "DOCKER" not in os.environ
|
||||
else "/data"
|
||||
)
|
||||
|
||||
LOADED_MODULES_DIR = os.path.join(DATA_DIR, "loaded_modules")
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BackuperMod(loader.Module):
|
||||
"""Create the backup of all modules or the whole database"""
|
||||
|
||||
strings = {
|
||||
"name": "Backuper",
|
||||
"backup_caption": (
|
||||
"<emoji document_id=5469718869536940860>👆</emoji> <b>This is your database"
|
||||
" backup. Do not give it to anyone, it contains personal info.</b>"
|
||||
),
|
||||
"reply_to_file": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Reply to .json or"
|
||||
" .zip"
|
||||
" file</b>"
|
||||
),
|
||||
"db_restored": (
|
||||
"<emoji document_id=5774134533590880843>🔄</emoji> <b>Database updated,"
|
||||
" restarting...</b>"
|
||||
),
|
||||
"modules_backup": (
|
||||
"<emoji document_id=6334332637041134172>🗃</emoji> <b>Backup mods ({})</b>"
|
||||
),
|
||||
"mods_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Mods restored,"
|
||||
" restarting</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"backup_caption": (
|
||||
"<emoji document_id=5469718869536940860>👆</emoji> <b>Это - бекап базы"
|
||||
" данных. Никому его не передавай, он содержит личную информацию.</b>"
|
||||
),
|
||||
"reply_to_file": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Ответь на .json или"
|
||||
" .zip файл</b>"
|
||||
),
|
||||
"db_restored": (
|
||||
"<emoji document_id=5774134533590880843>🔄</emoji> <b>База обновлена,"
|
||||
" перезагружаюсь...</b>"
|
||||
),
|
||||
"modules_backup": (
|
||||
"<emoji document_id=6334332637041134172>🗃</emoji> <b>Бекап модулей ({})</b>"
|
||||
),
|
||||
"mods_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Модули восстановлены,"
|
||||
" перезагружаюсь</b>"
|
||||
),
|
||||
"_cmd_doc_backupdb": "Создать бекап базы данных [будет отправлен в Избранное]",
|
||||
"_cmd_doc_restoredb": "Восстановить базу данных из файла",
|
||||
"_cmd_doc_backupmods": "Создать бекап модулей",
|
||||
"_cmd_doc_restoremods": "<reply to file> - Восстановить модули из файла",
|
||||
"_cls_doc": "Создает резервные копии",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"backup_caption": (
|
||||
"<emoji document_id=5469718869536940860>👆</emoji> <b>Dies ist ein"
|
||||
" Datenbank-Backup. Gib es niemandem, es enthält persönliche"
|
||||
" Informationen.</b>"
|
||||
),
|
||||
"reply_to_file": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Antworte auf .json"
|
||||
" oder .zip Datei</b>"
|
||||
),
|
||||
"db_restored": (
|
||||
"<emoji document_id=5774134533590880843>🔄</emoji> <b>Datenbank"
|
||||
" aktualisiert, starte neu...</b>"
|
||||
),
|
||||
"modules_backup": (
|
||||
"<emoji document_id=6334332637041134172>🗃</emoji> <b>Backup-Module ({})</b>"
|
||||
),
|
||||
"mods_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Module"
|
||||
" wiederhergestellt, starte neu</b>"
|
||||
),
|
||||
"_cmd_doc_backupdb": (
|
||||
"Datenbank-Backup erstellen [wird in den Favoriten gesendet]"
|
||||
),
|
||||
"_cmd_doc_restoredb": "Datenbank aus Datei wiederherstellen",
|
||||
"_cmd_doc_backupmods": "Backup-Module erstellen",
|
||||
"_cmd_doc_restoremods": "<reply to file> - Module aus Datei wiederherstellen",
|
||||
"_cls_doc": "Erstellt Sicherungskopien",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"backup_caption": (
|
||||
"<emoji document_id=5469718869536940860>👆</emoji> <b>यह आपका डेटाबेस बैकअप"
|
||||
" है। किसी को भी न दें, यह व्यक्तिगत जानकारी सामग्री में है।</b>"
|
||||
),
|
||||
"reply_to_file": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>.json या .zip फ़ाइल पर"
|
||||
" जवाब दें</b>"
|
||||
),
|
||||
"db_restored": (
|
||||
"<emoji document_id=5774134533590880843>🔄</emoji> <b>डेटाबेस अपडेट कर रहा"
|
||||
" है, पुनः आरंभ कर रहा है...</b>"
|
||||
),
|
||||
"modules_backup": (
|
||||
"<emoji document_id=6334332637041134172>🗃</emoji> <b>मॉड्स बैकअप ({})</b>"
|
||||
),
|
||||
"mods_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>मॉड्स पुनः स्थापित कर"
|
||||
" रहे हैं, पुनः आरंभ कर रहे हैं</b>"
|
||||
),
|
||||
"_cmd_doc_backupdb": "डेटाबेस बैकअप बनाएं [फ़ेवरिट्स में भेजा जाएगा]",
|
||||
"_cmd_doc_restoredb": "फ़ाइल से डेटाबेस पुनः स्थापित करें",
|
||||
"_cmd_doc_backupmods": "मॉड्स बैकअप बनाएं",
|
||||
"_cmd_doc_restoremods": "<reply to file> - फ़ाइल से मॉड्स पुनः स्थापित करें",
|
||||
"_cls_doc": "बैकअप बनाता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"backup_caption": (
|
||||
"<emoji document_id=5469718869536940860>👆</emoji> <b>Bu sizning"
|
||||
" ma'lumotlar"
|
||||
" bazangizning e'loni. Kimga ko'rsatmasangiz, shu shaxsiy ma'lumotlarni o'z"
|
||||
" ichiga oladi.</b>"
|
||||
),
|
||||
"reply_to_file": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>.json yoki .zip"
|
||||
" faylga"
|
||||
" javob bering</b>"
|
||||
),
|
||||
"db_restored": (
|
||||
"<emoji document_id=5774134533590880843>🔄</emoji> <b>Ma'lumotlar bazasi"
|
||||
" yangilandi, qayta ishga tushirilmoqda...</b>"
|
||||
),
|
||||
"modules_backup": (
|
||||
"<emoji document_id=6334332637041134172>🗃</emoji> <b>Modullar e'loni"
|
||||
" ({})</b>"
|
||||
),
|
||||
"mods_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Modullar qayta"
|
||||
" tiklandi, qayta ishga tushirilmoqda</b>"
|
||||
),
|
||||
"_cmd_doc_backupdb": (
|
||||
"Ma'lumotlar bazasini e'lon qiling [Favoritlarga jo'natiladi]"
|
||||
),
|
||||
"_cmd_doc_restoredb": "Fayldan ma'lumotlar bazasini tiklash",
|
||||
"_cmd_doc_backupmods": "Modullarni e'lon qiling",
|
||||
"_cmd_doc_restoremods": "<reply to file> - Fayldan modullarni tiklash",
|
||||
"_cls_doc": "E'lon qiladi",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"backup_caption": (
|
||||
"<emoji document_id=5469718869536940860>👆</emoji> <b>Bu veritabanı"
|
||||
" yedeğinizdir. Kimseye verin, kişisel bilgiler içerir.</b>"
|
||||
),
|
||||
"reply_to_file": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>.json veya .zip"
|
||||
" dosyasına yanıt verin</b>"
|
||||
),
|
||||
"db_restored": (
|
||||
"<emoji document_id=5774134533590880843>🔄</emoji> <b>Veritabanı"
|
||||
" güncellendi, yeniden başlatılıyor...</b>"
|
||||
),
|
||||
"modules_backup": (
|
||||
"<emoji document_id=6334332637041134172>🗃</emoji> <b>Modüller yedeği"
|
||||
" ({})</b>"
|
||||
),
|
||||
"mods_restored": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Modüller geri"
|
||||
" yüklendi, yeniden başlatılıyor</b>"
|
||||
),
|
||||
"_cmd_doc_backupdb": "Veritabanı yedeği oluştur [favorilere gönderilecek]",
|
||||
"_cmd_doc_restoredb": "Dosyadan veritabanını geri yükle",
|
||||
"_cmd_doc_backupmods": "Modüller yedeği oluştur",
|
||||
"_cmd_doc_restoremods": "<dosyaya yanıt ver> - Modülleri dosyadan geri yükle",
|
||||
"_cls_doc": "Yedek oluşturur",
|
||||
}
|
||||
|
||||
async def backupdbcmd(self, message: Message):
|
||||
"""Create database backup [will be sent in pm]"""
|
||||
txt = io.BytesIO(json.dumps(self._db).encode("utf-8"))
|
||||
txt.name = (
|
||||
f"db-backup-{getattr(datetime, 'datetime', datetime).now().strftime('%d-%m-%Y-%H-%M')}.json"
|
||||
)
|
||||
await self._client.send_file("me", txt, caption=self.strings("backup_caption"))
|
||||
await message.delete()
|
||||
|
||||
async def restoredbcmd(self, message: Message):
|
||||
"""Restore database from file"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.media:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("reply_to_file"),
|
||||
)
|
||||
return
|
||||
|
||||
file = await self._client.download_file(reply.media, bytes)
|
||||
decoded_text = json.loads(file.decode("utf-8"))
|
||||
|
||||
if not self._db.process_db_autofix(decoded_text):
|
||||
raise RuntimeError("Attempted to restore broken database")
|
||||
|
||||
self._db.clear()
|
||||
self._db.update(**decoded_text)
|
||||
self._db.save()
|
||||
await utils.answer(message, self.strings("db_restored"))
|
||||
await self.allmodules.commands["restart"](
|
||||
await message.respond(f"{self.get_prefix()}restart --force")
|
||||
)
|
||||
|
||||
async def backupmodscmd(self, message: Message):
|
||||
"""Create backup of mods"""
|
||||
mods_quantity = len(self.lookup("Loader").get("loaded_modules", {}))
|
||||
|
||||
result = io.BytesIO()
|
||||
result.name = "mods.zip"
|
||||
|
||||
db_mods = json.dumps(self.lookup("Loader").get("loaded_modules", {})).encode()
|
||||
|
||||
with zipfile.ZipFile(result, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
if "DYNO" not in os.environ:
|
||||
for root, _, files in os.walk(LOADED_MODULES_DIR):
|
||||
for file in files:
|
||||
with open(os.path.join(root, file), "rb") as f:
|
||||
zipf.writestr(file, f.read())
|
||||
mods_quantity += 1
|
||||
|
||||
zipf.writestr("db_mods.json", db_mods)
|
||||
|
||||
archive = io.BytesIO(result.getvalue())
|
||||
archive.name = (
|
||||
f"mods-{getattr(datetime, 'datetime', datetime).now().strftime('%d-%m-%Y-%H-%M')}.zip"
|
||||
)
|
||||
|
||||
await self._client.send_file(
|
||||
utils.get_chat_id(message),
|
||||
archive,
|
||||
caption=self.strings("modules_backup").format(mods_quantity),
|
||||
)
|
||||
await message.delete()
|
||||
|
||||
async def restoremodscmd(self, message: Message):
|
||||
"""<reply to file> - Restore mods from backup"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.media:
|
||||
await utils.answer(message, self.strings("reply_to_file"))
|
||||
return
|
||||
|
||||
file = await self._client.download_file(reply.media, bytes)
|
||||
try:
|
||||
decoded_text = json.loads(file.decode("utf-8"))
|
||||
except Exception:
|
||||
try:
|
||||
file = io.BytesIO(file)
|
||||
file.name = "mods.zip"
|
||||
|
||||
with zipfile.ZipFile(file) as zf:
|
||||
for name in zf.namelist():
|
||||
with zf.open(name, "r") as module:
|
||||
if name == "db_mods.json":
|
||||
db_mods = json.loads(module.read().decode())
|
||||
if isinstance(db_mods, dict) and all(
|
||||
isinstance(key, str) and isinstance(value, str)
|
||||
for key, value in db_mods.items()
|
||||
):
|
||||
self.lookup("Loader").set("loaded_modules", db_mods)
|
||||
|
||||
continue
|
||||
|
||||
if "DYNO" not in os.environ:
|
||||
with open(
|
||||
os.path.join(LOADED_MODULES_DIR, name), "wb"
|
||||
) as f:
|
||||
f.write(module.read())
|
||||
except Exception:
|
||||
logger.exception("Can't restore mods")
|
||||
await utils.answer(message, self.strings("reply_to_file"))
|
||||
return
|
||||
else:
|
||||
if not isinstance(decoded_text, dict) or not all(
|
||||
isinstance(key, str) and isinstance(value, str)
|
||||
for key, value in decoded_text.items()
|
||||
):
|
||||
raise RuntimeError("Invalid backup")
|
||||
|
||||
self.lookup("Loader").set("loaded_modules", decoded_text)
|
||||
|
||||
await utils.answer(message, self.strings("mods_restored"))
|
||||
await self.allmodules.commands["restart"](
|
||||
await message.respond(f"{self.get_prefix()}restart --force")
|
||||
)
|
||||
708
hikariatama/ftg/banstickers.py
Normal file
708
hikariatama/ftg/banstickers.py
Normal file
@@ -0,0 +1,708 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/344/cancel-2.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/banstickers.jpg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
# requires: aiofile Pillow
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import logging
|
||||
import math
|
||||
import operator
|
||||
import os
|
||||
import time
|
||||
from functools import reduce
|
||||
|
||||
from aiofile import async_open
|
||||
from PIL import Image, ImageChops
|
||||
from telethon.tl.functions.messages import GetStickerSetRequest
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BanStickers(loader.Module):
|
||||
"""Bans stickerpacks, stickers and gifs in chat"""
|
||||
|
||||
strings = {
|
||||
"name": "BanStickers",
|
||||
"args": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Reply to sticker is"
|
||||
" required</b>"
|
||||
),
|
||||
"sticker_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>This sticker is now"
|
||||
" banned in current chat</b>"
|
||||
),
|
||||
"pack_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>{} sticker(-s) from"
|
||||
" pack {} are now banned in current chat</b>"
|
||||
),
|
||||
"wait": (
|
||||
"<emoji document_id='5451732530048802485'>⏳</emoji> <b>Banning stickers"
|
||||
" from this pack in current chat...</b>"
|
||||
),
|
||||
"sticker_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>This sticker is not"
|
||||
" banned in current chat</b>"
|
||||
),
|
||||
"sticker_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>This sticker is no"
|
||||
" longer banned in current chat</b>"
|
||||
),
|
||||
"pack_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{} / {} sticker(-s)"
|
||||
" from pack {} are no longer banned in current chat</b>"
|
||||
),
|
||||
"pack_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>This pack is not"
|
||||
" banned in current chat</b>"
|
||||
),
|
||||
"no_restrictions": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>This chat has"
|
||||
" no restrictions</b>"
|
||||
),
|
||||
"all_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>All stickers are"
|
||||
" unbanned in current chat</b>"
|
||||
),
|
||||
"already_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Animated and video"
|
||||
" stickers are already restricted</b>"
|
||||
),
|
||||
"not_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Animated stickers"
|
||||
" are not restricted</b>"
|
||||
),
|
||||
"animations_restricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Animated and video"
|
||||
" stickers are now restricted in current chat</b>"
|
||||
),
|
||||
"animations_unrestricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Animated stickers"
|
||||
" are no longer restricted</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Нужен ответ на"
|
||||
" стикер</b>"
|
||||
),
|
||||
"sticker_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>Этот стикер теперь"
|
||||
" запрещен в текущем чате</b>"
|
||||
),
|
||||
"pack_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>{} стикер(-ов) из"
|
||||
" пака {} теперь запрещены в текущем чате</b>"
|
||||
),
|
||||
"wait": (
|
||||
"<emoji document_id='5451732530048802485'>⏳</emoji> <b>Запрещаю"
|
||||
" стикеры из этого пака в текущем чате...</b>"
|
||||
),
|
||||
"sticker_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Этот стикер не"
|
||||
" запрещен в текущем чате</b>"
|
||||
),
|
||||
"sticker_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Этот стикер больше"
|
||||
" не запрещен в текущем чате</b>"
|
||||
),
|
||||
"pack_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{} / {} стикер(-ов)"
|
||||
" из пака {} больше не запрещены в текущем чате</b>"
|
||||
),
|
||||
"pack_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Этот пак не запре"
|
||||
"щен в текущем чате</b>"
|
||||
),
|
||||
"no_restrictions": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>В текущем чате нет"
|
||||
" ограничений</b>"
|
||||
),
|
||||
"all_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Все стикеры"
|
||||
" разблокированы в текущем чате</b>"
|
||||
),
|
||||
"already_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Анимированные и"
|
||||
" видео стикеры уже запрещены</b>"
|
||||
),
|
||||
"not_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Анимированные"
|
||||
" стикеры не запрещены</b>"
|
||||
),
|
||||
"animations_restricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Анимированные и"
|
||||
" видео стикеры запрещены в текущем чате</b>"
|
||||
),
|
||||
"animations_unrestricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Анимированные"
|
||||
" стикеры больше не запрещены</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Antwort auf einen"
|
||||
" Sticker erforderlich</b>"
|
||||
),
|
||||
"sticker_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>Dieser Sticker ist"
|
||||
" nun im aktuellen Chat gesperrt</b>"
|
||||
),
|
||||
"pack_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>{} Sticker aus dem"
|
||||
" Pack {} sind nun im aktuellen Chat gesperrt</b>"
|
||||
),
|
||||
"wait": (
|
||||
"<emoji document_id='5451732530048802485'>⏳</emoji> <b>Sticker aus diesem"
|
||||
" Pack werden im aktuellen Chat gesperrt...</b>"
|
||||
),
|
||||
"sticker_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Dieser Sticker ist"
|
||||
" nicht im aktuellen Chat gesperrt</b>"
|
||||
),
|
||||
"sticker_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Dieser Sticker ist"
|
||||
" nun wieder im aktuellen Chat erlaubt</b>"
|
||||
),
|
||||
"pack_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{} / {} Sticker aus"
|
||||
" dem Pack {} sind nun wieder im aktuellen Chat erlaubt</b>"
|
||||
),
|
||||
"pack_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Dieses Pack ist im"
|
||||
" aktuellen Chat nicht gesperrt</b>"
|
||||
),
|
||||
"no_restrictions": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Im aktuellen Chat"
|
||||
" gibt es keine Einschränkungen</b>"
|
||||
),
|
||||
"all_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Alle Sticker"
|
||||
" sind im"
|
||||
" aktuellen Chat wieder erlaubt</b>"
|
||||
),
|
||||
"already_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Animierte Sticker"
|
||||
" sind bereits gesperrt</b>"
|
||||
),
|
||||
"not_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Animierte Sticker"
|
||||
" sind nicht gesperrt</b>"
|
||||
),
|
||||
"animations_restricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Animierte Sticker"
|
||||
" sind nun im aktuellen Chat gesperrt</b>"
|
||||
),
|
||||
"animations_unrestricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Animierte Sticker"
|
||||
" sind nun wieder im aktuellen Chat erlaubt</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"args": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>एक स्टिकर पर उत्तर"
|
||||
" आवश्यक है</b>"
|
||||
),
|
||||
"sticker_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>इस स्टिकर को वर्तमान"
|
||||
" चैट में प्रतिबंधित किया गया है</b>"
|
||||
),
|
||||
"pack_banned": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>{1} पैक से {0}"
|
||||
" स्टिकर वर्तमान चैट में प्रतिबंधित किए गए हैं</b>"
|
||||
),
|
||||
"wait": (
|
||||
"<emoji document_id='5451732530048802485'>⏳</emoji> <b>वर्तमान चैट में {1}"
|
||||
" पैक से स्टिकर प्रतिबंधित किए जा रहे हैं...</b>"
|
||||
),
|
||||
"sticker_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>वर्तमान चैट में इस"
|
||||
" स्टिकर को प्रतिबंधित नहीं किया गया है</b>"
|
||||
),
|
||||
"sticker_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>इस स्टिकर को वर्तमान"
|
||||
" चैट में प्रतिबंधित नहीं किया गया है</b>"
|
||||
),
|
||||
"pack_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{1} पैक से {0}"
|
||||
" स्टिकर वर्तमान चैट में प्रतिबंधित नहीं किए गए हैं</b>"
|
||||
),
|
||||
"pack_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>वर्तमान चैट में यह"
|
||||
" पैक प्रतिबंधित नहीं किया गया है</b>"
|
||||
),
|
||||
"no_restrictions": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>वर्तमान चैट में कोई"
|
||||
" प्रतिबंध नहीं है</b>"
|
||||
),
|
||||
"all_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>वर्तमान चैट में सभी"
|
||||
" स्टिकर प्रतिबंधित नहीं किए गए हैं</b>"
|
||||
),
|
||||
"already_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>आगे बढ़ने के लिए"
|
||||
" पहले से ही प्रतिबंधित किए गए हैं</b>"
|
||||
),
|
||||
"not_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>इस स्टिकर को पहले"
|
||||
" से ही प्रतिबंधित नहीं किया गया है</b>"
|
||||
),
|
||||
"already_unrestricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>इस स्टिकर को पहले"
|
||||
" से ही प्रतिबंधित नहीं किया गया है</b>"
|
||||
),
|
||||
"animations_restricted": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>वर्तमान चैट में"
|
||||
" एनीमेटेड स्टिकर अब प्रतिबंधित हैं</b>"
|
||||
),
|
||||
"animations_unrestricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>वर्तमान चैट में"
|
||||
" एनीमेटेड स्टिकर अब प्रतिबंधित नहीं हैं</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"pack_banned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{1} pakidan {0}"
|
||||
" stikerlar cheklangan</b>"
|
||||
),
|
||||
"sticker_banned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Stiker"
|
||||
" cheklangan</b>"
|
||||
),
|
||||
"not_a_pack": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Bu paket emas</b>"
|
||||
),
|
||||
"pack_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Ushbu paket"
|
||||
" cheklangan emas</b>"
|
||||
),
|
||||
"sticker_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Ushbu stiker"
|
||||
" cheklangan emas</b>"
|
||||
),
|
||||
"sticker_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Stiker cheklangan"
|
||||
" emas</b>"
|
||||
),
|
||||
"pack_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{1} pakidan {0}"
|
||||
" stikerlar cheklangan emas</b>"
|
||||
),
|
||||
"no_restrictions": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Ushbu chatda"
|
||||
" cheklangan stikerlar yo'q</b>"
|
||||
),
|
||||
"all_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Ushbu chatda barcha"
|
||||
" stikerlar cheklangan emas</b>"
|
||||
),
|
||||
"already_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Ushbu stiker oldin"
|
||||
" cheklangan</b>"
|
||||
),
|
||||
"not_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Ushbu stiker"
|
||||
" cheklangan emas</b>"
|
||||
),
|
||||
"already_unrestricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Ushbu stiker oldin"
|
||||
" cheklangan emas</b>"
|
||||
),
|
||||
"animations_restricted": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>Ushbu chatda"
|
||||
" animatsiya stikerlari cheklangan</b>"
|
||||
),
|
||||
"animations_unrestricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Ushbu chatda"
|
||||
" animatsiya stikerlari cheklangan emas</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"pack_banned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{1} paketinden {0}"
|
||||
" çıkartma yasaklandı</b>"
|
||||
),
|
||||
"sticker_banned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Çıkartma"
|
||||
" yasaklandı</b>"
|
||||
),
|
||||
"not_a_pack": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Bu bir paket"
|
||||
" değil</b>"
|
||||
),
|
||||
"pack_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Bu paket"
|
||||
" yasaklanmamış</b>"
|
||||
),
|
||||
"sticker_not_banned": (
|
||||
"<emoji document_id='5436162517686557387'>😵</emoji> <b>Bu çıkartma"
|
||||
" yasaklanmamış</b>"
|
||||
),
|
||||
"sticker_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Çıkartma"
|
||||
" yasaklanmamış</b>"
|
||||
),
|
||||
"pack_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>{1} paketinden {0}"
|
||||
" çıkartma yasaklanmamış</b>"
|
||||
),
|
||||
"no_restrictions": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Bu sohbette"
|
||||
" yasaklanmış çıkartma yok</b>"
|
||||
),
|
||||
"all_unbanned": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Bu sohbette tüm"
|
||||
" çıkartmalar yasaklanmamış</b>"
|
||||
),
|
||||
"already_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Bu çıkartma zaten"
|
||||
" yasaklanmış</b>"
|
||||
),
|
||||
"not_restricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Bu çıkartma"
|
||||
" yasaklanmamış</b>"
|
||||
),
|
||||
"already_unrestricted": (
|
||||
"<emoji document_id='5436040291507247633'>🎉</emoji> <b>Bu çıkartma zaten"
|
||||
" yasaklanmamış</b>"
|
||||
),
|
||||
"animations_restricted": (
|
||||
"<emoji document_id='6037557968914877661'>🛡</emoji> <b>Bu sohbette"
|
||||
" animasyonlu çıkartmalar yasaklanmış</b>"
|
||||
),
|
||||
"animations_unrestricted": (
|
||||
"<emoji document_id='5472308992514464048'>🔐</emoji> <b>Bu sohbette"
|
||||
" animasyonlu çıkartmalar yasaklanmamış</b>"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"rms_threshold",
|
||||
4.0,
|
||||
(
|
||||
"The lower this value is - the more light the detection will be."
|
||||
" 0.0 - Full match, 4.0 - approximate match"
|
||||
),
|
||||
validator=loader.validators.Float(maximum=10.0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"bantime",
|
||||
180,
|
||||
(
|
||||
"Once the user sent forbidden sticker, he will be restricted from"
|
||||
" sending more for this amount of seconds"
|
||||
),
|
||||
validator=loader.validators.Integer(minimum=60),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self._banlist = self.pointer("banlist", {})
|
||||
self._bananim = self.pointer("bananim", [])
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(utils.get_base_dir(), "..", "loaded_modules")
|
||||
)
|
||||
if not os.path.isdir(dir_path):
|
||||
os.mkdir(dir_path)
|
||||
|
||||
dir_path = os.path.join(dir_path, "banmedia")
|
||||
if not os.path.isdir(dir_path):
|
||||
os.mkdir(dir_path)
|
||||
|
||||
self._db_path = dir_path
|
||||
self._cache = {}
|
||||
self._filecache = {}
|
||||
for file in os.listdir(self._db_path):
|
||||
async with async_open(os.path.join(self._db_path, file), "rb") as f:
|
||||
self._cache[file] = await f.read()
|
||||
|
||||
@staticmethod
|
||||
def rmsdiff(image_1: Image, image_2: Image) -> float:
|
||||
"Calculate the root-mean-square difference between two images"
|
||||
# https://stackoverflow.com/a/11818358/19170642
|
||||
|
||||
try:
|
||||
h = ImageChops.difference(image_1, image_2).histogram()
|
||||
except ValueError:
|
||||
return 100.0
|
||||
|
||||
return math.sqrt(
|
||||
reduce(operator.add, map(lambda h, i: h * (i**2), h, range(256)))
|
||||
/ (float(image_1.size[0]) * image_1.size[1])
|
||||
)
|
||||
|
||||
async def _add_cache(self, sticker_id: int, bytes_: bytes):
|
||||
if not os.path.isfile(os.path.join(self._db_path, str(sticker_id))):
|
||||
async with async_open(
|
||||
os.path.join(self._db_path, str(sticker_id)), "wb"
|
||||
) as f:
|
||||
await f.write(bytes_)
|
||||
|
||||
self._cache[str(sticker_id)] = bytes_
|
||||
|
||||
async def _remove_cache(self, sticker_id: int):
|
||||
if os.path.isfile(os.path.join(self._db_path, str(sticker_id))):
|
||||
os.remove(os.path.join(self._db_path, str(sticker_id)))
|
||||
|
||||
if str(sticker_id) in self._cache:
|
||||
self._cache.pop(str(sticker_id))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<ответ на стикер> - Запретить стикер в текущем чате",
|
||||
de_doc="<auf Antwort auf Sticker> - Verbotene Sticker in diesem Chat",
|
||||
hi_doc="<उत्तर दिए गए स्टिकर पर> - इस चैट में अनुमति नहीं देने वाले स्टिकर",
|
||||
uz_doc="<stickerga javob> - Joriy suhbatda stikerni taqiqlash",
|
||||
tr_doc="<sticker'a yanıt> - Bu sohbette yasaklanmış çıkartma",
|
||||
)
|
||||
async def banstick(self, message: Message):
|
||||
"""<reply to sticker> - Ban sticker in current chat"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.sticker:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
self._banlist.setdefault(chat_id, []).append(reply.sticker.id)
|
||||
self._banlist[chat_id] = list(set(self._banlist[chat_id]))
|
||||
|
||||
if reply.sticker.mime_type.startswith("image"):
|
||||
await self._add_cache(reply.sticker.id, await reply.download_media(bytes))
|
||||
|
||||
await utils.answer(message, self.strings("sticker_banned"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<ответ на стикер> - Запретить весь стикерпак в текущем чате",
|
||||
de_doc="<auf Antwort auf Sticker> - Verbotene Stickerpack in diesem Chat",
|
||||
hi_doc="<उत्तर दिए गए स्टिकर पर> - इस चैट में अनुमति नहीं देने वाले स्टिकर पैक",
|
||||
uz_doc="<stickerga javob> - Joriy suhbatda stikerni taqiqlash",
|
||||
tr_doc="<sticker'a yanıt> - Bu sohbette yasaklanmış çıkartma paketi",
|
||||
)
|
||||
async def banpack(self, message: Message):
|
||||
"""<reply to sticker> - Ban the whole stickerpack in current chat"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.sticker:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("wait"))
|
||||
stickerset = await self._client(
|
||||
GetStickerSetRequest(
|
||||
next(
|
||||
attr.stickerset
|
||||
for attr in reply.sticker.attributes
|
||||
if hasattr(attr, "stickerset")
|
||||
),
|
||||
hash=0,
|
||||
)
|
||||
)
|
||||
|
||||
stickers = stickerset.documents
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
|
||||
for sticker in stickers:
|
||||
self._banlist.setdefault(chat_id, []).append(sticker.id)
|
||||
if sticker.mime_type.startswith("image"):
|
||||
await self._add_cache(
|
||||
sticker.id,
|
||||
await self._client.download_file(sticker, bytes),
|
||||
)
|
||||
await asyncio.sleep(1) # Light FW protection
|
||||
|
||||
self._banlist[chat_id] = list(set(self._banlist[chat_id]))
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("pack_banned").format(
|
||||
len(stickers),
|
||||
utils.escape_html(stickerset.set.title),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<ответ на стикер> - Разбанить стикер в текущем чате",
|
||||
de_doc="<auf Antwort auf Sticker> - Entbanne Sticker in diesem Chat",
|
||||
hi_doc="<उत्तर दिए गए स्टिकर पर> - इस चैट में अनुमति देने वाले स्टिकर",
|
||||
uz_doc="<stickerga javob> - Joriy suhbatda stikerni taqiqlash",
|
||||
tr_doc="<sticker'a yanıt> - Bu sohbette yasaklanmış çıkartma",
|
||||
)
|
||||
async def unbanstick(self, message: Message):
|
||||
"""<reply to sticker> - Unban sticker in current chat"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.sticker:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
if reply.sticker.id not in self._banlist.get(chat_id, []):
|
||||
await utils.answer(message, self.strings("sticker_not_banned"))
|
||||
return
|
||||
|
||||
self._banlist[chat_id].remove(reply.sticker.id)
|
||||
if reply.sticker.mime_type.startswith("image"):
|
||||
await self._remove_cache(reply.sticker.id)
|
||||
|
||||
await utils.answer(message, self.strings("sticker_unbanned"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<ответ на стикер> - Разбанить весь стикерпак в текущем чате"
|
||||
)
|
||||
async def unbanpack(self, message: Message):
|
||||
"""<reply to sticker> - Unban the whole stickerpack in current chat"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.sticker:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("wait"))
|
||||
stickerset = await self._client(
|
||||
GetStickerSetRequest(
|
||||
next(
|
||||
attr.stickerset
|
||||
for attr in reply.sticker.attributes
|
||||
if hasattr(attr, "stickerset")
|
||||
),
|
||||
hash=0,
|
||||
)
|
||||
)
|
||||
|
||||
stickers = stickerset.documents
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
|
||||
unbanned = 0
|
||||
|
||||
for sticker in stickers:
|
||||
if sticker.id in self._banlist.get(chat_id, []):
|
||||
self._banlist[chat_id].remove(sticker.id)
|
||||
if sticker.mime_type.startswith("image"):
|
||||
await self._remove_cache(sticker.id)
|
||||
unbanned += 1
|
||||
|
||||
if not unbanned:
|
||||
await utils.answer(message, self.strings("pack_not_banned"))
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("pack_unbanned").format(
|
||||
unbanned,
|
||||
len(stickers),
|
||||
utils.escape_html(stickerset.set.title),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Убрать все ограничения в текущем чате",
|
||||
de_doc="Entferne alle Einschränkungen in diesem Chat",
|
||||
hi_doc="इस चैट में सभी सीमाएं निकालें",
|
||||
uz_doc="Joriy suhbatda barcha cheklarni olib tashlang",
|
||||
tr_doc="Bu sohbetteki tüm yasaklamaları kaldırın",
|
||||
)
|
||||
async def unbanall(self, message: Message):
|
||||
"""Remove all restrictions in current chat"""
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
if not self._banlist.get(chat_id, []):
|
||||
await utils.answer(message, self.strings("no_restrictions"))
|
||||
return
|
||||
|
||||
for sticker in self._banlist.pop(chat_id):
|
||||
await self._remove_cache(sticker)
|
||||
|
||||
await utils.answer(message, self.strings("all_unbanned"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Запретить анимированные и видео стикеры в этом чате",
|
||||
de_doc="Verbiete animierte und Video-Sticker in diesem Chat",
|
||||
hi_doc="इस चैट में एनीमेटेड और वीडियो स्टिकर्स को अस्वीकार करें",
|
||||
uz_doc="Bu suhbatda animatsiya va video stikerni taqiqlang",
|
||||
tr_doc="Bu sohbette animasyonlu ve video çıkartmaları yasaklayın",
|
||||
)
|
||||
async def bananim(self, message: Message):
|
||||
"""Restrict animated stickers in current chat"""
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
if chat_id in self._bananim:
|
||||
await utils.answer(message, self.strings("already_restricted"))
|
||||
return
|
||||
|
||||
self._bananim.append(chat_id)
|
||||
await utils.answer(message, self.strings("animations_restricted"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Разблокировать анимированные и видео стикеры в этом чате",
|
||||
de_doc=(
|
||||
"Entferne die Einschränkung für animierte und Video-Sticker in diesem Chat"
|
||||
),
|
||||
hi_doc="इस चैट में एनीमेटेड और वीडियो स्टिकर्स की प्रतिबंध निकालें",
|
||||
uz_doc="Bu suhbatda animatsiya va video stikerni taqiqlashni olib tashlang",
|
||||
tr_doc="Bu sohbette animasyonlu ve video çıkartmaları yasaklamasını kaldırın",
|
||||
)
|
||||
async def unbananim(self, message: Message):
|
||||
"""Unrestrict animated stickers in current chat"""
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
if chat_id not in self._bananim:
|
||||
await utils.answer(message, self.strings("not_restricted"))
|
||||
return
|
||||
|
||||
self._bananim.remove(chat_id)
|
||||
await utils.answer(message, self.strings("animations_unrestricted"))
|
||||
|
||||
@loader.watcher("in", only_stickers=True, only_groups=True)
|
||||
async def watcher(self, message: Message):
|
||||
chat_id = str(utils.get_chat_id(message))
|
||||
if not self._banlist.get(chat_id):
|
||||
return
|
||||
|
||||
async def _restrict():
|
||||
nonlocal message
|
||||
await message.delete()
|
||||
await self._client.edit_permissions(
|
||||
message.peer_id,
|
||||
message.sender_id,
|
||||
until_date=time.time() + self.config["bantime"],
|
||||
send_stickers=False,
|
||||
)
|
||||
|
||||
if not message.sticker.mime_type.startswith("image"):
|
||||
if chat_id in self._bananim or message.sticker.id in self._banlist[chat_id]:
|
||||
await _restrict()
|
||||
|
||||
return
|
||||
|
||||
if message.sticker.id in self._filecache:
|
||||
file = self._filecache[message.sticker.id]
|
||||
else:
|
||||
file = await message.download_media(bytes)
|
||||
self._filecache[message.sticker.id] = file
|
||||
|
||||
image = Image.open(io.BytesIO(file))
|
||||
for sticker_id, bytes_ in self._cache.items():
|
||||
res = await utils.run_sync(
|
||||
self.rmsdiff,
|
||||
image,
|
||||
Image.open(io.BytesIO(bytes_)),
|
||||
)
|
||||
if res < self.config["rms_threshold"]:
|
||||
await self._add_cache(sticker_id, file)
|
||||
return await _restrict()
|
||||
491
hikariatama/ftg/bfg.py
Normal file
491
hikariatama/ftg/bfg.py
Normal file
@@ -0,0 +1,491 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/bfg_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/bfg.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scop: hikka_min 1.3.0
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
||||
from telethon.events import NewMessage
|
||||
from telethon.tl.functions.contacts import UnblockRequest
|
||||
from telethon.tl.functions.messages import ReadMentionsRequest
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Mining:
|
||||
async def _automining(self) -> bool:
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Моя шахта")
|
||||
r = await conv.get_response()
|
||||
mining_exp = int(
|
||||
"".join(
|
||||
s
|
||||
for s in r.raw_text.splitlines()[1].split()[2].strip()
|
||||
if s.isdigit()
|
||||
)
|
||||
)
|
||||
self.set("mining_exp", mining_exp)
|
||||
energy = int(
|
||||
"".join(
|
||||
s
|
||||
for s in r.raw_text.splitlines()[2].split()[2].strip()
|
||||
if s.isdigit()
|
||||
)
|
||||
)
|
||||
|
||||
if energy == 0:
|
||||
return False
|
||||
|
||||
resource = next(
|
||||
resource
|
||||
for range_, resource in self._resources_map.items()
|
||||
if mining_exp in range_
|
||||
)
|
||||
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
while energy > 0:
|
||||
await conv.send_message(f"копать {resource}")
|
||||
r = await conv.get_response()
|
||||
if "у вас закончилась" in r.raw_text:
|
||||
break
|
||||
|
||||
if "Энергия" in r.raw_text:
|
||||
energy = int(r.raw_text.split("Энергия:")[1].split(",")[0].strip())
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
return True
|
||||
|
||||
async def _sell_btc(self) -> bool:
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Продать биткоины")
|
||||
await conv.get_response()
|
||||
|
||||
return True
|
||||
|
||||
async def _mining_sell(self) -> bool:
|
||||
if not self.get("mining_exp"):
|
||||
return False
|
||||
|
||||
resources = []
|
||||
for range_, resource in self._resources_map.items():
|
||||
resources += [resource]
|
||||
if self.get("mining_exp") in range_:
|
||||
break
|
||||
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
for resource in self._resources_map.values():
|
||||
if resource == "материю":
|
||||
continue
|
||||
|
||||
await conv.send_message(f"продать {resource}")
|
||||
await conv.get_response()
|
||||
|
||||
|
||||
class Bonuses:
|
||||
async def _daily(self):
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Ежедневный бонус")
|
||||
r = await conv.get_response()
|
||||
if "ты уже получал" not in r.raw_text:
|
||||
await asyncio.sleep(2)
|
||||
await conv.send_message("Ежедневный бонус")
|
||||
r = await conv.get_response()
|
||||
|
||||
hours, minutes = (
|
||||
r.raw_text.split("ты сможешь получить через")[1].strip().split()
|
||||
)
|
||||
hours, minutes = int(hours[:-1]), int(minutes[:-1])
|
||||
time_ = hours * 60 * 60 + minutes * 60
|
||||
self.set("daily", int(time.time() + time_ + 60))
|
||||
return True
|
||||
|
||||
async def _treasures(self):
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Ограбить казну")
|
||||
await conv.get_response()
|
||||
self.set("treasures", int(time.time() + 24 * 60 * 60))
|
||||
|
||||
|
||||
class Potions:
|
||||
async def _create_poisons(self) -> bool:
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Инвентарь")
|
||||
r = await conv.get_response()
|
||||
if "Зёрна:" not in r.raw_text:
|
||||
self.set("poisons", int(time.time() + 30 * 60))
|
||||
return False
|
||||
|
||||
grains = int(
|
||||
"".join(
|
||||
s
|
||||
for s in r.raw_text.split("Зёрна:")[1].strip().split(" ")[0]
|
||||
if s.isdigit()
|
||||
)
|
||||
)
|
||||
|
||||
any_ = False
|
||||
|
||||
for _ in range(grains // 40):
|
||||
await conv.send_message("Создать зелье 1")
|
||||
await conv.get_response()
|
||||
await asyncio.sleep(0.5)
|
||||
any_ = True
|
||||
|
||||
if any_:
|
||||
self.set("automining", 0)
|
||||
|
||||
self.set("poisons", int(time.time() + 30 * 60))
|
||||
return True
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BFG2Mod(loader.Module, Mining, Bonuses, Potions):
|
||||
"""Tasks automation for @bforgame_bot"""
|
||||
|
||||
strings = {"name": "BFG"}
|
||||
|
||||
strings_ru = {"_cls_doc": "Фарм в @bforgame_bot"}
|
||||
|
||||
_request_timeout = 3
|
||||
_last_iter = 0
|
||||
_cache = {}
|
||||
_resources_map = {
|
||||
range(0, 500): "железо",
|
||||
range(500, 2000): "золото",
|
||||
range(2000, 10000): "алмазы",
|
||||
range(10000, 25000): "аметисты",
|
||||
range(25000, 60000): "аквамарин",
|
||||
range(60000, 100000): "изумруды",
|
||||
range(100000, 500000): "материю",
|
||||
range(
|
||||
500000, 10**50
|
||||
): "плазму", # We don't care about the size of value, bc it's range
|
||||
}
|
||||
_bot = "@bforgame_bot"
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"autodaily",
|
||||
True,
|
||||
"Автоматически собирать ежедневный бонус",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"autotreasures",
|
||||
True,
|
||||
"Автоматически грабить мэрию",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"automining",
|
||||
True,
|
||||
"Автоматически копать шахту и продавать все ресурсы, кроме материи",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"autofarm",
|
||||
True,
|
||||
"Автоматически собирать налоги и прибыль с фермы",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"autogarden",
|
||||
True,
|
||||
"Автоматически собирать налоги, собирать прибыль и поливать сад",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"autogenerator",
|
||||
True,
|
||||
"Автоматически собирать налоги и прибыль с бизнеса",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"autobusiness",
|
||||
True,
|
||||
"Автоматически собирать налоги и прибыль с бизнеса",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"autopotions",
|
||||
True,
|
||||
"Автоматически варить зелья",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"sell_btc",
|
||||
False,
|
||||
"Автоматически продавать биткоины",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
try:
|
||||
await self._client.send_message(
|
||||
self._bot,
|
||||
"💫 <i>~модуль автоматизации bfg от hikari. запущен~~</i>",
|
||||
)
|
||||
except YouBlockedUserError:
|
||||
await self._client(UnblockRequest(self._bot))
|
||||
await self._client.send_message(
|
||||
self._bot,
|
||||
"💫 <i>~модуль автоматизации bfg от hikari. запущен~~</i>",
|
||||
)
|
||||
|
||||
async def _garden(self) -> bool:
|
||||
try:
|
||||
message = await self._get_msg("Мой сад")
|
||||
if not message:
|
||||
return False
|
||||
|
||||
await message.click(data=b"payTaxesGarden")
|
||||
await asyncio.sleep(1)
|
||||
await message.click(data=b"pourGarden")
|
||||
await asyncio.sleep(1)
|
||||
await message.click(data=b"collectIncomeGarden")
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Can't process BFG click")
|
||||
self.set("garden", None)
|
||||
return False
|
||||
|
||||
async def _generator(self) -> bool:
|
||||
try:
|
||||
message = await self._get_msg("Мой генератор")
|
||||
if not message:
|
||||
return False
|
||||
|
||||
await message.click(data=b"payTaxesGenerator")
|
||||
await asyncio.sleep(1)
|
||||
await message.click(data=b"collectIncomeGenerator")
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Can't process BFG click")
|
||||
self.set("generator", None)
|
||||
return False
|
||||
|
||||
async def _business(self) -> bool:
|
||||
try:
|
||||
message = await self._get_msg("Мой бизнес")
|
||||
if not message:
|
||||
return False
|
||||
|
||||
await message.click(data=b"payTaxes")
|
||||
await asyncio.sleep(1)
|
||||
await message.click(data=b"collectIncome")
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Can't process BFG click")
|
||||
self.set("business", None)
|
||||
return False
|
||||
|
||||
async def _farm(self) -> bool:
|
||||
try:
|
||||
message = await self._get_msg("Моя ферма")
|
||||
if not message:
|
||||
return False
|
||||
|
||||
await message.click(data=b"payTaxesFarm")
|
||||
await asyncio.sleep(1)
|
||||
await message.click(data=b"collectIncomeFarm")
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Can't process BFG click")
|
||||
self.set("farm", None)
|
||||
return False
|
||||
|
||||
async def _get_msg(self, key: str) -> Message:
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message(key)
|
||||
r = await conv.get_response()
|
||||
if "чтобы построить введите команду" in r.raw_text:
|
||||
key = {
|
||||
"Мой генератор": "generator",
|
||||
"Моя ферма": "farm",
|
||||
"Мой сад": "garden",
|
||||
"Мой бизнес": "business",
|
||||
}[key]
|
||||
self.config[f"auto{key}"] = False
|
||||
return False
|
||||
|
||||
return r
|
||||
|
||||
@loader.loop(interval=15, autostart=True)
|
||||
async def loop(self):
|
||||
any_ = False
|
||||
if not self.get("fee_time") or self.get("fee_time") < time.time():
|
||||
if self.config["autopotions"]:
|
||||
await self._create_poisons()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["autofarm"]:
|
||||
await self._farm()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["autogarden"]:
|
||||
await self._garden()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["autogenerator"]:
|
||||
await self._generator()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["autobusiness"]:
|
||||
await self._business()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["automining"]:
|
||||
await self._automining()
|
||||
await self._mining_sell()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["sell_btc"]:
|
||||
await self._sell_btc()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if any_:
|
||||
self.set("fee_time", int(time.time() + 60 * 60))
|
||||
|
||||
if self.config["autodaily"] and (
|
||||
not self.get("daily") or self.get("daily") < time.time()
|
||||
):
|
||||
await self._daily()
|
||||
any_ = True
|
||||
await asyncio.sleep(5)
|
||||
|
||||
if self.config["autotreasures"] and (
|
||||
not self.get("treasures") or self.get("treasures") < time.time()
|
||||
):
|
||||
await self._treasures()
|
||||
any_ = True
|
||||
|
||||
if any_:
|
||||
await self._client(ReadMentionsRequest(self._bot))
|
||||
|
||||
@loader.command(ru_doc="[уровни] - покупка уровней для фермы")
|
||||
async def farmlvlcmd(self, message: Message):
|
||||
"""[levels] - Level-up farm for specfied amount of levels"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args and not args.isdigit():
|
||||
await utils.answer(message, "🚫 <b>Некорректное количество уровней</b>")
|
||||
return
|
||||
|
||||
message = await utils.answer(message, "🫶 <b>Улучшаю ферму</b>")
|
||||
|
||||
levels = 0 if not args else int(args)
|
||||
chunk = 0
|
||||
enchanced = 0
|
||||
|
||||
while levels:
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Моя ферма")
|
||||
r = await conv.get_response()
|
||||
if "Видеокарты: 100" in r.raw_text:
|
||||
await utils.answer(message, "🫶 <b>Ферма улучшена до максимума</b>")
|
||||
return
|
||||
|
||||
while chunk < 10 and levels:
|
||||
await r.click(data=b"buyFarmCard")
|
||||
await conv.wait_event(
|
||||
NewMessage(outgoing=False, chats=conv.chat_id)
|
||||
)
|
||||
resp = (await self._client.get_messages(self._bot, limit=1))[0]
|
||||
if "вы успешно увеличили" not in resp.raw_text:
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"🫶 <b>Ферма улучшена на {enchanced} уровней."
|
||||
" Закончились деньги</b>"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
enchanced += 1
|
||||
levels -= 1
|
||||
chunk += 1
|
||||
|
||||
await utils.answer(message, f"🫶 <b>Ферма улучшена на {enchanced} уровней.</b>")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[уровни] - покупка уровней для бизнеса (территория + сам бизнес)"
|
||||
)
|
||||
async def businesslvlcmd(self, message: Message):
|
||||
"""[levels] - Level-up business for specfied amount of levels (territory + business itself)"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args and not args.isdigit():
|
||||
await utils.answer(message, "🚫 <b>Некорректное количество уровней</b>")
|
||||
return
|
||||
|
||||
message = await utils.answer(message, "🫶 <b>Улучшаю бизнес</b>")
|
||||
|
||||
levels = 0 if not args else int(args)
|
||||
chunk = 0
|
||||
enchanced = 0
|
||||
|
||||
while levels:
|
||||
async with self._client.conversation(self._bot) as conv:
|
||||
await conv.send_message("Мой бизнес")
|
||||
r = await conv.get_response()
|
||||
while chunk < 10 and levels:
|
||||
await r.click(data=b"upTerritory")
|
||||
await conv.wait_event(
|
||||
NewMessage(outgoing=False, chats=conv.chat_id)
|
||||
)
|
||||
resp = (await self._client.get_messages(self._bot, limit=1))[0]
|
||||
if "вы достигли максимального размера" in resp.raw_text:
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"🫶 <b>Бизнес улучшен на {enchanced} уровней."
|
||||
" Закончились деньги</b>"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
await r.click(data=b"upBusiness")
|
||||
await conv.wait_event(
|
||||
NewMessage(outgoing=False, chats=conv.chat_id)
|
||||
)
|
||||
resp = (await self._client.get_messages(self._bot, limit=1))[0]
|
||||
if "чтобы увеличить бизнес" in resp.raw_text:
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"🫶 <b>Бизнес улучшен на {enchanced} уровней."
|
||||
" Закончились деньги</b>"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
enchanced += 1
|
||||
levels -= 1
|
||||
chunk += 1
|
||||
|
||||
await utils.answer(message, f"🫶 <b>Бизнес улучшен на {enchanced} уровней.</b>")
|
||||
89
hikariatama/ftg/bigtext.py
Normal file
89
hikariatama/ftg/bigtext.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/external-soft-fill-juicy-fish/480/000000/external-big-cute-monsters-soft-fill-soft-fill-juicy-fish-4.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/bigtext.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
|
||||
import contextlib
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
mapping = {
|
||||
"a": """█▀▀█\n █▄▄█\n ▀ ▀""",
|
||||
"b": """█▀▀▄\n █▀▀▄\n ▀▀▀""",
|
||||
"c": """█▀▀\n █\n ▀▀▀""",
|
||||
"d": """█▀▀▄\n █ █\n ▀▀▀""",
|
||||
"e": """█▀▀\n █▀▀\n ▀▀▀""",
|
||||
"f": """█▀▀\n █▀▀\n ▀""",
|
||||
"g": """█▀▀▀\n █ ▀█\n ▀▀▀▀""",
|
||||
"h": """█ █\n █▀▀█\n ▀ ▀""",
|
||||
"i": """▀\n ▀█▀\n ▀▀▀""",
|
||||
"j": """▀\n █\n █▄█""",
|
||||
"k": """█ █\n █▀▄\n ▀ ▀""",
|
||||
"l": """█\n █\n ▀▀▀""",
|
||||
"m": """█▀▄▀█\n █ ▀ █\n ▀ ▀""",
|
||||
"n": """█▀▀▄\n █ █\n ▀ ▀""",
|
||||
"o": """█▀▀█\n █ █\n ▀▀▀▀""",
|
||||
"p": """█▀▀█\n █ █\n █▀▀▀""",
|
||||
"q": """█▀▀█\n █ █\n ▀▀█▄""",
|
||||
"r": """█▀▀█\n █▄▄▀\n █ █""",
|
||||
"s": """█▀▀▀█\n ▀▀▀▄▄\n █▄▄▄█""",
|
||||
"t": """▀▀█▀▀\n █\n █""",
|
||||
"u": """█ █\n █ █\n ▀▄▄▀""",
|
||||
"v": """█ █\n █ █\n ▀▄▀""",
|
||||
"w": """█ █\n █ █ █\n █▄▀▄█""",
|
||||
"x": """▀▄ ▄▀\n █\n ▄▀ ▀▄""",
|
||||
"y": """█ █\n █▄▄▄█\n █""",
|
||||
"z": """█▀▀▀█\n ▄▄▄▀▀\n █▄▄▄█""",
|
||||
" ": """ \n \n """,
|
||||
}
|
||||
|
||||
|
||||
def process(cir, text):
|
||||
result = ""
|
||||
for chunk in utils.chunks(
|
||||
[mapping.get(letter.lower(), "").splitlines() for letter in text], cir
|
||||
):
|
||||
row = ["" for _ in range(max(list(map(len, mapping.values()))))]
|
||||
row_result = []
|
||||
for i, line in enumerate(row):
|
||||
for letter in chunk:
|
||||
with contextlib.suppress(IndexError):
|
||||
l_ = letter[i]
|
||||
if len(l_) < 5:
|
||||
l_ += " " * (5 - len(l_))
|
||||
line += f"{l_} "
|
||||
|
||||
row_result += [line]
|
||||
|
||||
result += "\n".join([r for r in row_result if r.strip()]) + "\n"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BigTextMod(loader.Module):
|
||||
"""Creates big ASCII Text"""
|
||||
|
||||
strings = {"name": "BigText"}
|
||||
|
||||
async def btcmd(self, message: Message):
|
||||
"""[chars in line] - Create big text"""
|
||||
args = utils.get_args_raw(message)
|
||||
cir = 6
|
||||
if args.split() and args.split()[0].isdigit():
|
||||
cir = int(args.split()[0])
|
||||
args = args[args.find(" ") + 1 :]
|
||||
|
||||
await utils.answer(message, f"<code>{process(cir, args)}</code>")
|
||||
117
hikariatama/ftg/bincheck.py
Normal file
117
hikariatama/ftg/bincheck.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/bincheck_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/bincheck.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BinCheckerMod(loader.Module):
|
||||
"""Show bin info about card"""
|
||||
|
||||
strings = {
|
||||
"name": "BinCheck",
|
||||
"args": (
|
||||
"<emoji document_id=5765086867154276106>💳</emoji> <b>To get bin info, you"
|
||||
" need to specify Bin of card (first 6 digits)</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": (
|
||||
"<emoji document_id=5765086867154276106>💳</emoji> <b>Для получения"
|
||||
" информации БИН укажи первые 6 цифр карты</b>"
|
||||
),
|
||||
"_cmd_doc_bincheck": "[bin] - Получить информацию БИН",
|
||||
"_cls_doc": "Показать информацию БИН о банковской карте",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": (
|
||||
"<emoji document_id=5765086867154276106>💳</emoji> <b>Um die Bin-Info zu"
|
||||
" erhalten, musst du die Bin der Karte (erste 6 Ziffern) angeben</b>"
|
||||
),
|
||||
"_cmd_doc_bincheck": "[bin] - Erhalte Bin-Info",
|
||||
"_cls_doc": "Zeigt Bin-Info über eine Bankkarte an",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"args": (
|
||||
"<emoji document_id=5765086867154276106>💳</emoji> <b>बिन जानकारी प्राप्त"
|
||||
" करने के लिए, आपको कार्ड का बिन (पहले 6 अंक) निर्दिष्ट करना होगा</b>"
|
||||
),
|
||||
"_cmd_doc_bincheck": "[bin] - बिन जानकारी प्राप्त करें",
|
||||
"_cls_doc": "बैंक कार्ड के बारे में बिन जानकारी दिखाएं",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"args": (
|
||||
"<emoji document_id=5765086867154276106>💳</emoji> <b>Bin haqida ma'lumot"
|
||||
" olish uchun, siz karta bin (birinchi 6 raqam) belgilashingiz kerak</b>"
|
||||
),
|
||||
"_cmd_doc_bincheck": "[bin] - Bin haqida ma'lumot olish",
|
||||
"_cls_doc": "Bank karta haqida bin ma'lumotini ko'rsatish",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"args": (
|
||||
"<emoji document_id=5765086867154276106>💳</emoji> <b>Bin bilgisi almak"
|
||||
" için, kartın bin (ilk 6 rakam) belirtmeniz gerekir</b>"
|
||||
),
|
||||
"_cmd_doc_bincheck": "[bin] - Bin bilgisi al",
|
||||
"_cls_doc": "Banka kartı hakkında bin bilgisi göster",
|
||||
}
|
||||
|
||||
@loader.unrestricted
|
||||
async def bincheckcmd(self, message: Message):
|
||||
"""[bin] - Get card Bin info"""
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
args = int(args)
|
||||
if args < 100000 or args > 999999:
|
||||
raise Exception()
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
async def bincheck(cc):
|
||||
try:
|
||||
ans = json.loads(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get, f"https://bin-checker.net/api/{str(cc)}"
|
||||
)
|
||||
).text
|
||||
)
|
||||
|
||||
return (
|
||||
"<b><u>Bin: %s</u></b>\n<code>\n🏦 Bank: %s\n🌐 Payment system: %s"
|
||||
" [%s]\n✳️ Level: %s\n⚛️ Country: %s </code>"
|
||||
% (
|
||||
cc,
|
||||
ans["bank"]["name"],
|
||||
ans["scheme"],
|
||||
ans["type"],
|
||||
ans["level"],
|
||||
ans["country"]["name"],
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return "BIN data unavailable"
|
||||
|
||||
await utils.answer(message, await bincheck(args))
|
||||
179
hikariatama/ftg/bulkcheck.py
Normal file
179
hikariatama/ftg/bulkcheck.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/bulkcheck_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/bulkcheck.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
# requires: requests
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BulkCheckMod(loader.Module):
|
||||
"""Check all members of chat for leaked numbers"""
|
||||
|
||||
strings = {
|
||||
"name": "BulkCheck",
|
||||
"processing": (
|
||||
"<emoji document_id=5451732530048802485>⏳</emoji> <b>Processing...</b>"
|
||||
),
|
||||
"no_pm": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>This command can be"
|
||||
" used only in chat</b>"
|
||||
),
|
||||
"leaked": (
|
||||
"<emoji document_id=5465169893580086142>☎️</emoji> <b>Leaked numbers in"
|
||||
" current chat:</b>\n\n{}"
|
||||
),
|
||||
"404": (
|
||||
"<emoji document_id=5465325710698617730>☹️</emoji> <b>No leaked numbers"
|
||||
" found here</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"processing": (
|
||||
"<emoji document_id=5451732530048802485>⏳</emoji> <b>Работаю...</b>"
|
||||
),
|
||||
"no_pm": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Эту команду нужно"
|
||||
" выполнять в чате</b>"
|
||||
),
|
||||
"leaked": (
|
||||
"<emoji document_id=5465169893580086142>☎️</emoji> <b>Слитые номера в этом"
|
||||
" чате:</b>\n\n{}"
|
||||
),
|
||||
"404": (
|
||||
"<emoji document_id=5465325710698617730>☹️</emoji> <b>Тут нет слитых"
|
||||
" номеров</b>"
|
||||
),
|
||||
"_cmd_doc_bulkcheck": "Проверить все участников чата на слитые номера",
|
||||
"_cls_doc": "Проверяет всех участников чата на слитые номера",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"processing": (
|
||||
"<emoji document_id=5451732530048802485>⏳</emoji> <b>Verarbeite...</b>"
|
||||
),
|
||||
"no_pm": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Dieser Befehl"
|
||||
" kann nur"
|
||||
" in einem Chat verwendet werden</b>"
|
||||
),
|
||||
"leaked": (
|
||||
"<emoji document_id=5465169893580086142>☎️</emoji> <b>Leaked Nummern in"
|
||||
" diesem Chat:</b>\n\n{}"
|
||||
),
|
||||
"404": (
|
||||
"<emoji document_id=5465325710698617730>☹️</emoji> <b>Keine leaked Nummern"
|
||||
" in diesem Chat gefunden</b>"
|
||||
),
|
||||
"_cmd_doc_bulkcheck": "Überprüfe alle Mitglieder des Chats auf leaked Nummern",
|
||||
"_cls_doc": "Überprüft alle Mitglieder des Chats auf leaked Nummern",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"processing": (
|
||||
"<emoji document_id=5451732530048802485>⏳</emoji> <b>प्रोसेसिंग...</b>"
|
||||
),
|
||||
"no_pm": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>यह कमांड केवल चैट में"
|
||||
" उपयोग किया जा सकता है</b>"
|
||||
),
|
||||
"leaked": (
|
||||
"<emoji document_id=5465169893580086142>☎️</emoji> <b>वर्तमान चैट में लीक"
|
||||
" किए गए नंबर:</b>\n\n{}"
|
||||
),
|
||||
"404": (
|
||||
"<emoji document_id=5465325710698617730>☹️</emoji> <b>यहां कोई लीक किए गए"
|
||||
" नंबर नहीं मिला</b>"
|
||||
),
|
||||
"_cmd_doc_bulkcheck": "चैट के सभी सदस्यों को लीक किए गए नंबरों के लिए जांचें",
|
||||
"_cls_doc": "चैट के सभी सदस्यों को लीक किए गए नंबरों के लिए जांचता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"processing": (
|
||||
"<emoji document_id=5451732530048802485>⏳</emoji> <b>Ishlamoqda...</b>"
|
||||
),
|
||||
"no_pm": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Ushbu buyruq faqat"
|
||||
" guruhda ishlatilishi mumkin</b>"
|
||||
),
|
||||
"leaked": (
|
||||
"<emoji document_id=5465169893580086142>☎️</emoji> <b>Joriy guruhda"
|
||||
" chiqarilgan raqamlar:</b>\n\n{}"
|
||||
),
|
||||
"404": (
|
||||
"<emoji document_id=5465325710698617730>☹️</emoji> <b>Bu guruhda"
|
||||
" chiqarilgan raqamlar topilmadi</b>"
|
||||
),
|
||||
"_cmd_doc_bulkcheck": (
|
||||
"Guruhning barcha a'zolarini chiqarilgan raqamlar uchun tekshirish"
|
||||
),
|
||||
"_cls_doc": "Guruhning barcha a'zolarini chiqarilgan raqamlar uchun tekshiradi",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"processing": (
|
||||
"<emoji document_id=5451732530048802485>⏳</emoji> <b>İşleniyor...</b>"
|
||||
),
|
||||
"no_pm": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bu komut sadece"
|
||||
" sohbetlerde kullanılabilir</b>"
|
||||
),
|
||||
"leaked": (
|
||||
"<emoji document_id=5465169893580086142>☎️</emoji> <b>Bu sohbetteki sızan"
|
||||
" numaralar:</b>\n\n{}"
|
||||
),
|
||||
"404": (
|
||||
"<emoji document_id=5465325710698617730>☹️</emoji> <b>Bu sohbette sızan"
|
||||
" numara bulunamadı</b>"
|
||||
),
|
||||
"_cmd_doc_bulkcheck": "Sohbetteki tüm üyeleri sızan numaralar için kontrol et",
|
||||
"_cls_doc": "Sohbetteki tüm üyeleri sızan numaralar için kontrol eder",
|
||||
}
|
||||
|
||||
async def bcheckcmd(self, message: Message):
|
||||
"""Bulk check using Murix database"""
|
||||
if message.is_private:
|
||||
await utils.answer(message, self.strings("no_pm"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("processing"))
|
||||
|
||||
results = []
|
||||
async for member in self._client.iter_participants(message.peer_id):
|
||||
result = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
f"http://api.murix.ru/eye?uid={member.id}&v=1.2",
|
||||
)
|
||||
).json()
|
||||
if result["data"] != "NOT_FOUND":
|
||||
results += [
|
||||
"<b>▫️ <a"
|
||||
f' href="tg://user?id={member.id}">{utils.escape_html(get_display_name(member))}</a></b>:'
|
||||
f" <code>+{result['data']}</code>"
|
||||
]
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
self.strings("leaked").format("\n".join(results))
|
||||
if results
|
||||
else self.strings("404")
|
||||
),
|
||||
)
|
||||
136
hikariatama/ftg/carbon.py
Normal file
136
hikariatama/ftg/carbon.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/stickers/500/000000/code.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/carbon.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
# requires: urllib requests
|
||||
|
||||
import io
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CarbonMod(loader.Module):
|
||||
"""Create beautiful code images"""
|
||||
|
||||
strings = {
|
||||
"name": "Carbon",
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>No args specified</b>"
|
||||
),
|
||||
"loading": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Loading...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Не указаны"
|
||||
" аргументы</b>"
|
||||
),
|
||||
"loading": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Обработка...</b>"
|
||||
),
|
||||
"_cls_doc": "Создает симпатичные фотки кода",
|
||||
"_cmd_doc_carbon": "<код> - Сделать красивую фотку кода",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Keine Argumente"
|
||||
" angegeben</b>"
|
||||
),
|
||||
"loading": "<emoji document_id=5213452215527677338>⏳</emoji> <b>Laden...</b>",
|
||||
"_cls_doc": "Erstellt schöne Code-Bilder",
|
||||
"_cmd_doc_carbon": "<code> - Erstelle ein schönes Code-Bild",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>कोई आर्ग्यूमेंट नहीं"
|
||||
" दिया गया</b>"
|
||||
),
|
||||
"loading": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>लोड हो रहा है...</b>"
|
||||
),
|
||||
"_cls_doc": "सुंदर कोड छवियां बनाएं",
|
||||
"_cmd_doc_carbon": "<कोड> - सुंदर कोड छवि बनाएं",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Hech qanday"
|
||||
" argumentlar ko'rsatilmadi</b>"
|
||||
),
|
||||
"loading": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Yuklanmoqda...</b>"
|
||||
),
|
||||
"_cls_doc": "G'ayratli kod rasmlarini yaratish",
|
||||
"_cmd_doc_carbon": "<kod> - G'ayratli kod rasmini yaratish",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Argümanlar"
|
||||
" belirtilmedi</b>"
|
||||
),
|
||||
"loading": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Yükleniyor...</b>"
|
||||
),
|
||||
"_cls_doc": "Güzel kod resimleri oluşturur",
|
||||
"_cmd_doc_carbon": "<kod> - Güzel kod resmi oluşturur",
|
||||
}
|
||||
|
||||
async def carboncmd(self, message: Message):
|
||||
"""<code> - Create beautiful code image"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
try:
|
||||
code_from_message = (
|
||||
await self._client.download_file(message.media, bytes)
|
||||
).decode("utf-8")
|
||||
except Exception:
|
||||
code_from_message = ""
|
||||
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
code_from_reply = (
|
||||
await self._client.download_file(reply.media, bytes)
|
||||
).decode("utf-8")
|
||||
except Exception:
|
||||
code_from_reply = ""
|
||||
|
||||
args = args or code_from_message or code_from_reply
|
||||
|
||||
message = await utils.answer(message, self.strings("loading"))
|
||||
|
||||
doc = io.BytesIO(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://carbonara-42.herokuapp.com/api/cook",
|
||||
json={"code": args},
|
||||
)
|
||||
).content
|
||||
)
|
||||
doc.name = "carbonized.jpg"
|
||||
|
||||
await self._client.send_message(
|
||||
utils.get_chat_id(message),
|
||||
file=doc,
|
||||
force_document=(len(args.splitlines()) > 50),
|
||||
reply_to=getattr(message, "reply_to_msg_id", None),
|
||||
)
|
||||
await message.delete()
|
||||
79
hikariatama/ftg/catboy.py
Normal file
79
hikariatama/ftg/catboy.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/catboy_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/catboy.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
|
||||
async def photo() -> str:
|
||||
return (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.catboys.com/img",
|
||||
)
|
||||
).json()["url"]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CatboyMod(loader.Module):
|
||||
"""Sends cute anime boy pictures"""
|
||||
|
||||
strings = {"name": "Catboy"}
|
||||
strings_ru = {"_cls_doc": "Отправляет фотографии милых аниме мальчиков"}
|
||||
strings_de = {"_cls_doc": "Sendet Anime-Katzenjungen-Bilder"}
|
||||
strings_uz = {"_cls_doc": "Anime o'g'irlar rasmlarini jo'natadi"}
|
||||
strings_hi = {"_cls_doc": "एक एनीमे कैटबॉय तस्वीर भेजें"}
|
||||
strings_tr = {"_cls_doc": "Anime kedi erkek resmi gönderir"}
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Показать кошкомальчика",
|
||||
de_doc="Zeigt ein Anime-Katzenjungen-Bild",
|
||||
uz_doc="Anime kishi rasmlarini ko'rsatadi",
|
||||
hi_doc="एक एनीमे कैटबॉय तस्वीर दिखाएं",
|
||||
tr_doc="Anime kedi erkek resmi gönderir",
|
||||
)
|
||||
async def catboycmd(self, message: Message):
|
||||
"""Send catboy picture"""
|
||||
await self.inline.gallery(
|
||||
caption=lambda: f"<i>{utils.ascii_face()}</i>",
|
||||
message=message,
|
||||
next_handler=photo,
|
||||
preload=5,
|
||||
)
|
||||
|
||||
@loader.inline_handler(
|
||||
ru_doc="Показать кошкомальчиков",
|
||||
de_doc="Zeigt Anime-Katzenjungen-Bilder",
|
||||
uz_doc="Anime kishi rasmlarini ko'rsatadi",
|
||||
hi_doc="एनीमे कैटबॉय तस्वीरें दिखाएं",
|
||||
tr_doc="Anime kedi erkek resimleri gönderir",
|
||||
)
|
||||
async def catboy(self, query: InlineQuery):
|
||||
"""Send Catboys"""
|
||||
await self.inline.query_gallery(
|
||||
query,
|
||||
[
|
||||
{
|
||||
"title": "👩🎤 Catboy",
|
||||
"description": "Send catboy photo",
|
||||
"next_handler": photo,
|
||||
"thumb_handler": photo,
|
||||
"caption": lambda: f"<i>Enjoy! {utils.ascii_face()}</i>",
|
||||
}
|
||||
],
|
||||
)
|
||||
74
hikariatama/ftg/catgirl.py
Normal file
74
hikariatama/ftg/catgirl.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/catgirl_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/catgirl.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
async def photo(nsfw: bool) -> str:
|
||||
tag = "not_found"
|
||||
while tag == "not_found":
|
||||
try:
|
||||
img = (
|
||||
await utils.run_sync(
|
||||
requests.get, "https://nekos.moe/api/v1/random/image"
|
||||
)
|
||||
).json()["images"][0]
|
||||
except KeyError:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
|
||||
tag = (
|
||||
"not_found"
|
||||
if img["nsfw"] and not nsfw or not img["nsfw"] and nsfw
|
||||
else "found"
|
||||
)
|
||||
|
||||
return f'https://nekos.moe/image/{img["id"]}.jpg'
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CatgirlMod(loader.Module):
|
||||
"""Sends cute anime girl pictures"""
|
||||
|
||||
strings = {"name": "Catgirl"}
|
||||
strings_ru = {"_cls_doc": "Отправляет милые фотографии аниме девочек"}
|
||||
strings_de = {"_cls_doc": "Sendet Anime-Katzenmädchen-Bilder"}
|
||||
strings_uz = {"_cls_doc": "Anime qiz rasmlarini jo'natadi"}
|
||||
strings_hi = {"_cls_doc": "एक एनीमे कैटगर्ल तस्वीर भेजें"}
|
||||
strings_tr = {"_cls_doc": "Anime kedi kız resmi gönderir"}
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[nsfw] - Показать кошкодевочку",
|
||||
de_doc="[nsfw] - Zeigt ein Anime-Katzenmädchen-Bild",
|
||||
uz_doc="[nsfw] - Anime qiz rasmlarini ko'rsatadi",
|
||||
hi_doc="[nsfw] - एक एनीमे कैटगर्ल तस्वीर दिखाएं",
|
||||
tr_doc="[nsfw] - Anime kedi kız resmi gönderir",
|
||||
)
|
||||
async def catgirlcmd(self, message: Message):
|
||||
"""[nsfw] - Send catgirl picture"""
|
||||
await self.inline.gallery(
|
||||
caption=lambda: f"<i>{utils.ascii_face()}</i>",
|
||||
message=message,
|
||||
next_handler=functools.partial(
|
||||
photo,
|
||||
nsfw="nsfw" in utils.get_args_raw(message).lower(),
|
||||
),
|
||||
preload=5,
|
||||
)
|
||||
332
hikariatama/ftg/checkege.py
Normal file
332
hikariatama/ftg/checkege.py
Normal file
@@ -0,0 +1,332 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# ©️ Dan Gazizullin, 2021-2023
|
||||
# This file is a part of Hikka Userbot
|
||||
# Code is licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
|
||||
# 🌐 https://github.com/hikariatama/Hikka
|
||||
# 🔑 https://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||
# + attribution
|
||||
# + non-commercial
|
||||
# + no-derivatives
|
||||
|
||||
# You CANNOT edit this file without direct permission from the author.
|
||||
# You can redistribute this file without any changes.
|
||||
|
||||
# meta pic: https://0x0.st/Hcj1.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/checkege.jpg
|
||||
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.6.2
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import hashlib
|
||||
import typing
|
||||
import warnings
|
||||
|
||||
import requests
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
SUBJECT_MAPPING = {
|
||||
"Русский": "<emoji document_id=5449408995691341691>🇷🇺</emoji>",
|
||||
"Математика": "<emoji document_id=5226470789682833538>➗</emoji>",
|
||||
"Физика": "<emoji document_id=5373039692574893940>👨🏫</emoji>",
|
||||
"География": "<emoji document_id=5454093069844487380>🗺</emoji>",
|
||||
"Информатика": "<emoji document_id=5431376038628171216>💻</emoji>",
|
||||
"Английский": "<emoji document_id=5202196682497859879>🇬🇧</emoji>",
|
||||
"Немецкий": "<emoji document_id=5409360418520967565>🇩🇪</emoji>",
|
||||
"Французский": "<emoji document_id=5202132623060640759>🇫🇷</emoji>",
|
||||
"Китайский": "<emoji document_id=5431782733376399004>🇨🇳</emoji>",
|
||||
"Общество": "<emoji document_id=5372926953978341366>👥</emoji>",
|
||||
"История": "<emoji document_id=5190941656274181429>👵</emoji>",
|
||||
"Литература": "<emoji document_id=5373098009640836781>📚</emoji>",
|
||||
"Химия": "<emoji document_id=5411512278740640309>🧪</emoji>",
|
||||
"Биология": "<emoji document_id=5460905716904633427>😺</emoji>",
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CheckEge(loader.Module):
|
||||
"""Checks Russian National Exam results"""
|
||||
|
||||
strings = {"name": "CheckEge"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"CHECKEGE_TOKEN",
|
||||
None,
|
||||
(
|
||||
"Токен CheckEge. Можно получить на https://checkege.rustest.ru из"
|
||||
" куки Participant. Если заполнить остальные поля конфига, при"
|
||||
" авторизации заполнится автоматически."
|
||||
),
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"FIO",
|
||||
None,
|
||||
(
|
||||
"ФИО, с которым нужно авторизоваться в формате Иванов Иван"
|
||||
" Иванович. Требует наличия RuCaptcha токена в конфиге."
|
||||
),
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"DOCUMENT",
|
||||
None,
|
||||
"Номер паспорта без серии. Требует наличия RuCaptcha токена в конфиге.",
|
||||
validator=loader.validators.Hidden(
|
||||
loader.validators.RegExp(r"^\d{6}$")
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"REGION",
|
||||
None,
|
||||
(
|
||||
"Код региона, в котором вы сдавали ЕГЭ. Можно посмотреть в"
|
||||
" https://gist.github.com/hikariatama/95f1a92dbe0379a88b6e673a1d79ed17."
|
||||
" Требует наличия RuCaptcha токена в конфиге."
|
||||
),
|
||||
validator=loader.validators.Hidden(
|
||||
loader.validators.RegExp(r"^\d{1,2}$")
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"RUCAPTCHA_TOKEN",
|
||||
None,
|
||||
"Токен RuCaptcha. Можно получить на https://rucaptcha.com",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"PROXY",
|
||||
None,
|
||||
"Прокси в формате http://user:pass@host:port",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
)
|
||||
|
||||
async def _auth(self):
|
||||
captcha = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://checkege.rustest.ru/api/captcha",
|
||||
proxies={"https": self.config["PROXY"]},
|
||||
verify=False,
|
||||
)
|
||||
).json()
|
||||
|
||||
captcha_img = base64.b64decode(captcha["Image"].encode())
|
||||
captcha_token = captcha["Token"]
|
||||
|
||||
captcha_id = (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://rucaptcha.com/in.php",
|
||||
data={
|
||||
"key": self.config["RUCAPTCHA_TOKEN"],
|
||||
"method": "post",
|
||||
"numeric": 1,
|
||||
"min_len": 6,
|
||||
"max_len": 6,
|
||||
},
|
||||
files={
|
||||
"file": ("captcha.png", captcha_img, "image/png"),
|
||||
},
|
||||
proxies={"https": self.config["PROXY"]},
|
||||
)
|
||||
).text.split("|")[1]
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(3)
|
||||
captcha_result = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://rucaptcha.com/res.php",
|
||||
params={
|
||||
"key": self.config["RUCAPTCHA_TOKEN"],
|
||||
"action": "get",
|
||||
"id": captcha_id,
|
||||
},
|
||||
proxies={"https": self.config["PROXY"]},
|
||||
)
|
||||
).text
|
||||
|
||||
if captcha_result != "CAPCHA_NOT_READY":
|
||||
break
|
||||
|
||||
captcha_result = captcha_result.split("|")[1]
|
||||
|
||||
self.config["CHECKEGE_TOKEN"] = dict(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://checkege.rustest.ru/api/participant/login",
|
||||
data={
|
||||
"Hash": hashlib.md5(
|
||||
self.config["FIO"].replace(" ", "").lower().encode()
|
||||
).hexdigest(),
|
||||
"Code": "",
|
||||
"Document": f"000000{self.config['DOCUMENT']}",
|
||||
"Region": self.config["REGION"],
|
||||
"AgreeCheck": "on",
|
||||
"Captcha": captcha_result,
|
||||
"Token": captcha_token,
|
||||
"reCaptureToken": captcha_result,
|
||||
},
|
||||
verify=False,
|
||||
proxies={"https": self.config["PROXY"]},
|
||||
)
|
||||
).cookies
|
||||
)["Participant"]
|
||||
|
||||
async def _get_result(self, retry: bool = True) -> typing.Union[dict, bool]:
|
||||
if not self.config["CHECKEGE_TOKEN"] and (
|
||||
not self.config["FIO"]
|
||||
or not self.config["DOCUMENT"]
|
||||
or not self.config["REGION"]
|
||||
):
|
||||
return False
|
||||
|
||||
if not self.config["CHECKEGE_TOKEN"]:
|
||||
await self._auth()
|
||||
|
||||
result = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://checkege.rustest.ru/api/exam",
|
||||
cookies={"Participant": self.config["CHECKEGE_TOKEN"]},
|
||||
verify=False,
|
||||
proxies={"https": self.config["PROXY"]},
|
||||
)
|
||||
).json()
|
||||
|
||||
if result.get("Message") == "Authorization has been denied for this request.":
|
||||
if retry:
|
||||
await self._auth()
|
||||
return await self._get_result(retry=False)
|
||||
|
||||
return False
|
||||
|
||||
return result
|
||||
|
||||
async def _format_result(self, result: dict) -> str:
|
||||
strings = ""
|
||||
for exam in result["Result"]["Exams"]:
|
||||
name, has_result, test_mark = (
|
||||
exam["Subject"],
|
||||
exam["HasResult"],
|
||||
exam["TestMark"],
|
||||
)
|
||||
emoji = next(
|
||||
(SUBJECT_MAPPING.get(n) for n in SUBJECT_MAPPING if n in name),
|
||||
"<emoji document_id=5470089812977391436>📕</emoji>",
|
||||
)
|
||||
|
||||
result = (
|
||||
(
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>зачёт</b>"
|
||||
if has_result and test_mark
|
||||
else (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji>"
|
||||
" <b>незачёт</b>"
|
||||
)
|
||||
)
|
||||
if name == "Сочинение"
|
||||
else (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji>"
|
||||
f" <b>{test_mark} балл(-ов)</b>"
|
||||
if has_result
|
||||
else (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>нет"
|
||||
" результата</b>"
|
||||
)
|
||||
)
|
||||
)
|
||||
strings += f"{emoji} <b>{name}</b> · {result}\n"
|
||||
|
||||
return strings
|
||||
|
||||
def _update_current_results(self, result: dict):
|
||||
self.set(
|
||||
"have_results",
|
||||
[
|
||||
(exam["ExamId"], exam["TestMark"])
|
||||
for exam in result["Result"]["Exams"]
|
||||
if exam["HasResult"]
|
||||
],
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def checkege(self, message):
|
||||
"""Авторизоваться и вывести результаты ЕГЭ"""
|
||||
if not self.config["CHECKEGE_TOKEN"] and (
|
||||
not self.config["FIO"]
|
||||
or not self.config["DOCUMENT"]
|
||||
or not self.config["REGION"]
|
||||
):
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Токен"
|
||||
" CheckEge не установлен.</b>\n\nАвторизуйтесь на"
|
||||
" https://checkege.rustest.ru и получите его из cookie Participant"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
message = await utils.answer(
|
||||
message,
|
||||
(
|
||||
"<emoji document_id=5465443379917629504>🔓</emoji> <b>Взламываю"
|
||||
" ФИПИ...</b>"
|
||||
),
|
||||
)
|
||||
if not (result := await self._get_result()):
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
"<emoji document_id=5463186335948878489>⚰️</emoji> <b>Неверный токен"
|
||||
" / данные авторизации!</b>"
|
||||
),
|
||||
)
|
||||
self.set("authorized", False)
|
||||
return
|
||||
|
||||
await utils.answer(message, await self._format_result(result))
|
||||
self.set("authorized", True)
|
||||
|
||||
@loader.loop(interval=30, autostart=True)
|
||||
async def check_loop(self):
|
||||
if not self.get("authorized"):
|
||||
return
|
||||
|
||||
if not (result := await self._get_result()):
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
(
|
||||
"⚰️ <b>Авторизация на CheckEge истекла, авторизоваться не"
|
||||
" получилось!</b>"
|
||||
),
|
||||
)
|
||||
|
||||
self.set("authorized", False)
|
||||
return
|
||||
|
||||
for exam in result["Result"]["Exams"]:
|
||||
if exam["HasResult"] and (exam["ExamId"], exam["TestMark"]) not in self.get(
|
||||
"have_results", []
|
||||
):
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
(
|
||||
f"🎉 Получен результат за экзамен <b>{exam['Subject']}</b>:"
|
||||
f" <b>{exam['TestMark']} балл(-ов)</b>"
|
||||
),
|
||||
)
|
||||
|
||||
self._update_current_results(result)
|
||||
71
hikariatama/ftg/cloud.py
Normal file
71
hikariatama/ftg/cloud.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/cloud_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/cloud.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import time
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ModuleCloudMod(loader.Module):
|
||||
"""Hikari modules management"""
|
||||
|
||||
strings = {
|
||||
"name": "ModuleCloud",
|
||||
"args": "🚫 <b>Args not specified</b>",
|
||||
"mod404": "🚫 <b>Module {} not found</b>",
|
||||
"ilink": (
|
||||
"<emoji document_id=5370705803051279512>🐹</emoji> <b><u>{name}</u> - <a"
|
||||
' href="https://mods.hikariatama.ru/view/{file}.py">source</a></b>\n<emoji'
|
||||
" document_id=5787544344906959608>ℹ️</emoji> <i>{desc}</i>\n\n<i>By"
|
||||
" @hikarimods with </i><emoji"
|
||||
" document_id=5875452644599795072>🔞</emoji>\n\n<emoji"
|
||||
" document_id=5188377234380954537>🌘</emoji> <code>.dlm {file}</code>"
|
||||
),
|
||||
"404": "😔 <b>Module not found</b>",
|
||||
"not_exact": (
|
||||
"⚠️ <b>No exact match occured, so the closest result is shown instead</b>"
|
||||
),
|
||||
}
|
||||
|
||||
@loader.unrestricted
|
||||
async def ilinkcmd(self, message: Message):
|
||||
"""<modname> - Get hikari module banner"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
badge = await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://mods.hikariatama.ru/badge/{args}",
|
||||
)
|
||||
|
||||
if badge.status_code == 404:
|
||||
await utils.answer(message, self.strings("mod404").format(args))
|
||||
return
|
||||
|
||||
img = requests.get(badge.json()["badge"] + f"?t={round(time.time())}").content
|
||||
info = badge.json()["info"]
|
||||
info["file"] = info["file"].split(".")[0]
|
||||
|
||||
if not message.media or not message.out:
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
img,
|
||||
caption=self.strings("ilink").format(**info),
|
||||
)
|
||||
await message.delete()
|
||||
else:
|
||||
await message.edit(self.strings("ilink").format(**info), file=img)
|
||||
159
hikariatama/ftg/craiyon.py
Normal file
159
hikariatama/ftg/craiyon.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
#
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/craiyon_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/craiyon.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import base64
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CrAIyonMod(loader.Module):
|
||||
"""Generates images by description using Craiyon AI (DALL-E)"""
|
||||
|
||||
strings = {
|
||||
"name": "CrAIyon",
|
||||
"args": "🚫 <b>No photo description specified</b>",
|
||||
"generating": (
|
||||
"🖌 <b>Drawing request </b><code>{}</code><b> using craiyon. Be patient,"
|
||||
" this takes some time</b>"
|
||||
),
|
||||
"error": "🚫 <b>I can't draw </b><code>{}</code>",
|
||||
"drawing": "🖌 <b>This is delicious </b><code>{}</code>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Не указано описание фотографии</b>",
|
||||
"generating": (
|
||||
"🖌 <b>Рисую запрос </b><code>{}</code><b> через craiyon. Будьте терпеливы,"
|
||||
" это занимает некоторое время</b>"
|
||||
),
|
||||
"error": "🚫 <b>Я не могу нарисовать </b><code>{}</code>",
|
||||
"drawing": "🖌 <b>Восхитительный </b><code>{}</code>",
|
||||
"_cmd_doc_craiyon": (
|
||||
"<описание> - Сгенерировать изображение по описанию с помощью Craiyon AI"
|
||||
" (DALL-E)"
|
||||
),
|
||||
"_cls_doc": "Генерирует изображения по описанию с помощью Craiyon AI (DALL-E)",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": "🚫 <b>Keine Bildbeschreibung angegeben</b>",
|
||||
"generating": (
|
||||
"🖌 <b>Zeichne Anfrage </b><code>{}</code><b> mit craiyon. Sei geduldig,"
|
||||
" das dauert ein wenig</b>"
|
||||
),
|
||||
"error": "🚫 <b>Kann nicht zeichnen </b><code>{}</code>",
|
||||
"drawing": "🖌 <b>Das ist lecker </b><code>{}</code>",
|
||||
"_cmd_doc_craiyon": (
|
||||
"<Beschreibung> - Generiert ein Bild nach Beschreibung mit Craiyon AI"
|
||||
" (DALL-E)"
|
||||
),
|
||||
"_cls_doc": "Generiert Bilder nach Beschreibung mit Craiyon AI (DALL-E)",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"args": "🚫 <b>कोई फोटो विवरण निर्दिष्ट नहीं किया गया</b>",
|
||||
"generating": (
|
||||
"🖌 <b>craiyon के साथ अनुरोध रचना </b><code>{}</code><b>। धैर्य रखें,"
|
||||
" यह कुछ समय लेता है</b>"
|
||||
),
|
||||
"error": "🚫 <b>मैं नहीं चित्र बना सकता </b><code>{}</code>",
|
||||
"drawing": "🖌 <b>यह अद्भुत है </b><code>{}</code>",
|
||||
"_cmd_doc_craiyon": (
|
||||
"<विवरण> - Craiyon AI (DALL-E) का उपयोग करके विवरण के अनुसार एक छवि उत्पन्न"
|
||||
" करता है"
|
||||
),
|
||||
"_cls_doc": "Craiyon AI (DALL-E) का उपयोग करके विवरण के अनुसार छवियां उत्पन्न करता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"args": "🚫 <b>Rasm tavsifi ko'rsatilmadi</b>",
|
||||
"generating": (
|
||||
"🖌 <b>craiyon orqali so'rovni chizish </b><code>{}</code><b>."
|
||||
" Sabr qiling, bu bir necha vaqt oladi</b>"
|
||||
),
|
||||
"error": "🚫 <b>Rasmni chizib bo'lmadi </b><code>{}</code>",
|
||||
"drawing": "🖌 <b>Bu juda yaxshi </b><code>{}</code>",
|
||||
"_cmd_doc_craiyon": (
|
||||
"<tavsif> - Craiyon AI (DALL-E) orqali tavsifga mos rasm yaratadi"
|
||||
),
|
||||
"_cls_doc": "Craiyon AI (DALL-E) orqali tavsifga mos rasmlar yaratadi",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"args": "🚫 <b>Fotoğraf açıklaması belirtilmedi</b>",
|
||||
"generating": (
|
||||
"🖌 <b>craiyon ile istek çizimi </b><code>{}</code><b>."
|
||||
" Sabırlı olun, bu biraz zaman alır</b>"
|
||||
),
|
||||
"error": "🚫 <b>Çizemiyorum </b><code>{}</code>",
|
||||
"drawing": "🖌 <b>Bu lezzetli </b><code>{}</code>",
|
||||
"_cmd_doc_craiyon": (
|
||||
"<açıklama> - Craiyon AI (DALL-E) kullanarak açıklamaya göre bir resim"
|
||||
" oluşturun"
|
||||
),
|
||||
"_cls_doc": "Craiyon AI (DALL-E) kullanarak açıklamaya göre resimler oluşturur",
|
||||
}
|
||||
|
||||
async def craiyoncmd(self, message: Message):
|
||||
"""<description> - Generate an image by description using Craiyon AI (DALL-E)"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
form = await self.inline.form(
|
||||
self.strings("generating").format(utils.escape_html(args)),
|
||||
message=message,
|
||||
gif="https://pa1.narvii.com/6074/b2f0163e5dd1ff7ee6582e1e032eb906b25228ac_hq.gif",
|
||||
silent=True,
|
||||
reply_markup={"text": "🧑🎨 Drawing...", "data": "empty"},
|
||||
ttl=24 * 60 * 60,
|
||||
)
|
||||
|
||||
result = (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://backend.craiyon.com/generate",
|
||||
json={"prompt": args},
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
|
||||
"content-type": "application/json",
|
||||
"origin": "https://www.craiyon.com",
|
||||
"referer": "https://www.craiyon.com/",
|
||||
"user-agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
" (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
if not result.get("images"):
|
||||
await form.edit(
|
||||
self.strings("error").format(args),
|
||||
reply_markup=None,
|
||||
gif="https://data.whicdn.com/images/61134119/original.gif",
|
||||
)
|
||||
return
|
||||
|
||||
images = [base64.b64decode(i.encode()) for i in result["images"]]
|
||||
await message.respond(self.strings("drawing").format(args), file=images)
|
||||
await form.delete()
|
||||
739
hikariatama/ftg/crypto.py
Normal file
739
hikariatama/ftg/crypto.py
Normal file
@@ -0,0 +1,739 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/3d-plastilina/344/3d-plastilina-three-quarter-view-of-a-bitcoin-emblem.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/crypto.jpg
|
||||
|
||||
import asyncio
|
||||
import difflib
|
||||
import logging
|
||||
import re
|
||||
|
||||
import requests
|
||||
from lxml import etree
|
||||
from telethon.errors.rpcerrorlist import BotResponseTimeoutError
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AMOUNT_REGEX = r"(?:Create check · |Создать чек · )(.*?)(?: ·|$)"
|
||||
INVOICE_AMOUNT_REGEX = (
|
||||
r"(?:Invoice for |Счёт на)(.*?)(?:\.$| with description.| с описанием.)"
|
||||
)
|
||||
RECEIVER_REGEX = r"(?:given to | для )(.*?)(?:\.| with| с описанием)"
|
||||
BALANCE_REGEX = r"(?:Available: |Доступно: )(.*)"
|
||||
|
||||
EMOJI_MAP = {
|
||||
"USDT": "<emoji document_id=6032709766881479783>💵</emoji>",
|
||||
"TON": "<emoji document_id=6032804204622384196>💵</emoji>",
|
||||
"BTC": "<emoji document_id=6032744483102133873>💵</emoji>",
|
||||
"ETH": "<emoji document_id=6032967271645711263>💵</emoji>",
|
||||
"BNB": "<emoji document_id=6032733926072520137>💵</emoji>",
|
||||
"BUSD": "<emoji document_id=6033097439219551284>💵</emoji>",
|
||||
"USDC": "<emoji document_id=6030553792083135328>💵</emoji>",
|
||||
}
|
||||
|
||||
RATES_CONFIG = {
|
||||
"USD": "<emoji document_id=6323374027985389586>🇺🇸</emoji> <b>USD: {} $</b>",
|
||||
"RUB": "<emoji document_id=6323139226418284334>🇷🇺</emoji> <b>RUB: {} ₽</b>",
|
||||
"EUR": "<emoji document_id=6323217102765295143>🇪🇺</emoji> <b>EUR: {} €</b>",
|
||||
"UAH": "<emoji document_id=6323289850921354919>🇺🇦</emoji> <b>UAH: {} ₴</b>",
|
||||
"KZT": "<emoji document_id=6323135275048371614>🇰🇿</emoji> <b>KZT: {} ₸</b>",
|
||||
"PLN": "<emoji document_id=6323602387101550101>🇵🇱</emoji> <b>PLN: {} zł</b>",
|
||||
"UZS": "<emoji document_id=6323430017179059570>🇺🇿</emoji> <b>UZS: {} сўм</b>",
|
||||
"INR": "<emoji document_id=6323181871148566277>🇮🇳</emoji> <b>INR: {} ₹</b>",
|
||||
"TRY": "<emoji document_id=6321003171678259486>🇹🇷</emoji> <b>TRY: {} ₺</b>",
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Crypto(loader.Module):
|
||||
"""Some basic stuff with cryptocurrencies and @CryptoBot"""
|
||||
|
||||
strings = {
|
||||
"name": "Crypto",
|
||||
"no_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>You need to specify"
|
||||
" args</b>"
|
||||
),
|
||||
"incorrect_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Incorrect args</b>"
|
||||
),
|
||||
"insufficient_funds": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Insufficient"
|
||||
" funds</b>"
|
||||
),
|
||||
"empty_balance": (
|
||||
"<emoji document_id=5370646412243510708>😭</emoji> <b>You don't have any"
|
||||
" money</b>"
|
||||
),
|
||||
"confirm_check": (
|
||||
"👛 <b>Please, confirm that info below is valid:</b>\n\n<b>🪙 Amount:"
|
||||
" {amount}</b>{receiver}{comment}\n\n{balance}"
|
||||
),
|
||||
"confirm_invoice": (
|
||||
"👛 <b>Please, confirm that info below is valid:</b>\n\n<b>🪙 Amount:"
|
||||
" {amount}</b>{comment}\n\n{balance}"
|
||||
),
|
||||
"check": (
|
||||
"{emoji} <b>Check for {amount}</b>{receiver}{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Receive'
|
||||
" funds</a></b>"
|
||||
),
|
||||
"invoice": (
|
||||
"{emoji} <b>Invoice for {amount}</b>{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Proceed'
|
||||
" with payment</a></b>"
|
||||
),
|
||||
"comment": "\n💬 <b>Comment: </b><i>{}</i>",
|
||||
"receiver": "\n👤 <b>Receiver: </b><i>{}</i>",
|
||||
"available": "💰 <b>Available: </b><i>{}</i>",
|
||||
"send_check": "👛 Send check",
|
||||
"send_invoice": "👛 Send invoice",
|
||||
"cancel": "🔻 Cancel",
|
||||
"wallet": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Your <a"
|
||||
' href="{}">CryptoBot</a> wallet:</b>\n\n{}'
|
||||
),
|
||||
"multi-use_invoice": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b><a"
|
||||
' href="{url}">Multi-use invoice</a></b>'
|
||||
),
|
||||
"exchange_rates": "{emoji} <b>{amount} {name} exchange rates:</b>\n\n{rates}",
|
||||
"processing_rates": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Stealing some crypto"
|
||||
" from exchange...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Ты должен указать"
|
||||
" аргументы</b>"
|
||||
),
|
||||
"incorrect_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Неверные"
|
||||
" аргументы</b>"
|
||||
),
|
||||
"insufficient_funds": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Недостаточно"
|
||||
" средств</b>"
|
||||
),
|
||||
"confirm_check": (
|
||||
"👛 <b>Пожалуйста, подтвердите, что информация ниже верна:</b>\n\n<b>🪙"
|
||||
" Сумма: {amount}</b>{receiver}{comment}\n\n{balance}"
|
||||
),
|
||||
"confirm_invoice": (
|
||||
"👛 <b>Пожалуйста, подтвердите, что информация ниже верна:</b>\n\n<b>🪙"
|
||||
" Сумма: {amount}</b>{comment}\n\n{balance}"
|
||||
),
|
||||
"check": (
|
||||
"{emoji} <b>Чек на {amount}</b>{receiver}{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Получить'
|
||||
" средства</a></b>"
|
||||
),
|
||||
"invoice": (
|
||||
"{emoji} <b>Счёт на {amount}</b>{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Оплатить'
|
||||
"</a></b>"
|
||||
),
|
||||
"comment": "\n💬 <b>Комментарий: </b><i>{}</i>",
|
||||
"receiver": "\n👤 <b>Получатель: </b><i>{}</i>",
|
||||
"available": "💰 <b>Доступно: </b><i>{}</i>",
|
||||
"send_check": "👛 Отправить чек",
|
||||
"send_invoice": "👛 Отправить счёт",
|
||||
"cancel": "🔻 Отмена",
|
||||
"wallet": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Твой <a"
|
||||
' href="{}">CryptoBot</a> кошелек:</b>\n\n{}'
|
||||
),
|
||||
"multi-use_invoice": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b><a"
|
||||
' href="{url}">Многоразовый счёт</a></b>'
|
||||
),
|
||||
"processing_rates": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Краду криптовалюту с"
|
||||
" биржи...</b>"
|
||||
),
|
||||
"exchange_rates": "{emoji} <b>Курс {amount} {name}:</b>\n\n{rates}",
|
||||
"empty_balance": (
|
||||
"<emoji document_id=5370646412243510708>😭</emoji> <b>На балансе ни"
|
||||
" гроша</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"no_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Du musst Argumente"
|
||||
" angeben</b>"
|
||||
),
|
||||
"incorrect_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Falsche Argumente</b>"
|
||||
),
|
||||
"insufficient_funds": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Unzureichende"
|
||||
" Mittel</b>"
|
||||
),
|
||||
"confirm_check": (
|
||||
"👛 <b>Bitte bestätige, dass die folgenden Informationen korrekt sind:</b>"
|
||||
"\n\n<b>🪙 Betrag: {amount}</b>{receiver}{comment}\n\n{balance}"
|
||||
),
|
||||
"confirm_invoice": (
|
||||
"👛 <b>Bitte bestätige, dass die folgenden Informationen korrekt sind:</b>"
|
||||
"\n\n<b>🪙 Betrag: {amount}</b>{comment}\n\n{balance}"
|
||||
),
|
||||
"check": (
|
||||
"{emoji} <b>Check für {amount}</b>{receiver}{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Erhalte'
|
||||
" Zahlung</a></b>"
|
||||
),
|
||||
"invoice": (
|
||||
"{emoji} <b>Rechnung für {amount}</b>{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Bezahle'
|
||||
"</a></b>"
|
||||
),
|
||||
"comment": "\n💬 <b>Kommentar: </b><i>{}</i>",
|
||||
"receiver": "\n👤 <b>Empfänger: </b><i>{}</i>",
|
||||
"available": "💰 <b>Verfügbar: </b><i>{}</i>",
|
||||
"send_check": "👛 Senden Sie den Scheck",
|
||||
"send_invoice": "👛 Senden Sie die Rechnung",
|
||||
"cancel": "🔻 Stornieren",
|
||||
"wallet": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Deine <a"
|
||||
' href="{}">CryptoBot</a> Brieftasche:</b>\n\n{}'
|
||||
),
|
||||
"multi-use_invoice": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b><a"
|
||||
' href="{url}">Mehrfachrechnung</a></b>'
|
||||
),
|
||||
"processing_rates": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Ich stehle"
|
||||
" Kryptowährung von der Börse...</b>"
|
||||
),
|
||||
"exchange_rates": "{emoji} <b>Kurs {amount} {name}:</b>\n\n{rates}",
|
||||
"empty_balance": (
|
||||
"<emoji document_id=5370646412243510708>😭</emoji> <b>Nichts auf dem"
|
||||
" Konto</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"no_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Siz argumentlar"
|
||||
" berishingiz kerak</b>"
|
||||
),
|
||||
"incorrect_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Noto'g'ri"
|
||||
" argumentlar</b>"
|
||||
),
|
||||
"insufficient_funds": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Kifoya pul yo'q</b>"
|
||||
),
|
||||
"confirm_check": (
|
||||
"👛 <b>Iltimos, quyidagi ma'lumotlarning to'g'ri yoki yo'qligini"
|
||||
" tekshiring:</b>\n\n<b>🪙 Summa:"
|
||||
" {amount}</b>{receiver}{comment}\n\n{balance}"
|
||||
),
|
||||
"confirm_invoice": (
|
||||
"👛 <b>Iltimos, quyidagi ma'lumotlarning to'g'ri yoki yo'qligini"
|
||||
" tekshiring:</b>\n\n<b>🪙 Summa: {amount}</b>{comment}\n\n{balance}"
|
||||
),
|
||||
"check": (
|
||||
"{emoji} <b>{amount} uchun chex</b>{receiver}{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">To\'lov'
|
||||
"</a></b>"
|
||||
),
|
||||
"invoice": (
|
||||
"{emoji} <b>{amount} uchun chalan</b>{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">To\'lov'
|
||||
"</a></b>"
|
||||
),
|
||||
"comment": "\n💬 <b>Izoh: </b><i>{}</i>",
|
||||
"receiver": "\n👤 <b>Qabul qiluvchi: </b><i>{}</i>",
|
||||
"available": "💰 <b>Mavjud: </b><i>{}</i>",
|
||||
"send_check": "👛 Chexni yuborish",
|
||||
"send_invoice": "👛 Chalan yuborish",
|
||||
"cancel": "🔻 Bekor qilish",
|
||||
"wallet": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Sizning <a"
|
||||
' href="{}">CryptoBot</a> botingiz:</b>\n\n{}'
|
||||
),
|
||||
"multi-use_invoice": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b><a"
|
||||
' href="{url}">Bir qatorda ko\'p foydalanish uchun chalan</a></b>'
|
||||
),
|
||||
"processing_rates": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Mening bozoridan"
|
||||
" kriptoni chorayman...</b>"
|
||||
),
|
||||
"exchange_rates": "{emoji} <b>{amount} {name} narxi:</b>\n\n{rates}",
|
||||
"empty_balance": (
|
||||
"<emoji document_id=5370646412243510708>😭</emoji> <b>Hisob bo'sh</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"no_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Argümanlarınız"
|
||||
" gerekli</b>"
|
||||
),
|
||||
"incorrect_args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Yanlış argümanlar</b>"
|
||||
),
|
||||
"insufficient_funds": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b>Yeterli bakiye"
|
||||
" yok</b>"
|
||||
),
|
||||
"confirm_check": (
|
||||
"👛 <b>Lütfen, aşağıdaki bilgilerin doğru veya yanlış olduğunu kontrol"
|
||||
" edin:</b>\n\n<b>🪙 Miktar: {amount}</b>{receiver}{comment}\n\n{balance}"
|
||||
),
|
||||
"confirm_invoice": (
|
||||
"👛 <b>Lütfen, aşağıdaki bilgilerin doğru veya yanlış olduğunu"
|
||||
" kontrol edin:</b>\n\n<b>🪙 Miktar: {amount}</b>{comment}\n\n{balance}"
|
||||
),
|
||||
"check": (
|
||||
"{emoji} <b>{amount} için çek</b>{receiver}{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Ödeme'
|
||||
"</a></b>"
|
||||
),
|
||||
"invoice": (
|
||||
"{emoji} <b>{amount} için fatura</b>{comment}\n\n<emoji"
|
||||
' document_id=5188509837201252052>💸</emoji> <b><a href="{link}">Ödeme'
|
||||
"</a></b>"
|
||||
),
|
||||
"comment": "\n💬 <b>Yorum: </b><i>{}</i>",
|
||||
"receiver": "\n👤 <b>Alıcı: </b><i>{}</i>",
|
||||
"available": "💰 <b>Mevcut: </b><i>{}</i>",
|
||||
"send_check": "👛 Çeki yolla",
|
||||
"send_invoice": "👛 Faturayı yolla",
|
||||
"cancel": "🔻 İptal et",
|
||||
"wallet": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b><a"
|
||||
' href="{}">CryptoBot</a> cüzdanınız:</b>\n\n{}'
|
||||
),
|
||||
"multi-use_invoice": (
|
||||
"<emoji document_id=5472363448404809929>👛</emoji> <b><a"
|
||||
' href="{url}">Tek kullanımlık fatura</a></b>'
|
||||
),
|
||||
"processing_rates": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Kripto para"
|
||||
" değiştiriyorum...</b>"
|
||||
),
|
||||
"exchange_rates": "{emoji} <b>{amount} {name} fiyatı:</b>\n\n{rates}",
|
||||
"empty_balance": (
|
||||
"<emoji document_id=5370646412243510708>😭</emoji> <b>Bakiye boş</b>"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.bot = "@CryptoBot"
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"spoiler_balance",
|
||||
True,
|
||||
"Hide balance under spoiler",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"hide_balance",
|
||||
False,
|
||||
"Do not show balance at all",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"valutes",
|
||||
list(RATES_CONFIG),
|
||||
"Valutes to show in exchange rates",
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.Choice(list(RATES_CONFIG))
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"use_testnet",
|
||||
False,
|
||||
"Use testnet version of CryptoBot",
|
||||
validator=loader.validators.Boolean(),
|
||||
on_change=lambda: asyncio.ensure_future(self._process_config()),
|
||||
),
|
||||
)
|
||||
|
||||
async def _process_config(self):
|
||||
await asyncio.sleep(0.5)
|
||||
self.bot = "@CryptoBot" if not self.config["debug"] else "@CryptoTestnetBot"
|
||||
|
||||
async def _form_action(
|
||||
self,
|
||||
call: InlineCall,
|
||||
args: str,
|
||||
message: Message,
|
||||
formatting: dict,
|
||||
name: str,
|
||||
index: int,
|
||||
):
|
||||
query = await self._client.inline_query(self.bot, args)
|
||||
result = await query[index].click("me")
|
||||
await result.delete()
|
||||
await self._client.send_message(
|
||||
message.peer_id,
|
||||
self.strings(name).format(
|
||||
**formatting,
|
||||
link=result.reply_markup.rows[0].buttons[0].url,
|
||||
emoji=next(
|
||||
(
|
||||
emoji
|
||||
for trigger, emoji in EMOJI_MAP.items()
|
||||
if trigger in query[0].description
|
||||
),
|
||||
"<emoji document_id=5471952986970267163>💎</emoji>",
|
||||
),
|
||||
),
|
||||
reply_to=message.reply_to_msg_id,
|
||||
link_preview=False,
|
||||
)
|
||||
await call.delete()
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<сумма> [человек] [комментарий] - Выписать чек",
|
||||
de_doc="<Betrag> [Person] [Kommentar] - Ausstellen eines Schecks",
|
||||
tr_doc="<miktar> [kişi] [yorum] - Çek çıkar",
|
||||
uz_doc="<miqdor> [odam] [izoh] - Chiqarish chiqoni",
|
||||
hi_doc="<राशि> [व्यक्ति] [टिप्पणी] - चेक बनाएं",
|
||||
)
|
||||
async def check(self, message: Message):
|
||||
"""<amount> [person] [comment] - Send check"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
if args.split()[0] == "0":
|
||||
receiver = (
|
||||
args.split()[1]
|
||||
if len(args.split()) > 1 and args.split()[1].startswith("@")
|
||||
else ""
|
||||
)
|
||||
if receiver:
|
||||
receiver = self.strings("receiver").format(receiver)
|
||||
|
||||
comment = (
|
||||
(
|
||||
args.split(maxsplit=2)[2]
|
||||
if len(args.split()) > 2 and args.split()[1].startswith("@")
|
||||
else args.split(maxsplit=1)[1]
|
||||
)
|
||||
if len(args.split()) > 1
|
||||
else ""
|
||||
)
|
||||
|
||||
if comment:
|
||||
comment = self.strings("comment").format(comment)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("check").format(
|
||||
amount="1.205487 BTC (25621.80$)",
|
||||
comment=comment,
|
||||
receiver=receiver,
|
||||
link="https://www.youtube.com/watch?v=hGA6MGBuaCs",
|
||||
emoji=EMOJI_MAP["BTC"],
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
query = await asyncio.wait_for(
|
||||
self._client.inline_query(self.bot, args),
|
||||
timeout=3000,
|
||||
)
|
||||
except (BotResponseTimeoutError, asyncio.TimeoutError):
|
||||
await utils.answer(message, self.strings("incorrect_args"))
|
||||
return
|
||||
|
||||
article = query[0].description.strip()
|
||||
article_t = query[0].title.strip()
|
||||
if not article.startswith("Check") and not article.startswith("Чек"):
|
||||
await utils.answer(message, self.strings("insufficient_funds"))
|
||||
return
|
||||
|
||||
amount = re.search(AMOUNT_REGEX, article_t)[1]
|
||||
if re.search(RECEIVER_REGEX, article):
|
||||
receiver = self.strings("receiver").format(
|
||||
utils.escape_html(re.search(RECEIVER_REGEX, article)[1])
|
||||
)
|
||||
else:
|
||||
receiver = ""
|
||||
|
||||
if re.search(BALANCE_REGEX, article) and not self.config["hide_balance"]:
|
||||
balance = self.strings("available").format(
|
||||
(
|
||||
"<tg-spoiler>{}</tg-spoiler>"
|
||||
if self.config["spoiler_balance"]
|
||||
else "{}"
|
||||
).format(utils.escape_html(re.search(BALANCE_REGEX, article)[1]))
|
||||
)
|
||||
else:
|
||||
balance = ""
|
||||
|
||||
comment = args.split(maxsplit=1)[1] if len(args.split()) > 1 else ""
|
||||
if receiver:
|
||||
comment = comment.split(maxsplit=1)[1] if len(comment.split()) > 1 else ""
|
||||
|
||||
if comment:
|
||||
comment = self.strings("comment").format(utils.escape_html(comment))
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("confirm_check").format(
|
||||
amount=amount,
|
||||
comment=comment,
|
||||
receiver=receiver,
|
||||
balance=balance,
|
||||
),
|
||||
reply_markup=[
|
||||
{
|
||||
"text": self.strings("send_check"),
|
||||
"callback": self._form_action,
|
||||
"args": (
|
||||
args,
|
||||
message,
|
||||
{"amount": amount, "comment": comment, "receiver": receiver},
|
||||
"check",
|
||||
0,
|
||||
),
|
||||
},
|
||||
{"text": self.strings("cancel"), "action": "close"},
|
||||
],
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Показать баланс криптокошелька",
|
||||
de_doc="Zeige den Kryptowährungsbetrag",
|
||||
tr_doc="Kripto cüzdanınızın bakiyesini göster",
|
||||
uz_doc="Kriptovalyuta portfelingizdagi balansni ko'rsatish",
|
||||
hi_doc="क्रिप्टो वॉलेट की शेष राशि दिखाएं",
|
||||
)
|
||||
async def wallet(self, message: Message):
|
||||
"""Show wallet balance"""
|
||||
async with self._client.conversation(self.bot) as conv:
|
||||
m = await conv.send_message("/wallet")
|
||||
r = await conv.get_response()
|
||||
|
||||
await m.delete()
|
||||
buttons = utils.array_sum([row.buttons for row in r.reply_markup.rows])
|
||||
button = next(
|
||||
(btn for btn in buttons if btn.text == "Show Small Balances"), None
|
||||
)
|
||||
if button:
|
||||
await r.click(data=button.data)
|
||||
r = (await self._client.get_messages(r.peer_id, ids=[r.id]))[0]
|
||||
|
||||
await r.delete()
|
||||
|
||||
info = "\n\n".join(
|
||||
f"{next((emoji for trigger, emoji in EMOJI_MAP.items() if trigger in line), '<emoji document_id=5471952986970267163>💎</emoji>')} <b>{line.split(maxsplit=1)[1]}</b>"
|
||||
for line in r.raw_text.splitlines()
|
||||
if line.startswith("·") and ": 0 " not in line
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
self.strings("wallet").format(
|
||||
f"https://t.me/{self.bot.strip('@')}", info
|
||||
)
|
||||
if info
|
||||
else self.strings("empty_balance")
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[-o - не создавать новый] - Отправить мультисчёт",
|
||||
de_doc="[-o - erstelle keine neue] - Sende eine Mehrfachzahlung",
|
||||
tr_doc="[-o - yeni oluşturma] - Çoklu ödeme gönder",
|
||||
uz_doc="[-o - yangi yaratmaslik] - Ko'p mablag'li to'lovni yuborish",
|
||||
hi_doc="[-o - नया नहीं बनाएं] - एकाधिक भुगतान भेजें",
|
||||
)
|
||||
async def muinvoice(self, message: Message):
|
||||
"""[-o - don't create new one] Send multi-use invoice"""
|
||||
if "-o" in utils.get_args_raw(message) and self.get("muinvoice_url"):
|
||||
url = self.get("muinvoice_url")
|
||||
else:
|
||||
query = await self._client.inline_query(self.bot, "")
|
||||
m = await query[0].click("me")
|
||||
await m.delete()
|
||||
url = m.reply_markup.rows[0].buttons[0].url
|
||||
self.set("muinvoice_url", url)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("multi-use_invoice").format(url=url),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<сумма> [комментарий] - Выставить счет",
|
||||
de_doc="<Betrag> [Kommentar] - Stelle eine Rechnung aus",
|
||||
tr_doc="<miktar> [yorum] - Fatura çıkar",
|
||||
uz_doc="<miqdor> [izoh] - Hisobni chiqarish",
|
||||
hi_doc="<राशि> [टिप्पणी] - चालान बनाएं",
|
||||
)
|
||||
async def invoice(self, message: Message):
|
||||
"""<amount> [comment] - Send invoice"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
try:
|
||||
query = await asyncio.wait_for(
|
||||
self._client.inline_query(self.bot, args),
|
||||
timeout=3000,
|
||||
)
|
||||
except (BotResponseTimeoutError, asyncio.TimeoutError):
|
||||
await utils.answer(message, self.strings("incorrect_args"))
|
||||
return
|
||||
|
||||
article = query[-1].description.strip()
|
||||
if not article.startswith("Invoice") and not article.startswith("Счёт"):
|
||||
await utils.answer(message, self.strings("insufficient_funds"))
|
||||
return
|
||||
|
||||
amount = re.search(INVOICE_AMOUNT_REGEX, article)[1]
|
||||
|
||||
if re.search(BALANCE_REGEX, article) and not self.config["hide_balance"]:
|
||||
balance = self.strings("available").format(
|
||||
(
|
||||
"<tg-spoiler>{}</tg-spoiler>"
|
||||
if self.config["spoiler_balance"]
|
||||
else "{}"
|
||||
).format(utils.escape_html(re.search(BALANCE_REGEX, article)[1]))
|
||||
)
|
||||
else:
|
||||
balance = ""
|
||||
|
||||
comment = args.split(maxsplit=1)[1] if len(args.split()) > 1 else ""
|
||||
if comment:
|
||||
comment = self.strings("comment").format(utils.escape_html(comment))
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("confirm_invoice").format(
|
||||
amount=amount,
|
||||
comment=comment,
|
||||
balance=balance,
|
||||
),
|
||||
reply_markup=[
|
||||
{
|
||||
"text": self.strings("send_invoice"),
|
||||
"callback": self._form_action,
|
||||
"args": (
|
||||
args,
|
||||
message,
|
||||
{"amount": amount, "comment": comment},
|
||||
"invoice",
|
||||
-1,
|
||||
),
|
||||
},
|
||||
{"text": self.strings("cancel"), "action": "close"},
|
||||
],
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[amount] <name> - Показать курс криптовалюты",
|
||||
de_doc="[Betrag] <Name> - Zeige den Kurs der Kryptowährung",
|
||||
tr_doc="[miktar] <isim> - Kripto para biriminin kurunu göster",
|
||||
uz_doc="[miqdor] <nomi> - Kriptovalyutaning kursini ko'rsatish",
|
||||
hi_doc="[राशि] <नाम> - क्रिप्टोकरेंसी की दर दिखाएं",
|
||||
)
|
||||
async def rates(self, message: Message):
|
||||
"""[amount] <name> - Show cryptocurrency exchange rates"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
if len(args.split()) > 1 and args[0].isdigit():
|
||||
amount = float(args.split(maxsplit=1)[0])
|
||||
args = args.split(maxsplit=1)[1]
|
||||
else:
|
||||
amount = 1
|
||||
|
||||
message = await utils.answer(message, self.strings("processing_rates"))
|
||||
|
||||
valutes = {
|
||||
valute.getchildren()[1].text: float(
|
||||
valute.getchildren()[4].text.replace(",", ".")
|
||||
) / int(valute.getchildren()[2].text)
|
||||
for valute in etree.fromstring(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get, "https://www.cbr.ru/scripts/XML_daily.asp"
|
||||
)
|
||||
).content
|
||||
).getchildren()
|
||||
}
|
||||
|
||||
def to_RUB(price_usd: float) -> float:
|
||||
return price_usd * valutes["USD"]
|
||||
|
||||
def to_XXX(price_usd: float, name: str) -> float:
|
||||
return to_RUB(price_usd) / valutes[name]
|
||||
|
||||
crypto = {
|
||||
crypto["symbol"]: {
|
||||
"rates": {
|
||||
"USD": float(crypto["priceUsd"]),
|
||||
"RUB": to_RUB(float(crypto["priceUsd"])),
|
||||
**{
|
||||
name: to_XXX(float(crypto["priceUsd"]), name)
|
||||
for name in self.config["valutes"]
|
||||
if name not in {"USD", "RUB"}
|
||||
},
|
||||
},
|
||||
"name": crypto["name"],
|
||||
}
|
||||
for crypto in (
|
||||
await utils.run_sync(requests.get, "https://api.coincap.io/v2/assets")
|
||||
).json()["data"]
|
||||
}
|
||||
|
||||
closest_crypto = difflib.get_close_matches(
|
||||
args.upper(),
|
||||
crypto.keys(),
|
||||
n=1,
|
||||
)
|
||||
if not closest_crypto:
|
||||
await utils.answer(message, self.strings("incorrect_args"))
|
||||
return
|
||||
|
||||
exchange_rates = crypto[closest_crypto[0]]["rates"]
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("exchange_rates").format(
|
||||
emoji=next(
|
||||
(
|
||||
emoji
|
||||
for name, emoji in EMOJI_MAP.items()
|
||||
if name in closest_crypto[0] or closest_crypto[0] in name
|
||||
),
|
||||
"<emoji document_id=5471952986970267163>💎</emoji>",
|
||||
),
|
||||
name=crypto[closest_crypto[0]]["name"],
|
||||
rates="\n".join(
|
||||
RATES_CONFIG[valute].format(
|
||||
f"{exchange_rates[valute] * amount:_.2f}".replace("_", " ")
|
||||
)
|
||||
for valute in self.config["valutes"]
|
||||
),
|
||||
amount=amount,
|
||||
),
|
||||
)
|
||||
133
hikariatama/ftg/cryptosteal.py
Normal file
133
hikariatama/ftg/cryptosteal.py
Normal file
@@ -0,0 +1,133 @@
|
||||
__version__ = (1, 0, 0)
|
||||
|
||||
# ©️ Dan Gazizullin, 2021-2023
|
||||
# This file is a part of Hikka Userbot
|
||||
# Code is licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
|
||||
# 🌐 https://github.com/hikariatama/Hikka
|
||||
# 🔑 https://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||
# + attribution
|
||||
# + non-commercial
|
||||
# + no-derivatives
|
||||
|
||||
# You CANNOT edit this file without direct permission from the author.
|
||||
# You can redistribute this file without any changes.
|
||||
|
||||
# meta pic: https://ton.org/download/ton_symbol.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/cryptosteal.jpg
|
||||
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.6.3
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
|
||||
from hikkatl.tl.functions.messages import StartBotRequest
|
||||
from hikkatl.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CryptoSteal(loader.Module):
|
||||
"""Steals checks for crypto"""
|
||||
|
||||
strings = {"name": "CryptoSteal"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"delay",
|
||||
100,
|
||||
"Delay before claiming the check in ms",
|
||||
validator=loader.validators.Integer(minimum=10),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"bots",
|
||||
["cryptobot", "wallet", "cryptotestnetbot"],
|
||||
"Bots from which the checks should be captured",
|
||||
validator=loader.validators.Series(loader.validators.String()),
|
||||
on_change=lambda: asyncio.ensure_future(self._process_config()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"only_inline",
|
||||
False,
|
||||
(
|
||||
"Capture only checks sent through inline mode of the bots, not just"
|
||||
" links"
|
||||
),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"token_length_limit",
|
||||
32,
|
||||
"To avoid database overflow",
|
||||
validator=loader.validators.Integer(minimum=1),
|
||||
),
|
||||
)
|
||||
|
||||
self._acquired = []
|
||||
self._regex: str = None
|
||||
self._regex_ready = asyncio.Event()
|
||||
|
||||
async def _process_config(self):
|
||||
self._regex_ready.clear()
|
||||
|
||||
whitelist = []
|
||||
for entity in self.config["bots"]:
|
||||
with contextlib.suppress(Exception):
|
||||
whitelist.append(
|
||||
re.escape(
|
||||
(await self._client.get_entity(entity, exp=0)).username.lower()
|
||||
)
|
||||
)
|
||||
|
||||
self._regex = f"t\\.me\\/(?i:(?P<bot>{'|'.join(whitelist)}))\\?start=(?P<token>[a-zA-Z0-9+/_-]+)"
|
||||
self._regex_ready.set()
|
||||
|
||||
async def _acquire(self, bot: str, token: str):
|
||||
if (
|
||||
token.lower() in self._acquired
|
||||
or len(token) > self.config["token_length_limit"]
|
||||
):
|
||||
return
|
||||
|
||||
self._acquired.append(token.lower())
|
||||
|
||||
await asyncio.sleep(self.config["delay"] / 1000)
|
||||
|
||||
await self._client(
|
||||
StartBotRequest(
|
||||
bot=bot,
|
||||
peer=bot,
|
||||
start_param=token,
|
||||
)
|
||||
)
|
||||
|
||||
@loader.watcher("in", "only_messages")
|
||||
async def watcher(self, message: Message):
|
||||
await self._regex_ready.wait()
|
||||
|
||||
if not self.config["only_inline"] and (
|
||||
match := re.search(
|
||||
self._regex,
|
||||
message.text,
|
||||
)
|
||||
):
|
||||
await self._acquire(match.group("bot"), match.group("token"))
|
||||
|
||||
if (
|
||||
message.reply_markup
|
||||
and (url := getattr(message.reply_markup.rows[0].buttons[0], "url", None))
|
||||
and (
|
||||
match := re.search(
|
||||
self._regex,
|
||||
url,
|
||||
)
|
||||
)
|
||||
):
|
||||
await self._acquire(match.group("bot"), match.group("token"))
|
||||
322
hikariatama/ftg/declaration.py
Normal file
322
hikariatama/ftg/declaration.py
Normal file
@@ -0,0 +1,322 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/stickers/344/love-message.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/declaration.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.5.3
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import BotMessage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Declaration(loader.Module):
|
||||
"""If you are too humble to declare your love, use this module"""
|
||||
|
||||
strings = {
|
||||
"name": "Declaration",
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>This command must be"
|
||||
" runned in personal messages...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>You have 1 new"
|
||||
' message. <a href="https://t.me/{}?start=read_{}">Please, read it</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>Hi. I'm <b>Hikka</b>.</i>",
|
||||
(
|
||||
"🫣 <i>My owner is very humble to say something, so he asked me to help"
|
||||
" him...</i>"
|
||||
),
|
||||
"🥰 <i>He just wanted you to know, that <b>he loves you</b>...</i>",
|
||||
"🤗 <i>These are sincere feelings... Please, don't blame him.</i>",
|
||||
"🫶 <i>Better say him some warm words... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 Talk",
|
||||
"404": "😢 <b>Message has already disappeared. You can't read it now...</b>",
|
||||
"read": "🫰 <b>{} has read your declaration</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Wrong"
|
||||
" arguments...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Эту команду нужно"
|
||||
" выполнять в личных сообщениях...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>У вас 1 новое"
|
||||
' сообщение. <a href="https://t.me/{}?start=read_{}">Пожалуйста, прочтите'
|
||||
" его</a></b>"
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>Привет. Я <b>Хикка</b>.</i>",
|
||||
(
|
||||
"🫣 <i>Мой хозяин очень стесняется сказать о чем-то, поэтому он"
|
||||
" попросил меня помочь ему...</i>"
|
||||
),
|
||||
"🥰 <i>Он просто хотел, чтобы Вы знали, что <b>он любит Вас</b>...</i>",
|
||||
"🤗 <i>Это искренние чувства... Пожалуйста, не злитесь на него.</i>",
|
||||
"🫶 <i>Лучше скажите ему несколько теплых слов... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 Поговорить",
|
||||
"404": "😢 <b>Сообщение уже исчезло. Вы не можете его прочитать...</b>",
|
||||
"read": "🫰 <b>{} прочитал ваше признание</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Неверные"
|
||||
" аргументы...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Diese Befehl muss in"
|
||||
" privaten Nachrichten ausgeführt werden...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>Du hast 1 neue"
|
||||
' Nachricht. <a href="https://t.me/{}?start=read_{}">Bitte, lies es</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>Hallo. Ich bin <b>Hikka</b>.</i>",
|
||||
(
|
||||
"🫣 <i>Mein Besitzer ist zu bescheiden, um etwas zu sagen, also hat er"
|
||||
" mich gebeten, ihm zu helfen...</i>"
|
||||
),
|
||||
"🥰 <i>Er wollte nur, dass du weißt, dass <b>er dich liebt</b>...</i>",
|
||||
"🤗 <i>Das sind ehrliche Gefühle... Bitte, verzeih ihm.</i>",
|
||||
"🫶 <i>Sag ihm besser ein paar warme Worte... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 Reden",
|
||||
"404": (
|
||||
"😢 <b>Die Nachricht ist bereits verschwunden. Du kannst sie jetzt nicht"
|
||||
" lesen...</b>"
|
||||
),
|
||||
"read": "🫰 <b>{} hat dein Geständnis gelesen</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Falsche"
|
||||
" Argumente...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>यह कमांड निजी"
|
||||
" संदेशों में चलाए जाने चाहिए...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>आपके पास 1 नया संदेश"
|
||||
' है। <a href="https://t.me/{}?start=read_{}">कृपया, उसे पढ़ें</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>नमस्ते। मैं <b>हिक्का</b> हूँ।</i>",
|
||||
(
|
||||
"🫣 <i>मेरे मालिक को कुछ कहने के लिए बहुत बारीच है, इसलिए उन्होंने"
|
||||
" मुझे उनकी मदद करने के लिए कहा...</i>"
|
||||
),
|
||||
"🥰 <i>उसने आपको सिर्फ यह बताना चाहता था कि <b>वह आपको पसंद करता है</b>...</i>",
|
||||
"🤗 <i>ये सच्चे भावनाएं हैं... कृपया उसे माफ़ करें।</i>",
|
||||
"🫶 <i>उसे बेहतर शब्दों के साथ बोलें... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 बात करना",
|
||||
"404": "😢 <b>संदेश पहले ही नष्ट हो गया है। आप इसे अब पढ़ नहीं सकते...</b>",
|
||||
"read": "🫰 <b>आपने {} के लिए अपना प्रार्थना पढ़ा</b>",
|
||||
"args": "<emoji document_id=6053166094816905153>💀</emoji> <b>गलत तर्क...</b>",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Bu komut özel"
|
||||
" mesajlarda çalıştırılmalıdır...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>Yeni 1 mesajınız var."
|
||||
' <a href="https://t.me/{}?start=read_{}">Lütfen, okuyun</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>Merhaba. Ben <b>Hikka</b>.</i>",
|
||||
"🫣 <i>Sahibim bir şey söylemekten çekinince, yardım etmeme söyledi...</i>",
|
||||
"🥰 <i>Sadece ona <b>seni sevdiğini</b> söylemek istedi...</i>",
|
||||
"🤗 <i>Bu gerçek duygular... Lütfen, affet.</i>",
|
||||
"🫶 <i>Bunu ona daha iyi söyle... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 Konuş",
|
||||
"404": "😢 <b>Mesaj zaten kaybolmuş. Okuyamazsın...</b>",
|
||||
"read": "🫰 <b>{} senin itirafını okudu</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Yanlış argüman...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ja = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>このコマンドはプライベート"
|
||||
" メッセージで実行される必要があります...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji>"
|
||||
" <b>新しい1つのメッセージがあります。"
|
||||
' <a href="https://t.me/{}?start=read_{}">読んでください</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>こんにちは。 私は<b>ヒッカ</b>です。</i>",
|
||||
"🫣 <i>主人が何か言いたくないので、助けてほしいと言った...</i>",
|
||||
"🥰 <i>彼はただ<b>あなたを愛している</b>と言いたかった...</i>",
|
||||
"🤗 <i>これは本当の感情です... 許してください。</i>",
|
||||
"🫶 <i>もっと良い言葉で言ってください... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 会話",
|
||||
"404": (
|
||||
"😢 <b>メッセージはすでに消えています。"
|
||||
" あなたはそれを読むことはできません...</b>"
|
||||
),
|
||||
"read": "🫰 <b>{}はあなたの告白を読みました</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>間違った引数...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_kr = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>이 명령은 개인"
|
||||
" 메시지에서 실행되어야합니다...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>새로운 메시지가 있습니다."
|
||||
' <a href="https://t.me/{}?start=read_{}">읽어주세요</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>안녕하세요. 나는 <b>히카</b>입니다.</i>",
|
||||
"🫣 <i>주인이 무언가를 말하고 싶지 않아서 도움을 요청했습니다...</i>",
|
||||
"🥰 <i>그저 그에게 <b>너를 사랑한다</b>고 말하고 싶었습니다...</i>",
|
||||
"🤗 <i>이것은 진짜 감정입니다... 용서해주세요.</i>",
|
||||
"🫶 <i>더 좋은 말로 말하세요... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 대화",
|
||||
"404": "😢 <b>메시지는 이미 삭제되었습니다. 읽을 수 없습니다...</b>",
|
||||
"read": "🫰 <b>{} 당신의 고백을 읽었습니다</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>잘못된 인수...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ar = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>هذا الأمر يجب أن يتم"
|
||||
" تنفيذه في رسالة خاصة...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>هناك رسالة جديدة."
|
||||
' <a href="https://t.me/{}?start=read_{}">اقرأ</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>مرحبا. أنا <b>هيكا</b>.</i>",
|
||||
"🫣 <i>طلب المالك مساعدة لأنه لا يريد أن يقول شيئا...</i>",
|
||||
"🥰 <i>أراد فقط أن يقول له <b>أنا أحبك</b>...</i>",
|
||||
"🤗 <i>هذه حقيقة العواطف... يرجى التكرم.</i>",
|
||||
"🫶 <i>قلها بطريقة أفضل... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 محادثة",
|
||||
"404": "😢 <b>تم حذف الرسالة بالفعل. لا يمكن قراءتها...</b>",
|
||||
"read": "🫰 <b>{} قرأت إعترافك</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>وسيطغير صالح...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_es = {
|
||||
"not_private": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Este comando debe"
|
||||
" ejecutarse en mensaje privado...</b>"
|
||||
),
|
||||
"ily": (
|
||||
"<emoji document_id=5465143921912846619>💭</emoji> <b>Tienes un nuevo"
|
||||
' mensaje. <a href="https://t.me/{}?start=read_{}">Lee</a></b>'
|
||||
),
|
||||
"ily_love": [
|
||||
"👋 <i>Hola. Soy <b>Hika</b>.</i>",
|
||||
"🫣 <i>El dueño solicitó ayuda porque no quería decir nada...</i>",
|
||||
"🥰 <i>Simplemente quería decirle <b>te amo</b>...</i>",
|
||||
"🤗 <i>Esto es real... Por favor perdóname.</i>",
|
||||
"🫶 <i>Dilo mejor... 🙂</i>",
|
||||
],
|
||||
"talk": "🫰 Conversación",
|
||||
"404": "😢 <b>El mensaje ya ha sido eliminado. No se puede leer...</b>",
|
||||
"read": "🫰 <b>{} leyó tu confesión</b>",
|
||||
"args": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Argumento"
|
||||
"no válido...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.ids = self.pointer("declarations", {})
|
||||
|
||||
@loader.command(ru_doc="Признаться в любви")
|
||||
async def declare(self, message: Message):
|
||||
"""Declare love"""
|
||||
if not message.is_private:
|
||||
await utils.answer(message, self.strings("not_private"))
|
||||
return
|
||||
|
||||
id_ = utils.rand(8)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("ily").format(self.inline.bot_username, id_),
|
||||
)
|
||||
self.ids[id_] = int(time.time()) + 24 * 60 * 60
|
||||
|
||||
async def aiogram_watcher(self, message: BotMessage):
|
||||
if not message.text.startswith("/start read_"):
|
||||
return
|
||||
|
||||
for id_, info in self.ids.copy().items():
|
||||
if info < int(time.time()):
|
||||
self.ids.pop(id_)
|
||||
continue
|
||||
|
||||
id_ = message.text.split("_")[1]
|
||||
if id_ not in self.ids:
|
||||
await message.answer(self.strings("404"))
|
||||
return
|
||||
|
||||
info = self.ids.pop(id_)
|
||||
for m in self.strings("ily_love")[:-1]:
|
||||
await message.answer(m)
|
||||
await asyncio.sleep(random.randint(350, 400) / 100)
|
||||
|
||||
await self.inline.bot.send_message(
|
||||
self._client.tg_id,
|
||||
self.strings("read").format(
|
||||
utils.escape_html(message.from_user.full_name),
|
||||
),
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
self.strings("ily_love")[-1],
|
||||
reply_markup=self.inline.generate_markup(
|
||||
{
|
||||
"text": self.strings("talk"),
|
||||
"url": f"tg://user?id={self._client.tg_id}",
|
||||
}
|
||||
),
|
||||
)
|
||||
183
hikariatama/ftg/deepl.py
Normal file
183
hikariatama/ftg/deepl.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/external-xnimrodx-lineal-color-xnimrodx/512/000000/external-translate-discussion-xnimrodx-lineal-color-xnimrodx.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/deepl.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
import typing
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
async def translate(text: str, target: str, proxy: dict) -> str:
|
||||
a = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://www2.deepl.com/jsonrpc?method=LMT_handle_jobs",
|
||||
headers={
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,"
|
||||
" like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"Content-type": "application/json",
|
||||
"Accept": "*/*",
|
||||
"Sec-GPC": "1",
|
||||
"Origin": "https://www.deepl.com",
|
||||
"Sec-Fetch-Site": "same-site",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Referer": "https://www.deepl.com/",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9,ru;q=0.8",
|
||||
},
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"method": "LMT_handle_jobs",
|
||||
"params": {
|
||||
"jobs": [
|
||||
{
|
||||
"kind": "default",
|
||||
"sentences": [{"text": text, "id": 0, "prefix": ""}],
|
||||
"raw_en_context_before": [],
|
||||
"raw_en_context_after": [],
|
||||
"preferred_num_beams": 4,
|
||||
"quality": "fast",
|
||||
}
|
||||
],
|
||||
"lang": {
|
||||
"user_preferred_langs": ["EN", "DE", "RU"],
|
||||
"source_lang_user_selected": "auto",
|
||||
"target_lang": target.upper(),
|
||||
},
|
||||
"priority": -1,
|
||||
"commonJobParams": {
|
||||
"regionalVariant": None,
|
||||
"browserType": 1,
|
||||
"formality": None,
|
||||
},
|
||||
"timestamp": time.time() * 1000,
|
||||
},
|
||||
"id": random.randint(1000000, 99999999),
|
||||
},
|
||||
proxies=proxy,
|
||||
)
|
||||
|
||||
try:
|
||||
return a.json()["result"]["translations"][0]["beams"][0]["sentences"][0]["text"]
|
||||
except Exception:
|
||||
logger.error(a.text)
|
||||
try:
|
||||
return f"Error while translating: {a.json()['error']['message']}"
|
||||
except Exception:
|
||||
return "Error while translating"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class DeepLMod(loader.Module):
|
||||
"""Translates text via DeepL scraping. Proxies are recommended"""
|
||||
|
||||
strings = {
|
||||
"name": "DeepLScraper",
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>No text specified</b>"
|
||||
),
|
||||
"translated": "🇺🇸 <code>{}</code>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Не указан текст</b>"
|
||||
),
|
||||
"translated": "🇺🇸 <code>{}</code>",
|
||||
"_cmd_doc_deepl": "<text or reply> - Перевести текст через DeepL",
|
||||
"_cls_doc": "Переводит текст через DeepL. Рекомендуется использовать прокси",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Kein Text"
|
||||
" angegeben</b>"
|
||||
),
|
||||
"translated": "🇺🇸 <code>{}</code>",
|
||||
"_cmd_doc_deepl": "<Text oder Antwort> - Übersetze Text über DeepL",
|
||||
"_cls_doc": (
|
||||
"Übersetzt Text über DeepL. Es wird empfohlen, einen Proxy zu verwenden"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Matn"
|
||||
" ko'rsatilmadi</b>"
|
||||
),
|
||||
"translated": "🇺🇸 <code>{}</code>",
|
||||
"_cmd_doc_deepl": "<matn yoki javob> - DeepL orqali matnni tarjima qilish",
|
||||
"_cls_doc": (
|
||||
"DeepL orqali matnni tarjima qilish. Proxydan foydalanish maslahat beriladi"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>कोई टेक्स्ट नहीं दिया"
|
||||
" गया</b>"
|
||||
),
|
||||
"translated": "🇺🇸 <code>{}</code>",
|
||||
"_cmd_doc_deepl": "<टेक्स्ट या उत्तर> - डीपएल के माध्यम से पाठ का अनुवाद करें",
|
||||
"_cls_doc": (
|
||||
"डीपएल के माध्यम से पाठ का अनुवाद करता है। प्रॉक्सी का उपयोग करने की सलाह दी जाती है"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Metin"
|
||||
" belirtilmedi</b>"
|
||||
),
|
||||
"translated": "🇺🇸 <code>{}</code>",
|
||||
"_cmd_doc_deepl": "<metin veya yanıt> - DeepL ile metni çevir",
|
||||
"_cls_doc": "DeepL ile metni çevirir. Proxy kullanmanız önerilir",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue("proxy", "", lambda: "Proxy url")
|
||||
)
|
||||
|
||||
async def deeplcmd(self, message: Message):
|
||||
"""<text or reply> - Translate text via DeepL scraping"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
target = (
|
||||
"en" if not args or "->" not in args else args[args.find("->") + 2 :][:2]
|
||||
)
|
||||
args = args.replace(f"->{target}", "")
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
if not args and (not reply or not reply.raw_text):
|
||||
await utils.answer(message, self.strings("no_text"))
|
||||
return
|
||||
|
||||
text = args or reply.raw_text
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("translated").format(
|
||||
await translate(text, target, {"https": self.config["proxy"]})
|
||||
),
|
||||
)
|
||||
121
hikariatama/ftg/dictionary.py
Normal file
121
hikariatama/ftg/dictionary.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/dictionary_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/dictionary.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: aiohttp urllib bs4
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import logging
|
||||
import re
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import aiohttp
|
||||
from bs4 import BeautifulSoup
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logging.getLogger("charset_normalizer").setLevel(logging.ERROR)
|
||||
|
||||
HEADERS = {
|
||||
"accept": "text/html",
|
||||
"user-agent": "Hikka userbot",
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class UrbanDictionaryMod(loader.Module):
|
||||
"""Search for words meaning in urban dictionary"""
|
||||
|
||||
strings = {
|
||||
"name": "UrbanDictionary",
|
||||
"no_args": "🚫 <b>Specify term to find the definition for</b>",
|
||||
"err": "🧞♂️ <b>I don't know about term </b><code>{}</code>",
|
||||
"no_page": "🚫 Can't switch to that page",
|
||||
"meaning": "🧞♂️ <b><u>{}</u></b>:\n\n<i>{}</i>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_args": "🚫 <b>Укажи, для какого слова искать определение</b>",
|
||||
"err": "🧞♂️ <b>Я не знаю, что значит </b><code>{}</code>",
|
||||
"no_page": "🚫 Нельзя переключиться на эту страницу",
|
||||
"meaning": "🧞♂️ <b><u>{}</u></b>:\n\n<i>{}</i>",
|
||||
"_cmd_doc_mean": "<слова> - Найти определение слова в UrbanDictionary",
|
||||
"_cls_doc": "Ищет определения слов в UrbanDictionary",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"no_args": "🚫 <b>Gib ein Wort ein, um dessen Bedeutung zu finden</b>",
|
||||
"err": "🧞♂️ <b>Ich weiß nicht, was </b><code>{}</code><b> bedeutet</b>",
|
||||
"no_page": "🚫 Du kannst nicht zu dieser Seite wechseln",
|
||||
"meaning": "🧞♂️ <b><u>{}</u></b>:\n\n<i>{}</i>",
|
||||
"_cmd_doc_mean": "<Wort> - Finde die Bedeutung eines Wortes in UrbanDictionary",
|
||||
"_cls_doc": "Sucht nach Bedeutungen von Wörtern in UrbanDictionary",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"no_args": "🚫 <b>किस शब्द के लिए परिभाषा ढूंढने के लिए निर्दिष्ट करें</b>",
|
||||
"err": "🧞♂️ <b>मैं नहीं जानता है कि </b><code>{}</code><b> क्या मतलब है</b>",
|
||||
"no_page": "🚫 आप इस पृष्ठ पर नहीं जा सकते",
|
||||
"meaning": "🧞♂️ <b><u>{}</u></b>:\n\n<i>{}</i>",
|
||||
"_cmd_doc_mean": "<शब्द> - उर्बन डिक्शनरी में शब्द का अर्थ ढूंढें",
|
||||
"_cls_doc": "उर्बन डिक्शनरी में शब्दों के अर्थ ढूंढता है",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"no_args": "🚫 <b>Bir kelimenin anlamını bulmak için belirtin</b>",
|
||||
"err": "🧞♂️ <b>Bilmiyorum </b><code>{}</code><b> ne demek</b>",
|
||||
"no_page": "🚫 Bu sayfaya geçemezsiniz",
|
||||
"meaning": "🧞♂️ <b><u>{}</u></b>:\n\n<i>{}</i>",
|
||||
"_cmd_doc_mean": "<kelime> - UrbanDictionary'de bir kelimenin anlamını bulun",
|
||||
"_cls_doc": "UrbanDictionary'de kelimelerin anlamlarını arar",
|
||||
}
|
||||
|
||||
async def scrape(self, term: str) -> str:
|
||||
term = "".join(
|
||||
[
|
||||
i.lower()
|
||||
for i in term
|
||||
if i.lower()
|
||||
in "абвгдежзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz "
|
||||
]
|
||||
)
|
||||
endpoint = "https://www.urbandictionary.com/define.php?term={}"
|
||||
url = endpoint.format(quote_plus(term.lower()))
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.request("GET", url, headers=HEADERS) as resp:
|
||||
html = await resp.text()
|
||||
|
||||
soup = BeautifulSoup(re.sub(r"<br.*?>", "♠️", html), "html.parser")
|
||||
|
||||
return [
|
||||
definition.get_text().replace("♠️", "\n")
|
||||
for definition in soup.find_all("div", class_="meaning")
|
||||
]
|
||||
|
||||
async def meancmd(self, message: Message):
|
||||
"""<term> - Find definition of the word in urban dictionary"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
means = await self.scrape(args)
|
||||
|
||||
if not means:
|
||||
await utils.answer(message, self.strings("err").format(args))
|
||||
return
|
||||
|
||||
await self.inline.list(
|
||||
message=message,
|
||||
strings=[self.strings("meaning").format(args, mean) for mean in means],
|
||||
)
|
||||
283
hikariatama/ftg/dnd_statuses.py
Normal file
283
hikariatama/ftg/dnd_statuses.py
Normal file
@@ -0,0 +1,283 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/dnd_statuses_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/dnd_statuses.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class StatusesMod(loader.Module):
|
||||
"""AFK Module analog with extended functionality"""
|
||||
|
||||
strings = {
|
||||
"name": "Statuses",
|
||||
"status_not_found": "<b>🚫 Status not found</b>",
|
||||
"status_set": "<b>✅ Status set\n</b><code>{}</code>\nNotify: {}",
|
||||
"pzd_with_args": "<b>🚫 Args are incorrect</b>",
|
||||
"status_created": "<b>✅ Status {} created\n</b><code>{}</code>\nNotify: {}",
|
||||
"status_removed": "<b>✅ Status {} deleted</b>",
|
||||
"no_status": "<b>🚫 No status is active</b>",
|
||||
"status_unset": "<b>✅ Status removed</b>",
|
||||
"available_statuses": "<b>🦊 Available statuses:</b>\n\n",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"status_not_found": "<b>🚫 Статус не найден</b>",
|
||||
"status_set": "<b>✅ Статус установлен\n</b><code>{}</code>\nУведомлять: {}",
|
||||
"pzd_with_args": "<b>🚫 Неверные аргументы</b>",
|
||||
"status_created": "<b>✅ Статус {} создан\n</b><code>{}</code>\nУведомлять: {}",
|
||||
"status_removed": "<b>✅ Статус {} удален</b>",
|
||||
"no_status": "<b>🚫 Сейчас нет активного статуса</b>",
|
||||
"status_unset": "<b>✅ Статус удален</b>",
|
||||
"available_statuses": "<b>🦊 Доступные статусы:</b>\n\n",
|
||||
"_cmd_doc_status": "<short_name> - Установить статус",
|
||||
"_cmd_doc_newstatus": (
|
||||
"<short_name> <уведомлять|0/1> <текст> - Создать новый статус\nПример:"
|
||||
" .newstatus test 1 Hello!"
|
||||
),
|
||||
"_cmd_doc_delstatus": "<short_name> - Удалить статус",
|
||||
"_cmd_doc_unstatus": "Удалить статус",
|
||||
"_cmd_doc_statuses": "Показать доступные статусы",
|
||||
"_cls_doc": "AFK модуль с расширенным функционалом",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"status_not_found": "<b>🚫 Status nicht gefunden</b>",
|
||||
"status_set": "<b>✅ Status gesetzt\n</b><code>{}</code>\nBenachrichtigen: {}",
|
||||
"pzd_with_args": "<b>🚫 Falsche Argumente</b>",
|
||||
"status_created": (
|
||||
"<b>✅ Status {} erstellt\n</b><code>{}</code>\nBenachrichtigen: {}"
|
||||
),
|
||||
"status_removed": "<b>✅ Status {} gelöscht</b>",
|
||||
"no_status": "<b>🚫 Es ist kein Status aktiv</b>",
|
||||
"status_unset": "<b>✅ Status gelöscht</b>",
|
||||
"available_statuses": "<b>🦊 Verfügbarer Status:</b>\n\n",
|
||||
"_cmd_doc_status": "<short_name> - Setze Status",
|
||||
"_cmd_doc_newstatus": (
|
||||
"<short_name> <benachrichtigen|0/1> <text> - Erstelle neuen"
|
||||
" Status\nBeispiel: .newstatus test 1 Hallo!"
|
||||
),
|
||||
"_cmd_doc_delstatus": "<short_name> - Lösche Status",
|
||||
"_cmd_doc_unstatus": "Lösche Status",
|
||||
"_cmd_doc_statuses": "Zeige verfügbare Status",
|
||||
"_cls_doc": "AFK Modul mit erweitertem Funktionsumfang",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"status_not_found": "<b>🚫 Status topilmadi</b>",
|
||||
"status_set": "<b>✅ Status o'rnatildi\n</b><code>{}</code>\nBildirish: {}",
|
||||
"pzd_with_args": "<b>🚫 Argumetlarni xato kiritdingiz</b>",
|
||||
"status_created": (
|
||||
"<b>✅ Status {} yaratildi\n</b><code>{}</code>\nBildirish: {}"
|
||||
),
|
||||
"status_removed": "<b>✅ Status {} o'chirildi</b>",
|
||||
"no_status": "<b>🚫 Hozircha aktiv status yo'q</b>",
|
||||
"status_unset": "<b>✅ Status o'chirildi</b>",
|
||||
"available_statuses": "<b>🦊 Mavjud statuslar:</b>\n\n",
|
||||
"_cmd_doc_status": "<short_name> - Statusni o'rnatish",
|
||||
"_cmd_doc_newstatus": (
|
||||
"<short_name> <bildirish|0/1> <matn> - Yangi status yaratish\nMasalan:"
|
||||
" .newstatus test 1 Salom!"
|
||||
),
|
||||
"_cmd_doc_delstatus": "<short_name> - Statusni o'chirish",
|
||||
"_cmd_doc_unstatus": "Statusni o'chirish",
|
||||
"_cmd_doc_statuses": "Mavjud statuslarni ko'rsatish",
|
||||
"_cls_doc": "AFK moduli kengaytirilgan funktsiyalari bilan",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"status_not_found": "<b>🚫 Durum bulunamadı</b>",
|
||||
"status_set": "<b>✅ Durum ayarlandı\n</b><code>{}</code>\nBildirim: {}",
|
||||
"pzd_with_args": "<b>🚫 Yanlış argümanlar</b>",
|
||||
"status_created": (
|
||||
"<b>✅ Durum {} oluşturuldu\n</b><code>{}</code>\nBildirim: {}"
|
||||
),
|
||||
"status_removed": "<b>✅ Durum {} kaldırıldı</b>",
|
||||
"no_status": "<b>🚫 Şu anda aktif durum yok</b>",
|
||||
"status_unset": "<b>✅ Durum kaldırıldı</b>",
|
||||
"available_statuses": "<b>🦊 Mevcut durumlar:</b>\n\n",
|
||||
"_cmd_doc_status": "<short_name> - Durum ayarla",
|
||||
"_cmd_doc_newstatus": (
|
||||
"<short_name> <bildirim|0/1> <metin> - Yeni durum oluştur\nÖrnek:"
|
||||
" .newstatus test 1 Merhaba!"
|
||||
),
|
||||
"_cmd_doc_delstatus": "<short_name> - Durum kaldır",
|
||||
"_cmd_doc_unstatus": "Durum kaldır",
|
||||
"_cmd_doc_statuses": "Mevcut durumları göster",
|
||||
"_cls_doc": "AFK modülü genişletilmiş özelliklerle",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"status_not_found": "<b>🚫 स्थिति नहीं मिली</b>",
|
||||
"status_set": "<b>✅ स्थिति सेट की गई\n</b><code>{}</code>\nसूचित करना: {}",
|
||||
"pzd_with_args": "<b>🚫 गलत तर्क</b>",
|
||||
"status_created": (
|
||||
"<b>✅ स्थिति {} बनाया गया\n</b><code>{}</code>\nसूचित करना: {}"
|
||||
),
|
||||
"status_removed": "<b>✅ स्थिति {} हटाया गया</b>",
|
||||
"no_status": "<b>🚫 अभी कोई सक्रिय स्थिति नहीं है</b>",
|
||||
"status_unset": "<b>✅ स्थिति हटाया गया</b>",
|
||||
"available_statuses": "<b>🦊 उपलब्ध स्थितियां:</b>\n\n",
|
||||
"_cmd_doc_status": "<short_name> - स्थिति सेट करें",
|
||||
"_cmd_doc_newstatus": (
|
||||
"<short_name> <सूचित करना|0/1> <पाठ> - नया स्थिति बनाएं\nउदाहरण:"
|
||||
" .newstatus test 1 हैलो!"
|
||||
),
|
||||
"_cmd_doc_delstatus": "<short_name> - स्थिति हटाएं",
|
||||
"_cmd_doc_unstatus": "स्थिति हटाएं",
|
||||
"_cmd_doc_statuses": "उपलब्ध स्थितियों को दिखाएं",
|
||||
"_cls_doc": "एफके मॉड्यूल विस्तारित सुविधाओं के साथ",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self._sent_messages = []
|
||||
|
||||
@loader.tag("only_messages", "in")
|
||||
async def watcher(self, message: Message):
|
||||
if not self.get("status", False):
|
||||
return
|
||||
|
||||
if message.is_private:
|
||||
user = await message.get_sender()
|
||||
if user.id in self._ratelimit or user.is_self or user.bot or user.verified:
|
||||
return
|
||||
elif not message.mentioned:
|
||||
return
|
||||
|
||||
chat = utils.get_chat_id(message)
|
||||
|
||||
if chat in self._ratelimit:
|
||||
return
|
||||
|
||||
m = await utils.answer(
|
||||
message,
|
||||
self.get("texts", {"": ""})[self.get("status", "")],
|
||||
)
|
||||
|
||||
self._sent_messages += [m]
|
||||
|
||||
if not self.get("notif", {"": False})[self.get("status", "")]:
|
||||
await self._client.send_read_acknowledge(
|
||||
message.peer_id,
|
||||
clear_mentions=True,
|
||||
)
|
||||
|
||||
self._ratelimit += [chat]
|
||||
|
||||
async def statuscmd(self, message: Message):
|
||||
"""<short_name> - Set status"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args not in self.get("texts", {}):
|
||||
await utils.answer(message, self.strings("status_not_found"))
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
self.set("status", args)
|
||||
self._ratelimit = []
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("status_set").format(
|
||||
utils.escape_html(self.get("texts", {})[args]),
|
||||
str(self.get("notif")[args]),
|
||||
),
|
||||
)
|
||||
|
||||
async def newstatuscmd(self, message: Message):
|
||||
"""<short_name> <notif|0/1> <text> - New status
|
||||
Example: .newstatus test 1 Hello!"""
|
||||
args = utils.get_args_raw(message)
|
||||
args = args.split(" ", 2)
|
||||
if len(args) < 3:
|
||||
await utils.answer(message, self.strings("pzd_with_args"))
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
args[1] = args[1] in ["1", "true", "yes", "+"]
|
||||
texts = self.get("texts", {})
|
||||
texts[args[0]] = args[2]
|
||||
self.set("texts", texts)
|
||||
|
||||
notif = self.get("notif", {})
|
||||
notif[args[0]] = args[1]
|
||||
self.set("notif", notif)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("status_created").format(
|
||||
utils.escape_html(args[0]),
|
||||
utils.escape_html(args[2]),
|
||||
args[1],
|
||||
),
|
||||
)
|
||||
|
||||
async def delstatuscmd(self, message: Message):
|
||||
"""<short_name> - Delete status"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args not in self.get("texts", {}):
|
||||
await utils.answer(message, self.strings("status_not_found"))
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
texts = self.get("texts", {})
|
||||
del texts[args]
|
||||
self.set("texts", texts)
|
||||
|
||||
notif = self.get("notif", {})
|
||||
del notif[args]
|
||||
self.set("notif", notif)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("status_removed").format(utils.escape_html(args)),
|
||||
)
|
||||
|
||||
async def unstatuscmd(self, message: Message):
|
||||
"""Remove status"""
|
||||
if not self.get("status", False):
|
||||
await utils.answer(message, self.strings("no_status"))
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
self.set("status", False)
|
||||
self._ratelimit = []
|
||||
|
||||
for m in self._sent_messages:
|
||||
try:
|
||||
await m.delete()
|
||||
except Exception:
|
||||
logger.exception("Message not deleted due to")
|
||||
|
||||
self._sent_messages = []
|
||||
|
||||
await utils.answer(message, self.strings("status_unset"))
|
||||
|
||||
async def statusescmd(self, message: Message):
|
||||
"""Show available statuses"""
|
||||
res = self.strings("available_statuses")
|
||||
for short_name, status in self.get("texts", {}).items():
|
||||
res += (
|
||||
f"<b><u>{short_name}</u></b> | Notify:"
|
||||
f" <b>{self.get('notif', {})[short_name]}</b>\n{status}\n➖➖➖➖➖➖➖➖➖\n"
|
||||
)
|
||||
|
||||
await utils.answer(message, res)
|
||||
58
hikariatama/ftg/donations.py
Normal file
58
hikariatama/ftg/donations.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/donations_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/donations.jpg
|
||||
# meta desc: [RU] Create donate widgets through Hikari.Donations platform
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HikariDonationsMod(loader.Module):
|
||||
"""Создает виджеты для доната"""
|
||||
|
||||
strings = {"name": "HikariDonations", "args": "🚫 <b>Неверные аргументы</b>"}
|
||||
|
||||
async def donatecmd(self, message: Message):
|
||||
"""<сумма> <описание> - Создать новый донатный виджет в текущем чате"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args or len(args.split()) < 2 or not args.split()[0].isdigit():
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
amount = int(args.split()[0])
|
||||
target = args.split(maxsplit=1)[1]
|
||||
|
||||
if amount not in range(1, 50001):
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
amount = str(amount)
|
||||
|
||||
async with self._client.conversation("@hikaridonate_bot") as conv:
|
||||
for msg in ["/widget", target, amount]:
|
||||
m = await conv.send_message(msg)
|
||||
r = await conv.get_response()
|
||||
|
||||
await m.delete()
|
||||
await r.delete()
|
||||
|
||||
widget_id = r.reply_markup.rows[0].buttons[0].query
|
||||
q = await self._client.inline_query("@hikaridonate_bot", widget_id)
|
||||
await q[0].click(message.peer_id)
|
||||
await message.delete()
|
||||
109
hikariatama/ftg/dyslexia.py
Normal file
109
hikariatama/ftg/dyslexia.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/240/000000/apple-music-lyrics.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/dyslexia.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import re
|
||||
from random import shuffle
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
def dyslex(text: str) -> str:
|
||||
res = ""
|
||||
for word in text.split():
|
||||
newline = False
|
||||
if "\n" in word:
|
||||
word = word.replace("\n", "")
|
||||
newline = True
|
||||
|
||||
to_shuffle = re.sub(r"[^a-zA-Zа-яА-Я0-9]", "", word)[1:-1]
|
||||
shuffled = list(to_shuffle)
|
||||
shuffle(shuffled)
|
||||
|
||||
res += word.replace(to_shuffle, "".join(shuffled)) + " "
|
||||
if newline:
|
||||
res += "\n"
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@loader.tds
|
||||
class DyslexiaMod(loader.Module):
|
||||
"""Shows the text as how you would see it if you have dyslexia"""
|
||||
|
||||
strings = {
|
||||
"name": "Dyslexia",
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>You need to provide"
|
||||
" text</b>"
|
||||
),
|
||||
}
|
||||
strings_ru = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Текст не найден</b>"
|
||||
),
|
||||
"_cmd_doc_dyslex": (
|
||||
"<текст | реплай> - Показывает, как люди с дислексией бы видели этот текст"
|
||||
),
|
||||
}
|
||||
strings_de = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Kein Text"
|
||||
" gefunden</b>"
|
||||
),
|
||||
"_cmd_doc_dyslex": (
|
||||
"<text | reply> - Zeigt den Text so an, wie er für Menschen mit Dyslexie"
|
||||
" aussieht"
|
||||
),
|
||||
}
|
||||
strings_hi = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>पाठ नहीं मिला</b>"
|
||||
),
|
||||
"_cmd_doc_dyslex": "<पाठ | रिप्लाई> - डिस्लेक्सिया वाले लोगों के लिए यह पाठ दिखाता है",
|
||||
}
|
||||
strings_uz = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Matn topilmadi</b>"
|
||||
),
|
||||
"_cmd_doc_dyslex": (
|
||||
"<matn | javob> - Dyslexia bo'lgan odamlar uchun ushbu matnni ko'rsatadi"
|
||||
),
|
||||
}
|
||||
strings_tr = {
|
||||
"no_text": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Metin bulunamadı</b>"
|
||||
),
|
||||
"_cmd_doc_dyslex": (
|
||||
"<metin | yanıt> - Dyslexia olan insanlar için bu metni gösterir"
|
||||
),
|
||||
}
|
||||
|
||||
async def dyslexcmd(self, message: Message):
|
||||
"""<text | reply> - Show, how people with dyslexia would have seen this text"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
try:
|
||||
args = (await message.get_reply_message()).text
|
||||
except Exception:
|
||||
return await utils.answer(message, self.strings("no_text"))
|
||||
|
||||
await self.animate(
|
||||
message,
|
||||
[dyslex(args) for _ in range(20)],
|
||||
interval=2,
|
||||
inline=True,
|
||||
)
|
||||
425
hikariatama/ftg/edutatar.py
Normal file
425
hikariatama/ftg/edutatar.py
Normal file
@@ -0,0 +1,425 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/edutatar_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/edutatar.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
filters = {
|
||||
"Иностранный язык (английский)": "🇺🇸 Англ",
|
||||
"Физическая культура": "⛹️♂️ PE",
|
||||
"Физика": "⚛️ Физон",
|
||||
"Литература": "📕 Лит-ра",
|
||||
"Математика": "📐 Maths",
|
||||
"Основы безопасности жизнедеятельности": "🧰 ОБЖ",
|
||||
"Родной язык": "🗣 Родной",
|
||||
"История": "⚒ История",
|
||||
"Родная литература": "📖 Родн.лит",
|
||||
"География": "🗺 Гео",
|
||||
"Информатика": "💻 IT",
|
||||
"Обществознание": "⚖️ Общество",
|
||||
"Русский язык": "✍️ Русский",
|
||||
"Химия": "🧪 Химия",
|
||||
"Биология": "🧬 Био",
|
||||
"Технология": "🔩 Технология",
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class EduTatarMod(loader.Module):
|
||||
"""Telegram client for edu.tatar.ru"""
|
||||
|
||||
strings = {
|
||||
"name": "eduTatar",
|
||||
"login_pass_not_specified": (
|
||||
"<b>🔑 Необходимо указать логин и пароль от edu.tatar.ru в конфиге</b>"
|
||||
),
|
||||
"loading_info": "<b>👩🏼🏫 Загружаю информацию с edu.tatar.ru...</b>",
|
||||
"host_error": (
|
||||
"🚫 Error occured while parsing. Maybe edutatar host is down or <b>you"
|
||||
" forgot to change proxy in script</b>?"
|
||||
),
|
||||
"no_hw": "📕 Нет д\\з",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"login_pass_not_specified": (
|
||||
"<b>🔑 Необходимо указать логин и пароль от edu.tatar.ru в конфиге</b>"
|
||||
),
|
||||
"loading_info": "<b>👩🏼🏫 Загружаю информацию с edu.tatar.ru...</b>",
|
||||
"host_error": (
|
||||
"🚫 Произошла ошибка получения данных с edu.tatar.ru. <b>Может, ты забыл"
|
||||
" указать прокси</b>?"
|
||||
),
|
||||
"no_hw": "📕 Нет д\\з",
|
||||
"_cmd_doc_eduweek": "Показать расписание на неделю",
|
||||
"_cmd_doc_eduday": "<день:число{0,}> - Показать расписание конкретного дня",
|
||||
"_cmd_doc_eduterm": "Показать оценки за четверть",
|
||||
"_cls_doc": "Телеграм клиент для edu.tatar.ru",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"edu_tatar_login", doc=lambda: "Login from edu.tatar.ru"
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"edu_tatar_pass",
|
||||
doc=lambda: "Password from edu.tatar.ru",
|
||||
validator=loader.validators.Hidden(loader.validators.String()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"marks_parse_delay",
|
||||
300,
|
||||
lambda: "Delay for parsing new marks in seconds",
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
loader.ConfigValue("proxy", "", lambda: "Proxy for correct work of module"),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.sess = {"DNSID": db.get("eduTatar", "sess", None)}
|
||||
if self.sess["DNSID"] is None:
|
||||
await self.revoke_token()
|
||||
|
||||
asyncio.ensure_future(self.parse_marks_async())
|
||||
|
||||
async def parse_marks_async(self):
|
||||
while True:
|
||||
await self.check_marks()
|
||||
await asyncio.sleep(self.config["marks_parse_delay"])
|
||||
|
||||
async def eduweekcmd(self, message: Message):
|
||||
"""Show schedule for a week"""
|
||||
if not self.config["edu_tatar_login"] or not self.config["edu_tatar_pass"]:
|
||||
await utils.answer(
|
||||
message, self.strings("login_pass_not_specified", message)
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings("loading_info", message))
|
||||
data = await self.scrape_week()
|
||||
await utils.answer(message, data)
|
||||
|
||||
async def edudaycmd(self, message: Message):
|
||||
"""<day:integer{0,}> - Show schedule for today"""
|
||||
if not self.config["edu_tatar_login"] or not self.config["edu_tatar_pass"]:
|
||||
await utils.answer(
|
||||
message, self.strings("login_pass_not_specified", message)
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if args == "":
|
||||
offset = 0
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
offset = abs(int(args))
|
||||
offset = offset * 60 * 60 * 24
|
||||
|
||||
now = datetime.now()
|
||||
today = now - timedelta(hours=now.hour, minutes=now.minute, seconds=now.second)
|
||||
day = time.mktime(today.timetuple()) + offset
|
||||
day_datetime = datetime.utcfromtimestamp(day)
|
||||
await utils.answer(message, self.strings("loading_info", message))
|
||||
weekdays = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
"Monday",
|
||||
]
|
||||
data = (
|
||||
f"📚 <b>{weekdays[day_datetime.weekday() + 1]}</b> 📚\n\n"
|
||||
+ await self.scrape_date(day)
|
||||
)
|
||||
await utils.answer(message, data)
|
||||
|
||||
async def edutermcmd(self, message: Message):
|
||||
"""Get term grades"""
|
||||
if not self.config["edu_tatar_login"] or not self.config["edu_tatar_pass"]:
|
||||
await utils.answer(
|
||||
message, self.string("login_pass_not_specified", message)
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings("loading_info", message))
|
||||
data = await self.scrape_term(utils.get_args_raw(message))
|
||||
await utils.answer(message, data)
|
||||
|
||||
async def revoke_token(self):
|
||||
try:
|
||||
answ = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://edu.tatar.ru/logon",
|
||||
headers={
|
||||
"Host": "edu.tatar.ru",
|
||||
"Connection": "keep-alive",
|
||||
"Content-Length": "52",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"Origin": "https://edu.tatar.ru",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
" (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"Sec-GPC": "1",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-User": "?1",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Referer": "https://edu.tatar.ru/logon",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
data={
|
||||
"main_login2": self.config["edu_tatar_login"],
|
||||
"main_password2": self.config["edu_tatar_pass"],
|
||||
},
|
||||
allow_redirects=True,
|
||||
proxies={"https": self.config["proxy"]},
|
||||
)
|
||||
except requests.exceptions.ProxyError:
|
||||
return self.strings("host_error")
|
||||
|
||||
if "DNSID" in dict(answ.cookies):
|
||||
self.sess = dict(answ.cookies)
|
||||
else:
|
||||
raise ValueError("Failed logging in")
|
||||
|
||||
self._db.set("eduTatar", "sess", self.sess["DNSID"])
|
||||
|
||||
async def check_marks(self):
|
||||
marks_tmp = self._db.get("eduTatar", "marks", {}).copy()
|
||||
await self.scrape_term("")
|
||||
marks_new = self._db.get("eduTatar", "marks", {}).copy()
|
||||
for subject, current_marks_2 in list(marks_new.items()):
|
||||
current_marks_1 = [] if subject not in marks_tmp else marks_tmp[subject]
|
||||
with contextlib.suppress(KeyError):
|
||||
subject = filters[subject]
|
||||
|
||||
for i in range(min(len(current_marks_1), len(current_marks_2))):
|
||||
if current_marks_1[i] != current_marks_2[i]:
|
||||
await self._client.send_message(
|
||||
"@userbot_notifies_bot",
|
||||
utils.escape_html(
|
||||
f"<b>{subject}:"
|
||||
f" {current_marks_1[i]}->{current_marks_2[i]}\n</b><code>{' '.join(list(map(str, current_marks_2)))}</code>"
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
for i in range(
|
||||
min(len(current_marks_1), len(current_marks_2)), len(current_marks_2)
|
||||
):
|
||||
await self._client.send_message(
|
||||
"@userbot_notifies_bot",
|
||||
utils.escape_html(
|
||||
f"<b>{subject}:"
|
||||
f" {current_marks_2[i ]}\n</b><code>{' '.join(list(map(str, current_marks_2)))}</code>"
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
async def scrape_date(self, date):
|
||||
try:
|
||||
answ = await utils.run_sync(
|
||||
requests.get,
|
||||
"https://edu.tatar.ru/user/diary/day?for=" + str(date),
|
||||
cookies=self.sess,
|
||||
headers={
|
||||
"Host": "edu.tatar.ru",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
" (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"Sec-GPC": "1",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-User": "?1",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Referer": "https://edu.tatar.ru/user/diary/week",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
proxies={"https": self.config["proxy"]},
|
||||
)
|
||||
except requests.exceptions.ProxyError:
|
||||
return self.strings("host_error")
|
||||
|
||||
day = re.findall(
|
||||
r"<td style=.vertical.*?>.*?<td"
|
||||
r" style=.vertical.*?middle.*?>(.*?)</td>.*?<p>(.*?)</p>.*?</tr>",
|
||||
answ.text.replace("\n", ""),
|
||||
)
|
||||
if len(day) < 5:
|
||||
await self.revoke_token()
|
||||
return await self.scrape_date(date)
|
||||
ans = ""
|
||||
for sub in day:
|
||||
hw = sub[1].strip()
|
||||
if hw == "":
|
||||
hw = self.strings("no_hw")
|
||||
subject = sub[0].strip()
|
||||
|
||||
for from_, to_ in filters.items():
|
||||
subject = subject.replace(from_, to_)
|
||||
|
||||
ans += f" <b>{subject}</b> - <i>{hw}" + "</i>\n"
|
||||
|
||||
return ans
|
||||
|
||||
async def scrape_week(self):
|
||||
now = datetime.now()
|
||||
monday = now - timedelta(
|
||||
days=now.weekday(), hours=now.hour, minutes=now.minute, seconds=now.second
|
||||
)
|
||||
monday = time.mktime(monday.timetuple())
|
||||
|
||||
week = ""
|
||||
weekdays = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"]
|
||||
for i in range(6):
|
||||
week += f"📚 <b>{weekdays[i]}</b> 📚\n"
|
||||
week += await self.scrape_date(monday + 60**2 * 24 * i)
|
||||
|
||||
return week
|
||||
|
||||
async def scrape_term(self, args):
|
||||
try:
|
||||
answ = await utils.run_sync(
|
||||
requests.get,
|
||||
"https://edu.tatar.ru/user/diary/term",
|
||||
cookies=self.sess,
|
||||
headers={
|
||||
"Host": "edu.tatar.ru",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
" (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"Sec-GPC": "1",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-User": "?1",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Referer": "https://edu.tatar.ru/user/diary/week",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
proxies={"https": self.config["proxy"]},
|
||||
)
|
||||
except requests.exceptions.ProxyError:
|
||||
return self.strings("host_error")
|
||||
|
||||
term = "<b>={ Табель успеваемости }=</b>\n"
|
||||
rows = re.findall(
|
||||
r"<tr>.*?<td>(.*?)</td>(.*?)</tr>", answ.text.replace("\n", "")
|
||||
)
|
||||
cols = {}
|
||||
for row in rows[1:-1]:
|
||||
subject = row[0]
|
||||
processing = (
|
||||
row[1][: row[1].find("<!--")].replace("<td>", "").replace(" ", "")
|
||||
)
|
||||
marks_temp = list(filter(lambda a: a != "", processing.split("</td>")))
|
||||
marks_tmp = " ".join(marks_temp[:-1])
|
||||
marks_db = self._db.get("eduTatar", "marks", {})
|
||||
if "-n" in args:
|
||||
marks = (
|
||||
str(marks_tmp.count("5"))
|
||||
+ " | "
|
||||
+ str(marks_tmp.count("4"))
|
||||
+ " | "
|
||||
+ str(marks_tmp.count("3"))
|
||||
+ " | "
|
||||
+ str(marks_tmp.count("2"))
|
||||
+ " |"
|
||||
)
|
||||
else:
|
||||
marks = marks_tmp
|
||||
|
||||
marks_db[subject] = marks_tmp.split()
|
||||
self._db.set("eduTatar", "marks", marks_db)
|
||||
|
||||
marks += (
|
||||
" <b>="
|
||||
+ marks_temp[-1]
|
||||
+ " | "
|
||||
+ str(round(float(marks_temp[-1]) + 0.001))
|
||||
+ "</b>"
|
||||
)
|
||||
marks = marks.replace("\t", "")
|
||||
marks = re.sub(r"[ ]{2,}", "", marks)
|
||||
for from_, to_ in filters.items():
|
||||
subject = subject.replace(from_, to_)
|
||||
cols[subject] = marks
|
||||
|
||||
try:
|
||||
maxelem = max(
|
||||
list(map(len, list(map(lambda a: a.split(" ")[1], list(cols.keys())))))
|
||||
)
|
||||
maxelem_val = max(
|
||||
list(
|
||||
map(
|
||||
len,
|
||||
list(map(lambda a: a.split("<b>", 1)[0], list(cols.values()))),
|
||||
)
|
||||
)
|
||||
)
|
||||
except ValueError:
|
||||
time.sleep(5)
|
||||
return await self.scrape_term(args)
|
||||
# print(maxelem)
|
||||
offset = " " * (maxelem - 7)
|
||||
if "-n" in args:
|
||||
term += (
|
||||
f"<code> Subject{offset} 5 | 4 | 3 | 2 | Result</code>\n<code>"
|
||||
+ ("=" * (maxelem - 7 + 33))
|
||||
+ "</code>\n"
|
||||
)
|
||||
else:
|
||||
term += "\n"
|
||||
|
||||
for sub, marks in cols.items():
|
||||
offset = " " * (maxelem - len(sub.split(" ")[1]))
|
||||
offset_val = " " * (maxelem_val - len(marks.split("<b>", 1)[0]))
|
||||
term += (
|
||||
f'<code>{sub}:{offset} {marks.split("<b>", 1)[0]}{offset_val}</code><b>{marks.split("<b>", 1)[1]}\n'
|
||||
)
|
||||
|
||||
return term
|
||||
165
hikariatama/ftg/emotionless.py
Normal file
165
hikariatama/ftg/emotionless.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/external-vitaliy-gorbachev-flat-vitaly-gorbachev/464/000000/external-sad-social-media-vitaliy-gorbachev-flat-vitaly-gorbachev.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/emotionless.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.6.3
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
from hikkatl.tl.functions.messages import ReadReactionsRequest
|
||||
from hikkatl.tl.types import Message, UpdateMessageReactions
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Entry(NamedTuple):
|
||||
"""Entry for reaction queue"""
|
||||
|
||||
chat: int
|
||||
schedule: float
|
||||
top_msg_id: Optional[int] = None
|
||||
|
||||
|
||||
@loader.tds
|
||||
class EmotionlessMod(loader.Module):
|
||||
"""Automatically reads reactions"""
|
||||
|
||||
strings = {
|
||||
"name": "Emotionless",
|
||||
"state": (
|
||||
"<emoji document_id=5314591660192046611>😑</emoji> <b>Emotionless mode is"
|
||||
" now {}</b>"
|
||||
),
|
||||
"on": "on",
|
||||
"off": "off",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"state": (
|
||||
"<emoji document_id=5314591660192046611>😑</emoji> <b>Режим без реакций"
|
||||
" {}</b>"
|
||||
),
|
||||
"on": "включен",
|
||||
"off": "выключен",
|
||||
"_cls_doc": "Автоматически читает реакции",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"state": (
|
||||
"<emoji document_id=5314591660192046611>😑</emoji> <b>Emotionless-Modus"
|
||||
" ist jetzt {}</b>"
|
||||
),
|
||||
"on": "ein",
|
||||
"off": "aus",
|
||||
"_cls_doc": "Liest automatisch Reaktionen",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"state": (
|
||||
"<emoji document_id=5314591660192046611>😑</emoji> <b>Emotionless rejimi"
|
||||
" {}</b>"
|
||||
),
|
||||
"on": "yoqilgan",
|
||||
"off": "o'chirilgan",
|
||||
"_cls_doc": "Avtomatik ravishda reaksiyalarni o'qiydi",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"state": (
|
||||
"<emoji document_id=5314591660192046611>😑</emoji> <b>Emotionless modu"
|
||||
" {}</b>"
|
||||
),
|
||||
"on": "açık",
|
||||
"off": "kapalı",
|
||||
"_cls_doc": "Otomatik olarak tepkileri okur",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._queue = []
|
||||
self._flood_protect = []
|
||||
self._flood_protect_sample = 60
|
||||
self._threshold = 10
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Переключить авточтение реакций",
|
||||
de_doc="Schaltet das automatische Lesen von Reaktionen um",
|
||||
tr_doc="Otomatik tepki okumayı aç/kapa",
|
||||
uz_doc="Avtomatik reaksiya o'qishni yoqish/ochish",
|
||||
)
|
||||
async def noreacts(self, message: Message):
|
||||
"""Toggle reactions auto-reader"""
|
||||
state = not self.get("state", False)
|
||||
self.set("state", state)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("state").format(self.strings("on" if state else "off")),
|
||||
)
|
||||
|
||||
@loader.loop(interval=3, autostart=True)
|
||||
async def _queue_handler(self):
|
||||
if not self._queue:
|
||||
return
|
||||
|
||||
chat, schedule, top_msg_id = self._queue[0]
|
||||
if schedule > time.time():
|
||||
return
|
||||
|
||||
self._queue.pop(0)
|
||||
|
||||
await self._client(ReadReactionsRequest(chat, top_msg_id))
|
||||
logger.debug(
|
||||
"Read reactions in queued peer %s, top_msg_id %s",
|
||||
chat,
|
||||
top_msg_id,
|
||||
)
|
||||
|
||||
@loader.raw_handler(UpdateMessageReactions)
|
||||
async def _handler(self, update: UpdateMessageReactions):
|
||||
if (
|
||||
not self.get("state", False)
|
||||
or not hasattr(update, "reactions")
|
||||
or not hasattr(update.reactions, "recent_reactions")
|
||||
or not isinstance(update.reactions.recent_reactions, (list, set, tuple))
|
||||
or not any(i.unread for i in update.reactions.recent_reactions)
|
||||
):
|
||||
return
|
||||
|
||||
self._flood_protect = list(
|
||||
filter(lambda x: x > time.time(), self._flood_protect)
|
||||
)
|
||||
|
||||
chat = next(
|
||||
getattr(update.peer, attribute)
|
||||
for attribute in {"channel_id", "chat_id", "user_id"}
|
||||
if hasattr(update.peer, attribute)
|
||||
)
|
||||
|
||||
if len(self._flood_protect) > self._threshold:
|
||||
self._queue.append(
|
||||
Entry(
|
||||
chat=chat,
|
||||
schedule=self._flood_protect[0],
|
||||
top_msg_id=update.top_msg_id,
|
||||
)
|
||||
)
|
||||
logger.debug("Flood protect triggered, chat %s added to queue", update)
|
||||
return
|
||||
|
||||
self._flood_protect += [int(time.time()) + self._flood_protect_sample]
|
||||
|
||||
await self._client(ReadReactionsRequest(update.peer, update.top_msg_id))
|
||||
logger.debug(
|
||||
"Read reaction in %s, top_msg_id %s", update.peer, update.top_msg_id
|
||||
)
|
||||
67
hikariatama/ftg/fancyfonts.py
Normal file
67
hikariatama/ftg/fancyfonts.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://static.dan.tatar/fancy_fonts_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/fancyfonts.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
BASE = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
FONTS = [
|
||||
"𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡𝟘𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ",
|
||||
"𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷յշՅկՏճԴՑգօ𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ",
|
||||
"𝖆𝖇𝖈𝖉𝖊𝖋𝖌𝖍𝖎𝖏𝖐𝖑𝖒𝖓𝖔𝖕𝖖𝖗𝖘𝖙𝖚𝖛𝖜𝖝𝖞𝖟յշՅկՏճԴՑգօ𝕬𝕭𝕮𝕯𝕰𝕱𝕲𝕳𝕴𝕵𝕶𝕷𝕸𝕹𝕺𝕻𝕼𝕽𝕾𝕿𝖀𝖁𝖂𝖃𝖄𝖅",
|
||||
"𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃1234567890𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩",
|
||||
"𝒶𝒷𝒸𝒹𝑒𝒻𝑔𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝑜𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏1234567890𝒜𝐵𝒞𝒟𝐸𝐹𝒢𝐻𝐼𝒥𝒦𝐿𝑀𝒩𝒪𝒫𝒬𝑅𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵",
|
||||
"🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩➊➋➌➍➎➏➐➑➒⓿🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩",
|
||||
"🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉1234567890🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉",
|
||||
"卂乃匚ᗪ乇千Ꮆ卄丨フҜㄥ爪几ㄖ卩Ɋ尺丂ㄒㄩᐯ山乂ㄚ乙1234567890卂乃匚ᗪ乇千Ꮆ卄丨フҜㄥ爪几ㄖ卩Ɋ尺丂ㄒㄩᐯ山乂ㄚ乙",
|
||||
"ɐbɔdǝɟƃɥ!ɾʞןɯnodbɹsʇnʌʍxʎz1234567890ɐbɔdǝɟƃɥ!ɾʞןɯnodbɹsʇnʌʍxʎz",
|
||||
"ΔβĆĐ€₣ǤĦƗĴҜŁΜŇØƤΩŘŞŦỮVŴЖ¥Ž1234567890ΔβĆĐ€₣ǤĦƗĴҜŁΜŇØƤΩŘŞŦỮVŴЖ¥Ž",
|
||||
"ᗩᗷᑕᗪᗴᖴǤᕼᎥᒎᛕᒪᗰᑎᗝᑭɊᖇᔕ丅ᑌᐯᗯ᙭Ƴ乙1234567890ᗩᗷᑕᗪᗴᖴǤᕼᎥᒎᛕᒪᗰᑎᗝᑭɊᖇᔕ丅ᑌᐯᗯ᙭Ƴ乙",
|
||||
"ꋫꃃꏸꁕꍟꄘꁍꑛꂑꀭꀗ꒒ꁒꁹꆂꉣꁸ꒓ꌚ꓅ꐇꏝꅐꇓꐟꁴ1234567890ꋫꃃꏸꁕꍟꄘꁍꑛꂑꀭꀗ꒒ꁒꁹꆂꉣꁸ꒓ꌚ꓅ꐇꏝꅐꇓꐟꁴ",
|
||||
"ᗩᗷᑕᗪEᖴGᕼIᒍKᒪᗰᑎOᑭᑫᖇᔕTᑌᐯᗯ᙭Yᘔ1234567890ᗩᗷᑕᗪEᖴGᕼIᒍKᒪᗰᑎOᑭᑫᖇᔕTᑌᐯᗯ᙭Yᘔ",
|
||||
"abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵𝟬𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭",
|
||||
"𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿𝟶𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉",
|
||||
]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FancyFontsMod(loader.Module):
|
||||
"""Create fancy font text with more than 10 styles available"""
|
||||
|
||||
strings = {"name": "FancyFonts", "no_args": "🚫 <b>No text specified</b>"}
|
||||
strings_ru = {
|
||||
"no_args": "🚫 <b>Не указан текст</b>",
|
||||
"_cls_doc": "Сделай текст более красивым шрифтом",
|
||||
"_cmd_doc_ffont": "<текст> - Превратить текст в красивый",
|
||||
}
|
||||
|
||||
async def ffontcmd(self, message: Message) -> None:
|
||||
"""<text> - Create the fancy version of text"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
await self.inline.list(
|
||||
message,
|
||||
[
|
||||
f"<code>{str.translate(args, str.maketrans(BASE, i))}</code>"
|
||||
for i in FONTS
|
||||
],
|
||||
)
|
||||
185
hikariatama/ftg/feedback.py
Normal file
185
hikariatama/ftg/feedback.py
Normal file
@@ -0,0 +1,185 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/feedback_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/feedback.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import abc
|
||||
import time
|
||||
|
||||
from aiogram.types import Message as AiogramMessage
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FeedbackMod(loader.Module):
|
||||
"""Simple feedback bot for Hikka"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
strings = {
|
||||
"name": "Feedback",
|
||||
"/start": (
|
||||
"🤵♀️ <b>Hello. I'm feedback bot of {}. Read /nometa before"
|
||||
" continuing</b>\n<b>You can send only one message per minute</b>"
|
||||
),
|
||||
"/nometa": (
|
||||
"👨🎓 <b><u>Internet-talk rules:</u></b>\n\n"
|
||||
"<b>🚫 Do <u>not</u> send just 'Hello'</b>\n"
|
||||
"<b>🚫 Do <u>not</u> advertise</b>\n"
|
||||
"<b>🚫 Do <u>not</u> insult</b>\n"
|
||||
"<b>🚫 Do <u>not</u> split message</b>\n"
|
||||
"<b>✅ Write your question in one message</b>"
|
||||
),
|
||||
"enter_message": "✍️ <b>Enter your message here</b>",
|
||||
"sent": "✅ <b>Your message has been sent to owner</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"/start": (
|
||||
"🤵♀️ <b>Привет. Я бот обратной связи {}. Прочитай /nometa перед"
|
||||
" продолжением</b>\n<b>Ты можешь отправлять только одно сообщение в"
|
||||
" минуту</b>"
|
||||
),
|
||||
"enter_message": "✍️ <b>Ввведи сообщение</b>",
|
||||
"sent": "✅ <b>Сообщение передано владельцу</b>",
|
||||
"_cls_doc": "Бот обратной связи для Hikka",
|
||||
"/nometa": (
|
||||
"👨🎓 <b><u>Правила общения в Интернете:</u></b>\n\n <b>🚫 <u>Не пиши</u>"
|
||||
" просто 'Привет'</b>\n <b>🚫 <u>Не рекламируй </u> ничего</b>\n <b>🚫 <u>Не"
|
||||
" оскорбляй</u> никого</b>\n <b>🚫 <u>Не разбивай</u> сообщения на миллион"
|
||||
" кусочков</b>\n <b>✅ Пиши вопрос в одном сообщении</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"/start": (
|
||||
"🤵♀️ <b>Hallo. Ich bin der Feedback-Bot von {}. Lies /nometa, bevor"
|
||||
" du fortfährst</b>\n<b>Du kannst nur eine Nachricht pro Minute senden</b>"
|
||||
),
|
||||
"enter_message": "✍️ <b>Gib deine Nachricht hier ein</b>",
|
||||
"sent": "✅ <b>Deine Nachricht wurde dem Besitzer gesendet</b>",
|
||||
"_cls_doc": "Feedback-Bot für Hikka",
|
||||
"/nometa": (
|
||||
"👨🎓 <b><u>Internet-Talk-Regeln:</u></b>\n\n <b>🚫 <u>Nicht</u> 'Hallo'"
|
||||
" schreiben</b>\n <b>🚫 <u>Nicht</u> werben</b>\n <b>🚫 <u>Nicht</u>"
|
||||
" beleidigen</b>\n <b>🚫 <u>Nicht</u> aufteilen</b>\n <b>✅ Schreibe deine"
|
||||
" Frage in einer Nachricht</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"/start": (
|
||||
"🤵♀️ <b>नमस्ते। मैं {} का फीडबैक बॉट हूँ। जारी रखने से पहले /nometa"
|
||||
" पढ़ें</b>\n<b>आप मिनट में केवल एक संदेश भेज सकते हैं</b>"
|
||||
),
|
||||
"enter_message": "✍️ <b>यहां संदेश दर्ज करें</b>",
|
||||
"sent": "✅ <b>आपका संदेश मालिक को भेज दिया गया है</b>",
|
||||
"_cls_doc": "Hikka के लिए प्रतिक्रिया बॉट",
|
||||
"/nometa": (
|
||||
"👨🎓 <b><u>इंटरनेट बातचीत नियम:</u></b>\n\n <b>🚫 'नमस्ते' न लिखें</b>\n"
|
||||
" <b>🚫 विज्ञापन न करें</b>\n <b>🚫 अपमान न करें</b>\n <b>🚫 संदेश को विभाजित"
|
||||
" न करें</b>\n <b>✅ अपना सवाल एक संदेश में लिखें</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"/start": (
|
||||
"🤵♀️ <b>Merhaba. Ben {}'ın geri bildirim botuyum. Devam etmeden önce"
|
||||
" /nometa'ya bakın</b>\n<b>Sadece bir dakikada bir mesaj"
|
||||
" gönderebilirsiniz</b>"
|
||||
),
|
||||
"enter_message": "✍️ <b>Mesajınızı buraya girin</b>",
|
||||
"sent": "✅ <b>Sahibine mesajınız gönderildi</b>",
|
||||
"_cls_doc": "Hikka için geri bildirim botu",
|
||||
"/nometa": (
|
||||
"👨🎓 <b><u>İnternet Konuşma Kuralları:</u></b>\n\n <b>🚫 'Merhaba'"
|
||||
" yazmayın</b>\n <b>🚫 Reklam yapmayın</b>\n <b>🚫 Kimsenin ağzına"
|
||||
" sıçramayın</b>\n <b>🚫 Mesajı parçalaymayın</b>\n <b>✅ Sorunuzu bir"
|
||||
" mesajda yazın</b>"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self._name = utils.escape_html(get_display_name(self._client.hikka_me))
|
||||
self._ratelimit = {}
|
||||
self._markup = self.inline.generate_markup(
|
||||
{"text": "✍️ Leave a message [1 per minute]", "data": "fb_leave_message"}
|
||||
)
|
||||
self._cancel = self.inline.generate_markup(
|
||||
{"text": "🚫 Cancel", "data": "fb_cancel"}
|
||||
)
|
||||
|
||||
self.__doc__ = (
|
||||
"Feedback bot\n"
|
||||
f"Your feeback link: t.me/{self.inline.bot_username}?start=feedback\n"
|
||||
"You can freely share it"
|
||||
)
|
||||
|
||||
async def aiogram_watcher(self, message: AiogramMessage):
|
||||
if message.text == "/start feedback":
|
||||
await message.answer(
|
||||
self.strings("/start").format(self._name),
|
||||
reply_markup=self._markup,
|
||||
)
|
||||
elif message.text == "/nometa":
|
||||
await message.answer(self.strings("/nometa"), reply_markup=self._markup)
|
||||
elif self.inline.gs(message.from_user.id) == "fb_send_message":
|
||||
await self.inline.bot.forward_message(
|
||||
self._tg_id,
|
||||
message.chat.id,
|
||||
message.message_id,
|
||||
)
|
||||
await message.answer(self.strings("sent"))
|
||||
self._ratelimit[message.from_user.id] = time.time() + 60
|
||||
self.inline.ss(message.from_user.id, False)
|
||||
|
||||
@loader.inline_everyone
|
||||
@loader.callback_handler()
|
||||
async def feedback(self, call: InlineCall):
|
||||
"""Handles button clicks"""
|
||||
if call.data == "fb_cancel":
|
||||
self.inline.ss(call.from_user.id, False)
|
||||
await self.inline.bot.delete_message(
|
||||
call.message.chat.id,
|
||||
call.message.message_id,
|
||||
)
|
||||
return
|
||||
|
||||
if call.data != "fb_leave_message":
|
||||
return
|
||||
|
||||
if (
|
||||
call.from_user.id in self._ratelimit
|
||||
and self._ratelimit[call.from_user.id] > time.time()
|
||||
):
|
||||
await call.answer(
|
||||
(
|
||||
"You can send next message in"
|
||||
f" {self._ratelimit[call.from_user.id] - time.time():.0f} second(-s)"
|
||||
),
|
||||
show_alert=True,
|
||||
)
|
||||
return
|
||||
|
||||
self.inline.ss(call.from_user.id, "fb_send_message")
|
||||
await self.inline.bot.edit_message_text(
|
||||
chat_id=call.message.chat.id,
|
||||
message_id=call.message.message_id,
|
||||
text=self.strings("enter_message"),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=self._cancel,
|
||||
)
|
||||
533
hikariatama/ftg/flash_cards.py
Normal file
533
hikariatama/ftg/flash_cards.py
Normal file
@@ -0,0 +1,533 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/stickers/500/000000/cards.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/flash_cards.jpg
|
||||
# meta developer: @hikarimods
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
from random import randint
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Testing ^title_deck_name^</title>
|
||||
<style type="text/css">
|
||||
@import url('https://fonts.googleapis.com/css2?family=Exo+2&display=swap');
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
transition: all .3s ease;
|
||||
}
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #121212;
|
||||
color: #fff;
|
||||
font-family: 'Exo 2';
|
||||
}
|
||||
|
||||
.cards, .testing {
|
||||
width: 94%;
|
||||
margin-left: 3%;
|
||||
min-height: 30vh;
|
||||
background: #121212;
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 9.31px 9.31px 19px #0B0B0B, inset -9.31px -9.31px 19px #161616;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 94%;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
margin-left: 3%;
|
||||
background: #121212;
|
||||
border-radius: 10px;
|
||||
margin-top: 10px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
box-shadow: inset 9.31px 9.31px 19px #0B0B0B, inset -9.31px -9.31px 19px #161616;
|
||||
}
|
||||
|
||||
.back {
|
||||
width: 94%;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
margin-left: 3%;
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
|
||||
background: linear-gradient(145deg , #d9d9d9, #C8C8C8);
|
||||
box-shadow: rgb(117 117 117) 0px 1px 20px 0px inset;
|
||||
}
|
||||
|
||||
.back::placeholder {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
padding: 0;
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 736px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 25px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.testing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.front {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>^deck_name^</h1>
|
||||
<div class="cards">
|
||||
Loading...
|
||||
</div>
|
||||
<div class="testing">
|
||||
<h1 class="front"></h1>
|
||||
<input class="back" type="text" placeholder="Ответ">
|
||||
</div>
|
||||
<div class="begin button">Start test</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
cards = JSON.parse("^json_cards^");
|
||||
var cards_html = "";
|
||||
for (var front in cards) {
|
||||
cards_html += front + " - " + cards[front] + "<br>\\n";
|
||||
}
|
||||
|
||||
document.querySelector('.cards').innerHTML = cards_html;
|
||||
|
||||
function getRndInteger(min, max) {return Math.floor(Math.random() * (max - min) ) + min;}
|
||||
|
||||
function render_next_one() {
|
||||
var keys = Object.keys(cards);
|
||||
var front = keys[getRndInteger(0,keys.length)];
|
||||
var back = cards[front];
|
||||
document.querySelector('.front').innerHTML = front;
|
||||
document.querySelector('.back').setAttribute('answer', back);
|
||||
}
|
||||
|
||||
function check_answer() {
|
||||
var el = document.querySelector('.back');
|
||||
if(el.getAttribute('answer') == el.value){
|
||||
document.querySelector('.front').innerHTML = "Yup!";
|
||||
document.querySelector('.testing').style.background = '#26681e';
|
||||
} else {
|
||||
document.querySelector('.testing').style.background = '#611a1a';
|
||||
document.querySelector('.front').innerHTML = "Nope. Right answer: " + el.getAttribute('answer');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelector('.testing').style.background = '#121212';
|
||||
render_next_one();
|
||||
}, 1000);
|
||||
el.value = "";
|
||||
}
|
||||
|
||||
document.querySelector('.begin').onclick = function() {
|
||||
document.querySelector('.cards').style.opacity = 0;
|
||||
setTimeout(() => {document.querySelector('.cards').style.display = 'none'; document.querySelector('.testing').style.display = 'block';}, 300);
|
||||
this.classList.remove('begin');
|
||||
this.classList.add('next');
|
||||
this.innerHTML = "Check answer";
|
||||
render_next_one();
|
||||
document.querySelector('.next').onclick = function() {
|
||||
check_answer();
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('.back').onkeyup = function(e) {
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
check_answer();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FlashCardsMod(loader.Module):
|
||||
"""Flash cards for learning"""
|
||||
|
||||
strings = {
|
||||
"name": "FlashCards",
|
||||
"deck_not_found": "<b>🚫 Deck not found</b",
|
||||
"no_deck_name": "<b>You haven't provided deck name</b>",
|
||||
"deck_created": "#Deck <code>#{}</code> <b>{}</b> successfully created!",
|
||||
"deck_removed": "<b>🚫 Deck removed</b>",
|
||||
"save_deck_no_reply": (
|
||||
"<b>🚫 This command should be used in reply to message with deck items.</b>"
|
||||
),
|
||||
"deck_saved": "✅ <b>Deck saved!</b>",
|
||||
"generating_page": "<b>⚙️ Generating page, please wait ...</b>",
|
||||
"offline_testing": "<b>📖 Offline testing, based on deck {}</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"deck_not_found": "<b>🚫 Дека не найдена</b",
|
||||
"no_deck_name": "<b>Ты не указал имя деки</b>",
|
||||
"deck_created": "#Deck <code>#{}</code> <b>{}</b> успешно создана!",
|
||||
"deck_removed": "<b>🚫 Дека удалена</b>",
|
||||
"save_deck_no_reply": (
|
||||
"<b>🚫 Эта команда должна выполняться в ответ на измененную деку.</b>"
|
||||
),
|
||||
"deck_saved": "✅ <b>Дека сохранена!</b>",
|
||||
"generating_page": "<b>⚙️ Генерирую страницу, секунду...</b>",
|
||||
"offline_testing": "<b>📖 Оффлайн тестирование на основе деки {}</b>",
|
||||
"_cmd_doc_newdeck": "<name> - Создать новую деку",
|
||||
"_cmd_doc_decks": "Показать деки",
|
||||
"_cmd_doc_deletedeck": "<id> - Удалить деку",
|
||||
"_cmd_doc_listdeck": "<id> - Показать деку",
|
||||
"_cmd_doc_editdeck": "<id> - Редактировать деку",
|
||||
"_cmd_doc_savedeck": "<reply> - Сохранить деку",
|
||||
"_cmd_doc_htmldeck": "<id> - Сгенерировать оффлайн-тестирование по деке",
|
||||
"_cls_doc": "Флеш-карты для обучения",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"deck_not_found": "<b>🚫 Deck nicht gefunden</b",
|
||||
"no_deck_name": "<b>Du hast keinen Decknamen angegeben</b>",
|
||||
"deck_created": "#Deck <code>#{}</code> <b>{}</b> erfolgreich erstellt!",
|
||||
"deck_removed": "<b>🚫 Deck entfernt</b>",
|
||||
"save_deck_no_reply": (
|
||||
"<b>🚫 Dieser Befehl sollte in Antwort auf eine Nachricht mit"
|
||||
" Deck-Elementen"
|
||||
" verwendet werden.</b>"
|
||||
),
|
||||
"deck_saved": "✅ <b>Deck gespeichert!</b>",
|
||||
"generating_page": "<b>⚙️ Seite wird generiert, bitte warten ...</b>",
|
||||
"offline_testing": "<b>📖 Offline-Testing basierend auf dem Deck {}</b>",
|
||||
"_cmd_doc_newdeck": "<name> - Erstelle ein neues Deck",
|
||||
"_cmd_doc_decks": "Zeige Decks",
|
||||
"_cmd_doc_deletedeck": "<id> - Deck löschen",
|
||||
"_cmd_doc_listdeck": "<id> - Deck anzeigen",
|
||||
"_cmd_doc_editdeck": "<id> - Deck bearbeiten",
|
||||
"_cmd_doc_savedeck": "<reply> - Deck speichern",
|
||||
"_cmd_doc_htmldeck": "<id> - Offline-Testing basierend auf dem Deck",
|
||||
"_cls_doc": "Flash-Karten für das Lernen",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"deck_not_found": "<b>🚫 Deck bulunamadı</b",
|
||||
"no_deck_name": "<b>Deck adı belirtmedin</b>",
|
||||
"deck_created": "#Deck <code>#{}</code> <b>{}</b> başarıyla oluşturuldu!",
|
||||
"deck_removed": "<b>🚫 Deck kaldırıldı</b>",
|
||||
"save_deck_no_reply": "<b>🚫 Bu komut, deck öğeleriyle yanıtlanmalıdır.</b>",
|
||||
"deck_saved": "✅ <b>Deck kaydedildi!</b>",
|
||||
"generating_page": "<b>⚙️ Sayfa oluşturuluyor, lütfen bekleyin ...</b>",
|
||||
"offline_testing": "<b>📖 {} deckine dayalı çevrimdışı test</b>",
|
||||
"_cmd_doc_newdeck": "<isim> - Yeni bir deck oluştur",
|
||||
"_cmd_doc_decks": "Deckleri göster",
|
||||
"_cmd_doc_deletedeck": "<id> - Deck sil",
|
||||
"_cmd_doc_listdeck": "<id> - Decki göster",
|
||||
"_cmd_doc_editdeck": "<id> - Decki düzenle",
|
||||
"_cmd_doc_savedeck": "<reply> - Decki kaydet",
|
||||
"_cmd_doc_htmldeck": "<id> - Decke dayalı çevrimdışı test oluştur",
|
||||
"_cls_doc": "Öğrenmek için flaş kartlar",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"deck_not_found": "<b>🚫 डेक नहीं मिला</b",
|
||||
"no_deck_name": "<b>आपने डेक का नाम नहीं दिया</b>",
|
||||
"deck_created": "#Deck <code>#{}</code> <b>{}</b> सफलतापूर्वक बनाया गया!",
|
||||
"deck_removed": "<b>🚫 डेक हटा दिया गया</b>",
|
||||
"save_deck_no_reply": (
|
||||
"<b>🚫 यह कमांड डेक आइटम के साथ उत्तर देने के लिए उपयोग किया जाना चाहिए।</b>"
|
||||
),
|
||||
"deck_saved": "✅ <b>डेक सहेज लिया गया!</b>",
|
||||
"generating_page": "<b>⚙️ पेज उत्पन्न किया जा रहा है, कृपया प्रतीक्षा करें ...</b>",
|
||||
"offline_testing": "<b>📖 {} डेक पर आधारित ऑफ़लाइन परीक्षण</b>",
|
||||
"_cmd_doc_newdeck": "<नाम> - एक नया डेक बनाएं",
|
||||
"_cmd_doc_decks": "डेक दिखाएं",
|
||||
"_cmd_doc_deletedeck": "<आईडी> - डेक हटाएं",
|
||||
"_cmd_doc_listdeck": "<आईडी> - डेक दिखाएं",
|
||||
"_cmd_doc_editdeck": "<आईडी> - डेक संपादित करें",
|
||||
"_cmd_doc_savedeck": "<उत्तर> - डेक सहेजें",
|
||||
"_cmd_doc_htmldeck": "<आईडी> - डेक पर आधारित ऑफ़लाइन परीक्षण बनाएं",
|
||||
"_cls_doc": "फ्लैश कार्ड अध्ययन के लिए",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"deck_not_found": "<b>🚫 Deck topilmadi</b",
|
||||
"no_deck_name": "<b>Deck nomini kiritmadingiz</b>",
|
||||
"deck_created": "#Deck <code>#{}</code> <b>{}</b> muvaffaqiyatli yaratildi!",
|
||||
"deck_removed": "<b>🚫 Deck o'chirildi</b>",
|
||||
"save_deck_no_reply": (
|
||||
"<b>🚫 Bu buyruq deck elementlari bilan javob berilishi kerak.</b>"
|
||||
),
|
||||
"deck_saved": "✅ <b>Deck saqlandi!</b>",
|
||||
"generating_page": "<b>⚙️ Sahifa yaratilmoqda, iltimos kuting ...</b>",
|
||||
"offline_testing": "<b>📖 {} deckiga asoslangan oflayn test</b>",
|
||||
"_cmd_doc_newdeck": "<nom> - Yangi deck yaratish",
|
||||
"_cmd_doc_decks": "Decklarni ko'rsatish",
|
||||
"_cmd_doc_deletedeck": "<id> - Deckni o'chirish",
|
||||
"_cmd_doc_listdeck": "<id> - Deckni ko'rsatish",
|
||||
"_cmd_doc_editdeck": "<id> - Deckni tahrirlash",
|
||||
"_cmd_doc_savedeck": "<javob> - Deckni saqlash",
|
||||
"_cmd_doc_htmldeck": "<id> - Deckiga asoslangan oflayn test yaratish",
|
||||
"_cls_doc": "O'rganish uchun flash kartalar",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.decks = self.get("decks", {})
|
||||
|
||||
def get_deck_from_reply(self, reply, limit=None):
|
||||
if reply is None:
|
||||
return False
|
||||
|
||||
if "#Deck" in reply.text:
|
||||
counter = 1
|
||||
|
||||
for line in reply.text.split("\n"):
|
||||
line = line.split()
|
||||
if len(line) > 1:
|
||||
deck = (
|
||||
line[1]
|
||||
.replace("<code>", "")
|
||||
.replace("</code>", "")
|
||||
.replace("#", "")
|
||||
)
|
||||
try:
|
||||
int(deck)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if deck in self.decks:
|
||||
if (
|
||||
limit is None
|
||||
or not limit
|
||||
and "#Decks" not in reply.text
|
||||
or counter == limit
|
||||
):
|
||||
return deck
|
||||
else:
|
||||
counter += 1
|
||||
|
||||
return False
|
||||
|
||||
async def get_from_message(self, message: Message):
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
args = args.split()[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if args.startswith("#"):
|
||||
args = args[1:]
|
||||
|
||||
try:
|
||||
int_args = int(args)
|
||||
except Exception:
|
||||
args = False
|
||||
int_args = False
|
||||
|
||||
if int(int_args) < 1000:
|
||||
args = self.get_deck_from_reply(await message.get_reply_message(), int_args)
|
||||
|
||||
if not args or args not in self.decks:
|
||||
await utils.answer(message, self.strings("deck_not_found"))
|
||||
await asyncio.sleep(2)
|
||||
await message.delete()
|
||||
return False
|
||||
|
||||
return args
|
||||
|
||||
async def newdeckcmd(self, message: Message):
|
||||
"""<name> - New deck of cards"""
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if args == "":
|
||||
await utils.answer(message, self.strings("no_deck_name"))
|
||||
await asyncio.sleep(2)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
random_id = str(randint(10000, 99999))
|
||||
|
||||
self.decks[random_id] = {"name": args, "cards": [("sample", "sample")]}
|
||||
|
||||
self.set("decks", self.decks)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("deck_created").format(random_id, args),
|
||||
)
|
||||
|
||||
async def deckscmd(self, message: Message):
|
||||
"""List decks"""
|
||||
res = "<b>#Decks:</b>\n\n"
|
||||
for counter, (item_id, item) in enumerate(self.decks.items(), start=1):
|
||||
if len(item["cards"]) == 0:
|
||||
items = "No cards"
|
||||
else:
|
||||
items = "".join(
|
||||
f"\n {front} - {back}" for front, back in item["cards"][:2]
|
||||
)
|
||||
if len(item["cards"]) > 2:
|
||||
items += "\n <...>"
|
||||
res += (
|
||||
f"🔸<b>{counter}.</b> <code>{item_id}</code> |"
|
||||
f" {item['name']}<code>{items}</code>\n\n"
|
||||
)
|
||||
await utils.answer(message, res)
|
||||
|
||||
async def deletedeckcmd(self, message: Message):
|
||||
"""<id> - Delete deck"""
|
||||
deck_id = await self.get_from_message(message)
|
||||
if not deck_id:
|
||||
return
|
||||
|
||||
del self.decks[deck_id]
|
||||
self.set("decks", self.decks)
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
if "#Decks" in reply.text:
|
||||
await self.deckscmd(reply)
|
||||
elif "#Deck" in reply.text:
|
||||
await reply.edit(reply.text + "\n" + self.strings("deck_removed"))
|
||||
await utils.answer(message, self.strings("deck_removed"))
|
||||
|
||||
async def listdeckcmd(self, message: Message):
|
||||
"""<id> - List deck items"""
|
||||
deck_id = await self.get_from_message(message)
|
||||
if not deck_id:
|
||||
return
|
||||
|
||||
deck = self.decks[deck_id]
|
||||
res = f"📋#Deck #{deck_id} <b>{deck['name']}</b>:\n➖➖➖➖➖➖➖➖➖➖"
|
||||
for i, (front, back) in enumerate(deck["cards"], start=1):
|
||||
res += f"\n<b>{i}. {front} - {back}</b>"
|
||||
await utils.answer(message, res)
|
||||
|
||||
async def editdeckcmd(self, message: Message):
|
||||
"""<id> - Edit deck items"""
|
||||
deck_id = await self.get_from_message(message)
|
||||
if not deck_id:
|
||||
return
|
||||
|
||||
deck = self.decks[deck_id]
|
||||
res = f"📋#Deck #{deck_id} \"<b>{deck['name']}</b>\":\n➖➖➖➖➖➖➖➖➖➖"
|
||||
for front, back in deck["cards"]:
|
||||
res += f"\n<b>{front} - {back}</b>"
|
||||
|
||||
res += (
|
||||
"\n➖➖➖➖➖➖➖➖➖➖\nEdit and type <code>.savedeck</code> in reply to"
|
||||
" this"
|
||||
" message\n<i>Note: you can edit title and cards, but other message should"
|
||||
" stay untouched, otherwise it can be saved incorrectly!</i> #Editing"
|
||||
)
|
||||
|
||||
await utils.answer(message, res)
|
||||
|
||||
def remove_html(self, text):
|
||||
return re.sub(r"<.*?>", "", text)
|
||||
|
||||
async def savedeckcmd(self, message: Message):
|
||||
"""<reply> - Save deck. Do not use if you don't know what is this"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or "#Editing" not in reply.text:
|
||||
await utils.answer(message, self.strings("save_deck_no_reply"))
|
||||
await asyncio.sleep(2)
|
||||
await message.delete()
|
||||
return False
|
||||
|
||||
deck_id = await self.get_from_message(message)
|
||||
if not deck_id:
|
||||
return
|
||||
|
||||
deck = self.decks[deck_id]
|
||||
self.decks[deck_id]["cards"] = []
|
||||
items = reply.text.split("\n")
|
||||
for item in items[2:-3]:
|
||||
self.decks[deck_id]["cards"].append(
|
||||
(
|
||||
self.remove_html(item.split(" - ")[0]),
|
||||
self.remove_html(item.split(" - ")[1]),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
self.decks[deck_id]["name"] = self.remove_html(
|
||||
re.search(r""(.+?)"", items[0]).group(1)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.set("decks", self.decks)
|
||||
|
||||
res = f"📋#Deck #{deck_id} <b>{deck['name']}</b>:\n➖➖➖➖➖➖➖➖➖➖"
|
||||
for i, (front, back) in enumerate(deck["cards"], start=1):
|
||||
res += f"\n<b>{i}. {front} - {back}</b>"
|
||||
res += "\n➖➖➖➖➖➖➖➖➖➖\n" + self.strings("deck_saved")
|
||||
|
||||
await utils.answer(reply, res)
|
||||
await message.delete()
|
||||
|
||||
async def htmldeckcmd(self, message: Message):
|
||||
"""<id> - Generates the page with specified deck"""
|
||||
deck_id = await self.get_from_message(message)
|
||||
if not deck_id:
|
||||
return
|
||||
|
||||
deck = self.decks[deck_id]
|
||||
await utils.answer(message, self.strings("generating_page"))
|
||||
deck_name = deck["name"]
|
||||
loc_cards = deck["cards"].copy()
|
||||
cards = dict(loc_cards)
|
||||
json_cards = json.dumps(cards).replace('"', '\\"')
|
||||
txt = io.BytesIO(
|
||||
TEMPLATE.replace("^title_deck_name^", deck_name)
|
||||
.replace("^deck_name^", deck_name)
|
||||
.replace("^json_cards^", json_cards)
|
||||
.encode("utf-8")
|
||||
)
|
||||
txt.name = "testing.html"
|
||||
await message.delete()
|
||||
await message.client.send_file(
|
||||
message.to_id,
|
||||
txt,
|
||||
caption=self.strings("offline_testing").format(deck_name),
|
||||
)
|
||||
58
hikariatama/ftg/forbid_joins.py
Normal file
58
hikariatama/ftg/forbid_joins.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/forbid_joins_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/forbid_joins.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from .. import loader
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ForbidJoinMod(loader.Module):
|
||||
"""Tired of trojans in modules, which join channels? Load this module!"""
|
||||
|
||||
strings = {
|
||||
"name": "ForbidJoin",
|
||||
"welcome": (
|
||||
"⚔️ <b>Unit «LAMBDA» will protect you from pesky"
|
||||
" </b><code>JoinChannelRequest</code>\n\n<b>All you need is to keep this"
|
||||
" module installed!</b>\n\n<i>If any developer tries to bypass this"
|
||||
" protection, he will be added to SCAM modules list.</i>\n\n⚠️"
|
||||
" <b>Protection will be activated after you restart userbot!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"welcome": (
|
||||
"⚔️ <b>Юнит «LAMBDA» будет защищать тебя от надоедливых"
|
||||
" </b><code>JoinChannelRequest</code>\n\n<b>Все, что требуется - держать"
|
||||
" этот модуль установленным!</b>\n\n<i>Если какой-либо разработчик"
|
||||
" попытается обойти эту защиту, он будет добавлен в список SCAM"
|
||||
" модулей.</i>\n\n⚠️ <b>Защита станет активной только после"
|
||||
" перезагрузки!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
async def on_dlmod(self, client, db):
|
||||
await self.inline.bot.send_photo(
|
||||
client._tg_id,
|
||||
"https://github.com/hikariatama/assets/raw/master/unit_lambda.png",
|
||||
caption=self.strings("welcome"),
|
||||
)
|
||||
|
||||
|
||||
# ⚠️⚠️ WARNING! ⚠️⚠️
|
||||
# If you are a module developer, and you'll try to bypass this protection to
|
||||
# force user join your channel, you will be added to SCAM modules
|
||||
# list and you will be banned from Hikka federation.
|
||||
# Let USER decide, which channel he will follow. Do not be so petty
|
||||
# I hope, you understood me.
|
||||
# Thank you
|
||||
169
hikariatama/ftg/forex_wss.py
Normal file
169
hikariatama/ftg/forex_wss.py
Normal file
@@ -0,0 +1,169 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/forex_wss.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/forex_wss.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: websockets
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import requests
|
||||
import websockets
|
||||
from aiogram.utils.exceptions import MessageNotModified
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
|
||||
@loader.tds
|
||||
class RealTimeValutesMod(loader.Module):
|
||||
"""Track valutes in real time. Updates more than once a second"""
|
||||
|
||||
strings = {
|
||||
"name": "RealTimeValutes",
|
||||
"loading": "😌 <b>Loading the most actual info from Forex...</b>",
|
||||
"wss_error": "🚫 <b>Socket connection error</b>",
|
||||
"exchanges": (
|
||||
"😌 <b>Exchange rates by Forex</b>\n\n<b>💵 1 USD = {:.2f} RUB\n💶 1 EUR ="
|
||||
" {:.2f} RUB</b>\n\n<i>This info is relevant to <u>{:%m/%d/%Y"
|
||||
" %H:%M:%S}</u></i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"loading": "😌 <b>Загружаю информацию с Forex...</b>",
|
||||
"wss_error": "🚫 <b>Ошибка подеключения к сокету</b>",
|
||||
"exchanges": (
|
||||
"😌 <b>Курсы валют Forex</b>\n\n<b>💵 1 USD = {:.2f} RUB\n💶 1 EUR = {:.2f}"
|
||||
" RUB</b>\n\n<i>Информация актуальна на <u>{:%m/%d/%Y %H:%M:%S}</u></i>"
|
||||
),
|
||||
"_cmd_doc_val": "Показать курсы валют",
|
||||
"_cls_doc": (
|
||||
"Отслеживает курсы валют в режиме реального времени. Обновляется несколько"
|
||||
" раз в секунду"
|
||||
),
|
||||
}
|
||||
|
||||
async def _connect(self):
|
||||
r = await utils.run_sync(
|
||||
requests.get,
|
||||
(
|
||||
f"https://rates-live.efxnow.com/signalr/negotiate?clientProtocol=2.1&connectionData=%5B%7B%22name%22%3A%22ratesstreamer%22%7D%5D&_={time.time() * 1000:.0f}"
|
||||
),
|
||||
)
|
||||
|
||||
token = quote_plus(r.json()["ConnectionToken"])
|
||||
base = f"wss://rates-live.efxnow.com/signalr/connect?transport=webSockets&clientProtocol=2.1&connectionToken={token}&connectionData=%5B%7B%22name%22%3A%22ratesstreamer%22%7D%5D&tid=8"
|
||||
|
||||
async with websockets.connect(base) as wss:
|
||||
await wss.send(
|
||||
'{"H":"ratesstreamer","M":"SubscribeToPricesUpdates","A":[["401203106","401203109"]],"I":8}'
|
||||
) # USD/RUB | EUR/RUB
|
||||
|
||||
self._restart_at = time.time() + 5 * 60
|
||||
|
||||
while time.time() < self._restart_at:
|
||||
rates = json.loads(await wss.recv())
|
||||
if "M" not in rates or not rates["M"]:
|
||||
continue
|
||||
|
||||
for row in rates["M"]:
|
||||
if "A" not in row:
|
||||
continue
|
||||
|
||||
rate = row["A"]
|
||||
valute = rate[0].split("|")[1].split("/")[0]
|
||||
rate = float(rate[0].split("|")[3])
|
||||
|
||||
self._rates[valute] = rate
|
||||
self._upd_time = time.time()
|
||||
|
||||
return await self._connect()
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._rates = {}
|
||||
self._upd_time = 0
|
||||
|
||||
self._ratelimit = 0
|
||||
|
||||
self._reload_markup = self.inline.generate_markup(
|
||||
{"text": "🔄 Update", "data": "update_exchanges"}
|
||||
)
|
||||
|
||||
self._task = asyncio.ensure_future(self._connect())
|
||||
|
||||
async def valcmd(self, message: Message):
|
||||
"""Show exchange rates"""
|
||||
try:
|
||||
m = self.strings("exchanges").format(
|
||||
self._rates["USD"],
|
||||
self._rates["EUR"],
|
||||
getattr(datetime, "datetime", datetime).fromtimestamp(self._upd_time),
|
||||
)
|
||||
except (KeyError, IndexError):
|
||||
await utils.answer(message, self.strings("wss_error"))
|
||||
return
|
||||
|
||||
try:
|
||||
await self.inline.form(
|
||||
m,
|
||||
message=message,
|
||||
reply_markup={"text": "🔄 Update", "data": "update_exchanges"},
|
||||
disable_security=True,
|
||||
silent=True,
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(message, m)
|
||||
|
||||
@loader.inline_everyone
|
||||
async def reload_callback_handler(self, call: InlineCall):
|
||||
"""Processes 'reload' button clicks"""
|
||||
if call.data != "update_exchanges":
|
||||
return
|
||||
|
||||
if self._ratelimit and time.time() < self._ratelimit:
|
||||
await call.answer("Do not spam this button")
|
||||
return
|
||||
|
||||
self._ratelimit = time.time() + 1
|
||||
|
||||
try:
|
||||
await self.inline.bot.edit_message_text(
|
||||
inline_message_id=call.inline_message_id,
|
||||
text=self.strings("exchanges").format(
|
||||
self._rates["USD"],
|
||||
self._rates["EUR"],
|
||||
getattr(datetime, "datetime", datetime).fromtimestamp(
|
||||
self._upd_time
|
||||
),
|
||||
),
|
||||
reply_markup=self._reload_markup,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
await call.answer("😌 Exchange rates update complete!", show_alert=True)
|
||||
except (IndexError, KeyError):
|
||||
await call.answer("Socket connection error", show_alert=True)
|
||||
return
|
||||
except MessageNotModified:
|
||||
await call.answer(
|
||||
"Exchange rates have not changes since last update", show_alert=True
|
||||
)
|
||||
return
|
||||
|
||||
async def on_unload(self):
|
||||
self._task.cancel()
|
||||
139
hikariatama/ftg/fuck_tags.py
Normal file
139
hikariatama/ftg/fuck_tags.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/emoji/256/000000/middle-finger-light-skin-tone.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/fuck_tags.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
|
||||
import asyncio
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FuckTagsMod(loader.Module):
|
||||
"""Auto-read tags and messages in selected chats"""
|
||||
|
||||
strings = {
|
||||
"name": "FuckTags",
|
||||
"args": "🚫 <b>Incorrect args specified</b>",
|
||||
"on": "✅ <b>Now I ignore tags in this chat</b>",
|
||||
"off": "✅ <b>Now I don't ignore tags in this chat</b>",
|
||||
"on_strict": "✅ <b>Now I automatically read messages in this chat</b>",
|
||||
"off_strict": "✅ <b>Now I don't automatically read messages in this chat</b>",
|
||||
"do_not_tag_me": "🦊 <b>Please, do not tag me.</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Указаны неверные аргументы</b>",
|
||||
"on": "✅ <b>Теперь я буду игнорировать теги в этом чате</b>",
|
||||
"off": "✅ <b>Теперь я не буду игнорировать теги в этом чате</b>",
|
||||
"on_strict": (
|
||||
"✅ <b>Теперь я буду автоматически читать сообщения в этом чате</b>"
|
||||
),
|
||||
"off_strict": (
|
||||
"✅ <b>Теперь я не буду автоматически читать сообщения в этом чате</b>"
|
||||
),
|
||||
"do_not_tag_me": "🦊 <b>Пожалуйста, не тегайте меня.</b>",
|
||||
"_cmd_doc_fucktags": "[чат] - Включить\\выключить тихие теги",
|
||||
"_cmd_doc_fuckall": "[чат] - Включить\\выключить авточтение",
|
||||
"_cmd_doc_fuckchats": "Показать активные авточтения в чатах",
|
||||
"_cls_doc": "Автоматически читает теги в выбранных чатах",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._ratelimit = []
|
||||
|
||||
async def fucktagscmd(self, message: Message):
|
||||
"""[chat] - Toggle notags"""
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
try:
|
||||
args = int(args)
|
||||
except Exception:
|
||||
pass
|
||||
cid = (await self._client.get_entity(args)).id
|
||||
except Exception:
|
||||
cid = utils.get_chat_id(message)
|
||||
|
||||
self._ratelimit = list(set(self._ratelimit) - set([cid]))
|
||||
|
||||
if cid not in self.get("tags", []):
|
||||
self.set("tags", self.get("tags", []) + [cid])
|
||||
await utils.answer(message, self.strings("on"))
|
||||
else:
|
||||
self.set(
|
||||
"tags",
|
||||
list(set(self.get("tags", [])) - set([cid])),
|
||||
)
|
||||
await utils.answer(message, self.strings("off"))
|
||||
|
||||
async def fuckallcmd(self, message: Message):
|
||||
"""[chat] - Toggle autoread"""
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
if str(args).isdigit():
|
||||
args = int(args)
|
||||
cid = (await self._client.get_entity(args)).id
|
||||
except Exception:
|
||||
cid = utils.get_chat_id(message)
|
||||
|
||||
if cid not in self.get("strict", []):
|
||||
self.set("strict", self.get("strict", []) + [cid])
|
||||
await utils.answer(message, self.strings("on_strict"))
|
||||
return
|
||||
|
||||
self.set(
|
||||
"strict",
|
||||
list(set(self.get("strict", [])) - set([cid])),
|
||||
)
|
||||
await utils.answer(message, self.strings("off_strict"))
|
||||
|
||||
async def fuckchatscmd(self, message: Message):
|
||||
"""Показать активные авточтения в чатах"""
|
||||
res = "<b>== FuckTags ==</b>\n"
|
||||
for chat in self.get("tags", []):
|
||||
try:
|
||||
c = await self._client.get_entity(chat)
|
||||
res += (c.title if c.title is not None else c.first_name) + "\n"
|
||||
except Exception:
|
||||
res += str(chat) + "\n"
|
||||
|
||||
res += "\n<b>== FuckMessages ==</b>\n"
|
||||
for chat in self.get("strict", []):
|
||||
try:
|
||||
c = await self._client.get_entity(chat)
|
||||
res += (c.title if c.title is not None else c.first_name) + "\n"
|
||||
except Exception:
|
||||
res += str(chat) + "\n"
|
||||
|
||||
await utils.answer(message, res)
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
if not hasattr(message, "text") or not isinstance(message, Message):
|
||||
return
|
||||
|
||||
if utils.get_chat_id(message) in self.get("tags", []) and message.mentioned:
|
||||
await self._client.send_read_acknowledge(
|
||||
message.peer_id,
|
||||
message,
|
||||
clear_mentions=True,
|
||||
)
|
||||
|
||||
if utils.get_chat_id(message) not in self._ratelimit:
|
||||
msg = await utils.answer(message, self.strings("do_not_tag_me"))
|
||||
self._ratelimit += [utils.get_chat_id(message)]
|
||||
await asyncio.sleep(2)
|
||||
await msg.delete()
|
||||
elif utils.get_chat_id(message) in self.get("strict", []):
|
||||
await self._client.send_read_acknowledge(message.peer_id, message)
|
||||
84
hikariatama/ftg/git_pusher.py
Normal file
84
hikariatama/ftg/git_pusher.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/git_pusher.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/git_pusher.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import os
|
||||
from random import choice
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class GitPusherMod(loader.Module):
|
||||
"""Easily push your repo from within the Telegram"""
|
||||
|
||||
strings = {
|
||||
"name": "GitPusher",
|
||||
"bad_dir": "🚫 <b>Invalid directory</b>",
|
||||
"no_dir": "🚫 <b>Specify directory with </b><code>.setghdir</code>",
|
||||
"dir_set": "🌳 <b>Updated git directory to</b> <code>{}</code>",
|
||||
"terminal_required": "🚫 <b>Terminal module is required</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"bad_dir": "🚫 <b>Неверная директория</b>",
|
||||
"no_dir": "🚫 <b>Укажи директорию используя </b><code>.setghdir</code>",
|
||||
"dir_set": "🌳 <b>Директория обновлена на</b> <code>{}</code>",
|
||||
"terminal_required": "🚫 <b>Необходими модуль Terminal</b>",
|
||||
"_cmd_doc_setghdir": "<path> - Установить директорию в качестве основной",
|
||||
"_cmd_doc_push": "[commit message] - Закоммитить установленную директорию",
|
||||
"_cls_doc": "Быстро коммить изменения в директории не выходя из Телеграм",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.commits = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://gist.github.com/hikariatama/b0a7001306ebcc74535992c13cd33f99/raw/7a5e2c0439d31c4fedf2530ffae650ae1cb9dd0c/commit_msgs.json",
|
||||
)
|
||||
).json()
|
||||
|
||||
async def setghdircmd(self, message: Message):
|
||||
"""<path> - Set directory as upstream"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args or not os.path.isdir(args.strip()):
|
||||
await utils.answer(message, self.strings("bad_dir"))
|
||||
return
|
||||
|
||||
self.set("dir", args)
|
||||
await utils.answer(message, self.strings("dir_set").format(args))
|
||||
|
||||
async def pushcmd(self, message: Message):
|
||||
"""[commit message] - Push current upstream directory"""
|
||||
if not self.get("dir"):
|
||||
await utils.answer(message, self.strings("no_dir"))
|
||||
return
|
||||
|
||||
if "terminal" not in self.allmodules.commands:
|
||||
await utils.answer(message, self.strings("terminal_required"))
|
||||
return
|
||||
|
||||
args = (utils.get_args_raw(message) or choice(self.commits)).replace('"', '\\"')
|
||||
|
||||
message = await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"<code>.terminal cd {utils.escape_html(self.get('dir'))} && git commit"
|
||||
f' -am "{utils.escape_html(args)}" && git push</code>'
|
||||
),
|
||||
)
|
||||
|
||||
await self.allmodules.commands["terminal"](message)
|
||||
395
hikariatama/ftg/grustnogram.py
Normal file
395
hikariatama/ftg/grustnogram.py
Normal file
@@ -0,0 +1,395 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/grustnogram_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/grustnogram.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: Pillow requests_toolbelt
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
__version__ = (1, 0, 1)
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import textwrap
|
||||
|
||||
import requests
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from requests_toolbelt import MultipartEncoder
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
fnt = requests.get(
|
||||
"https://github.com/hikariatama/assets/raw/master/EversonMono.ttf"
|
||||
).content
|
||||
|
||||
font = lambda size: ImageFont.truetype(
|
||||
io.BytesIO(fnt),
|
||||
size,
|
||||
encoding="UTF-8",
|
||||
)
|
||||
|
||||
|
||||
async def create_badge(data) -> bytes:
|
||||
SIZE = (1200, 300)
|
||||
INNER_MARGIN = (30, 30)
|
||||
|
||||
thumb = Image.open(
|
||||
io.BytesIO((await utils.run_sync(requests.get, data["avatar"])).content)
|
||||
)
|
||||
|
||||
im = Image.new("RGB", SIZE, (11, 11, 11))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
thumb_size = SIZE[1] - INNER_MARGIN[1] * 2
|
||||
|
||||
thumb = thumb.resize((thumb_size, thumb_size))
|
||||
# thumb = add_corners(thumb, 10)
|
||||
|
||||
im.paste(thumb, INNER_MARGIN)
|
||||
|
||||
tpos = (
|
||||
INNER_MARGIN[0] + thumb_size + INNER_MARGIN[0] + 8,
|
||||
INNER_MARGIN[1],
|
||||
)
|
||||
|
||||
draw.text(tpos, f'{data["name"]}', (255, 255, 255), font=font(64))
|
||||
link_pos = tpos[1] + 8 + font(64).getsize(data["name"])[1]
|
||||
draw.text(
|
||||
(tpos[0], link_pos),
|
||||
f'https://grustnogram.ru/u/{data["nickname"]}',
|
||||
(220, 220, 220),
|
||||
font=font(32),
|
||||
)
|
||||
|
||||
offset = link_pos + 16 + font(32).getsize(data["nickname"])[1]
|
||||
for line in textwrap.wrap(
|
||||
data["about"], width=(SIZE[0] - tpos[0]) // font(32).getsize("a")[0]
|
||||
):
|
||||
draw.text(
|
||||
(
|
||||
tpos[0],
|
||||
offset,
|
||||
),
|
||||
line,
|
||||
(180, 180, 180),
|
||||
font=font(32),
|
||||
)
|
||||
offset += font(32).getsize(line)[1]
|
||||
|
||||
offset += 16
|
||||
|
||||
draw.text(
|
||||
(tpos[0], offset),
|
||||
f'Followers: {data["followers"]} / Follow: {data["follow"]}',
|
||||
(150, 150, 150),
|
||||
font=font(26),
|
||||
)
|
||||
|
||||
img = io.BytesIO()
|
||||
im.save(img, format="PNG")
|
||||
return img.getvalue()
|
||||
|
||||
|
||||
@loader.tds
|
||||
class GrustnoGramMod(loader.Module):
|
||||
"""Grustnogram.ru Telegram client"""
|
||||
|
||||
strings = {
|
||||
"name": "GrustnoGram",
|
||||
"invalid_args": (
|
||||
"🚫 <b>Invalid args. Pass email and password, separated by space</b>"
|
||||
),
|
||||
"api_error": "🚫 <b>API error.</b>\n<pre>{}</pre>",
|
||||
"auth_successful": "🖤 <b>Auth successful as {}</b>",
|
||||
"no_photo": "🚫 <b>You need to reply to a photo</b>",
|
||||
"published": (
|
||||
'🖤 <b><a href="https://grustnogram.ru/p/{}">Post</a> successfully'
|
||||
" published</b>"
|
||||
),
|
||||
"delete": "🗑 Delete",
|
||||
"deleted": "🖤 <b>Post deleted</b>",
|
||||
"notif_follow": (
|
||||
'🖤 <b><a href="https://grustnogram.ru/u/{0}">{0}</a> is now sad with'
|
||||
" you</b>"
|
||||
),
|
||||
"notif_like": (
|
||||
'🖤 <b><a href="https://grustnogram.ru/u/{0}">{0}</a> have broken heart'
|
||||
" from"
|
||||
' your <a href="https://grustnogram.ru/p/{1}">post</a></b>'
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
if not self.get("email") or not self.get("password"):
|
||||
self.sadauthcmd = self.sadauthcmd_
|
||||
else:
|
||||
self._register()
|
||||
|
||||
self._task = asyncio.ensure_future(self._poller())
|
||||
|
||||
async def on_unload(self):
|
||||
if hasattr(self, "_task"):
|
||||
self._task.cancel()
|
||||
|
||||
def _register(self):
|
||||
self.sadmecmd = self.sadmecmd_
|
||||
self.saduploadcmd = self.saduploadcmd_
|
||||
|
||||
async def _login(self, email: str, password: str) -> dict:
|
||||
return (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://api.grustnogram.ru/sessions",
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"user-agent": "Hikka Userbot",
|
||||
},
|
||||
data=json.dumps({"email": email, "password": password}).encode(),
|
||||
)
|
||||
).json()
|
||||
|
||||
async def _get_self(self) -> dict:
|
||||
return (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.grustnogram.ru/users/self",
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"user-agent": "Hikka Userbot",
|
||||
"access-token": self.get("token", "undefined"),
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
async def _publish(self, media: bytes, caption: str) -> dict:
|
||||
boundary = "----WebKitFormBoundary" + "".join(
|
||||
random.sample(string.ascii_letters + string.digits, 16)
|
||||
)
|
||||
|
||||
m = MultipartEncoder(
|
||||
fields={"file": ("image.jpg", io.BytesIO(media), "image/jpg")},
|
||||
boundary=boundary,
|
||||
)
|
||||
|
||||
res = (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://media.grustnogram.ru/cors.php",
|
||||
headers={
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"user-agent": "Hikka Userbot",
|
||||
"access-token": self.get("token", "undefined"),
|
||||
"content-type": m.content_type,
|
||||
},
|
||||
data=m,
|
||||
)
|
||||
).json()
|
||||
|
||||
if any(res["err_msg"]):
|
||||
raise RuntimeError(f"Can't upload image {json.dumps(res, indent=4)}")
|
||||
|
||||
url = res["data"]
|
||||
|
||||
return (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://api.grustnogram.ru/posts",
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"user-agent": "Hikka Userbot",
|
||||
"access-token": self.get("token", "undefined"),
|
||||
},
|
||||
data=json.dumps(
|
||||
{"filter": 1, "text": caption, "media": [url]}
|
||||
).encode(),
|
||||
)
|
||||
).json()
|
||||
|
||||
async def _delete(self, id_: int) -> dict:
|
||||
return (
|
||||
await utils.run_sync(
|
||||
requests.delete,
|
||||
f"https://api.grustnogram.ru/posts/{id_}",
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"user-agent": "Hikka Userbot",
|
||||
"access-token": self.get("token", "undefined"),
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
async def sadauthcmd_(self, message: Message):
|
||||
"""<email> <password> - Auth on grustnogram.ru"""
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
email, password = args.split(maxsplit=1)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings("invalid_args"))
|
||||
return
|
||||
|
||||
result = await self._login(email, password)
|
||||
|
||||
if any(result["err_msg"]):
|
||||
await self._api_error(message, result)
|
||||
return
|
||||
|
||||
token = result["data"]["access_token"]
|
||||
|
||||
self.set("email", email)
|
||||
self.set("password", password)
|
||||
self.set("token", token)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("auth_successful").format(
|
||||
(await self._get_self())["data"]["name"]
|
||||
),
|
||||
)
|
||||
self._register()
|
||||
|
||||
async def sadmecmd_(self, message: Message):
|
||||
"""Get sad banner"""
|
||||
await message.delete()
|
||||
me = (await self._get_self())["data"]
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
file=await create_badge(me),
|
||||
caption=f"https://grustnogram.ru/u/{me['nickname']}",
|
||||
)
|
||||
|
||||
async def _api_error(self, message: Message, result: dict):
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("api_error").format(
|
||||
json.dumps(
|
||||
result,
|
||||
indent=4,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
async def inline_delete(self, call: InlineCall, id_: int):
|
||||
result = await self._delete(id_)
|
||||
if any(result["err_msg"]):
|
||||
await self._api_error(call, result)
|
||||
return
|
||||
|
||||
await call.edit(self.strings("deleted"))
|
||||
await call.unload()
|
||||
|
||||
async def _poller(self):
|
||||
try:
|
||||
while True:
|
||||
if not self.get("token"):
|
||||
await asyncio.sleep(10)
|
||||
continue
|
||||
|
||||
res = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.grustnogram.ru/status",
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"user-agent": "Hikka Userbot",
|
||||
"access-token": self.get("token", "undefined"),
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
if not res["data"]["notifications_count"]:
|
||||
await asyncio.sleep(30)
|
||||
continue
|
||||
|
||||
logger.debug(
|
||||
f"Got {res['data']['notifications_count']} notification(-s) from"
|
||||
" GrustnoGram"
|
||||
)
|
||||
|
||||
res = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.grustnogram.ru/notifications",
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"user-agent": "Hikka Userbot",
|
||||
"access-token": self.get("token", "undefined"),
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
if any(res["data"]):
|
||||
for notification in res["data"]:
|
||||
if int(notification["data"]["read"]):
|
||||
continue
|
||||
|
||||
if notification["type"] == "follow":
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
self.strings("notif_follow").format(
|
||||
notification["data"]["nickname"]
|
||||
),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
elif notification["type"] == "like":
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
self.strings("notif_like").format(
|
||||
notification["data"]["nickname"],
|
||||
notification["data"]["post_url"],
|
||||
),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Unknown notification type"
|
||||
f" {json.dumps(notification, indent=4)}"
|
||||
)
|
||||
|
||||
await asyncio.sleep(10)
|
||||
except Exception:
|
||||
logger.exception("GrustnoGram poller got himself in trouble!")
|
||||
|
||||
async def saduploadcmd_(self, message: Message):
|
||||
"""Upload image to Grustnogram"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.photo:
|
||||
await utils.answer(message, self.strings("no_photo"))
|
||||
return
|
||||
|
||||
media = await self._client.download_file(reply.media, bytes)
|
||||
|
||||
caption = getattr(reply, "raw_text", None) or ""
|
||||
result = await self._publish(media, caption)
|
||||
|
||||
if any(result["err_msg"]):
|
||||
await self._api_error(message, result)
|
||||
return
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("published").format(result["data"]["url"]),
|
||||
reply_markup={
|
||||
"text": self.strings("delete"),
|
||||
"callback": self.inline_delete,
|
||||
"args": (result["data"]["id"],),
|
||||
},
|
||||
)
|
||||
6216
hikariatama/ftg/hikarichat.py
Normal file
6216
hikariatama/ftg/hikarichat.py
Normal file
File diff suppressed because it is too large
Load Diff
195
hikariatama/ftg/hikkamods_socket.py
Normal file
195
hikariatama/ftg/hikkamods_socket.py
Normal file
@@ -0,0 +1,195 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
__version__ = (2, 0, 1)
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta developer: @hikarimods
|
||||
# requires: rsa base64
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
import rsa
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, main, translations, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
pubkey = rsa.PublicKey(
|
||||
7110455561671499155469672749235101198284219627796886527432331759773809536504953770286294224729310191037878347906574131955439231159825047868272932664151403,
|
||||
65537,
|
||||
)
|
||||
|
||||
REGEXES = [
|
||||
re.compile(
|
||||
r"https:\/\/github\.com\/([^\/]+?)\/([^\/]+?)\/raw\/(?:main|master)\/([^\/]+\.py)"
|
||||
),
|
||||
re.compile(
|
||||
r"https:\/\/raw\.githubusercontent\.com\/([^\/]+?)\/([^\/]+?)\/(?:main|master)\/([^\/]+\.py)"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HikkaModsSocketMod(loader.Module):
|
||||
"""Gives @hikkamods_bot a right to download modules from official modules aggregator and autoupdate them"""
|
||||
|
||||
strings = {"name": "HikkaModsSocket"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"autoupdate",
|
||||
False,
|
||||
(
|
||||
"Do you want to autoupdate modules? (Join @heta_updates in order"
|
||||
" for this option to take effect) ⚠️ Use at your own risk!"
|
||||
),
|
||||
validator=loader.validators.Boolean(),
|
||||
)
|
||||
)
|
||||
|
||||
async def client_ready(self, *_):
|
||||
if self.config["autoupdate"] and hasattr(self, "request_join"):
|
||||
await self.request_join(
|
||||
"@heta_updates",
|
||||
"This channel is the source of update notifications",
|
||||
)
|
||||
|
||||
if self.get("nomute"):
|
||||
return
|
||||
|
||||
await utils.dnd(self._client, "@hikkamods_bot", archive=False)
|
||||
self.set("nomute", True)
|
||||
|
||||
@loader.loop(interval=60 * 60 * 6, autostart=True, wait_before=True)
|
||||
async def stats_collector(self):
|
||||
if not self._db.get(main.__name__, "stats", True):
|
||||
raise loader.StopLoop
|
||||
|
||||
logger.debug("Sending additional stats")
|
||||
for module in [
|
||||
mod.__origin__
|
||||
for mod in self.allmodules.modules
|
||||
if utils.check_url(mod.__origin__)
|
||||
]:
|
||||
try:
|
||||
await self.lookup("loader")._send_stats(module)
|
||||
except Exception:
|
||||
logger.debug(f"Failed to send stats for {module}", exc_info=True)
|
||||
|
||||
async def _load_module(self, url: str, message: Optional[Message] = None):
|
||||
loader_m = self.lookup("loader")
|
||||
|
||||
await loader_m.download_and_install(url, None)
|
||||
|
||||
if getattr(loader_m, "_fully_loaded", getattr(loader_m, "fully_loaded", False)):
|
||||
getattr(
|
||||
loader_m,
|
||||
"_update_modules_in_db",
|
||||
getattr(loader_m, "update_modules_in_db", lambda: None),
|
||||
)()
|
||||
|
||||
if message:
|
||||
if any(link == url for link in loader_m.get("loaded_modules", {}).values()):
|
||||
await self._client.inline_query(
|
||||
"@hikkamods_bot",
|
||||
f"#confirm_load {message.raw_text.splitlines()[2].strip()}",
|
||||
)
|
||||
else:
|
||||
await self._client.inline_query(
|
||||
"@hikkamods_bot",
|
||||
f"#confirm_fload {message.raw_text.splitlines()[2].strip()}",
|
||||
)
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
if not isinstance(message, Message):
|
||||
return
|
||||
|
||||
if message.sender_id == 5519484330 and message.raw_text.startswith("#install"):
|
||||
await message.delete()
|
||||
|
||||
fileref = (
|
||||
message.raw_text.split("#install:")[1].strip().splitlines()[0].strip()
|
||||
)
|
||||
sig = base64.b64decode(message.raw_text.splitlines()[1].strip().encode())
|
||||
try:
|
||||
rsa.verify(
|
||||
rsa.compute_hash(fileref.encode("utf-8"), "SHA-1"), sig, pubkey
|
||||
)
|
||||
except rsa.pkcs1.VerificationError:
|
||||
logger.error(f"Got message with non-verified signature ({fileref=})")
|
||||
return
|
||||
|
||||
await self._load_module(f"https://heta.hikariatama.ru/{fileref}", message)
|
||||
elif message.sender_id == 5519484330 and message.raw_text.startswith(
|
||||
"#setlang"
|
||||
):
|
||||
lang = message.raw_text.split()[1]
|
||||
self._db.set(translations.__name__, "lang", lang)
|
||||
await self.allmodules.reload_translations()
|
||||
await self._client.inline_query("@hikkamods_bot", "#confirm_setlang")
|
||||
elif (
|
||||
utils.get_chat_id(message) == 1688624566
|
||||
and "Heta url: " in message.raw_text
|
||||
):
|
||||
url = message.raw_text.split("Heta url: ")[1].strip()
|
||||
heta_dev, heta_repo, heta_mod = (
|
||||
url.lower().split("hikariatama.ru/")[1].split("/")
|
||||
)
|
||||
|
||||
if heta_dev == "hikariatama" and heta_repo == "ftg":
|
||||
urls = [f"https://mods.hikariatama.ru/{heta_mod}", url]
|
||||
if any(
|
||||
getattr(module, "__origin__", None).lower().strip("/") in urls
|
||||
for module in self.allmodules.modules
|
||||
):
|
||||
await self._load_module(urls[0])
|
||||
await asyncio.sleep(random.randint(1, 10))
|
||||
await self._client.inline_query(
|
||||
"@hikkamods_bot",
|
||||
f"#confirm_update_noheta {url.split('hikariatama.ru/')[1]}",
|
||||
)
|
||||
return
|
||||
|
||||
if any(
|
||||
getattr(module, "__origin__", "").lower().strip("/")
|
||||
== url.lower().strip("/")
|
||||
for module in self.allmodules.modules
|
||||
):
|
||||
await self._load_module(url)
|
||||
await asyncio.sleep(random.randint(1, 10))
|
||||
await self._client.inline_query(
|
||||
"@hikkamods_bot",
|
||||
f"#confirm_update {url.split('hikariatama.ru/')[1]}",
|
||||
)
|
||||
return
|
||||
|
||||
for module in self.allmodules.modules:
|
||||
link = getattr(module, "__origin__", "").lower().strip("/")
|
||||
for regex in REGEXES:
|
||||
if regex.search(link):
|
||||
dev, repo, mod = regex.search(link).groups()
|
||||
if dev == heta_dev and repo == heta_repo and mod == heta_mod:
|
||||
await self._load_module(link)
|
||||
await asyncio.sleep(random.randint(1, 10))
|
||||
await self._client.inline_query(
|
||||
"@hikkamods_bot",
|
||||
(
|
||||
"#confirm_update_noheta"
|
||||
f" {url.split('hikariatama.ru/')[1]}"
|
||||
),
|
||||
)
|
||||
return
|
||||
112
hikariatama/ftg/httpsc.py
Normal file
112
hikariatama/ftg/httpsc.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/external-flaticons-lineal-color-flat-icons/512/000000/external-status-agile-flaticons-lineal-color-flat-icons-2.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/httpsc.jpg
|
||||
# meta developer: @hikarimods
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
responses = {
|
||||
100: ("ℹ️ Continue", "Запрос принят, продолжай"),
|
||||
101: ("ℹ️ Switching Protocols", "Изменение протокола; подчинйся Upgrade хедеру"),
|
||||
200: ("✅ OK", "Запрос успешный, контент отображен"),
|
||||
201: ("✅ Created", "Запрос создан, url прилагается"),
|
||||
202: ("✅ Accepted", "Запрос принят и обрабатывается оффлайн"),
|
||||
203: ("✅ Non-Authoritative Information", "Загружено из кэша"),
|
||||
204: ("✅ No Content", "Запрос успешный, нет контента"),
|
||||
205: ("✅ Reset Content", "Очистить форму для продолжения"),
|
||||
206: ("✅ Partial Content", "Частичный контент прилагается"),
|
||||
300: ("↩️ Multiple Choices", "У объекта есть несколько источников"),
|
||||
301: ("↩️ Moved Permanently", "Адрес изменен навсегда"),
|
||||
302: ("↩️ Found", "Адрес изменен временно"),
|
||||
303: ("↩️ See Other", "Адрес и\\или объект изменен"),
|
||||
304: ("↩️ Not Modified", "Контент не изменился с предыдущего запроса"),
|
||||
305: ("↩️ Use Proxy", "Неверная локация"),
|
||||
307: ("↩️ Temporary Redirect", "Временное перенаправление"),
|
||||
400: ("🚫 Bad Request", "Ошибка формирования запроса со стороны клиента"),
|
||||
401: ("🚫 Unauthorized", "Не авторизован"),
|
||||
402: ("🚫 Payment Required", "Не оплачено"),
|
||||
403: ("🚫 Forbidden", "Доступ запрещен - бан / нехватка прав"),
|
||||
404: ("🚫 Not Found", "Не найдено"),
|
||||
405: ("🚫 Method Not Allowed", "Метод запрещен"),
|
||||
406: ("🚫 Not Acceptable", "Метод недоступен"),
|
||||
407: ("🚫 Proxy Authentication Required", "Не хватает авторизации прокси"),
|
||||
408: ("🚫 Request Timeout", "Время ожидания истекло"),
|
||||
409: ("🚫 Conflict", "Конфликт запросов"),
|
||||
410: ("🚫 Gone", "Адрес не существует и был перемещен"),
|
||||
411: ("🚫 Length Required", "Требуется указание длины контента запроса"),
|
||||
412: ("🚫 Precondition Failed", "Предусловие в хедерах неверно"),
|
||||
413: ("🚫 Request Entity Too Large", "Запрос слишком большой"),
|
||||
414: ("🚫 Request-URI Too Long", "Ссылка слишком большая"),
|
||||
415: ("🚫 Unsupported Media Type", "Неподдерживаеый формат контента"),
|
||||
416: ("🚫 Requested Range Not Satisfiable", "Не входит в разрешенный диапазон"),
|
||||
417: ("🚫 Expectation Failed", "Ожидания не выполняются"),
|
||||
500: ("💢 Internal Server Error", "Ошибка сервера"),
|
||||
501: ("💢 Not Implemented", "Операция не поддерживается"),
|
||||
502: ("💢 Bad Gateway", "Прокси \\ шлюз недоступен"),
|
||||
503: ("💢 Service Unavailable", "Перегрузка сервера"),
|
||||
504: ("💢 Gateway Timeout", "Таймаут прокси \\ шлюза"),
|
||||
505: ("💢 HTTP Version Not Supported", "Версия HTTP не соответствует требованиям"),
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HttpErrorsMod(loader.Module):
|
||||
"""Dictionary of http status codes"""
|
||||
|
||||
strings = {
|
||||
"name": "HttpStatusCodes",
|
||||
"args_incorrect": "<b>Incorrect args</b>",
|
||||
"not_found": "<b>Code not found</b>",
|
||||
"syntax_error": "<b>Args are mandatory</b>",
|
||||
"scode": "<b>{} {}</b>\n⚜️ Описание кода: <i>{}</i>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args_incorrect": "<b>Неверные аргументы</b>",
|
||||
"not_found": "<b>Код не найден</b>",
|
||||
"syntax_error": "<b>Аргументы обязательны</b>",
|
||||
"_cmd_doc_httpsc": "<код> - Получить информацию о HTTP-коде",
|
||||
"_cmd_doc_httpscs": "Показать все доступные коды",
|
||||
"_cls_doc": "Словарь HTTP-кодов",
|
||||
}
|
||||
|
||||
@loader.unrestricted
|
||||
async def httpsccmd(self, message: Message):
|
||||
"""<statuscode> - Get status code info"""
|
||||
args = utils.get_args(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("syntax_error", message))
|
||||
|
||||
try:
|
||||
if int(args[0]) not in responses:
|
||||
await utils.answer(message, self.strings("not_found", message))
|
||||
except ValueError:
|
||||
await utils.answer(message, self.strings("args_incorrect", message))
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("scode", message).format(
|
||||
responses[int(args[0])][0], args[0], responses[int(args[0])][1]
|
||||
),
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
async def httpscscmd(self, message: Message):
|
||||
"""Get all http status codes"""
|
||||
await utils.answer(
|
||||
message,
|
||||
"\n".join(
|
||||
[f"<b>{str(sc)}: {_[0][0]} {_[1]}</b>" for sc, _ in responses.items()]
|
||||
),
|
||||
)
|
||||
92
hikariatama/ftg/hw.py
Normal file
92
hikariatama/ftg/hw.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/hw_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/hw.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from random import randint
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HomeworkMod(loader.Module):
|
||||
"""Simple Homework planner"""
|
||||
|
||||
strings = {
|
||||
"name": "HomeWork",
|
||||
"no_hometask": "🚫 <b>You haven't provided hometask</b>",
|
||||
"new_hometask": "<b>Hometask </b><code>#{}</code>:\n<pre>{}</pre>",
|
||||
"not_found": "<b>🚫 Hometask not found</b",
|
||||
"removed": "<b>✅ Hometask removed</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_hometask": "🚫 <b>Укажи домашнее задание</b>",
|
||||
"new_hometask": "<b>Домашнее задание </b><code>#{}</code>:\n<pre>{}</pre>",
|
||||
"not_found": "<b>🚫 Домашнее задание не найдено</b",
|
||||
"removed": "<b>✅ Домашнее задание удалено</b>",
|
||||
"_cmd_doc_hw": "<item> - Новое домашнее задание",
|
||||
"_cmd_doc_hwl": "Список домашних заданий",
|
||||
"_cmd_doc_uhw": "<id> - Удалить домашнее задание",
|
||||
"_cls_doc": "Простой планнер домашних заданий",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.hw = self.get("hw", {})
|
||||
|
||||
async def hwcmd(self, message: Message):
|
||||
"""<item> - New hometask"""
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
if args == "" and not reply:
|
||||
await utils.answer(message, self.strings("no_hometask"))
|
||||
return
|
||||
|
||||
if args == "":
|
||||
args = reply.text
|
||||
|
||||
random_id = str(randint(10000, 99999))
|
||||
|
||||
self.hw[random_id] = args
|
||||
|
||||
self.set("hw", self.hw)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("new_hometask").format(random_id, str(args)),
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
async def hwlcmd(self, message: Message):
|
||||
"""List of hometasks"""
|
||||
res = "<b>#HW:</b>\n\n"
|
||||
|
||||
for item_id, item in self.hw.items():
|
||||
res += f"🔸 <code>.uhw {item_id}</code>: <code>{item}" + "</code>\n"
|
||||
|
||||
await utils.answer(message, res)
|
||||
|
||||
async def uhwcmd(self, message: Message):
|
||||
"""<id> - Remove hometask"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args.startswith("#"):
|
||||
args = args[1:]
|
||||
|
||||
if args not in self.hw:
|
||||
await utils.answer(message, self.strings("not_found"))
|
||||
return
|
||||
|
||||
del self.hw[args]
|
||||
self.set("hw", self.hw)
|
||||
await utils.answer(message, self.strings("removed"))
|
||||
97
hikariatama/ftg/img2pdf.py
Normal file
97
hikariatama/ftg/img2pdf.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/stickers/500/000000/pdf.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/img2pdf.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: Pillow
|
||||
|
||||
import io
|
||||
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Img2PdfMod(loader.Module):
|
||||
"""Packs images to pdf"""
|
||||
|
||||
strings = {
|
||||
"name": "Img2Pdf",
|
||||
"processing": (
|
||||
"<emoji document_id=5307865634032329170>🫥</emoji> <b>Processing"
|
||||
" files...</b>"
|
||||
),
|
||||
}
|
||||
strings_ru = {
|
||||
"processing": (
|
||||
"<emoji document_id=5307865634032329170>🫥</emoji> <b>Обрабатываю"
|
||||
" файлы...</b>"
|
||||
)
|
||||
}
|
||||
strings_es = {
|
||||
"processing": (
|
||||
"<emoji document_id=5307865634032329170>🫥</emoji> <b>Procesando"
|
||||
" archivos...</b>"
|
||||
)
|
||||
}
|
||||
strings_de = {
|
||||
"processing": (
|
||||
"<emoji document_id=5307865634032329170>🫥</emoji> <b>Dateien werden"
|
||||
" verarbeitet...</b>"
|
||||
)
|
||||
}
|
||||
strings_tr = {
|
||||
"processing": (
|
||||
"<emoji document_id=5307865634032329170>🫥</emoji> <b>Dosyalar"
|
||||
" işleniyor...</b>"
|
||||
)
|
||||
}
|
||||
|
||||
@loader.unrestricted
|
||||
async def img2pdfcmd(self, message: Message):
|
||||
"""<filename | optional> - Pack images into pdf"""
|
||||
try:
|
||||
start_offset = (
|
||||
message.id if message.media else (await message.get_reply_message()).id
|
||||
)
|
||||
except Exception:
|
||||
return await utils.answer(message, self.strings("no_file"))
|
||||
|
||||
message = await utils.answer(message, self.strings("processing"))
|
||||
|
||||
images = []
|
||||
|
||||
async for ms in self._client.iter_messages(
|
||||
message.peer_id, offset_id=start_offset - 1, reverse=True
|
||||
):
|
||||
if not ms.media:
|
||||
break
|
||||
im = await self._client.download_file(ms.media, bytes)
|
||||
try:
|
||||
images.append(Image.open(io.BytesIO(im)))
|
||||
except UnidentifiedImageError:
|
||||
break
|
||||
|
||||
first_image, images = images[0], images[1:]
|
||||
file = io.BytesIO()
|
||||
first_image.save(
|
||||
file,
|
||||
"PDF",
|
||||
resolution=100.0,
|
||||
save_all=True,
|
||||
append_images=images,
|
||||
)
|
||||
f = io.BytesIO(file.getvalue())
|
||||
f.name = utils.get_args_raw(message) or "packed_images.pdf"
|
||||
await self._client.send_file(message.peer_id, f)
|
||||
await message.delete()
|
||||
446
hikariatama/ftg/inactive.py
Normal file
446
hikariatama/ftg/inactive.py
Normal file
@@ -0,0 +1,446 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/external-wanicon-flat-wanicon/344/external-dead-halloween-costume-avatar-wanicon-flat-wanicon.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/inactive.jpg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
import time
|
||||
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Inactive(loader.Module):
|
||||
"""Blocks people who are inactive for a long time. Check .config"""
|
||||
|
||||
strings = {
|
||||
"name": "Inactive",
|
||||
"config": (
|
||||
"<emoji document_id='6041914500272098262'>🚫</emoji> <b>You need to"
|
||||
" configure module first: </b>\n\n<emoji"
|
||||
" document_id='6039769000898988691'>⚙️</emoji> <code>{}config {}</code>"
|
||||
),
|
||||
"confirm": (
|
||||
"⚠️ <b>Please, confirm that you want to start cleaning this chat from"
|
||||
" inactive users with these parameters:</b>\n\n⌚️ <b>Inactive time:"
|
||||
" {}</b>\n💭 <b>Minimal amount of messages: {}</b>\n\n☝️ <i>Please, note,"
|
||||
" that this operation might take a lot of API requests and cause"
|
||||
" FloodWaits</i>"
|
||||
),
|
||||
"start": "🧹 Start",
|
||||
"cancel": "🔻 Cancel",
|
||||
"configure": "⚙️ Open config",
|
||||
"started": "😼 <b>Processing started! This message will update</b>",
|
||||
"processing": (
|
||||
"🫶 <b>Processed {} messages from {} users. Already found {} users to"
|
||||
" {} and"
|
||||
" {} trusted</b>\n\n<i>Still processing...</i>"
|
||||
),
|
||||
"kick": "kick",
|
||||
"ban": "ban",
|
||||
"processing_complete": (
|
||||
"😻 <b>Processing complete! Processed {} messages from {} users. Found {}"
|
||||
" users to {}. Apply restrictions?</b>\n"
|
||||
),
|
||||
"processing_already": "😼 <b>Processing already in progress!</b>",
|
||||
"restrictions_applied": "🔒 <b>Action `{}` applied to {} inactive users!</b>",
|
||||
"cancelling_processing": "🔻 <b>Cancelling processing...</b>",
|
||||
"processing_cancelled": "😼 <b>Processing cancelled!</b>",
|
||||
"hrs": "hour(-s)",
|
||||
"applying_restrictions": (
|
||||
"🔒 <b>Applying restrictions. Found {} users to {}</b>"
|
||||
),
|
||||
"restrict": "🔒 Restrict",
|
||||
"no_users": "😼 <b>No inactive users found!</b>",
|
||||
"messages": "messages",
|
||||
"waiting_lock": (
|
||||
"🛃 <b>Processing is already active in other chat, waiting for lock to"
|
||||
" release</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"config": (
|
||||
"<emoji document_id='6041914500272098262'>🚫</emoji> <b>Вам нужно вначале"
|
||||
" настроить модуль: </b>\n\n<emoji"
|
||||
" document_id='6039769000898988691'>⚙️</emoji> <code>{}config {}</code>"
|
||||
),
|
||||
"confirm": (
|
||||
"⚠️ <b>Пожалуйста, подтвердите, что вы хотите начать очистку этого чата от"
|
||||
" неактивных пользователей с этими параметрами:</b>\n\n⌚️ <b>Время"
|
||||
" неактивности: {}</b>\n💭 <b>Минимальное количество сообщений: {}</b>\n\n☝️"
|
||||
" <i>Пожалуйста, обратите внимание, что эта операция может занять много API"
|
||||
" запросов и вызвать FloodWait'ы</i>"
|
||||
),
|
||||
"start": "🧹 Начать",
|
||||
"cancel": "🔻 Отмена",
|
||||
"configure": "⚙️ Открыть настройки",
|
||||
"started": "😼 <b>Обработка началась! Это сообщение будет обновляться</b>",
|
||||
"processing": (
|
||||
"🫶 <b>Обработано {} сообщений от {} пользователей. Уже найдено {}"
|
||||
" пользователей для {} и {} доверенных</b>\n\n<i>Все еще обрабатываю...</i>"
|
||||
),
|
||||
"kick": "кика",
|
||||
"ban": "бана",
|
||||
"processing_complete": (
|
||||
"😻 <b>Обработка завершена! Обработано {} сообщений от {} пользователей."
|
||||
" Найдено {} пользователей для {}. Применять ограничения?</b>\n"
|
||||
),
|
||||
"processing_already": "😼 <b>Обработка уже выполняется!</b>",
|
||||
"restrictions_applied": (
|
||||
"🔒 <b>Действие `{}` применено к {} неактивным пользователям!</b>"
|
||||
),
|
||||
"cancelling_processing": "🔻 <b>Отменяю обработку...</b>",
|
||||
"processing_cancelled": "😼 <b>Обработка отменена!</b>",
|
||||
"hrs": "час(-ов)",
|
||||
"applying_restrictions": (
|
||||
"🔒 <b>Применяю ограничения. Найдено {} пользователей для {}</b>"
|
||||
),
|
||||
"restrict": "🔒 Ограничить",
|
||||
"no_users": "😼 <b>Не найдено неактивных пользователей!</b>",
|
||||
"messages": "сообщений",
|
||||
"waiting_lock": (
|
||||
"🛃 <b>Обработка уже выполняется в другом чате, жду освобождения"
|
||||
" блокировки</b>"
|
||||
),
|
||||
}
|
||||
|
||||
_lock = {}
|
||||
_global_lock = asyncio.Lock()
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"action",
|
||||
"kick",
|
||||
"Action to perform when user is inactive",
|
||||
validator=loader.validators.Choice(["ban", "kick"]),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"inactive_time",
|
||||
None,
|
||||
(
|
||||
"If specified, any user, which sent no messages for this amount of"
|
||||
" hours, will be blocked."
|
||||
),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Integer(minimum=1), loader.validators.NoneType()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"inactive_messages",
|
||||
None,
|
||||
(
|
||||
"If specified, any user, which sent less than this amount of"
|
||||
" messages, will be blocked."
|
||||
),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Integer(minimum=1), loader.validators.NoneType()
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
async def _configure(self, call: InlineCall):
|
||||
await self.lookup("HikkaConfig").inline__configure(
|
||||
call,
|
||||
self.__class__.__name__,
|
||||
obj_type=False,
|
||||
)
|
||||
|
||||
async def _cancel(self, call: InlineCall, chat_id: int):
|
||||
if chat_id in self._lock:
|
||||
self._lock[chat_id].set()
|
||||
await call.edit(self.strings("processing_cancelled"))
|
||||
|
||||
async def _start(self, call: InlineCall, chat_id: int):
|
||||
if chat_id in self._lock:
|
||||
await call.edit(self.strings("processing_already"))
|
||||
return
|
||||
|
||||
self._lock[chat_id] = asyncio.Event()
|
||||
|
||||
markup = {
|
||||
"text": self.strings("cancel"),
|
||||
"callback": self._cancel,
|
||||
"args": (chat_id,),
|
||||
}
|
||||
|
||||
chat = await self._client.get_entity(chat_id)
|
||||
data = {}
|
||||
restrict = set()
|
||||
processing_finished = asyncio.Event()
|
||||
|
||||
async def _():
|
||||
nonlocal call, data, restrict
|
||||
while True:
|
||||
await asyncio.sleep(20)
|
||||
if (
|
||||
processing_finished.is_set()
|
||||
or chat_id not in self._lock
|
||||
or self._lock[chat_id].is_set()
|
||||
):
|
||||
break
|
||||
|
||||
await call.edit(
|
||||
self.strings("processing").format(
|
||||
sum([len(user_messages) for user_messages in data.values()]),
|
||||
len(data),
|
||||
len(restrict),
|
||||
self.strings(self.config["action"]),
|
||||
len(
|
||||
[
|
||||
user
|
||||
for user, messages in data.items()
|
||||
if (
|
||||
not self.config["inactive_messages"]
|
||||
or len(messages) > self.config["inactive_messages"]
|
||||
)
|
||||
and (
|
||||
not self.config["inactive_time"]
|
||||
or messages
|
||||
and time.time() - max(messages)
|
||||
< self.config["inactive_time"] * 3600
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
await call.edit(
|
||||
(
|
||||
self.strings("waiting_lock")
|
||||
if self._global_lock.locked()
|
||||
else self.strings("started")
|
||||
),
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
async with self._global_lock:
|
||||
if self._lock[chat_id].is_set():
|
||||
await call.edit(self.strings("processing_cancelled"))
|
||||
self._lock.pop(chat_id)
|
||||
return
|
||||
|
||||
task = asyncio.ensure_future(_())
|
||||
|
||||
names = {}
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
await self._client.end_takeout(True)
|
||||
|
||||
async with self._client.takeout(
|
||||
**({"megagroups": True} if chat.megagroup else {"chats": True})
|
||||
) as takeout:
|
||||
async for user in takeout.iter_participants(chat):
|
||||
data.setdefault(user.id, [])
|
||||
names[user.id] = get_display_name(user)
|
||||
|
||||
async for message in takeout.iter_messages(chat, wait_time=5):
|
||||
sender = message.sender_id
|
||||
if sender not in names:
|
||||
continue
|
||||
|
||||
date = time.mktime(message.date.timetuple())
|
||||
data.setdefault(sender, []).append(date)
|
||||
if self.config["inactive_time"]:
|
||||
if (
|
||||
time.time() - max(data[sender])
|
||||
> self.config["inactive_time"] * 3600
|
||||
):
|
||||
restrict.add(sender)
|
||||
elif sender in restrict:
|
||||
restrict.remove(sender)
|
||||
|
||||
if self.config["inactive_messages"]:
|
||||
if len(data[sender]) < self.config["inactive_messages"]:
|
||||
restrict.add(sender)
|
||||
elif sender in restrict:
|
||||
restrict.remove(sender)
|
||||
|
||||
if (
|
||||
self.config["inactive_messages"]
|
||||
and all(
|
||||
len(msgs) > self.config["inactive_messages"]
|
||||
for msgs in data.values()
|
||||
)
|
||||
and (
|
||||
not self.config["inactive_time"]
|
||||
or all(
|
||||
msgs
|
||||
and time.time() - max(msgs)
|
||||
> self.config["inactive_time"] * 3600
|
||||
for msgs in data.values()
|
||||
)
|
||||
)
|
||||
):
|
||||
break
|
||||
|
||||
if self._lock[chat_id].is_set():
|
||||
await call.edit(self.strings("processing_cancelled"))
|
||||
self._lock.pop(chat_id)
|
||||
return
|
||||
|
||||
for user, messages in data.items():
|
||||
if (
|
||||
self.config["inactive_messages"]
|
||||
and len(messages) < self.config["inactive_messages"]
|
||||
or self.config["inactive_time"]
|
||||
and time.time() - max(messages) > self.config["inactive_time"] * 3600
|
||||
):
|
||||
restrict.add(user)
|
||||
elif user in restrict:
|
||||
restrict.remove(user)
|
||||
|
||||
processing_finished.set()
|
||||
task.cancel()
|
||||
|
||||
if not restrict:
|
||||
await call.edit(self.strings("no_users"))
|
||||
self._lock.pop(chat_id)
|
||||
return
|
||||
|
||||
m = self.strings("processing_complete").format(
|
||||
sum([len(user_messages) for user_messages in data.values()]),
|
||||
len(data),
|
||||
len(restrict),
|
||||
self.strings(self.config["action"]),
|
||||
)
|
||||
|
||||
for user in restrict:
|
||||
line = (
|
||||
"\n▫️ <a"
|
||||
f" href='tg://user?id={user}'>{utils.escape_html(names.get(user, user))}</a>"
|
||||
f" ({len(data[user])} {self.strings('messages')},"
|
||||
f" {round((time.time() - max(data[user])) / 3600, 1) if data[user] else 'n/a'} {self.strings('hrs')})"
|
||||
)
|
||||
if len(m + line) >= 4096:
|
||||
m += "\n..."
|
||||
break
|
||||
|
||||
m += line
|
||||
|
||||
await call.edit(
|
||||
m,
|
||||
reply_markup=[
|
||||
{
|
||||
"text": self.strings("restrict"),
|
||||
"callback": self._restrict,
|
||||
"args": (chat_id, restrict, markup),
|
||||
},
|
||||
{
|
||||
"text": self.strings("cancel"),
|
||||
"callback": self._im_cancel,
|
||||
"args": (chat_id,),
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
async def _im_cancel(self, call: InlineCall, chat_id: int):
|
||||
self._lock.pop(chat_id)
|
||||
await call.edit(self.strings("processing_cancelled"))
|
||||
|
||||
async def _restrict(
|
||||
self,
|
||||
call: InlineCall,
|
||||
chat_id: int,
|
||||
restrict: set,
|
||||
markup: dict,
|
||||
):
|
||||
await call.edit(
|
||||
self.strings("applying_restrictions").format(
|
||||
len(restrict), self.strings(self.config["action"])
|
||||
),
|
||||
reply_markup=markup,
|
||||
)
|
||||
for user_id in restrict:
|
||||
if self.config["action"] == "kick":
|
||||
await self._client.kick_participant(chat_id, user_id)
|
||||
else:
|
||||
await self._client.edit_permissions(
|
||||
chat_id,
|
||||
user_id,
|
||||
until_date=0,
|
||||
view_messages=False,
|
||||
send_messages=False,
|
||||
send_media=False,
|
||||
send_stickers=False,
|
||||
send_gifs=False,
|
||||
send_games=False,
|
||||
send_inline=False,
|
||||
send_polls=False,
|
||||
change_info=False,
|
||||
invite_users=False,
|
||||
)
|
||||
|
||||
await asyncio.sleep(3)
|
||||
|
||||
if self._lock[chat_id].is_set():
|
||||
await call.edit(self.strings("processing_cancelled"))
|
||||
self._lock.pop(chat_id)
|
||||
return
|
||||
|
||||
await call.edit(
|
||||
self.strings("restrictions_applied").format(
|
||||
self.strings(self.config["action"]),
|
||||
len(restrict),
|
||||
)
|
||||
)
|
||||
self._lock.pop(chat_id)
|
||||
|
||||
@loader.command(ru_doc="Запустить чистку неактивных юзеров")
|
||||
async def inactive(self, message: Message):
|
||||
"""Start inactive users cleaner"""
|
||||
if not self.config["inactive_time"] and not self.config["inactive_messages"]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("config").format(
|
||||
self.get_prefix(),
|
||||
self.__class__.__name__,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
if utils.get_chat_id(message) in self._lock:
|
||||
await utils.answer(message, self.strings("processing_already"))
|
||||
return
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("confirm").format(
|
||||
(
|
||||
f'{self.config["inactive_time"]} {self.strings("hrs")}'
|
||||
if self.config["inactive_time"]
|
||||
else "-"
|
||||
),
|
||||
self.config["inactive_messages"] or "-",
|
||||
),
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("start"),
|
||||
"callback": self._start,
|
||||
"args": (utils.get_chat_id(message),),
|
||||
},
|
||||
{"text": self.strings("cancel"), "action": "close"},
|
||||
],
|
||||
[{"text": self.strings("configure"), "callback": self._configure}],
|
||||
],
|
||||
)
|
||||
42
hikariatama/ftg/inline_ghoul.py
Normal file
42
hikariatama/ftg/inline_ghoul.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/color/480/000000/dota.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/inline_ghoul.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader
|
||||
|
||||
|
||||
@loader.tds
|
||||
class InlineGhoulMod(loader.Module):
|
||||
"""Non-spammy ghoul module"""
|
||||
|
||||
strings = {"name": "InlineGhoul", "tired": "😾 <b>Tired of counting!</b>"}
|
||||
|
||||
strings_ru = {
|
||||
"tired": "😾 <b>Я устал считать!</b>",
|
||||
"_cmd_doc_ghoul": "Отправляет сообщение Гуля",
|
||||
"_cls_doc": "Неспамящий модуль Гуль",
|
||||
}
|
||||
|
||||
async def ghoulcmd(self, message: Message):
|
||||
"""Sends ghoul message"""
|
||||
await self.animate(
|
||||
message,
|
||||
[f"👊 <b>{x} - 7 = {x - 7}</b>" for x in range(1000, 900, -7)]
|
||||
+ [self.strings("tired")],
|
||||
interval=1,
|
||||
inline=True,
|
||||
)
|
||||
87
hikariatama/ftg/inline_random.py
Normal file
87
hikariatama/ftg/inline_random.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/240/000000/shuffle.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/inline_random.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
|
||||
from random import choice, randint
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
|
||||
@loader.tds
|
||||
class InlineRandomMod(loader.Module):
|
||||
"""Random tools for your userbot"""
|
||||
|
||||
strings = {"name": "InlineRandom"}
|
||||
|
||||
@loader.inline_everyone
|
||||
async def coin_inline_handler(self, query: InlineQuery) -> dict:
|
||||
"""Heads or tails?"""
|
||||
|
||||
r = "🦅 Heads" if randint(0, 1) else "🪙 Tails"
|
||||
|
||||
return {
|
||||
"title": "Toss a coin",
|
||||
"description": "Trust in the God of luck, and he will be by your side!",
|
||||
"message": f"<i>The God of luck tells us...</i> <b>{r}</b>",
|
||||
"thumb": "https://img.icons8.com/external-justicon-flat-justicon/64/000000/external-coin-pirates-justicon-flat-justicon-1.png",
|
||||
}
|
||||
|
||||
@loader.inline_everyone
|
||||
async def random_inline_handler(self, query: InlineQuery) -> dict:
|
||||
"""[number] - Send random number less than specified"""
|
||||
|
||||
if not query.args:
|
||||
return
|
||||
|
||||
a = query.args
|
||||
|
||||
if not str(a).isdigit():
|
||||
return
|
||||
|
||||
return {
|
||||
"title": f"Toss random number less or equal to {a}",
|
||||
"description": "Trust in the God of luck, and he will be by your side!",
|
||||
"message": f"<i>The God of luck screams...</i> <b>{randint(1, int(a))}</b>",
|
||||
"thumb": "https://img.icons8.com/external-flaticons-flat-flat-icons/64/000000/external-numbers-auction-house-flaticons-flat-flat-icons.png",
|
||||
}
|
||||
|
||||
@loader.inline_everyone
|
||||
async def choice_inline_handler(self, query: InlineQuery) -> dict:
|
||||
"""[args, separated by comma] - Make a choice"""
|
||||
|
||||
if not query.args or not query.args.count(","):
|
||||
return
|
||||
|
||||
a = query.args
|
||||
|
||||
return {
|
||||
"title": "Choose one item from list",
|
||||
"description": "Trust in the God of luck, and he will be by your side!",
|
||||
"message": (
|
||||
"<i>The God of luck whispers...</i>"
|
||||
f" <b>{choice(a.split(',')).strip()}</b>"
|
||||
),
|
||||
"thumb": "https://img.icons8.com/external-filled-outline-geotatah/64/000000/external-choice-customer-satisfaction-filled-outline-filled-outline-geotatah.png",
|
||||
}
|
||||
|
||||
@loader.inline_everyone
|
||||
async def person_inline_handler(self, query: InlineQuery) -> dict:
|
||||
"""This person doesn't exist"""
|
||||
|
||||
return {
|
||||
"photo": f"https://thispersondoesnotexist.com/image?id={utils.rand(10)}",
|
||||
"title": "This person doesn't exist",
|
||||
}
|
||||
363
hikariatama/ftg/inline_spotify.py
Normal file
363
hikariatama/ftg/inline_spotify.py
Normal file
@@ -0,0 +1,363 @@
|
||||
__version__ = (2, 1, 1)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/color/480/000000/playstation-buttons.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/inline_spotify.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.5.3
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from math import ceil
|
||||
from typing import Union
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall, InlineMessage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_bar(pb):
|
||||
try:
|
||||
percentage = ceil(pb["progress_ms"] / pb["item"]["duration_ms"] * 100)
|
||||
bar_filled = ceil(percentage / 10)
|
||||
bar_empty = 10 - bar_filled
|
||||
bar = "".join("─" for _ in range(bar_filled))
|
||||
bar += "🞆"
|
||||
bar += "".join("─" for _ in range(bar_empty))
|
||||
|
||||
bar += (
|
||||
f' {pb["progress_ms"] // 1000 // 60:02}:{pb["progress_ms"] // 1000 % 60:02} /'
|
||||
)
|
||||
bar += (
|
||||
f' {pb["item"]["duration_ms"] // 1000 // 60:02}:{pb["item"]["duration_ms"] // 1000 % 60:02}'
|
||||
)
|
||||
except Exception:
|
||||
bar = "──────🞆─── 0:00 / 0:00"
|
||||
|
||||
return bar
|
||||
|
||||
|
||||
@loader.tds
|
||||
class InlineSpotifyMod(loader.Module):
|
||||
"""EXTENSION for SpotifyNow mod, that allows you to send interactive player."""
|
||||
|
||||
strings = {
|
||||
"name": "InlineSpotify",
|
||||
"input": "🎧 Enter the track name",
|
||||
"search": "🔎 Search",
|
||||
"listening_to": "I'm listening to",
|
||||
"download": "📥 Download",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"input": "🎧 Введи название трека",
|
||||
"search": "🔎 Поиск",
|
||||
"_cmd_doc_splayer": (
|
||||
"Отправляет интерактивный плеер Spotify (активен в течение 5 минут!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"Дополнение для модуля SpotifyNow, позволяющее вызвать интерактивный плеер."
|
||||
),
|
||||
"listening_to": "Сейчас я слушаю",
|
||||
"download": "📥 Скачать",
|
||||
}
|
||||
|
||||
strings_it = {
|
||||
"input": "🎧 Inserisci il nome della traccia",
|
||||
"search": "🔎 Cerca",
|
||||
"_cmd_doc_splayer": (
|
||||
"Invia un player Spotify interattivo (attivo per 5 minuti!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"Estensione per il modulo SpotifyNow, che consente di inviare un player"
|
||||
" interattivo."
|
||||
),
|
||||
"listening_to": "Sto ascoltando",
|
||||
"download": "📥 Scarica",
|
||||
}
|
||||
|
||||
strings_es = {
|
||||
"input": "🎧 Introduzca el nombre de la pista",
|
||||
"search": "🔎 Buscar",
|
||||
"_cmd_doc_splayer": (
|
||||
"Envía un reproductor de Spotify interactivo (¡activo durante 5 minutos!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"Extensión para el módulo SpotifyNow, que permite enviar un reproductor"
|
||||
" interactivo."
|
||||
),
|
||||
"listening_to": "Estoy escuchando",
|
||||
"download": "📥 Descargar",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"input": "🎧 Ishora nomini kiriting",
|
||||
"search": "🔎 Qidirish",
|
||||
"_cmd_doc_splayer": (
|
||||
"Qo'llab-quvvatlash uchun Spotify interaktiv oynasini yuboring (5 daqiqada"
|
||||
" faol!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"SpotifyNow moduli uchun kengaytma, interaktiv oynani yuborish mumkin."
|
||||
),
|
||||
"listening_to": "Meni eshitib turaman",
|
||||
"download": "📥 Yuklab oling",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"input": "🎧 Parçanın adını girin",
|
||||
"search": "🔎 Ara",
|
||||
"_cmd_doc_splayer": (
|
||||
"Etkileşimli bir Spotify oynatıcı gönderir (5 dakika boyunca etkin!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"SpotifyNow modülü eklentisi, etkileşimli bir oynatıcı göndermenizi sağlar."
|
||||
),
|
||||
"listening_to": "Şu anda dinliyorum",
|
||||
"download": "📥 İndir",
|
||||
}
|
||||
|
||||
strings_kk = {
|
||||
"input": "🎧 Тақырып атауын енгізіңіз",
|
||||
"search": "🔎 іздеу",
|
||||
"_cmd_doc_splayer": (
|
||||
"Spotify интерактивті ойынды жіберіңіз (5 минутта белсенді!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"SpotifyNow модулі қосымшасы, интерактивті ойынды жіберуге мүмкіндік"
|
||||
" береді."
|
||||
),
|
||||
"listening_to": "Ағымда маңызды болатындыңызды көрудіңіз керек",
|
||||
"download": "📥 Жүктеу",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"input": "🎧 Geben Sie den Namen des Tracks ein",
|
||||
"search": "🔎 Suche",
|
||||
"_cmd_doc_splayer": (
|
||||
"Sendet einen interaktiven Spotify-Player (aktiv für 5 Minuten!)"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"Erweiterung für das SpotifyNow-Modul, das es ermöglicht, einen"
|
||||
" interaktiven Player zu senden."
|
||||
),
|
||||
"listening_to": "Ich höre zu",
|
||||
"download": "📥 Herunterladen",
|
||||
}
|
||||
|
||||
async def _reload_sp(self, once: bool = False):
|
||||
while True:
|
||||
self.sp = getattr(self.lookup("SpotifyMod"), "sp", None)
|
||||
if once:
|
||||
break
|
||||
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def client_ready(self):
|
||||
self.sp = None
|
||||
|
||||
self._tasks = [asyncio.ensure_future(self._reload_sp())]
|
||||
await self._reload_sp(True)
|
||||
|
||||
self._active_forms = []
|
||||
|
||||
async def on_unload(self):
|
||||
for task in self._tasks:
|
||||
task.cancel()
|
||||
|
||||
async def inline_close(self, call: InlineCall):
|
||||
if any(
|
||||
call.form.get("uid") == getattr(i, "unit_id", None)
|
||||
for i in self._active_forms
|
||||
):
|
||||
self._active_forms.remove(
|
||||
next(
|
||||
i
|
||||
for i in self._active_forms
|
||||
if call.form.get("uid") == getattr(i, "unit_id", None)
|
||||
)
|
||||
)
|
||||
|
||||
await call.delete()
|
||||
|
||||
async def sp_previous(self, call: InlineCall):
|
||||
self.sp.previous_track()
|
||||
await self.inline_iter(call, True)
|
||||
|
||||
async def sp_next(self, call: InlineCall):
|
||||
self.sp.next_track()
|
||||
await self.inline_iter(call, True)
|
||||
|
||||
async def sp_pause(self, call: InlineCall):
|
||||
self.sp.pause_playback()
|
||||
await self.inline_iter(call, True)
|
||||
|
||||
async def sp_play(self, call: InlineCall):
|
||||
self.sp.start_playback()
|
||||
await self.inline_iter(call, True)
|
||||
|
||||
async def sp_shuffle(self, call: InlineCall, state: bool):
|
||||
self.sp.shuffle(state)
|
||||
await self.inline_iter(call, True)
|
||||
|
||||
async def sp_repeat(self, call: InlineCall, state: bool):
|
||||
self.sp.repeat(state)
|
||||
await self.inline_iter(call, True)
|
||||
|
||||
async def sp_play_track(self, call: InlineCall, query: str):
|
||||
try:
|
||||
track = self.sp.track(query)
|
||||
except Exception:
|
||||
search = self.sp.search(q=query, type="track", limit=1)
|
||||
try:
|
||||
track = search["tracks"]["items"][0]
|
||||
except Exception:
|
||||
return
|
||||
|
||||
self.sp.add_to_queue(track["id"])
|
||||
self.sp.next_track()
|
||||
|
||||
async def inline_iter(
|
||||
self,
|
||||
call: Union[InlineCall, InlineMessage],
|
||||
once: bool = False,
|
||||
uid: str = False,
|
||||
):
|
||||
try:
|
||||
if not uid:
|
||||
uid = getattr(call, "unit_id", call.form["id"])
|
||||
|
||||
until = time.time() + 5 * 60
|
||||
while (
|
||||
any(uid == i.unit_id for i in self._active_forms)
|
||||
and until > time.time()
|
||||
or once
|
||||
):
|
||||
pb = self.sp.current_playback()
|
||||
is_resuming = (
|
||||
"actions" in pb
|
||||
and "disallows" in pb["actions"]
|
||||
and "resuming" in pb["actions"]["disallows"]
|
||||
and pb["actions"]["disallows"]["resuming"]
|
||||
)
|
||||
|
||||
try:
|
||||
artists = [artist["name"] for artist in pb["item"]["artists"]]
|
||||
except Exception:
|
||||
artists = []
|
||||
|
||||
try:
|
||||
track = pb["item"]["name"]
|
||||
track_id = pb["item"]["id"]
|
||||
except Exception:
|
||||
track = ""
|
||||
track_id = ""
|
||||
|
||||
full_name = f"{', '.join(artists)} - {track}"
|
||||
|
||||
keyboard = [
|
||||
[
|
||||
(
|
||||
{"text": "🔁", "callback": self.sp_repeat, "args": (False,)}
|
||||
if pb["repeat_state"]
|
||||
else {
|
||||
"text": "🔂",
|
||||
"callback": self.sp_repeat,
|
||||
"args": (True,),
|
||||
}
|
||||
),
|
||||
{"text": "⏮", "callback": self.sp_previous},
|
||||
(
|
||||
{"text": "⏸", "callback": self.sp_pause}
|
||||
if is_resuming
|
||||
else {"text": "▶️", "callback": self.sp_play}
|
||||
),
|
||||
{"text": "⏭", "callback": self.sp_next},
|
||||
(
|
||||
{
|
||||
"text": "↩️",
|
||||
"callback": self.sp_shuffle,
|
||||
"args": (False,),
|
||||
}
|
||||
if pb["shuffle_state"]
|
||||
else {
|
||||
"text": "🔀",
|
||||
"callback": self.sp_shuffle,
|
||||
"args": (True,),
|
||||
}
|
||||
),
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings("search"),
|
||||
"input": self.strings("input"),
|
||||
"handler": self.sp_play_track,
|
||||
},
|
||||
{
|
||||
"text": self.strings("download"),
|
||||
"callback": self._download,
|
||||
"args": (full_name,),
|
||||
},
|
||||
{"text": "🔗 Link", "url": f"https://song.link/s/{track_id}"},
|
||||
],
|
||||
[{"text": "🚫 Close", "callback": self.inline_close}],
|
||||
]
|
||||
|
||||
text = (
|
||||
f"🎧 <b>{self.strings('listening_to')} {full_name}</b>\n<code>{create_bar(pb)}</code><a"
|
||||
f" href='https://song.link/s/{track_id}'>\u206f</a>"
|
||||
)
|
||||
|
||||
await call.edit(
|
||||
text,
|
||||
reply_markup=keyboard,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
if once:
|
||||
break
|
||||
|
||||
await asyncio.sleep(10)
|
||||
except Exception:
|
||||
logger.exception("BRUH")
|
||||
|
||||
async def _download(self, call: InlineCall, track: str):
|
||||
await call.answer(self.strings("download"))
|
||||
await self.allmodules.commands["sfind"](
|
||||
await call.form["caller"].reply(
|
||||
f"<code>{self.get_prefix()}sfind {utils.escape_html(track)}</code>"
|
||||
)
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Отправляет интерактивный плеер Spotify (активен в течение 5 минут!)",
|
||||
it_doc="Invia un player interattivo di Spotify (attivo per 5 minuti!)",
|
||||
de_doc="Sendet einen interaktiven Spotify-Player (aktiv für 5 Minuten!)",
|
||||
tr_doc="Etkin Spotify oynatıcı gönderir (5 dakika boyunca aktif!)",
|
||||
uz_doc="Faol Spotify oynatuvchisini yuboradi (5 daqiqada aktiv!)",
|
||||
es_doc=(
|
||||
"Envía un reproductor interactivo de Spotify (activo durante 5 minutos!)"
|
||||
),
|
||||
kk_doc="Интерактивті Spotify ойындысын жібереді (5 минутта актив!)",
|
||||
)
|
||||
async def splayer(self, message: Message):
|
||||
"""Send interactive Spotify player (active only for 5 minutes!)"""
|
||||
form = await self.inline.form(
|
||||
"<b>🐻 Bear with us, while player is loading...</b>", message=message
|
||||
)
|
||||
|
||||
self._active_forms += [form]
|
||||
self._tasks += [asyncio.ensure_future(self.inline_iter(form))]
|
||||
219
hikariatama/ftg/insult.py
Normal file
219
hikariatama/ftg/insult.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/color/480/000000/angry--v1.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/insult.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import random
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PoliteInsultMod(loader.Module):
|
||||
"""If you need to insult but to be intelligent"""
|
||||
|
||||
strings = {
|
||||
"name": "PoliteInsult",
|
||||
"insult": (
|
||||
"<emoji document_id=5373123633415723713>🤬</emoji> {} you are {} {} {} {}"
|
||||
),
|
||||
"adjectives_start": [
|
||||
"temperamental",
|
||||
"rude",
|
||||
"silly to me",
|
||||
"arrogant",
|
||||
"non-individualistic",
|
||||
"undisciplined",
|
||||
"unprofessional",
|
||||
"irresponsible",
|
||||
"reckless",
|
||||
"indifferent to meser",
|
||||
],
|
||||
"nouns": ["participant of this group chat", "this world citizen"],
|
||||
"starts": [
|
||||
(
|
||||
"I don't want to jump to conclusions and I certainly can't claim, and"
|
||||
" this is my subjective opinion, but"
|
||||
),
|
||||
(
|
||||
"Having analyzed the situation, I can express my subjective opinion. It"
|
||||
" lies in the fact that"
|
||||
),
|
||||
(
|
||||
"Not trying to make anyone feel bad, but just expressing my humble"
|
||||
" point of view, which does not affect other people's points of view, I"
|
||||
" can say that"
|
||||
),
|
||||
(
|
||||
"Without intending to affect any social minorities, I would like to say"
|
||||
" that"
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"insult": (
|
||||
"<emoji document_id=5373123633415723713>🤬</emoji> {} ты - {} {} {} {}"
|
||||
),
|
||||
"adjectives_start": [
|
||||
"вспыльчивый(-ая)",
|
||||
"невоспитанный(-ая)",
|
||||
"осточертевший(-ая) мне",
|
||||
"глуповатый(-ая)",
|
||||
"надменный(-ая)",
|
||||
"неиндивидуалистичный(-ая)",
|
||||
"индифферентный(-ая)",
|
||||
"недисциплинированный(-ая)",
|
||||
"непрофессиональный(-ая)",
|
||||
"безответственный(-ая)",
|
||||
"безрассудный(-ая)",
|
||||
"безразличный(-ая) мне",
|
||||
],
|
||||
"nouns": ["участник(-ца) данного чата", "житель(-ница) мира сего"],
|
||||
"starts": [
|
||||
"Не хочу делать поспешных выводов, но",
|
||||
"Я, конечно, не могу утверждать, и это мое субъективное мнение, но",
|
||||
(
|
||||
"Проанализировав ситуацию, я могу высказать свое субъективное мнение."
|
||||
" Оно заключается в том, что"
|
||||
),
|
||||
(
|
||||
"Не пытаясь никого оскорбить, а лишь высказывая свою скромную точку"
|
||||
" зрения, которая не влияет на точку зрения других людей, могу"
|
||||
" сказать, что"
|
||||
),
|
||||
(
|
||||
"Не преследуя попытку затронуть какие-либо социальные меньшинства, хочу"
|
||||
" сказать, что"
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"insult": (
|
||||
"<emoji document_id=5373123633415723713>🤬</emoji> {} du bist {} {} {} {}"
|
||||
),
|
||||
"adjectives_start": [
|
||||
"launisch",
|
||||
"hässlich",
|
||||
"sinnlos",
|
||||
"überheblich",
|
||||
"nicht-individualistisch",
|
||||
"unordentlich",
|
||||
"unprofessionell",
|
||||
"unverantwortlich",
|
||||
"unvernünftig",
|
||||
"uninteressiert",
|
||||
],
|
||||
"nouns": ["Teilnehmer dieser Gruppe", "dieser Weltbürger"],
|
||||
"starts": [
|
||||
(
|
||||
"Ich möchte nicht zu voreilig sein und kann nicht behaupten, und"
|
||||
" dies ist meine subjektive Meinung, aber"
|
||||
),
|
||||
(
|
||||
"Nachdem ich die Situation analysiert habe, kann ich meine subjektive"
|
||||
" Meinung ausdrücken. Es liegt darin, dass"
|
||||
),
|
||||
(
|
||||
"Ohne jemanden verletzen zu wollen, sondern nur meine bescheidene"
|
||||
" Meinung auszudrücken, die die Meinungen anderer Menschen nicht"
|
||||
" beeinflusst, kann ich sagen, dass"
|
||||
),
|
||||
(
|
||||
"Ohne die Absicht, irgendwelche sozialen Minderheiten zu beeinflussen,"
|
||||
" möchte ich sagen, dass"
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"insult": (
|
||||
"<emoji document_id=5373123633415723713>🤬</emoji> {} sen {} {} {} {}"
|
||||
),
|
||||
"adjectives_start": [
|
||||
"öfkeli",
|
||||
"kaba",
|
||||
"gözümü korkutmuş",
|
||||
"kibirli",
|
||||
"bireysel olmayan",
|
||||
"düzensiz",
|
||||
"profesyonel olmayan",
|
||||
"sorumluluk almamış",
|
||||
"akılsız",
|
||||
"ilgisiz",
|
||||
],
|
||||
"nouns": ["bu sohbet grubunun katılımcısı", "bu dünya vatandaşı"],
|
||||
"starts": [
|
||||
(
|
||||
"Çabucak sonuçlara atlamak istemiyorum ve kesinlikle iddia edemem,"
|
||||
" ve bu benim kişisel görüşüm, ama"
|
||||
),
|
||||
(
|
||||
"Durumu analiz ettiğimde, kişisel görüşümü ifade edebilirim. Bunun"
|
||||
" içinde şu var ki"
|
||||
),
|
||||
(
|
||||
"Herhangi biri duygulanmasını istememekle birlikte, sadece kibarca"
|
||||
" bir görüş belirtmek, kişilerin görüşlerinin etkilenmediği, ki"
|
||||
" söyleyebilirim ki"
|
||||
),
|
||||
(
|
||||
"Herhangi bir sosyal azınlığı etkilemek için bir girişimde bulunmadan,"
|
||||
" söylemek istediğim şey budur"
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"insult": "<emoji document_id=5373123633415723713>🤬</emoji> {} तुम {} {} {} {}",
|
||||
"adjectives_start": [
|
||||
"अशांत",
|
||||
"अज्ञानी",
|
||||
"अच्छी तरह से नहीं देखा",
|
||||
"अपमानजनक",
|
||||
"गैर-व्यक्तिगत",
|
||||
"अनुचित",
|
||||
"अप्रतिबंधी",
|
||||
"अदायगी",
|
||||
"असंवेदनशील",
|
||||
"अव्यक्तिक",
|
||||
],
|
||||
"nouns": ["इस चैट के भागीदार", "इस विश्व नागरिक"],
|
||||
"starts": [
|
||||
(
|
||||
"मैं जल्दी निष्कर्षों को नहीं चाहता हूं और यह कहने से नहीं कि"
|
||||
" यह मेरा व्यक्तिगत राय है, लेकिन"
|
||||
),
|
||||
"अवस्था का विश्लेषण करके, मैं अपना व्यक्तिगत राय व्यक्त कर सकता हूं। इसमें यह है कि",
|
||||
(
|
||||
"किसी को दुखाने की कोशिश न करते हुए, केवल मेरा बहुत छोटा राय"
|
||||
" बताना, लोगों की रायों को प्रभावित न करने के लिए, जो"
|
||||
" मैं कह सकता हूं कि"
|
||||
),
|
||||
"किसी सामाजिक अनुकूलित समूह को प्रभावित न करने के लिए, मैं कहना चाहता हूं कि",
|
||||
],
|
||||
}
|
||||
|
||||
async def insultocmd(self, message: Message):
|
||||
"""Use when angry"""
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("insult").format(
|
||||
random.choice(self.strings("starts")),
|
||||
random.choice(self.strings("adjectives_start")),
|
||||
random.choice(self.strings("adjectives_start")),
|
||||
random.choice(self.strings("nouns")),
|
||||
random.choice(["!!!!", "!", "."]),
|
||||
),
|
||||
)
|
||||
369
hikariatama/ftg/keyword.py
Normal file
369
hikariatama/ftg/keyword.py
Normal file
@@ -0,0 +1,369 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/48/000000/macbook-chat.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/keyword.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
|
||||
import contextlib
|
||||
import re
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class KeywordMod(loader.Module):
|
||||
"""Allows you to create custom filters with regexes, commands and unlimited funcionality"""
|
||||
|
||||
strings = {
|
||||
"name": "Keyword",
|
||||
"args": "🚫 <b>Args are incorrect</b>",
|
||||
"kw_404": '🚫 <b>Keyword "{}" not found</b>',
|
||||
"kw_added": "✅ <b>New keyword:\nTrigger: {}\nMessage: {}\n{}{}{}{}{}</b>",
|
||||
"kw_removed": '✅ <b>Keyword "{}" removed</b>',
|
||||
"kwbl_list": "🦊 <b>Blacklisted chats:</b>\n{}",
|
||||
"bl_added": "✅ <b>This chat is now blacklisted for Keywords</b>",
|
||||
"bl_removed": "✅ <b>This chat is now whitelisted for Keywords</b>",
|
||||
"sent": "🦊 <b>[Keywords]: Sent message to {}, triggered by {}:\n{}</b>",
|
||||
"kwords": "🦊 <b>Current keywords:\n</b>{}",
|
||||
"no_command": (
|
||||
"🚫 <b>Execution of command forbidden, because message contains reply</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Неверные аргументы</b>",
|
||||
"kw_404": '🚫 <b>Кейворд "{}" не найден</b>',
|
||||
"kw_added": "✅ <b>Новый кейворд:\nТриггер: {}\nСообщение: {}\n{}{}{}{}{}</b>",
|
||||
"kw_removed": '✅ <b>Кейворд "{}" удален</b>',
|
||||
"kwbl_list": "🦊 <b>Чаты в черном списке:</b>\n{}",
|
||||
"bl_added": "✅ <b>Этот чат теперь в черном списке Кейвордов</b>",
|
||||
"bl_removed": "✅ <b>Этот чат больше не в черном списке Кейвордов</b>",
|
||||
"sent": "🦊 <b>[Кейворды]: Отправлено сообщение в {}, активировано {}:\n{}</b>",
|
||||
"kwords": "🦊 <b>Текущие кейворды:\n</b>{}",
|
||||
"no_command": (
|
||||
"🚫 <b>Команда не была выполнена, так как сообщение содержит реплай</b>"
|
||||
),
|
||||
"_cmd_doc_kword": (
|
||||
"<кейворд | можно в кавычках | & для нескольких слов, которые должны быть в"
|
||||
" сообщении в любом порядке> <сообщение | оставь пустым для удаления"
|
||||
" кейворда> [-r для полного совпадения] [-m для автопрочтения сообщения]"
|
||||
" [-l для включения логирования] [-e для включения регулярных выражений]"
|
||||
),
|
||||
"_cmd_doc_kwords": "Показать активные кейворды",
|
||||
"_cmd_doc_kwbl": "Добавить чат в черный список кейвордов",
|
||||
"_cmd_doc_kwbllist": "Показать чаты в черном списке",
|
||||
"_cls_doc": "Создавай кастомные кейворды с регулярными выражениями и командами",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": "🚫 <b>Falsche Argumente</b>",
|
||||
"kw_404": '🚫 <b>Keyword "{}" nicht gefunden</b>',
|
||||
"kw_added": "✅ <b>Neuer Keyword:\nTrigger: {}\nNachricht: {}\n{}{}{}{}{}</b>",
|
||||
"kw_removed": '✅ <b>Keyword "{}" entfernt</b>',
|
||||
"kwbl_list": "🦊 <b>Blacklisted Chats:</b>\n{}",
|
||||
"bl_added": "✅ <b>Dieser Chat ist nun blacklisted für Keywords</b>",
|
||||
"bl_removed": "✅ <b>Dieser Chat ist nun whitelisted für Keywords</b>",
|
||||
"sent": "🦊 <b>[Keywords]: Nachricht an {}, getriggert durch {}:\n{}</b>",
|
||||
"kwords": "🦊 <b>Aktuelle Keywords:\n</b>{}",
|
||||
"no_command": (
|
||||
"🚫 <b>Kommando nicht ausgeführt, da die Nachricht einen Reply enthält</b>"
|
||||
),
|
||||
"_cmd_doc_kword": (
|
||||
"<keyword | kann in Anführungszeichen | & für mehrere Wörter, die in"
|
||||
" Nachricht in irgendeiner Reihenfolge sein müssen> <Nachricht | leer"
|
||||
" lassen um Keyword zu löschen> [-r für exakte Übereinstimmung] [-m für"
|
||||
" automatische Nachrichtenlöschung] [-l für Logging] [-e für reguläre"
|
||||
" Ausdrücke]"
|
||||
),
|
||||
"_cmd_doc_kwords": "Zeige aktive Keywords",
|
||||
"_cmd_doc_kwbl": "Füge Chat zur Keyword Blacklist hinzu",
|
||||
"_cmd_doc_kwbllist": "Zeige Chats in der Keyword Blacklist",
|
||||
"_cls_doc": "Erstelle eigene Keywords mit regulären Ausdrücken und Befehlen",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"args": "🚫 <b>गलत तर्क</b>",
|
||||
"kw_404": '🚫 <b>"{}" कीवर्ड नहीं मिला</b>',
|
||||
"kw_added": "✅ <b>नया कीवर्ड:\nट्रिगर: {}\nसंदेश: {}\n{}{}{}{}{}</b>",
|
||||
"kw_removed": '✅ <b>"{}" कीवर्ड हटा दिया</b>',
|
||||
"kwbl_list": "🦊 <b>ब्लैकलिस्टेड चैट्स:</b>\n{}",
|
||||
"bl_added": "✅ <b>यह चैट अब कीवर्ड ब्लैकलिस्ट में है</b>",
|
||||
"bl_removed": "✅ <b>यह चैट अब कीवर्ड व्हाइटलिस्ट में है</b>",
|
||||
"sent": "🦊 <b>[कीवर्ड्स]: {} को, {} ने ट्रिगर किया:\n{}</b>",
|
||||
"kwords": "🦊 <b>वर्तमान कीवर्ड्स:\n</b>{}",
|
||||
"no_command": "🚫 <b>कमांड नहीं चलाया क्योंकि संदेश रिप्लाई का सामना कर रहा है</b>",
|
||||
"_cmd_doc_kword": (
|
||||
"<कीवर्ड | उदाहरण के लिए & | & के बाद एक से अधिक शब्द, जो संदेश में किसी भी"
|
||||
" क्रम में होने चाहिए> <संदेश | खाली छोड़ने से कीवर्ड हट जाएगा> [-r बिल्कुल"
|
||||
" मेल के लिए] [-m स्वचालित संदेश हटाने के लिए] [-l लॉगिंग के लिए] [-e"
|
||||
" रेगुलर एक्सप्रेशन के लिए]"
|
||||
),
|
||||
"_cmd_doc_kwords": "वर्तमान कीवर्ड्स दिखाएं",
|
||||
"_cmd_doc_kwbl": "कीवर्ड ब्लैकलिस्ट में चैट जोड़ें",
|
||||
"_cmd_doc_kwbllist": "कीवर्ड ब्लैकलिस्ट में चैट दिखाएं",
|
||||
"_cls_doc": "रेगुलर एक्सप्रेशन और कमांड के साथ अपने कीवर्ड बनाएं",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"args": "🚫 <b>Noto'g'ri argument</b>",
|
||||
"kw_404": '🚫 <b>"{}" kalit so\'z topilmadi</b>',
|
||||
"kw_added": "✅ <b>Yangi kalit so'z:\nTriger: {}\nXabar: {}\n{}{}{}{}{}</b>",
|
||||
"kw_removed": "✅ <b>\"{}\" kalit so'z o'chirildi</b>",
|
||||
"kwbl_list": "🦊 <b>Qora ro'yxatli guruhlar:</b>\n{}",
|
||||
"bl_added": "✅ <b>Bu guruh kalit so'zlarni qora ro'yxatga qo'shildi</b>",
|
||||
"bl_removed": "✅ <b>Bu guruh kalit so'zlarni oq ro'yxatga qo'shildi</b>",
|
||||
"sent": "🦊 <b>[Kalit so'zlarni]: {} ga, {} guruhga xabar jo'natdi:\n{}</b>",
|
||||
"kwords": "🦊 <b>Hozirgi kalit so'zlarni:\n</b>{}",
|
||||
"no_command": "🚫 <b>Komanda bajarilmadi chunki xabar javob qaytaradi</b>",
|
||||
"_cmd_doc_kword": (
|
||||
"<kalit so'z | & orqali bir nechta so'zlarni | & keyingi bir nechta so'z,"
|
||||
" xabarda biror tartibda bo'lishi kerak> <xabar | bo'sh qoldirish kalit"
|
||||
" so'zni o'chiradi> [-r to'g'ri moslik uchun] [-m avtomatik xabar o'chirish"
|
||||
" uchun] [-l yozuvni qayd etish uchun] [-e regular ifodalar uchun]"
|
||||
),
|
||||
"_cmd_doc_kwords": "Hozirgi kalit so'zlarni ko'rsatish",
|
||||
"_cmd_doc_kwbl": "Qora ro'yxatga guruh qo'shish",
|
||||
"_cmd_doc_kwbllist": "Qora ro'yxatda guruhlar ro'yxatini ko'rsatish",
|
||||
"_cls_doc": "Regular ifodalarni va buyruqlarni ishlatib kalit so'z yarating",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"args": "🚫 <b>Yanlış argüman</b>",
|
||||
"kw_404": '🚫 <b>"{}" anahtar kelime bulunamadı</b>',
|
||||
"kw_added": "✅ <b>Yeni anahtar kelime:\nTriger: {}\nMesaj: {}\n{}{}{}{}{}</b>",
|
||||
"kw_removed": '✅ <b>"{}" anahtar kelime kaldırıldı</b>',
|
||||
"kwbl_list": "🦊 <b>Kara liste sohbetler:</b>\n{}",
|
||||
"bl_added": "✅ <b>Bu sohbet anahtar kelimeleri kara listeye eklendi</b>",
|
||||
"bl_removed": "✅ <b>Bu sohbet anahtar kelimeleri açık listeye eklendi</b>",
|
||||
"sent": "🦊 <b>[Anahtar Kelimeler]: {}'a, {} sohbetine mesaj gönderdi:\n{}</b>",
|
||||
"kwords": "🦊 <b>Geçerli anahtar kelimeler:\n</b>{}",
|
||||
"no_command": "🚫 <b>Komut yürütülemedi çünkü mesaj yanıt veriyor</b>",
|
||||
"_cmd_doc_kword": (
|
||||
"<anahtar kelime | & ile birden çok sözcük | & sonra birden çok sözcük,"
|
||||
" mesajda herhangi bir sırayla olmalıdır> <mesaj | boş bırakmak anahtar"
|
||||
" kelimeyi kaldırır> [-r tam eşleme için] [-m otomatik mesaj silmek için]"
|
||||
" [-l kayıt için] [-e düzenli ifadeler için]"
|
||||
),
|
||||
"_cmd_doc_kwords": "Geçerli anahtar kelimeleri göster",
|
||||
"_cmd_doc_kwbl": "Sohbeti kara listeye ekle",
|
||||
"_cmd_doc_kwbllist": "Kara listede sohbetleri göster",
|
||||
"_cls_doc": (
|
||||
"Anahtar kelimeleri oluşturmak için düzenli ifadeleri ve komutları kullanın"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.keywords = self.get("keywords", {})
|
||||
self.bl = self.get("bl", [])
|
||||
|
||||
async def kwordcmd(self, message: Message):
|
||||
"""<keyword | could be in quotes | & for multiple words that should be in msg> <message | empty to remove keyword> [-r for full match] [-m for autoreading msg] [-l to log in pm] [-e for regular expressions]"""
|
||||
args = utils.get_args_raw(message)
|
||||
kw, ph, restrict, ar, l, e, c = "", "", False, False, False, False, False
|
||||
if "-r" in args:
|
||||
restrict = True
|
||||
args = args.replace(" -r", "").replace("-r", "")
|
||||
|
||||
if "-m" in args:
|
||||
ar = True
|
||||
args = args.replace(" -m", "").replace("-m", "")
|
||||
|
||||
if "-l" in args:
|
||||
l = True
|
||||
args = args.replace(" -l", "").replace("-l", "")
|
||||
|
||||
if "-e" in args:
|
||||
e = True
|
||||
args = args.replace(" -e", "").replace("-e", "")
|
||||
|
||||
if "-c" in args:
|
||||
c = True
|
||||
args = args.replace(" -c", "").replace("-c", "")
|
||||
|
||||
if args[0] == "'":
|
||||
kw = args[1 : args.find("'", 1)]
|
||||
args = args[args.find("'", 1) + 1 :]
|
||||
elif args[0] == '"':
|
||||
kw = args[1 : args.find('"', 1)]
|
||||
args = args[args.find('"', 1) + 1 :]
|
||||
else:
|
||||
kw = args.split()[0]
|
||||
try:
|
||||
args = args.split(maxsplit=1)[1]
|
||||
except Exception:
|
||||
args = ""
|
||||
|
||||
if ph := args:
|
||||
ph = ph.strip()
|
||||
kw = kw.strip()
|
||||
self.keywords[kw] = [f"🤖 {ph}", restrict, ar, l, e, c]
|
||||
self.set("keywords", self.keywords)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings("kw_added").format(
|
||||
kw,
|
||||
utils.escape_html(ph),
|
||||
("Restrict: yes\n" if restrict else ""),
|
||||
("Auto-read: yes\n" if ar else ""),
|
||||
("Log: yes" if l else ""),
|
||||
("Regex: yes" if e else ""),
|
||||
("Command: yes" if c else ""),
|
||||
),
|
||||
)
|
||||
else:
|
||||
if kw not in self.keywords:
|
||||
return await utils.answer(message, self.strings("kw_404").format(kw))
|
||||
|
||||
del self.keywords[kw]
|
||||
|
||||
self.set("keywords", self.keywords)
|
||||
return await utils.answer(message, self.strings("kw_removed").format(kw))
|
||||
|
||||
async def kwordscmd(self, message: Message):
|
||||
"""List current kwords"""
|
||||
res = ""
|
||||
for kw, ph in self.keywords.items():
|
||||
res += (
|
||||
"<code>"
|
||||
+ kw
|
||||
+ "</code>\n<b>Message: "
|
||||
+ utils.escape_html(ph[0])
|
||||
+ "\n"
|
||||
+ ("Restrict: yes\n" if ph[1] else "")
|
||||
+ ("Auto-read: yes\n" if ph[2] else "")
|
||||
+ ("Log: yes" if ph[3] else "")
|
||||
+ ("Regex: yes" if len(ph) > 4 and ph[4] else "")
|
||||
+ ("Command: yes" if len(ph) > 5 and ph[5] else "")
|
||||
+ "</b>"
|
||||
)
|
||||
if res[-1] != "\n":
|
||||
res += "\n"
|
||||
|
||||
res += "\n"
|
||||
|
||||
await utils.answer(message, self.strings("kwords").format(res))
|
||||
|
||||
@loader.group_admin_ban_users
|
||||
async def kwblcmd(self, message: Message):
|
||||
"""Blacklist chat from answering keywords"""
|
||||
cid = utils.get_chat_id(message)
|
||||
if cid not in self.bl:
|
||||
self.bl.append(cid)
|
||||
self.set("bl", self.bl)
|
||||
return await utils.answer(message, self.strings("bl_added"))
|
||||
else:
|
||||
self.bl.remove(cid)
|
||||
self.set("bl", self.bl)
|
||||
return await utils.answer(message, self.strings("bl_removed"))
|
||||
|
||||
async def kwbllistcmd(self, message: Message):
|
||||
"""List blacklisted chats"""
|
||||
chat = str(utils.get_chat_id(message))
|
||||
res = ""
|
||||
for user in self.bl:
|
||||
try:
|
||||
u = await self._client.get_entity(user)
|
||||
except Exception:
|
||||
self.chats[chat]["defense"].remove(user)
|
||||
continue
|
||||
|
||||
tit = (
|
||||
u.first_name if getattr(u, "first_name", None) is not None else u.title
|
||||
)
|
||||
res += (
|
||||
" 👺 <a"
|
||||
f" href=\"tg://user?id={u.id}\">{tit}{(' ' + u.last_name) if getattr(u, 'last_name', None) is not None else ''}</a>\n"
|
||||
)
|
||||
|
||||
if not res:
|
||||
res = "<i>No</i>"
|
||||
|
||||
return await utils.answer(message, self.strings("kwbl_list").format(res))
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
with contextlib.suppress(Exception):
|
||||
cid = utils.get_chat_id(message)
|
||||
if cid in self.bl:
|
||||
return
|
||||
|
||||
for kw, ph in self.keywords.copy().items():
|
||||
if len(ph) > 4 and ph[4]:
|
||||
try:
|
||||
if not re.match(kw, message.raw_text):
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
kws = [
|
||||
_.strip() for _ in ([kw] if "&" not in kw else kw.split("&"))
|
||||
]
|
||||
trigger = False
|
||||
for k in kws:
|
||||
if k.lower() in message.text.lower():
|
||||
trigger = True
|
||||
if not ph[1]:
|
||||
break
|
||||
elif k.lower() not in message.text.lower() and ph[1]:
|
||||
trigger = False
|
||||
break
|
||||
|
||||
if not trigger:
|
||||
continue
|
||||
|
||||
offset = 2
|
||||
|
||||
if (
|
||||
len(ph) > 5
|
||||
and ph[5]
|
||||
and ph[0][offset:].startswith(self.get_prefix())
|
||||
):
|
||||
offset += 1
|
||||
|
||||
if ph[2]:
|
||||
await self._client.send_read_acknowledge(cid, clear_mentions=True)
|
||||
|
||||
if ph[3]:
|
||||
chat = await message.get_chat()
|
||||
ch = (
|
||||
message.first_name
|
||||
if getattr(message, "first_name", None) is not None
|
||||
else ""
|
||||
)
|
||||
if not ch:
|
||||
ch = (
|
||||
chat.title
|
||||
if getattr(message, "title", None) is not None
|
||||
else ""
|
||||
)
|
||||
await self._client.send_message(
|
||||
"me", self.strings("sent").format(ch, kw, ph[0])
|
||||
)
|
||||
|
||||
if not message.reply_to_msg_id:
|
||||
ms = await utils.answer(message, ph[0])
|
||||
else:
|
||||
ms = await message.respond(ph[0])
|
||||
|
||||
ms.text = ph[0][2:]
|
||||
|
||||
if len(ph) > 5 and ph[5]:
|
||||
if ph[0][offset:].split()[0] == "del":
|
||||
await message.delete()
|
||||
await ms.delete()
|
||||
elif not message.reply_to_msg_id:
|
||||
cmd = ph[0][offset:].split()[0]
|
||||
if cmd in self.allmodules.commands:
|
||||
await self.allmodules.commands[cmd](ms)
|
||||
else:
|
||||
await ms.respond(self.strings("no_command"))
|
||||
61
hikariatama/ftg/lastcommand.py
Normal file
61
hikariatama/ftg/lastcommand.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/lastcommand_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/lastcommand.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader
|
||||
|
||||
|
||||
@loader.tds
|
||||
class LastCommandMod(loader.Module):
|
||||
"""Execute last command"""
|
||||
|
||||
strings = {"name": "LastCommand"}
|
||||
strings_ru = {
|
||||
"_cls_doc": "Выполняет последнюю команду",
|
||||
"_cmd_doc_lc": "Выполнить последнюю команду",
|
||||
}
|
||||
strings_de = {
|
||||
"_cls_doc": "Führt den letzten Befehl aus",
|
||||
"_cmd_doc_lc": "Letzten Befehl ausführen",
|
||||
}
|
||||
strings_tr = {
|
||||
"_cls_doc": "Son komutu çalıştırır",
|
||||
"_cmd_doc_lc": "Son komutu çalıştır",
|
||||
}
|
||||
strings_hi = {
|
||||
"_cls_doc": "अंतिम आदेश निष्पादित करें",
|
||||
"_cmd_doc_lc": "अंतिम आदेश निष्पादित करें",
|
||||
}
|
||||
strings_uz = {
|
||||
"_cls_doc": "Oxirgi buyruqni bajarish",
|
||||
"_cmd_doc_lc": "Oxirgi buyruqni bajarish",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
orig_dispatch = self.allmodules.dispatch
|
||||
|
||||
def _disp_wrap(command: callable) -> tuple:
|
||||
txt, func = orig_dispatch(command)
|
||||
|
||||
if "lc" not in txt:
|
||||
self.allmodules.last_command = func
|
||||
|
||||
return txt, func
|
||||
|
||||
self.allmodules.dispatch = _disp_wrap
|
||||
|
||||
async def lccmd(self, message: Message):
|
||||
"""Execute last command"""
|
||||
await self.allmodules.last_command(message)
|
||||
85
hikariatama/ftg/latex.py
Normal file
85
hikariatama/ftg/latex.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/452/texshop.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/latex.jpg
|
||||
# meta developer: @hikarimods
|
||||
|
||||
import io
|
||||
import logging
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class LaTeXMod(loader.Module):
|
||||
"""Renders mathematical formulas in LaTeX pngs"""
|
||||
|
||||
strings = {
|
||||
"name": "LaTeX",
|
||||
"no_args": "🚫 <b>Specify a formula to render</b>",
|
||||
"cant_render": "🚫 <b>Can't render formula</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_args": "🚫 <b>Укажи формулу для рендера</b>",
|
||||
"cant_render": "🚫 <b>В формуле обнаружена ошибка</b>",
|
||||
}
|
||||
|
||||
async def latexcmd(self, message: Message):
|
||||
"""<formula> - Create LaTeX render"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
try:
|
||||
tex = f"${args}$"
|
||||
|
||||
fig = plt.figure()
|
||||
ax = fig.add_axes([0, 0, 1, 1])
|
||||
ax.set_axis_off()
|
||||
|
||||
t = ax.text(
|
||||
0.5,
|
||||
0.5,
|
||||
tex,
|
||||
horizontalalignment="center",
|
||||
verticalalignment="center",
|
||||
fontsize=25,
|
||||
color="black",
|
||||
)
|
||||
|
||||
ax.figure.canvas.draw()
|
||||
|
||||
bbox = t.get_window_extent()
|
||||
fig.set_size_inches(bbox.width / 80, bbox.height / 80)
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf)
|
||||
buf.seek(0)
|
||||
except Exception:
|
||||
logger.exception("Can't render formula")
|
||||
await utils.answer(message, self.strings("cant_render"))
|
||||
return
|
||||
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
buf.getvalue(),
|
||||
reply_to=message.reply_to_msg_id,
|
||||
caption=f"🧮 <b>LaTeX</b>: <code>{args}</code>",
|
||||
)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
374
hikariatama/ftg/leomatch.py
Normal file
374
hikariatama/ftg/leomatch.py
Normal file
@@ -0,0 +1,374 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta desc: Simplifies the interaction with @leomatchbot - Rejects slag, allows you to create filters by age, cities, blacklisted words.
|
||||
# meta pic: https://static.dan.tatar/leomatch_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/leomatch.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: russian-names
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
__version__ = (2, 0, 3)
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from russian_names import RussianNames
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class LeomatchMod(loader.Module):
|
||||
"""Simplifies the interaction with @leomatchbot - Rejects slag, allows you to create filters by age, cities, blacklisted words. Check .config for more info"""
|
||||
|
||||
strings = {"name": "Leomatch"}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": (
|
||||
"Упрощает взаимодействие с @leomatchbot - отклоняет шлак, позволяет"
|
||||
" создавать фильтры по возрасту, городам, черному списку слов. Загляни в"
|
||||
" .config для подробной информации"
|
||||
),
|
||||
}
|
||||
|
||||
_last_decline = 0
|
||||
_queue = []
|
||||
_groups = {}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"min_age",
|
||||
0,
|
||||
(
|
||||
"Минимальный возраст собеседника - будет автоматически отклонять"
|
||||
" всех, кто младше"
|
||||
),
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"max_age",
|
||||
100,
|
||||
(
|
||||
"Максимальный возраст собеседника - будет автоматически отклонять"
|
||||
" всех, кто старше"
|
||||
),
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"blacklist_cities",
|
||||
[],
|
||||
(
|
||||
"Список городов, пользователи из которых будут автоматически"
|
||||
" отклоняться"
|
||||
),
|
||||
validator=loader.validators.Series(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"whitelist_cities",
|
||||
[],
|
||||
(
|
||||
"Список городов для белого списка. Пользователи из других городов"
|
||||
" будут автоматически отклоняться"
|
||||
),
|
||||
validator=loader.validators.Series(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"blacklist_words",
|
||||
[],
|
||||
(
|
||||
"Если в анкете пользователя есть слово из этого списка, она будет"
|
||||
" автоматически отклонена"
|
||||
),
|
||||
validator=loader.validators.Series(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"whitelist_words",
|
||||
[],
|
||||
(
|
||||
"Если в анкете пользователя есть нет слов из этого списка, она"
|
||||
" будет автоматически отклонена"
|
||||
),
|
||||
validator=loader.validators.Series(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"decline_slag",
|
||||
True,
|
||||
"Отклонять ли шлак (Подпишитесь на наш ТикТок и др.)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable",
|
||||
True,
|
||||
"Включить ли модуль",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"minimal_len",
|
||||
0,
|
||||
"Минимальное количество слов в анкете",
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log",
|
||||
True,
|
||||
"Отправлять в логи информацию о причинах отклонения анкет",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delay",
|
||||
5,
|
||||
"Задержка между автоматическим отклонением анкет",
|
||||
validator=loader.validators.Integer(minimum=3),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"no_female",
|
||||
False,
|
||||
"Автоматически отклонять девушек",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"no_male",
|
||||
False,
|
||||
"Автоматически отклонять парней",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
names = RussianNames()
|
||||
await utils.run_sync(names._fill_base)
|
||||
|
||||
self.female_names = map(lambda x: x.lower(), names._base["woman"]["name"])
|
||||
self.male_names = map(lambda x: x.lower(), names._base["man"]["name"])
|
||||
|
||||
@loader.loop(interval=1, autostart=True)
|
||||
async def loop(self):
|
||||
if not self._queue:
|
||||
return
|
||||
|
||||
if self._last_decline + self.config["delay"] > time.time():
|
||||
await asyncio.sleep(self._last_decline + self.config["delay"] - time.time())
|
||||
|
||||
self._last_decline = time.time()
|
||||
|
||||
log, answer = self._queue.pop(0)
|
||||
|
||||
async with self._client.conversation(1234060895) as conv:
|
||||
m = await conv.send_message(answer)
|
||||
await conv.get_response()
|
||||
|
||||
await m.delete()
|
||||
|
||||
if self.config["log"] and log:
|
||||
logger.info(log)
|
||||
|
||||
async def _decline(
|
||||
self,
|
||||
message: Message,
|
||||
log: Optional[str] = None,
|
||||
answer: Optional[str] = "👎",
|
||||
):
|
||||
for m in [message] + (
|
||||
[m for m in self._groups[message.grouped_id]]
|
||||
if message.grouped_id and message.grouped_id in self._groups
|
||||
else []
|
||||
):
|
||||
await m.delete()
|
||||
|
||||
self._queue += [(log, answer)]
|
||||
|
||||
@staticmethod
|
||||
def _in(needle: str, haystack: Iterable, alter: str) -> bool:
|
||||
"""
|
||||
Checks for occurence of needle in haystack using smart method
|
||||
:param needle: string to search for
|
||||
:param haystack: iterable to search in
|
||||
:param alter: string to search in if needle is not a one word
|
||||
:return: True if needle is found in haystack, False otherwise
|
||||
"""
|
||||
return (
|
||||
True
|
||||
if needle.strip().lower() in map(lambda x: x.lower().strip(), haystack)
|
||||
else " " in needle and needle.strip().lower() in alter.lower()
|
||||
)
|
||||
|
||||
@loader.watcher(chat_id=1234060895, out=True)
|
||||
async def out_watcher(self, _):
|
||||
if self._queue:
|
||||
self._queue = []
|
||||
logger.info("Останавливаюсь, т.к. ты отправил сообщение")
|
||||
return
|
||||
|
||||
@loader.watcher("in", from_id=1234060895)
|
||||
async def watcher(self, message: Message):
|
||||
if not self.config["enable"]:
|
||||
return
|
||||
|
||||
if (
|
||||
"Пригласи друзей и получи больше" in message.raw_text
|
||||
and "Твоя статистика" not in message.raw_text
|
||||
):
|
||||
self._queue = []
|
||||
logger.info("Останавливаюсь, т.к. закончились доступные лайки")
|
||||
return
|
||||
|
||||
if message.grouped_id:
|
||||
self._groups.setdefault(message.grouped_id, []).append(message)
|
||||
|
||||
if self.config["decline_slag"] and (
|
||||
(
|
||||
"Подпишись на канал Дайвинчика" in message.raw_text
|
||||
and "https://t.me/leoday" in message.text
|
||||
)
|
||||
or (
|
||||
"Бот не запрашивает личные данные и не идентифицирует пользователей по"
|
||||
" паспортным данным"
|
||||
in message.raw_text
|
||||
)
|
||||
or (
|
||||
"хочешь больше просмотров в TikTok?" in message.raw_text
|
||||
and "tiktok.com/tag/дайвинчик" in message.raw_text
|
||||
)
|
||||
or (
|
||||
"Пришли свое расположение и увидишь анкеты рядом с тобой"
|
||||
in message.raw_text
|
||||
)
|
||||
):
|
||||
await self._decline(
|
||||
message,
|
||||
"Отклонил какой-то шлак",
|
||||
"Продолжить просмотр анкет",
|
||||
)
|
||||
return
|
||||
|
||||
if self.config["decline_slag"] and message.raw_text == "Это все, идем дальше?":
|
||||
await self._decline(
|
||||
message,
|
||||
"Автоматически продолжаю просмотр анкет",
|
||||
"Смотреть анкеты",
|
||||
)
|
||||
return
|
||||
|
||||
if (
|
||||
message.raw_text.count(",") < 2
|
||||
or message.raw_text.startswith("Кому-то понравилась твоя анкета:")
|
||||
or "Отлично! Надеюсь хорошо проведете время ;) Начинай общаться"
|
||||
in message.raw_text
|
||||
):
|
||||
return
|
||||
|
||||
words = re.sub(
|
||||
r" {2,}",
|
||||
" ",
|
||||
"".join(
|
||||
(
|
||||
symbol
|
||||
if symbol
|
||||
in "abcdefghijklmnopqrstuvwxyzёйцукенгшщзхъфывапролджэячсмитьбю1234567890 "
|
||||
else " "
|
||||
)
|
||||
for symbol in (
|
||||
""
|
||||
if len(message.raw_text.lower().split(",", maxsplit=2)) < 3
|
||||
or "–" not in message.raw_text
|
||||
else message.raw_text.lower()
|
||||
.split(",", maxsplit=2)[2]
|
||||
.split("–")[1]
|
||||
)
|
||||
),
|
||||
).split()
|
||||
|
||||
user = (
|
||||
message.raw_text.split("–")[0].strip()
|
||||
if "–" in message.raw_text
|
||||
else message.raw_text
|
||||
)
|
||||
|
||||
user_name = user.split(",")[0].strip().lower()
|
||||
if (
|
||||
self.config["no_female"]
|
||||
and user_name in self.female_names
|
||||
or self.config["no_male"]
|
||||
and user_name in self.male_names
|
||||
):
|
||||
await self._decline(message, f"{user} отклонен по несовпадению пола")
|
||||
|
||||
if self.config["minimal_len"] and len(list(words)) < self.config["minimal_len"]:
|
||||
await self._decline(
|
||||
message,
|
||||
f"{user} отклонен из-за слишком короткой анкеты",
|
||||
)
|
||||
return
|
||||
|
||||
if (
|
||||
self.config["blacklist_cities"]
|
||||
and len(message.raw_text.split(",")) >= 3
|
||||
and message.raw_text.split(",")[2].split()[0].lower().strip()
|
||||
in map(lambda x: x.lower().strip(), self.config["blacklist_cities"])
|
||||
):
|
||||
await self._decline(
|
||||
message,
|
||||
f"{user} отклонен из-за наличия города в черном списке",
|
||||
)
|
||||
return
|
||||
|
||||
if (
|
||||
self.config["whitelist_cities"]
|
||||
and len(message.raw_text.split(",")) >= 3
|
||||
and message.raw_text.split(",")[2].split()[0].lower().strip()
|
||||
not in map(lambda x: x.lower().strip(), self.config["whitelist_cities"])
|
||||
):
|
||||
await self._decline(
|
||||
message,
|
||||
f"{user} отклонен из-за отсутствия города в белом списке",
|
||||
)
|
||||
return
|
||||
|
||||
if self.config["blacklist_words"] and any(
|
||||
self._in(word, words, message.raw_text)
|
||||
for word in self.config["blacklist_words"]
|
||||
):
|
||||
await self._decline(message, f"{user} отклонен из-за слов в черном списке")
|
||||
return
|
||||
|
||||
if self.config["whitelist_words"] and not any(
|
||||
self._in(word, words, message.raw_text)
|
||||
for word in self.config["whitelist_words"]
|
||||
):
|
||||
await self._decline(
|
||||
message,
|
||||
f"{user} отклонен из-за отсутствия в анкете слов из белого списка",
|
||||
)
|
||||
return
|
||||
|
||||
if (
|
||||
self.config["min_age"]
|
||||
and len(message.raw_text.split(",")) >= 2
|
||||
and message.raw_text.split(",")[1].strip().isdigit()
|
||||
and int(message.raw_text.split(",")[1].strip()) < self.config["min_age"]
|
||||
):
|
||||
await self._decline(message, f"{user} отклонен из-за младшего возраста")
|
||||
return
|
||||
|
||||
if (
|
||||
self.config["max_age"]
|
||||
and len(message.raw_text.split(",")) >= 2
|
||||
and message.raw_text.split(",")[1].strip().isdigit()
|
||||
and int(message.raw_text.split(",")[1].strip()) > self.config["max_age"]
|
||||
):
|
||||
await self._decline(message, f"{user} отклонен из-за старшего возраста")
|
||||
return
|
||||
88
hikariatama/ftg/linter.py
Normal file
88
hikariatama/ftg/linter.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/linter_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/linter.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: black
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
from random import choice
|
||||
|
||||
import black
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logging.getLogger("blib2to3.pgen2.driver").setLevel(logging.ERROR)
|
||||
|
||||
URL = r"(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]+"
|
||||
|
||||
captions = [
|
||||
"Here is your new fresh linted code! Enjoy",
|
||||
"This was such a hard work to clean this code... Uff..",
|
||||
"Here we go!",
|
||||
"Glad to be your virtual code-cleaning-maid!",
|
||||
"Take this, master!",
|
||||
]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PyLinterMod(loader.Module):
|
||||
"""`Black` plugin wrapper for telegram"""
|
||||
|
||||
strings = {"name": "PyLinter", "no_code": "🚫 <b>Please, specify code to lint</b>"}
|
||||
|
||||
async def lintcmd(self, message: Message):
|
||||
"""[code|reply] - Perform automatic lint to python code"""
|
||||
reply = await message.get_reply_message()
|
||||
args = utils.get_args_raw(message)
|
||||
media = message.media or (reply.media if reply else False)
|
||||
|
||||
if media:
|
||||
try:
|
||||
args = (await self._client.download_file(media, bytes)).decode("utf-8")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if not args:
|
||||
if not reply:
|
||||
await utils.answer(message, self.strings("no_code"))
|
||||
return
|
||||
|
||||
args = reply.raw_text
|
||||
|
||||
if re.match(URL, args):
|
||||
args = (await utils.run_sync(requests.get, args)).text
|
||||
|
||||
lint_result = black.format_str(args, mode=black.Mode())
|
||||
|
||||
if len(lint_result) < 2048:
|
||||
await utils.answer(
|
||||
message,
|
||||
f"<code>{utils.escape_html(lint_result)}</code>",
|
||||
)
|
||||
return
|
||||
|
||||
file = io.BytesIO(args.encode("utf-8"))
|
||||
file.name = "lint_result.py"
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
file,
|
||||
caption=(
|
||||
f"<i>{choice(captions)}</i>"
|
||||
f" <b>{utils.escape_html(utils.ascii_face())}</b>"
|
||||
),
|
||||
)
|
||||
if message.out:
|
||||
await message.delete()
|
||||
87
hikariatama/ftg/longread.py
Normal file
87
hikariatama/ftg/longread.py
Normal file
@@ -0,0 +1,87 @@
|
||||
__version__ = (1, 0, 2)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/longread_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/longread.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall, InlineQuery
|
||||
|
||||
|
||||
@loader.tds
|
||||
class LongReadMod(loader.Module):
|
||||
"""Pack longreads under button spoilers"""
|
||||
|
||||
strings = {
|
||||
"name": "LongRead",
|
||||
"no_text": "🚫 <b>Please, specify text to hide</b>",
|
||||
"longread": (
|
||||
"🗄 <b>This is long read</b>\n<i>Click button to show text!\nThis button is"
|
||||
" active within 6 hours</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_text": "🚫 <b>Укажи текст, который надо спрятать</b>",
|
||||
"longread": (
|
||||
"🗄 <b>Это - лонгрид</b>\n<i>Нажми на кнопку, чтобы показать текст!\nОна"
|
||||
" активна в течение 6 часов</i>"
|
||||
),
|
||||
"_cmd_doc_lr": "<text> - Создать лонгрид",
|
||||
"_cls_doc": "Пакует лонгриды под спойлеры",
|
||||
}
|
||||
|
||||
async def lrcmd(self, message: Message):
|
||||
"""<text> - Create new hidden message"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return
|
||||
|
||||
await self.inline.form(
|
||||
self.strings("longread"),
|
||||
message,
|
||||
reply_markup={
|
||||
"text": "📖 Open spoiler",
|
||||
"callback": self._handler,
|
||||
"args": (args,),
|
||||
},
|
||||
disable_security=True,
|
||||
)
|
||||
|
||||
async def lr_inline_handler(self, query: InlineQuery):
|
||||
"""Create new hidden message"""
|
||||
text = query.args
|
||||
|
||||
if not text:
|
||||
return await query.e400()
|
||||
|
||||
return {
|
||||
"title": "Create new longread",
|
||||
"description": "ℹ This will create button-spoiler",
|
||||
"thumb": "https://img.icons8.com/external-wanicon-flat-wanicon/64/000000/external-read-free-time-wanicon-flat-wanicon.png",
|
||||
"message": self.strings("longread"),
|
||||
"reply_markup": {
|
||||
"text": "📖 Open spoiler",
|
||||
"callback": self._handler,
|
||||
"args": (text,),
|
||||
"disable_security": True,
|
||||
},
|
||||
}
|
||||
|
||||
async def _handler(self, call: InlineCall, text: str):
|
||||
"""Process button presses"""
|
||||
await call.edit(text)
|
||||
await call.answer()
|
||||
162
hikariatama/ftg/lovemagic.py
Normal file
162
hikariatama/ftg/lovemagic.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/lovemagic_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/lovemagic.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
from asyncio import sleep
|
||||
from typing import Union
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ILYMod(loader.Module):
|
||||
"""Famous TikTok hearts animation implemented in Hikka w/o logspam"""
|
||||
|
||||
strings = {
|
||||
"name": "LoveMagic",
|
||||
"message": "<b>❤️🔥 I want to tell you something...</b>\n<i>{}</i>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"message": "<b>❤️🔥 Я хочу тебе сказать кое-что...</b>\n<i>{}</i>",
|
||||
"_cls_doc": "Известная TikTok анимация сердечек без спама в логи и флудвейтов",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.classic_frames = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://gist.github.com/hikariatama/89d0246c72e5882e12af43be63f5bca5/raw/08a5df7255d5e925ab2ede1efc892d9dc93af8e1/ily_classic.json",
|
||||
)
|
||||
).json()
|
||||
|
||||
self.gay_frames = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://gist.github.com/hikariatama/3596a7c4f273a41e5289586ccff53a71/raw/f680c04f5855dcb02645b603d84d2496a8ea3350/ily_gay.json",
|
||||
)
|
||||
).json()
|
||||
|
||||
async def ily_handler(
|
||||
self,
|
||||
obj: Union[InlineCall, Message],
|
||||
text: str,
|
||||
inline: bool = False,
|
||||
):
|
||||
frames = self.classic_frames + [
|
||||
f'<b>{" ".join(text.split()[: i + 1])}</b>'
|
||||
for i in range(len(text.split()))
|
||||
]
|
||||
|
||||
obj = await self.animate(obj, frames, interval=0.5, inline=inline)
|
||||
|
||||
await sleep(10)
|
||||
if not isinstance(obj, Message):
|
||||
await obj.edit(
|
||||
f"<b>{text}</b>",
|
||||
reply_markup={
|
||||
"text": "💔 Хочу также!",
|
||||
"url": "https://t.me/hikka_talks",
|
||||
},
|
||||
)
|
||||
|
||||
await obj.unload()
|
||||
|
||||
async def ily_handler_gay(
|
||||
self,
|
||||
obj: Union[InlineCall, Message],
|
||||
text: str,
|
||||
inline: bool = False,
|
||||
):
|
||||
obj = await self.animate(
|
||||
obj,
|
||||
self.gay_frames
|
||||
+ [
|
||||
f'<b>{" ".join(text.split()[: i + 1])}</b>'
|
||||
for i in range(len(text.split()))
|
||||
],
|
||||
interval=0.5,
|
||||
inline=inline,
|
||||
)
|
||||
|
||||
await sleep(10)
|
||||
if not isinstance(obj, Message):
|
||||
await obj.edit(
|
||||
f"<b>{text}</b>",
|
||||
reply_markup={
|
||||
"text": "💔 Хочу также!",
|
||||
"url": "https://t.me/hikka_talks",
|
||||
},
|
||||
)
|
||||
|
||||
await obj.unload()
|
||||
|
||||
@loader.command(ru_doc="Отправить анимацию сердец в инлайне")
|
||||
async def ilyicmd(self, message: Message):
|
||||
"""Send inline message with animated hearts"""
|
||||
args = utils.get_args_raw(message)
|
||||
await self.inline.form(
|
||||
self.strings("message").format("*" * (len(args) or 9)),
|
||||
reply_markup={
|
||||
"text": "🧸 Open",
|
||||
"callback": self.ily_handler,
|
||||
"args": (args or "I ❤️ you!",),
|
||||
"kwargs": {"inline": True},
|
||||
},
|
||||
message=message,
|
||||
disable_security=True,
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="Отправить анимацию сердец")
|
||||
async def ily(self, message: Message):
|
||||
"""Send message with animated hearts"""
|
||||
await self.ily_handler(
|
||||
message,
|
||||
utils.get_args_raw(message) or "I ❤️ you!",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="Отправить гейскую анимацию сердец в инлайне")
|
||||
async def ilygayicmd(self, message: Message):
|
||||
"""Send inline message with animated hearts (gay)"""
|
||||
args = utils.get_args_raw(message)
|
||||
await self.inline.form(
|
||||
self.strings("message").format("*" * (len(args) or 21)),
|
||||
reply_markup={
|
||||
"text": "🧸 Open",
|
||||
"callback": self.ily_handler_gay,
|
||||
"args": (args or "I am gay and I 💙 you!",),
|
||||
"kwargs": {"inline": True},
|
||||
},
|
||||
message=message,
|
||||
disable_security=True,
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="Отправить гейскую анимацию сердец")
|
||||
async def ilygay(self, message: Message):
|
||||
"""Send message with animated hearts (gay)"""
|
||||
await self.ily_handler_gay(
|
||||
message,
|
||||
utils.get_args_raw(message) or "I am gay and I 💙 you!",
|
||||
inline=False,
|
||||
)
|
||||
105
hikariatama/ftg/mindgame.py
Normal file
105
hikariatama/ftg/mindgame.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/mindgame_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/mindgame.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import random
|
||||
|
||||
import grapheme
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
EMOJIES = utils.chunks(
|
||||
list(
|
||||
grapheme.graphemes(
|
||||
"😌☺️😞😔🧑🏫👨🏫👨💻🧑💻"
|
||||
"🤵♂️🤵👩🚀🧑🚀👨🚒🧑🚒👨⚖️🧑⚖️"
|
||||
"🧟🧟♀️🦹🦹♀️🌇🌆🦸🦸♂️"
|
||||
"🧙🧙♀️🧚🧚♂️👯♀️👯👭👫"
|
||||
"👨👩👦👨👩👧👨🏭🧑🏭👳👳♂️🧑👨"
|
||||
"🕵️🕵️♂️🧑🌾👨🌾👨⚕️🧑⚕️🕵️🕵️♂️"
|
||||
"👨🍳🧑🍳🧑🔬👨🔬🧝♀️🧝♂️💏👨❤️💋👨"
|
||||
)
|
||||
),
|
||||
2,
|
||||
)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class MindGameMod(loader.Module):
|
||||
"""Train your brain and mind"""
|
||||
|
||||
strings = {
|
||||
"name": "MindGame",
|
||||
"header": (
|
||||
"🎮 <b>Find an emoji, that differs from others</b>\n<i>You've completed {}"
|
||||
" levels!</i>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"header": (
|
||||
"🎮 <b>Найди эмодзи, который отличается от других</b>\n<i>Ты прошел {}"
|
||||
" уровней!</i>"
|
||||
)
|
||||
}
|
||||
|
||||
_ratelimit = []
|
||||
|
||||
def generate_markup(self, counter: int) -> list:
|
||||
fail_emoji, next_step_emoji = random.choice(EMOJIES)
|
||||
markup = [
|
||||
{"text": fail_emoji, "callback": self._incorrect} for _ in range(8**2 - 1)
|
||||
] + [
|
||||
{
|
||||
"text": next_step_emoji,
|
||||
"callback": self._next_step_callback,
|
||||
"args": (counter + 1,),
|
||||
}
|
||||
]
|
||||
random.shuffle(markup)
|
||||
return utils.chunks(markup, 8)
|
||||
|
||||
async def mindgamecmd(self, message: Message):
|
||||
"""Open a new mindgame"""
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("header").format(0),
|
||||
reply_markup=self.generate_markup(0),
|
||||
disable_security=True,
|
||||
)
|
||||
|
||||
async def _next_step_callback(self, call: InlineCall, counter: int):
|
||||
if call.from_user.id != self._tg_id and call.from_user.id in self._ratelimit:
|
||||
await call.answer("You've spent your chance...")
|
||||
return
|
||||
|
||||
await call.edit(
|
||||
self.strings("header").format(counter),
|
||||
self.generate_markup(counter),
|
||||
)
|
||||
|
||||
await call.answer("Correct!")
|
||||
|
||||
self._ratelimit = []
|
||||
|
||||
async def _incorrect(self, call: InlineCall):
|
||||
if call.from_user.id != self._tg_id:
|
||||
if call.from_user.id in self._ratelimit:
|
||||
await call.answer("You've spent your chance...")
|
||||
return
|
||||
|
||||
self._ratelimit += [call.from_user.id]
|
||||
|
||||
await call.answer("NO!")
|
||||
65
hikariatama/ftg/moonlove.py
Normal file
65
hikariatama/ftg/moonlove.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/moonlove_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/moonlove.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
FRAMES = [
|
||||
"🌘🌗🌖🌕🌔🌓🌒\n🌙❤️❤️🌙❤️❤️🌙\n❤️💓💓❤️💓💓❤️\n❤️💓💓💓💓💓❤️\n🌙❤️💓💓💓❤️🌙\n🌙🌙❤️💓❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌘🌗🌖🌕🌔🌓🌒",
|
||||
"🌗🌖🌕🌔🌓🌒🌘\n🌙❤️❤️🌙❤️❤️🌙\n❤️💓💓❤️💓💓❤️\n❤️💓💓💗💓💓❤️\n🌙❤️💓💓💓❤️🌙\n🌙🌙❤️💓❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌗🌖🌕🌔🌓🌒🌘",
|
||||
"🌖🌕🌔🌓🌒🌘🌗\n🌙❤️❤️🌙❤️❤️🌙\n❤️💓💗❤️💗💓❤️\n❤️💓💗💗💗💓❤️\n🌙❤️💓💗💓❤️🌙\n🌙🌙❤️💓❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌖🌕🌔🌓🌒🌘🌗",
|
||||
"🌕🌔🌓🌒🌘🌗🌖\n🌙❤️❤️🌙❤️❤️🌙\n❤️💗💗❤️💗💗❤️\n❤️💗💗💗💗💗❤️\n🌙❤️💗💗💗❤️🌙\n🌙🌙❤️💗❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌕🌔🌓🌒🌘🌗🌖",
|
||||
"🌔🌓🌒🌘🌗🌖🌕\n🌙❤️❤️🌙❤️❤️🌙\n❤️💗💗❤️💗💗❤️\n❤️💗💗💖💗💗❤️\n🌙❤️💗💗💗❤️🌙\n🌙🌙❤️💗❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌔🌓🌒🌘🌗🌖🌕",
|
||||
"🌓🌒🌘🌗🌖🌕🌔\n🌙❤️❤️🌙❤️❤️🌙\n❤️💗💖❤️💖💗❤️\n❤️💗💖💖💖💗❤️\n🌙❤️💗💖💗❤️🌙\n🌙🌙❤️💗❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌓🌒🌘🌗🌖🌕🌔",
|
||||
"🌒🌘🌗🌖🌕🌔🌓\n🌙❤️❤️🌙❤️❤️🌙\n❤️💖💖❤️💖💖❤️\n❤️💖💖💖💖💖❤️\n🌙❤️💖💖💖❤️🌙\n🌙🌙❤️💖❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌒🌘🌗🌖🌕🌔🌓",
|
||||
"🌘🌗🌖🌕🌔🌓🌒\n🌙❤️❤️🌙❤️❤️🌙\n❤️💖💖❤️💖💖❤️\n❤️💖💖💓💖💖❤️\n🌙❤️💖💖💖❤️🌙\n🌙🌙❤️💖❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌘🌗🌖🌕🌔🌓🌒",
|
||||
"🌗🌖🌕🌔🌓🌒🌘\n🌙❤️❤️🌙❤️❤️🌙\n❤️💖💓❤️💓💖❤️\n❤️💖💓💓💓💖❤️\n🌙❤️💖💓💖❤️🌙\n🌙🌙❤️💖❤️🌙🌙\n🌙🌙🌙❤️🌙🌙🌙\n🌗🌖🌕🌔🌓🌒🌘",
|
||||
] * 3 + [ # It's shit, I know. But it's the easiest solution tho
|
||||
"💓",
|
||||
"💗",
|
||||
"💖",
|
||||
]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class MoonLoveMod(loader.Module):
|
||||
"""Animation with moon and hearts for your beloved"""
|
||||
|
||||
strings = {"name": "MoonLove"}
|
||||
strings_ru = {
|
||||
"_cls_doc": "Анимация с лунами и сердечками для любимой",
|
||||
"_cmd_doc_moonlove": "[текст] - Люблю тебя невообразимо",
|
||||
"_cmd_doc_moonlovei": "[текст] - Люблю тебя невообразимо (инлайн)",
|
||||
}
|
||||
|
||||
async def moonlovecmd(self, message: Message):
|
||||
"""[text] - Love you to the moon"""
|
||||
m = await self.animate(
|
||||
message,
|
||||
FRAMES,
|
||||
interval=0.3,
|
||||
inline=False,
|
||||
)
|
||||
await m.edit(utils.get_args_raw(message) or "❤️")
|
||||
|
||||
async def moonloveicmd(self, message: Message):
|
||||
"""[text] - Love you to the moon [Inline]"""
|
||||
m = await self.animate(
|
||||
message,
|
||||
FRAMES,
|
||||
interval=0.3,
|
||||
inline=True,
|
||||
)
|
||||
await m.edit(utils.get_args_raw(message) or "❤️")
|
||||
65
hikariatama/ftg/musicdl.py
Normal file
65
hikariatama/ftg/musicdl.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/musicdl_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/musicdl.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class MusicDLMod(loader.Module):
|
||||
"""Download music"""
|
||||
|
||||
strings = {
|
||||
"name": "MusicDL",
|
||||
"args": "🚫 <b>Arguments not specified</b>",
|
||||
"loading": "🔍 <b>Loading...</b>",
|
||||
"404": "🚫 <b>Music </b><code>{}</code><b> not found</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Не указаны аргументы</b>",
|
||||
"loading": "🔍 <b>Загрузка...</b>",
|
||||
"404": "🚫 <b>Песня </b><code>{}</code><b> не найдена</b>",
|
||||
}
|
||||
|
||||
async def client_ready(self, *_):
|
||||
self.musicdl = await self.import_lib(
|
||||
"https://libs.hikariatama.ru/musicdl.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
@loader.command(ru_doc="<название> - Скачать песню")
|
||||
async def mdl(self, message: Message):
|
||||
"""<name> - Download track"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("loading"))
|
||||
result = await self.musicdl.dl(args, only_document=True)
|
||||
|
||||
if not result:
|
||||
await utils.answer(message, self.strings("404").format(args))
|
||||
return
|
||||
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
result,
|
||||
caption=f"🎧 {utils.ascii_face()}",
|
||||
reply_to=getattr(message, "reply_to_msg_id", None),
|
||||
)
|
||||
if message.out:
|
||||
await message.delete()
|
||||
148
hikariatama/ftg/neko.py
Normal file
148
hikariatama/ftg/neko.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/neko_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/neko.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
import random
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
phrases = ["Uwu", "Senpai", "Uff", "Meow", "Bonk", "Ara-ara", "Hewwo", "You're cute!"]
|
||||
|
||||
|
||||
async def photo(self, args: str) -> str:
|
||||
return (
|
||||
await utils.run_sync(requests.get, f"{self.endpoints['img']}{args}")
|
||||
).json()["url"]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class NekosLifeMod(loader.Module):
|
||||
"""NekosLife API Wrapper"""
|
||||
|
||||
strings = {"name": "NekosLife"}
|
||||
|
||||
strings_ru = {
|
||||
"_cmd_doc_nk": "Отправить аниме арт",
|
||||
"_cmd_doc_nkct": "Показать доступные категории",
|
||||
"_cmd_doc_owoify": "OwOфицировать текст",
|
||||
"_cmd_doc_why": "Почему?",
|
||||
"_cmd_doc_fact": "А ты знал?",
|
||||
"_cmd_doc_meow": "Отправляет ASCII-арт кошки",
|
||||
"_cls_doc": "Обертка NekosLife API",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
ans = (
|
||||
await utils.run_sync(requests.get, "https://nekos.life/api/v2/endpoints")
|
||||
).json()
|
||||
self.categories = json.loads(
|
||||
"["
|
||||
+ [_ for _ in ans if "/api" in _ and "/img/" in _][0]
|
||||
.split("<")[1]
|
||||
.split(">")[0]
|
||||
.replace("'", '"')
|
||||
+ "]"
|
||||
)
|
||||
self.endpoints = {
|
||||
"img": "https://nekos.life/api/v2/img/",
|
||||
"owoify": "https://nekos.life/api/v2/owoify?text=",
|
||||
"why": "https://nekos.life/api/v2/why",
|
||||
"cat": "https://nekos.life/api/v2/cat",
|
||||
"fact": "https://nekos.life/api/v2/fact",
|
||||
}
|
||||
|
||||
@loader.pm
|
||||
async def nkcmd(self, message: Message):
|
||||
"""Send anime pic"""
|
||||
args = utils.get_args_raw(message)
|
||||
args = "neko" if args not in self.categories else args
|
||||
pic = functools.partial(photo, self=self, args=args)
|
||||
await self.inline.gallery(
|
||||
message=message,
|
||||
next_handler=pic,
|
||||
caption=lambda: f"<i>{random.choice(phrases)}</i> {utils.ascii_face()}",
|
||||
)
|
||||
|
||||
@loader.pm
|
||||
async def nkctcmd(self, message: Message):
|
||||
"""Show available categories"""
|
||||
cats = "\n".join(
|
||||
[" | </code><code>".join(_) for _ in utils.chunks(self.categories, 5)]
|
||||
)
|
||||
await utils.answer(
|
||||
message,
|
||||
f"<b>Available categories:</b>\n<code>{cats}</code>",
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
async def owoifycmd(self, message: Message):
|
||||
"""OwOify text"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
args = await message.get_reply_message()
|
||||
if not args:
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
args = args.text
|
||||
|
||||
if len(args) > 180:
|
||||
message = await utils.answer(message, "<b>OwOifying...</b>")
|
||||
|
||||
args = quote_plus(args)
|
||||
owo = ""
|
||||
for chunk in utils.chunks(args, 180):
|
||||
owo += (
|
||||
await utils.run_sync(requests.get, f"{self.endpoints['owoify']}{chunk}")
|
||||
).json()["owo"]
|
||||
await asyncio.sleep(0.1)
|
||||
await utils.answer(message, owo)
|
||||
|
||||
@loader.unrestricted
|
||||
async def whycmd(self, message: Message):
|
||||
"""Why?"""
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
"<code>👾"
|
||||
f" {(await utils.run_sync(requests.get, self.endpoints['why'])).json()['why']}</code>"
|
||||
),
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
async def factcmd(self, message: Message):
|
||||
"""Did you know?"""
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
"<b>🧐 Did you know, that"
|
||||
f" </b><code>{(await utils.run_sync(requests.get, self.endpoints['fact'])).json()['fact']}</code>"
|
||||
),
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
async def meowcmd(self, message: Message):
|
||||
"""Sends cat ascii art"""
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"<b>{(await utils.run_sync(requests.get, self.endpoints['cat'])).json()['cat']}</b>"
|
||||
),
|
||||
)
|
||||
1212
hikariatama/ftg/nekospy.py
Normal file
1212
hikariatama/ftg/nekospy.py
Normal file
File diff suppressed because it is too large
Load Diff
1193
hikariatama/ftg/nekospy_beta.py
Normal file
1193
hikariatama/ftg/nekospy_beta.py
Normal file
File diff suppressed because it is too large
Load Diff
87
hikariatama/ftg/nometa.py
Normal file
87
hikariatama/ftg/nometa.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
|
||||
# meta pic: https://static.dan.tatar/nometa_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/nometa.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class NoMetaMod(loader.Module):
|
||||
"""Warns people about Meta messages"""
|
||||
|
||||
strings = {
|
||||
"name": "NoMeta",
|
||||
"no_meta": (
|
||||
"<b>👾 <u>Please!</u></b>\n<b>NoMeta</b> aka <i>'Hello', 'Hi' etc.</i>\nAsk"
|
||||
" <b>directly</b>, what do you want from me."
|
||||
),
|
||||
"no_meta_ru": (
|
||||
"<b>👾 <u>Пожалуйста!</u></b>\n<b>Не нужно лишних сообщений</b> по типу"
|
||||
" <i>'Привет', 'Хай' и др.</i>\nСпрашивай(-те) <b>конкретно</b>, что от"
|
||||
" меня нужно."
|
||||
),
|
||||
}
|
||||
|
||||
@loader.command(ru_doc="Показать сообщение с предупреждением о мете")
|
||||
@loader.unrestricted
|
||||
async def nometacmd(self, message: Message):
|
||||
"""Show message about NoMeta"""
|
||||
await self._client.send_message(
|
||||
message.peer_id,
|
||||
self.strings("no_meta"),
|
||||
reply_to=getattr(message, "reply_to_msg_id", None),
|
||||
)
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
@loader.tag("only_messages", "only_pm", "in")
|
||||
async def watcher(self, message: Message):
|
||||
meta = ["hi", "hello", "hey there", "konichiwa", "hey"]
|
||||
|
||||
meta_ru = [
|
||||
"привет",
|
||||
"хай",
|
||||
"хелло",
|
||||
"хеллоу",
|
||||
"хэллоу",
|
||||
"коничива",
|
||||
"алоха",
|
||||
"слушай",
|
||||
"о",
|
||||
"слуш",
|
||||
"м?",
|
||||
"а?",
|
||||
"хей",
|
||||
"хэй",
|
||||
"йо",
|
||||
"йоу",
|
||||
"прив",
|
||||
"yo",
|
||||
"ку",
|
||||
]
|
||||
|
||||
if message.raw_text.lower() in meta:
|
||||
await utils.answer(message, self.strings("no_meta"))
|
||||
await self._client.send_read_acknowledge(
|
||||
message.chat_id,
|
||||
clear_mentions=True,
|
||||
)
|
||||
|
||||
if message.raw_text.lower() in meta_ru:
|
||||
await utils.answer(message, self.strings("no_meta_ru"))
|
||||
await self._client.send_read_acknowledge(
|
||||
message.chat_id, clear_mentions=True
|
||||
)
|
||||
260
hikariatama/ftg/notes.py
Normal file
260
hikariatama/ftg/notes.py
Normal file
@@ -0,0 +1,260 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/notes_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/notes.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class NotesMod(loader.Module):
|
||||
"""Advanced notes module with folders and other features"""
|
||||
|
||||
strings = {
|
||||
"name": "Notes",
|
||||
"saved": (
|
||||
"💾 <b>Saved note with name </b><code>{}</code>.\nFolder:"
|
||||
" </b><code>{}</code>.</b>"
|
||||
),
|
||||
"no_reply": "🚫 <b>Reply and note name are required.</b>",
|
||||
"no_name": "🚫 <b>Specify note name.</b>",
|
||||
"no_note": "🚫 <b>Note not found.</b>",
|
||||
"available_notes": "💾 <b>Current notes:</b>\n",
|
||||
"no_notes": "😔 <b>You have no notes yet</b>",
|
||||
"deleted": "🙂 <b>Deleted note </b><code>{}</code>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"saved": (
|
||||
"💾 <b>Заметка с именем </b><code>{}</code><b> сохранена</b>.\nПапка:"
|
||||
" </b><code>{}</code>.</b>"
|
||||
),
|
||||
"no_reply": "🚫 <b>Требуется реплай на контент заметки.</b>",
|
||||
"no_name": "🚫 <b>Укажи имя заметки.</b>",
|
||||
"no_note": "🚫 <b>Заметка не найдена.</b>",
|
||||
"available_notes": "💾 <b>Текущие заметки:</b>\n",
|
||||
"no_notes": "😔 <b>У тебя пока что нет заметок</b>",
|
||||
"deleted": "🙂 <b>Заметка с именем </b><code>{}</code> <b>удалена</b>",
|
||||
"_cmd_doc_hsave": "[папка] <имя> - Сохранить заметку",
|
||||
"_cmd_doc_hget": "<имя> - Показать заметку",
|
||||
"_cmd_doc_hdel": "<имя> - Удалить заметку",
|
||||
"_cmd_doc_hlist": "[папка] - Показать все заметки",
|
||||
"_cls_doc": "Модуль заметок с расширенным функционалом. Папки и категории",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"saved": (
|
||||
"💾 <b>Notiz mit dem Namen </b><code>{}</code><b> gespeichert</b>.\nOrdner:"
|
||||
" </b><code>{}</code>.</b>"
|
||||
),
|
||||
"no_reply": "🚫 <b>Antworte auf den Inhalt der Notiz.</b>",
|
||||
"no_name": "🚫 <b>Gib einen Namen für die Notiz an.</b>",
|
||||
"no_note": "🚫 <b>Notiz nicht gefunden.</b>",
|
||||
"available_notes": "💾 <b>Aktuelle Notizen:</b>\n",
|
||||
"no_notes": "😔 <b>Du hast noch keine Notizen</b>",
|
||||
"deleted": "🙂 <b>Notiz mit dem Namen </b><code>{}</code> <b>gelöscht</b>",
|
||||
"_cmd_doc_hsave": "[Ordner] <Name> - Speichert eine neue Notiz",
|
||||
"_cmd_doc_hget": "<Name> - Zeigt eine Notiz an",
|
||||
"_cmd_doc_hdel": "<Name> - Löscht eine Notiz",
|
||||
"_cmd_doc_hlist": "[Ordner] - Zeigt alle Notizen an",
|
||||
"_cls_doc": "Notizenmodul mit erweiterten Funktionen. Ordner und Kategorien",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"saved": (
|
||||
"💾 <b>Notu adı </b><code>{}</code><b> kaydedildi</b>.\nKlasör:"
|
||||
" </b><code>{}</code>.</b>"
|
||||
),
|
||||
"no_reply": "🚫 <b>Not içeriğine yanıt verin.</b>",
|
||||
"no_name": "🚫 <b>Bir not adı belirtin.</b>",
|
||||
"no_note": "🚫 <b>Not bulunamadı.</b>",
|
||||
"available_notes": "💾 <b>Mevcut notlar:</b>\n",
|
||||
"no_notes": "😔 <b>Henüz notunuz yok</b>",
|
||||
"deleted": "🙂 <b>Not adı </b><code>{}</code> <b>silindi</b>",
|
||||
"_cmd_doc_hsave": "[Klasör] <Ad> - Yeni bir not kaydedin",
|
||||
"_cmd_doc_hget": "<Ad> - Bir notu göster",
|
||||
"_cmd_doc_hdel": "<Ad> - Bir notu sil",
|
||||
"_cmd_doc_hlist": "[Klasör] - Tüm notları göster",
|
||||
"_cls_doc": "Gelişmiş not modülü. Klasörler ve diğer özellikler",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"saved": (
|
||||
"💾 <b>Qayd nomi </b><code>{}</code><b> saqlandi</b>.\nJild:"
|
||||
" </b><code>{}</code>.</b>"
|
||||
),
|
||||
"no_reply": "🚫 <b>Qayd tarkibiga javob bering.</b>",
|
||||
"no_name": "🚫 <b>Qayd nomini kiriting.</b>",
|
||||
"no_note": "🚫 <b>Qayd topilmadi.</b>",
|
||||
"available_notes": "💾 <b>Mavjud qaydlar:</b>\n",
|
||||
"no_notes": "😔 <b>Hozircha sizda qayd yo'q</b>",
|
||||
"deleted": "🙂 <b>Qayd nomi </b><code>{}</code> <b>o'chirildi</b>",
|
||||
"_cmd_doc_hsave": "[Jild] <Nomi> - Yangi qayd saqlash",
|
||||
"_cmd_doc_hget": "<Nomi> - Qaydni ko'rsatish",
|
||||
"_cmd_doc_hdel": "<Nomi> - Qaydni o'chirish",
|
||||
"_cmd_doc_hlist": "[Jild] - Barcha qaydlarni ko'rsatish",
|
||||
"_cls_doc": "Kengaytirilgan qayd moduli. Jildlar va kategoriyalar",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"saved": (
|
||||
"💾 <b>नोट का नाम </b><code>{}</code><b> सहेजा गया</b>.\nफ़ोल्डर:"
|
||||
" </b><code>{}</code>.</b>"
|
||||
),
|
||||
"no_reply": "🚫 <b>नोट की अंतर्दृष्टि पर जवाब दें।</b>",
|
||||
"no_name": "🚫 <b>एक नोट नाम दर्ज करें।</b>",
|
||||
"no_note": "🚫 <b>नोट नहीं मिला।</b>",
|
||||
"available_notes": "💾 <b>उपलब्ध नोट्स:</b>\n",
|
||||
"no_notes": "😔 <b>आपके पास अभी तक कोई नोट नहीं है</b>",
|
||||
"deleted": "🙂 <b>नोट नाम </b><code>{}</code> <b>हटा दिया गया</b>",
|
||||
"_cmd_doc_hsave": "[फ़ोल्डर] <नाम> - एक नया नोट सहेजें",
|
||||
"_cmd_doc_hget": "<नाम> - एक नोट दिखाएं",
|
||||
"_cmd_doc_hdel": "<नाम> - एक नोट हटाएं",
|
||||
"_cmd_doc_hlist": "[फ़ोल्डर] - सभी नोट्स दिखाएं",
|
||||
"_cls_doc": "उन्नत नोट्स मॉड्यूल। फ़ोल्डर और श्रेणियाँ",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self._notes = self.get("notes", {})
|
||||
|
||||
async def hsavecmd(self, message: Message):
|
||||
"""[folder] <name> - Save new note"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if len(args.split()) >= 2:
|
||||
folder = args.split()[0]
|
||||
args = args.split(maxsplit=1)[1]
|
||||
else:
|
||||
folder = "global"
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if not (reply and args):
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
if folder not in self._notes:
|
||||
self._notes[folder] = {}
|
||||
logger.warning(f"Created new folder {folder}")
|
||||
|
||||
asset = await self._db.store_asset(reply)
|
||||
|
||||
if getattr(reply, "video", False):
|
||||
type_ = "🎞"
|
||||
elif getattr(reply, "photo", False):
|
||||
type_ = "🖼"
|
||||
elif getattr(reply, "voice", False):
|
||||
type_ = "🗣"
|
||||
elif getattr(reply, "audio", False):
|
||||
type_ = "🎧"
|
||||
elif getattr(reply, "file", False):
|
||||
type_ = "📝"
|
||||
else:
|
||||
type_ = "🔹"
|
||||
|
||||
self._notes[folder][args] = {"id": asset, "type": type_}
|
||||
|
||||
self.set("notes", self._notes)
|
||||
|
||||
await utils.answer(message, self.strings("saved").format(args, folder))
|
||||
|
||||
def _get_note(self, name):
|
||||
for category, notes in self._notes.items():
|
||||
for note, asset in notes.items():
|
||||
if note == name:
|
||||
return asset
|
||||
|
||||
def _del_note(self, name):
|
||||
for category, notes in self._notes.copy().items():
|
||||
for note, asset in notes.copy().items():
|
||||
if note == name:
|
||||
del self._notes[category][note]
|
||||
|
||||
if not self._notes[category]:
|
||||
del self._notes[category]
|
||||
|
||||
self.set("notes", self._notes)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def hgetcmd(self, message: Message):
|
||||
"""<name> - Show specified note"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_name"))
|
||||
return
|
||||
|
||||
asset = self._get_note(args)
|
||||
if not asset:
|
||||
await utils.answer(message, self.strings("no_note"))
|
||||
return
|
||||
|
||||
await self._client.send_message(
|
||||
message.peer_id,
|
||||
await self._db.fetch_asset(asset["id"]),
|
||||
reply_to=getattr(message, "reply_to_msg_id", False),
|
||||
)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
async def hdelcmd(self, message: Message):
|
||||
"""<name> - Delete specified note"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_name"))
|
||||
return
|
||||
|
||||
asset = self._get_note(args)
|
||||
if not asset:
|
||||
await utils.answer(message, self.strings("no_note"))
|
||||
return
|
||||
|
||||
try:
|
||||
await (await self._db.fetch_asset(asset["id"])).delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._del_note(args)
|
||||
|
||||
await utils.answer(message, self.strings("deleted").format(args))
|
||||
|
||||
async def hlistcmd(self, message: Message):
|
||||
"""[folder] - List all notes"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if not self._notes:
|
||||
await utils.answer(message, self.strings("no_notes"))
|
||||
return
|
||||
|
||||
result = self.strings("available_notes")
|
||||
|
||||
if not args or args not in self._notes:
|
||||
for category, notes in self._notes.items():
|
||||
result += f"\n🔸 <b>{category}</b>\n"
|
||||
for note, asset in notes.items():
|
||||
result += f" {asset['type']} <code>{note}</code>\n"
|
||||
|
||||
await utils.answer(message, result)
|
||||
return
|
||||
|
||||
for note, asset in self._notes[args].items():
|
||||
result += f"{asset['type']} <code>{note}</code>\n"
|
||||
|
||||
await utils.answer(message, result)
|
||||
51
hikariatama/ftg/onload.py
Normal file
51
hikariatama/ftg/onload.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/onload_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/onload.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class OnloadExecutorMod(loader.Module):
|
||||
"""Executes selected commands after every userbot restart"""
|
||||
|
||||
strings = {"name": "OnloadExecutor"}
|
||||
|
||||
async def client_ready(self, client, _):
|
||||
self.c, _ = await utils.asset_channel(
|
||||
client,
|
||||
"hikka-onload",
|
||||
(
|
||||
"All commands from this chat will be executed once Hikka is started, be"
|
||||
" careful!"
|
||||
),
|
||||
archive=True,
|
||||
avatar="https://raw.githubusercontent.com/hikariatama/assets/master/hikka-onload.png",
|
||||
_folder="hikka",
|
||||
)
|
||||
|
||||
async for message in client.iter_messages(self.c):
|
||||
if (getattr(message, "raw_text", "") or "").startswith(self.get_prefix()):
|
||||
try:
|
||||
m = await client.send_message("me", message.raw_text)
|
||||
await self.allmodules.commands[message.raw_text[1:].split()[0]](m)
|
||||
logger.debug("Registered onload command")
|
||||
await m.delete()
|
||||
except Exception:
|
||||
logger.exception(
|
||||
f"Exception while executing command {message.raw_text[:15]}..."
|
||||
)
|
||||
191
hikariatama/ftg/oxford.py
Normal file
191
hikariatama/ftg/oxford.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/oxford_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/oxford.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: bs4
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
import random
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import grapheme
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
DEFAULT_HEADERS = {
|
||||
"Connection": "keep-alive",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like"
|
||||
" Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"Referer": "https://www.oxfordlearnersdictionaries.com",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9,ru;q=0.8",
|
||||
}
|
||||
|
||||
|
||||
async def search(term: str) -> str:
|
||||
res = await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://www.oxfordlearnersdictionaries.com/search/english/direct/?q={quote_plus(term)}",
|
||||
headers=DEFAULT_HEADERS,
|
||||
)
|
||||
|
||||
soup = BeautifulSoup(res.text, "html.parser")
|
||||
|
||||
if "spellcheck" in res.url:
|
||||
try:
|
||||
possible = [
|
||||
a.get("href").split("?q=")[1]
|
||||
for a in soup.find("ul", {"class": "result-list"}).find_all("a")
|
||||
]
|
||||
except Exception:
|
||||
return {"ok": False, "possible": ["emptiness"]}
|
||||
|
||||
return {"ok": False, "possible": possible}
|
||||
|
||||
try:
|
||||
soup.find("div", {"class": "idioms"}).clear()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"definitions": [
|
||||
definition.get_text()
|
||||
for definition in soup.find_all("span", {"class": "def"})
|
||||
],
|
||||
"part_of_speech": soup.find("span", {"class": "pos"}).get_text(),
|
||||
"pronunciation": soup.find("span", {"class": "phon"}).get_text(),
|
||||
"term": term,
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class OxfordMod(loader.Module):
|
||||
"""Quickly access word definitions in Oxford Learners dictionary"""
|
||||
|
||||
parts_of_speech = {
|
||||
"noun": "существительное",
|
||||
"pronoun": "местоимение",
|
||||
"verb": "глагол",
|
||||
"adjective": "прилагательное",
|
||||
"adverb": "наречие",
|
||||
"preposition": "предлог",
|
||||
"conjunction": "союз",
|
||||
"interjection": "междометие",
|
||||
"determiner": "определитель",
|
||||
"auxiliary verb": "вспомогательный глагол",
|
||||
"modal verb": "модальный глагол",
|
||||
"phrasal verb": "фразеологизм",
|
||||
"idiom": "идиома",
|
||||
"phrase": "фраза",
|
||||
"abbreviation": "аббревиатура",
|
||||
"article": "артикль",
|
||||
"collocation": "коллокация",
|
||||
"exclamation": "восклицание",
|
||||
"expression": "выражение",
|
||||
}
|
||||
|
||||
strings = {
|
||||
"name": "Oxford",
|
||||
"no_exact": (
|
||||
"😔 <b>There is no definition for </b><code>{}</code>\n<b>Maybe, you"
|
||||
" meant:</b>"
|
||||
),
|
||||
"match": '{} <b><a href="{}">{}</a></b> [{}] <i>({})</i>\n\n{}',
|
||||
**{key: key for key in parts_of_speech},
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": (
|
||||
"Быстрый доступ к определениям слов в образовательном Оксфордском словаре"
|
||||
),
|
||||
"no_exact": (
|
||||
"😔 <b>Нет определения для </b><code>{}</code>\n<b>Возможно, вы имели в"
|
||||
" виду:</b>"
|
||||
),
|
||||
**parts_of_speech,
|
||||
}
|
||||
|
||||
async def _search(self, call: InlineCall, term: str):
|
||||
result = await search(term)
|
||||
await call.edit(self.format_match(result))
|
||||
|
||||
def format_match(self, match: dict) -> str:
|
||||
return self.strings("match").format(
|
||||
random.choice(
|
||||
[
|
||||
"<emoji document_id=5188448663982055338>{}</emoji>",
|
||||
"<emoji document_id=5472411062412254753>{}</emoji>",
|
||||
"<emoji document_id=5208541547489927655>{}</emoji>",
|
||||
"<emoji document_id=5206186681346039457>{}</emoji>",
|
||||
"<emoji document_id=5190925490017279861>{}</emoji>",
|
||||
"<emoji document_id=5211151105194467156>{}</emoji>",
|
||||
"<emoji document_id=5204128352629169390>{}</emoji>",
|
||||
"<emoji document_id=5211062143536864914>{}</emoji>",
|
||||
]
|
||||
).format(
|
||||
random.choice(
|
||||
list(
|
||||
grapheme.graphemes(
|
||||
"👩🎓🧑🎓👨🎓👨🏫🧑🏫👩🏫🤵♀️🤵🤵♂️💁♀️💁♂️🙋♂️🙋♀️🙍♀️🙎♂️"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
f"https://www.oxfordlearnersdictionaries.com/search/english/direct/?q={match['term']}",
|
||||
utils.escape_html(match["term"]),
|
||||
utils.escape_html(match["pronunciation"]),
|
||||
utils.escape_html(self.strings(match["part_of_speech"])),
|
||||
"\n\n".join(
|
||||
[
|
||||
"<emoji document_id=4974629970623071075>▫️</emoji><i>"
|
||||
f" {utils.escape_html(definition)}</i>"
|
||||
for definition in match["definitions"]
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<слово> - Поиск слова в образовательном Оксфордском словаре"
|
||||
)
|
||||
async def oxford(self, message: Message):
|
||||
"""<term> - Search word in Oxford Learner's Dictionary"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
args = "emptiness"
|
||||
|
||||
result = await search(args)
|
||||
if not result["ok"]:
|
||||
await self.inline.form(
|
||||
self.strings("no_exact").format(utils.escape_html(args)),
|
||||
message,
|
||||
reply_markup=utils.chunks(
|
||||
[
|
||||
{"text": term, "callback": self._search, "args": (term,)}
|
||||
for term in result["possible"]
|
||||
],
|
||||
2,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
await utils.answer(message, self.format_match(result))
|
||||
697
hikariatama/ftg/pmbl.py
Normal file
697
hikariatama/ftg/pmbl.py
Normal file
@@ -0,0 +1,697 @@
|
||||
__version__ = (3, 0, 4)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta title: PM->BL
|
||||
# meta pic: https://img.icons8.com/external-dreamcreateicons-flat-dreamcreateicons/512/000000/external-death-halloween-dreamcreateicons-flat-dreamcreateicons.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/pmbl.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.5.0
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from telethon.tl.functions.contacts import BlockRequest
|
||||
from telethon.tl.functions.messages import DeleteHistoryRequest, ReportSpamRequest
|
||||
from telethon.tl.types import Message, PeerUser, User
|
||||
from telethon.utils import get_display_name, get_peer_id
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def format_(state: Optional[bool]) -> str:
|
||||
if state is None:
|
||||
return "❔"
|
||||
|
||||
return "🫡" if state else "🙅♂️ Not"
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PMBLMod(loader.Module):
|
||||
"""Bans and reports incoming messages from unknown users"""
|
||||
|
||||
strings = {
|
||||
"name": "PMBL",
|
||||
"state": (
|
||||
"<emoji document_id=5314803591058301611>🛡</emoji> <b>PM->BL is now"
|
||||
" {}</b>\n<i>Report spam? - {}\nDelete dialog? - {}</i>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Usage example:"
|
||||
" </b><code>.pmblsett 0 0</code>"
|
||||
),
|
||||
"args_pmban": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Usage example:"
|
||||
" </b><code>.pmbanlast 5</code>"
|
||||
),
|
||||
"banned": (
|
||||
"😃 <b>Hey there"
|
||||
" •ᴗ•</b>\n<b>Unit «SIGMA»<b>, the <b>guardian</b> of this account. You are"
|
||||
" <b>not approved</b>! You can contact my owner <b>in chat</b>, if you need"
|
||||
" help.\n<b>I need to ban you in terms of security</b>"
|
||||
),
|
||||
"removing": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Removing {} last"
|
||||
" dialogs...</b>"
|
||||
),
|
||||
"removed": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Removed {} last"
|
||||
" dialogs!</b>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>You haven't specified"
|
||||
" user</b>"
|
||||
),
|
||||
"approved": (
|
||||
"<emoji document_id=5461129450341014019>✋</emoji> <b><a"
|
||||
' href="tg://user?id={}">{}</a> approved in pm</b>'
|
||||
),
|
||||
"banned_log": (
|
||||
'👮 <b>I banned <a href="tg://user?id={}">{}</a>.</b>\n\n<b>{} Reported'
|
||||
" spam</b>\n<b>{} Deleted dialog</b>\n\n<b>"
|
||||
" 📝 Message</b>\n<code>{}</code>"
|
||||
),
|
||||
"hello": (
|
||||
"🔏 <b>Unit «SIGMA»</b> protects your personal messages from intrusions. It"
|
||||
" will block everyone, who's trying to invade you.\n\nUse"
|
||||
" <code>.pmbl</code> to enable protection, <code>.pmblsett</code> to"
|
||||
" configure it and <code>.pmbanlast</code> if you've already been"
|
||||
" pm-raided."
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"state": (
|
||||
"<emoji document_id=5314803591058301611>🛡</emoji> <b>Текущее состояние"
|
||||
" PM->BL: {}</b>\n<i>Сообщать о спаме? - {}\nУдалять диалог? - {}</i>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Пример:"
|
||||
" </b><code>.pmblsett 0 0</code>"
|
||||
),
|
||||
"args_pmban": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Пример:"
|
||||
" </b><code>.pmbanlast 5</code>"
|
||||
),
|
||||
"banned": (
|
||||
"😃 <b>Добрый день"
|
||||
" •ᴗ•</b>\n<b>Юнит «SIGMA»<b>, <b>защитник</b> этого аккаунта. Вы <b>не"
|
||||
" потверждены</b>! Вы можете связаться с моим владельцем <b>в чате</b>,"
|
||||
" если нужна помощь.\n<b>Я вынужден заблокировать вас из соображений"
|
||||
" безопасности</b>"
|
||||
),
|
||||
"hello": (
|
||||
"🔏 <b>Юнит «SIGMA»</b> защищает твои личные сообщенния от неизвестных"
|
||||
" пользователей. Он будет блокировать всех, кто не соответствует"
|
||||
" настройкам.\n\nВведи <code>.pmbl</code> для активации защиты,"
|
||||
" <code>.pmblsett</code> для ее настройки и <code>.pmbanlast</code> если"
|
||||
" нужно очистить уже прошедший рейд на личные сообщения."
|
||||
),
|
||||
"removing": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Удаляю {} последних"
|
||||
" диалогов...</b>"
|
||||
),
|
||||
"removed": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Удалил {} последних"
|
||||
" диалогов!</b>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Укажи"
|
||||
" пользователя</b>"
|
||||
),
|
||||
"_cmd_doc_pmbl": "Включить или выключить защиту",
|
||||
"_cmd_doc_pmbanlast": (
|
||||
"<количество> - Забанить и удалить n последних диалогов с пользователями"
|
||||
),
|
||||
"_cmd_doc_allowpm": "<пользователь> - Разрешить пользователю писать тебе в ЛС",
|
||||
"_cls_doc": "Блокирует и репортит входящие сообщения от незнакомцев",
|
||||
"approved": (
|
||||
"<emoji document_id=5461129450341014019>✋</emoji> <b><a"
|
||||
' href="tg://user?id={}">{}</a> одобрен в лс</b>'
|
||||
),
|
||||
"banned_log": (
|
||||
'👮 <b>Я заблокировал <a href="tg://user?id={}">{}</a>.</b>\n\n<b>{}'
|
||||
" Сообщил"
|
||||
" о спаме</b>\n<b>{} Удалил диалог</b>\n\n<b>📝"
|
||||
" Сообщение</b>\n<code>{}</code>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"state": (
|
||||
"<emoji document_id=5314803591058301611>🛡</emoji> <b>Aktueller PM->BL"
|
||||
" Status: {}</b>\n<i>Spam melden? - {}\nDialoge löschen? - {}</i>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Beispiel:"
|
||||
" </b><code>.pmblsett 0 0</code>"
|
||||
),
|
||||
"args_pmban": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Beispiel:"
|
||||
" </b><code>.pmbanlast 5</code>"
|
||||
),
|
||||
"banned": (
|
||||
"😃 <b>Hallo"
|
||||
" •ᴗ•</b>\n<b>Einheit «SIGMA»<b>, <b>der Schutz dieses Accounts</b>. Sie"
|
||||
" sind <b>nicht autorisiert</b>! Sie können sich an den Besitzer meines"
|
||||
" Accounts wenden, wenn Sie Hilfe benötigen.\n<b>Ich bin gezwungen, Sie aus"
|
||||
" Sicherheitsgründen zu sperren</b>"
|
||||
),
|
||||
"hello": (
|
||||
"🔏 <b>Einheit «SIGMA»</b> schützt Ihre persönlichen Nachrichten vor"
|
||||
" unbekannten Benutzern. Es wird alle blockieren, die nicht den"
|
||||
" Einstellungen entsprechen.\n\nGeben Sie <code>.pmbl</code> ein, um die"
|
||||
" Schutzfunktion zu aktivieren, <code>.pmblsett</code> zum Konfigurieren"
|
||||
" und <code>.pmbanlast</code>, wenn Sie bereits einen Raid auf Ihre"
|
||||
" persönlichen Nachrichten durchgeführt haben."
|
||||
),
|
||||
"removing": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Entferne {} letzte"
|
||||
" Dialoge...</b>"
|
||||
),
|
||||
"removed": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Entfernt {} letzte"
|
||||
" Dialoge!</b>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Du hast keinen"
|
||||
" Benutzer angegeben</b>"
|
||||
),
|
||||
"_cmd_doc_pmbl": "Aktiviert oder deaktiviert den Schutz",
|
||||
"_cmd_doc_pmbanlast": (
|
||||
"<Anzahl> - Bannt und löscht n letzte Dialoge mit Benutzern"
|
||||
),
|
||||
"_cmd_doc_allowpm": (
|
||||
"<Benutzer> - Erlaubt dem Benutzer, dir eine private Nachricht zu senden"
|
||||
),
|
||||
"_cls_doc": "Blockiert und meldet eingehende Nachrichten von Unbekannten",
|
||||
"approved": (
|
||||
"<emoji document_id=5461129450341014019>✋</emoji> <b><a"
|
||||
' href="tg://user?id={}">{}</a> wurde in den Ls genehmigt</b>'
|
||||
),
|
||||
"banned_log": (
|
||||
'👮 <b>Ich habe <a href="tg://user?id={}">{}</a> geblockt.</b>\n\n<b>{} Hat'
|
||||
" über Spam berichtet</b>\n<b>{} Hat den Dialog gelöscht</b>\n\n<b>📝"
|
||||
" Nachricht</b>\n<code>{}</code>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"state": (
|
||||
"<emoji document_id=5314803591058301611>🛡</emoji> <b>Şu anki PM->BL durumu:"
|
||||
" {}</b>\n<i>Spam rapor edilsin mi? - {}\nSohbetler silinsin mi? - {}</i>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Örnek:"
|
||||
" </b><code>.pmblsett 0 0</code>"
|
||||
),
|
||||
"args_pmban": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Örnek:"
|
||||
" </b><code>.pmbanlast 5</code>"
|
||||
),
|
||||
"banned": (
|
||||
"😃 <b>Merhaba"
|
||||
" •ᴗ•</b>\n<b>«SIGMA» birimi</b>, <b>hesabınızın koruması</b>. <b>Yetkili"
|
||||
" değilsiniz</b>! Yardım için hesabımın sahibi ile iletişime"
|
||||
" geçebilirsiniz.\n<b>Güvenlik nedeniyle sizi zorunlu olarak"
|
||||
" engelliyorum</b>"
|
||||
),
|
||||
"hello": (
|
||||
"🔏 <b>«SIGMA» birimi</b>, tanımadığınız kullanıcılarla kişisel"
|
||||
" mesajlarınızı korur. Ayarlara uygun olmayanları tümünü engeller.\n\n"
|
||||
"<code>.pmbl</code> yazarak koruma özelliğini etkinleştirebilir, "
|
||||
"<code>.pmblsett</code> yazarak yapılandırabilir ve zaten kişisel"
|
||||
" mesajlarınıza bir raid gerçekleştirdiyseniz <code>.pmbanlast</code>"
|
||||
" yazarak bunu gerçekleştirebilirsiniz."
|
||||
),
|
||||
"removing": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Son {} sohbet"
|
||||
" siliniyor...</b>"
|
||||
),
|
||||
"removed": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Son {} sohbet"
|
||||
" silindi!</b>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bir kullanıcı"
|
||||
" belirtmediniz</b>"
|
||||
),
|
||||
"_cmd_doc_pmbl": "Korumayı etkinleştirir veya devre dışı bırakır",
|
||||
"_cmd_doc_pmbanlast": "<sayı> - Kullanıcılarla son n sohbeti yasaklar ve siler",
|
||||
"_cmd_doc_allowpm": (
|
||||
"<kullanıcı> - Kullanıcıya kişisel mesaj göndermeye izin verir"
|
||||
),
|
||||
"_cls_doc": (
|
||||
"Tanımadığınız kullanıcıların gelen mesajlarını engeller ve rapor eder"
|
||||
),
|
||||
"approved": (
|
||||
"<emoji document_id=5461129450341014019>✋</emoji> <b><a"
|
||||
' href="tg://user?id={}">{}</a> Ls listesine eklendi</b>'
|
||||
),
|
||||
"banned_log": (
|
||||
'👮 <b><a href="tg://user?id={}">{}</a> engellendi.</b>\n\n<b>{} Spam rapor'
|
||||
" etti</b>\n<b>{} Sohbeti sildi</b>\n\n<b>📝 Mesaj</b>\n<code>{}</code>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"state": (
|
||||
"<emoji document_id=5314803591058301611>🛡</emoji> <b>Joriy PM->BL holati:"
|
||||
" {}</b>\n<i>Spam haqida xabar berilsinmi? - {}\nSuhbatlar o'chirilsinmi? -"
|
||||
" {}</i>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Misol:"
|
||||
" </b><code>.pmblsett 0 0</code>"
|
||||
),
|
||||
"args_pmban": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Misol:"
|
||||
" </b><code>.pmbanlast 5</code>"
|
||||
),
|
||||
"banned": (
|
||||
"😃 <b>Salom"
|
||||
" •ᴗ•</b>\n<b>«SIGMA» birimi</b>, <b>hisobingizni himoya</b>. <b>Ruxsat"
|
||||
" berilmaganingiz</b>! Yordam kerak bo'lsa hisobimning egasi bilan"
|
||||
" bog'lanishingiz mumkin.\n<b>Xavfsizlik sababli sizni majbur qilishim"
|
||||
" kerak</b>"
|
||||
),
|
||||
"hello": (
|
||||
"🔏 <b>«SIGMA» birimi</b>, tanimaydigan foydalanuvchilar bilan"
|
||||
" shaxsiy xabarlarini himoya qiladi. Sozlamalarga mos bo'lmasa"
|
||||
" barchasini bloklashadi.\n\n<code>.pmbl</code> yozib himoya"
|
||||
" imkoniyatini yoqish, <code>.pmblsett</code> yozib konfiguratsiyani"
|
||||
" o'zgartirish va agar sizda shaxsiy xabarlariga raid bormi bo'lsa"
|
||||
" <code>.pmbanlast</code> yozib uni bajarishingiz mumkin."
|
||||
),
|
||||
"removing": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Son {} suhbat"
|
||||
" o'chirilmoqda...</b>"
|
||||
),
|
||||
"removed": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>Son {} suhbat"
|
||||
" o'chirildi!</b>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Siz foydalanuvchi"
|
||||
" belgilamadingiz</b>"
|
||||
),
|
||||
"_cmd_doc_pmbl": "Himoyani yoqadi yoki o'chiradi",
|
||||
"_cmd_doc_pmbanlast": (
|
||||
"<son> - Foydalanuvchilar bilan son n suhbatni yasaklaydi"
|
||||
),
|
||||
"_cmd_doc_allowpm": (
|
||||
"<foydalanuvchi> - Foydalanuvchiga shaxsiy xabar yuborishga ruxsat beradi"
|
||||
),
|
||||
"_cls_doc": "Tanimaydigan foydalanuvchilar gelen xabarlarini bloklashadi",
|
||||
"approved": (
|
||||
"<emoji document_id=5461129450341014019>✋</emoji> <b><a"
|
||||
" href=\"tg://user?id={}\">{}</a> Ls ro'yxatiga qo'shildi</b>"
|
||||
),
|
||||
"banned_log": (
|
||||
'👮 <b><a href="tg://user?id={}">{}</a> bloklandi.</b>\n\n<b>{} Spam xabar'
|
||||
" berdi</b>\n<b>{} Suhbat o'chirildi</b>\n\n<b>📝"
|
||||
" Xabar</b>\n<code>{}</code>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"state": (
|
||||
"<emoji document_id=5314803591058301611>🛡</emoji> <b>वर्तमान PM->BL स्थिति:"
|
||||
" {}</b>\n<i>स्पैम रिपोर्ट करें? - {}\nडायलॉगहटाएं? - {}</i>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>उदाहरण:"
|
||||
" </b><code>.pmblsett 0 0</code>"
|
||||
),
|
||||
"args_pmban": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>उदाहरण:"
|
||||
" </b><code>.pmbanlast 5</code>"
|
||||
),
|
||||
"banned": (
|
||||
"😃 <b>नमस्ते"
|
||||
" •ᴗ•</b>\n<b>यूनिट «SIGMA»<b>, <b>इस खाते की सुरक्षा</b>. आप"
|
||||
" <b>अनधिकृत</b> हैं! आप मेरे खाते के मालिक को अपनी मदद के लिए या आपको"
|
||||
" सहायता की आवश्यकता है तो उसे संपर्क कर सकते हैं।\n<b>मैं आपको सुरक्षा के"
|
||||
" कारण बंद करने के लिए बाधित कर दूंगा</b>"
|
||||
),
|
||||
"hello": (
|
||||
"🔏 <b>यूनिट «SIGMA»</b> अपने निजी संदेशों को अज्ञात उपयोगकर्ताओं से"
|
||||
" सुरक्षित करता है। इसे सेटिंग्स के अनुसार सभी ब्लॉक करेगा।\n\n<code>"
|
||||
".pmbl</code> दर्ज करें, ताकि सुरक्षा कार्यक्षमता सक्रिय हो, <code>"
|
||||
".pmblsett</code> कॉन्फ़िगर करने के लिए और <code>.pmbanlast</code>, जब आपने"
|
||||
" अपने निजी संदेशों पर एक रैड किया है।"
|
||||
),
|
||||
"removing": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>{} अंतिम डायलॉग हटा"
|
||||
" रहा है...</b>"
|
||||
),
|
||||
"removed": (
|
||||
"<emoji document_id=5456529570431770384>🚮</emoji> <b>{} अंतिम डायलॉग हटा"
|
||||
" दिया!</b>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>आपने किसी उपयोगकर्ता"
|
||||
" को नहीं निर्दिष्ट किया</b>"
|
||||
),
|
||||
"_cmd_doc_pmbl": "सुरक्षा को सक्षम या अक्षम करता है",
|
||||
"_cmd_doc_pmbanlast": "<अंक> - उपयोगकर्ताओं के साथ निजी संदेशों को ब्लॉक और हटाता है",
|
||||
"_cmd_doc_allowpm": (
|
||||
"<उपयोगकर्ता> - उपयोगकर्ता को आपको एक निजी संदेश भेजने की अनुमति देता है"
|
||||
),
|
||||
"_cmd_doc_pmblsett": (
|
||||
"<ब्लॉक> <अनुमति> - ब्लॉक और अनुमति को सेट करता है, जब आपके पास एक निजी संदेश आता है"
|
||||
),
|
||||
"_cls_doc": "एक निजी संदेश भेजने की अनुमति देता है",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._queue = []
|
||||
self._ban_queue = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"ignore_contacts",
|
||||
True,
|
||||
lambda: "Ignore contacts?",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_active",
|
||||
True,
|
||||
lambda: "Ignore peers, where you participated?",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"active_threshold",
|
||||
5,
|
||||
lambda: "What number of your messages is required to trust peer",
|
||||
validator=loader.validators.Integer(minimum=1),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_message",
|
||||
doc=lambda: "Custom message to notify untrusted peers. Leave empty for default one",
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"photo",
|
||||
"https://github.com/hikariatama/assets/raw/master/unit_sigma.png",
|
||||
lambda: "Photo, which is sent along with banned notification",
|
||||
validator=loader.validators.Link(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"report_spam",
|
||||
False,
|
||||
lambda: "Report spam?",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delete_dialog",
|
||||
False,
|
||||
lambda: "Delete dialog?",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent",
|
||||
False,
|
||||
lambda: "Do not send anything to banned user",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self._whitelist = self.get("whitelist", [])
|
||||
self._ratelimit = []
|
||||
self._ratelimit_timeout = 5 * 60
|
||||
self._ratelimit_threshold = 10
|
||||
if not self.get("ignore_hello", False):
|
||||
await self.inline.bot.send_photo(
|
||||
self._tg_id,
|
||||
photo=(
|
||||
r"https://github.com/hikariatama/assets/raw/master/unit_sigma.png"
|
||||
),
|
||||
caption=self.strings("hello"),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
self.set("ignore_hello", True)
|
||||
|
||||
async def pmblcmd(self, message: Message):
|
||||
"""Toggle PMBL"""
|
||||
current = self.get("state", False)
|
||||
new = not current
|
||||
self.set("state", new)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("state").format(
|
||||
"on" if new else "off",
|
||||
"yes" if self.config["report_spam"] else "no",
|
||||
"yes" if self.config["delete_dialog"] else "no",
|
||||
),
|
||||
)
|
||||
|
||||
async def pmbanlastcmd(self, message: Message):
|
||||
"""<number> - Ban and delete dialogs with n most new users"""
|
||||
n = utils.get_args_raw(message)
|
||||
if not n or not n.isdigit():
|
||||
await utils.answer(message, self.strings("args_pmban"))
|
||||
return
|
||||
|
||||
n = int(n)
|
||||
|
||||
await utils.answer(message, self.strings("removing").format(n))
|
||||
|
||||
dialogs = []
|
||||
async for dialog in self._client.iter_dialogs(ignore_pinned=True):
|
||||
try:
|
||||
if not isinstance(dialog.message.peer_id, PeerUser):
|
||||
continue
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
m = (
|
||||
await self._client.get_messages(
|
||||
dialog.message.peer_id,
|
||||
limit=1,
|
||||
reverse=True,
|
||||
)
|
||||
)[0]
|
||||
|
||||
dialogs += [
|
||||
(
|
||||
get_peer_id(dialog.message.peer_id),
|
||||
int(time.mktime(m.date.timetuple())),
|
||||
)
|
||||
]
|
||||
|
||||
dialogs.sort(key=lambda x: x[1])
|
||||
to_ban = [d for d, _ in dialogs[::-1][:n]]
|
||||
|
||||
for d in to_ban:
|
||||
await self._client(BlockRequest(id=d))
|
||||
|
||||
await self._client(DeleteHistoryRequest(peer=d, just_clear=True, max_id=0))
|
||||
|
||||
await utils.answer(message, self.strings("removed").format(n))
|
||||
|
||||
def _approve(self, user: int, reason: str = "unknown"):
|
||||
self._whitelist += [user]
|
||||
self._whitelist = list(set(self._whitelist))
|
||||
self.set("whitelist", self._whitelist)
|
||||
logger.debug(f"User approved in pm {user}, filter: {reason}")
|
||||
return
|
||||
|
||||
async def allowpmcmd(self, message: Message):
|
||||
"""<reply or user> - Allow user to pm you"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
user = None
|
||||
|
||||
try:
|
||||
user = await self._client.get_entity(args)
|
||||
except Exception:
|
||||
with contextlib.suppress(Exception):
|
||||
user = await self._client.get_entity(reply.sender_id) if reply else None
|
||||
|
||||
if not user:
|
||||
chat = await message.get_chat()
|
||||
if not isinstance(chat, User):
|
||||
await utils.answer(message, self.strings("user_not_specified"))
|
||||
return
|
||||
|
||||
user = chat
|
||||
|
||||
self._approve(user.id, "manual_approve")
|
||||
await utils.answer(
|
||||
message, self.strings("approved").format(user.id, get_display_name(user))
|
||||
)
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
getattr(message, "out", False)
|
||||
or not isinstance(message, Message)
|
||||
or not isinstance(message.peer_id, PeerUser)
|
||||
or not self.get("state", False)
|
||||
or utils.get_chat_id(message)
|
||||
in {
|
||||
1271266957, # @replies
|
||||
777000, # Telegram Notifications
|
||||
self._tg_id, # Self
|
||||
}
|
||||
):
|
||||
return
|
||||
|
||||
self._queue += [message]
|
||||
|
||||
@loader.loop(interval=0.05, autostart=True)
|
||||
async def ban_loop(self):
|
||||
if not self._ban_queue:
|
||||
return
|
||||
|
||||
message = self._ban_queue.pop(0)
|
||||
self._ratelimit = list(
|
||||
filter(
|
||||
lambda x: x + self._ratelimit_timeout < time.time(),
|
||||
self._ratelimit,
|
||||
)
|
||||
)
|
||||
|
||||
dialog = None
|
||||
|
||||
if len(self._ratelimit) < self._ratelimit_threshold:
|
||||
if not self.config["silent"]:
|
||||
try:
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
self.config["photo"],
|
||||
caption=self.config["custom_message"] or self.strings("banned"),
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.config["custom_message"] or self.strings("banned"),
|
||||
)
|
||||
|
||||
self._ratelimit += [round(time.time())]
|
||||
|
||||
try:
|
||||
dialog = await self._client.get_entity(message.peer_id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
await self.inline.bot.send_message(
|
||||
self._client.tg_id,
|
||||
self.strings("banned_log").format(
|
||||
dialog.id if dialog is not None else message.sender_id,
|
||||
(
|
||||
utils.escape_html(dialog.first_name)
|
||||
if dialog is not None
|
||||
else (
|
||||
getattr(getattr(message, "sender", None), "username", None)
|
||||
or message.sender_id
|
||||
)
|
||||
),
|
||||
format_(self.config["report_spam"]),
|
||||
format_(self.config["delete_dialog"]),
|
||||
utils.escape_html(
|
||||
"<sticker"
|
||||
if message.sticker
|
||||
else (
|
||||
"<photo>"
|
||||
if message.photo
|
||||
else (
|
||||
"<video>"
|
||||
if message.video
|
||||
else (
|
||||
"<file>"
|
||||
if message.document
|
||||
else message.raw_text[:3000]
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
await self._client(BlockRequest(id=message.sender_id))
|
||||
|
||||
if self.config["report_spam"]:
|
||||
await self._client(ReportSpamRequest(peer=message.sender_id))
|
||||
|
||||
if self.config["delete_dialog"]:
|
||||
await self._client(
|
||||
DeleteHistoryRequest(peer=message.sender_id, just_clear=True, max_id=0)
|
||||
)
|
||||
|
||||
self._approve(message.sender_id, "banned")
|
||||
|
||||
logger.warning(f"Intruder punished: {message.sender_id}")
|
||||
|
||||
@loader.loop(interval=0.01, autostart=True)
|
||||
async def queue_processor(self):
|
||||
if not self._queue:
|
||||
return
|
||||
|
||||
message = self._queue.pop(0)
|
||||
|
||||
cid = utils.get_chat_id(message)
|
||||
if cid in self._whitelist:
|
||||
return
|
||||
|
||||
peer = (
|
||||
getattr(getattr(message, "sender", None), "username", None)
|
||||
or message.peer_id
|
||||
)
|
||||
|
||||
with contextlib.suppress(ValueError):
|
||||
entity = await self._client.get_entity(peer)
|
||||
|
||||
if entity.bot:
|
||||
return self._approve(cid, "bot")
|
||||
|
||||
if self.config["ignore_contacts"]:
|
||||
if entity.contact:
|
||||
return self._approve(cid, "ignore_contacts")
|
||||
|
||||
first_message = (
|
||||
await self._client.get_messages(
|
||||
peer,
|
||||
limit=1,
|
||||
reverse=True,
|
||||
)
|
||||
)[0]
|
||||
|
||||
if (
|
||||
getattr(message, "raw_text", False)
|
||||
and first_message.sender_id == self._tg_id
|
||||
):
|
||||
return self._approve(cid, "started_by_you")
|
||||
|
||||
if self.config["ignore_active"]:
|
||||
q = 0
|
||||
|
||||
async for msg in self._client.iter_messages(peer, limit=200):
|
||||
if msg.sender_id == self._tg_id:
|
||||
q += 1
|
||||
|
||||
if q >= self.config["active_threshold"]:
|
||||
return self._approve(cid, "active_threshold")
|
||||
|
||||
self._ban_queue += [message]
|
||||
|
||||
@loader.debug_method(name="unwhitelist")
|
||||
async def denypm(self, message: Message):
|
||||
user = (await message.get_reply_message()).sender_id
|
||||
self.set("whitelist", list(set(self.get("whitelist", [])) - {user}))
|
||||
return f"User unwhitelisted: {user}"
|
||||
141
hikariatama/ftg/pollplot.py
Normal file
141
hikariatama/ftg/pollplot.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/pollplot_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/pollplot.jpg
|
||||
# requires: matplotlib
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import io
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PollPlotMod(loader.Module):
|
||||
"""Visualises polls as plots"""
|
||||
|
||||
strings = {
|
||||
"name": "PollPlot",
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Reply to a poll is"
|
||||
" required!</b>"
|
||||
),
|
||||
"no_answers": (
|
||||
"<emoji document_id=5197183257367552085>😢</emoji> <b>This poll has not"
|
||||
" answers yet.</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Нужен ответ на"
|
||||
" опрос!</b>"
|
||||
),
|
||||
"no_answers": (
|
||||
"<emoji document_id=5197183257367552085>😢</emoji> <b>В этом опросе"
|
||||
" пока что"
|
||||
" нет участников.</b>"
|
||||
),
|
||||
"_cmd_doc_plot": "<reply> - Создать визуализацию опроса",
|
||||
"_cls_doc": "Визуализирует опросы в виде графиков",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Antwort auf eine"
|
||||
" Umfrage erforderlich!</b>"
|
||||
),
|
||||
"no_answers": (
|
||||
"<emoji document_id=5197183257367552085>😢</emoji> <b>Diese Umfrage hat"
|
||||
" noch"
|
||||
" keine Antworten.</b>"
|
||||
),
|
||||
"_cmd_doc_plot": "<reply> - Erstelle eine Visualisierung von Umfragen",
|
||||
"_cls_doc": "Visualisiert Umfragen als Diagramme",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>एक पोल पर जवाब आवश्यक"
|
||||
" है!</b>"
|
||||
),
|
||||
"no_answers": (
|
||||
"<emoji document_id=5197183257367552085>😢</emoji> <b>इस पोल में अभी तक कोई"
|
||||
" उत्तर नहीं है।</b>"
|
||||
),
|
||||
"_cmd_doc_plot": "<reply> - पोल को बनाने के लिए प्लॉट करें",
|
||||
"_cls_doc": "पोल को प्लॉट के रूप में दर्शाता है",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Javob berilgan savol"
|
||||
" kerak!</b>"
|
||||
),
|
||||
"no_answers": (
|
||||
"<emoji document_id=5197183257367552085>😢</emoji> <b>Ushbu savolda hali"
|
||||
" hech qanday javob yo'q.</b>"
|
||||
),
|
||||
"_cmd_doc_plot": "<reply> - Savolni chizishga o'tkazish",
|
||||
"_cls_doc": "Savollarni chizishlar shaklida ko'rsatadi",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bir anket yanıtı"
|
||||
" gerekli!</b>"
|
||||
),
|
||||
"no_answers": (
|
||||
"<emoji document_id=5197183257367552085>😢</emoji> <b>Bu anket henüz cevap"
|
||||
" yok.</b>"
|
||||
),
|
||||
"_cmd_doc_plot": "<yanıt> - Bir anketi çizimden oluşturun",
|
||||
"_cls_doc": "Anketleri çizimler şeklinde gösterir",
|
||||
}
|
||||
|
||||
async def plotcmd(self, message: Message):
|
||||
"""<reply> - Create plot from poll"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not getattr(reply, "poll", False):
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
sizes = [i.voters for i in reply.poll.results.results]
|
||||
|
||||
if not sum(sizes):
|
||||
await utils.answer(message, self.strings("no_answers"))
|
||||
return
|
||||
|
||||
labels = [
|
||||
f"{a.text} [{sizes[i]}] ({round(sizes[i] / sum(sizes) * 100, 1)}%)"
|
||||
for i, a in enumerate(reply.poll.poll.answers)
|
||||
]
|
||||
|
||||
explode = [0.05] * len(sizes)
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.pie(
|
||||
sizes,
|
||||
explode=explode,
|
||||
labels=labels,
|
||||
textprops={"color": "white", "size": "large"},
|
||||
)
|
||||
buf = io.BytesIO()
|
||||
fig1.patch.set_facecolor("#303841")
|
||||
fig1.savefig(buf)
|
||||
buf.seek(0)
|
||||
|
||||
await self._client.send_file(message.peer_id, buf.getvalue(), reply_to=reply)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
331
hikariatama/ftg/porn.py
Normal file
331
hikariatama/ftg/porn.py
Normal file
@@ -0,0 +1,331 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/porn_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/porn.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import io
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from typing import List
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PornVideo:
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
keywords: str,
|
||||
views: int,
|
||||
rate: str,
|
||||
url: str,
|
||||
embed: str,
|
||||
default_thumb: dict,
|
||||
length_min: str,
|
||||
**_,
|
||||
):
|
||||
"""
|
||||
:param title: title of the video
|
||||
:param keywords: keywords of the video
|
||||
:param views: views of the video
|
||||
:param rate: rate of the video
|
||||
:param url: url of the video
|
||||
:param embed: embed of the video
|
||||
:param default_thumb: default thumbnail of the video
|
||||
:param length_min: length of the video
|
||||
"""
|
||||
self.title = title
|
||||
self.keywords = list(
|
||||
map(
|
||||
lambda x: "".join(
|
||||
[
|
||||
i if i in string.ascii_letters + string.digits else "_"
|
||||
for i in x.strip()
|
||||
]
|
||||
),
|
||||
keywords.split(","),
|
||||
)
|
||||
)
|
||||
self.views = views
|
||||
if views > 1000:
|
||||
self.views = str(round(views / 1000, 1)) + "k"
|
||||
|
||||
self.rate = float(rate)
|
||||
self.url = url
|
||||
self.embed = embed
|
||||
self.thumb = default_thumb["src"]
|
||||
self.info = (
|
||||
f"🔞 <b>{utils.escape_html(title)}</b>\n\n<b>💫 Rating: {rate}\n👀 Views:"
|
||||
f" {self.views}</b>\n<b>⌚️ Duration:"
|
||||
f" {length_min}</b>\n\n<i>#{' #'.join(self.keywords)}</i>"
|
||||
)
|
||||
self._headers = {
|
||||
"host": "www.eporner.com",
|
||||
"referer": self.embed,
|
||||
"user-agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,"
|
||||
" like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
|
||||
"cache-control": "no-cache",
|
||||
}
|
||||
|
||||
def convert_hash(self, hash_: str) -> str:
|
||||
"""
|
||||
Process hash
|
||||
:param hash_: hash to convert
|
||||
:return: converted hash
|
||||
"""
|
||||
|
||||
def dec_to_36(dec: int) -> str:
|
||||
"""
|
||||
Dec to 36-numeric string
|
||||
:param dec: dec to convert
|
||||
:return: converted string
|
||||
"""
|
||||
digits = string.digits + string.ascii_lowercase
|
||||
x = dec % len(digits)
|
||||
rest = dec // len(digits)
|
||||
return digits[x] if rest == 0 else dec_to_36(rest) + digits[x]
|
||||
|
||||
return "".join([dec_to_36(int(x, 16)) for x in utils.chunks(hash_, 8)]).lower()
|
||||
|
||||
async def _get_media_url(self) -> str:
|
||||
"""
|
||||
Gets the media url of the video
|
||||
:return: url of the video
|
||||
"""
|
||||
init = await utils.run_sync(requests.get, self.embed, headers=self._headers)
|
||||
|
||||
qualities = ["360p", "480p", "720p"]
|
||||
|
||||
res = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
(
|
||||
f"https://www.eporner.com/xhr/video/{self.embed.strip('/').split('/')[-1]}?"
|
||||
+ "&".join(
|
||||
f"{k}={v}"
|
||||
for k, v in {
|
||||
"hash": self.convert_hash(
|
||||
next(
|
||||
line.split("'")[1]
|
||||
for line in init.text.splitlines()
|
||||
if line.strip().startswith("EP.video.player.hash")
|
||||
)
|
||||
),
|
||||
"domain": "www.eporner.com",
|
||||
"pixelRatio": 1.5,
|
||||
"playerWidth": 0,
|
||||
"playerHeight": 0,
|
||||
"fallback": False,
|
||||
"embed": True,
|
||||
"supportedFormats": "hls,dash,mp4",
|
||||
"_": str(round(time.time())) + str(
|
||||
random.randint(100, 999)
|
||||
),
|
||||
}.items()
|
||||
)
|
||||
),
|
||||
headers=self._headers,
|
||||
cookies=init.cookies,
|
||||
allow_redirects=True,
|
||||
)
|
||||
).json()["sources"]["mp4"]
|
||||
|
||||
return res[
|
||||
next(
|
||||
(quality for quality in qualities if quality in res),
|
||||
list(res.keys())[0],
|
||||
)
|
||||
]["src"]
|
||||
|
||||
|
||||
class PornManager:
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
def _from_json(self, json: dict) -> List[PornVideo]:
|
||||
"""
|
||||
Convert API data from json to Python OOP objects
|
||||
:param json: Json data from API
|
||||
:return: List of obj:`PornVideo`
|
||||
"""
|
||||
|
||||
return [PornVideo(**item) for item in json]
|
||||
|
||||
async def search(self, query: str, gay: bool) -> List[PornVideo]:
|
||||
"""
|
||||
Search for porn videos
|
||||
:param query: Search query
|
||||
:param gay: Are you searching for gay content
|
||||
:return: List of obj:`PornVideo`
|
||||
"""
|
||||
|
||||
return self._from_json(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
(
|
||||
f"https://www.eporner.com/api/v2/video/search/?query={quote(query)}&per_page=30&page=1&thumbsize=big&order=top-weekly&gay={'2' if gay else '0'}&lq=0&format=json"
|
||||
),
|
||||
)
|
||||
).json()["videos"]
|
||||
)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PornMod(loader.Module):
|
||||
"""Sends adult content directly to Telegram. Use with caution"""
|
||||
|
||||
strings = {
|
||||
"name": "Porn",
|
||||
"args": "🚫 <b>Specify search query</b>",
|
||||
"404": "🚫 <b>No results found</b>",
|
||||
"downloading_porn": "🚍 <b>Downloading your porn...</b>",
|
||||
"page404": "🚫 Page doesn't exist",
|
||||
"back": "👈 Back",
|
||||
"next": "👉 Next",
|
||||
"download": "Download",
|
||||
"close": "🔻 Close",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Укажи поисковый запрос</b>",
|
||||
"404": "🚫 <b>Результатов не найдено</b>",
|
||||
"downloading_porn": "🚍 <b>Скачиваю твою порнушку...</b>",
|
||||
"page404": "🚫 Страница не существует",
|
||||
"back": "👈 Назад",
|
||||
"next": "👉 Далее",
|
||||
"download": "Скачать",
|
||||
"close": "🔻 Закрыть",
|
||||
"_cls_doc": (
|
||||
"Позволяет просматривать и скачивать контент для взрослых напрямую в"
|
||||
" Телеграм"
|
||||
),
|
||||
"_cmd_doc_porn": (
|
||||
"<запрос> - Показать порнушку по запросу (будь осторожен в публичных чатах)"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"gay",
|
||||
False,
|
||||
"Are you gay?",
|
||||
validator=loader.validators.Boolean(),
|
||||
)
|
||||
)
|
||||
|
||||
async def client_ready(self, *_):
|
||||
self.porn = PornManager()
|
||||
|
||||
async def _download_video(self, call: InlineCall, video: PornVideo):
|
||||
await call.edit(
|
||||
self.strings("downloading_porn"),
|
||||
gif="https://c.tenor.com/TAIxD-ulneYAAAAC/anime-anime-background.gif",
|
||||
)
|
||||
|
||||
vid = io.BytesIO(
|
||||
(await utils.run_sync(requests.get, await video._get_media_url())).content
|
||||
)
|
||||
vid.name = "video.mp4"
|
||||
|
||||
await self._client.send_file(call.form["chat"], vid, caption=video.info)
|
||||
await call.delete()
|
||||
|
||||
async def _display_video(
|
||||
self,
|
||||
call: InlineCall,
|
||||
results: list,
|
||||
index: int,
|
||||
):
|
||||
if index not in range(len(results)):
|
||||
await call.answer(self.strings("page404"))
|
||||
return
|
||||
|
||||
try:
|
||||
await call.edit(
|
||||
results[index].info,
|
||||
reply_markup=self._get_markup(results, index),
|
||||
photo=results[index].thumb,
|
||||
)
|
||||
except Exception:
|
||||
return await self._display_video(call, results, index)
|
||||
|
||||
def _get_markup(self, results: list, index: int) -> dict:
|
||||
return [
|
||||
[
|
||||
*(
|
||||
[
|
||||
{
|
||||
"text": self.strings("back"),
|
||||
"callback": self._display_video,
|
||||
"args": (results, index - 1),
|
||||
}
|
||||
]
|
||||
if index > 0
|
||||
else []
|
||||
),
|
||||
{
|
||||
"text": (
|
||||
f"{'🏳️🌈' if self.config['gay'] else '🔞'} {self.strings('download')}"
|
||||
),
|
||||
"callback": self._download_video,
|
||||
"args": (results[index],),
|
||||
},
|
||||
*(
|
||||
[
|
||||
{
|
||||
"text": self.strings("next"),
|
||||
"callback": self._display_video,
|
||||
"args": (results, index + 1),
|
||||
}
|
||||
]
|
||||
if index + 1 < len(results)
|
||||
else []
|
||||
),
|
||||
],
|
||||
[{"text": self.strings("close"), "action": "close"}],
|
||||
]
|
||||
|
||||
async def porncmd(self, message: Message):
|
||||
"""<query> - Send adult content gallery (be aware using in public chats)"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
results = await self.porn.search(args, self.config["gay"])
|
||||
|
||||
if not results:
|
||||
await utils.answer(message, self.strings("404"))
|
||||
return
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=results[0].info,
|
||||
reply_markup=self._get_markup(results, 0),
|
||||
photo=results[0].thumb,
|
||||
)
|
||||
35
hikariatama/ftg/premium_sticks.py
Normal file
35
hikariatama/ftg/premium_sticks.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://0x0.st/ojP2.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/premium_sticks.jpg
|
||||
# meta developer: @hikarimods
|
||||
|
||||
import random
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PremiumStickersMod(loader.Module):
|
||||
"""Sends premium stickers for free"""
|
||||
|
||||
strings = {"name": "PremiumStickers"}
|
||||
|
||||
async def premstickcmd(self, message: Message):
|
||||
"""Send random premium sticker without premium"""
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
await message.respond(
|
||||
(
|
||||
f'<a href="https://t.me/hikka_premum_stickers/{random.randint(2, 106)}"></a>'
|
||||
),
|
||||
)
|
||||
72
hikariatama/ftg/purr.py
Normal file
72
hikariatama/ftg/purr.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
|
||||
# meta pic: https://static.dan.tatar/purr_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/purr.jpg
|
||||
# requires: pydub python-ffmpeg
|
||||
# meta developer: @hikarimods
|
||||
# scope: ffmpeg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import io
|
||||
import random
|
||||
|
||||
import requests
|
||||
from pydub import AudioSegment
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class KeywordMod(loader.Module):
|
||||
"""Sends purr-r message"""
|
||||
|
||||
strings = {"name": "Purr"}
|
||||
|
||||
@loader.unrestricted
|
||||
async def purrcmd(self, message: Message):
|
||||
"""Sends 'purr' voice message"""
|
||||
args = utils.get_args_raw(message) or "<i>🐈 Purrr-r-r-r...</i>"
|
||||
purrs = [
|
||||
"https://github.com/hikariatama/assets/raw/master/ne6O.mp3",
|
||||
"https://github.com/hikariatama/assets/raw/master/Kc0L.mp3",
|
||||
"https://github.com/hikariatama/assets/raw/master/rGdI.mp3",
|
||||
"https://github.com/hikariatama/assets/raw/master/3mtz.mp3",
|
||||
"https://github.com/hikariatama/assets/raw/master/3U9J.mp3",
|
||||
]
|
||||
|
||||
voice = (await utils.run_sync(requests.get, random.choice(purrs))).content
|
||||
|
||||
byte = io.BytesIO(b"0")
|
||||
segm = AudioSegment.from_file(io.BytesIO(voice))
|
||||
random_duration = random.randint(5000, 15000)
|
||||
end = len(segm) - random_duration
|
||||
end = len(segm) if end < 0 else end
|
||||
random_begin = random.randint(0, end)
|
||||
random_begin = 0 if end < 0 else random_begin
|
||||
|
||||
segm[random_begin : min(len(segm), random_begin + random_duration)].export(
|
||||
byte,
|
||||
format="ogg",
|
||||
)
|
||||
|
||||
byte.name = "purr.ogg"
|
||||
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
byte,
|
||||
caption=args,
|
||||
voice_note=True,
|
||||
reply_to=message.reply_to_msg_id,
|
||||
)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
271
hikariatama/ftg/ratemod.py
Normal file
271
hikariatama/ftg/ratemod.py
Normal file
@@ -0,0 +1,271 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/external-phatplus-lineal-color-phatplus/512/000000/external-rate-email-phatplus-lineal-color-phatplus.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/ratemod.jpg
|
||||
# meta developer: @hikarimods
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class RateModuleMod(loader.Module):
|
||||
"""Rates module and suggests fixes"""
|
||||
|
||||
strings = {
|
||||
"name": "RateMod",
|
||||
"template": (
|
||||
"👮♂️ <b>Mode rating </b><code>{}</code><b>:</b>\n{} {} <b>[{}]</b>\n\n{}"
|
||||
),
|
||||
"no_file": "<b>What should I check?... 🗿</b>",
|
||||
"cannot_check_file": "<b>Check error</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"template": (
|
||||
"👮♂️ <b>Оценка модуля </b><code>{}</code><b>:</b>\n{} {} <b>[{}]</b>\n\n{}"
|
||||
),
|
||||
"no_file": "<b>А что проверять то?... 🗿</b>",
|
||||
"cannot_check_file": "<b>Ошибка проверки</b>",
|
||||
"_cmd_doc_ratemod": "<код> - Оценить модуль",
|
||||
"_cls_doc": "Оценивает модуль и дает рекомендации",
|
||||
}
|
||||
|
||||
@loader.unrestricted
|
||||
async def ratemodcmd(self, message: Message):
|
||||
"""<reply_to_file|file|link> - Rate code"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
if (
|
||||
not reply
|
||||
and not getattr(reply, "media", None)
|
||||
and not getattr(message, "media", None)
|
||||
and not args
|
||||
and not utils.check_url(args)
|
||||
):
|
||||
return await utils.answer(message, self.strings("no_file"))
|
||||
|
||||
checking = (
|
||||
getattr(reply, "media", None)
|
||||
if getattr(reply, "media", None) is not None
|
||||
else (
|
||||
getattr(message, "media", None)
|
||||
if getattr(message, "media", None) is not None
|
||||
else (args if args and utils.check_url(args) else 0)
|
||||
)
|
||||
)
|
||||
if type(checking) is int:
|
||||
return await utils.answer(message, self.strings("no_file"))
|
||||
|
||||
if type(checking) is not str:
|
||||
try:
|
||||
file = await self._client.download_file(
|
||||
(
|
||||
getattr(reply, "media", None)
|
||||
if getattr(reply, "media", None) is not None
|
||||
else getattr(message, "media", None)
|
||||
),
|
||||
bytes,
|
||||
)
|
||||
except Exception:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings("cannot_check_file"),
|
||||
)
|
||||
|
||||
try:
|
||||
code = file.decode("utf-8").replace("\r\n", "\n")
|
||||
except Exception:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings("cannot_check_file"),
|
||||
)
|
||||
|
||||
else:
|
||||
try:
|
||||
code = (await utils.run_sync(requests.get, args)).text
|
||||
except Exception:
|
||||
return await utils.answer(message, self.strings("cannot_check_file"))
|
||||
|
||||
try:
|
||||
mod_name = re.search(
|
||||
r"""strings[ ]*=[ ]*{.*?name['"]:[ ]*['"](.*?)['"]""", code, flags=re.S
|
||||
).group(1)
|
||||
except Exception:
|
||||
mod_name = "Unknown"
|
||||
|
||||
import_regex = [
|
||||
r"^[^#]rom ([^\n\r]*) import [^\n\r]*$",
|
||||
r"^[^#]mport ([^\n\r]*)[^\n\r]*$",
|
||||
r"""__import__[(]['"]([^'"]*)['"][)]""",
|
||||
]
|
||||
imports = [
|
||||
re.findall(import_re, code, flags=re.M | re.DOTALL)
|
||||
for import_re in import_regex
|
||||
]
|
||||
|
||||
if ".." in imports:
|
||||
del imports[imports.index("..")]
|
||||
|
||||
splitted = [
|
||||
_
|
||||
for _ in list(
|
||||
zip(
|
||||
list(
|
||||
map(
|
||||
lambda x: len(re.findall(r"[ \t]+(if|elif|else).+:", x)),
|
||||
re.split(r"[ \t]*async def .*?cmd\(", code),
|
||||
)
|
||||
),
|
||||
[""] + re.findall(r"[ \t]*async def (.*?)cmd\(", code),
|
||||
)
|
||||
)
|
||||
if _[0] > 10
|
||||
]
|
||||
|
||||
comments = ""
|
||||
|
||||
score = 4.6
|
||||
if len(imports) > 10:
|
||||
comments += (
|
||||
f"🔻 <code>{{-0.1}}</code> <b>A lot of imports ({len(imports)})"
|
||||
" </b><i>[memory]</i>\n"
|
||||
)
|
||||
score -= 0.1
|
||||
if "requests" in imports and "utils.run_sync" not in code:
|
||||
comments += (
|
||||
"🔻 <code>{-0.5}</code> <b>Sync requests</b> <i>[blocks runtime]</i>\n"
|
||||
)
|
||||
score -= 0.5
|
||||
if "while True" in code or "while 1" in code:
|
||||
comments += (
|
||||
"🔻 <code>{-0.1}</code> <b>While true</b> <i>[block runtime*]</i>\n"
|
||||
)
|
||||
score -= 0.1
|
||||
if ".edit(" in code:
|
||||
comments += (
|
||||
"🔻 <code>{-0.3}</code> <b>Classic message.edit</b> <i>[no twink"
|
||||
" support]</i>\n"
|
||||
)
|
||||
score -= 0.3
|
||||
if re.search(r"@.*?[bB][oO][tT]", code) is not None:
|
||||
bots = " | ".join(re.findall(r"@.*?[bB][oO][tT]", code))
|
||||
comments += (
|
||||
"🔻 <code>{-0.2}</code> <b>Bot-abuse"
|
||||
f" (</b><code>{bots}</code><b>)</b>"
|
||||
" <i>[module will die some day]</i>\n"
|
||||
)
|
||||
score -= 0.2
|
||||
if re.search(r'[ \t]+async def .*?cmd.*\n[ \t]+[^\'" \t]', code) is not None:
|
||||
undoc = " | ".join(
|
||||
list(re.findall(r'[ \t]+async def (.*?)cmd.*\n[ \t]+[^" \t]', code))
|
||||
)
|
||||
|
||||
comments += (
|
||||
f"🔻 <code>{{-0.4}}</code> <b>No docs (</b><code>{undoc}</code><b>)</b>"
|
||||
" <i>[all commands must be documented]</i>\n"
|
||||
)
|
||||
score -= 0.4
|
||||
if "time.sleep" in code or "from time import sleep" in code:
|
||||
comments += (
|
||||
"🔻 <code>{-2.0}</code> <b>Sync sleep (</b><code>time.sleep</code><b>)"
|
||||
" replace with (</b><code>await asyncio.sleep</code><b>)</b> <i>[blocks"
|
||||
" runtime]</i>\n"
|
||||
)
|
||||
score -= 2
|
||||
if [_ for _ in code.split("\n") if len(_) > 300]:
|
||||
ll = max(len(_) for _ in code.split("\n") if len(_) > 300)
|
||||
comments += (
|
||||
f"🔻 <code>{{-0.1}}</code> <b>Long lines ({ll})</b> <i>[PEP"
|
||||
" violation]</i>\n"
|
||||
)
|
||||
score -= 0.1
|
||||
if re.search(r'[\'"] ?\+ ?.*? ?\+ ?[\'"]', code) is not None:
|
||||
comments += (
|
||||
"🔻 <code>{-0.1}</code> <b>Avoiding f-строк</b> <i>[causes"
|
||||
" problems]</i>\n"
|
||||
)
|
||||
score -= 0.1
|
||||
if splitted:
|
||||
comments += (
|
||||
"🔻 <code>{-0.2}</code> <b>Big 'if' trees"
|
||||
f" (</b><code>{' | '.join([f'{chain} в {fun}' for chain, fun in splitted])}</code><b>)</b>"
|
||||
" <i>[readability]</i>\n"
|
||||
)
|
||||
score -= 0.2
|
||||
if "== None" in code or "==None" in code:
|
||||
comments += (
|
||||
"🔻 <code>{-0.3}</code> <b>Type comparsation via ==</b> <i>[google"
|
||||
" it]</i>\n"
|
||||
)
|
||||
|
||||
score -= 0.3
|
||||
if "is not None else" in code:
|
||||
comments += (
|
||||
"🔻 <code>{-0.1}</code> <b>Unneccessary ternary operator usage"
|
||||
" (</b><code>if some_var is not None else another</code> <b>-></b>"
|
||||
" <code>some_var or another</code><b>)</b> <i>[readability]</i>\n"
|
||||
)
|
||||
|
||||
score -= 0.1
|
||||
if "utils.answer" in code and ".edit(" not in code:
|
||||
comments += (
|
||||
"🔸 <code>{+0.3}</code> <b>utils.answer</b> <i>[twinks support]</i>\n"
|
||||
)
|
||||
score += 0.3
|
||||
if re.search(r'[ \t]+async def .*?cmd.*\n[ \t]+[^\'" \t]', code) is None:
|
||||
comments += (
|
||||
"🔸 <code>{+0.3}</code> <b>Docs</b> <i>[all commands are"
|
||||
" documented]</i>\n"
|
||||
)
|
||||
score += 0.3
|
||||
if "requests" in imports and "utils.run_sync" in code or "aiohttp" in imports:
|
||||
comments += (
|
||||
"🔸 <code>{+0.3}</code> <b>Async requests</b> <i>[do not stop"
|
||||
" runtime]</i>\n"
|
||||
)
|
||||
score += 0.3
|
||||
|
||||
api_endpoint = "https://mods.hikariatama.ru/check?hash="
|
||||
sha1 = hashlib.sha1()
|
||||
sha1.update(code.encode("utf-8"))
|
||||
try:
|
||||
check_res = (
|
||||
await utils.run_sync(requests.get, api_endpoint + str(sha1.hexdigest()))
|
||||
).text
|
||||
except Exception:
|
||||
check_res = ""
|
||||
|
||||
if check_res in {"yes", "db"}:
|
||||
comments += (
|
||||
"🔸 <code>{+1.0}</code> <b>Module is verified</b> <i>[no scam]</i>\n"
|
||||
)
|
||||
score += 1.0
|
||||
|
||||
score = round(score, 1)
|
||||
|
||||
score = min(score, 5.0)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("template").format(
|
||||
mod_name,
|
||||
"⭐️" * round(score),
|
||||
score,
|
||||
["Shit", "Bad", "Poor", "Normal", "Ok", "Good"][round(score)],
|
||||
comments,
|
||||
),
|
||||
)
|
||||
234
hikariatama/ftg/rpmod.py
Normal file
234
hikariatama/ftg/rpmod.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/color/480/000000/comedy.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/rpmod.jpg
|
||||
# meta developer: @hikarimods
|
||||
|
||||
import io
|
||||
import json
|
||||
|
||||
import grapheme
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class RPMod(loader.Module):
|
||||
"""RPMod by HikariMods"""
|
||||
|
||||
strings = {
|
||||
"name": "RPMod",
|
||||
"args": "🚫 <b>Incorrect args</b>",
|
||||
"success": "✅ <b>Success</b>",
|
||||
"rp_on": "✅ <b>RPM on</b>",
|
||||
"rp_off": "✅ <b>RPM off</b>",
|
||||
"rplist": "🦊 <b>Current RP commands</b>\n\n{}",
|
||||
"backup_caption": (
|
||||
"🦊 <b>My RP commands. Restore with </b><code>.rprestore</code>"
|
||||
),
|
||||
"no_file": "🚫 <b>Reply to file</b>",
|
||||
"restored": (
|
||||
"✅ <b>RP Commands restored. See them with </b><code>.rplist</code>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Неверные аргументы</b>",
|
||||
"success": "✅ <b>Успешно</b>",
|
||||
"rp_on": "✅ <b>RPM включен</b>",
|
||||
"rp_off": "✅ <b>RPM выключен</b>",
|
||||
"rplist": "🦊 <b>Текущие RP команды</b>\n\n{}",
|
||||
"backup_caption": (
|
||||
"🦊 <b>Мои RP команды. Ты можешь восстановить их используя"
|
||||
" </b><code>.rprestore</code>"
|
||||
),
|
||||
"no_file": "🚫 <b>Ответь на файл</b>",
|
||||
"restored": (
|
||||
"✅ <b>RP команды восстановлены. Их можно посмотреть используя"
|
||||
" </b><code>.rplist</code>"
|
||||
),
|
||||
"_cmd_doc_rp": (
|
||||
"<command> <message> - Добавить RP команду. Если не указано сообщение,"
|
||||
" команда будет удалена"
|
||||
),
|
||||
"_cmd_doc_rptoggle": "Включить\\выключить RP режим в текущем чате",
|
||||
"_cmd_doc_rplist": "Показать RP команды",
|
||||
"_cmd_doc_rpbackup": "Сохранить RP команды в файл",
|
||||
"_cmd_doc_rprestore": "Восстановить RP команды из файла",
|
||||
"_cmd_doc_rpchats": "Показать чаты, где активен режим RP",
|
||||
"_cls_doc": "RPMod от HikariMods",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.rp = self.get(
|
||||
"rp",
|
||||
{
|
||||
"поцеловать": " 💋 поцеловал",
|
||||
"чмок": " ❤️ чмокнул",
|
||||
"обнять": "☺️ обнял",
|
||||
"лизнуть": "👅 лизнул",
|
||||
"напоить": "🥃 напоил",
|
||||
"связать": "⛓ связал",
|
||||
"приковать": "🔗 приковал",
|
||||
"трахнуть": "👉👌 сочно трахнул",
|
||||
"убить": "🔪 убил",
|
||||
"уничтожить": " 💥 низвёл до атомов",
|
||||
"расстрелять": "🔫 расстрелял",
|
||||
"отдаться": "🥵 страстно отдался",
|
||||
"раб": "⛓ забрал в рабство",
|
||||
},
|
||||
)
|
||||
self.chats = self.get("active", [])
|
||||
|
||||
async def rpcmd(self, message: Message):
|
||||
"""<command> <message> - Add RP Command. If message unspecified, remove command"""
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
command = args.split(" ", 1)[0]
|
||||
msg = args.split(" ", 1)[1]
|
||||
except Exception:
|
||||
if not args or command not in self.rp:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
else:
|
||||
del self.rp[command]
|
||||
self.set("rp", self.rp)
|
||||
await utils.answer(message, self.strings("success"))
|
||||
|
||||
return
|
||||
|
||||
self.rp[command] = msg
|
||||
self.set("rp", self.rp)
|
||||
await utils.answer(message, self.strings("success"))
|
||||
|
||||
async def rptogglecmd(self, message: Message):
|
||||
"""Toggle RP Mode in current chat"""
|
||||
cid = str(utils.get_chat_id(message))
|
||||
if cid in self.chats:
|
||||
self.chats.remove(cid)
|
||||
await utils.answer(message, self.strings("rp_off"))
|
||||
else:
|
||||
self.chats += [cid]
|
||||
await utils.answer(message, self.strings("rp_on"))
|
||||
|
||||
self.set("active", self.chats)
|
||||
|
||||
@loader.unrestricted
|
||||
async def rplistcmd(self, message: Message):
|
||||
"""List RP Commands"""
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("rplist").format(
|
||||
"\n".join(
|
||||
[f" ▫️ {command} - {msg}" for command, msg in self.rp.items()]
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
async def rpbackupcmd(self, message: Message):
|
||||
"""Backup RP Commands to file"""
|
||||
file = io.BytesIO(json.dumps(self.rp).encode("utf-8"))
|
||||
file.name = "rp-backup.json"
|
||||
await self._client.send_file(
|
||||
utils.get_chat_id(message),
|
||||
file,
|
||||
caption=self.strings("backup_caption"),
|
||||
)
|
||||
await message.delete()
|
||||
|
||||
async def rprestorecmd(self, message: Message):
|
||||
"""Restore RP Commands from file"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.media:
|
||||
await utils.answer(message, self.strings("no_file"))
|
||||
return
|
||||
|
||||
file = (await self._client.download_file(reply.media, bytes)).decode("utf-8")
|
||||
|
||||
self.rp = json.loads(file)
|
||||
self.set("rp", self.rp)
|
||||
await utils.answer(message, self.strings("restored"))
|
||||
|
||||
async def rpchatscmd(self, message: Message):
|
||||
"""List chats, where RPM is active"""
|
||||
await utils.answer(
|
||||
message,
|
||||
f"🦊 <b>RPM is active in {len(self.chats)} chats:</b>\n\n"
|
||||
+ "\n".join(
|
||||
[
|
||||
" 🇯🇵"
|
||||
f" {utils.escape_html(get_display_name(await self._client.get_entity(int(chat))))}"
|
||||
for chat in self.chats
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
cid = str(utils.get_chat_id(message))
|
||||
try:
|
||||
if (
|
||||
cid not in self.chats
|
||||
or not isinstance(message, Message)
|
||||
or not hasattr(message, "raw_text")
|
||||
or message.raw_text.split(maxsplit=1)[0].lower() not in self.rp
|
||||
):
|
||||
return
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
try:
|
||||
cmd = message.raw_text.split(maxsplit=1)[0].lower()
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
msg = self.rp[cmd]
|
||||
|
||||
entity = None
|
||||
|
||||
try:
|
||||
entity = await self._client.get_entity(
|
||||
message.raw_text.split(maxsplit=2)[1]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
try:
|
||||
reply = await self._client.get_entity(reply.sender_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not reply and not entity:
|
||||
return
|
||||
|
||||
if reply and entity or not reply:
|
||||
reply = entity
|
||||
|
||||
sender = await self._client.get_entity(message.sender_id)
|
||||
|
||||
if utils.emoji_pattern.match(next(grapheme.graphemes(msg))):
|
||||
msg = list(grapheme.graphemes(msg))
|
||||
emoji = msg[0]
|
||||
msg = "".join(msg[1:])
|
||||
else:
|
||||
emoji = "🦊"
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"{emoji} <a"
|
||||
f' href="tg://user?id={sender.id}">{utils.escape_html(sender.first_name)}</a>'
|
||||
f" <b>{utils.escape_html(msg)}</b> <a"
|
||||
f' href="tg://user?id={reply.id}">{utils.escape_html(reply.first_name)}</a>'
|
||||
),
|
||||
)
|
||||
227
hikariatama/ftg/scrolller.py
Normal file
227
hikariatama/ftg/scrolller.py
Normal file
@@ -0,0 +1,227 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/scrolller_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/scrolller.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import functools
|
||||
import random
|
||||
from typing import List, Union
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
|
||||
async def photos(subreddit: str, quantity: int) -> List[str]:
|
||||
"""Loads `quantity` photos from `subreddit` on scrolller.com"""
|
||||
ans = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.scrolller.com/api/v2/graphql",
|
||||
json={
|
||||
"query": (
|
||||
" query SubredditQuery( $url: String! $filter: SubredditPostFilter"
|
||||
" $iterator: String ) { getSubreddit(url: $url) { children("
|
||||
f" limit: {quantity} iterator: $iterator filter: $filter"
|
||||
" disabledHosts: null ) { iterator items {url subredditTitle"
|
||||
" isNsfw mediaSources { url } } } } } "
|
||||
),
|
||||
"variables": {"url": subreddit, "filter": None, "hostsDown": None},
|
||||
"authorization": None,
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
posts = ans["data"]["getSubreddit"]["children"]["items"]
|
||||
return [post["mediaSources"][-1]["url"] for post in posts]
|
||||
|
||||
|
||||
def caption(subreddit: dict) -> str:
|
||||
return (
|
||||
f"{'🔞' if subreddit['isNsfw'] else '👨👩👧'} <b>{utils.escape_html(subreddit['secondaryTitle'])} ({utils.escape_html(subreddit['url'])})</b>\n\n<i>{utils.escape_html(subreddit['description'])}</i>\n\n<i>Enjoy!"
|
||||
f" {utils.ascii_face()}</i>"
|
||||
)
|
||||
|
||||
|
||||
async def search_subreddit(query: str) -> List[dict]:
|
||||
"""Searches for subreddits using `query`"""
|
||||
ans = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.scrolller.com/api/v2/graphql",
|
||||
json={
|
||||
"query": (
|
||||
" query SearchQuery($query: String!, $isNsfw: Boolean) {"
|
||||
" searchSubreddits( query: $query isNsfw: $isNsfw limit: 500 ) {"
|
||||
" __typename url title secondaryTitle description createdAt isNsfw"
|
||||
" subscribers isComplete itemCount videoCount pictureCount"
|
||||
" albumCount isFollowing } } "
|
||||
),
|
||||
"variables": {"query": query, "isNsfw": None},
|
||||
"authorization": None,
|
||||
},
|
||||
)
|
||||
).json()
|
||||
res = ans["data"]["searchSubreddits"]
|
||||
random.shuffle(res)
|
||||
return res[:30]
|
||||
|
||||
|
||||
async def fetch_multiple_subreddits(subreddits: List[str]) -> Union[List[str], bool]:
|
||||
"""Fetches preview from multiple `subreddits`"""
|
||||
args = [f"$url_{i}: String!" for i in range(len(subreddits))]
|
||||
vals = {f"url_{i}": subreddit for i, subreddit in enumerate(subreddits)}
|
||||
funcs = [
|
||||
subreddit.split("/")[-1]
|
||||
+ """: getSubreddit(url: $url_"""
|
||||
+ str(i)
|
||||
+ """) {children(limit: 1 iterator: $iterator filter: $filter disabledHosts: null ) {iterator items {url subredditTitle isNsfw mediaSources { url } } } }"""
|
||||
for i, subreddit in enumerate(subreddits)
|
||||
]
|
||||
|
||||
r = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
"https://api.scrolller.com/api/v2/graphql",
|
||||
json={
|
||||
"query": (
|
||||
"""query SubredditQuery ("""
|
||||
+ "\n".join(args)
|
||||
+ """$filter: SubredditPostFilter $iterator: String ) {"""
|
||||
+ "\n".join(funcs)
|
||||
+ """} """
|
||||
),
|
||||
"variables": {**vals, "filter": None, "hostsDown": None},
|
||||
"authorization": None,
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
try:
|
||||
return [
|
||||
i["children"]["items"][0]["mediaSources"][0]["url"]
|
||||
for i in r["data"].values()
|
||||
]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ScrolllerMod(loader.Module):
|
||||
"""Sends pictures from scrolller.com via inline gallery"""
|
||||
|
||||
strings = {
|
||||
"name": "Scrolller",
|
||||
"sreddit404": "🚫 <b>Subreddit not found</b>",
|
||||
"default_subreddit": "🙂 <b>Set new default subreddit: </b><code>{}</code>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"sreddit404": "🚫 <b>Сабреддит не найден</b>",
|
||||
"default_subreddit": (
|
||||
"🙂 <b>Установил новый сабреддит по умолчанию: </b><code>{}</code>"
|
||||
),
|
||||
"_cmd_doc_gallery": (
|
||||
"<сабреддит> [-n <количество | 1 по умолчанию>] - Отправляет случайную 18+"
|
||||
" картинку"
|
||||
),
|
||||
"_cmd_doc_gallerycat": "<сабреддит> - Установить новый сабреддит по умолчанию",
|
||||
"_cls_doc": "Отправляет изображения с scrolller.com в виде инлайн галереи",
|
||||
}
|
||||
|
||||
async def gallerycmd(self, message: Message):
|
||||
"""<subreddit | default> - Send inline gallery with photos from subreddit"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if reply:
|
||||
for_ = (
|
||||
"<b>❤️ Special for"
|
||||
f" {utils.escape_html(get_display_name(reply.sender))}</b>"
|
||||
)
|
||||
else:
|
||||
for_ = ""
|
||||
|
||||
if not args:
|
||||
args = self.get("default_subreddit", "cat")
|
||||
|
||||
subreddit = f"/r/{args}"
|
||||
|
||||
ans = await utils.run_sync(
|
||||
requests.get, f"https://api.scrolller.com{subreddit}"
|
||||
)
|
||||
if ans.status_code != 200:
|
||||
await utils.answer(message, self.strings("sreddit404", message))
|
||||
return
|
||||
|
||||
await self.inline.gallery(
|
||||
message=message,
|
||||
next_handler=functools.partial(photos, subreddit=subreddit, quantity=15),
|
||||
caption=lambda: f"<i>Enjoy this {subreddit} photos <3\n{utils.ascii_face()}</i>\n\n{for_}",
|
||||
always_allow=[reply.sender_id] if reply else [],
|
||||
)
|
||||
|
||||
async def gallerycatcmd(self, message: Message):
|
||||
"""<subreddit> - Set new default subreddit"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
args = "cat"
|
||||
|
||||
ans = await utils.run_sync(requests.get, f"https://api.scrolller.com/r/{args}")
|
||||
if ans.status_code != 200:
|
||||
await utils.answer(message, self.strings("sreddit404", message))
|
||||
return
|
||||
|
||||
self.set("default_subreddit", args)
|
||||
await utils.answer(
|
||||
message, self.strings("default_subreddit", message).format(args)
|
||||
)
|
||||
|
||||
async def gallery_inline_handler(self, query: InlineQuery):
|
||||
"""
|
||||
Search for Scrolller subreddits
|
||||
"""
|
||||
if not query.args:
|
||||
query.args = self.get("default_subreddit", "cat")
|
||||
|
||||
subreddits = await search_subreddit(query.args)
|
||||
thumbs = await fetch_multiple_subreddits([i["url"] for i in subreddits])
|
||||
|
||||
if not thumbs or not subreddits:
|
||||
await query.e404()
|
||||
return
|
||||
|
||||
await self.inline.query_gallery(
|
||||
query,
|
||||
[
|
||||
{
|
||||
"title": (
|
||||
f"{'🔞' if subreddit['isNsfw'] else '👨👩👧'} {subreddit['secondaryTitle']} ({subreddit['url']})"
|
||||
),
|
||||
"description": subreddit["description"],
|
||||
"next_handler": functools.partial(
|
||||
photos,
|
||||
subreddit=subreddit["url"],
|
||||
quantity=15,
|
||||
),
|
||||
"thumb_handler": [thumbs[i]],
|
||||
"caption": functools.partial(
|
||||
caption,
|
||||
subreddit=subreddit,
|
||||
),
|
||||
}
|
||||
for i, subreddit in enumerate(subreddits)
|
||||
],
|
||||
)
|
||||
143
hikariatama/ftg/secret_chat.py
Normal file
143
hikariatama/ftg/secret_chat.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/secret_chat_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/secret_chat.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: telethon_secret_chat
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import io
|
||||
import logging
|
||||
|
||||
from telethon.events import NewMessage
|
||||
from telethon.tl.functions.channels import CreateChannelRequest
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
from telethon_secret_chat import SecretChatManager
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SecretChatMod(loader.Module):
|
||||
"""De-secrets secret chats"""
|
||||
|
||||
strings = {"name": "SecretChat", "state": "👀 <b>SecretChat is now {}</b>"}
|
||||
|
||||
def _get_chat_id(self, chat) -> int:
|
||||
cid = [chat.admin_id] + [chat.participant_id]
|
||||
cid.remove(self._tg_id)
|
||||
cid = cid[0]
|
||||
return cid
|
||||
|
||||
async def _create_chat(self, chat):
|
||||
cid = self._get_chat_id(chat)
|
||||
|
||||
decrypted_chat = None
|
||||
|
||||
async for d in self._client.iter_dialogs():
|
||||
if d.title == f"secret-chat-with-{cid}":
|
||||
decrypted_chat = d.entity
|
||||
|
||||
if not decrypted_chat:
|
||||
decrypted_chat = (
|
||||
await self._client(
|
||||
CreateChannelRequest(
|
||||
f"secret-chat-with-{cid}",
|
||||
"SecretChat conversation with {}",
|
||||
megagroup=True,
|
||||
)
|
||||
)
|
||||
).chats[0]
|
||||
|
||||
@self._client.on(NewMessage(chats=[decrypted_chat.id]))
|
||||
async def secret_chat_processor(event):
|
||||
"""secret_chat_processor"""
|
||||
await self._manager.send_secret_message(chat.id, event.text)
|
||||
await event.edit(f"<< {event.text}")
|
||||
|
||||
self._chats[cid] = decrypted_chat
|
||||
|
||||
self._manager = SecretChatManager(
|
||||
self._client,
|
||||
auto_accept=True,
|
||||
new_chat_created=self._new_chat,
|
||||
)
|
||||
self._manager.add_secret_event_handler(func=self._replier)
|
||||
self._chats = {}
|
||||
self._secret_chats = {}
|
||||
|
||||
async def _replier(self, event):
|
||||
if not self.get("state", False):
|
||||
return
|
||||
|
||||
e = event.decrypted_event
|
||||
user = self._secret_chats[event.message.chat_id]
|
||||
|
||||
if e.message:
|
||||
await self._client.send_message(self._chats[user], f">> {e.message}")
|
||||
|
||||
if e.file:
|
||||
try:
|
||||
m = await self._manager.download_secret_media(e)
|
||||
if m:
|
||||
attrs = {}
|
||||
f = io.BytesIO(m)
|
||||
if "/" in (getattr(e.media, "mime_type", "") or ""):
|
||||
f.name = "secret_media." + e.media.mime_type.split("/")[-1]
|
||||
|
||||
if getattr(e.media, "mime_type", None) == "audio/ogg":
|
||||
attrs["voice_note"] = True
|
||||
|
||||
if getattr(e.media, "caption", False):
|
||||
attrs["caption"] = e.media.caption
|
||||
|
||||
if "caption" not in attrs:
|
||||
attrs["caption"] = ""
|
||||
|
||||
attrs["caption"] = ">> " + attrs["caption"]
|
||||
|
||||
await self._client.send_file(self._chats[user], f, **attrs)
|
||||
except Exception:
|
||||
await self._client.send_message(self._chats[user], ">>> [File]")
|
||||
|
||||
async def _new_chat(self, chat, _: bool):
|
||||
if not self.get("state", False):
|
||||
return
|
||||
|
||||
await self._create_chat(chat)
|
||||
user = self._get_chat_id(chat)
|
||||
self._secret_chats[chat.id] = user
|
||||
u = await self._client.get_entity(user)
|
||||
await self._client.send_message(
|
||||
self._chats[user],
|
||||
(
|
||||
"㊙️ <b>New secret chat with <a"
|
||||
f' href="tg://user?id={user}">{get_display_name(u)}</a> started</b>'
|
||||
),
|
||||
)
|
||||
|
||||
async def on_unload(self):
|
||||
self._client.remove_event_handler(self._manager._secret_chat_event_loop)
|
||||
del self._manager
|
||||
for handler in self._client.list_event_handlers():
|
||||
if handler[0].__doc__ == "secret_chat_processor":
|
||||
self._client.remove_event_handler(handler)
|
||||
|
||||
async def desecretcmd(self, message: Message):
|
||||
"""Toggle secret chat handler"""
|
||||
current = self.get("state", False)
|
||||
new = not current
|
||||
self.set("state", new)
|
||||
await utils.answer(
|
||||
message, self.strings("state").format("on" if new else "off")
|
||||
)
|
||||
132
hikariatama/ftg/serverinfo.py
Normal file
132
hikariatama/ftg/serverinfo.py
Normal file
@@ -0,0 +1,132 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/serverinfo_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/serverinfo.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: psutil
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import psutil
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
def bytes_to_megabytes(b: int) -> int:
|
||||
return round(b / 1024 / 1024, 1)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class serverInfoMod(loader.Module):
|
||||
"""Show server info"""
|
||||
|
||||
strings = {
|
||||
"name": "ServerInfo",
|
||||
"loading": (
|
||||
"<emoji document_id=5271897426117009417>🚘</emoji> <b>Loading server"
|
||||
" info...</b>"
|
||||
),
|
||||
"servinfo": (
|
||||
"<emoji document_id=5271897426117009417>🚘</emoji> <b>Server"
|
||||
" Info</b>:\n\n<emoji document_id=5172854840321114816>💻</emoji> <b>CPU:"
|
||||
" {cpu} Cores {cpu_load}%</b>\n<emoji"
|
||||
" document_id=5174693704799093859>💻</emoji> <b>RAM: {ram} /"
|
||||
" {ram_load_mb}MB"
|
||||
" ({ram_load}%)</b>\n\n<emoji document_id=5172474181664637769>💻</emoji>"
|
||||
" <b>Kernel: {kernel}</b>\n{arch_emoji} <b>Arch: {arch}</b>\n<emoji"
|
||||
" document_id=5172622400986022463>💻</emoji> <b>OS: {os}</b>\n\n<emoji"
|
||||
" document_id=5172839378438849164>💻</emoji> <b>Python: {python}</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"loading": (
|
||||
"<emoji document_id=5271897426117009417>🚘</emoji> <b>Загрузка информации о"
|
||||
" сервере...</b>"
|
||||
),
|
||||
"servinfo": (
|
||||
"<emoji document_id=5271897426117009417>🚘</emoji> <b>Информация о сервере"
|
||||
"</b>:\n\n<emoji document_id=5172854840321114816>💻</emoji> <b>CPU:"
|
||||
" {cpu} ядер(-ро) {cpu_load}%</b>\n<emoji"
|
||||
" document_id=5174693704799093859>💻</emoji> <b>RAM: {ram} /"
|
||||
" {ram_load_mb}MB"
|
||||
" ({ram_load}%)</b>\n\n<emoji document_id=5172474181664637769>💻</emoji>"
|
||||
" <b>Kernel: {kernel}</b>\n{arch_emoji} <b>Arch: {arch}</b>\n<emoji"
|
||||
" document_id=5172622400986022463>💻</emoji> <b>OS: {os}</b>\n\n<emoji"
|
||||
" document_id=5172839378438849164>💻</emoji> <b>Python: {python}</b>"
|
||||
),
|
||||
"_cls_doc": "Показывает информацию о сервере",
|
||||
}
|
||||
|
||||
@loader.command(ru_doc="Показать информацию о сервере")
|
||||
async def serverinfo(self, message: Message):
|
||||
"""Show server info"""
|
||||
message = await utils.answer(message, self.strings("loading"))
|
||||
|
||||
inf = {
|
||||
"cpu": "n/a",
|
||||
"cpu_load": "n/a",
|
||||
"ram": "n/a",
|
||||
"ram_load_mb": "n/a",
|
||||
"ram_load": "n/a",
|
||||
"kernel": "n/a",
|
||||
"arch_emoji": "n/a",
|
||||
"arch": "n/a",
|
||||
"os": "n/a",
|
||||
}
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["cpu"] = psutil.cpu_count(logical=True)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["cpu_load"] = psutil.cpu_percent()
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["ram"] = bytes_to_megabytes(
|
||||
psutil.virtual_memory().total - psutil.virtual_memory().available
|
||||
)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["ram_load_mb"] = bytes_to_megabytes(psutil.virtual_memory().total)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["ram_load"] = psutil.virtual_memory().percent
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["kernel"] = utils.escape_html(platform.release())
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["arch"] = utils.escape_html(platform.architecture()[0])
|
||||
|
||||
inf["arch_emoji"] = (
|
||||
"<emoji document_id=5172881503478088537>💻</emoji>"
|
||||
if "64" in (inf.get("arch", "") or "")
|
||||
else "<emoji document_id=5174703196676817427>💻</emoji>"
|
||||
)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
system = os.popen("cat /etc/*release").read()
|
||||
b = system.find('DISTRIB_DESCRIPTION="') + 21
|
||||
system = system[b : system.find('"', b)]
|
||||
inf["os"] = utils.escape_html(system)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
inf["python"] = (
|
||||
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
||||
)
|
||||
|
||||
await utils.answer(message, self.strings("servinfo").format(**inf))
|
||||
575
hikariatama/ftg/shikimori.py
Normal file
575
hikariatama/ftg/shikimori.py
Normal file
@@ -0,0 +1,575 @@
|
||||
# scope: hikka_min 1.2.10
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://i.imgur.com/MTkqvXX.jpeg
|
||||
# meta banner: https://mods.hikariatama.ru/badges/shikimori.jpg
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# meta developer: @hikarimods
|
||||
|
||||
import logging
|
||||
import time
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall, InlineQuery
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ShikimoriMod(loader.Module):
|
||||
"""Shikimori API Wrapper"""
|
||||
|
||||
strings = {
|
||||
"name": "Shikimori",
|
||||
"authorize": "🔓 Authorize",
|
||||
"code": "✍️ Code",
|
||||
"code_input": "✍️ Redirect url after auth",
|
||||
"auth": (
|
||||
'🔓 <b>Shikimori authorization:</b>\n\n1. Click "🔓 Authorize"\n2. Click'
|
||||
' "Allow"\n3. Copy redirect url, and enter it in "✍️ Code"'
|
||||
),
|
||||
"my_animes": (
|
||||
"🐙 <b>My humble anime <a"
|
||||
' href="https://shikimori.one/{}/list/anime/order-by/name">list</a>:</b>\n\n{}'
|
||||
),
|
||||
"no_args": "🚫 <b>No arguments specified</b>",
|
||||
"added": "❤️ <b>Anime {} added to planned</b>",
|
||||
"auth_successful": "👍 <b>Authorized! Check module help for new commands</b>",
|
||||
"planned": "🕐 Planned",
|
||||
"watching": "🎬 Watching",
|
||||
"rewatching": "🔄 Re-watching",
|
||||
"completed": "✅ Completed",
|
||||
"on_hold": "🗓 Holded",
|
||||
"dropped": "🚫 Dropped",
|
||||
"interact": (
|
||||
'📼 <b>Interacting with <a href="https://shikimori.one{}">{}</a></b>'
|
||||
),
|
||||
"state_changed": "Anime state changed to {}",
|
||||
"delete": "🗑 Delete",
|
||||
"no_status": "🔘 Change status",
|
||||
"error": "🚫 Error",
|
||||
"success": "✅ Success",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"authorize": "🔓 Авторизоваться",
|
||||
"code": "✍️ Код",
|
||||
"code_input": "✍️ Ссылка, на которую тебя перебросило после авторизации",
|
||||
"auth": (
|
||||
'🔓 <b>Авторизация на Shikimori:</b>\n\n1. Нажми "🔓 Авторизоваться"\n2.'
|
||||
' Нажми "Разрешить"\n3. Скопируй ссылку, на которую тебя перекинет, и введи'
|
||||
' ее в "✍️ Код"'
|
||||
),
|
||||
"my_animes": (
|
||||
"🐙 <b>Мой скромный <a"
|
||||
' href="https://shikimori.one/{}/list/anime/order-by/name">список</a>'
|
||||
" аниме:</b>\n\n{}"
|
||||
),
|
||||
"no_args": "🚫 <b>Аргументы не указаны</b>",
|
||||
"added": "❤️ <b>Аниме {} добавлено в отложенные</b>",
|
||||
"auth_successful": (
|
||||
"👍 <b>Авторизован! Смотри помощь модуля, там новые команды</b>"
|
||||
),
|
||||
"planned": "🕐 Запланировано",
|
||||
"watching": "🎬 Смотрю",
|
||||
"rewatching": "🔄 Пересматриваю",
|
||||
"completed": "✅ Просмотрено",
|
||||
"on_hold": "🗓 Отложено",
|
||||
"dropped": "🚫 Заброшено",
|
||||
"interact": (
|
||||
'📼 <b>Взаимодействие с <a href="https://shikimori.one{}">{}</a></b>'
|
||||
),
|
||||
"state_changed": "Состояние аниме изменено на {}",
|
||||
"delete": "🗑 Удалить",
|
||||
"no_status": "🔘 Изменить статус",
|
||||
"error": "🚫 Ошибка",
|
||||
"success": "✅ Успешно",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._shiki_me = None # will be set later
|
||||
self._rates_cache = {}
|
||||
|
||||
async def _search(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
no_retry: bool = False,
|
||||
) -> dict:
|
||||
result = await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://shikimori.one/api/animes?search={quote_plus(query)}&limit={limit}",
|
||||
headers={
|
||||
"User-Agent": "Hikka",
|
||||
"Authorization": f"Bearer {self.get('token')}",
|
||||
},
|
||||
)
|
||||
|
||||
if result.status_code == 401:
|
||||
if no_retry:
|
||||
logger.error("Can't refresh token")
|
||||
return {}
|
||||
|
||||
await self._refresh_token()
|
||||
return await self._search(query, limit, no_retry=True)
|
||||
|
||||
result = result.json()
|
||||
rates = {
|
||||
i["anime"]["id"]: i
|
||||
for i in await self._get_rates()
|
||||
if "anime" in i and "id" in i["anime"]
|
||||
}
|
||||
|
||||
for i, item in enumerate(result):
|
||||
if item["id"] in rates:
|
||||
result[i]["status"] = rates[item["id"]]["status"]
|
||||
result[i]["episodes_seen"] = rates[item["id"]]["episodes"]
|
||||
else:
|
||||
if "status" in result[i]:
|
||||
del result[i]["status"]
|
||||
|
||||
return result
|
||||
|
||||
def _get_anime_message(self, anime: dict) -> str:
|
||||
return (
|
||||
f'🐱 <b>{utils.escape_html(anime["russian"])}</b>\n'
|
||||
f'🌍 <b>URL:</b> https://shikimori.one{anime["url"]}\n'
|
||||
f'🧮 <b>Type:</b> {anime["kind"]}\n'
|
||||
f'📺 <b>Episodes: </b>{anime.get("episodes_seen", 0)}/{anime["episodes"]}\n'
|
||||
f'📅 <b>Released: </b>{anime["released_on"]}'
|
||||
)
|
||||
|
||||
def _get_anime_markup(self, anime: dict) -> str:
|
||||
return [
|
||||
[
|
||||
{
|
||||
"text": self.strings(anime.get("status", "no_status")),
|
||||
"callback": self._anime_interact,
|
||||
"args": (anime,),
|
||||
}
|
||||
],
|
||||
] + (
|
||||
[
|
||||
[
|
||||
{
|
||||
"text": "➖ Episode",
|
||||
"callback": self._change_episodes_quantity,
|
||||
"args": (anime, -1),
|
||||
},
|
||||
{
|
||||
"text": "➕ Episode",
|
||||
"callback": self._change_episodes_quantity,
|
||||
"args": (anime, 1),
|
||||
},
|
||||
]
|
||||
]
|
||||
if anime.get("status", "no_status") not in {"completed", "no_status"}
|
||||
else []
|
||||
)
|
||||
|
||||
async def anime_inline_handler(self, query: InlineQuery):
|
||||
"""<query> - Search Shikimori"""
|
||||
if not query.args:
|
||||
return await query.e400()
|
||||
|
||||
result = []
|
||||
for anime in await self._search(query.args):
|
||||
result += [
|
||||
{
|
||||
"title": anime["name"],
|
||||
"thumb": f'https://shikimori.one/{anime["image"]["preview"]}',
|
||||
"message": self._get_anime_message(anime),
|
||||
"reply_markup": self._get_anime_markup(anime),
|
||||
}
|
||||
]
|
||||
|
||||
return result
|
||||
|
||||
async def shikicmd(self, message: Message):
|
||||
"""<anime> - Search anime and return best match as form"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
anime = (await self._search(args, limit=1))[0]
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self._get_anime_message(anime),
|
||||
reply_markup=self._get_anime_markup(anime),
|
||||
photo=f'https://shikimori.one/{anime["image"]["original"]}',
|
||||
)
|
||||
|
||||
async def _change_episodes_quantity(
|
||||
self,
|
||||
call: InlineCall,
|
||||
anime: dict,
|
||||
diff: int,
|
||||
no_retry: bool = False,
|
||||
):
|
||||
if not self._shiki_me:
|
||||
self._shiki_me = await self._get_me()
|
||||
|
||||
self._rates_cache = {}
|
||||
await self._get_rates()
|
||||
|
||||
rate = None
|
||||
|
||||
for i, local_rate in enumerate(self._rates_cache["rates"]):
|
||||
if local_rate["anime"]["id"] == anime["id"]:
|
||||
rate = local_rate
|
||||
self._rates_cache["rates"][i]["episodes"] += diff
|
||||
|
||||
if not rate:
|
||||
logger.error("Can't find rate by anime id")
|
||||
return False
|
||||
|
||||
payload = {
|
||||
"user_rate[chapters]": rate.get("chapters", 0),
|
||||
"user_rate[episodes]": rate.get("episodes", 0) + diff,
|
||||
"user_rate[rewatches]": rate.get("rewatches", 0),
|
||||
"user_rate[volumes]": rate.get("volumes", 0),
|
||||
"user_rate[score]": rate.get("score", 0),
|
||||
"user_rate[status]": rate.get("status", None),
|
||||
"user_rate[text]": rate.get("text", None),
|
||||
}
|
||||
|
||||
result = await utils.run_sync(
|
||||
requests.put,
|
||||
f"https://shikimori.one/api/v2/user_rates/{rate['id']}",
|
||||
headers={
|
||||
"User-Agent": "Hikka",
|
||||
"Authorization": f"Bearer {self.get('token')}",
|
||||
},
|
||||
data=payload,
|
||||
)
|
||||
|
||||
if result.status_code == 401:
|
||||
if no_retry:
|
||||
logger.error("Can't refresh token")
|
||||
return {}
|
||||
|
||||
await self._refresh_token()
|
||||
return await self._change_episodes_quantity(
|
||||
call, anime, diff, no_retry=True
|
||||
)
|
||||
|
||||
if not str(result.status_code).startswith("2"):
|
||||
logger.error(result.text)
|
||||
await call.answer(self.strings("error"))
|
||||
return
|
||||
|
||||
if "episodes_seen" in anime:
|
||||
anime["episodes_seen"] += diff
|
||||
else:
|
||||
anime["episodes_seen"] = 0 if diff < 0 else 1
|
||||
|
||||
await call.answer(self.strings("success"))
|
||||
await call.edit(self._get_anime_message(anime), self._get_anime_markup(anime))
|
||||
|
||||
async def _anime_interact(
|
||||
self,
|
||||
call: InlineCall,
|
||||
anime: dict,
|
||||
):
|
||||
await call.edit(
|
||||
self.strings("interact").format(
|
||||
anime["url"],
|
||||
utils.escape_html(anime["russian"]),
|
||||
),
|
||||
reply_markup=utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": self.strings(status),
|
||||
"callback": self._change_anime_state,
|
||||
"args": (anime, status),
|
||||
}
|
||||
for status in {
|
||||
"planned",
|
||||
"watching",
|
||||
"rewatching",
|
||||
"completed",
|
||||
"on_hold",
|
||||
"dropped",
|
||||
}
|
||||
],
|
||||
2,
|
||||
)
|
||||
+ [
|
||||
[
|
||||
{
|
||||
"text": self.strings("delete"),
|
||||
"callback": self._delete_anime_rate,
|
||||
"args": (anime,),
|
||||
}
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
async def _change_anime_state(self, call: InlineCall, anime: dict, state: str):
|
||||
await self._change_anime_state_api(anime["id"], state)
|
||||
|
||||
# We can do this locally to prevent API Flood
|
||||
for i, item in enumerate(self._rates_cache["rates"]):
|
||||
if item["anime"]["id"] == anime["id"]:
|
||||
self._rates_cache["rates"][i]["status"] = state
|
||||
|
||||
anime["status"] = state
|
||||
|
||||
await call.answer(self.strings("state_changed").format(self.strings(state)))
|
||||
await call.edit(self._get_anime_message(anime), self._get_anime_markup(anime))
|
||||
|
||||
async def _delete_anime_rate(self, call: InlineCall, anime: dict):
|
||||
await self._delete_anime_rate_api(anime["id"])
|
||||
|
||||
# We can do this locally to prevent API Flood
|
||||
for i, item in enumerate(self._rates_cache["rates"]):
|
||||
if item["anime"]["id"] == anime["id"]:
|
||||
del self._rates_cache["rates"][i]
|
||||
|
||||
del anime["status"]
|
||||
|
||||
await call.answer(self.strings("state_changed").format(self.strings("delete")))
|
||||
await call.edit(self._get_anime_message(anime), self._get_anime_markup(anime))
|
||||
|
||||
async def shikiauthcmd(self, message: Message):
|
||||
"""Authorize on Shikimori.one"""
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("auth"),
|
||||
reply_markup=[
|
||||
{
|
||||
"text": self.strings("authorize"),
|
||||
"url": r"https://shikimori.one/oauth/authorize?client_id=-wQ_BBnF3GOvhRi6Z6pS60sYzY5ge7Y92aBtCEYSbgc&redirect_uri=https%3A%2F%2Fmods.hikariatama.ru&response_type=code&scope=user_rates",
|
||||
},
|
||||
{
|
||||
"text": self.strings("code"),
|
||||
"input": self.strings("code_input"),
|
||||
"handler": self._proceed_auth,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
async def _request_token(self, code: str):
|
||||
result = (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://shikimori.one/oauth/token",
|
||||
headers={"User-Agent": "Hikka"},
|
||||
data={
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "-wQ_BBnF3GOvhRi6Z6pS60sYzY5ge7Y92aBtCEYSbgc",
|
||||
"client_secret": "mRESsAiJxzuOPMCkrgRvbdnMfLycZAAt_YDgD4hMDyA",
|
||||
"code": code,
|
||||
"redirect_uri": "https://mods.hikariatama.ru",
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
self.set("token", result["access_token"])
|
||||
self.set("refresh_token", result["refresh_token"])
|
||||
|
||||
async def _refresh_token(self):
|
||||
result = (
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://shikimori.one/oauth/token",
|
||||
headers={"User-Agent": "Hikka"},
|
||||
data={
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": "-wQ_BBnF3GOvhRi6Z6pS60sYzY5ge7Y92aBtCEYSbgc",
|
||||
"client_secret": "mRESsAiJxzuOPMCkrgRvbdnMfLycZAAt_YDgD4hMDyA",
|
||||
"refresh_token": self.get("refresh_token"),
|
||||
},
|
||||
)
|
||||
).json()
|
||||
self.set("token", result["access_token"])
|
||||
self.set("refresh_token", result["refresh_token"])
|
||||
|
||||
async def _change_anime_state_api(
|
||||
self,
|
||||
uid: int,
|
||||
state: str,
|
||||
kind: str = "Anime",
|
||||
no_retry: bool = False,
|
||||
) -> bool:
|
||||
if not self._shiki_me:
|
||||
self._shiki_me = await self._get_me()
|
||||
|
||||
payload = {
|
||||
"user_rate[status]": state,
|
||||
"user_rate[target_id]": uid,
|
||||
"user_rate[target_type]": kind,
|
||||
"user_rate[user_id]": self._shiki_me["id"],
|
||||
"user_rate[score]": 0,
|
||||
}
|
||||
|
||||
result = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://shikimori.one/api/v2/user_rates",
|
||||
headers={
|
||||
"User-Agent": "Hikka",
|
||||
"Authorization": f"Bearer {self.get('token')}",
|
||||
},
|
||||
data=payload,
|
||||
)
|
||||
|
||||
if result.status_code == 401:
|
||||
if no_retry:
|
||||
logger.error("Can't refresh token")
|
||||
return {}
|
||||
|
||||
await self._refresh_token()
|
||||
return await self._change_anime_state_api(uid, state, kind, no_retry=True)
|
||||
|
||||
if not str(result.status_code).startswith("2"):
|
||||
logger.error(result.text)
|
||||
|
||||
return str(result.status_code).startswith("2")
|
||||
|
||||
async def _delete_anime_rate_api(
|
||||
self,
|
||||
uid: int,
|
||||
no_retry: bool = False,
|
||||
) -> bool:
|
||||
if not self._shiki_me:
|
||||
self._shiki_me = await self._get_me()
|
||||
|
||||
self._rates_cache = {}
|
||||
await self._get_rates()
|
||||
|
||||
rate_id = None
|
||||
|
||||
for rate in self._rates_cache["rates"]:
|
||||
if rate["anime"]["id"] == uid:
|
||||
rate_id = rate["id"]
|
||||
|
||||
if not rate_id:
|
||||
logger.error("Can't find rate by anime id")
|
||||
return False
|
||||
|
||||
result = await utils.run_sync(
|
||||
requests.delete,
|
||||
f"https://shikimori.one/api/v2/user_rates/{rate_id}",
|
||||
headers={
|
||||
"User-Agent": "Hikka",
|
||||
"Authorization": f"Bearer {self.get('token')}",
|
||||
},
|
||||
)
|
||||
|
||||
if result.status_code == 401:
|
||||
if no_retry:
|
||||
logger.error("Can't refresh token")
|
||||
return {}
|
||||
|
||||
await self._refresh_token()
|
||||
return await self._delete_anime_rate_api(uid, no_retry=True)
|
||||
|
||||
if not str(result.status_code).startswith("2"):
|
||||
logger.error(result.text)
|
||||
logger.error(result.status_code)
|
||||
|
||||
return str(result.status_code).startswith("2")
|
||||
|
||||
async def _get_me(self, no_retry: bool = False) -> dict:
|
||||
result = await utils.run_sync(
|
||||
requests.get,
|
||||
"https://shikimori.one/api/users/whoami",
|
||||
headers={
|
||||
"User-Agent": "Hikka",
|
||||
"Authorization": f"Bearer {self.get('token')}",
|
||||
},
|
||||
)
|
||||
|
||||
if result.status_code == 401:
|
||||
if no_retry:
|
||||
logger.error("Can't refresh token")
|
||||
return {}
|
||||
|
||||
await self._refresh_token()
|
||||
return await self._get_me(no_retry=True)
|
||||
|
||||
return result.json()
|
||||
|
||||
async def myshikicmd(self, message: Message):
|
||||
"""Show watched animes from Shikimori.one"""
|
||||
rates = await self._get_rates() # do this early so the self._shiki_me is set
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("my_animes").format(
|
||||
self._shiki_me["nickname"],
|
||||
"\n".join(
|
||||
[
|
||||
f"<a href=\"https://shikimori.one{rate['anime']['url']}\">▫️</a>"
|
||||
f" <i>{utils.escape_html(rate['anime'].get('russian', rate['anime']['name']))}</i>"
|
||||
for rate in rates
|
||||
if rate["status"] == "completed"
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
async def aniaddcmd(self, message: Message):
|
||||
"""<name> - Add best search match to the list of planned animes"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
anime = (await self._search(args, 1))[0]
|
||||
await self._change_anime_state_api(anime["id"], "planned")
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("added").format(utils.escape_html(anime["russian"])),
|
||||
)
|
||||
|
||||
async def _get_rates(self, no_retry: bool = False) -> list:
|
||||
if self._rates_cache and self._rates_cache["timeout"] > time.time():
|
||||
return self._rates_cache["rates"]
|
||||
|
||||
if not self._shiki_me:
|
||||
self._shiki_me = await self._get_me()
|
||||
|
||||
result = await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://shikimori.one/api/users/{self._shiki_me['id']}/anime_rates?limit=5000",
|
||||
headers={
|
||||
"User-Agent": "Hikka",
|
||||
"Authorization": f"Bearer {self.get('token')}",
|
||||
},
|
||||
)
|
||||
|
||||
if result.status_code == 401:
|
||||
if no_retry:
|
||||
logger.error("Can't refresh token")
|
||||
return {}
|
||||
|
||||
await self._refresh_token()
|
||||
return await self._get_rates(no_retry=True)
|
||||
|
||||
self._rates_cache = {"timeout": time.time() + 5 * 60, "rates": result.json()}
|
||||
|
||||
return result.json()
|
||||
|
||||
async def _proceed_auth(self, call: InlineCall, query: str):
|
||||
try:
|
||||
code = query.split("?code=")[1]
|
||||
except Exception:
|
||||
return
|
||||
|
||||
await self._request_token(code)
|
||||
await call.edit(self.strings("auth_successful"))
|
||||
426
hikariatama/ftg/silent_tags.py
Normal file
426
hikariatama/ftg/silent_tags.py
Normal file
@@ -0,0 +1,426 @@
|
||||
__version__ = (2, 0, 3)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/silent_tags_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/silent_tags.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.5.2
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from telethon.tl.functions.contacts import GetBlockedRequest
|
||||
from telethon.tl.types import Channel, Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SilentTagsMod(loader.Module):
|
||||
"""Mutes tags and logs them"""
|
||||
|
||||
strings = {
|
||||
"name": "SilentTags",
|
||||
"tagged": (
|
||||
'<b>🤫 You were tagged in <a href="{}">{}</a> by <a'
|
||||
' href="tg://openmessage?user_id={}">{}</a></b>\n<code>Message:</code>\n<code>{}</code>\n<b>Link:'
|
||||
' <a href="https://t.me/c/{}/{}">click</a></b>'
|
||||
),
|
||||
"tag_mentioned": "<b>🤫 Silent Tags are active</b>",
|
||||
"stags_status": "<b>🤫 Silent Tags are {}</b>",
|
||||
"_cfg_doc_silent_users": (
|
||||
"Do not send notifications about tags from users with ids listed"
|
||||
),
|
||||
"_cfg_doc_silent_chats": (
|
||||
"Do not send notifications about tags from chats with ids listed"
|
||||
),
|
||||
"_cfg_doc_silent_bots": "Do not send notifications about tags from bots",
|
||||
"_cfg_doc_silent_blocked": (
|
||||
"Do not send notifications about tags from blocked users"
|
||||
),
|
||||
"_cfg_doc_ignore_users": "Disable SilentTags for users with ids listed",
|
||||
"_cfg_doc_ignore_chats": "Disable SilentTags for chats with ids listed",
|
||||
"_cfg_doc_ignore_bots": "Disable SilentTags for bots",
|
||||
"_cfg_doc_ignore_blocked": "Disable SilentTags for blocked users",
|
||||
"_cfg_doc_silent": "Do not send notifications about Silent Tags being active",
|
||||
"_cfg_doc_use_whitelist": "Convert all Series-like options to whitelist",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"tag_mentioned": "<b>🤫 Silent Tags включены</b>",
|
||||
"stags_status": "<b>🤫 Silent Tags {}</b>",
|
||||
"_cmd_doc_stags": "<on\\off> - Включить\\выключить уведомления о тегах",
|
||||
"_cls_doc": "Отключает уведомления о тегах",
|
||||
"_cfg_doc_ignore_users": (
|
||||
"Отключить SilentTags для пользователей с перечисленными ID"
|
||||
),
|
||||
"_cfg_doc_ignore_chats": "Отключить SilentTags в чатах с перечисленными ID",
|
||||
"_cfg_doc_ignore_bots": "Отключить SilentTags для ботов",
|
||||
"_cfg_doc_ignore_blocked": (
|
||||
"Отключить SilentTags для заблокированных пользователей"
|
||||
),
|
||||
"_cfg_doc_silent_users": (
|
||||
"Не отправлять сообщения о тегах от пользователей с перечисленными ID"
|
||||
),
|
||||
"_cfg_doc_silent_chats": (
|
||||
"Не отправлять сообщения о тегах в чатах с перечисленными ID"
|
||||
),
|
||||
"_cfg_doc_silent_bots": "Не отправлять сообщения о тегах от ботов",
|
||||
"_cfg_doc_silent_blocked": (
|
||||
"Не отправлять сообщения о тегах от заблокированных пользователей"
|
||||
),
|
||||
"_cfg_doc_silent": "Не отправлять сообщение о том, что активны Silent Tags",
|
||||
"_cfg_doc_use_whitelist": (
|
||||
"Преобразовать все списковые настройки в белый список"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"tag_mentioned": "<b>🤫 Stille Tags sind aktiviert</b>",
|
||||
"stags_status": "<b>🤫 Stille Tags sind {}</b>",
|
||||
"_cmd_doc_stags": "<on\\off> - Stille Tags aktivieren\\deaktivieren",
|
||||
"_cls_doc": "Deaktiviert Stille Tags",
|
||||
"_cfg_doc_ignore_users": (
|
||||
"Deaktiviert Stille Tags für Nutzer mit den folgenden IDs"
|
||||
),
|
||||
"_cfg_doc_ignore_chats": (
|
||||
"Deaktiviert Stille Tags in Chats mit den folgenden IDs"
|
||||
),
|
||||
"_cfg_doc_ignore_bots": "Deaktiviert Stille Tags für Bots",
|
||||
"_cfg_doc_ignore_blocked": "Deaktiviert Stille Tags für blockierte Nutzer",
|
||||
"_cfg_doc_silent_users": (
|
||||
"Sendet keine Nachrichten über Stille Tags von Nutzern mit den"
|
||||
" folgenden IDs"
|
||||
),
|
||||
"_cfg_doc_silent_chats": (
|
||||
"Sendet keine Nachrichten über Stille Tags in Chats mit den folgenden IDs"
|
||||
),
|
||||
"_cfg_doc_silent_bots": "Sendet keine Nachrichten über Stille Tags von Bots",
|
||||
"_cfg_doc_silent_blocked": (
|
||||
"Sendet keine Nachrichten über Stille Tags von blockierten Nutzern"
|
||||
),
|
||||
"_cfg_doc_silent": (
|
||||
"Sendet keine Nachricht über den aktivierten Status von Stille Tags"
|
||||
),
|
||||
"_cfg_doc_use_whitelist": (
|
||||
"Konvertiert alle Serienähnlichen Optionen in eine Whitelist"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"tag_mentioned": "<b>🤫 साइलेंट टैग चालू हैं</b>",
|
||||
"stags_status": "<b>🤫 साइलेंट टैग {}</b>",
|
||||
"_cmd_doc_stags": "<on\\off> - साइलेंट टैग को चालू\\बंद करें",
|
||||
"_cls_doc": "साइलेंट टैग निष्क्रिय करता है",
|
||||
"_cfg_doc_ignore_users": "निम्न आईडी के साथ साइलेंट टैग नहीं करें",
|
||||
"_cfg_doc_ignore_chats": "निम्न आईडी के साथ साइलेंट टैग नहीं करें",
|
||||
"_cfg_doc_ignore_bots": "साइलेंट टैग नहीं करें बॉटों के लिए",
|
||||
"_cfg_doc_ignore_blocked": "साइलेंट टैग नहीं करें ब्लॉक किए गए उपयोगकर्ताओं के लिए",
|
||||
"_cfg_doc_silent_users": "निम्न आईडी के साथ साइलेंट टैग के साथ संदेश नहीं भेजें",
|
||||
"_cfg_doc_silent_chats": "निम्न आईडी के साथ साइलेंट टैग के साथ संदेश नहीं भेजें",
|
||||
"_cfg_doc_silent_bots": "साइलेंट टैग के साथ संदेश नहीं भेजें बॉटों के लिए",
|
||||
"_cfg_doc_silent_blocked": (
|
||||
"साइलेंट टैग के साथ संदेश नहीं भेजें ब्लॉक किए गए उपयोगकर्ताओं के लिए"
|
||||
),
|
||||
"_cfg_doc_silent": "साइलेंट टैग की स्थिति को सक्रिय करने से संदेश नहीं भेजें",
|
||||
"_cfg_doc_use_whitelist": "सभी सीरीज़ विकल्पों को व्हाइटलिस्ट में कनवर्ट करें",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"tag_mentioned": "<b>🤫 Sessiz etiketler etkin</b>",
|
||||
"stags_status": "<b>🤫 Sessiz etiketler {}</b>",
|
||||
"_cmd_doc_stags": (
|
||||
"<on\\off> - Sessiz etiketleri etkinleştirin\\devre dışı bırakın"
|
||||
),
|
||||
"_cls_doc": "Sessiz etiketleri devre dışı bırakır",
|
||||
"_cfg_doc_ignore_users": (
|
||||
"Sessiz etiketleri aşağıdaki kimliklerle devre dışı bırakın"
|
||||
),
|
||||
"_cfg_doc_ignore_chats": (
|
||||
"Sessiz etiketleri aşağıdaki kimliklerle devre dışı bırakın"
|
||||
),
|
||||
"_cfg_doc_ignore_bots": "Sessiz etiketleri devre dışı bırakın",
|
||||
"_cfg_doc_ignore_blocked": (
|
||||
"Sessiz etiketleri engellenen kullanıcılar için devre dışı bırakın"
|
||||
),
|
||||
"_cfg_doc_silent_users": (
|
||||
"Aşağıdaki kimliklerle sessiz etiketlerle ileti gönderme"
|
||||
),
|
||||
"_cfg_doc_silent_chats": (
|
||||
"Aşağıdaki kimliklerle sessiz etiketlerle ileti gönderme"
|
||||
),
|
||||
"_cfg_doc_silent_bots": "Sessiz etiketlerle ileti gönderme",
|
||||
"_cfg_doc_silent_blocked": (
|
||||
"Sessiz etiketlerle ileti gönderme engellenen kullanıcılar için"
|
||||
),
|
||||
"_cfg_doc_silent": "Sessiz etiketlerin etkinleştirilmesi ile ileti gönderme",
|
||||
"_cfg_doc_use_whitelist": (
|
||||
"Tüm serisi benzer seçenekleri beyaz listeye dönüştürün"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"tag_mentioned": "<b>🤫 Sessiz etiketlar yoqilgan</b>",
|
||||
"stags_status": "<b>🤫 Sessiz etiketlar {}</b>",
|
||||
"_cmd_doc_stags": "<on\\off> - Sessiz etiketlarni yoqish\\o'chirish",
|
||||
"_cls_doc": "Sessiz etiketlarni o'chiradi",
|
||||
"_cfg_doc_ignore_users": (
|
||||
"Sessiz etiketlarni quyidagi identifikatorlar bilan o'chirish"
|
||||
),
|
||||
"_cfg_doc_ignore_chats": (
|
||||
"Sessiz etiketlarni quyidagi identifikatorlar bilan o'chirish"
|
||||
),
|
||||
"_cfg_doc_ignore_bots": "Sessiz etiketlarni o'chirish",
|
||||
"_cfg_doc_ignore_blocked": (
|
||||
"Sessiz etiketlarni bloklangan foydalanuvchilar uchun o'chirish"
|
||||
),
|
||||
"_cfg_doc_silent_users": (
|
||||
"Quyidagi identifikatorlar bilan sessiz etiketlar bilan xabar yuborish"
|
||||
),
|
||||
"_cfg_doc_silent_chats": (
|
||||
"Quyidagi identifikatorlar bilan sessiz etiketlar bilan xabar yuborish"
|
||||
),
|
||||
"_cfg_doc_silent_bots": "Sessiz etiketlar bilan xabar yuborish",
|
||||
"_cfg_doc_silent_blocked": (
|
||||
"Sessiz etiketlar bilan xabar yuborish bloklangan foydalanuvchilar uchun"
|
||||
),
|
||||
"_cfg_doc_silent": "Sessiz etiketlar yoqilgan bo'lishi xabar yuborish",
|
||||
"_cfg_doc_use_whitelist": (
|
||||
"Barcha seriyalar bir-biriga o'xshash variantlarni o'q ro'yxatiga"
|
||||
" o'zgartirish"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"silent",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_silent"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_bots",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_ignore_bots"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_blocked",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_ignore_blocked"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_users",
|
||||
doc=lambda: self.strings("_cfg_doc_ignore_users"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_chats",
|
||||
doc=lambda: self.strings("_cfg_doc_ignore_chats"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent_bots",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_silent_bots"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent_blocked",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_silent_blocked"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent_users",
|
||||
doc=lambda: self.strings("_cfg_doc_silent_users"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent_chats",
|
||||
doc=lambda: self.strings("_cfg_doc_silent_chats"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"use_whitelist",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_use_whitelist"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.loop(interval=300)
|
||||
async def get_blocked(self):
|
||||
self._blocked = [
|
||||
user.id
|
||||
for user in (
|
||||
await self._client(GetBlockedRequest(offset=0, limit=1000))
|
||||
).users
|
||||
]
|
||||
|
||||
async def client_ready(self):
|
||||
self._ratelimit = []
|
||||
self._fw_protect = {}
|
||||
self._blocked = []
|
||||
self._fw_protect_limit = 5
|
||||
|
||||
self.c, _ = await utils.asset_channel(
|
||||
self._client,
|
||||
"silent-tags",
|
||||
"🔇 Chat for silent tags",
|
||||
silent=True,
|
||||
invite_bot=True,
|
||||
_folder="hikka",
|
||||
)
|
||||
|
||||
if self.config["ignore_blocked"] or self.config["silent_blocked"]:
|
||||
self.get_blocked.start()
|
||||
|
||||
self.chat_aio = f"-100{self.c.id}"
|
||||
|
||||
async def stagscmd(self, message: Message):
|
||||
"""<on\\off> - Toggle notifications about tags"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if args not in ["on", "off"]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("stags_status").format(
|
||||
"active" if self.get("stags", False) else "inactive"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
args = args == "on"
|
||||
self.set("stags", args)
|
||||
self._ratelimit = []
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("stags_status").format("now on" if args else "now off"),
|
||||
)
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
not getattr(message, "mentioned", False)
|
||||
or not self.get("stags", False)
|
||||
or utils.get_chat_id(message) == self.c.id
|
||||
or (
|
||||
self.config["whitelist"]
|
||||
and message.sender_id not in (self.config["ignore_users"] or [])
|
||||
or not self.config["whitelist"]
|
||||
and message.sender_id in (self.config["ignore_users"] or [])
|
||||
)
|
||||
or self.config["ignore_blocked"]
|
||||
and message.sender.id in self._blocked
|
||||
or (
|
||||
self.config["whitelist"]
|
||||
and utils.get_chat_id(message)
|
||||
not in (self.config["ignore_chats"] or [])
|
||||
or not self.config["whitelist"]
|
||||
and utils.get_chat_id(message) in (self.config["ignore_chats"] or [])
|
||||
)
|
||||
or self.config["ignore_bots"]
|
||||
and message.sender.bot
|
||||
):
|
||||
return
|
||||
|
||||
await self._client.send_read_acknowledge(
|
||||
message.chat_id,
|
||||
clear_mentions=True,
|
||||
)
|
||||
|
||||
cid = utils.get_chat_id(message)
|
||||
|
||||
if (
|
||||
cid in self._fw_protect
|
||||
and len(list(filter(lambda x: x > time.time(), self._fw_protect[cid])))
|
||||
> self._fw_protect_limit
|
||||
):
|
||||
return
|
||||
|
||||
if message.is_private:
|
||||
ctitle = "pm"
|
||||
else:
|
||||
chat = await self._client.get_entity(message.peer_id)
|
||||
grouplink = (
|
||||
f"https://t.me/{chat.username}"
|
||||
if getattr(chat, "username", None) is not None
|
||||
else ""
|
||||
)
|
||||
ctitle = chat.title
|
||||
|
||||
if cid not in self._fw_protect:
|
||||
self._fw_protect[cid] = []
|
||||
|
||||
uid = message.sender_id
|
||||
|
||||
try:
|
||||
user = await self._client.get_entity(message.sender_id)
|
||||
uname = user.first_name
|
||||
except Exception:
|
||||
uname = "Unknown user"
|
||||
user = None
|
||||
|
||||
if (
|
||||
(
|
||||
self.config["whitelist"]
|
||||
and message.sender_id not in (self.config["silent_users"] or [])
|
||||
or not self.config["whitelist"]
|
||||
and message.sender_id in (self.config["silent_users"] or [])
|
||||
)
|
||||
or self.config["silent_blocked"]
|
||||
and message.sender.id in self._blocked
|
||||
or (
|
||||
self.config["whitelist"]
|
||||
and utils.get_chat_id(message)
|
||||
not in (self.config["silent_chats"] or [])
|
||||
or not self.config["whitelist"]
|
||||
and utils.get_chat_id(message) in (self.config["silent_chats"] or [])
|
||||
)
|
||||
or not (isinstance(user, Channel))
|
||||
and self.config["silent_bots"]
|
||||
and message.sender.bot
|
||||
):
|
||||
return
|
||||
|
||||
await self.inline.bot.send_message(
|
||||
self.chat_aio,
|
||||
self.strings("tagged").format(
|
||||
grouplink,
|
||||
utils.escape_html(ctitle),
|
||||
uid,
|
||||
utils.escape_html(uname),
|
||||
utils.escape_html(message.raw_text),
|
||||
cid,
|
||||
message.id,
|
||||
),
|
||||
disable_web_page_preview=True,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
self._fw_protect[cid] += [time.time() + 5 * 60]
|
||||
|
||||
if cid not in self._ratelimit and not self.config["silent"]:
|
||||
self._ratelimit += [cid]
|
||||
ms = await utils.answer(message, self.strings("tag_mentioned"))
|
||||
await asyncio.sleep(3)
|
||||
await ms.delete()
|
||||
209
hikariatama/ftg/speller.py
Normal file
209
hikariatama/ftg/speller.py
Normal file
@@ -0,0 +1,209 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/fluency/240/000000/spell-check.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/speller.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# requires: requests cloudscraper requests_toolbelt aiohttp bs4 langid
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
from typing import Union
|
||||
|
||||
import aiohttp
|
||||
import cloudscraper
|
||||
import langid
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from requests_toolbelt import MultipartEncoder
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
URL = "https://services.gingersoftware.com/Ginger/correct/jsonSecured/GingerTheTextFull" # noqa
|
||||
API_KEY = "6ae0c3a0-afdc-4532-a810-82ded0054236"
|
||||
|
||||
|
||||
class GingerIt(object):
|
||||
def __init__(self):
|
||||
self.url = URL
|
||||
self.api_key = API_KEY
|
||||
self.api_version = "2.0"
|
||||
self.lang = "US"
|
||||
|
||||
def parse(self, text, verify=True):
|
||||
session = cloudscraper.create_scraper()
|
||||
request = session.get(
|
||||
self.url,
|
||||
params={
|
||||
"lang": self.lang,
|
||||
"apiKey": self.api_key,
|
||||
"clientVersion": self.api_version,
|
||||
"text": text,
|
||||
},
|
||||
verify=verify,
|
||||
)
|
||||
data = request.json()
|
||||
return self._process_data(text, data)
|
||||
|
||||
@staticmethod
|
||||
def _change_char(original_text, from_position, to_position, change_with):
|
||||
return "{}{}{}".format(
|
||||
original_text[:from_position], change_with, original_text[to_position + 1 :]
|
||||
)
|
||||
|
||||
def _process_data(self, text, data):
|
||||
result = text
|
||||
corrections = []
|
||||
|
||||
for suggestion in reversed(data["Corrections"]):
|
||||
start = suggestion["From"]
|
||||
end = suggestion["To"]
|
||||
|
||||
if suggestion["Suggestions"]:
|
||||
suggest = suggestion["Suggestions"][0]
|
||||
result = self._change_char(result, start, end, suggest["Text"])
|
||||
|
||||
corrections.append(
|
||||
{
|
||||
"start": start,
|
||||
"text": text[start : end + 1],
|
||||
"correct": suggest.get("Text", None),
|
||||
"definition": suggest.get("Definition", None),
|
||||
}
|
||||
)
|
||||
|
||||
return {"text": text, "result": result, "corrections": corrections}
|
||||
|
||||
|
||||
async def process(text: str) -> str:
|
||||
fields = {"mytext": text, "autofix": "1", "lang_var": "Russian"}
|
||||
|
||||
boundary = "----WebKitFormBoundary" + "".join(
|
||||
random.sample(string.ascii_letters + string.digits, 16)
|
||||
)
|
||||
|
||||
m = MultipartEncoder(fields=fields, boundary=boundary)
|
||||
|
||||
a = requests.post(
|
||||
"https://www.russiancorrector.com",
|
||||
headers={
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"Origin": "https://www.russiancorrector.com",
|
||||
"Content-Type": m.content_type,
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,"
|
||||
" like Gecko) Chrome/92.0.4515.131 Safari/537.36"
|
||||
),
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"Sec-GPC": "1",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-User": "?1",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Referer": "https://www.russiancorrector.com/",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9,ru;q=0.8",
|
||||
},
|
||||
data=m,
|
||||
).text
|
||||
|
||||
url = "https://www.russiancorrector.com" + re.search(
|
||||
r"var url = \'(.+?)\'", a
|
||||
).group(1)
|
||||
|
||||
res = "__wait__123"
|
||||
while res == "__wait__123":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.request("GET", url) as resp:
|
||||
res = await resp.text()
|
||||
if res != "__wait__123":
|
||||
break
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def parse(text: str) -> Union[bool, str]:
|
||||
if "We could not find any errors in your text" in text:
|
||||
return False
|
||||
|
||||
soup = BeautifulSoup(text, "html.parser")
|
||||
|
||||
for misspell in soup.find_all("div", class_="misspelling"):
|
||||
try:
|
||||
misspell.replace_with(
|
||||
misspell.find("li", class_="replace-option").get_text() or ""
|
||||
)
|
||||
except Exception:
|
||||
misspell.replace_with("")
|
||||
|
||||
return (
|
||||
re.sub(r" {2,}", " ", soup.get_text().strip().replace("\n", " "))
|
||||
.replace("Types and number of errors found: ", "")
|
||||
.replace(
|
||||
(
|
||||
"Autocorrect: Check box to correct errors automatically, where"
|
||||
" possible.A list of all corrected errors will be shown on the results"
|
||||
" page. Submit"
|
||||
),
|
||||
"",
|
||||
)
|
||||
.strip()
|
||||
)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SpellCheckMod(loader.Module):
|
||||
"""Just a simple two-lang spell checker"""
|
||||
|
||||
strings = {
|
||||
"name": "SpellCheck",
|
||||
"processing": (
|
||||
"👩🏫 <b>Let me take a look... Seems like this message is misspelled!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"processing": "👩🏫 <b>Дай глянуть. Похоже, тут есть ошибки!</b>",
|
||||
"_cmd_doc_spell": "Проверить сообщение на грамотность",
|
||||
"_cls_doc": "Проверяет правописание",
|
||||
}
|
||||
|
||||
async def spellcmd(self, message: Message):
|
||||
"""Perform spell check on reply"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not getattr(reply, "raw_text", False):
|
||||
await message.delete()
|
||||
|
||||
message = await utils.answer(message, self.strings("processing"))
|
||||
text = reply.text
|
||||
|
||||
tt = langid.classify(text)[0]
|
||||
if tt == "en":
|
||||
spell_checker = GingerIt()
|
||||
result = spell_checker.parse(text)
|
||||
corrected_text = result["result"]
|
||||
elif tt == "ru":
|
||||
corrected_text = parse(await process(text))
|
||||
else:
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
if corrected_text == text or not corrected_text:
|
||||
await message.delete()
|
||||
|
||||
await utils.answer(message, f"✍️ {corrected_text}")
|
||||
223
hikariatama/ftg/spoilers.py
Normal file
223
hikariatama/ftg/spoilers.py
Normal file
@@ -0,0 +1,223 @@
|
||||
__version__ = (1, 0, 4)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/external-sketchy-juicy-fish/480/000000/external-anonymous-cryptography-sketchy-sketchy-juicy-fish.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/spoilers.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.utils import get_display_name, resolve_inline_message_id
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall, InlineQuery
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SpoilersMod(loader.Module):
|
||||
"""Create spoilers, that can be accessed only by certain users"""
|
||||
|
||||
_cache = {}
|
||||
_msg_cache = {}
|
||||
|
||||
strings = {
|
||||
"name": "Spoilers",
|
||||
"only_he_can_open": "ℹ Only (s)he will be able to open it",
|
||||
"message": (
|
||||
'🫦 <b>Hidden message for <a href="tg://user?id={}">{}</a></b>\n<i>You can'
|
||||
" open this message only once!</i>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"🫦 <b>Hidden message for you!</b>\n<i>You can"
|
||||
" open this message only once!</i>"
|
||||
),
|
||||
"not4u": "🫦 I won't whisper you",
|
||||
"open": "👀 Open",
|
||||
"in_the_end": "Send spoiler to user in reply",
|
||||
"broken": "🫦 Cats have eaten this whisper. Do not whisper in pm anymore.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"only_he_can_open": "ℹ Только он(-а) сможет открыть его",
|
||||
"message": (
|
||||
'🫦 <b>Шепот для <a href="tg://user?id={}">{}</a></b>\n<i>Сообщение можно'
|
||||
" открыть только один раз!</i>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"🫦 <b>Шепот для тебя!</b>\n<i>Сообщение можно открыть только один раз!</i>"
|
||||
),
|
||||
"not4u": "🫦 Я не буду тебе шептать",
|
||||
"open": "👀 Открыть",
|
||||
"in_the_end": "Отправь шепот пользователю в ответе",
|
||||
"_cls_doc": (
|
||||
"Создает спойлеры, которые доступны только определенным пользователям"
|
||||
),
|
||||
"broken": "🫦 Коты съели этот шепот. Не шепчите в личных сообщениях.",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"only_he_can_open": "ℹ Nur er/sie kann es öffnen",
|
||||
"message": (
|
||||
'🫦 <b>Geheimnachricht für <a href="tg://user?id={}">{}</a></b>\n<i>Du'
|
||||
" kannst diese Nachricht nur einmal öffnen!</i>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"🫦 <b>Geheimnachricht für dich!</b>\n<i>Du kannst diese Nachricht nur"
|
||||
" einmal öffnen!</i>"
|
||||
),
|
||||
"not4u": "🫦 Ich werde dir nicht flüstern",
|
||||
"open": "👀 Öffnen",
|
||||
"in_the_end": "Sende Geheimnachricht an Benutzer als Antwort",
|
||||
"_cls_doc": (
|
||||
"Erstellt Geheimnachrichten, die nur bestimmten Benutzern zugänglich sind"
|
||||
),
|
||||
"broken": (
|
||||
"🫦 Die Katzen haben diesen Geheimnachricht gefressen. Flüstern Sie nicht"
|
||||
" mehr in PM."
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"only_he_can_open": "ℹ Sadece onu açabilir",
|
||||
"message": (
|
||||
'🫦 <b><a href="tg://user?id={}">{}</a> için gizli mesaj</b>\n<i>Bu mesajı'
|
||||
" yalnızca bir kez açabilirsiniz!</i>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"🫦 <b>Sana gizli mesaj!</b>\n<i>Bu mesajı yalnızca bir kez"
|
||||
" açabilirsiniz!</i>"
|
||||
),
|
||||
"not4u": "🫦 Sana fısıldamayacağım",
|
||||
"open": "👀 Açmak",
|
||||
"in_the_end": "Kullanıcıya yanıt olarak gizli mesaj gönder",
|
||||
"_cls_doc": (
|
||||
"Sadece belirli kullanıcılara erişilebilen gizli mesajlar oluşturur"
|
||||
),
|
||||
"broken": "🫦 Bu gizli mesaj kediler tarafından yendi. PM'de fısıldamayın.",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"only_he_can_open": "ℹ Faqat u o'ynay oladi",
|
||||
"message": (
|
||||
'🫦 <b><a href="tg://user?id={}">{}</a> uchun shifrlangan xabar</b>\n<i>Siz'
|
||||
" bu xabarni faqat bir marta ochishingiz mumkin!</i>"
|
||||
),
|
||||
"user_not_specified": (
|
||||
"🫦 <b>Siz uchun shifrlangan xabar!</b>\n<i>Siz bu xabarni faqat bir marta"
|
||||
" ochishingiz mumkin!</i>"
|
||||
),
|
||||
"not4u": "🫦 Sizga shifrlashmayman",
|
||||
"open": "👀 Ochish",
|
||||
"in_the_end": "Foydalanuvchiga javob sifrlangan xabarini yuborish",
|
||||
"_cls_doc": (
|
||||
"Faqat belgilangan foydalanuvchilarga kirish mumkin bo'lgan shifrlangan"
|
||||
" xabarlar yaratadi"
|
||||
),
|
||||
"broken": (
|
||||
"🫦 Bu shifrlangan xabar moshinalar tomonidan yig'ildi. PM'da"
|
||||
" shifrlashmayin."
|
||||
),
|
||||
}
|
||||
|
||||
@loader.inline_handler(
|
||||
ru_doc="Создать скрытое сообщение",
|
||||
de_doc="Erstellt eine versteckte Nachricht",
|
||||
uz_doc="Shifrlangan xabar yaratish",
|
||||
tr_doc="Gizli mesaj oluştur",
|
||||
hi_doc="छिपा संदेश बनाएं",
|
||||
)
|
||||
async def hide(self, query: InlineQuery):
|
||||
"""Create hidden message"""
|
||||
text = query.args
|
||||
for_user = self.strings("in_the_end")
|
||||
for_user_id = None
|
||||
user = None
|
||||
if len(text.split()) > 1:
|
||||
try:
|
||||
possible_entity = text.split()[0]
|
||||
|
||||
if possible_entity.isdigit():
|
||||
possible_entity = int(possible_entity)
|
||||
|
||||
user = await self._client.get_entity(possible_entity)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
for_user = "Hidden message for " + get_display_name(user)
|
||||
for_user_id = user.id
|
||||
text = " ".join(text.split(" ")[1:])
|
||||
|
||||
id_ = utils.rand(16)
|
||||
self._cache[id_] = text
|
||||
|
||||
return {
|
||||
"title": for_user,
|
||||
"description": self.strings("only_he_can_open"),
|
||||
"message": (
|
||||
self.strings("message").format(
|
||||
for_user_id,
|
||||
utils.escape_html(get_display_name(user)),
|
||||
)
|
||||
if user
|
||||
else self.strings("user_not_specified").format(id_)
|
||||
),
|
||||
"thumb": "https://img.icons8.com/color/48/000000/anonymous-mask.png",
|
||||
"reply_markup": {
|
||||
"text": self.strings("open"),
|
||||
"callback": self._handler,
|
||||
"args": (text, for_user_id, id_),
|
||||
"disable_security": True,
|
||||
},
|
||||
}
|
||||
|
||||
async def _handler(self, call: InlineCall, text: str, for_user: int, id_: str):
|
||||
"""Process button presses"""
|
||||
if for_user is None:
|
||||
if id_ not in self._msg_cache:
|
||||
message_id, peer, _, _ = resolve_inline_message_id(
|
||||
call.inline_message_id
|
||||
)
|
||||
msg = (await self._client.get_messages(peer, ids=[message_id]))[0]
|
||||
if msg is None:
|
||||
await call.answer(self.strings("broken"))
|
||||
self._msg_cache[id_] = None
|
||||
return
|
||||
|
||||
msg = await msg.get_reply_message()
|
||||
if msg is None:
|
||||
await call.answer(self.strings("broken"))
|
||||
self._msg_cache[id_] = None
|
||||
return
|
||||
else:
|
||||
msg = self._msg_cache[id_]
|
||||
if msg is None:
|
||||
await call.answer(self.strings("broken"))
|
||||
return
|
||||
|
||||
for_user = msg.sender_id
|
||||
self._msg_cache[id_] = msg
|
||||
|
||||
if call.from_user.id not in {
|
||||
for_user,
|
||||
self._tg_id,
|
||||
}:
|
||||
await call.answer(self.strings("not4u"))
|
||||
return
|
||||
|
||||
await call.answer(text, show_alert=True)
|
||||
|
||||
if call.from_user.id != self._tg_id:
|
||||
message_id, peer, _, _ = resolve_inline_message_id(call.inline_message_id)
|
||||
await self._client.delete_messages(peer, [message_id])
|
||||
1064
hikariatama/ftg/spotify.py
Normal file
1064
hikariatama/ftg/spotify.py
Normal file
File diff suppressed because it is too large
Load Diff
1780
hikariatama/ftg/sticks.py
Normal file
1780
hikariatama/ftg/sticks.py
Normal file
File diff suppressed because it is too large
Load Diff
249
hikariatama/ftg/surl.py
Normal file
249
hikariatama/ftg/surl.py
Normal file
@@ -0,0 +1,249 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/external-xnimrodx-lineal-color-xnimrodx/512/000000/external-short-shopping-mall-xnimrodx-lineal-color-xnimrodx.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/surl.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message, MessageEntityUrl
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AutoShortenerMod(loader.Module):
|
||||
"""Automatically shortens urls in your messages, which are larger than specified threshold"""
|
||||
|
||||
strings = {
|
||||
"name": "AutoShortener",
|
||||
"state": "🔗 <b>Auotmatic url shortener is now {}</b>",
|
||||
"no_args": "🔗 <b>No link to shorten</b>",
|
||||
"on": "on",
|
||||
"off": "off",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"state": "🔗 <b>Автоматический сократитель ссылок теперь {}</b>",
|
||||
"no_args": "🔗 <b>Не указана ссылка для сокращения</b>",
|
||||
"_cmd_doc_autosurl": "Включить\\выключить автоматическое сокращение ссылок",
|
||||
"_cmd_doc_surl": "[ссылка] [движок]- Сократить ссылку",
|
||||
"_cls_doc": (
|
||||
"Автоматически сокращает ссылки в твоих сообщениях, если они длиннее"
|
||||
" значения в конфиге"
|
||||
),
|
||||
"on": "включен",
|
||||
"off": "выключен",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"state": "🔗 <b>Automatisches URL-Kürzen ist jetzt {}</b>",
|
||||
"no_args": "🔗 <b>Kein Link zum Kürzen</b>",
|
||||
"_cmd_doc_autosurl": (
|
||||
"Aktivieren\\Deaktivieren Sie das automatische Kürzen von URLs"
|
||||
),
|
||||
"_cmd_doc_surl": "[URL] [Engine] - URL kürzen",
|
||||
"_cls_doc": (
|
||||
"Kürzt automatisch URLs in Ihren Nachrichten, wenn sie länger sind als"
|
||||
" Wert in der Konfiguration"
|
||||
),
|
||||
"on": "Aktiviert",
|
||||
"off": "Deaktiviert",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"state": "🔗 <b>Otomatik URL kısaltıcı şimdi {}</b>",
|
||||
"no_args": "🔗 <b>Kısaltılacak URL yok</b>",
|
||||
"_cmd_doc_autosurl": (
|
||||
"URL'leri otomatik olarak kısaltmayı etkinleştirin\\devre dışı bırakın"
|
||||
),
|
||||
"_cmd_doc_surl": "[URL] [motor] - URL kısalt",
|
||||
"_cls_doc": (
|
||||
"URL'leri, yapılandırmanın değerinden daha uzun olduğunda mesajlarınızda"
|
||||
" otomatik olarak kısaltır"
|
||||
),
|
||||
"on": "açık",
|
||||
"off": "kapalı",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"state": "🔗 <b>ऑटो यूआरएल शॉर्टनर अब {} है</b>",
|
||||
"no_args": "🔗 <b>संक्षिप्त करने के लिए कोई लिंक नहीं</b>",
|
||||
"_cmd_doc_autosurl": "URL को स्वचालित रूप से छोटा करना चालू\\बंद करें",
|
||||
"_cmd_doc_surl": "[URL] [Engine] - URL को छोटा करें",
|
||||
"_cls_doc": (
|
||||
"अपने संदेशों में यूआरएल को छोटा करता है, जब वे विन्यास में निर्दिष्ट मान से अधिक होते हैं"
|
||||
),
|
||||
"on": "चालू",
|
||||
"off": "बंद",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"state": "🔗 <b>URL avtomatik qisqartiruvchisi hozir {}</b>",
|
||||
"no_args": "🔗 <b>Qisqartiladigan URL yo'q</b>",
|
||||
"_cmd_doc_autosurl": "URL'ni avtomatik ravishda qisqartishni yoqish\\o'chirish",
|
||||
"_cmd_doc_surl": "[URL] [mashina] - URL'ni qisqartirish",
|
||||
"_cls_doc": (
|
||||
"So'rovlarizdagi URL'ni konfiguratsiyadagi qiymatdan katta bo'lganda"
|
||||
" avtomatik ravishda qisqartadi"
|
||||
),
|
||||
"on": "yoqilgan",
|
||||
"off": "o'chirilgan",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"threshold",
|
||||
80,
|
||||
lambda: "Urls larger than this value will be automatically shortened",
|
||||
validator=loader.validators.Integer(minimum=50),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_engine",
|
||||
"owo",
|
||||
lambda: "Engine to auto-shorten urls with",
|
||||
validator=loader.validators.Choice(["owo", "gg", "gay"]),
|
||||
),
|
||||
)
|
||||
|
||||
async def autosurlcmd(self, message: Message):
|
||||
"""Toggle automatic url shortener"""
|
||||
state = not self.get("state", False)
|
||||
self.set("state", state)
|
||||
await utils.answer(
|
||||
message, self.strings("state").format("on" if state else "off")
|
||||
)
|
||||
|
||||
async def surlcmd(self, message: Message):
|
||||
"""[url] [engine]- Shorten url"""
|
||||
if (
|
||||
not getattr(message, "raw_text", False)
|
||||
or not getattr(message, "entities", False)
|
||||
or not message.entities
|
||||
or not any(
|
||||
isinstance(entity, MessageEntityUrl) for entity in message.entities
|
||||
)
|
||||
):
|
||||
reply = await message.get_reply_message()
|
||||
if (
|
||||
not reply
|
||||
or not getattr(reply, "raw_text", False)
|
||||
or not getattr(reply, "entities", False)
|
||||
or not reply.entities
|
||||
or not any(
|
||||
isinstance(entity, MessageEntityUrl) for entity in reply.entities
|
||||
)
|
||||
):
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
return
|
||||
|
||||
txt = reply.raw_text
|
||||
text = reply.text
|
||||
entities = reply.entities
|
||||
just_url = False
|
||||
else:
|
||||
txt = message.raw_text
|
||||
text = message.text
|
||||
entities = message.entities
|
||||
just_url = True
|
||||
|
||||
urls = [
|
||||
txt[entity.offset : entity.offset + entity.length] for entity in entities
|
||||
]
|
||||
|
||||
if just_url:
|
||||
text = ""
|
||||
|
||||
for url in urls:
|
||||
surl = await self.shorten(
|
||||
url, txt.split()[-1] if len(txt.split()) > 1 else None
|
||||
)
|
||||
if not just_url:
|
||||
text = text.replace(url, surl)
|
||||
else:
|
||||
text += f"{surl} | "
|
||||
|
||||
await utils.answer(message, text.strip(" | "))
|
||||
|
||||
@staticmethod
|
||||
async def shorten(url, engine=None) -> str:
|
||||
if not engine or engine == "gg":
|
||||
r = await utils.run_sync(
|
||||
requests.post,
|
||||
"http://gg.gg/create",
|
||||
data={
|
||||
"custom_path": None,
|
||||
"use_norefs": 0,
|
||||
"long_url": url,
|
||||
"app": "site",
|
||||
"version": "0.1",
|
||||
},
|
||||
)
|
||||
|
||||
return r.text
|
||||
elif engine in ["owo", "gay"]:
|
||||
r = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://owo.vc/generate",
|
||||
json={
|
||||
"link": url,
|
||||
"generator": engine,
|
||||
"preventScrape": True,
|
||||
"owoify": True,
|
||||
},
|
||||
headers={"User-Agent": "https://mods.hikariatama.ru/view/surl.py"},
|
||||
)
|
||||
|
||||
logger.debug(r.json())
|
||||
|
||||
return "https://" + r.json()["result"]
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
not getattr(message, "text", False)
|
||||
or not getattr(message, "out", False)
|
||||
or not getattr(message, "entities", False)
|
||||
or not message.entities
|
||||
or not any(
|
||||
isinstance(entity, MessageEntityUrl) for entity in message.entities
|
||||
)
|
||||
or not self.get("state", False)
|
||||
or message.raw_text.lower().startswith(self.get_prefix())
|
||||
):
|
||||
return
|
||||
|
||||
entities = message.entities
|
||||
urls = list(
|
||||
filter(
|
||||
lambda x: len(x) > int(self.config["threshold"]),
|
||||
[
|
||||
message.raw_text[entity.offset : entity.offset + entity.length]
|
||||
for entity in entities
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
if not urls:
|
||||
return
|
||||
|
||||
text = message.text
|
||||
|
||||
for url in urls:
|
||||
text = text.replace(
|
||||
url, await self.shorten(url, self.config["auto_engine"])
|
||||
)
|
||||
|
||||
await message.edit(text)
|
||||
714
hikariatama/ftg/systemd.py
Normal file
714
hikariatama/ftg/systemd.py
Normal file
@@ -0,0 +1,714 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/plasticine/344/apple-settings--v2.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/systemd.jpg
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# meta developer: @hikarimods
|
||||
|
||||
# ⚠️ Please, ensure that userbot has enough rights to control units
|
||||
# Put these lines in /etc/sudoers using visudo command:
|
||||
#
|
||||
# user ALL=(ALL) NOPASSWD: /bin/systemctl
|
||||
# user ALL=(ALL) NOPASSWD: /bin/journalctl
|
||||
#
|
||||
# Where `user` is user on behalf of which the userbot is running
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import subprocess
|
||||
from typing import Union
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
|
||||
def human_readable_size(size: float, decimal_places: int = 2) -> str:
|
||||
for unit in ["B", "K", "M", "G", "T", "P"]:
|
||||
if size < 1024.0 or unit == "P":
|
||||
break
|
||||
size /= 1024.0
|
||||
|
||||
return f"{size:.{decimal_places}f} {unit}"
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SystemdMod(loader.Module):
|
||||
"""Control systemd units easily"""
|
||||
|
||||
strings = {
|
||||
"name": "Systemd",
|
||||
"panel": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Here you can control"
|
||||
" your systemd units</b>\n\n{}"
|
||||
),
|
||||
"unit_doesnt_exist": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Unit</b>"
|
||||
" <code>{}</code> <b>doesn't exist!</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>No arguments"
|
||||
" specified</b>"
|
||||
),
|
||||
"unit_added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Unit"
|
||||
" </b><code>{}</code><b> with name </b><code>{}</code><b> added</b>"
|
||||
),
|
||||
"unit_removed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Unit"
|
||||
" </b><code>{}</code><b> removed</b>"
|
||||
),
|
||||
"unit_action_done": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Action"
|
||||
" </b><code>{}</code><b> performed on unit </b><code>{}</code>"
|
||||
),
|
||||
"unit_control": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Interacting with unit"
|
||||
" </b><code>{}</code><b> (</b><code>{}</code><b>)</b>\n{} <b>Unit status:"
|
||||
" </b><code>{}</code>"
|
||||
),
|
||||
"action_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Action"
|
||||
" </b><code>{}</code><b> not found</b>"
|
||||
),
|
||||
"unit_renamed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Unit"
|
||||
" </b><code>{}</code><b> renamed to </b><code>{}</code>"
|
||||
),
|
||||
"stop_btn": "🍎 Stop",
|
||||
"start_btn": "🍏 Start",
|
||||
"restart_btn": "🔄 Restart",
|
||||
"logs_btn": "📄 Logs",
|
||||
"tail_btn": "🚅 Tail",
|
||||
"back_btn": "🔙 Back",
|
||||
"close_btn": "✖️ Close",
|
||||
"refresh_btn": "🔄 Refresh",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"panel": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Здесь вы можете"
|
||||
" управлять своими юнитами systemd</b>\n\n{}"
|
||||
),
|
||||
"unit_doesnt_exist": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Юнит</b>"
|
||||
" <code>{}</code> <b>не существует!</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Не указаны"
|
||||
" аргументы</b>"
|
||||
),
|
||||
"unit_added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Юнит"
|
||||
" </b><code>{}</code><b> с именем </b><code>{}</code><b> добавлен</b>"
|
||||
),
|
||||
"unit_removed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Юнит"
|
||||
" </b><code>{}</code><b> удалён</b>"
|
||||
),
|
||||
"unit_action_done": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Действие"
|
||||
" </b><code>{}</code><b> выполнено на юните </b><code>{}</code>"
|
||||
),
|
||||
"unit_control": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Взаимодействие с"
|
||||
" юнитом </b><code>{}</code><b> (</b><code>{}</code><b>)</b>\n{} <b>Статус"
|
||||
" юнита: </b><code>{}</code>"
|
||||
),
|
||||
"action_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Действие"
|
||||
" </b><code>{}</code><b> не найдено</b>"
|
||||
),
|
||||
"unit_renamed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Юнит"
|
||||
" </b><code>{}</code><b> переименован в </b><code>{}</code>"
|
||||
),
|
||||
"stop_btn": "🍎 Стоп",
|
||||
"start_btn": "🍏 Старт",
|
||||
"restart_btn": "🔄 Рестарт",
|
||||
"logs_btn": "📄 Логи",
|
||||
"tail_btn": "🚅 Тейл",
|
||||
"back_btn": "🔙 Назад",
|
||||
"close_btn": "✖️ Закрыть",
|
||||
"refresh_btn": "🔄 Обновить",
|
||||
"_cmd_doc_units": "Показать список юнитов",
|
||||
"_cmd_doc_addunit": "<unit> - Добавить юнит",
|
||||
"_cmd_doc_nameunit": "<unit> - Переименовать юнит",
|
||||
"_cmd_doc_delunit": "<unit> - Удалить юнит",
|
||||
"_cmd_doc_unit": "<unit> - Управлять юнитом",
|
||||
"_cls_doc": "Простое и удобное управление юнитами systemd",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"panel": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Hier kannst du deine"
|
||||
" systemd-Einheiten kontrollieren</b>\n\n{}"
|
||||
),
|
||||
"unit_doesnt_exist": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Einheit</b>"
|
||||
" <code>{}</code> <b>existiert nicht!</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Keine Argumente"
|
||||
" angegeben</b>"
|
||||
),
|
||||
"unit_added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Einheit"
|
||||
" </b><code>{}</code><b> mit dem Namen </b><code>{}</code><b>"
|
||||
" hinzugefügt</b>"
|
||||
),
|
||||
"unit_removed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Einheit"
|
||||
" </b><code>{}</code><b> entfernt</b>"
|
||||
),
|
||||
"unit_action_done": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Aktion"
|
||||
" </b><code>{}</code><b> auf Einheit </b><code>{}</code><b> ausgeführt</b>"
|
||||
),
|
||||
"unit_control": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Interagiere mit"
|
||||
" Einheit </b><code>{}</code><b> (</b><code>{}</code><b>)</b>\n{}"
|
||||
" <b>Einheitsstatus: </b><code>{}</code>"
|
||||
),
|
||||
"action_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Aktion"
|
||||
" </b><code>{}</code><b> nicht gefunden</b>"
|
||||
),
|
||||
"unit_renamed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Einheit"
|
||||
" </b><code>{}</code><b> umbenannt zu </b><code>{}</code>"
|
||||
),
|
||||
"stop_btn": "🍎 Stop",
|
||||
"start_btn": "🍏 Start",
|
||||
"restart_btn": "🔄 Neustart",
|
||||
"logs_btn": "📄 Logs",
|
||||
"tail_btn": "🚅 Tail",
|
||||
"back_btn": "🔙 Zurück",
|
||||
"close_btn": "✖️ Schließen",
|
||||
"refresh_btn": "🔄 Aktualisieren",
|
||||
"_cmd_doc_units": "Liste der Einheiten anzeigen",
|
||||
"_cmd_doc_addunit": "<unit> - Einheit hinzufügen",
|
||||
"_cmd_doc_nameunit": "<unit> - Einheit umbenennen",
|
||||
"_cmd_doc_delunit": "<unit> - Einheit entfernen",
|
||||
"_cmd_doc_unit": "<unit> - Einheit verwalten",
|
||||
"_cls_doc": "Einfache und bequeme Verwaltung von systemd-Einheiten",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"panel": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>यहाँ आप अपने systemd"
|
||||
" इकाइयों का नियंत्रण कर सकते हैं</b>\n\n{}"
|
||||
),
|
||||
"unit_doesnt_exist": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>इकाई</b>"
|
||||
" <code>{}</code> <b>अस्तित्व में नहीं है!</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>कोई तर्क निर्दिष्ट"
|
||||
" नहीं किया गया</b>"
|
||||
),
|
||||
"unit_added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>इकाई"
|
||||
" </b><code>{}</code><b> नाम </b><code>{}</code><b> के साथ जोड़ा गया</b>"
|
||||
),
|
||||
"unit_removed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>इकाई"
|
||||
" </b><code>{}</code><b> हटा दिया गया</b>"
|
||||
),
|
||||
"unit_action_done": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>कार्य"
|
||||
" </b><code>{}</code><b> इकाई </b><code>{}</code><b> पर किया गया</b>"
|
||||
),
|
||||
"unit_control": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>इकाई"
|
||||
" </b><code>{}</code><b> के साथ इंटरैक्ट करें"
|
||||
" (</b><code>{}</code><b>)</b>\n{} <b>इकाई स्थिति: </b><code>{}</code>"
|
||||
),
|
||||
"action_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>कार्य"
|
||||
" </b><code>{}</code><b> नहीं मिला</b>"
|
||||
),
|
||||
"unit_renamed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>इकाई"
|
||||
" </b><code>{}</code><b> का नाम बदल दिया गया </b><code>{}</code>"
|
||||
),
|
||||
"stop_btn": "🍎 रोकें",
|
||||
"start_btn": "🍏 शुरू करें",
|
||||
"restart_btn": "🔄 पुनः शुरू करें",
|
||||
"logs_btn": "📄 लॉग",
|
||||
"tail_btn": "🚅 Tail",
|
||||
"back_btn": "🔙 पीछे जाएँ",
|
||||
"close_btn": "✖️ बंद करें",
|
||||
"refresh_btn": "🔄 ताज़ा करें",
|
||||
"_cmd_doc_units": "इकाइयों की सूची दिखाएँ",
|
||||
"_cmd_doc_addunit": "<unit> - इकाई जोड़ें",
|
||||
"_cmd_doc_nameunit": "<unit> - इकाई का नाम बदलें",
|
||||
"_cmd_doc_delunit": "<unit> - इकाई हटाएँ",
|
||||
"_cmd_doc_unit": "<unit> - इकाई प्रबंधित करें",
|
||||
"_cls_doc": "systemd इकाइयों का सरल और सुविधाजनक प्रबंधन",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"panel": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Bu yerda siz sizning"
|
||||
" systemd birliklaringizni boshqarishingiz mumkin</b>\n\n{}"
|
||||
),
|
||||
"unit_doesnt_exist": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Birlik</b>"
|
||||
" <code>{}</code> <b>mavjud emas!</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Hech qanday"
|
||||
" argumentlar ko'rsatilmadi</b>"
|
||||
),
|
||||
"unit_added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Birlik"
|
||||
" </b><code>{}</code><b> nomi </b><code>{}</code><b> qo'shildi</b>"
|
||||
),
|
||||
"unit_removed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Birlik"
|
||||
" </b><code>{}</code><b> o'chirildi</b>"
|
||||
),
|
||||
"unit_action_done": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Amal"
|
||||
" </b><code>{}</code><b> birlik </b><code>{}</code><b> uchun bajirildi</b>"
|
||||
),
|
||||
"unit_control": (
|
||||
"<emoji document_id=5771858080664915483>🎛</emoji> <b>Birlik"
|
||||
" </b><code>{}</code><b> bilan ishlash (</b><code>{}</code><b>)</b>\n{}"
|
||||
" <b>Birlik holati: </b><code>{}</code>"
|
||||
),
|
||||
"action_not_found": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Amal"
|
||||
" </b><code>{}</code><b> topilmadi</b>"
|
||||
),
|
||||
"unit_renamed": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Birlik"
|
||||
" </b><code>{}</code><b> nomi </b><code>{}</code><b> o'zgartirildi</b>"
|
||||
),
|
||||
"stop_btn": "🍎 To'xtatish",
|
||||
"start_btn": "🍏 Boshlash",
|
||||
"restart_btn": "🔄 Qayta ishga tushirish",
|
||||
"logs_btn": "📄 Jurnal",
|
||||
"tail_btn": "🚅 Tail",
|
||||
"back_btn": "🔙 Orqaga",
|
||||
"close_btn": "✖️ Yopish",
|
||||
"refresh_btn": "🔄 Yangilash",
|
||||
"_cmd_doc_units": "Birliklar ro'yxatini ko'rsatish",
|
||||
"_cmd_doc_addunit": "<birlik> - Birlik qo'shish",
|
||||
"_cmd_doc_nameunit": "<birlik> - Birlik nomini o'zgartirish",
|
||||
"_cmd_doc_delunit": "<birlik> - Birlikni o'chirish",
|
||||
"_cmd_doc_unit": "<birlik> - Birlikni boshqarish",
|
||||
}
|
||||
|
||||
def _get_unit_status_text(self, unit: str) -> str:
|
||||
return (
|
||||
subprocess.run(
|
||||
[
|
||||
"sudo",
|
||||
"-S",
|
||||
"systemctl",
|
||||
"is-active",
|
||||
unit,
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
)
|
||||
|
||||
def _is_running(self, unit: str) -> bool:
|
||||
return self._get_unit_status_text(unit) == "active"
|
||||
|
||||
def _unit_exists(self, unit: str) -> bool:
|
||||
return (
|
||||
subprocess.run(
|
||||
[
|
||||
"sudo",
|
||||
"-S",
|
||||
"systemctl",
|
||||
"cat",
|
||||
unit,
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
async def _manage_unit(self, call: Union[InlineCall, int], unit: dict, action: str):
|
||||
if action == "start":
|
||||
subprocess.run(
|
||||
["sudo", "-S", "systemctl", "start", unit["formal"]], check=True
|
||||
)
|
||||
elif action == "stop":
|
||||
subprocess.run(
|
||||
["sudo", "-S", "systemctl", "stop", unit["formal"]], check=True
|
||||
)
|
||||
elif action == "restart":
|
||||
subprocess.run(
|
||||
["sudo", "-S", "systemctl", "restart", unit["formal"]], check=True
|
||||
)
|
||||
elif action in {"logs", "tail"}:
|
||||
logs = (
|
||||
subprocess.run(
|
||||
[
|
||||
"sudo",
|
||||
"-S",
|
||||
"journalctl",
|
||||
"-u",
|
||||
unit["formal"],
|
||||
"-n",
|
||||
"1000",
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
)
|
||||
|
||||
hostname = (
|
||||
subprocess.run(["hostname"], check=True, stdout=subprocess.PIPE)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
)
|
||||
logs = logs.replace(f"{hostname} ", "")
|
||||
logs = logs.replace("[" + str(self._get_unit_pid(unit["formal"])) + "]", "")
|
||||
|
||||
if action == "logs":
|
||||
logs = io.BytesIO(logs.encode())
|
||||
logs.name = f"{unit['formal']}-logs.txt"
|
||||
|
||||
await self._client.send_file(
|
||||
call.form["chat"] if not isinstance(call, int) else call, logs
|
||||
)
|
||||
else:
|
||||
actual_logs = ""
|
||||
logs = list(reversed(logs.splitlines()))
|
||||
while logs:
|
||||
chunk = f"{logs.pop()}\n"
|
||||
if len(actual_logs + chunk) >= 4096:
|
||||
break
|
||||
|
||||
actual_logs += chunk
|
||||
|
||||
if isinstance(call, int):
|
||||
await self.inline.form(
|
||||
f"<code>{utils.escape_html(actual_logs)}</code>",
|
||||
call,
|
||||
reply_markup=self._get_unit_markup(unit),
|
||||
)
|
||||
return
|
||||
|
||||
await call.edit(
|
||||
f"<code>{utils.escape_html(actual_logs)}</code>",
|
||||
reply_markup=self._get_unit_markup(unit),
|
||||
)
|
||||
await call.answer("Action complete")
|
||||
return
|
||||
|
||||
if isinstance(call, int):
|
||||
return
|
||||
|
||||
await call.answer("Action complete")
|
||||
await asyncio.sleep(2)
|
||||
await self._control_service(call, unit)
|
||||
|
||||
def _get_unit_markup(self, unit: dict) -> list:
|
||||
return [
|
||||
[
|
||||
{
|
||||
"text": self.strings("start_btn"),
|
||||
"callback": self._manage_unit,
|
||||
"args": (unit, "start"),
|
||||
},
|
||||
{
|
||||
"text": self.strings("stop_btn"),
|
||||
"callback": self._manage_unit,
|
||||
"args": (unit, "stop"),
|
||||
},
|
||||
{
|
||||
"text": self.strings("restart_btn"),
|
||||
"callback": self._manage_unit,
|
||||
"args": (unit, "restart"),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings("logs_btn"),
|
||||
"callback": self._manage_unit,
|
||||
"args": (unit, "logs"),
|
||||
},
|
||||
{
|
||||
"text": self.strings("tail_btn"),
|
||||
"callback": self._manage_unit,
|
||||
"args": (unit, "tail"),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings("refresh_btn"),
|
||||
"callback": self._control_service,
|
||||
"args": (unit,),
|
||||
},
|
||||
{
|
||||
"text": self.strings("back_btn"),
|
||||
"callback": self._control_services,
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
async def _control_service(self, call: InlineCall, unit: dict):
|
||||
await call.edit(
|
||||
self.strings("unit_control").format(
|
||||
unit["name"],
|
||||
unit["formal"],
|
||||
self._get_unit_status_emoji(unit["formal"]),
|
||||
self._get_unit_status_text(unit["formal"]),
|
||||
),
|
||||
reply_markup=self._get_unit_markup(unit),
|
||||
)
|
||||
|
||||
def _get_unit_pid(self, unit: str) -> str:
|
||||
return (
|
||||
subprocess.run(
|
||||
[
|
||||
"sudo",
|
||||
"-S",
|
||||
"systemctl",
|
||||
"show",
|
||||
unit,
|
||||
"--property=MainPID",
|
||||
"--value",
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
)
|
||||
|
||||
def _get_unit_resources_consumption(self, unit: str) -> str:
|
||||
if not self._is_running(unit):
|
||||
return ""
|
||||
|
||||
pid = self._get_unit_pid(unit)
|
||||
ram = human_readable_size(
|
||||
int(
|
||||
subprocess.run(
|
||||
[
|
||||
"ps",
|
||||
"-p",
|
||||
pid,
|
||||
"-o",
|
||||
"rss",
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
.split("\n")[1]
|
||||
)
|
||||
* 1024
|
||||
)
|
||||
|
||||
cpu = (
|
||||
subprocess.run(
|
||||
[
|
||||
"ps",
|
||||
"-p",
|
||||
pid,
|
||||
"-o",
|
||||
r"%cpu",
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
.split("\n")[1]
|
||||
+ "%"
|
||||
)
|
||||
|
||||
return f"📟 <code>{ram}</code> | 🗃 <code>{cpu}</code>"
|
||||
|
||||
def _get_panel(self):
|
||||
return self.strings("panel").format(
|
||||
"\n".join(
|
||||
[
|
||||
f"{self._get_unit_status_emoji(unit['formal'])} <b>{unit['name']}</b>"
|
||||
f" (<code>{unit['formal']}</code>):"
|
||||
f" {self._get_unit_status_text(unit['formal'])} {self._get_unit_resources_consumption(unit['formal'])}"
|
||||
for unit in self.get("services", [])
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
async def _control_services(self, call: InlineCall, refresh: bool = False):
|
||||
await call.edit(
|
||||
self._get_panel(),
|
||||
reply_markup=self._get_services_markup(),
|
||||
)
|
||||
|
||||
if refresh:
|
||||
await call.answer("Information updated!")
|
||||
|
||||
def _get_unit_status_emoji(self, unit: str) -> str:
|
||||
status = self._get_unit_status_text(unit)
|
||||
if status == "active":
|
||||
return "🍏"
|
||||
elif status == "inactive":
|
||||
return "🍎"
|
||||
elif status == "failed":
|
||||
return "🚫"
|
||||
elif status == "activating":
|
||||
return "🔄"
|
||||
else:
|
||||
return "❓"
|
||||
|
||||
def _get_services_markup(self) -> list:
|
||||
return utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": (
|
||||
self._get_unit_status_emoji(service["formal"])
|
||||
+ " "
|
||||
+ service["name"]
|
||||
),
|
||||
"callback": self._control_service,
|
||||
"args": (service,),
|
||||
}
|
||||
for service in self.get("services", [])
|
||||
],
|
||||
2,
|
||||
) + [
|
||||
[
|
||||
{
|
||||
"text": self.strings("refresh_btn"),
|
||||
"callback": self._control_services,
|
||||
"args": (True,),
|
||||
},
|
||||
{"text": self.strings("close_btn"), "action": "close"},
|
||||
]
|
||||
]
|
||||
|
||||
async def unitscmd(self, message: Message):
|
||||
"""Open control panel"""
|
||||
form = await self.inline.form(
|
||||
self._get_panel(),
|
||||
message,
|
||||
reply_markup=self._get_services_markup(),
|
||||
)
|
||||
|
||||
async def addunitcmd(self, message: Message):
|
||||
"""<unit> <name> - Add new unit"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
try:
|
||||
unit, name = args.split(maxsplit=1)
|
||||
except ValueError:
|
||||
unit = args
|
||||
name = args
|
||||
|
||||
if not self._unit_exists(unit):
|
||||
await utils.answer(message, self.strings("unit_doesnt_exist").format(unit))
|
||||
return
|
||||
|
||||
self.set(
|
||||
"services",
|
||||
self.get("services", []) + [{"name": name, "formal": unit}],
|
||||
)
|
||||
await utils.answer(message, self.strings("unit_added").format(unit, name))
|
||||
|
||||
async def delunitcmd(self, message: Message):
|
||||
"""<unit> - Delete unit"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
if not any(unit["formal"] == args for unit in self.get("services", [])):
|
||||
await utils.answer(message, self.strings("unit_doesnt_exist").format(args))
|
||||
return
|
||||
|
||||
self.set(
|
||||
"services",
|
||||
[
|
||||
service
|
||||
for service in self.get("services", [])
|
||||
if service["formal"] != args
|
||||
],
|
||||
)
|
||||
await utils.answer(message, self.strings("unit_removed").format(args))
|
||||
|
||||
async def unitcmd(self, message: Message):
|
||||
"""<unit> <start|stop|restart|logs|tail> - Perform specific action on unit bypassing main menu"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args or len(args.split()) < 2:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
unit, action = args.split(maxsplit=1)
|
||||
if not self._unit_exists(unit):
|
||||
await utils.answer(message, self.strings("unit_doesnt_exist").format(unit))
|
||||
return
|
||||
|
||||
if action in {"start", "stop", "restart", "logs"}:
|
||||
await self._manage_unit(
|
||||
utils.get_chat_id(message),
|
||||
{"formal": unit, "name": unit},
|
||||
action,
|
||||
)
|
||||
elif action == "tail":
|
||||
await self._manage_unit(
|
||||
utils.get_chat_id(message),
|
||||
{"formal": unit, "name": unit},
|
||||
"tail",
|
||||
)
|
||||
else:
|
||||
await utils.answer(message, self.strings("action_not_found").format(action))
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("unit_action_done").format(action, unit),
|
||||
)
|
||||
|
||||
async def nameunitcmd(self, message: Message):
|
||||
"""<unit> <new_name> - Rename unit"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args or len(args.split()) < 2:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
unit, name = args.split(maxsplit=1)
|
||||
if not any(unit_["formal"] == unit for unit_ in self.get("services", [])):
|
||||
await utils.answer(message, self.strings("unit_doesnt_exist").format(unit))
|
||||
return
|
||||
|
||||
self.set(
|
||||
"services",
|
||||
[
|
||||
service
|
||||
for service in self.get("services", [])
|
||||
if service["formal"] != unit
|
||||
]
|
||||
+ [{"name": name, "formal": unit}],
|
||||
)
|
||||
await utils.answer(message, self.strings("unit_renamed").format(unit, name))
|
||||
282
hikariatama/ftg/tagall.py
Normal file
282
hikariatama/ftg/tagall.py
Normal file
@@ -0,0 +1,282 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# ©️ Dan Gazizullin, 2021-2023
|
||||
# This file is a part of Hikka Userbot
|
||||
# Code is licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
|
||||
# 🌐 https://github.com/hikariatama/Hikka
|
||||
# 🔑 https://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||
# + attribution
|
||||
# + non-commercial
|
||||
# + no-derivatives
|
||||
|
||||
# You CANNOT edit this file without direct permission from the author.
|
||||
# You can redistribute this file without any changes.
|
||||
|
||||
# meta pic: https://static.dan.tatar/tagall_icon.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/tagall.jpg
|
||||
# scope: hikka_min 1.6.3
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from aiogram import Bot
|
||||
from hikkatl.tl.functions.channels import InviteToChannelRequest
|
||||
from hikkatl.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StopEvent:
|
||||
def __init__(self):
|
||||
self.state = True
|
||||
|
||||
def stop(self):
|
||||
self.state = False
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TagAllMod(loader.Module):
|
||||
"""Tags all people in chat with either inline bot or client"""
|
||||
|
||||
strings = {
|
||||
"name": "TagAll",
|
||||
"bot_error": "🚫 <b>Unable to invite inline bot to chat</b>",
|
||||
"_cfg_doc_default_message": "Default message of mentions",
|
||||
"_cfg_doc_delete": "Delete messages after tagging",
|
||||
"_cfg_doc_use_bot": "Use inline bot to tag people",
|
||||
"_cfg_doc_timeout": "What time interval to sleep between each tag message",
|
||||
"_cfg_doc_silent": "Do not send message with cancel button",
|
||||
"_cfg_doc_cycle_tagging": (
|
||||
"Tag all participants over and over again until you stop the script using"
|
||||
" the button in the message"
|
||||
),
|
||||
"_cfg_doc_cycle_delay": "Delay between each cycle of tagging in seconds",
|
||||
"gathering": "🧚♀️ <b>Calling participants of this chat...</b>",
|
||||
"cancel": "🚫 Cancel",
|
||||
"cancelled": "🧚♀️ <b>TagAll cancelled!</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"bot_error": "🚫 <b>Не получилось пригласить бота в чат</b>",
|
||||
"_cls_doc": (
|
||||
"Отмечает всех участников чата, используя инлайн бот или классическим"
|
||||
" методом"
|
||||
),
|
||||
"_cfg_doc_default_message": "Сообщение по умолчанию для тегов",
|
||||
"_cfg_doc_delete": "Удалять сообщения после тега",
|
||||
"_cfg_doc_use_bot": "Использовать бота для тегов",
|
||||
"_cfg_doc_timeout": "Время между сообщениями с тегами",
|
||||
"_cfg_doc_silent": "Не отправлять сообщение с кнопкой отмены",
|
||||
"_cfg_doc_cycle_tagging": (
|
||||
"Тегать всех участников снова и снова, пока вы не остановите скрипт,"
|
||||
" используя кнопку в сообщении"
|
||||
),
|
||||
"_cfg_doc_cycle_delay": "Задержка между циклами тегов в секундах",
|
||||
"gathering": "🧚♀️ <b>Отмечаю участников чата...</b>",
|
||||
"cancel": "🚫 Отмена",
|
||||
"cancelled": "🧚♀️ <b>Сбор участников отменен!</b>",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"bot_error": "🚫 <b>Einladung des Inline-Bots in den Chat fehlgeschlagen</b>",
|
||||
"_cfg_doc_default_message": "Standardnachricht für Erwähnungen",
|
||||
"_cfg_doc_delete": "Nachrichten nach Erwähnung löschen",
|
||||
"_cfg_doc_use_bot": "Inline-Bot verwenden, um Leute zu erwähnen",
|
||||
"_cfg_doc_timeout": (
|
||||
"Zeitintervall, in dem zwischen den Erwähnungen gewartet wird"
|
||||
),
|
||||
"_cfg_doc_silent": "Nachricht ohne Abbrechen-Button senden",
|
||||
"_cfg_doc_cycle_tagging": (
|
||||
"Alle Teilnehmer immer wieder erwähnen, bis du das Skript mit der"
|
||||
" Schaltfläche in der Nachricht stoppst"
|
||||
),
|
||||
"_cfg_doc_cycle_delay": (
|
||||
"Verzögerung zwischen jedem Zyklus der Erwähnung in Sekunden"
|
||||
),
|
||||
"gathering": "🧚♀️ <b>Erwähne Teilnehmer dieses Chats...</b>",
|
||||
"cancel": "🚫 Abbrechen",
|
||||
"cancelled": "🧚♀️ <b>TagAll abgebrochen!</b>",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"bot_error": "🚫 <b>Inline botunu sohbete davet edilemedi</b>",
|
||||
"_cfg_doc_default_message": "Varsayılan etiket mesajı",
|
||||
"_cfg_doc_delete": "Etiketledikten sonra mesajları sil",
|
||||
"_cfg_doc_use_bot": "İnsanları etiketlemek için inline botu kullan",
|
||||
"_cfg_doc_timeout": "Her etiket mesajı arasında ne kadar bekleneceği",
|
||||
"_cfg_doc_silent": "İptal düğmesi olmadan mesaj gönderme",
|
||||
"_cfg_doc_cycle_tagging": (
|
||||
"Mesajdaki düğmeyi kullanarak betiği durdurana kadar tüm katılımcıları"
|
||||
" tekrar tekrar etiketle"
|
||||
),
|
||||
"_cfg_doc_cycle_delay": "Etiketleme döngüsü arasındaki gecikme süresi (saniye)",
|
||||
"gathering": "🧚♀️ <b>Bu sohbetteki katılımcıları çağırıyorum...</b>",
|
||||
"cancel": "🚫 İptal",
|
||||
"cancelled": "🧚♀️ <b>TagAll iptal edildi!</b>",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"bot_error": (
|
||||
"🚫 <b>Inline botni chatga taklif qilish muvaffaqiyatsiz bo‘ldi</b>"
|
||||
),
|
||||
"_cfg_doc_default_message": "Odatiy etiket xabari",
|
||||
"_cfg_doc_delete": "Etiketdan so‘ng xabarlarni o‘chirish",
|
||||
"_cfg_doc_use_bot": "Odamlarni etiketlash uchun inline botdan foydalanish",
|
||||
"_cfg_doc_timeout": "Har bir etiket xabari orasida nechta kutish kerak",
|
||||
"_cfg_doc_silent": "Bekor tugmasi olmadan xabar jo‘natish",
|
||||
"_cfg_doc_cycle_tagging": (
|
||||
"Xabar bo‘yicha tugmani ishlatib, skriptni to‘xtatguncha barcha"
|
||||
" qatnashuvchilarni qayta-qayta etiketlash"
|
||||
),
|
||||
"_cfg_doc_cycle_delay": "Har bir etiketlash tsikli orasida gecikma (soniya)",
|
||||
"gathering": "🧚♀️ <b>Ushbu chatta qatnashganlarni chaqiraman...</b>",
|
||||
"cancel": "🚫 Bekor qilish",
|
||||
"cancelled": "🧚♀️ <b>TagAll bekor qilindi!</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"default_message",
|
||||
"@all",
|
||||
lambda: self.strings("_cfg_doc_default_message"),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delete",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_delete"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"use_bot",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_use_bot"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"timeout",
|
||||
0.1,
|
||||
lambda: self.strings("_cfg_doc_timeout"),
|
||||
validator=loader.validators.Float(minimum=0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent",
|
||||
False,
|
||||
lambda: self.strings("_cfg_doc_silent"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"cycle_tagging",
|
||||
False,
|
||||
lambda: self.strings("_cfg_cycle_tagging"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"cycle_delay",
|
||||
0,
|
||||
lambda: self.strings("_cfg_cycle_delay"),
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
)
|
||||
|
||||
async def cancel(self, call: InlineCall, event: StopEvent):
|
||||
event.stop()
|
||||
await call.answer(self.strings("cancel"))
|
||||
|
||||
@loader.command(
|
||||
groups=True,
|
||||
ru_doc="[текст] - Отметить всех участников чата",
|
||||
de_doc="[Text] - Alle Chatteilnehmer erwähnen",
|
||||
tr_doc="[metin] - Sohbet katılımcılarını etiketle",
|
||||
uz_doc="[matn] - Chat qatnashuvchilarini tegish",
|
||||
)
|
||||
async def tagall(self, message: Message):
|
||||
"""[text] - Tag all users in chat"""
|
||||
args = utils.get_args_raw(message)
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
if self.config["use_bot"]:
|
||||
try:
|
||||
await self._client(
|
||||
InviteToChannelRequest(message.peer_id, [self.inline.bot_username])
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings("bot_error"))
|
||||
return
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
Bot.set_instance(self.inline.bot)
|
||||
|
||||
chat_id = int(f"-100{utils.get_chat_id(message)}")
|
||||
else:
|
||||
chat_id = utils.get_chat_id(message)
|
||||
|
||||
event = StopEvent()
|
||||
|
||||
if not self.config["silent"]:
|
||||
cancel = await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("gathering"),
|
||||
reply_markup={
|
||||
"text": self.strings("cancel"),
|
||||
"callback": self.cancel,
|
||||
"args": (event,),
|
||||
},
|
||||
)
|
||||
|
||||
first, br = True, False
|
||||
while True if self.config["cycle_tagging"] else first:
|
||||
for chunk in utils.chunks(
|
||||
[
|
||||
f'<a href="tg://user?id={user.id}">\xad</a>'
|
||||
async for user in self._client.iter_participants(message.peer_id)
|
||||
],
|
||||
5,
|
||||
):
|
||||
m = await (
|
||||
self.inline.bot.send_message
|
||||
if self.config["use_bot"]
|
||||
else self._client.send_message
|
||||
)(
|
||||
chat_id,
|
||||
utils.escape_html(args or self.config["default_message"])
|
||||
+ "\xad".join(chunk),
|
||||
)
|
||||
|
||||
if self.config["delete"]:
|
||||
with contextlib.suppress(Exception):
|
||||
await m.delete()
|
||||
|
||||
async def _task():
|
||||
nonlocal event, cancel
|
||||
if not self.config["silent"]:
|
||||
return
|
||||
|
||||
while True:
|
||||
if not event.state:
|
||||
await cancel.edit(self.strings("cancelled"))
|
||||
return
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
task = asyncio.ensure_future(_task())
|
||||
await asyncio.sleep(self.config["timeout"])
|
||||
task.cancel()
|
||||
if not event.state:
|
||||
br = True
|
||||
break
|
||||
|
||||
if br:
|
||||
break
|
||||
|
||||
first = False
|
||||
if self.config["cycle_tagging"]:
|
||||
await asyncio.sleep(self.config["cycle_delay"])
|
||||
|
||||
await cancel.delete()
|
||||
176
hikariatama/ftg/teledocs.py
Normal file
176
hikariatama/ftg/teledocs.py
Normal file
@@ -0,0 +1,176 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# May be working a lil bit weird, because info was manually
|
||||
# parsed from telegram schema and official telethon search
|
||||
# mechanism was used as a base for this search
|
||||
|
||||
# meta pic: https://i.imgur.com/jH9i1SW.jpeg
|
||||
# meta banner: https://mods.hikariatama.ru/badges/teledocs.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
|
||||
import re
|
||||
|
||||
import requests as rqsts
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
|
||||
def get_message(i: dict) -> str:
|
||||
return (
|
||||
f"🔧 <a href=\"https://tl.telethon.dev/{i['link']}\">{i['result']}</a>\n\n"
|
||||
"🍙 <b>Parameters:</b>\n\n"
|
||||
f"ℹ️ <i>{utils.escape_html(re.sub(r'<.*?>', '', i['description'][0]))}</i>\n\n"
|
||||
f"{i['description'][1]}\n\n"
|
||||
"🦀 <b>Example:</b>\n\n"
|
||||
f"<pre>{utils.escape_html(i['example'])}</pre>"
|
||||
)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TeledocsMod(loader.Module):
|
||||
"""Telethon docs in your pocket"""
|
||||
|
||||
strings = {"name": "Teledocs"}
|
||||
|
||||
@staticmethod
|
||||
def _find(haystack: list, needle: str):
|
||||
if needle in haystack:
|
||||
return 0
|
||||
|
||||
haystack_index, needle_index, penalty, started = 0, 0, 0, False
|
||||
while True:
|
||||
while needle[needle_index] < "a" or needle[needle_index] > "z":
|
||||
needle_index += 1
|
||||
if needle_index == len(needle):
|
||||
return penalty
|
||||
|
||||
while haystack[haystack_index] != needle[needle_index]:
|
||||
haystack_index += 1
|
||||
if started:
|
||||
penalty += 1
|
||||
|
||||
if haystack_index == len(haystack):
|
||||
return -1
|
||||
|
||||
haystack_index += 1
|
||||
needle_index += 1
|
||||
started = True
|
||||
if needle_index == len(needle):
|
||||
return penalty
|
||||
|
||||
if haystack_index == len(haystack):
|
||||
return -1
|
||||
|
||||
def _get_search_array(self, original: list, original_urls: list, query: str):
|
||||
destination, destination_urls = [], []
|
||||
for i, (item, itemu) in enumerate(zip(original, original_urls)):
|
||||
penalty = self._find(item.lower(), query)
|
||||
if penalty > -1 and penalty < len(item) / 3:
|
||||
destination += [[item, i]]
|
||||
destination_urls += [itemu]
|
||||
|
||||
return destination, destination_urls
|
||||
|
||||
def _build_list(
|
||||
self,
|
||||
found_elements: list,
|
||||
requests: bool = False,
|
||||
constructors: bool = False,
|
||||
) -> list:
|
||||
return (
|
||||
[
|
||||
{
|
||||
"link": link,
|
||||
"result": item[0],
|
||||
"description": self._tl[
|
||||
"requests_desc" if requests else "constructors_desc"
|
||||
][item[1]],
|
||||
**(
|
||||
{"example": self._tl["requests_ex"][item[1]]}
|
||||
if requests
|
||||
else {"example": ""}
|
||||
),
|
||||
}
|
||||
for item, link in zip(*found_elements)
|
||||
]
|
||||
if requests or constructors
|
||||
else [
|
||||
{
|
||||
"link": link,
|
||||
"result": item[0],
|
||||
"description": ["", ""],
|
||||
"example": "",
|
||||
}
|
||||
for item, link in zip(*found_elements)
|
||||
]
|
||||
)
|
||||
|
||||
def search(self, query: str):
|
||||
found_requests = self._get_search_array(
|
||||
self._tl["requests"],
|
||||
self._tl["requests_urls"],
|
||||
query,
|
||||
)
|
||||
found_types = self._get_search_array(
|
||||
self._tl["types"],
|
||||
self._tl["types_urls"],
|
||||
query,
|
||||
)
|
||||
found_constructors = self._get_search_array(
|
||||
self._tl["constructors"],
|
||||
self._tl["constructors_urls"],
|
||||
query,
|
||||
)
|
||||
original = self._tl["requests"] + self._tl["constructors"]
|
||||
original_urls = self._tl["requests_urls"] + self._tl["constructors_urls"]
|
||||
destination = []
|
||||
destination_urls = []
|
||||
for item, link in zip(original, original_urls):
|
||||
if item.lower().replace("request", "") == query:
|
||||
destination += [item]
|
||||
destination_urls += [link]
|
||||
|
||||
return (
|
||||
self._build_list(found_requests, True)
|
||||
+ self._build_list(found_types)
|
||||
+ self._build_list(found_constructors, False, True)
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._tl = (
|
||||
await utils.run_sync(
|
||||
rqsts.get,
|
||||
"https://github.com/hikariatama/assets/raw/master/tl_docs.json",
|
||||
)
|
||||
).json()
|
||||
|
||||
@loader.inline_everyone
|
||||
async def tl_inline_handler(self, query: InlineCall):
|
||||
return [
|
||||
{
|
||||
"title": i["result"],
|
||||
"description": re.sub("<.*?>", "", i["description"][0]),
|
||||
"message": get_message(i),
|
||||
}
|
||||
for i in self.search(query.args)
|
||||
if i["description"][0]
|
||||
][:50]
|
||||
|
||||
async def tlcmd(self, message: Message):
|
||||
"""<ref> - Return telethon reference"""
|
||||
await utils.answer(
|
||||
message,
|
||||
get_message(self.search(utils.get_args_raw(message))[0]),
|
||||
)
|
||||
658
hikariatama/ftg/temp_chat.py
Normal file
658
hikariatama/ftg/temp_chat.py
Normal file
@@ -0,0 +1,658 @@
|
||||
__version__ = (3, 0, 0)
|
||||
|
||||
# ©️ Dan Gazizullin, 2021-2023
|
||||
# This file is a part of Hikka Userbot
|
||||
# Code is licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
|
||||
# 🌐 https://github.com/hikariatama/Hikka
|
||||
# 🔑 https://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||
# + attribution
|
||||
# + non-commercial
|
||||
# + no-derivatives
|
||||
|
||||
# You CANNOT edit this file without direct permission from the author.
|
||||
# You can redistribute this file without any changes.
|
||||
|
||||
# meta pic: https://static.dan.tatar/temp_chat_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/temp_chat.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.6.3
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import typing
|
||||
|
||||
import requests
|
||||
from hikkatl.tl.functions.channels import (
|
||||
CreateChannelRequest,
|
||||
DeleteChannelRequest,
|
||||
EditPhotoRequest,
|
||||
)
|
||||
from hikkatl.tl.functions.messages import ExportChatInviteRequest
|
||||
from hikkatl.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TmpChatInfo(typing.NamedTuple):
|
||||
until: int
|
||||
title: str
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TmpChats(loader.Module):
|
||||
"""Creates temprorary chats"""
|
||||
|
||||
strings = {
|
||||
"name": "TmpChats",
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>This chat is being"
|
||||
" deleted...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Args are incorrect<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Chat not found</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Chat"
|
||||
" </b><code>{}</code><b> will now live forever!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>An error occured"
|
||||
" while deleting this temp chat. Please, do it manually.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>This chat will be"
|
||||
" permanently deleted {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Chat <a"
|
||||
' href="{}">{}</a> have been created</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Error occured while deleting chat {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Creating temporary"
|
||||
" chat...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Чат удаляется...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Неверные"
|
||||
" аргументы</b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Чат не найден</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Чат"
|
||||
" </b><code>{}</code><b> будет жить вечно!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Произошла ошибка"
|
||||
" удаления чата. Сделай это вручную.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Этот чат будет удален"
|
||||
" {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Чат <a"
|
||||
' href="{}">{}</a> создан</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Произошла ошибка при удалении чата {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Создание временного"
|
||||
" чата...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<время> <название> - Создать новый временный чат",
|
||||
"_cmd_doc_tmpcurrent": "<время> - Создать новый временный чат",
|
||||
"_cmd_doc_tmpchats": "Показать временные чаты",
|
||||
"_cmd_doc_tmpcancel": "[chat-id] - Отменить удаление чата.",
|
||||
"_cmd_doc_tmpctime": "<chat_id> <новое время> - Изменить время жизни чата",
|
||||
"_cls_doc": "Создает временные чаты во избежание мусорки в Телеграме.",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Dieser Chat wird"
|
||||
" gelöscht...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Argumente sind"
|
||||
" falsch<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Chat nicht"
|
||||
" gefunden</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Chat"
|
||||
" </b><code>{}</code><b> wird jetzt für immer leben!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Es ist ein Fehler"
|
||||
" beim Löschen dieses temporären Chats aufgetreten. Bitte tun Sie es"
|
||||
" manuell.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Dieser Chat wird"
|
||||
" dauerhaft gelöscht {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Chat <a"
|
||||
' href="{}">{}</a> wurde erstellt</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Fehler beim Löschen des Chats {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Erstelle temporären"
|
||||
" Chat...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<Zeit> <Titel> - Erstellt neuen temporären Chat",
|
||||
"_cmd_doc_tmpcurrent": "<Zeit> - Erstellt neuen temporären Chat",
|
||||
"_cmd_doc_tmpchats": "Liste temporärer Chats",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[Chat-ID] - Deaktiviert das Löschen des Chats nach Ablauf der Zeit."
|
||||
),
|
||||
"_cmd_doc_tmpctime": "<Chat-ID> <Neue Zeit> - Ändert die Löschzeit des Chats",
|
||||
"_cls_doc": "Erstellt temporäre Chats, um den Müll in Telegram zu vermeiden.",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Bu sohbet"
|
||||
" siliniyor...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Argümanlar yanlış<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Sohbet bulunamadı</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Sohbet"
|
||||
" </b><code>{}</code><b> artık sonsuza kadar yaşayacak!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Bu geçici sohbeti"
|
||||
" silerken bir hata oluştu. Lütfen bunu yapın manuel.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Bu sohbet kalıcı"
|
||||
" olarak silinecek {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Sohbet <a"
|
||||
' href="{}">{}</a> oluşturuldu</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Sohbeti silerken hata oluştu {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Geçici sohbet"
|
||||
" oluşturuluyor...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<zaman> <başlık> - Yeni geçici sohbet oluştur",
|
||||
"_cmd_doc_tmpcurrent": "<zaman> - Yeni geçici sohbet oluştur",
|
||||
"_cmd_doc_tmpchats": "Geçici sohbetleri listele",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[sohbet-id] - Süre dolduktan sonra sohbeti silmeyi devre dışı bırakın."
|
||||
),
|
||||
"_cmd_doc_tmpctime": (
|
||||
"<sohbet_id> <yeni zaman> - Sohbet silme süresini değiştir"
|
||||
),
|
||||
"_cls_doc": "Telegram'daki çöpleri önlemek için geçici sohbetler oluşturur.",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Ushbu chat"
|
||||
" o'chirilmoqda...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Argumetlar"
|
||||
" noto'g'ri<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Chat topilmadi</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Chat"
|
||||
" </b><code>{}</code><b> doimiy yashashga o'tkazildi!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Bu vaqtli chatni"
|
||||
" o'chirishda xatolik yuz berdi. Iltimos, uni bajarib ko'ring"
|
||||
" qo'llanma.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Bu chat doimiy"
|
||||
" ravishda o'chiriladi {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Chat <a"
|
||||
' href="{}">{}</a> yaratildi</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Chatni o'chirishda xatolik yuz berdi {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Vaqtli chat"
|
||||
" yaratilmoqda...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<vaqt> <nomi> - Yangi vaqtli chat yaratish",
|
||||
"_cmd_doc_tmpcurrent": "<vaqt> - Yangi vaqtli chat yaratish",
|
||||
"_cmd_doc_tmpchats": "Vaqtli chatlarni ro'yxatdan o'tkazish",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[chat-id] - Vaqt tugaganidan so'ng chatni o'chirishni bekor qilish."
|
||||
),
|
||||
"_cmd_doc_tmpctime": (
|
||||
"<chat_id> <yangi vaqt> - Chatni o'chirish vaqti o'zgartirish"
|
||||
),
|
||||
"_cls_doc": "Telegramdagi axlatni oldini olish uchun vaqtli chatlar yaratadi.",
|
||||
}
|
||||
|
||||
strings_it = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Questa chat sta per"
|
||||
" essere eliminata...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Gli argomenti sono"
|
||||
" sbagliati<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Chat non trovata</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>La chat"
|
||||
" </b><code>{}</code><b> vivrà per sempre!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Si è verificato un"
|
||||
" errore durante l'eliminazione di questa chat temporanea. Per favore,"
|
||||
" fallo manuale.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Questa chat verrà"
|
||||
" eliminata {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Chat <a"
|
||||
' href="{}">{}</a> è stata creata</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Errore durante l'eliminazione della chat {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Creazione chat"
|
||||
" temporanea...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<tempo> <titolo> - Crea nuova chat temporanea",
|
||||
"_cmd_doc_tmpcurrent": "<tempo> - Crea nuova chat temporanea",
|
||||
"_cmd_doc_tmpchats": "Elenco chat temporanee",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[chat-id] - Disabilita l'eliminazione della chat dopo il tempo"
|
||||
" specificato."
|
||||
),
|
||||
"_cmd_doc_tmpctime": (
|
||||
"<chat_id> <nuovo tempo> - Modifica il tempo di eliminazione della chat"
|
||||
),
|
||||
"_cls_doc": "Crea chat temporanee per evitare la spazzatura in Telegram.",
|
||||
}
|
||||
|
||||
strings_es = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Este chat está siendo"
|
||||
" eliminado...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Los argumentos son"
|
||||
" incorrectos<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Chat no"
|
||||
" encontrado</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>El chat"
|
||||
" </b><code>{}</code><b> ahora vivirá para siempre!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Se ha producido un"
|
||||
" error al eliminar este chat temporal. Por favor, hágalo manualmente.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Este chat será"
|
||||
" eliminado {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Chat <a"
|
||||
' href="{}">{}</a> ha sido creado</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Error al eliminar el chat {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Creando chat"
|
||||
" temporal...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<tiempo> <título> - Crea un nuevo chat temporal",
|
||||
"_cmd_doc_tmpcurrent": "<tiempo> - Crea un nuevo chat temporal",
|
||||
"_cmd_doc_tmpchats": "Lista de chats temporales",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[chat-id] - Desactiva la eliminación del chat después del tiempo"
|
||||
" especificado."
|
||||
),
|
||||
"_cmd_doc_tmpctime": (
|
||||
"<chat_id> <nuevo tiempo> - Cambia el tiempo de eliminación del chat"
|
||||
),
|
||||
"_cls_doc": "Crea chats temporales para evitar la basura en Telegram.",
|
||||
}
|
||||
|
||||
strings_fr = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Ce chat est en train"
|
||||
" d'être supprimé...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Les arguments sont"
|
||||
" incorrects<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Chat introuvable</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Le chat"
|
||||
" </b><code>{}</code><b> vivra maintenant pour toujours!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Une erreur s'est"
|
||||
" produite lors de la suppression de ce chat temporaire. S'il vous plaît,"
|
||||
" faites-le manuellement.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Ce chat sera"
|
||||
" définitivement supprimé {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Chat <a"
|
||||
' href="{}">{}</a> a été créé</b>'
|
||||
),
|
||||
"delete_error_me": (
|
||||
"🚫 <b>Une erreur s'est produite lors de la suppression du chat {}</b>"
|
||||
),
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Création du chat"
|
||||
" temporaire...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<temps> <titre> - Crée un nouveau chat temporaire",
|
||||
"_cmd_doc_tmpcurrent": "<temps> - Crée un nouveau chat temporaire",
|
||||
"_cmd_doc_tmpchats": "Liste des chats temporaires",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[chat-id] - Désactive la suppression du chat après le délai spécifié."
|
||||
),
|
||||
"_cmd_doc_tmpctime": (
|
||||
"<chat_id> <nouveau temps> - Modifie le temps de suppression du chat"
|
||||
),
|
||||
"_cls_doc": "Crée des chats temporaires pour éviter les déchets dans Telegram.",
|
||||
}
|
||||
|
||||
strings_kk = {
|
||||
"chat_is_being_removed": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Бұл сөйлеу"
|
||||
" өшірілуде...</b>"
|
||||
),
|
||||
"args": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Аргументтер дұрыс"
|
||||
" емес<b>"
|
||||
),
|
||||
"chat_not_found": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> <b>Сөйлеу табылмады</b>"
|
||||
),
|
||||
"tmp_cancelled": (
|
||||
"<emoji document_id=5463081281048818043>✅</emoji> <b>Сөйлеу"
|
||||
" </b><code>{}</code><b> қазірдің күніне дейін өмір сүрер!</b>"
|
||||
),
|
||||
"delete_error": (
|
||||
"<emoji document_id=5463358164705489689>⛔️</emoji> <b>Осы уақыттың сөйлеуін"
|
||||
" жою кезінде қате пайда болды. Өтінемін, оны қолмен орындаңыз.</b>"
|
||||
),
|
||||
"temp_chat_header": (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Бұл сөйлеу өшіріледі"
|
||||
" {}.</b>"
|
||||
),
|
||||
"chat_created": (
|
||||
"<emoji document_id=5465465194056525619>👍</emoji> <b>Сөйлеу <a"
|
||||
' href="{}">{}</a> жасалды</b>'
|
||||
),
|
||||
"delete_error_me": "🚫 <b>Сөйлеуді жою кезінде қате пайда болды {}</b>",
|
||||
"creating": (
|
||||
"<emoji document_id=5416081784641168838>🟢</emoji> <b>Уақытша сөйлеу"
|
||||
" жасау...</b>"
|
||||
),
|
||||
"_cmd_doc_tmpchat": "<уақыт> <атауы> - Жаңа уақытша сөйлеу жасау",
|
||||
"_cmd_doc_tmpcurrent": "<уақыт> - Жаңа уақытша сөйлеу жасау",
|
||||
"_cmd_doc_tmpchats": "Уақытша сөйлеулер тізімі",
|
||||
"_cmd_doc_tmpcancel": (
|
||||
"[сөйлеу-ID] - Уақыт аяқталғаннан кейін сөйлеуді жоюды өшіру."
|
||||
),
|
||||
"_cmd_doc_tmpctime": "<сөйлеу_ID> <жаңа уақыт> - Сөйлеуді жою уақытын өзгерту",
|
||||
"_cls_doc": "Telegramдағы тығыздықты алу үшін уақытша сөйлеулер жасайды.",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._chats: typing.Dict[str, TmpChatInfo] = None
|
||||
|
||||
async def client_ready(self):
|
||||
self._chats = self.pointer("chats", {}, item_type=TmpChatInfo)
|
||||
|
||||
@staticmethod
|
||||
def extract_time(t: str) -> int:
|
||||
"""
|
||||
Tries to export time from text
|
||||
"""
|
||||
try:
|
||||
if not str(t)[:-1].isdigit():
|
||||
return 0
|
||||
|
||||
if "d" in str(t):
|
||||
t = int(t[:-1]) * 60 * 60 * 24
|
||||
|
||||
if "h" in str(t):
|
||||
t = int(t[:-1]) * 60 * 60
|
||||
|
||||
if "m" in str(t):
|
||||
t = int(t[:-1]) * 60
|
||||
|
||||
if "s" in str(t):
|
||||
t = int(t[:-1])
|
||||
|
||||
t = int(re.sub(r"[^0-9]", "", str(t)))
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
return t
|
||||
|
||||
@loader.loop(interval=60, autostart=True)
|
||||
async def chats_handler_async(self):
|
||||
for chat, info in dict(self._chats).items():
|
||||
if info.until > time.time():
|
||||
continue
|
||||
|
||||
try:
|
||||
await self._client.send_message(
|
||||
int(chat),
|
||||
self.strings("chat_is_being_removed"),
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
await self._client(DeleteChannelRequest(int(chat)))
|
||||
except Exception:
|
||||
logger.exception("Failed to delete chat")
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
self.strings("delete_error_me").format(
|
||||
utils.escape_html(info.title)
|
||||
),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
self._chats.pop(chat)
|
||||
|
||||
@loader.command()
|
||||
async def tmpchat(self, message: Message):
|
||||
"""<time> <title> - Create new temporary chat"""
|
||||
if not (args := utils.get_args_raw(message)) or len(args.split()) < 2:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
until, title = args.split(maxsplit=1)
|
||||
if until != "0" and not (until := self.extract_time(until)):
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("creating"))
|
||||
|
||||
channel = (
|
||||
await self._client(
|
||||
CreateChannelRequest(
|
||||
title,
|
||||
"",
|
||||
megagroup=True,
|
||||
)
|
||||
)
|
||||
).chats[0]
|
||||
|
||||
await self._client(
|
||||
EditPhotoRequest(
|
||||
channel=channel,
|
||||
photo=await self._client.upload_file(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://api.dicebear.com/7.x/shapes/png?seed={utils.rand(10)}",
|
||||
)
|
||||
).content,
|
||||
file_name="photo.png",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
await self._client.delete_messages(channel, 2)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("chat_created").format(
|
||||
(await self._client(ExportChatInviteRequest(channel))).link,
|
||||
utils.escape_html(title),
|
||||
),
|
||||
)
|
||||
|
||||
if until != "0":
|
||||
await (
|
||||
await (
|
||||
await self._client.send_message(
|
||||
channel.id,
|
||||
self.strings("temp_chat_header").format(
|
||||
datetime.datetime.utcfromtimestamp(
|
||||
time.time() + until + 10800
|
||||
).strftime("%d.%m.%Y %H:%M:%S"),
|
||||
),
|
||||
)
|
||||
).pin()
|
||||
).delete()
|
||||
|
||||
self._chats[str(channel.id)] = TmpChatInfo(until + time.time(), title)
|
||||
|
||||
@loader.command()
|
||||
async def tmpcurrent(self, message: Message):
|
||||
"""<time> - Make current chat temporary"""
|
||||
if not (args := utils.get_args_raw(message)) or not (
|
||||
until := self.extract_time(args)
|
||||
):
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
channel_id = utils.get_chat_id(message)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("temp_chat_header").format(
|
||||
datetime.datetime.utcfromtimestamp(
|
||||
time.time() + until + 10800
|
||||
).strftime("%d.%m.%Y %H:%M:%S"),
|
||||
),
|
||||
)
|
||||
|
||||
self._chats[str(channel_id)] = TmpChatInfo(
|
||||
until + time.time(),
|
||||
(await self._client.get_entity(channel_id)).title,
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def tmpchats(self, message: Message):
|
||||
"""List temp chats"""
|
||||
text = (
|
||||
"<emoji document_id=5778550614669660455>⏲</emoji> <b>Temporary Chats</b>\n"
|
||||
)
|
||||
for chat, info in dict(self._chats).items():
|
||||
text += (
|
||||
f"<b>{utils.escape_html(info.title)}</b> (<code>{chat}</code>)<b>:"
|
||||
f" {datetime.datetime.utcfromtimestamp(info.until).strftime('%d.%m.%Y %H:%M:%S')}.</b>\n"
|
||||
)
|
||||
|
||||
await utils.answer(message, text)
|
||||
|
||||
@loader.command()
|
||||
async def tmpcancel(self, message: Message):
|
||||
"""[chat-id] - Disable deleting chat by id, or current chat if unspecified."""
|
||||
if (args := utils.get_args_raw(message)) not in self._chats:
|
||||
args = str(utils.get_chat_id(message))
|
||||
|
||||
if args not in self._chats:
|
||||
await utils.answer(message, self.strings("chat_not_found"))
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("tmp_cancelled").format(
|
||||
utils.escape_html(self._chats[args].title)
|
||||
),
|
||||
)
|
||||
self._chats.pop(args)
|
||||
|
||||
@loader.command()
|
||||
async def tmpctime(self, message: Message):
|
||||
"""[chat_id] <new_time> - Change chat deletion time"""
|
||||
if not (args := utils.get_args_raw(message)):
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
args = args.split()
|
||||
|
||||
if len(args) >= 2:
|
||||
chat = args[0]
|
||||
new_time = self.extract_time(args[1])
|
||||
else:
|
||||
chat = str(utils.get_chat_id(message))
|
||||
new_time = self.extract_time(args[0])
|
||||
|
||||
if chat not in self._chats:
|
||||
await utils.answer(message, self.strings("chat_not_found"))
|
||||
return
|
||||
|
||||
self._chats[chat] = TmpChatInfo(
|
||||
new_time + time.time() + 10800,
|
||||
(await self._client.get_entity(int(chat))).title,
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("temp_chat_header").format(
|
||||
datetime.datetime.utcfromtimestamp(
|
||||
new_time + time.time() + 10800
|
||||
).strftime("%d.%m.%Y %H:%M:%S"),
|
||||
),
|
||||
)
|
||||
745
hikariatama/ftg/terminal.py
Normal file
745
hikariatama/ftg/terminal.py
Normal file
@@ -0,0 +1,745 @@
|
||||
# scope: hikka_min 1.2.10
|
||||
# Friendly Telegram (telegram userbot)
|
||||
# Copyright (C) 2018-2019 The Authors
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/terminal_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/terminal.jpg
|
||||
# meta developer: @bsolute
|
||||
# rework: @hikariatama
|
||||
# scope: hikka_only
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
|
||||
import telethon
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def hash_msg(message):
|
||||
return f"{str(utils.get_chat_id(message))}/{str(message.id)}"
|
||||
|
||||
|
||||
async def read_stream(func: callable, stream, delay: float):
|
||||
last_task = None
|
||||
data = b""
|
||||
while True:
|
||||
dat = await stream.read(1)
|
||||
|
||||
if not dat:
|
||||
# EOF
|
||||
if last_task:
|
||||
# Send all pending data
|
||||
last_task.cancel()
|
||||
await func(data.decode("utf-8"))
|
||||
# If there is no last task there is inherently no data, so theres no point sending a blank string
|
||||
break
|
||||
|
||||
data += dat
|
||||
|
||||
if last_task:
|
||||
last_task.cancel()
|
||||
|
||||
last_task = asyncio.ensure_future(sleep_for_task(func, data, delay))
|
||||
|
||||
|
||||
async def sleep_for_task(func: callable, data: bytes, delay: float):
|
||||
await asyncio.sleep(delay)
|
||||
await func(data.decode("utf-8"))
|
||||
|
||||
|
||||
class MessageEditor:
|
||||
def __init__(
|
||||
self,
|
||||
message: telethon.tl.types.Message,
|
||||
command: str,
|
||||
config,
|
||||
strings,
|
||||
request_message,
|
||||
):
|
||||
self.message = message
|
||||
self.command = command
|
||||
self.stdout = ""
|
||||
self.stderr = ""
|
||||
self.rc = None
|
||||
self.redraws = 0
|
||||
self.config = config
|
||||
self.strings = strings
|
||||
self.request_message = request_message
|
||||
|
||||
async def update_stdout(self, stdout):
|
||||
self.stdout = stdout
|
||||
await self.redraw()
|
||||
|
||||
async def update_stderr(self, stderr):
|
||||
self.stderr = stderr
|
||||
await self.redraw()
|
||||
|
||||
async def redraw(self):
|
||||
text = self.strings("running").format(utils.escape_html(self.command)) # fmt: skip
|
||||
|
||||
if self.rc is not None:
|
||||
text += self.strings("finished").format(utils.escape_html(str(self.rc)))
|
||||
|
||||
text += self.strings("stdout")
|
||||
text += utils.escape_html(self.stdout[max(len(self.stdout) - 2048, 0) :])
|
||||
stderr = utils.escape_html(self.stderr[max(len(self.stderr) - 1024, 0) :])
|
||||
text += (self.strings("stderr") + stderr) if stderr else ""
|
||||
text += self.strings("end")
|
||||
|
||||
with contextlib.suppress(telethon.errors.rpcerrorlist.MessageNotModifiedError):
|
||||
try:
|
||||
self.message = await utils.answer(self.message, text)
|
||||
except telethon.errors.rpcerrorlist.MessageTooLongError as e:
|
||||
logger.error(e)
|
||||
logger.error(text)
|
||||
# The message is never empty due to the template header
|
||||
|
||||
async def cmd_ended(self, rc):
|
||||
self.rc = rc
|
||||
self.state = 4
|
||||
await self.redraw()
|
||||
|
||||
def update_process(self, process):
|
||||
pass
|
||||
|
||||
|
||||
class SudoMessageEditor(MessageEditor):
|
||||
# Let's just hope these are safe to parse
|
||||
PASS_REQ = "[sudo] password for"
|
||||
WRONG_PASS = r"\[sudo\] password for (.*): Sorry, try again\."
|
||||
TOO_MANY_TRIES = (r"\[sudo\] password for (.*): sudo: [0-9]+ incorrect password attempts") # fmt: skip
|
||||
|
||||
def __init__(self, message, command, config, strings, request_message):
|
||||
super().__init__(message, command, config, strings, request_message)
|
||||
self.process = None
|
||||
self.state = 0
|
||||
self.authmsg = None
|
||||
|
||||
def update_process(self, process):
|
||||
logger.debug("got sproc obj %s", process)
|
||||
self.process = process
|
||||
|
||||
async def update_stderr(self, stderr):
|
||||
logger.debug("stderr update " + stderr)
|
||||
self.stderr = stderr
|
||||
lines = stderr.strip().split("\n")
|
||||
lastline = lines[-1]
|
||||
lastlines = lastline.rsplit(" ", 1)
|
||||
handled = False
|
||||
|
||||
if (
|
||||
len(lines) > 1
|
||||
and re.fullmatch(self.WRONG_PASS, lines[-2])
|
||||
and lastlines[0] == self.PASS_REQ
|
||||
and self.state == 1
|
||||
):
|
||||
logger.debug("switching state to 0")
|
||||
await self.authmsg.edit(self.strings("auth_failed"))
|
||||
self.state = 0
|
||||
handled = True
|
||||
await asyncio.sleep(2)
|
||||
await self.authmsg.delete()
|
||||
|
||||
if lastlines[0] == self.PASS_REQ and self.state == 0:
|
||||
logger.debug("Success to find sudo log!")
|
||||
text = self.strings("auth_needed").format(self._tg_id)
|
||||
|
||||
try:
|
||||
await utils.answer(self.message, text)
|
||||
except telethon.errors.rpcerrorlist.MessageNotModifiedError as e:
|
||||
logger.debug(e)
|
||||
|
||||
logger.debug("edited message with link to self")
|
||||
command = "<code>" + utils.escape_html(self.command) + "</code>"
|
||||
user = utils.escape_html(lastlines[1][:-1])
|
||||
|
||||
self.authmsg = await self.message[0].client.send_message(
|
||||
"me",
|
||||
self.strings("auth_msg").format(command, user),
|
||||
)
|
||||
logger.debug("sent message to self")
|
||||
|
||||
self.message[0].client.remove_event_handler(self.on_message_edited)
|
||||
self.message[0].client.add_event_handler(
|
||||
self.on_message_edited,
|
||||
telethon.events.messageedited.MessageEdited(chats=["me"]),
|
||||
)
|
||||
|
||||
logger.debug("registered handler")
|
||||
handled = True
|
||||
|
||||
if len(lines) > 1 and (
|
||||
re.fullmatch(self.TOO_MANY_TRIES, lastline)
|
||||
and (self.state == 1 or self.state == 3 or self.state == 4)
|
||||
):
|
||||
logger.debug("password wrong lots of times")
|
||||
await utils.answer(self.message, self.strings("auth_locked"))
|
||||
await self.authmsg.delete()
|
||||
self.state = 2
|
||||
handled = True
|
||||
|
||||
if not handled:
|
||||
logger.debug("Didn't find sudo log.")
|
||||
if self.authmsg is not None:
|
||||
await self.authmsg[0].delete()
|
||||
self.authmsg = None
|
||||
self.state = 2
|
||||
await self.redraw()
|
||||
|
||||
logger.debug(self.state)
|
||||
|
||||
async def update_stdout(self, stdout):
|
||||
self.stdout = stdout
|
||||
|
||||
if self.state != 2:
|
||||
self.state = 3 # Means that we got stdout only
|
||||
|
||||
if self.authmsg is not None:
|
||||
await self.authmsg.delete()
|
||||
self.authmsg = None
|
||||
|
||||
await self.redraw()
|
||||
|
||||
async def on_message_edited(self, message):
|
||||
# Message contains sensitive information.
|
||||
if self.authmsg is None:
|
||||
return
|
||||
|
||||
logger.debug(f"got message edit update in self {str(message.id)}")
|
||||
|
||||
if hash_msg(message) == hash_msg(self.authmsg):
|
||||
# The user has provided interactive authentication. Send password to stdin for sudo.
|
||||
try:
|
||||
self.authmsg = await utils.answer(message, self.strings("auth_ongoing"))
|
||||
except telethon.errors.rpcerrorlist.MessageNotModifiedError:
|
||||
# Try to clear personal info if the edit fails
|
||||
await message.delete()
|
||||
|
||||
self.state = 1
|
||||
self.process.stdin.write(
|
||||
message.message.message.split("\n", 1)[0].encode("utf-8") + b"\n"
|
||||
)
|
||||
|
||||
|
||||
class RawMessageEditor(SudoMessageEditor):
|
||||
def __init__(
|
||||
self,
|
||||
message,
|
||||
command,
|
||||
config,
|
||||
strings,
|
||||
request_message,
|
||||
show_done=False,
|
||||
):
|
||||
super().__init__(message, command, config, strings, request_message)
|
||||
self.show_done = show_done
|
||||
|
||||
async def redraw(self):
|
||||
logger.debug(self.rc)
|
||||
|
||||
if self.rc is None:
|
||||
text = (
|
||||
"<code>"
|
||||
+ utils.escape_html(self.stdout[max(len(self.stdout) - 4095, 0) :])
|
||||
+ "</code>"
|
||||
)
|
||||
elif self.rc == 0:
|
||||
text = (
|
||||
"<code>"
|
||||
+ utils.escape_html(self.stdout[max(len(self.stdout) - 4090, 0) :])
|
||||
+ "</code>"
|
||||
)
|
||||
else:
|
||||
text = (
|
||||
"<code>"
|
||||
+ utils.escape_html(self.stderr[max(len(self.stderr) - 4095, 0) :])
|
||||
+ "</code>"
|
||||
)
|
||||
|
||||
if self.rc is not None and self.show_done:
|
||||
text += "\n" + self.strings("done")
|
||||
|
||||
logger.debug(text)
|
||||
|
||||
with contextlib.suppress(
|
||||
telethon.errors.rpcerrorlist.MessageNotModifiedError,
|
||||
telethon.errors.rpcerrorlist.MessageEmptyError,
|
||||
ValueError,
|
||||
):
|
||||
try:
|
||||
await utils.answer(self.message, text)
|
||||
except telethon.errors.rpcerrorlist.MessageTooLongError as e:
|
||||
logger.error(e)
|
||||
logger.error(text)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TerminalMod(loader.Module):
|
||||
"""Runs commands"""
|
||||
|
||||
strings = {
|
||||
"name": "Terminal",
|
||||
"fw_protect": "How long to wait in seconds between edits in commands",
|
||||
"what_to_kill": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Reply to a terminal"
|
||||
" command to terminate it</b>"
|
||||
),
|
||||
"kill_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Could not kill"
|
||||
" process</b>"
|
||||
),
|
||||
"killed": "<emoji document_id=5312526098750252863>🚫</emoji> <b>Killed</b>",
|
||||
"no_cmd": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>No command is running"
|
||||
" in that message</b>"
|
||||
),
|
||||
"running": (
|
||||
"<emoji document_id=5472111548572900003>⌨️</emoji><b> System call</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"finished": "\n<b>Exit code</b> <code>{}</code>",
|
||||
"stdout": "\n<b>📼 Stdout:</b>\n<code>",
|
||||
"stderr": (
|
||||
"</code>\n\n<b><emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Stderr:</b>\n<code>"
|
||||
),
|
||||
"end": "</code>",
|
||||
"auth_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Authentication"
|
||||
" failed,"
|
||||
" please try again</b>"
|
||||
),
|
||||
"auth_needed": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji><a"
|
||||
' href="tg://user?id={}">'
|
||||
" Interactive authentication required</a>"
|
||||
),
|
||||
"auth_msg": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji> <b>Please edit this"
|
||||
" message to the password for</b> <code>{}</code> <b>to run</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"auth_locked": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Authentication"
|
||||
" failed,"
|
||||
" please try again later</b>"
|
||||
),
|
||||
"auth_ongoing": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Authenticating...</b>"
|
||||
),
|
||||
"done": "<emoji document_id=5314250708508220914>✅</emoji> <b>Done</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"fw_protect": "Задержка между редактированиями",
|
||||
"what_to_kill": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Ответь на выполняемую"
|
||||
" команду для ее завершения</b>"
|
||||
),
|
||||
"kill_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Не могу убить"
|
||||
" процесс</b>"
|
||||
),
|
||||
"killed": "<b>Убит</b>",
|
||||
"no_cmd": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>В этом сообщении не"
|
||||
" выполняется команда</b>"
|
||||
),
|
||||
"running": (
|
||||
"<emoji document_id=5472111548572900003>⌨️</emoji><b> Системная команда</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"finished": "\n<b>Код выхода </b> <code>{}</code>",
|
||||
"stdout": "\n<b>📼 Вывод:</b>\n<code>",
|
||||
"stderr": (
|
||||
"</code>\n\n<b><emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Ошибки:</b>\n<code>"
|
||||
),
|
||||
"end": "</code>",
|
||||
"auth_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Аутентификация"
|
||||
" неуспешна, попробуй еще раз</b>"
|
||||
),
|
||||
"auth_needed": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji><a"
|
||||
' href="tg://user?id={}">'
|
||||
" Необходима аутентификация</a>"
|
||||
),
|
||||
"auth_msg": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji> <b>Пожалуйста,"
|
||||
" отредактируй это сообщение с паролем от рута для</b> <code>{}</code> <b>,"
|
||||
" чтобы выполнить</b> <code>{}</code>"
|
||||
),
|
||||
"auth_locked": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Аутентификация не"
|
||||
" удалась. Попробуй позже</b>"
|
||||
),
|
||||
"auth_ongoing": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Аутентификация...</b>"
|
||||
),
|
||||
"done": "<emoji document_id=5314250708508220914>✅</emoji> <b>Ура</b>",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"fw_protect": (
|
||||
"Wie lange soll zwischen den Editierungen in Befehlen gewartet werden"
|
||||
),
|
||||
"what_to_kill": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Antworte auf einen"
|
||||
" Terminal-Befehl um ihn zu stoppen</b>"
|
||||
),
|
||||
"kill_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Konnte den Prozess"
|
||||
" nicht stoppen</b>"
|
||||
),
|
||||
"killed": "<emoji document_id=5312526098750252863>🚫</emoji> <b>Gestoppt</b>",
|
||||
"no_cmd": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Kein Befehl wird in"
|
||||
" dieser Nachricht ausgeführt</b>"
|
||||
),
|
||||
"running": (
|
||||
"<emoji document_id=5472111548572900003>⌨️</emoji><b> Systemaufruf</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"finished": "\n<b>Exit-Code</b> <code>{}</code>",
|
||||
"stdout": "\n<b>📼 Stdout:</b>\n<code>",
|
||||
"stderr": (
|
||||
"</code>\n\n<b><emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Stderr:</b>\n<code>"
|
||||
),
|
||||
"end": "</code>",
|
||||
"auth_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Authentifizierung"
|
||||
" fehlgeschlagen, bitte versuche es erneut</b>"
|
||||
),
|
||||
"auth_needed": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji><a"
|
||||
' href="tg://user?id={}">'
|
||||
" Interaktive Authentifizierung benötigt</a>"
|
||||
),
|
||||
"auth_msg": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji> <b>Bitte bearbeite diese"
|
||||
" Nachricht mit dem Passwort für</b> <code>{}</code> <b>um</b>"
|
||||
" <code>{}</code> <b>auszuführen</b>"
|
||||
),
|
||||
"auth_locked": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Authentifizierung"
|
||||
" fehlgeschlagen, bitte versuche es später erneut</b>"
|
||||
),
|
||||
"auth_ongoing": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Authentifizierung"
|
||||
" läuft...</b>"
|
||||
),
|
||||
"done": "<emoji document_id=5314250708508220914>✅</emoji> <b>Fertig</b>",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"fw_protect": "Bir komut arasındaki düzenleme süresi",
|
||||
"what_to_kill": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Çalışan bir komutu"
|
||||
" durdurmak için yanıtlayın</b>"
|
||||
),
|
||||
"kill_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>İşlemi"
|
||||
" durduramadım</b>"
|
||||
),
|
||||
"killed": "<b>Durduruldu</b>",
|
||||
"no_cmd": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Bu mesajda"
|
||||
" çalışan bir"
|
||||
" komut yok</b>"
|
||||
),
|
||||
"running": (
|
||||
"<emoji document_id=5472111548572900003>⌨️</emoji><b> Sistem komutu</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"finished": "\n<b>Çıkış kodu</b> <code>{}</code>",
|
||||
"stdout": "\n<b>📼 Stdout:</b>\n<code>",
|
||||
"stderr": (
|
||||
"</code>\n\n<b><emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Stderr:</b>\n<code>"
|
||||
),
|
||||
"end": "</code>",
|
||||
"auth_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Kimlik doğrulama"
|
||||
" başarısız, lütfen tekrar deneyin</b>"
|
||||
),
|
||||
"auth_needed": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji><a"
|
||||
' href="tg://user?id={}">'
|
||||
" Etkileşimli kimlik doğrulaması gerekli</a>"
|
||||
),
|
||||
"auth_msg": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji> <b>Lütfen bu mesajı</b>"
|
||||
" <code>{}</code> <b>için</b> <code>{}</code> <b>çalıştırmak için parola"
|
||||
" olarak düzenleyin</b>"
|
||||
),
|
||||
"auth_locked": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Kimlik doğrulama"
|
||||
" başarısız, lütfen daha sonra tekrar deneyin</b>"
|
||||
),
|
||||
"auth_ongoing": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Kimlik doğrulaması"
|
||||
" sürüyor...</b>"
|
||||
),
|
||||
"done": "<emoji document_id=5314250708508220914>✅</emoji> <b>Bitti</b>",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"fw_protect": "Buyruqlar orasidagi tahrirlash vaqti",
|
||||
"what_to_kill": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Ishga tushgan"
|
||||
" buyruqni"
|
||||
" to'xtatish uchun uni javob qilib yuboring</b>"
|
||||
),
|
||||
"kill_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Protsessni to'xtatib"
|
||||
" bo'lmadi</b>"
|
||||
),
|
||||
"killed": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>To'xtatildi</b>"
|
||||
),
|
||||
"no_cmd": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Ushbu xabarda ishga"
|
||||
" tushgan buyruq yo'q</b>"
|
||||
),
|
||||
"running": (
|
||||
"<emoji document_id=5472111548572900003>⌨️</emoji><b> Tizim buyrug'i</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"finished": "\n<b>Chiqish kodi</b> <code>{}</code>",
|
||||
"stdout": "\n<b>📼 Stdout:</b>\n<code>",
|
||||
"stderr": (
|
||||
"</code>\n\n<b><emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Stderr:</b>\n<code>"
|
||||
),
|
||||
"end": "</code>",
|
||||
"auth_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Autentifikatsiya"
|
||||
" muvaffaqiyatsiz, iltimos qayta urinib ko'ring</b>"
|
||||
),
|
||||
"auth_needed": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji><a"
|
||||
' href="tg://user?id={}">'
|
||||
" Ishlanadigan autentifikatsiya talab qilinadi</a>"
|
||||
),
|
||||
"auth_msg": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji> <b>Iltimos, ushbu"
|
||||
" xabarni</b> <code>{}</code> <b>uchun</b> <code>{}</code> <b>ishga"
|
||||
" tushurish uchun parolasi sifatida tahrirlang</b>"
|
||||
),
|
||||
"auth_locked": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>Autentifikatsiya"
|
||||
" muvaffaqiyatsiz, iltimos keyinroq qayta urinib ko'ring</b>"
|
||||
),
|
||||
"auth_ongoing": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>Autentifikatsiya"
|
||||
" davom"
|
||||
" etmoqda...</b>"
|
||||
),
|
||||
"done": "<emoji document_id=5314250708508220914>✅</emoji> <b>Tugadi</b>",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"fw_protect": "कमांड के बीच संपादन समय",
|
||||
"what_to_kill": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>कमांड चलाने के लिए"
|
||||
" उत्तर दें</b>"
|
||||
),
|
||||
"kill_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>प्रक्रिया बंद नहीं की"
|
||||
" जा सकती</b>"
|
||||
),
|
||||
"killed": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>बंद किया गया</b>"
|
||||
),
|
||||
"no_cmd": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>इस संदेश में कोई कमांड"
|
||||
" नहीं चल रहा है</b>"
|
||||
),
|
||||
"running": (
|
||||
"<emoji document_id=5472111548572900003>⌨️</emoji><b> सिस्टम कमांड</b>"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
"finished": "\n<b>बाहरी कोड</b> <code>{}</code>",
|
||||
"stdout": "\n<b>📼 Stdout:</b>\n<code>",
|
||||
"stderr": (
|
||||
"</code>\n\n<b><emoji document_id=5312526098750252863>🚫</emoji>"
|
||||
" Stderr:</b>\n<code>"
|
||||
),
|
||||
"end": "</code>",
|
||||
"auth_fail": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>प्रमाणीकरण विफल, कृपया"
|
||||
" पुन: प्रयास करें</b>"
|
||||
),
|
||||
"auth_needed": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji><a"
|
||||
' href="tg://user?id={}">'
|
||||
" इंटरैक्टिव प्रमाणीकरण की आवश्यकता है</a>"
|
||||
),
|
||||
"auth_msg": (
|
||||
"<emoji document_id=5472308992514464048>🔐</emoji> <b>कृपया इस संदेश को</b>"
|
||||
" <code>{}</code> <b>के लिए</b> <code>{}</code> <b>कमांड चलाने के लिए"
|
||||
" पासवर्ड के रूप में संपादित करें</b>"
|
||||
),
|
||||
"auth_locked": (
|
||||
"<emoji document_id=5312526098750252863>🚫</emoji> <b>प्रमाणीकरण विफल, कृपया"
|
||||
" बाद में पुन: प्रयास करें</b>"
|
||||
),
|
||||
"auth_ongoing": (
|
||||
"<emoji document_id=5213452215527677338>⏳</emoji> <b>प्रमाणीकरण चल रहा"
|
||||
" है...</b>"
|
||||
),
|
||||
"done": "<emoji document_id=5314250708508220914>✅</emoji> <b>हो गया</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"FLOOD_WAIT_PROTECT",
|
||||
2,
|
||||
lambda: self.strings("fw_protect"),
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
)
|
||||
self.activecmds = {}
|
||||
|
||||
@loader.owner
|
||||
@loader.command(
|
||||
ru_doc="<команда> - Запустить команду в системе",
|
||||
de_doc="<Befehl> - Führt einen Befehl im System aus",
|
||||
tr_doc="<komut> - Sistemde komutu çalıştırır",
|
||||
hi_doc="<कमांड> - सिस्टम में कमांड चलाएं",
|
||||
uz_doc="<buyruq> - Tizimda buyruqni ishga tushiradi",
|
||||
)
|
||||
async def terminalcmd(self, message):
|
||||
"""<command> - Execute bash command"""
|
||||
await self.run_command(message, utils.get_args_raw(message))
|
||||
|
||||
@loader.owner
|
||||
@loader.command(
|
||||
ru_doc="Сокращение для '.terminal apt'",
|
||||
de_doc="Abkürzung für '.terminal apt'",
|
||||
tr_doc="'terminal apt' kısaltması",
|
||||
hi_doc="'.terminal apt' के लिए शब्द का छोटा रूप",
|
||||
uz_doc="'terminal apt' qisqartmasi",
|
||||
)
|
||||
async def aptcmd(self, message):
|
||||
"""Shorthand for '.terminal apt'"""
|
||||
await self.run_command(
|
||||
message,
|
||||
("apt " if os.geteuid() == 0 else "sudo -S apt ")
|
||||
+ utils.get_args_raw(message)
|
||||
+ " -y",
|
||||
RawMessageEditor(
|
||||
message,
|
||||
f"apt {utils.get_args_raw(message)}",
|
||||
self.config,
|
||||
self.strings,
|
||||
message,
|
||||
True,
|
||||
),
|
||||
)
|
||||
|
||||
async def run_command(
|
||||
self,
|
||||
message: telethon.tl.types.Message,
|
||||
cmd: str,
|
||||
editor: typing.Optional[MessageEditor] = None,
|
||||
):
|
||||
if len(cmd.split(" ")) > 1 and cmd.split(" ")[0] == "sudo":
|
||||
needsswitch = True
|
||||
|
||||
for word in cmd.split(" ", 1)[1].split(" "):
|
||||
if word[0] != "-":
|
||||
break
|
||||
|
||||
if word == "-S":
|
||||
needsswitch = False
|
||||
|
||||
if needsswitch:
|
||||
cmd = " ".join([cmd.split(" ", 1)[0], "-S", cmd.split(" ", 1)[1]])
|
||||
|
||||
sproc = await asyncio.create_subprocess_shell(
|
||||
cmd,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=utils.get_base_dir(),
|
||||
)
|
||||
|
||||
if editor is None:
|
||||
editor = SudoMessageEditor(message, cmd, self.config, self.strings, message)
|
||||
|
||||
editor.update_process(sproc)
|
||||
|
||||
self.activecmds[hash_msg(message)] = sproc
|
||||
|
||||
await editor.redraw()
|
||||
|
||||
await asyncio.gather(
|
||||
read_stream(
|
||||
editor.update_stdout,
|
||||
sproc.stdout,
|
||||
self.config["FLOOD_WAIT_PROTECT"],
|
||||
),
|
||||
read_stream(
|
||||
editor.update_stderr,
|
||||
sproc.stderr,
|
||||
self.config["FLOOD_WAIT_PROTECT"],
|
||||
),
|
||||
)
|
||||
|
||||
await editor.cmd_ended(await sproc.wait())
|
||||
del self.activecmds[hash_msg(message)]
|
||||
|
||||
@loader.owner
|
||||
async def terminatecmd(self, message):
|
||||
"""[-f to force kill] - Use in reply to send SIGTERM to a process"""
|
||||
if not message.is_reply:
|
||||
await utils.answer(message, self.strings("what_to_kill"))
|
||||
return
|
||||
|
||||
if hash_msg(await message.get_reply_message()) in self.activecmds:
|
||||
try:
|
||||
if "-f" not in utils.get_args_raw(message):
|
||||
self.activecmds[
|
||||
hash_msg(await message.get_reply_message())
|
||||
].terminate()
|
||||
else:
|
||||
self.activecmds[hash_msg(await message.get_reply_message())].kill()
|
||||
except Exception:
|
||||
logger.exception("Killing process failed")
|
||||
await utils.answer(message, self.strings("kill_fail"))
|
||||
else:
|
||||
await utils.answer(message, self.strings("killed"))
|
||||
else:
|
||||
await utils.answer(message, self.strings("no_cmd"))
|
||||
339
hikariatama/ftg/tgstatus.py
Normal file
339
hikariatama/ftg/tgstatus.py
Normal file
@@ -0,0 +1,339 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://img.icons8.com/cotton/344/like--v2.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/tgstatus.jpg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.4.2
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from telethon.tl.functions.messages import (
|
||||
GetCustomEmojiDocumentsRequest,
|
||||
GetStickerSetRequest,
|
||||
)
|
||||
from telethon.tl.types import Message, MessageEntityCustomEmoji
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TgStatus(loader.Module):
|
||||
"""Rotates Telegram status for Telegram Premium users only"""
|
||||
|
||||
strings = {
|
||||
"name": "TgStatus",
|
||||
"noargs": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>You must specify"
|
||||
" interval of status rotation and at least one custom emoji!</b>"
|
||||
),
|
||||
"status_started": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Status rotation"
|
||||
" started!</b>\n\n<emoji document_id=5451732530048802485>⏳</emoji>"
|
||||
" <b>Interval: every {} minute(-s)</b>\n<b>Emojis: </b>{}"
|
||||
),
|
||||
"status_stopped": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Status rotation"
|
||||
" stopped!</b>"
|
||||
),
|
||||
"no_status": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Status rotation"
|
||||
" is not"
|
||||
" running!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"noargs": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Вы должны"
|
||||
" указать интервал смены статуса и хотя бы один кастомный эмодзи!</b>"
|
||||
),
|
||||
"status_started": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Смена статуса"
|
||||
" запущена!</b>\n\n<emoji document_id=5451732530048802485>⏳</emoji>"
|
||||
" <b>Интервал: каждые {} минут(-ы)</b>\n<b>Эмодзи: </b>{}"
|
||||
),
|
||||
"status_stopped": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Смена статуса"
|
||||
" остановлена!</b>"
|
||||
),
|
||||
"no_status": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Смена статуса не"
|
||||
" запущена!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"noargs": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Du musst"
|
||||
" einen Intervall für den Statuswechsel angeben und mindestens einen"
|
||||
" benutzerdefinierten Emoji!</b>"
|
||||
),
|
||||
"status_started": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Statuswechsel"
|
||||
" gestartet!</b>\n\n<emoji document_id=5451732530048802485>⏳</emoji>"
|
||||
" <b>Intervall: alle {} Minute(n)</b>\n<b>Emojis: </b>{}"
|
||||
),
|
||||
"status_stopped": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Statuswechsel"
|
||||
" gestoppt!</b>"
|
||||
),
|
||||
"no_status": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Es läuft"
|
||||
" kein Statuswechsel!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"noargs": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>आपको स्थिति रोटेशन के"
|
||||
" लिए इंटरवल और कम से कम एक कस्टम इमोजी निर्दिष्ट करना होगा!</b>"
|
||||
),
|
||||
"status_started": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>स्थिति रोटेशन शुरू हो"
|
||||
" गया!</b>\n\n<emoji document_id=5451732530048802485>⏳</emoji> <b>अंतराल:"
|
||||
" प्रत्येक {} मिनट(-s)</b>\n<b>इमोजी: </b>{}"
|
||||
),
|
||||
"status_stopped": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>स्थिति रोटेशन बंद हो"
|
||||
" गया!</b>"
|
||||
),
|
||||
"no_status": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>स्थिति रोटेशन शुरू"
|
||||
" नहीं हुआ है!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"noargs": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Durum döngüsü için"
|
||||
" bir döngü süresi ve en az bir özel emoji belirtmelisin!</b>"
|
||||
),
|
||||
"status_started": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Durum döngüsü"
|
||||
" başladı!</b>\n\n<emoji document_id=5451732530048802485>⏳</emoji>"
|
||||
" <b>Döngü"
|
||||
" süresi: her {} dakika</b>\n<b>Emojiler: </b>{}"
|
||||
),
|
||||
"status_stopped": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Durum döngüsü"
|
||||
" durduruldu!</b>"
|
||||
),
|
||||
"no_status": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Durum döngüsü"
|
||||
" çalışmıyor!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"noargs": (
|
||||
"<emoji document_id=6053166094816905153>💀</emoji> <b>Savol javobi"
|
||||
" uchun vaqt oraligini va kamida bitta maxsus emoji belgilang!</b>"
|
||||
),
|
||||
"status_started": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Savol javobi"
|
||||
" boshlandi!</b>\n\n<emoji document_id=5451732530048802485>⏳</emoji>"
|
||||
" <b>Oraliq: har {} minut(-lar)</b>\n<b>Emojilar: </b>{}"
|
||||
),
|
||||
"status_stopped": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Savol javobi"
|
||||
" to'xtatildi!</b>"
|
||||
),
|
||||
"no_status": (
|
||||
"<emoji document_id=5789838291234720526>💸</emoji> <b>Savol javobi"
|
||||
" boshlanmagan!</b>"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
if not self._client.hikka_me.premium:
|
||||
raise loader.LoadError("⭐️ This module is for Telegram Premium only!")
|
||||
|
||||
self.status = self.pointer("status", [])
|
||||
self.status_loop.start()
|
||||
|
||||
@loader.loop(interval=1)
|
||||
async def status_loop(self):
|
||||
if (
|
||||
not self.status
|
||||
or not self.get("interval")
|
||||
or self.get("last_change", 0) + self.get("interval") > time.time()
|
||||
):
|
||||
return
|
||||
|
||||
await self._client.set_status(self.status[self.get("current_status", 0)])
|
||||
logger.debug(f"Status changed to {self.status[self.get('current_status', 0)]}")
|
||||
self.set("current_status", self.get("current_status", 0) + 1)
|
||||
|
||||
if self.get("current_status") >= len(self.status):
|
||||
self.set("current_status", 0)
|
||||
|
||||
self.set("last_change", int(time.time()))
|
||||
|
||||
@loader.command(
|
||||
ru_doc=(
|
||||
"<кастомные эмодзи для статуса> <интервал в минутах> - Запустить ротацию"
|
||||
" статуса с интервалом в минутах"
|
||||
),
|
||||
de_doc=(
|
||||
"<benutzerdefinierte Emojis für den Status> <Intervall in Minuten> - Starte"
|
||||
" den Status-Rotationszyklus mit einem Intervall in Minuten"
|
||||
),
|
||||
tr_doc=(
|
||||
"<özel emoji durumu için> <dakika aralığı> - Dakika aralığı ile"
|
||||
" durum döngüsünü başlat"
|
||||
),
|
||||
uz_doc=(
|
||||
"<status uchun maxsus emojilar> <daqiqa oraligi> - Daqiqa oraligi bilan"
|
||||
" savol javobini ishga tushirish"
|
||||
),
|
||||
hi_doc=(
|
||||
"<स्थिति के लिए कस्टम इमोजी> <मिनट अंतराल> - मिनट अंतराल के साथ"
|
||||
" स्थिति रोटेशन चक्र शुरू करें"
|
||||
),
|
||||
)
|
||||
async def tgstatus(self, message: Message):
|
||||
"""<custom emojis for statuses> <time to rotate in minutes> - Start status rotation with interval in minutes"""
|
||||
args = utils.get_args_raw(message)
|
||||
args = "".join(s for s in args if s.isdigit())
|
||||
if not args or not any(
|
||||
isinstance(entity, MessageEntityCustomEmoji) for entity in message.entities
|
||||
):
|
||||
await utils.answer(message, self.strings("noargs"))
|
||||
return
|
||||
|
||||
self.status.clear()
|
||||
self.status.extend(
|
||||
[
|
||||
entity.document_id
|
||||
for entity in message.entities
|
||||
if isinstance(entity, MessageEntityCustomEmoji)
|
||||
]
|
||||
)
|
||||
self.set("interval", int(args) * 60)
|
||||
self.set("last_change", 0)
|
||||
self.set("current_status", 0)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("status_started").format(
|
||||
args,
|
||||
"".join(
|
||||
f"<emoji document_id={emoji.document_id}>▫️</emoji>"
|
||||
for emoji in message.entities
|
||||
if isinstance(emoji, MessageEntityCustomEmoji)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc=(
|
||||
"<кастомные эмодзи для получения паков> <интервал в минутах> - Запустить"
|
||||
" ротацию статуса с интервалом в минутах, используя полный пак указанных"
|
||||
" эмодзи"
|
||||
),
|
||||
de_doc=(
|
||||
"<benutzerdefinierte Emojis für das Erhalten von Paketen> <Intervall in"
|
||||
" Minuten> - Starte den Status-Rotationszyklus mit einem Intervall in"
|
||||
" Minuten, indem du das volle Paket der angegebenen Emojis verwendest"
|
||||
),
|
||||
tr_doc=(
|
||||
"<emoji paketleri almak için özel emoji> <dakika aralığı> - Dakika aralığı"
|
||||
" ile belirtilen emoji paketini kullanarak durum döngüsünü başlat"
|
||||
),
|
||||
uz_doc=(
|
||||
"<emoji paketlarini olish uchun maxsus emojilar> <daqiqa oraligi> - Daqiqa"
|
||||
" oraligi bilan belgilangan emoji paketini ishlatib savol javobini ishga"
|
||||
" tushirish"
|
||||
),
|
||||
hi_doc=(
|
||||
"<पैकेट प्राप्त करने के लिए कस्टम इमोजी> <मिनट अंतराल> - मिनट अंतराल"
|
||||
" के साथ निर्दिष्ट इमोजी का पूरा पैकेट उपयोग करके स्थिति रोटेशन चक्र"
|
||||
" शुरू करें"
|
||||
),
|
||||
)
|
||||
async def tgstatuspack(self, message: Message):
|
||||
"""<custom emojis for pack search> <time to rotate in minutes> - Start status rotation with interval in minutes using full pack of specified emojis"""
|
||||
args = utils.get_args_raw(message)
|
||||
args = "".join(s for s in args if s.isdigit())
|
||||
if not args or not any(
|
||||
isinstance(entity, MessageEntityCustomEmoji) for entity in message.entities
|
||||
):
|
||||
await utils.answer(message, self.strings("noargs"))
|
||||
return
|
||||
|
||||
self.status.clear()
|
||||
self.status.extend(
|
||||
utils.array_sum(
|
||||
[
|
||||
[
|
||||
emoji.id
|
||||
for emoji in (
|
||||
await self._client(GetStickerSetRequest(stickerset, hash=0))
|
||||
).documents
|
||||
]
|
||||
for stickerset in filter(
|
||||
lambda x: x,
|
||||
[
|
||||
next(
|
||||
(
|
||||
attr.stickerset
|
||||
for attr in emoji.attributes
|
||||
if hasattr(attr, "stickerset")
|
||||
),
|
||||
None,
|
||||
)
|
||||
for emoji in await self._client(
|
||||
GetCustomEmojiDocumentsRequest(
|
||||
[
|
||||
entity.document_id
|
||||
for entity in message.entities
|
||||
if isinstance(entity, MessageEntityCustomEmoji)
|
||||
]
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
self.set("interval", int(args) * 60)
|
||||
self.set("last_change", 0)
|
||||
self.set("current_status", 0)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("status_started").format(
|
||||
args,
|
||||
"".join(
|
||||
f"<emoji document_id={emoji}>▫️</emoji>" for emoji in self.status
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Остановить статус",
|
||||
de_doc="Stoppe den Status",
|
||||
tr_doc="Durum durdur",
|
||||
uz_doc="Savol javobini to'xtatish",
|
||||
hi_doc="स्थिति रोकें",
|
||||
)
|
||||
async def untgstatus(self, message: Message):
|
||||
"""Stop status rotation"""
|
||||
if not self.status:
|
||||
await utils.answer(message, self.strings("no_status"))
|
||||
return
|
||||
|
||||
self.status.clear()
|
||||
self.set("interval", 0)
|
||||
self.set("last_change", 0)
|
||||
self.set("current_status", 0)
|
||||
|
||||
await utils.answer(message, self.strings("status_stopped"))
|
||||
570
hikariatama/ftg/tictactoe.py
Normal file
570
hikariatama/ftg/tictactoe.py
Normal file
@@ -0,0 +1,570 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/tictactoe_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/tictactoe.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import copy
|
||||
import enum
|
||||
from random import choice
|
||||
from typing import List
|
||||
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
phrases = [
|
||||
"Your brain is just a joke... Use it!",
|
||||
"What a nice move...",
|
||||
"Try to overcome me!",
|
||||
"I'm irresistible, you have no chances!",
|
||||
"The clock is ticking... Hurry up.",
|
||||
"Don't act, stop to think!",
|
||||
"It was your choice, not mine...",
|
||||
]
|
||||
|
||||
# AI from https://github.com/morgankenyon/RandomML/tree/master/src/tictactoe
|
||||
|
||||
|
||||
class Player(enum.Enum):
|
||||
x = 1
|
||||
o = 2
|
||||
|
||||
@property
|
||||
def other(self):
|
||||
return Player.x if self == Player.o else Player.o
|
||||
|
||||
|
||||
class Choice:
|
||||
def __init__(self, move, value, depth):
|
||||
self.move = move
|
||||
self.value = value
|
||||
self.depth = depth
|
||||
|
||||
def __str__(self):
|
||||
return f"{str(self.move)}: {str(self.value)}"
|
||||
|
||||
|
||||
class AbBot:
|
||||
def __init__(self, player):
|
||||
self.player = player
|
||||
|
||||
def alpha_beta_search(self, board, is_max, current_player, depth, alpha, beta):
|
||||
# if board has a winner or is a tie
|
||||
# return with appropriate values
|
||||
winner = board.has_winner()
|
||||
if winner == self.player:
|
||||
return Choice(board.last_move(), 10 - depth, depth)
|
||||
elif winner == self.player.other:
|
||||
return Choice(board.last_move(), -10 + depth, depth)
|
||||
elif len(board.moves) == 9:
|
||||
return Choice(board.last_move(), 0, depth)
|
||||
|
||||
candidates = board.get_legal_moves()
|
||||
max_choice = None
|
||||
min_choice = None
|
||||
for i in range(len(candidates)):
|
||||
row = candidates[i][0]
|
||||
col = candidates[i][1]
|
||||
newboard = copy.deepcopy(board)
|
||||
newboard.make_move(row, col, current_player)
|
||||
result = self.alpha_beta_search(
|
||||
newboard, not is_max, current_player.other, depth + 1, alpha, beta
|
||||
)
|
||||
result.move = newboard.last_move()
|
||||
|
||||
if is_max:
|
||||
alpha = max(result.value, alpha)
|
||||
if alpha >= beta:
|
||||
return result
|
||||
|
||||
if max_choice is None or result.value > max_choice.value:
|
||||
max_choice = result
|
||||
else:
|
||||
beta = min(result.value, beta)
|
||||
if alpha >= beta:
|
||||
return result
|
||||
|
||||
if min_choice is None or result.value < min_choice.value:
|
||||
min_choice = result
|
||||
|
||||
return max_choice if is_max else min_choice
|
||||
|
||||
def select_move(self, board):
|
||||
choice = self.alpha_beta_search(board, True, self.player, 0, -100, 100)
|
||||
return choice.move
|
||||
|
||||
|
||||
MARKER_TO_CHAR = {
|
||||
None: " . ",
|
||||
Player.x: " x ",
|
||||
Player.o: " o ",
|
||||
}
|
||||
|
||||
|
||||
class Board:
|
||||
def __init__(self):
|
||||
self.dimension = 3
|
||||
self.grid = [
|
||||
[None for _ in range(self.dimension)] for _ in range(self.dimension)
|
||||
]
|
||||
|
||||
self.moves = []
|
||||
|
||||
def print(self):
|
||||
print()
|
||||
for row in range(self.dimension):
|
||||
line = [
|
||||
MARKER_TO_CHAR[self.grid[row][col]] for col in range(self.dimension)
|
||||
]
|
||||
print("%s" % "".join(line))
|
||||
|
||||
def has_winner(self):
|
||||
# need at least 5 moves before x hits three in a row
|
||||
if len(self.moves) < 5:
|
||||
return None
|
||||
|
||||
# check rows for win
|
||||
for row in range(self.dimension):
|
||||
unique_rows = set(self.grid[row])
|
||||
if len(unique_rows) == 1:
|
||||
value = unique_rows.pop()
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
# check columns for win
|
||||
for col in range(self.dimension):
|
||||
unique_cols = {self.grid[row][col] for row in range(self.dimension)}
|
||||
if len(unique_cols) == 1:
|
||||
value = unique_cols.pop()
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
# check backwards diagonal (top left to bottom right) for win
|
||||
backwards_diag = {self.grid[0][0], self.grid[1][1], self.grid[2][2]}
|
||||
if len(backwards_diag) == 1:
|
||||
value = backwards_diag.pop()
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
# check forwards diagonal (bottom left to top right) for win
|
||||
forwards_diag = {self.grid[2][0], self.grid[1][1], self.grid[0][2]}
|
||||
if len(forwards_diag) == 1:
|
||||
value = forwards_diag.pop()
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
# found no winner, return None
|
||||
return None
|
||||
|
||||
def make_move(self, row, col, player):
|
||||
if self.is_space_empty(row, col):
|
||||
self.grid[row][col] = player
|
||||
self.moves.append([row, col])
|
||||
else:
|
||||
raise Exception("Attempting to move onto already occupied space")
|
||||
|
||||
def last_move(self):
|
||||
return self.moves[-1]
|
||||
|
||||
def is_space_empty(self, row, col):
|
||||
return self.grid[row][col] is None
|
||||
|
||||
def get_legal_moves(self):
|
||||
choices = []
|
||||
for row in range(self.dimension):
|
||||
choices.extend(
|
||||
[row, col]
|
||||
for col in range(self.dimension)
|
||||
if (self.is_space_empty(row, col))
|
||||
)
|
||||
|
||||
return choices
|
||||
|
||||
def __deepcopy__(self, memodict=None):
|
||||
if memodict is None:
|
||||
memodict = {}
|
||||
dp = Board()
|
||||
dp.grid = copy.deepcopy(self.grid)
|
||||
dp.moves = copy.deepcopy(self.moves)
|
||||
return dp
|
||||
|
||||
|
||||
# /AI
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TicTacToeMod(loader.Module):
|
||||
"""Play your favorite game in Telegram"""
|
||||
|
||||
strings = {
|
||||
"name": "TicTacToe",
|
||||
"gamestart": (
|
||||
"🧠 <b>You want to play, let's play!</b>\n<i>Waiting for second"
|
||||
" player...</i>"
|
||||
),
|
||||
"gamestart_ai": "🐻 <b>Bear is ready to compete! Are you?</b>",
|
||||
"game_discarded": "Game is discarded",
|
||||
"wait_for_your_turn": "Wait for your turn",
|
||||
"no_move": "This cell is not empty",
|
||||
"not_your_game": "It is not your game, don't interrupt it",
|
||||
"draw": (
|
||||
"🧠 <b>The game is over! What a pity...</b>\n<i>🐉 The game ended with"
|
||||
" <b>draw</b>. No winner, no argument...</i>"
|
||||
),
|
||||
"normal_game": (
|
||||
"🧠 <b>{}</b>\n<i>Playing with <b>{}</b></i>\n\n<i>Now is the turn of"
|
||||
" <b>{}</b></i>"
|
||||
),
|
||||
"win": (
|
||||
"🧠 <b>The game is over! What a pity...</b>\n\n<i>🏆 Winner: <b>{}"
|
||||
" ({})</b></i>\n<code>{}</code>"
|
||||
),
|
||||
"ai_game": (
|
||||
"🧠 <b>{}</b>\n<i><b>{}</b> is playing with <b>🐻"
|
||||
" Bear</b></i>\n\n<i>You are"
|
||||
" {}</i>"
|
||||
),
|
||||
"not_with_yourself": "You can't play with yourself!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"gamestart": (
|
||||
"🧠 <b>Поиграть захотелось? Поиграем!</b>\n<i>Ожидание второго"
|
||||
" игрока...</i>"
|
||||
),
|
||||
"gamestart_ai": "🐻 <b>Мишка готов сражаться! А что насчет тебя?</b>",
|
||||
"game_discarded": "Игра отменена",
|
||||
"wait_for_your_turn": "Ожидание хода",
|
||||
"no_move": "Эта клетка уже заполнена",
|
||||
"not_your_game": "Это не твоя игра, не мешай",
|
||||
"draw": (
|
||||
"🧠 <b>Игра окончена! Какая жалость...</b>\n<i>🐉 Игра закончилась"
|
||||
" <b>ничьей</b>. Нет победителя, нет спора...</i>"
|
||||
),
|
||||
"normal_game": (
|
||||
"🧠 <b>{}</b>\n<i>Игра с <b>{}</b></i>\n\n<i>Сейчас ходит <b>{}</b></i>"
|
||||
),
|
||||
"win": (
|
||||
"🧠 <b>Игра окончена! Какая жалость...</b>\n\n<i>🏆 Победитель: <b>{}"
|
||||
" ({})</b></i>\n<code>{}</code>"
|
||||
),
|
||||
"ai_game": (
|
||||
"🧠 <b>{}</b>\n<i><b>{}</b> играет с <b>🐻 Мишкой</b></i>\n\n<i>Ты {}</i>"
|
||||
),
|
||||
"not_with_yourself": "Ты не можешь играть сам с собой!",
|
||||
"_cmd_doc_tictactoe": "Начать новую игру в крестики-нолики",
|
||||
"_cmd_doc_tictacai": "Сыграть с 🐻 Мишкой (У тебя нет шансов)",
|
||||
"_cls_doc": "Сыграй в крестики-нолики прямо в Телеграм",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._games = {}
|
||||
self._me = await client.get_me()
|
||||
|
||||
async def _process_click(
|
||||
self,
|
||||
call: InlineCall,
|
||||
i: int,
|
||||
j: int,
|
||||
line: str,
|
||||
):
|
||||
if call.from_user.id not in [
|
||||
self._me.id,
|
||||
self._games[call.form["uid"]]["2_player"],
|
||||
]:
|
||||
await call.answer(self.strings("not_your_game"))
|
||||
return
|
||||
|
||||
if call.from_user.id != self._games[call.form["uid"]]["turn"]:
|
||||
await call.answer(self.strings("wait_for_your_turn"))
|
||||
return
|
||||
|
||||
if line != ".":
|
||||
await call.answer(self.strings("no_move"))
|
||||
return
|
||||
|
||||
self._games[call.form["uid"]]["score"] = (
|
||||
self._games[call.form["uid"]]["score"][: j + i * 4]
|
||||
+ self._games[call.form["uid"]]["mapping"][call.from_user.id]
|
||||
+ self._games[call.form["uid"]]["score"][j + i * 4 + 1 :]
|
||||
)
|
||||
|
||||
self._games[call.form["uid"]]["turn"] = (
|
||||
self._me.id
|
||||
if call.from_user.id != self._me.id
|
||||
else self._games[call.form["uid"]]["2_player"]
|
||||
)
|
||||
|
||||
await call.edit(**self._render(call.form["uid"]))
|
||||
|
||||
async def _process_click_ai(self, call: InlineCall, i: int, j: int, line: str):
|
||||
if call.form["uid"] not in self._games:
|
||||
await call.answer(self.strings("game_discarded"))
|
||||
await call.delete()
|
||||
|
||||
if call.from_user.id != self._games[call.form["uid"]]["user"].id:
|
||||
await call.answer(self.strings("not_your_game"))
|
||||
return
|
||||
|
||||
if line != ".":
|
||||
await call.answer(self.strings("no_move"))
|
||||
return
|
||||
|
||||
self._games[call.form["uid"]]["board"].make_move(
|
||||
i, j, self._games[call.form["uid"]]["human_player"]
|
||||
)
|
||||
|
||||
try:
|
||||
self._games[call.form["uid"]]["board"].make_move(
|
||||
*self._games[call.form["uid"]]["bot"].select_move(
|
||||
self._games[call.form["uid"]]["board"]
|
||||
),
|
||||
self._games[call.form["uid"]]["ai_player"],
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await call.edit(**self._render_ai(call.form["uid"]))
|
||||
|
||||
def win_indexes(self, n):
|
||||
return (
|
||||
[[(r, c) for r in range(n)] for c in range(n)]
|
||||
+ [[(r, c) for c in range(n)] for r in range(n)]
|
||||
+ [[(i, i) for i in range(n)]]
|
||||
+ [[(i, n - 1 - i) for i in range(n)]]
|
||||
)
|
||||
|
||||
def is_winner(self, board, decorator):
|
||||
n = len(board)
|
||||
|
||||
return any(
|
||||
all(board[r][c] == decorator for r, c in indexes)
|
||||
for indexes in self.win_indexes(n)
|
||||
)
|
||||
|
||||
def _render_text(self, board_raw: List[List[str]]) -> str:
|
||||
board = [[char.replace(".", " ") for char in line] for line in board_raw]
|
||||
return f"""
|
||||
{board[0][0]} | {board[0][1]} | {board[0][2]}
|
||||
----------
|
||||
{board[1][0]} | {board[1][1]} | {board[1][2]}
|
||||
----------
|
||||
{board[2][0]} | {board[2][1]} | {board[2][2]}"""
|
||||
|
||||
def _render(self, uid: str) -> dict:
|
||||
if uid not in self._games or uid not in self.inline._units:
|
||||
return
|
||||
|
||||
game = self._games[uid]
|
||||
text = self.strings("normal_game").format(
|
||||
choice(phrases),
|
||||
game["name"],
|
||||
(
|
||||
utils.escape_html(get_display_name(self._me))
|
||||
if game["turn"] == self._me.id
|
||||
else game["name"]
|
||||
),
|
||||
)
|
||||
score = game["score"].split("|")
|
||||
kb = []
|
||||
rmap = {v: k for k, v in game["mapping"].items()}
|
||||
|
||||
win_x, win_o = self.is_winner(score, "x"), self.is_winner(score, "o")
|
||||
|
||||
if win_o or win_x:
|
||||
try:
|
||||
del self._games[uid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
winner = rmap["x" if win_x else "o"]
|
||||
|
||||
return {
|
||||
"text": self.strings("win").format(
|
||||
(
|
||||
game["name"]
|
||||
if winner != self._me.id
|
||||
else utils.escape_html(get_display_name(self._me))
|
||||
),
|
||||
"❌" if win_x else "⭕️",
|
||||
self._render_text(score),
|
||||
)
|
||||
}
|
||||
|
||||
if game["score"].count("."):
|
||||
for i, row in enumerate(score):
|
||||
kb_row = [
|
||||
{
|
||||
"text": (
|
||||
line.replace(".", " ").replace("x", "❌").replace("o", "⭕️")
|
||||
),
|
||||
"callback": self._process_click,
|
||||
"args": (
|
||||
i,
|
||||
j,
|
||||
line,
|
||||
),
|
||||
}
|
||||
for j, line in enumerate(row)
|
||||
]
|
||||
kb += [kb_row]
|
||||
else:
|
||||
try:
|
||||
del self._games[uid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return {"text": self.strings("draw")}
|
||||
|
||||
return {"text": text, "reply_markup": kb}
|
||||
|
||||
async def inline__start_game(self, call: InlineCall):
|
||||
if call.from_user.id == self._me.id:
|
||||
await call.answer(self.strings("not_with_yourself"))
|
||||
return
|
||||
|
||||
uid = call.form["uid"]
|
||||
first = choice([call.from_user.id, self._me.id])
|
||||
self._games[uid] = {
|
||||
"2_player": call.from_user.id,
|
||||
"turn": first,
|
||||
"mapping": {
|
||||
first: "x",
|
||||
(call.from_user.id if call.from_user.id != first else self._me.id): "o",
|
||||
},
|
||||
"name": utils.escape_html(
|
||||
get_display_name(await self._client.get_entity(call.from_user.id))
|
||||
),
|
||||
"score": "...|...|...",
|
||||
}
|
||||
|
||||
await call.edit(**self._render(uid))
|
||||
|
||||
async def inline__start_game_ai(self, call: InlineCall):
|
||||
uid = call.form["uid"]
|
||||
|
||||
user = await self._client.get_entity(call.from_user.id)
|
||||
|
||||
first = choice(["bear", user.id])
|
||||
self._games[uid] = {
|
||||
"2_player": "bear",
|
||||
"turn": user.id,
|
||||
"mapping": {first: "x", "bear" if first != "bear" else user.id: "o"},
|
||||
"amifirst": first == user.id,
|
||||
"user": user,
|
||||
"ai_player": Player.x if first == "bear" else Player.o,
|
||||
"human_player": Player.o if first == "bear" else Player.x,
|
||||
"bot": AbBot(Player.x if first == "bear" else Player.o),
|
||||
"board": Board(),
|
||||
}
|
||||
|
||||
if first == "bear":
|
||||
self._games[uid]["board"].make_move(
|
||||
*self._games[uid]["bot"].select_move(self._games[uid]["board"]),
|
||||
self._games[uid]["ai_player"],
|
||||
)
|
||||
|
||||
await call.edit(**self._render_ai(uid))
|
||||
|
||||
async def tictactoecmd(self, message: Message):
|
||||
"""Start new tictactoe game"""
|
||||
await self.inline.form(
|
||||
self.strings("gamestart"),
|
||||
message=message,
|
||||
reply_markup={"text": "💪 Play", "callback": self.inline__start_game},
|
||||
ttl=15 * 60,
|
||||
disable_security=True,
|
||||
)
|
||||
|
||||
def _render_ai(self, uid: str) -> dict:
|
||||
if uid not in self._games or uid not in self.inline._units:
|
||||
return
|
||||
|
||||
game = self._games[uid]
|
||||
text = self.strings("ai_game").format(
|
||||
choice(phrases),
|
||||
utils.escape_html(get_display_name(game["user"])),
|
||||
"❌" if game["amifirst"] else "⭕️",
|
||||
)
|
||||
score = [
|
||||
[MARKER_TO_CHAR[char].strip() for char in line]
|
||||
for line in game["board"].grid
|
||||
]
|
||||
kb = []
|
||||
rmap = {v: k for k, v in game["mapping"].items()}
|
||||
|
||||
win_x, win_o = self.is_winner(score, "x"), self.is_winner(score, "o")
|
||||
|
||||
if win_o or win_x:
|
||||
try:
|
||||
del self._games[uid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
winner = rmap["x" if win_x else "o"]
|
||||
|
||||
return {
|
||||
"text": self.strings("win").format(
|
||||
(
|
||||
"🐻 Bear"
|
||||
if winner != game["user"]
|
||||
else utils.escape_html(get_display_name(game["user"]))
|
||||
),
|
||||
"❌" if win_x else "⭕️",
|
||||
self._render_text(score),
|
||||
)
|
||||
}
|
||||
|
||||
if "".join(["".join(line) for line in score]).count("."):
|
||||
for i, row in enumerate(score):
|
||||
kb_row = [
|
||||
{
|
||||
"text": (
|
||||
line.replace(".", " ").replace("x", "❌").replace("o", "⭕️")
|
||||
),
|
||||
"callback": self._process_click_ai,
|
||||
"args": (
|
||||
i,
|
||||
j,
|
||||
line,
|
||||
),
|
||||
}
|
||||
for j, line in enumerate(row)
|
||||
]
|
||||
kb += [kb_row]
|
||||
else:
|
||||
try:
|
||||
del self._games[uid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return {"text": self.strings("draw")}
|
||||
|
||||
return {"text": text, "reply_markup": kb}
|
||||
|
||||
async def tictacaicmd(self, message: Message):
|
||||
"""Play with 🐻 Bear (You have no chances to win)"""
|
||||
await self.inline.form(
|
||||
self.strings("gamestart_ai"),
|
||||
message=message,
|
||||
reply_markup={
|
||||
"text": "🧠 Let's go!",
|
||||
"callback": self.inline__start_game_ai,
|
||||
},
|
||||
ttl=15 * 60,
|
||||
disable_security=True,
|
||||
)
|
||||
386
hikariatama/ftg/tidal.py
Normal file
386
hikariatama/ftg/tidal.py
Normal file
@@ -0,0 +1,386 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
#
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/tidal_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/tidal.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
# requires: tidalapi
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import tidalapi
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TidalMod(loader.Module):
|
||||
"""API wrapper over TIDAL Hi-Fi music streaming service"""
|
||||
|
||||
strings = {
|
||||
"name": "Tidal",
|
||||
"args": "🚫 <b>Specify search query</b>",
|
||||
"404": "🚫 <b>No results found</b>",
|
||||
"oauth": (
|
||||
"🔑 <b>Login to TIDAL</b>\n\n<i>This link will expire in 5 minutes</i>"
|
||||
),
|
||||
"oauth_btn": "🔑 Login",
|
||||
"success": "✅ <b>Successfully logged in!</b>",
|
||||
"error": "🚫 <b>Error logging in</b>",
|
||||
"search": "🐈⬛ <b>{}</b>",
|
||||
"tidal_btn": "🐈⬛ Tidal",
|
||||
"searching": "🔍 <b>Searching...</b>",
|
||||
"tidal_like_btn": "🖤 Like",
|
||||
"tidal_dislike_btn": "💔 Dislike",
|
||||
"auth_first": "🚫 <b>You need to login first</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"args": "🚫 <b>Укажите поисковый запрос</b>",
|
||||
"404": "🚫 <b>Ничего не найдено</b>",
|
||||
"oauth": (
|
||||
"🔑 <b>Авторизуйтесь в TIDAL</b>\n\n<i>Эта ссылка будет действительна в"
|
||||
" течение 5 минут</i>"
|
||||
),
|
||||
"oauth_btn": "🔑 Авторизоваться",
|
||||
"success": "✅ <b>Успешно авторизованы!</b>",
|
||||
"error": "🚫 <b>Ошибка авторизации</b>",
|
||||
"search": "🐈⬛ <b>{}</b>",
|
||||
"tidal_btn": "🐈⬛ Tidal",
|
||||
"searching": "🔍 <b>Ищем...</b>",
|
||||
"tidal_like_btn": "🖤 Нравится",
|
||||
"tidal_dislike_btn": "💔 Не нравится",
|
||||
"auth_first": "🚫 <b>Сначала нужно авторизоваться</b>",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"args": "🚫 <b>Gib einen Suchbegriff an</b>",
|
||||
"404": "🚫 <b>Nichts gefunden</b>",
|
||||
"oauth": (
|
||||
"🔑 <b>Logge dich bei TIDAL ein</b>\n\n<i>Dieser Link ist 5 Minuten lang"
|
||||
" gültig</i>"
|
||||
),
|
||||
"oauth_btn": "🔑 Einloggen",
|
||||
"success": "✅ <b>Erfolgreich eingeloggt!</b>",
|
||||
"error": "🚫 <b>Fehler beim Einloggen</b>",
|
||||
"search": "🐈⬛ <b>{}</b>",
|
||||
"tidal_btn": "🐈⬛ Tidal",
|
||||
"searching": "🔍 <b>Suche...</b>",
|
||||
"tidal_like_btn": "🖤 Gefällt mir",
|
||||
"tidal_dislike_btn": "💔 Gefällt mir nicht",
|
||||
"auth_first": "🚫 <b>Du musst dich zuerst einloggen</b>",
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"args": "🚫 <b>Arama sorgusu belirtin</b>",
|
||||
"404": "🚫 <b>Sonuç bulunamadı</b>",
|
||||
"oauth": (
|
||||
"🔑 <b>TIDAL'e giriş yapın</b>\n\n<i>Bu bağlantı 5 dakika içinde sona"
|
||||
" erecek</i>"
|
||||
),
|
||||
"oauth_btn": "🔑 Giriş yap",
|
||||
"success": "✅ <b>Başarıyla giriş yaptınız!</b>",
|
||||
"error": "🚫 <b>Giriş hatası</b>",
|
||||
"search": "🐈⬛ <b>{}</b>",
|
||||
"tidal_btn": "🐈⬛ Tidal",
|
||||
"searching": "🔍 <b>Aranıyor...</b>",
|
||||
"tidal_like_btn": "🖤 Beğen",
|
||||
"tidal_dislike_btn": "💔 Beğenme",
|
||||
"auth_first": "🚫 <b>Önce giriş yapmanız gerekir</b>",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"args": "🚫 <b>खोज प्रश्न निर्दिष्ट करें</b>",
|
||||
"404": "🚫 <b>कोई परिणाम नहीं मिला</b>",
|
||||
"oauth": "🔑 <b>TIDAL में लॉगिन करें</b>\n\n<i>यह लिंक 5 मिनट के लिए सक्रिय होगा</i>",
|
||||
"oauth_btn": "🔑 लॉगिन करें",
|
||||
"success": "✅ <b>सफलतापूर्वक लॉगिन किया गया!</b>",
|
||||
"error": "🚫 <b>लॉगिन त्रुटि</b>",
|
||||
"search": "🐈⬛ <b>{}</b>",
|
||||
"tidal_btn": "🐈⬛ Tidal",
|
||||
"searching": "🔍 <b>खोज रहा है...</b>",
|
||||
"tidal_like_btn": "🖤 पसंद",
|
||||
"tidal_dislike_btn": "💔 पसंद नहीं",
|
||||
"auth_first": "🚫 <b>पहले लॉगिन करना आवश्यक है</b>",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"args": "🚫 <b>Qidiruv so'rovi belgilang</b>",
|
||||
"404": "🚫 <b>Natija topilmadi</b>",
|
||||
"oauth": (
|
||||
"🔑 <b>TIDAL'da kirishingiz kerak</b>\n\n<i>Ushbu havola 5 daqiqaga aktiv"
|
||||
" bo'ladi</i>"
|
||||
),
|
||||
"oauth_btn": "🔑 Kirish",
|
||||
"success": "✅ <b>Muvaffaqiyatli kirildi!</b>",
|
||||
"error": "🚫 <b>Kirishda xatolik</b>",
|
||||
"search": "🐈⬛ <b>{}</b>",
|
||||
"tidal_btn": "🐈⬛ Tidal",
|
||||
"searching": "🔍 <b>Izlanmoqda...</b>",
|
||||
"tidal_like_btn": "🖤 Yoqadi",
|
||||
"tidal_dislike_btn": "💔 Yo'qadi",
|
||||
"auth_first": "🚫 <b>Avval kirish kerak</b>",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self._faved = []
|
||||
|
||||
self.tidal = tidalapi.Session()
|
||||
login_credentials = (
|
||||
self.get("session_id"),
|
||||
self.get("token_type"),
|
||||
self.get("access_token"),
|
||||
self.get("refresh_token"),
|
||||
)
|
||||
|
||||
if all(login_credentials):
|
||||
try:
|
||||
await utils.run_sync(self.tidal.load_oauth_session, *login_credentials)
|
||||
assert await utils.run_sync(self.tidal.check_login)
|
||||
except Exception:
|
||||
logger.exception("Error loading OAuth session")
|
||||
|
||||
if not self.get("muted"):
|
||||
try:
|
||||
await utils.dnd(self._client, "@hikka_musicdl_bot", archive=True)
|
||||
await utils.dnd(self._client, "@DirectLinkGenerator_Bot", archive=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.set("muted", True)
|
||||
|
||||
self._obtain_faved.start()
|
||||
self.musicdl = await self.import_lib(
|
||||
"https://libs.hikariatama.ru/musicdl.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
@loader.loop(interval=60)
|
||||
async def _obtain_faved(self):
|
||||
if not await utils.run_sync(self.tidal.check_login):
|
||||
return
|
||||
|
||||
self._faved = list(
|
||||
map(
|
||||
int,
|
||||
(
|
||||
await utils.run_sync(
|
||||
self.tidal.request,
|
||||
"GET",
|
||||
f"users/{self.tidal.user.id}/favorites/ids",
|
||||
)
|
||||
).json()["TRACK"],
|
||||
)
|
||||
)
|
||||
|
||||
def _save_session_info(self):
|
||||
self.set("token_type", self.tidal.token_type)
|
||||
self.set("session_id", self.tidal.session_id)
|
||||
self.set("access_token", self.tidal.access_token)
|
||||
self.set("refresh_token", self.tidal.refresh_token)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Авторизация в TIDAL",
|
||||
de_doc="Authentifizierung in TIDAL",
|
||||
tr_doc="TIDAL'de oturum açma",
|
||||
hi_doc="TIDAL में प्रमाणीकरण",
|
||||
uz_doc="TIDAL'da avtorizatsiya",
|
||||
)
|
||||
async def tlogincmd(self, message: Message):
|
||||
"""Open OAuth window to login into TIDAL"""
|
||||
result, future = self.tidal.login_oauth()
|
||||
form = await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("oauth"),
|
||||
reply_markup={
|
||||
"text": self.strings("oauth_btn"),
|
||||
"url": f"https://{result.verification_uri_complete}",
|
||||
},
|
||||
gif="https://i.gifer.com/8Z2a.gif",
|
||||
)
|
||||
|
||||
outer_loop = asyncio.get_event_loop()
|
||||
|
||||
def callback(*args, **kwargs):
|
||||
nonlocal form, outer_loop
|
||||
if self.tidal.check_login():
|
||||
asyncio.ensure_future(
|
||||
form.edit(
|
||||
self.strings("success"),
|
||||
gif="https://c.tenor.com/IrKex2lXvR8AAAAC/sparkly-eyes-joy.gif",
|
||||
),
|
||||
loop=outer_loop,
|
||||
)
|
||||
self._save_session_info()
|
||||
else:
|
||||
asyncio.ensure_future(form.edit(self.strings("error")), loop=outer_loop)
|
||||
|
||||
future.add_done_callback(callback)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<запрос> - Поиск трека в TIDAL",
|
||||
de_doc="<Anfrage> - Suche nach einem Track in TIDAL",
|
||||
tr_doc="<sorgu> - TIDAL'de bir parça arama",
|
||||
hi_doc="<अनुरोध> - TIDAL में एक ट्रैक खोजें",
|
||||
uz_doc="<so'rov> - TIDAL'da parca qidirish",
|
||||
)
|
||||
async def tidalcmd(self, message: Message):
|
||||
"""<query> - Search TIDAL"""
|
||||
if not await utils.run_sync(self.tidal.check_login):
|
||||
await utils.answer(message, self.strings("auth_first"))
|
||||
return
|
||||
|
||||
query = utils.get_args_raw(message)
|
||||
if not query:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("searching"))
|
||||
|
||||
result = await utils.run_sync(self.tidal.search, "track", query, limit=1)
|
||||
if not result or not result.tracks:
|
||||
await utils.answer(message, self.strings("404"))
|
||||
return
|
||||
|
||||
track = result.tracks[0]
|
||||
full_name = f"{track.artist.name} - {track.name}"
|
||||
|
||||
meta = (
|
||||
await utils.run_sync(
|
||||
self.tidal.request,
|
||||
"GET",
|
||||
f"tracks/{track.id}",
|
||||
)
|
||||
).json()
|
||||
|
||||
tags = []
|
||||
|
||||
if meta.get("explicit"):
|
||||
tags += ["#explicit🤬"]
|
||||
|
||||
if meta.get("audioQuality"):
|
||||
tags += [f"#{meta['audioQuality']}🔈"]
|
||||
|
||||
if isinstance(meta.get("audioModes"), list):
|
||||
for tag in meta["audioModes"]:
|
||||
tags += [f"#{tag}🎧"]
|
||||
|
||||
if track.id in self._faved:
|
||||
tags += ["#favorite🖤"]
|
||||
|
||||
if tags:
|
||||
tags = "\n\n" + "\n".join(
|
||||
[" ".join(chunk) for chunk in utils.chunks(tags, 2)]
|
||||
)
|
||||
|
||||
text = self.strings("search").format(utils.escape_html(full_name)) + tags
|
||||
|
||||
message = await utils.answer(
|
||||
message, text + "\n\n<i>Downloading audio file...</i>"
|
||||
)
|
||||
url = await self.musicdl.dl(full_name)
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=text,
|
||||
**(
|
||||
{
|
||||
"audio": {
|
||||
"url": url,
|
||||
"title": track.name,
|
||||
"performer": track.artist.name,
|
||||
}
|
||||
}
|
||||
if url
|
||||
else {}
|
||||
),
|
||||
silent=True,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("tidal_btn"),
|
||||
"url": f"https://listen.tidal.com/track/{track.id}",
|
||||
},
|
||||
*(
|
||||
[
|
||||
{
|
||||
"text": self.strings("tidal_like_btn"),
|
||||
"callback": self._like,
|
||||
"args": (track, text),
|
||||
}
|
||||
]
|
||||
if track.id not in self._faved
|
||||
else [
|
||||
{
|
||||
"text": self.strings("tidal_dislike_btn"),
|
||||
"callback": self._dislike,
|
||||
"args": (track, text),
|
||||
}
|
||||
]
|
||||
),
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
async def _like(self, call: InlineCall, track: tidalapi.Track, text: str):
|
||||
try:
|
||||
await utils.run_sync(self.tidal.user.favorites.add_track, track.id)
|
||||
except Exception:
|
||||
logger.exception("Error liking track")
|
||||
await call.answer("🚫 Error!")
|
||||
else:
|
||||
await call.answer("💚 Liked!")
|
||||
await call.edit(
|
||||
text,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("tidal_btn"),
|
||||
"url": f"https://listen.tidal.com/track/{track.id}",
|
||||
},
|
||||
{
|
||||
"text": self.strings("tidal_dislike_btn"),
|
||||
"callback": self._dislike,
|
||||
"args": (track, text),
|
||||
},
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
async def _dislike(self, call: InlineCall, track: tidalapi.Track, text: str):
|
||||
try:
|
||||
await utils.run_sync(self.tidal.user.favorites.remove_track, track.id)
|
||||
except Exception:
|
||||
logger.exception("Error disliking track")
|
||||
await call.answer("🚫 Error!")
|
||||
else:
|
||||
await call.answer("💔 Disliked!")
|
||||
await call.edit(
|
||||
text,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("tidal_btn"),
|
||||
"url": f"https://listen.tidal.com/track/{track.id}",
|
||||
},
|
||||
{
|
||||
"text": self.strings("tidal_like_btn"),
|
||||
"callback": self._like,
|
||||
"args": (track, text),
|
||||
},
|
||||
],
|
||||
],
|
||||
)
|
||||
124
hikariatama/ftg/todo.py
Normal file
124
hikariatama/ftg/todo.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
# meta pic: https://img.icons8.com/stickers/500/000000/todo-list.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/todo.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
|
||||
from random import randint
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TodoMod(loader.Module):
|
||||
"""ToDo List"""
|
||||
|
||||
strings = {
|
||||
"name": "ToDo",
|
||||
"task_removed": "<b>✅ Task removed</b>",
|
||||
"task_not_found": "<b>🚫 Task not found</b",
|
||||
"new_task": "<b>Task </b><code>#{}</code>:\n<pre>{}</pre>\n{}",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"task_removed": "<b>✅ Задача удалена</b>",
|
||||
"task_not_found": "<b>🚫 Задача не найдена</b",
|
||||
"new_task": "<b>Задача </b><code>#{}</code>:\n<pre>{}</pre>\n{}",
|
||||
"_cls_doc": "Простой планнер задач",
|
||||
"_cmd_doc_td": "[importance:int] <item> - Добавить задачу в todo",
|
||||
"_cmd_doc_tdl": "Показать активные задачи",
|
||||
"_cmd_doc_utd": "<id> - Удалить задачу из todo",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.todolist = self.get("todo", {})
|
||||
|
||||
self.imp_levels = [
|
||||
"🌌 Watchlist",
|
||||
"💻 Proging",
|
||||
"⌚️ Work",
|
||||
"🎒 Family",
|
||||
"🚫 Private",
|
||||
]
|
||||
|
||||
async def tdcmd(self, message: Message):
|
||||
"""[importance:int] <item> - Add task"""
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
importance = int(args.split()[0])
|
||||
task = args.split(maxsplit=1)[1]
|
||||
except Exception:
|
||||
importance = 0
|
||||
task = args
|
||||
|
||||
try:
|
||||
importance = int(task) if task != "" else 0
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
task = reply.text
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if importance >= len(self.imp_levels):
|
||||
importance = 0
|
||||
|
||||
random_id = str(randint(10000, 99999))
|
||||
|
||||
self.todolist[random_id] = [task, importance]
|
||||
|
||||
self.set("todo", self.todolist)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("new_task").format(
|
||||
random_id,
|
||||
task,
|
||||
self.imp_levels[importance],
|
||||
),
|
||||
)
|
||||
|
||||
async def tdlcmd(self, message: Message):
|
||||
"""Show active tasks"""
|
||||
res = "<b>#ToDo:</b>\n"
|
||||
items = {len(self.imp_levels) - i - 1: [] for i in range(len(self.imp_levels))}
|
||||
for item_id, item in self.todolist.items():
|
||||
items[item[1]].append(
|
||||
f" <code>.utd {item_id}</code>: <code>{item[0]}</code>"
|
||||
)
|
||||
|
||||
for importance, strings in items.items():
|
||||
if len(strings) == 0:
|
||||
continue
|
||||
res += "\n -{ " + self.imp_levels[importance][2:] + " }-\n"
|
||||
res += (
|
||||
self.imp_levels[importance][0]
|
||||
+ ("\n" + self.imp_levels[importance][0]).join(strings)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
await utils.answer(message, res)
|
||||
|
||||
async def utdcmd(self, message: Message):
|
||||
"""<id> - Remove task from todo"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args.startswith("#"):
|
||||
args = args[1:]
|
||||
|
||||
if args not in self.todolist:
|
||||
await utils.answer(message, self.strings("task_not_found"))
|
||||
return
|
||||
|
||||
del self.todolist[args]
|
||||
self.set("todo", self.todolist)
|
||||
await utils.answer(message, self.strings("task_removed"))
|
||||
82
hikariatama/ftg/trashguy.py
Normal file
82
hikariatama/ftg/trashguy.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/trashguy_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/trashguy.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import grapheme
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
def trashguy(text: str) -> list:
|
||||
DISTANCE = 5
|
||||
SPACER = "\u0020\u2800"
|
||||
text = list(grapheme.graphemes(text))
|
||||
return [
|
||||
utils.escape_html(i)
|
||||
for i in utils.array_sum(
|
||||
[
|
||||
[
|
||||
f"🗑{SPACER * i}(>"
|
||||
f" ^_^)>{SPACER * (DISTANCE - i)}{''.join(text[offset:])}"
|
||||
for i in range(DISTANCE)
|
||||
]
|
||||
+ [
|
||||
f"🗑{SPACER * (DISTANCE - i)}{current_symbol}<(^_^"
|
||||
f" <){SPACER * i}{''.join(text[offset + 1:])}"
|
||||
for i in range(DISTANCE)
|
||||
]
|
||||
for offset, current_symbol in enumerate(text)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TrashGuyMod(loader.Module):
|
||||
"""Animation of trashguy taking out the trash"""
|
||||
|
||||
strings = {
|
||||
"name": "TrashGuy",
|
||||
"done": (
|
||||
"🗑 \\ (•◡•) /"
|
||||
" 🗑\n\u0020\u2800\u0020\u2800<b>Done!</b>\u0020\u2800\u0020\u2800"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"done": (
|
||||
"🗑 \\ (•◡•) / 🗑\n\u0020\u2800\u0020\u2800<b>Я"
|
||||
" закончил!</b>\u0020\u2800\u0020\u2800"
|
||||
),
|
||||
}
|
||||
|
||||
async def tguyicmd(self, message: Message):
|
||||
"""<text> - TrashGuy Inline"""
|
||||
await self.animate(
|
||||
message,
|
||||
trashguy(utils.get_args_raw(message) or "hikari's brain")
|
||||
+ [self.strings("done")],
|
||||
interval=1,
|
||||
inline=True,
|
||||
)
|
||||
|
||||
async def tguycmd(self, message: Message):
|
||||
"""<text> - TrashGuy"""
|
||||
await self.animate(
|
||||
message,
|
||||
trashguy(utils.get_args_raw(message) or "hikari's brain")
|
||||
+ [self.strings("done")],
|
||||
interval=1,
|
||||
)
|
||||
250
hikariatama/ftg/truth_or_dare.py
Normal file
250
hikariatama/ftg/truth_or_dare.py
Normal file
@@ -0,0 +1,250 @@
|
||||
__version__ = (2, 0, 1)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/truth_or_date_icon.py
|
||||
# meta banner: https://mods.hikariatama.ru/badges/truth_or_dare.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import json
|
||||
import random
|
||||
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TruthOrDareMod(loader.Module):
|
||||
"""Truth or dare? Play your favorite game from inside the Telegram (en/ru)"""
|
||||
|
||||
strings = {
|
||||
"name": "TruthOrDare",
|
||||
"choose_language": "👩🎤 <b>Choose language</b>",
|
||||
"truth_or_dare_ru": "🔴 <b>Правда</b> или <b>Действие</b>? 🔵",
|
||||
"truth_or_dare_en": "🔴 <b>Truth</b> or <b>Dare</b>? 🔵",
|
||||
"truth_ru": "🤵♀️ Правда",
|
||||
"dare_ru": "🥷 Действие",
|
||||
"truth_en": "🤵♀️ Truth",
|
||||
"dare_en": "🥷 Dare",
|
||||
"language_saved_ru": "🇷🇺 Язык сохранен",
|
||||
"language_saved_en": "🇬🇧 Language saved",
|
||||
"classic_ru": "🙂 Классика",
|
||||
"classic_en": "🙂 Classic",
|
||||
"kids_ru": "👨👦 Для детей",
|
||||
"kids_en": "👨👦 Kids",
|
||||
"party_ru": "🥳 Вечеринка",
|
||||
"party_en": "🥳 Party",
|
||||
"hot_ru": "❤️🔥 Горячее",
|
||||
"hot_en": "❤️🔥 Hot",
|
||||
"mixed_ru": "🔀 Разное",
|
||||
"mixed_en": "🔀 Mixed",
|
||||
"category_ru": "😇 <b>Выбери категорию игры:</b>",
|
||||
"category_en": "😇 <b>Choose game category:</b>",
|
||||
"args": "▫️ <code>.todlang en/ru</code>",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
if self.get("lang") in {"ru", "en"}:
|
||||
self._update_lang()
|
||||
|
||||
async def truth_or_dare(self, tod: str, category: str) -> str:
|
||||
return random.choice(
|
||||
(
|
||||
await utils.run_sync(
|
||||
requests.post,
|
||||
"https://psycatgames.com/api/tod-v2/",
|
||||
headers={"referer": "https://psycatgames.com/app/truth-or-dare"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"id": "truth-or-dare",
|
||||
"language": self.get("lang"),
|
||||
"category": category,
|
||||
"type": tod,
|
||||
}
|
||||
),
|
||||
)
|
||||
).json()["results"]
|
||||
)
|
||||
|
||||
def _update_lang(self):
|
||||
self._markup = [
|
||||
[
|
||||
{
|
||||
"text": self.strings(f"classic_{self.get('lang')}"),
|
||||
"callback": self._inline_start,
|
||||
"args": ("classic",),
|
||||
},
|
||||
{
|
||||
"text": self.strings(f"kids_{self.get('lang')}"),
|
||||
"callback": self._inline_start,
|
||||
"args": ("kids",),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings(f"party_{self.get('lang')}"),
|
||||
"callback": self._inline_start,
|
||||
"args": ("party",),
|
||||
},
|
||||
{
|
||||
"text": self.strings(f"hot_{self.get('lang')}"),
|
||||
"callback": self._inline_start,
|
||||
"args": ("hot",),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings(f"mixed_{self.get('lang')}"),
|
||||
"callback": self._inline_start,
|
||||
"args": ("mixed",),
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
async def _inline_set_language(self, call: InlineCall, lang: str):
|
||||
self.set("lang", lang)
|
||||
await call.answer(self.strings(f"language_saved_{lang}"), show_alert=True)
|
||||
self._update_lang()
|
||||
await call.edit(
|
||||
self.strings(f"truth_or_dare_{self.get('lang')}"), reply_markup=self._markup
|
||||
)
|
||||
|
||||
async def _inline_process(
|
||||
self,
|
||||
call: InlineCall,
|
||||
action: str,
|
||||
category: str,
|
||||
):
|
||||
action_babel = self.strings(f"{action}_{self.get('lang')}")
|
||||
await call.edit(
|
||||
f"<b>{action_babel}</b>:\n\n{await self.truth_or_dare(action, category)}",
|
||||
reply_markup=[
|
||||
{
|
||||
"text": self.strings(f"truth_{self.get('lang')}"),
|
||||
"callback": self._inline_process,
|
||||
"args": ("truth", category),
|
||||
},
|
||||
{
|
||||
"text": self.strings(f"dare_{self.get('lang')}"),
|
||||
"callback": self._inline_process,
|
||||
"args": ("dare", category),
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
async def _inline_start(self, call: InlineCall, category: str):
|
||||
await call.edit(
|
||||
self.strings(f"truth_or_dare_{self.get('lang')}"),
|
||||
reply_markup=[
|
||||
{
|
||||
"text": self.strings(f"truth_{self.get('lang')}"),
|
||||
"callback": self._inline_process,
|
||||
"args": ("truth", category),
|
||||
},
|
||||
{
|
||||
"text": self.strings(f"dare_{self.get('lang')}"),
|
||||
"callback": self._inline_process,
|
||||
"args": ("dare", category),
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
async def todcmd(self, message: Message):
|
||||
"""Get truth or dare"""
|
||||
if not self.get("lang"):
|
||||
await self.inline.form(
|
||||
self.strings("choose_language"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
{
|
||||
"text": "🇷🇺 Русский",
|
||||
"callback": self._inline_set_language,
|
||||
"args": ("ru",),
|
||||
},
|
||||
{
|
||||
"text": "🇬🇧 English",
|
||||
"callback": self._inline_set_language,
|
||||
"args": ("en",),
|
||||
},
|
||||
],
|
||||
)
|
||||
return
|
||||
|
||||
if (category := utils.get_args_raw(message).lower()) not in {
|
||||
"classic",
|
||||
"kids",
|
||||
"party",
|
||||
"hot",
|
||||
"mixed",
|
||||
}:
|
||||
category = "mixed"
|
||||
|
||||
if random.choice(("truth", "dare")) == "truth":
|
||||
action_babel = self.strings(f"truth_{self.get('lang')}")
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"<b>{action_babel}</b>:\n\n{await self.truth_or_dare('truth', category)}"
|
||||
),
|
||||
)
|
||||
else:
|
||||
action_babel = self.strings(f"dare_{self.get('lang')}")
|
||||
await utils.answer(
|
||||
message,
|
||||
(
|
||||
f"<b>{action_babel}</b>:\n\n{await self.truth_or_dare('dare', category)}"
|
||||
),
|
||||
)
|
||||
|
||||
async def todicmd(self, message: Message):
|
||||
"""Start new truth or dare game"""
|
||||
if not self.get("lang"):
|
||||
await self.inline.form(
|
||||
self.strings("choose_language"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
{
|
||||
"text": "🇷🇺 Русский",
|
||||
"callback": self._inline_set_language,
|
||||
"args": ("ru",),
|
||||
},
|
||||
{
|
||||
"text": "🇬🇧 English",
|
||||
"callback": self._inline_set_language,
|
||||
"args": ("en",),
|
||||
},
|
||||
],
|
||||
)
|
||||
return
|
||||
|
||||
await self.inline.form(
|
||||
self.strings(f"category_{self.get('lang')}"),
|
||||
message=message,
|
||||
reply_markup=self._markup,
|
||||
disable_security=True,
|
||||
)
|
||||
|
||||
async def todlangcmd(self, message: Message):
|
||||
"""[en/ru] - Change language"""
|
||||
args = utils.get_args_raw(message).lower().strip()
|
||||
if args not in {"ru", "en"}:
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
self.set("lang", args)
|
||||
self._update_lang()
|
||||
|
||||
ans = self.strings(f"language_saved_{args}")
|
||||
await utils.answer(message, f"<b>{ans}</b>")
|
||||
178
hikariatama/ftg/uploader.py
Normal file
178
hikariatama/ftg/uploader.py
Normal file
@@ -0,0 +1,178 @@
|
||||
__version__ = (2, 0, 1)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/uploader_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/uploader.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import imghdr
|
||||
import io
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
|
||||
import requests
|
||||
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FileUploaderMod(loader.Module):
|
||||
"""Different engines file uploader"""
|
||||
|
||||
strings = {
|
||||
"name": "Uploader",
|
||||
"uploading": "🚀 <b>Uploading...</b>",
|
||||
"noargs": "🚫 <b>No file specified</b>",
|
||||
"err": "🚫 <b>Upload error</b>",
|
||||
"uploaded": '🎡 <b>File <a href="{0}">uploaded</a></b>!\n\n<code>{0}</code>',
|
||||
"imgur_blocked": "🚫 <b>Unban @ImgUploadBot</b>",
|
||||
"not_an_image": "🚫 <b>This platform only supports images</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"uploading": "🚀 <b>Загрузка...</b>",
|
||||
"noargs": "🚫 <b>Файл не указан</b>",
|
||||
"err": "🚫 <b>Ошибка загрузки</b>",
|
||||
"uploaded": '🎡 <b>Файл <a href="{0}">загружен</a></b>!\n\n<code>{0}</code>',
|
||||
"imgur_blocked": "🚫 <b>Разблокируй @ImgUploadBot</b>",
|
||||
"not_an_image": "🚫 <b>Эта платформа поддерживает только изображения</b>",
|
||||
"_cmd_doc_imgur": "Загрузить на imgur.com",
|
||||
"_cmd_doc_oxo": "Загрузить на 0x0.st",
|
||||
"_cmd_doc_x0": "Загрузить на x0.at",
|
||||
"_cmd_doc_skynet": "Загрузить на децентрализованную платформу SkyNet",
|
||||
"_cls_doc": "Загружает файлы на различные хостинги",
|
||||
}
|
||||
|
||||
async def get_media(self, message: Message):
|
||||
reply = await message.get_reply_message()
|
||||
m = None
|
||||
if reply and reply.media:
|
||||
m = reply
|
||||
elif message.media:
|
||||
m = message
|
||||
elif not reply:
|
||||
await utils.answer(message, self.strings("noargs"))
|
||||
return False
|
||||
|
||||
if not m:
|
||||
file = io.BytesIO(bytes(reply.raw_text, "utf-8"))
|
||||
file.name = "file.txt"
|
||||
else:
|
||||
file = io.BytesIO(await self._client.download_media(m, bytes))
|
||||
file.name = (
|
||||
m.file.name
|
||||
or (
|
||||
"".join(
|
||||
[
|
||||
random.choice("abcdefghijklmnopqrstuvwxyz1234567890")
|
||||
for _ in range(16)
|
||||
]
|
||||
)
|
||||
)
|
||||
+ m.file.ext
|
||||
)
|
||||
|
||||
return file
|
||||
|
||||
async def get_image(self, message: Message):
|
||||
file = await self.get_media(message)
|
||||
if not file:
|
||||
return False
|
||||
|
||||
if imghdr.what(file) not in ["gif", "png", "jpg", "jpeg", "tiff", "bmp"]:
|
||||
await utils.answer(message, self.strings("not_an_image"))
|
||||
return False
|
||||
|
||||
return file
|
||||
|
||||
async def skynetcmd(self, message: Message):
|
||||
"""Upload to decentralized SkyNet"""
|
||||
message = await utils.answer(message, self.strings("uploading"))
|
||||
file = await self.get_media(message)
|
||||
if not file:
|
||||
return
|
||||
|
||||
try:
|
||||
skynet = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://siasky.net/skynet/skyfile",
|
||||
files={"file": file},
|
||||
)
|
||||
except ConnectionError:
|
||||
await utils.answer(message, self.strings("err"))
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("uploaded").format(
|
||||
f"https://siasky.net/{skynet.json()['skylink']}"
|
||||
),
|
||||
)
|
||||
|
||||
async def imgurcmd(self, message: Message):
|
||||
"""Upload to imgur.com"""
|
||||
message = await utils.answer(message, self.strings("uploading"))
|
||||
file = await self.get_image(message)
|
||||
if not file:
|
||||
return
|
||||
|
||||
chat = "@ImgUploadBot"
|
||||
|
||||
async with self._client.conversation(chat) as conv:
|
||||
try:
|
||||
m = await conv.send_message(file=file)
|
||||
response = await conv.get_response()
|
||||
except YouBlockedUserError:
|
||||
await utils.answer(message, self.strings("imgur_blocked"))
|
||||
return
|
||||
|
||||
await m.delete()
|
||||
await response.delete()
|
||||
|
||||
try:
|
||||
url = (
|
||||
re.search(
|
||||
r'<meta property="og:image" data-react-helmet="true"'
|
||||
r' content="(.*?)"',
|
||||
(await utils.run_sync(requests.get, response.raw_text)).text,
|
||||
)
|
||||
.group(1)
|
||||
.split("?")[0]
|
||||
)
|
||||
except Exception:
|
||||
url = response.raw_text
|
||||
|
||||
await utils.answer(message, self.strings("uploaded").format(url))
|
||||
|
||||
async def oxocmd(self, message: Message):
|
||||
"""Upload to 0x0.st"""
|
||||
message = await utils.answer(message, self.strings("uploading"))
|
||||
file = await self.get_media(message)
|
||||
if not file:
|
||||
return
|
||||
|
||||
try:
|
||||
oxo = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://0x0.st",
|
||||
files={"file": file},
|
||||
data={"secret": True},
|
||||
)
|
||||
except ConnectionError:
|
||||
await utils.answer(message, self.strings("err"))
|
||||
return
|
||||
|
||||
url = oxo.text
|
||||
await utils.answer(message, self.strings("uploaded").format(url))
|
||||
86
hikariatama/ftg/userinfo.py
Normal file
86
hikariatama/ftg/userinfo.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/userinfo_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/userinfo.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
from telethon.tl.functions.users import GetFullUserRequest
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class InfoMod(loader.Module):
|
||||
"""Retrieve information about bot/user/chat"""
|
||||
|
||||
strings = {
|
||||
"name": "Info",
|
||||
"loading": "🕐 <b>Processing entity...</b>",
|
||||
"not_chat": "🚫 <b>This is not a chat!</b>",
|
||||
}
|
||||
|
||||
async def userinfocmd(self, message: Message):
|
||||
"""Get object infomation"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
message = await utils.answer(message, self.strings("loading"))
|
||||
|
||||
try:
|
||||
user_id = (
|
||||
(
|
||||
(
|
||||
await self._client.get_entity(
|
||||
args if not args.isdigit() else int(args)
|
||||
)
|
||||
).id
|
||||
)
|
||||
if args
|
||||
else reply.sender_id
|
||||
)
|
||||
except Exception:
|
||||
user_id = self._tg_id
|
||||
|
||||
user = await self._client(GetFullUserRequest(user_id))
|
||||
|
||||
user_ent = user.users[0]
|
||||
|
||||
photo = await self._client.download_profile_photo(user_ent.id, bytes)
|
||||
|
||||
user_info = (
|
||||
"<b>👤 User:</b>\n\n"
|
||||
f"<b>First name:</b> {user_ent.first_name or '🚫'}\n"
|
||||
f"<b>Last name:</b> {user_ent.last_name or '🚫'}\n"
|
||||
f"<b>Username:</b> @{user_ent.username or '🚫'}\n"
|
||||
f"<b>About:</b> \n<code>{user.full_user.about or '🚫'}</code>\n\n"
|
||||
f"<b>Shared Chats:</b> {user.full_user.common_chats_count}\n"
|
||||
f'<b><a href="tg://user?id={user_ent.id}">🌐 Permalink</a></b>\n\n'
|
||||
f"<b>ID:</b> <code>{user_ent.id}</code>\n"
|
||||
)
|
||||
|
||||
if photo:
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
photo,
|
||||
caption=user_info,
|
||||
link_preview=False,
|
||||
reply_to=reply.id if reply else None,
|
||||
)
|
||||
if message.out:
|
||||
await message.delete()
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
user_info,
|
||||
reply_to=reply.id if reply else None,
|
||||
link_preview=False,
|
||||
)
|
||||
144
hikariatama/ftg/v2a.py
Normal file
144
hikariatama/ftg/v2a.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/v2a_icon.png
|
||||
# meta developer: @hikarimods
|
||||
# meta banner: https://mods.hikariatama.ru/badges/v2a.jpg
|
||||
# scope: ffmpeg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.0
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import telethon.utils as tlutils
|
||||
from telethon.tl.types import DocumentAttributeAudio, Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Video2Audio(loader.Module):
|
||||
"""Converts video \ round messages to audio \ voice messages"""
|
||||
|
||||
strings = {
|
||||
"name": "Video2Audio",
|
||||
"no_video": "🚫 <b>Reply to video required</b>",
|
||||
"converting": "🧚♀️ <b>Converting...</b>",
|
||||
"error": "🚫 <b>Error while converting</b>",
|
||||
}
|
||||
strings_ru = {
|
||||
"no_video": "🚫 <b>Ответь на видео</b>",
|
||||
"converting": "🧚♀️ <b>Конвертирую...</b>",
|
||||
"_cls_doc": "Конвертирует видео в аудио",
|
||||
"error": "🚫 <b>Ошибка при конвертировании</b>",
|
||||
}
|
||||
|
||||
async def client_ready(self):
|
||||
self.v2a = await self.import_lib(
|
||||
"https://libs.hikariatama.ru/v2a.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc=(
|
||||
"<ответ на видео> [-vm] [-b] - конвертировать видео в аудио\n-vm -"
|
||||
" Отправить голосовое сообщение"
|
||||
)
|
||||
)
|
||||
async def v2acmd(self, message: Message):
|
||||
"""<reply> [-vm] [-b] - Convert video to audio
|
||||
-vm - Use voice message instead"""
|
||||
use_voicemessage = "-vm" in utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.video:
|
||||
await utils.answer(message, self.strings("no_video"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("converting"))
|
||||
video = await self._client.download_media(reply, bytes)
|
||||
|
||||
out = f"audio.{'ogg' if use_voicemessage else 'mp3'}"
|
||||
try:
|
||||
audio = await self.v2a.convert(video, out)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings("error"))
|
||||
return
|
||||
|
||||
audiofile = io.BytesIO(audio)
|
||||
audiofile.name = out
|
||||
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
audiofile,
|
||||
voice_note=use_voicemessage,
|
||||
reply_to=reply.id,
|
||||
attributes=[
|
||||
DocumentAttributeAudio(
|
||||
duration=next(
|
||||
(
|
||||
attr.duration
|
||||
for attr in reply.document.attributes
|
||||
if hasattr(attr, "duration")
|
||||
),
|
||||
0,
|
||||
),
|
||||
voice=use_voicemessage,
|
||||
**(
|
||||
{"waveform": tlutils.encode_waveform(audio)}
|
||||
if use_voicemessage
|
||||
else {}
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
@loader.command(ru_doc="<reply> - Создать банованный вейвформ")
|
||||
async def waveform(self, message: Message):
|
||||
"""<reply to voice> - Create buggy waveform"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.media:
|
||||
await utils.answer(message, self.strings("no_video"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("converting"))
|
||||
document = io.BytesIO(await reply.download_media(bytes))
|
||||
document.name = "audio.ogg"
|
||||
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
document,
|
||||
voice_note=True,
|
||||
reply_to=reply.id,
|
||||
attributes=[
|
||||
DocumentAttributeAudio(
|
||||
duration=2147483647,
|
||||
voice=True,
|
||||
waveform=tlutils.encode_waveform(
|
||||
bytes(
|
||||
(
|
||||
*tuple(range(0, 30, 5)),
|
||||
*reversed(tuple(range(0, 30, 5))),
|
||||
)
|
||||
)
|
||||
* 20
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
791
hikariatama/ftg/voicechat.py
Normal file
791
hikariatama/ftg/voicechat.py
Normal file
@@ -0,0 +1,791 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/voicechat_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/voicechat.jpg
|
||||
# meta developer: @hikarimods
|
||||
# requires: py-tgcalls youtube_dl
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.2.10
|
||||
|
||||
import asyncio
|
||||
import atexit
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from pytgcalls import PyTgCalls, StreamType, types
|
||||
from pytgcalls.binding import Binding
|
||||
from pytgcalls.environment import Environment
|
||||
from pytgcalls.exceptions import AlreadyJoinedError, NoActiveGroupCall
|
||||
from pytgcalls.handlers import HandlersHolder
|
||||
from pytgcalls.methods import Methods
|
||||
from pytgcalls.mtproto import MtProtoClient
|
||||
from pytgcalls.scaffold import Scaffold
|
||||
from pytgcalls.types import Cache
|
||||
from pytgcalls.types.call_holder import CallHolder
|
||||
from pytgcalls.types.update_solver import UpdateSolver
|
||||
from telethon.tl.functions.phone import CreateGroupCallRequest
|
||||
from telethon.tl.types import DocumentAttributeFilename, Message
|
||||
from youtube_dl import YoutubeDL
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
from ..tl_cache import CustomTelegramClient
|
||||
|
||||
logging.getLogger("pytgcalls").setLevel(logging.ERROR)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class VoiceChatMod(loader.Module):
|
||||
"""
|
||||
Toolkit for VoiceChats handling
|
||||
DISCLAIMER: THIS MODULE MAY CAUSE MEMORY LEAK AND CORRUPT YOUR SERVER DUE TO PYTGCALLS BUG
|
||||
USE WITH CAUTION. DON'T FORGET TO LIMIT YOUR HIKKA DAEMON BY RAM AND CPU USAGE!
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "VoiceChat",
|
||||
"already_joined": "🚫 <b>You are already in VoiceChat</b>",
|
||||
"joined": "🎙 <b>Joined VoiceChat</b>",
|
||||
"no_reply": "🚫 <b>Reply to a message</b>",
|
||||
"no_queue": "🚫 <b>No queue</b>",
|
||||
"queue": "🎙 <b>Queue</b>:\n\n{}",
|
||||
"queueadd": "🎧 <b>{} added to queue</b>",
|
||||
"queueaddv": "🎬 <b>{} added to queue</b>",
|
||||
"downloading": "📥 <b>Downloading...</b>",
|
||||
"playing": "🎶 <b>Playing {}</b>",
|
||||
"playing_with_next": "🎶 <b>Playing {}</b>\n➡️ <b>Next: {}</b>",
|
||||
"pause": "🎵 Pause",
|
||||
"play": "🎵 Play",
|
||||
"mute": "🔇 Mute",
|
||||
"unmute": "🔈 Unmute",
|
||||
"next": "➡️ Next",
|
||||
"stopped": "🚨 <b>Stopped</b>",
|
||||
"stop": "🚨 Stop",
|
||||
"choose_delete": "♻️ <b>Choose a queue item to delete</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"already_joined": "🚫 <b>Уже в голосовом чате</b>",
|
||||
"joined": "🎙 <b>Присоединился к голосовому чату</b>",
|
||||
"no_reply": "🚫 <b>Ответьте на сообщение</b>",
|
||||
"no_queue": "🚫 <b>Очередь пуста</b>",
|
||||
"queue": "🎙 <b>Очередь</b>:\n\n{}",
|
||||
"queueadd": "🎧 <b>{} добавлен в очередь</b>",
|
||||
"queueaddv": "📼 <b>{} добавлен в очередь</b>",
|
||||
"downloading": "📥 <b>Загрузка...</b>",
|
||||
"playing": "🎶 <b>Играет {}</b>",
|
||||
"playing_with_next": "🎶 <b>Играет {}</b>\n➡️ <b>Далее: {}</b>",
|
||||
"pause": "🎵 Пауза",
|
||||
"play": "🎵 Играть",
|
||||
"mute": "🔇 Заглушить",
|
||||
"unmute": "🔈 Включить",
|
||||
"next": "➡️ Далее",
|
||||
"stopped": "🚨 <b>Остановлено</b>",
|
||||
"stop": "🚨 Остановить",
|
||||
"choose_delete": "♻️ <b>Выберите элемент очереди для удаления</b>",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"already_joined": "🚫 <b>Du bist bereits in einem Sprachchat</b>",
|
||||
"joined": "🎙 <b>In Sprachchat beigetreten</b>",
|
||||
"no_reply": "🚫 <b>Antworte auf eine Nachricht</b>",
|
||||
"no_queue": "🚫 <b>Keine Warteschlange</b>",
|
||||
"queue": "🎙 <b>Warteschlange</b>:\n\n{}",
|
||||
"queueadd": "🎧 <b>{} zur Warteschlange hinzugefügt</b>",
|
||||
"queueaddv": "📼 <b>{} zur Warteschlange hinzugefügt</b>",
|
||||
"downloading": "📥 <b>Herunterladen...</b>",
|
||||
"playing": "🎶 <b>Spiele {}</b>",
|
||||
"playing_with_next": "🎶 <b>Spiele {}</b>\n➡️ <b>Nächster: {}</b>",
|
||||
"pause": "🎵 Pause",
|
||||
"play": "🎵 Spielen",
|
||||
"mute": "🔇 Stumm",
|
||||
"unmute": "🔈 Ton",
|
||||
"next": "➡️ Nächster",
|
||||
"stopped": "🚨 <b>Gestoppt</b>",
|
||||
"stop": "🚨 Stoppen",
|
||||
"choose_delete": (
|
||||
"♻️ <b>Wähle einen Eintrag aus der Warteschlange zum Löschen</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"already_joined": "🚫 <b>Zaten sesli sohbette</b>",
|
||||
"joined": "🎙 <b>Sesli sohbete katıldı</b>",
|
||||
"no_reply": "🚫 <b>Bir mesaja yanıt verin</b>",
|
||||
"no_queue": "🚫 <b>Kuyruk yok</b>",
|
||||
"queue": "🎙 <b>Kuyruk</b>:\n\n{}",
|
||||
"queueadd": "🎧 <b>{} kuyruğa eklendi</b>",
|
||||
"queueaddv": "📼 <b>{} kuyruğa eklendi</b>",
|
||||
"downloading": "📥 <b>İndiriliyor...</b>",
|
||||
"playing": "🎶 <b>Oynatılıyor {}</b>",
|
||||
"playing_with_next": "🎶 <b>Oynatılıyor {}</b>\n➡️ <b>Sonraki: {}</b>",
|
||||
"pause": "🎵 Duraklat",
|
||||
"play": "🎵 Oynat",
|
||||
"mute": "🔇 Sessiz",
|
||||
"unmute": "🔈 Sesi aç",
|
||||
"next": "➡️ Sonraki",
|
||||
"stopped": "🚨 <b>Durduruldu</b>",
|
||||
"stop": "🚨 Durdur",
|
||||
"choose_delete": "♻️ <b>Silinecek kuyruk öğesini seçin</b>",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"already_joined": "🚫 <b>Siz allaqachon g‘ovushda</b>",
|
||||
"joined": "🎙 <b>G‘ovushga qo‘shildi</b>",
|
||||
"no_reply": "🚫 <b>Xabarga javob bering</b>",
|
||||
"no_queue": "🚫 <b>Navbat yo‘q</b>",
|
||||
"queue": "🎙 <b>Navbat</b>:\n\n{}",
|
||||
"queueadd": "🎧 <b>{} navbatga qo‘shildi</b>",
|
||||
"queueaddv": "📼 <b>{} navbatga qo‘shildi</b>",
|
||||
"downloading": "📥 <b>Yuklanmoqda...</b>",
|
||||
"playing": "🎶 <b>O‘ynatilmoqda {}</b>",
|
||||
"playing_with_next": "🎶 <b>O‘ynatilmoqda {}</b>\n➡️ <b>Keyingi: {}</b>",
|
||||
"pause": "🎵 To‘xtatish",
|
||||
"play": "🎵 O‘ynatish",
|
||||
"mute": "🔇 Sessiz",
|
||||
"unmute": "🔈 Suv",
|
||||
"next": "➡️ Keyingi",
|
||||
"stopped": "🚨 <b>To‘xtatildi</b>",
|
||||
"stop": "🚨 To‘xtatish",
|
||||
"choose_delete": "♻️ <b>O‘chirish uchun navbatdagi elementni tanlang</b>",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"already_joined": "🚫 <b>आप पहले से ही एक वाणिज्यिक चैट में हैं</b>",
|
||||
"joined": "🎙 <b>वाणिज्यिक चैट में शामिल हो गए</b>",
|
||||
"no_reply": "🚫 <b>एक संदेश पर उत्तर दें</b>",
|
||||
"no_queue": "🚫 <b>कोई पंक्ति नहीं</b>",
|
||||
"queue": "🎙 <b>पंक्ति</b>:\n\n{}",
|
||||
"queueadd": "🎧 <b>{} पंक्ति में जोड़ा गया</b>",
|
||||
"queueaddv": "📼 <b>{} पंक्ति में जोड़ा गया</b>",
|
||||
"downloading": "📥 <b>डाउनलोड हो रहा है...</b>",
|
||||
"playing": "🎶 <b>खेला जा रहा है {}</b>",
|
||||
"playing_with_next": "🎶 <b>खेला जा रहा है {}</b>\n➡️ <b>अगला: {}</b>",
|
||||
"pause": "🎵 रोकें",
|
||||
"play": "🎵 खेलो",
|
||||
"mute": "🔇 मौन",
|
||||
"unmute": "🔈 आवाज",
|
||||
"next": "➡️ अगला",
|
||||
"stopped": "🚨 <b>रोक दिया</b>",
|
||||
"stop": "🚨 रोकें",
|
||||
"choose_delete": "♻️ <b>हटाने के लिए पंक्ति आइटम का चयन करें</b>",
|
||||
}
|
||||
|
||||
_calls = {}
|
||||
_muted = {}
|
||||
_forms = {}
|
||||
_queue = {}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"silent_queue",
|
||||
False,
|
||||
"Do not notify about track changes in chat",
|
||||
validator=loader.validators.Boolean(),
|
||||
)
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
# Monkeypatch pytgcalls MtProtoClient to support hikka's custom one
|
||||
|
||||
class HikkaTLClient(MtProtoClient):
|
||||
def __init__(
|
||||
self,
|
||||
cache_duration: int,
|
||||
client: CustomTelegramClient,
|
||||
):
|
||||
self._bind_client = None
|
||||
from pytgcalls.mtproto.telethon_client import TelethonClient
|
||||
|
||||
self._bind_client = TelethonClient(
|
||||
cache_duration,
|
||||
client,
|
||||
)
|
||||
|
||||
class CustomPyTgCalls(PyTgCalls):
|
||||
def __init__(
|
||||
self,
|
||||
app: CustomTelegramClient,
|
||||
cache_duration: int = 120,
|
||||
overload_quiet_mode: bool = False,
|
||||
# BETA SUPPORT, BY DEFAULT IS DISABLED
|
||||
multi_thread: bool = False,
|
||||
):
|
||||
Methods.__init__(self)
|
||||
Scaffold.__init__(self)
|
||||
self._app = HikkaTLClient(
|
||||
cache_duration,
|
||||
app,
|
||||
)
|
||||
self._is_running = False
|
||||
self._env_checker = Environment(
|
||||
self._REQUIRED_NODEJS_VERSION,
|
||||
self._REQUIRED_PYROGRAM_VERSION,
|
||||
self._REQUIRED_TELETHON_VERSION,
|
||||
self._app.client,
|
||||
)
|
||||
self._call_holder = CallHolder()
|
||||
self._cache_user_peer = Cache()
|
||||
self._wait_result = UpdateSolver()
|
||||
self._on_event_update = HandlersHolder()
|
||||
self._binding = Binding(
|
||||
overload_quiet_mode,
|
||||
multi_thread,
|
||||
)
|
||||
|
||||
def cleanup():
|
||||
if self._async_core is not None:
|
||||
self._async_core.cancel()
|
||||
|
||||
atexit.register(cleanup)
|
||||
|
||||
# //
|
||||
|
||||
self._app = CustomPyTgCalls(client)
|
||||
self._dir = tempfile.mkdtemp()
|
||||
await self._app.start()
|
||||
self._app._on_event_update.add_handler("STREAM_END_HANDLER", self.stream_ended)
|
||||
self.musicdl = await self.import_lib(
|
||||
"https://libs.hikariatama.ru/musicdl.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
async def stream_ended(self, client: PyTgCalls, update: types.Update):
|
||||
chat_id = update.chat_id
|
||||
with contextlib.suppress(IndexError):
|
||||
self._queue[chat_id].pop(0)
|
||||
|
||||
if not self._queue.get(chat_id):
|
||||
with contextlib.suppress(Exception):
|
||||
await client.leave_group_call(chat_id)
|
||||
return
|
||||
|
||||
self._queue[chat_id][0]["playing"] = True
|
||||
|
||||
if self._queue[chat_id][0]["audio"]:
|
||||
await self.play(chat_id, self._queue[chat_id][0]["data"])
|
||||
else:
|
||||
if self._queue[chat_id][0]["youtube"]:
|
||||
await self.play_video_yt(chat_id, self._queue[chat_id][0]["data"])
|
||||
else:
|
||||
await self.play_video(chat_id, self._queue[chat_id][0]["data"])
|
||||
|
||||
async def _play(
|
||||
self,
|
||||
chat_id: int,
|
||||
stream,
|
||||
stream_type,
|
||||
reattempt: bool = False,
|
||||
):
|
||||
self._muted.setdefault(chat_id, False)
|
||||
try:
|
||||
await self._app.join_group_call(
|
||||
chat_id,
|
||||
stream,
|
||||
stream_type=stream_type,
|
||||
)
|
||||
except AlreadyJoinedError:
|
||||
await self._app.change_stream(chat_id, stream)
|
||||
except NoActiveGroupCall:
|
||||
if reattempt:
|
||||
raise
|
||||
|
||||
await self._client(CreateGroupCallRequest(chat_id))
|
||||
await self._play(chat_id, stream, stream_type, True)
|
||||
|
||||
def _get_fn(self, message: Message) -> str:
|
||||
filename = None
|
||||
with contextlib.suppress(Exception):
|
||||
attr = next(
|
||||
attr for attr in getattr(message, "document", message).attributes
|
||||
)
|
||||
filename = (
|
||||
getattr(attr, "performer", "") + " - " + getattr(attr, "title", "")
|
||||
)
|
||||
|
||||
if not filename:
|
||||
with contextlib.suppress(Exception):
|
||||
filename = next(
|
||||
attr
|
||||
for attr in getattr(message, "document", message).attributes
|
||||
if isinstance(attr, DocumentAttributeFilename)
|
||||
).file_name
|
||||
|
||||
return filename
|
||||
|
||||
@loader.command(
|
||||
ru_doc=(
|
||||
"<ответ на песню или ее имя> - Добавить песню в очередь прослушивания чата"
|
||||
),
|
||||
de_doc=(
|
||||
"<auf eine Musik oder ihren Namen antworten> - Fügen Sie eine Musik in die"
|
||||
" Warteschlange für die Wiedergabe im Chat hinzu"
|
||||
),
|
||||
tr_doc="<şarkıya veya adına yanıt> - Sohbette dinleme sırasına şarkı ekleyin",
|
||||
hi_doc=(
|
||||
"<एक गाने या उसके नाम पर उत्तर> - चैट में प्लेबैक के लिए गाने को लंबित करने"
|
||||
" के लिए गाने को लंबित करें"
|
||||
),
|
||||
uz_doc=(
|
||||
"<musiqaga yoki uning nomiga javob> - Chatda o'qish uchun musiqani qo'shing"
|
||||
),
|
||||
)
|
||||
async def qadd(self, message: Message):
|
||||
"""<reply to song or its name> - Add song to chat's voicechat queue"""
|
||||
reply = await message.get_reply_message()
|
||||
song = utils.get_args_raw(message)
|
||||
if (not reply or not reply.media) and not song:
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
message = await utils.answer(message, self.strings("downloading"))
|
||||
filename = None
|
||||
|
||||
if not reply or not reply.media and song:
|
||||
song, filename = await self._download_audio(song, message)
|
||||
if not song:
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
if song:
|
||||
raw_data = song
|
||||
else:
|
||||
raw_data = await self._client.download_file(reply.document, bytes)
|
||||
|
||||
filename = self._get_fn(reply)
|
||||
|
||||
if not filename:
|
||||
filename = "Some cool song"
|
||||
|
||||
filename = re.sub(r"\(.*?\)", "", filename)
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
|
||||
self._queue.setdefault(chat_id, []).append(
|
||||
{"data": raw_data, "filename": filename, "playing": False, "audio": True}
|
||||
)
|
||||
|
||||
if not any(i["playing"] for i in self._queue[chat_id]):
|
||||
self._queue[chat_id][-1]["playing"] = True
|
||||
await self.play(chat_id, raw_data)
|
||||
|
||||
await utils.answer(message, self.strings("queueadd").format(filename))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<ответ на видео или ссылка на YouTube> - Добавить видео в очередь чата",
|
||||
de_doc=(
|
||||
"<auf ein Video oder einen YouTube-Link antworten> - Fügen Sie ein Video in"
|
||||
" die Warteschlange des Chats ein"
|
||||
),
|
||||
tr_doc=(
|
||||
"<bir videoya veya YouTube bağlantısına yanıt> - Bir videoyu sohbet"
|
||||
" sırasına ekleyin"
|
||||
),
|
||||
hi_doc="<एक वीडियो या YouTube लिंक पर उत्तर> - चैट की लंबित को एक वीडियो जोड़ें",
|
||||
uz_doc=(
|
||||
"<videoga yoki YouTube havolasiga javob> - Chatni qo'shish uchun video"
|
||||
" qo'shing"
|
||||
),
|
||||
)
|
||||
async def qaddv(self, message: Message):
|
||||
"""<reply to video or yt link> - Add video to chat's voicechat queue"""
|
||||
reply = await message.get_reply_message()
|
||||
link = utils.get_args_raw(message)
|
||||
if (not reply or not reply.media) and not link:
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
filename = None
|
||||
message = await utils.answer(message, self.strings("downloading"))
|
||||
if reply and reply.media:
|
||||
raw_data = await self._client.download_file(reply.document, bytes)
|
||||
|
||||
filename = self._get_fn(reply)
|
||||
|
||||
else:
|
||||
raw_data = link
|
||||
with contextlib.suppress(Exception):
|
||||
with YoutubeDL() as ydl:
|
||||
filename = ydl.extract_info(link, download=False).get(
|
||||
"title",
|
||||
None,
|
||||
)
|
||||
|
||||
if not filename:
|
||||
filename = "Some cool video"
|
||||
|
||||
filename = re.sub(r"\(.*?\)", "", filename)
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
|
||||
self._queue.setdefault(chat_id, []).append(
|
||||
{
|
||||
"data": raw_data,
|
||||
"filename": filename,
|
||||
"playing": False,
|
||||
"audio": False,
|
||||
"youtube": not (reply and reply.media),
|
||||
}
|
||||
)
|
||||
|
||||
if not any(i["playing"] for i in self._queue[chat_id]):
|
||||
self._queue[chat_id][-1]["playing"] = True
|
||||
if self._queue[chat_id][-1]["youtube"]:
|
||||
await self.play_video_yt(chat_id, raw_data)
|
||||
else:
|
||||
await self.play_video(chat_id, raw_data)
|
||||
|
||||
await utils.answer(message, self.strings("queueadd").format(filename))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Переключить трек",
|
||||
de_doc="Track wechseln",
|
||||
tr_doc="Parçayı değiştir",
|
||||
hi_doc="ट्रैक बदलें",
|
||||
uz_doc="Trackni o'zgartiring",
|
||||
)
|
||||
async def qnext(self, message: Message):
|
||||
"""Skips current audio in queue"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
|
||||
if len(self._queue.get(chat_id, [])) <= 1:
|
||||
await utils.answer(message, self.strings("no_queue"))
|
||||
return
|
||||
|
||||
self._queue[chat_id].pop(0)
|
||||
self._queue[chat_id][0]["playing"] = True
|
||||
if self._queue[chat_id][0]["audio"]:
|
||||
await self.play(chat_id, self._queue[chat_id][0]["data"])
|
||||
else:
|
||||
if self._queue[chat_id][0]["youtube"]:
|
||||
await self.play_video_yt(chat_id, self._queue[chat_id][0]["data"])
|
||||
else:
|
||||
await self.play_video(chat_id, self._queue[chat_id][0]["data"])
|
||||
|
||||
await message.delete()
|
||||
|
||||
async def _download_audio(self, name: str, message: Message) -> bytes:
|
||||
result = await self.musicdl.dl(name, only_document=True)
|
||||
try:
|
||||
return await self._client.download_file(result, bytes), self._get_fn(result)
|
||||
except Exception:
|
||||
return None, None
|
||||
|
||||
async def vcqcmd(self, message: Message):
|
||||
"""Get current chat's queue"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
if not self._queue.get(chat_id):
|
||||
await utils.answer(message, self.strings("no_queue"))
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("queue").format(
|
||||
"\n".join(
|
||||
[
|
||||
("🎧" if i["playing"] else "🕓")
|
||||
+ ("" if i["audio"] else "🎬")
|
||||
+ f" {i['filename']}"
|
||||
for i in self._queue[chat_id]
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
async def qrmcmd(self, message: Message):
|
||||
"""Remove song from queue"""
|
||||
if not self._queue.get(chat_id) or all(
|
||||
i["playing"] for i in self._queue[chat_id]
|
||||
):
|
||||
await utils.answer(message, self.strings("no_queue"))
|
||||
return
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings("choose_delete"),
|
||||
reply_markup=utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": ("🎧" if i["audio"] else "🎬") + i["filename"],
|
||||
"callback": self._inline__delete,
|
||||
"args": (chat_id, index),
|
||||
}
|
||||
for index, i in enumerate(self._queue[chat_id])
|
||||
if not i["playing"]
|
||||
],
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
async def _inline__delete(self, call: InlineCall, chat_id: int, index: int):
|
||||
del self._queue[chat_id][index]
|
||||
await call.answer("OK")
|
||||
await call.delete()
|
||||
|
||||
async def _inline__pause(self, call: InlineCall, chat_id: int):
|
||||
await self._app.pause_stream(chat_id)
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
await call.edit(msg, reply_markup=markup)
|
||||
|
||||
async def _inline__play(self, call: InlineCall, chat_id: int):
|
||||
await self._app.resume_stream(chat_id)
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
await call.edit(msg, reply_markup=markup)
|
||||
|
||||
async def _inline__mute(self, call: InlineCall, chat_id: int):
|
||||
await self._app.mute_stream(chat_id)
|
||||
self._muted[chat_id] = True
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
await call.edit(msg, reply_markup=markup)
|
||||
|
||||
async def _inline__unmute(self, call: InlineCall, chat_id: int):
|
||||
await self._app.unmute_stream(chat_id)
|
||||
self._muted[chat_id] = False
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
await call.edit(msg, reply_markup=markup)
|
||||
|
||||
async def _inline__stop(self, call: InlineCall, chat_id: int):
|
||||
with contextlib.suppress(KeyError):
|
||||
del self._queue[chat_id]
|
||||
|
||||
with contextlib.suppress(KeyError):
|
||||
del self._forms[chat_id]
|
||||
|
||||
with contextlib.suppress(KeyError):
|
||||
del self._muted[chat_id]
|
||||
|
||||
await self._app.leave_group_call(chat_id)
|
||||
await utils.answer(call, self.strings("stopped"))
|
||||
|
||||
async def _inline__next(self, call: InlineCall, chat_id: int):
|
||||
self._queue[chat_id].pop(0)
|
||||
self._queue[chat_id][0]["playing"] = True
|
||||
if self._queue[chat_id][0]["audio"]:
|
||||
await self.play(chat_id, self._queue[chat_id][0]["data"])
|
||||
else:
|
||||
if self._queue[chat_id][0]["youtube"]:
|
||||
await self.play_video_yt(chat_id, self._queue[chat_id][0]["data"])
|
||||
else:
|
||||
await self.play_video(chat_id, self._queue[chat_id][0]["data"])
|
||||
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
await call.edit(msg, reply_markup=markup)
|
||||
|
||||
def _get_inline_info(self, chat_id: int) -> tuple:
|
||||
if not self._queue.get(chat_id):
|
||||
return None, None
|
||||
|
||||
if len(self._queue[chat_id]) == 1:
|
||||
msg = self.strings("playing").format(
|
||||
utils.escape_html(self._queue[chat_id][0]["filename"]),
|
||||
)
|
||||
else:
|
||||
msg = self.strings("playing_with_next").format(
|
||||
utils.escape_html(self._queue[chat_id][0]["filename"]),
|
||||
utils.escape_html(self._queue[chat_id][1]["filename"]),
|
||||
)
|
||||
|
||||
try:
|
||||
is_playing = self._app.get_call(chat_id).status == "playing"
|
||||
except Exception:
|
||||
is_playing = True
|
||||
|
||||
markup = [
|
||||
[
|
||||
{
|
||||
"text": self.strings("stop"),
|
||||
"callback": self._inline__stop,
|
||||
"args": (chat_id,),
|
||||
},
|
||||
],
|
||||
[
|
||||
*(
|
||||
[
|
||||
{
|
||||
"text": self.strings("pause"),
|
||||
"callback": self._inline__pause,
|
||||
"args": (chat_id,),
|
||||
}
|
||||
]
|
||||
if is_playing
|
||||
else [
|
||||
{
|
||||
"text": self.strings("play"),
|
||||
"callback": self._inline__play,
|
||||
"args": (chat_id,),
|
||||
}
|
||||
]
|
||||
),
|
||||
*(
|
||||
[
|
||||
{
|
||||
"text": self.strings("mute"),
|
||||
"callback": self._inline__mute,
|
||||
"args": (chat_id,),
|
||||
}
|
||||
]
|
||||
if not self._muted.get(chat_id, False)
|
||||
else [
|
||||
{
|
||||
"text": self.strings("unmute"),
|
||||
"callback": self._inline__unmute,
|
||||
"args": (chat_id,),
|
||||
}
|
||||
]
|
||||
),
|
||||
],
|
||||
*(
|
||||
[
|
||||
[
|
||||
{
|
||||
"text": self.strings("next"),
|
||||
"callback": self._inline__next,
|
||||
"args": (chat_id,),
|
||||
}
|
||||
]
|
||||
]
|
||||
if len(self._queue[chat_id]) > 1
|
||||
else []
|
||||
),
|
||||
]
|
||||
|
||||
return msg, markup
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Приостановить воспроизведение",
|
||||
de_doc="Pausiere die Wiedergabe",
|
||||
tr_doc="Oynatmayı duraklat",
|
||||
hi_doc="प्लेबैक को रोकें",
|
||||
uz_doc="Oynatmani to'xtatish",
|
||||
)
|
||||
async def qpause(self, message: Message):
|
||||
"""Pause current chat's queue"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._app.pause_stream(chat_id)
|
||||
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._forms[chat_id].delete()
|
||||
self._forms[chat_id] = await utils.answer(message, msg, reply_markup=markup)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Остановить воспроизведение",
|
||||
de_doc="Stoppe die Wiedergabe",
|
||||
tr_doc="Oynatmayı durdur",
|
||||
hi_doc="प्लेबैक को बंद करें",
|
||||
uz_doc="Oynatmani to'xtatish",
|
||||
)
|
||||
async def qstop(self, message: Message):
|
||||
"""Stop current chat's queue"""
|
||||
await self._inline__stop(message, utils.get_chat_id(message))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Продолжить воспроизведение",
|
||||
de_doc="Fahre die Wiedergabe fort",
|
||||
tr_doc="Oynatmaya devam et",
|
||||
hi_doc="प्लेबैक को फिर से शुरू करें",
|
||||
uz_doc="Oynatmani davom ettirish",
|
||||
)
|
||||
async def qresume(self, message: Message):
|
||||
"""Resume current chat's queue"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._app.resume_stream(chat_id)
|
||||
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._forms[chat_id].delete()
|
||||
self._forms[chat_id] = await utils.answer(message, msg, reply_markup=markup)
|
||||
|
||||
async def play(self, chat_id: int, array: bytes):
|
||||
file = os.path.join(self._dir, f"{utils.rand(8)}.ogg")
|
||||
with open(file, "wb") as f:
|
||||
f.write(array)
|
||||
|
||||
await self._play(
|
||||
chat_id,
|
||||
types.AudioPiped(file, types.HighQualityAudio()),
|
||||
StreamType().pulse_stream,
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
if not self.config["silent_queue"]:
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._forms[chat_id].delete()
|
||||
self._forms[chat_id] = await self.inline.form(
|
||||
message=chat_id, text=msg, reply_markup=markup
|
||||
)
|
||||
|
||||
async def play_video(self, chat_id: int, array: bytes):
|
||||
file = os.path.join(self._dir, f"{utils.rand(8)}.mp4")
|
||||
with open(file, "wb") as f:
|
||||
f.write(array)
|
||||
|
||||
await self._play(
|
||||
chat_id,
|
||||
types.AudioVideoPiped(
|
||||
file,
|
||||
types.HighQualityAudio(),
|
||||
types.HighQualityVideo(),
|
||||
),
|
||||
StreamType().pulse_stream,
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
if not self.config["silent_queue"]:
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._forms[chat_id].delete()
|
||||
self._forms[chat_id] = await self.inline.form(
|
||||
message=chat_id, text=msg, reply_markup=markup
|
||||
)
|
||||
|
||||
async def play_video_yt(self, chat_id: int, link: str):
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"youtube-dl",
|
||||
"-g",
|
||||
"-f",
|
||||
"worst",
|
||||
link,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
await self._play(
|
||||
chat_id,
|
||||
types.AudioVideoPiped(
|
||||
stdout.decode().split("\n")[0],
|
||||
types.HighQualityAudio(),
|
||||
types.HighQualityVideo(),
|
||||
),
|
||||
StreamType().pulse_stream,
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
if not self.config["silent_queue"]:
|
||||
msg, markup = self._get_inline_info(chat_id)
|
||||
with contextlib.suppress(Exception):
|
||||
await self._forms[chat_id].delete()
|
||||
self._forms[chat_id] = await self.inline.form(
|
||||
message=chat_id,
|
||||
text=msg,
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
async def on_unload(self):
|
||||
shutil.rmtree(self._dir)
|
||||
for chat_id in self._muted:
|
||||
await self._app.leave_group_call(chat_id)
|
||||
397
hikariatama/ftg/vtt.py
Normal file
397
hikariatama/ftg/vtt.py
Normal file
@@ -0,0 +1,397 @@
|
||||
__version__ = (2, 0, 1)
|
||||
|
||||
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
|
||||
# █▀█ █ █ █ █▀█ █▀▄ █
|
||||
# © Copyright 2022
|
||||
# https://t.me/hikariatama
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# meta pic: https://static.dan.tatar/vtt_icon.png
|
||||
# meta banner: https://mods.hikariatama.ru/badges/vtt.jpg
|
||||
# meta developer: @hikarimods
|
||||
# scope: ffmpeg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
# requires: pydub speechrecognition python-ffmpeg
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import speech_recognition as sr
|
||||
from pydub import AudioSegment
|
||||
from telethon.tl.types import DocumentAttributeVideo, Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class VoicyMod(loader.Module):
|
||||
"""Recognize voice messages, audios, videos and round messages"""
|
||||
|
||||
strings = {
|
||||
"name": "Voicy",
|
||||
"converting": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Recognizing voice"
|
||||
" message...</b>"
|
||||
),
|
||||
"converted": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji>"
|
||||
" Recognized:</b>\n<i>{}</i>"
|
||||
),
|
||||
"voice_not_found": (
|
||||
"<emoji document_id=6041850934756119589>🫠</emoji> <b>Voice not found</b>"
|
||||
),
|
||||
"autovoice_off": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> I will not recognize"
|
||||
" voice messages in this chat</b>"
|
||||
),
|
||||
"autovoice_on": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> I will recognize"
|
||||
" voice messages in this chat</b>"
|
||||
),
|
||||
"_cfg_lang": "Language of voices to recognize",
|
||||
"_cfg_engine": "Recognition engine",
|
||||
"error": "🚫 <b>Recognition error!</b>",
|
||||
"_cfg_ignore_users": "Users to ignore",
|
||||
"_cfg_silent": "Silent mode - do not notify about errors",
|
||||
"too_big": "🫥 <b>Voice message is too big, I can't recognise it...</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"converting": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Распознаю голосовое"
|
||||
" сообщение...</b>"
|
||||
),
|
||||
"converted": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji>"
|
||||
" Распознано:</b>\n<i>{}</i>"
|
||||
),
|
||||
"voice_not_found": (
|
||||
"<emoji document_id=6041850934756119589>🫠</emoji> <b>Нет ответа на"
|
||||
" войс</b>"
|
||||
),
|
||||
"autovoice_off": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Я больше не буду"
|
||||
" распознавать голосовые сообщения в этом чате</b>"
|
||||
),
|
||||
"autovoice_on": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Я буду распознавать"
|
||||
" голосовые сообщения в этом чате</b>"
|
||||
),
|
||||
"_cmd_doc_voicy": "Распознает голосовое сообщение",
|
||||
"_cmd_doc_autovoice": (
|
||||
"Включить\\выключить автораспознавание голосовых сообщений в чате"
|
||||
),
|
||||
"_cls_doc": "Распознает голосовые сообщения, аудио, видео и кругляши",
|
||||
"_cfg_lang": "Язык для распознавания голосовых сообщений",
|
||||
"_cfg_engine": "Распознаватель",
|
||||
"_cfg_ignore_users": "Игнорировать пользователей",
|
||||
"_cfg_silent": "Тихий режим - не оповещать об ошибках",
|
||||
"error": "🚫 <b>Ошибка распознавания!</b>",
|
||||
"too_big": (
|
||||
"🫥 <b>Голосовое сообщение слишком большое, я не могу его распознать...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"converting": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Sprachnachricht wird"
|
||||
" erkannt...</b>"
|
||||
),
|
||||
"converted": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji>"
|
||||
" Erkannt:</b>\n<i>{}</i>"
|
||||
),
|
||||
"voice_not_found": (
|
||||
"<emoji document_id=6041850934756119589>🫠</emoji> <b>Keine Antwort auf"
|
||||
" Voice</b>"
|
||||
),
|
||||
"autovoice_off": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Ich werde in diesem"
|
||||
" Chat keine Sprachnachrichten mehr erkennen</b>"
|
||||
),
|
||||
"autovoice_on": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Ich werde in diesem"
|
||||
" Chat Sprachnachrichten erkennen</b>"
|
||||
),
|
||||
"_cmd_doc_voicy": "Erkennt eine Sprachnachricht",
|
||||
"_cmd_doc_autovoice": (
|
||||
"Aktiviert\\Deaktiviert die automatische Erkennung von Sprachnachrichten im"
|
||||
" Chat"
|
||||
),
|
||||
"_cls_doc": "Erkennt Sprachnachrichten, Audios, Videos und Rundnachrichten",
|
||||
"_cfg_lang": "Sprache für die Spracherkennung",
|
||||
"_cfg_engine": "Erkennungsprogramm",
|
||||
"_cfg_ignore_users": "Benutzer ignorieren",
|
||||
"_cfg_silent": "Stiller Modus - Fehler nicht melden",
|
||||
"error": "🚫 <b>Erkennungsfehler!</b>",
|
||||
"too_big": (
|
||||
"🫥 <b>Sprachnachricht ist zu groß, ich kann sie nicht erkennen...</b>"
|
||||
),
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"converting": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Sesli mesajı"
|
||||
" tanıyorum...</b>"
|
||||
),
|
||||
"converted": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji>"
|
||||
" Tanımlandı:</b>\n<i>{}</i>"
|
||||
),
|
||||
"voice_not_found": (
|
||||
"<emoji document_id=6041850934756119589>🫠</emoji> <b>Sesli mesaja cevap"
|
||||
" yok</b>"
|
||||
),
|
||||
"autovoice_off": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Bu sohbetteki sesli"
|
||||
" mesajları artık tanımayacağım</b>"
|
||||
),
|
||||
"autovoice_on": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Bu sohbetteki sesli"
|
||||
" mesajları tanıyacağım</b>"
|
||||
),
|
||||
"_cmd_doc_voicy": "Sesli mesajı tanır",
|
||||
"_cmd_doc_autovoice": (
|
||||
"Sohbetteki sesli mesajların otomatik tanınmasını etkinleştirir\\devre dışı"
|
||||
" bırakır"
|
||||
),
|
||||
"_cls_doc": "Sesli mesajları, sesleri, videoları ve çevirileri tanır",
|
||||
"_cfg_lang": "Ses tanıma için dil",
|
||||
"_cfg_engine": "Tanıyıcı",
|
||||
"_cfg_ignore_users": "Kullanıcıları yoksay",
|
||||
"_cfg_silent": "Sessiz mod - hataları bildirmeyin",
|
||||
"error": "🚫 <b>Tanıma hatası!</b>",
|
||||
"too_big": "🫥 <b>Sesli mesaj çok büyük, tanıyamıyorum...</b>",
|
||||
}
|
||||
|
||||
strings_uz = {
|
||||
"converting": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> So'zli xabar"
|
||||
" aniqlanmoqda...</b>"
|
||||
),
|
||||
"converted": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji>"
|
||||
" Aniqlandi:</b>\n<i>{}</i>"
|
||||
),
|
||||
"voice_not_found": (
|
||||
"<emoji document_id=6041850934756119589>🫠</emoji> <b>So'zli xabarga"
|
||||
" javob yo'q</b>"
|
||||
),
|
||||
"autovoice_off": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Bu suhbatda so'zli"
|
||||
" xabarlar aniqlanmaydi</b>"
|
||||
),
|
||||
"autovoice_on": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> Bu suhbatda so'zli"
|
||||
" xabarlar aniqlanadi</b>"
|
||||
),
|
||||
"_cmd_doc_voicy": "So'zli xabarni aniqlash",
|
||||
"_cmd_doc_autovoice": (
|
||||
"Suhbatdagi so'zli xabarlar avtomatik aniqlashini yoqish\\o'chirish"
|
||||
),
|
||||
"_cls_doc": "So'zli xabarlar, audio, videolar va qarishmalarni aniqlaydi",
|
||||
"_cfg_lang": "Tilni aniqlash uchun",
|
||||
"_cfg_engine": "Aniqlash moliyaviyasi",
|
||||
"_cfg_ignore_users": "Foydalanuvchilarni e'tiborsiz qoldirish",
|
||||
"_cfg_silent": "Sessiz rejim - xatolarni bildirmang",
|
||||
"error": "🚫 <b>Aniqlash xatosi!</b>",
|
||||
"too_big": "🫥 <b>So'zli xabar juda katta, aniqlay olmayman...</b>",
|
||||
}
|
||||
|
||||
strings_hi = {
|
||||
"converting": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> वायस संदेश"
|
||||
" पहचान रहा है...</b>"
|
||||
),
|
||||
"converted": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji>"
|
||||
" पहचान लिया:</b>\n<i>{}</i>"
|
||||
),
|
||||
"voice_not_found": (
|
||||
"<emoji document_id=6041850934756119589>🫠</emoji> <b>वायस संदेश"
|
||||
" के लिए जवाब नहीं</b>"
|
||||
),
|
||||
"autovoice_off": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> इस चैट में वायस"
|
||||
" संदेश पहचान नहीं करेंगे</b>"
|
||||
),
|
||||
"autovoice_on": (
|
||||
"<b><emoji document_id=6041850934756119589>🫠</emoji> इस चैट में वायस"
|
||||
" संदेश पहचान करेंगे</b>"
|
||||
),
|
||||
"_cmd_doc_voicy": "वायस संदेश पहचान करें",
|
||||
"_cmd_doc_autovoice": "इस चैट में वायस संदेशों को ऑटोमैटिक पहचानने को सक्षम\\अक्षम करें",
|
||||
"_cls_doc": "वायस संदेश, ऑडियो, वीडियो और रैडियो संदेश पहचानता है",
|
||||
"_cfg_lang": "पहचान के लिए भाषा",
|
||||
"_cfg_engine": "पहचानकर्ता",
|
||||
"_cfg_ignore_users": "उपयोगकर्ताओं को नजरअंदाज करें",
|
||||
"_cfg_silent": "शांत मोड - त्रुटियों को सूचित न करें",
|
||||
"error": "🚫 <b>पहचान त्रुटि!</b>",
|
||||
"too_big": "🫥 <b>वायस संदेश बहुत बड़ा है, पहचान नहीं कर सकता...</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"language",
|
||||
"ru-RU",
|
||||
lambda: self.strings("_cfg_lang"),
|
||||
validator=loader.validators.RegExp(r"^[a-z]{2}-[A-Z]{2}$"),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_users",
|
||||
[],
|
||||
lambda: self.strings("_cfg_ignore_users"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"silent",
|
||||
False,
|
||||
lambda: self.strings("_cfg_silent"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.v2a = await self.import_lib(
|
||||
"https://libs.hikariatama.ru/v2a.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
self.chats = self.pointer("chats", [])
|
||||
|
||||
async def recognize(self, message: Message):
|
||||
try:
|
||||
m = await utils.answer(message, self.strings("converting"))
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
file = os.path.join(
|
||||
tmpdir,
|
||||
"audio.mp3" if message.audio else "audio.ogg",
|
||||
)
|
||||
|
||||
data = await message.download_media(bytes)
|
||||
|
||||
if message.video:
|
||||
data = await self.v2a.convert(data, "audio.ogg")
|
||||
|
||||
with open(file, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
song = AudioSegment.from_file(
|
||||
file, format="mp3" if message.audio else "ogg"
|
||||
)
|
||||
song.export(os.path.join(tmpdir, "audio.wav"), format="wav")
|
||||
|
||||
r = sr.Recognizer()
|
||||
|
||||
with sr.AudioFile(os.path.join(tmpdir, "audio.wav")) as source:
|
||||
audio_data = r.record(source)
|
||||
text = await utils.run_sync(
|
||||
r.recognize_google,
|
||||
audio_data,
|
||||
language=self.config["language"],
|
||||
)
|
||||
m = await utils.answer(
|
||||
m,
|
||||
self.strings("converted").format(text),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Can't recognize")
|
||||
if not self.config["silent"]:
|
||||
m = await utils.answer(m, self.strings("error"))
|
||||
await asyncio.sleep(3)
|
||||
if not message.out:
|
||||
await m.delete()
|
||||
|
||||
@loader.unrestricted
|
||||
async def voicycmd(self, message: Message):
|
||||
"""Recognize voice message"""
|
||||
reply = await message.get_reply_message()
|
||||
try:
|
||||
is_voice = (
|
||||
reply.video or reply.audio or reply.media.document.attributes[0].voice
|
||||
)
|
||||
except (AttributeError, IndexError):
|
||||
is_voice = False
|
||||
|
||||
if not reply or not reply.media or not is_voice:
|
||||
await utils.answer(message, self.strings("voice_not_found"))
|
||||
return
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
await self.recognize(reply)
|
||||
|
||||
if message.out:
|
||||
await message.delete()
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
try:
|
||||
if (
|
||||
utils.get_chat_id(message) not in self.get("chats", [])
|
||||
or not message.media
|
||||
or not message.video
|
||||
and not message.audio
|
||||
and not message.media.document.attributes[0].voice
|
||||
or message.gif
|
||||
or message.sticker
|
||||
):
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if message.sender_id in self.config["ignore_users"]:
|
||||
return
|
||||
|
||||
if (
|
||||
(
|
||||
message.video
|
||||
and (
|
||||
next(
|
||||
attr
|
||||
for attr in message.video.attributes
|
||||
if isinstance(attr, DocumentAttributeVideo)
|
||||
).duration
|
||||
> 120
|
||||
)
|
||||
)
|
||||
or getattr(
|
||||
(
|
||||
getattr(
|
||||
getattr(getattr(message, "media", None), "document", None),
|
||||
"attributes",
|
||||
False,
|
||||
)
|
||||
or [None]
|
||||
)[0],
|
||||
"duration",
|
||||
0,
|
||||
)
|
||||
> 300
|
||||
or message.document.size / 1024 / 1024 > 5
|
||||
):
|
||||
if not self.config["silent"]:
|
||||
await utils.answer(message, self.strings("too_big"))
|
||||
return
|
||||
|
||||
await self.recognize(message)
|
||||
|
||||
async def autovoicecmd(self, message: Message):
|
||||
"""Toggle automatic recognition in current chat"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
|
||||
if chat_id in self.get("chats", []):
|
||||
self.chats.remove(chat_id)
|
||||
await utils.answer(message, self.strings("autovoice_off"))
|
||||
else:
|
||||
self.chats.append(chat_id)
|
||||
await utils.answer(message, self.strings("autovoice_on"))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user