Commited backup

This commit is contained in:
2025-07-10 21:02:34 +03:00
parent 952c1001e3
commit da0b80823e
1310 changed files with 254133 additions and 41 deletions

2
hikariatama/ftg/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.stfolder
.stignore

661
hikariatama/ftg/LICENSE Normal file
View 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/>.

View 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

View 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("&gt;", ">")
.replace("&lt;", "<")
.replace("&quot;", '"')
.replace("&amp;", "&"),
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)

View 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
View 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
),
)

View 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()

View 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
View 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
View 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
View 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")
)

View 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
View 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>")

View 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
View 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))

View 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
View 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
View 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>",
}
],
)

View 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
View 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
View 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
View 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
View 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,
),
)

View 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"))

View 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
View 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"]})
),
)

View 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],
)

View 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)

View 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
View 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
View 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

View 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": "ı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
)

View 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ꋫꃃꏸꁕꍟꄘꁍꑛꂑꀭꀗ꒒ꁒꁹꆂꉣꁸ꒓ꌚ꓅ꐇꏝꅐꇓꐟꁴ",
"EGIKᑎOᔕTYᘔ1234567890ᗩEGIKᑎOᔕTYᘔ",
"",
"𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵𝟬𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭",
"𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿𝟶𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉",
]
@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
View 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,
)

View 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"&quot;(.+?)&quot;", 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),
)

View 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

View 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()

View 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)

View 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)

View 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"],),
},
)

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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"))

View 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
View 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}],
],
)

View 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,
)

View 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",
}

View 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
View 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
View 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"))

View 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
View 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
View 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
View 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()

View 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()

View 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
View 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!")

View 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 "❤️")

View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

87
hikariatama/ftg/nometa.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
)

View 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
View 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
View 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
View 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>'
),
)

View 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 &lt;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)
],
)

View 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")
)

View 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))

View 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"))

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

1780
hikariatama/ftg/sticks.py Normal file

File diff suppressed because it is too large Load Diff

249
hikariatama/ftg/surl.py Normal file
View 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": "ı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
View 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
View 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 boldi</b>"
),
"_cfg_doc_default_message": "Odatiy etiket xabari",
"_cfg_doc_delete": "Etiketdan song xabarlarni ochirish",
"_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 jonatish",
"_cfg_doc_cycle_tagging": (
"Xabar boyicha tugmani ishlatib, skriptni toxtatguncha 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
View 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]),
)

View 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
View 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
View 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"))

View 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
View 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
View 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"))

View 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,
)

View 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
View 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))

View 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
View 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()

View 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 govushda</b>",
"joined": "🎙 <b>Govushga qoshildi</b>",
"no_reply": "🚫 <b>Xabarga javob bering</b>",
"no_queue": "🚫 <b>Navbat yoq</b>",
"queue": "🎙 <b>Navbat</b>:\n\n{}",
"queueadd": "🎧 <b>{} navbatga qoshildi</b>",
"queueaddv": "📼 <b>{} navbatga qoshildi</b>",
"downloading": "📥 <b>Yuklanmoqda...</b>",
"playing": "🎶 <b>Oynatilmoqda {}</b>",
"playing_with_next": "🎶 <b>Oynatilmoqda {}</b>\n➡️ <b>Keyingi: {}</b>",
"pause": "🎵 Toxtatish",
"play": "🎵 Oynatish",
"mute": "🔇 Sessiz",
"unmute": "🔈 Suv",
"next": "➡️ Keyingi",
"stopped": "🚨 <b>Toxtatildi</b>",
"stop": "🚨 Toxtatish",
"choose_delete": "♻️ <b>Ochirish 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
View 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