Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b7ae6b2c2 | ||
|
|
9fc65440d0 | ||
|
|
295f8505e1 | ||
|
|
bc3191fadf | ||
|
|
0582acb6a0 | ||
|
|
2886690d64 | ||
|
|
f3bdf1a41d | ||
|
|
60cca88111 | ||
|
|
f816646b3b | ||
|
|
cc577ec087 | ||
|
|
9ca03ad853 | ||
|
|
3c09738c06 | ||
|
|
490827dc17 | ||
|
|
60768a9ed3 | ||
|
|
97e6b665c7 | ||
|
|
2b757e180e | ||
|
|
f6cb33029c | ||
|
|
806b84eaf5 | ||
|
|
26eef4db57 | ||
|
|
284c673b14 | ||
|
|
5126770ffb | ||
|
|
54bfeb81ae | ||
|
|
989d89df83 | ||
|
|
5cd27a01c5 | ||
|
|
8e1b3e482d | ||
|
|
2b39b616b3 | ||
|
|
928f873cfa | ||
|
|
b2fecab660 | ||
|
|
a48b747b8e | ||
|
|
d71da321d8 | ||
|
|
3d4c6da746 | ||
|
|
30e40cb3e1 | ||
|
|
2340deab4e | ||
|
|
f9eb14e5e3 | ||
|
|
3780936e01 | ||
|
|
2040382842 | ||
|
|
46d9c424b7 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,17 +1,8 @@
|
||||
env
|
||||
src/plugins/
|
||||
.wwebjs_auth
|
||||
.wwebjs_cache
|
||||
.claude
|
||||
downloads
|
||||
src/node_modules/
|
||||
node_modules/
|
||||
cookies.txt
|
||||
bin/
|
||||
mychats.txt
|
||||
manybot.conf
|
||||
update.log
|
||||
logs/audio-error.log
|
||||
logs/video-error.log
|
||||
registry.json
|
||||
|
||||
bin/
|
||||
36
Dockerfile
36
Dockerfile
@@ -1,36 +0,0 @@
|
||||
FROM node:20-slim
|
||||
|
||||
# Install dependencies for Puppeteer/Chrome
|
||||
RUN apt-get update && apt-get install -y \
|
||||
chromium \
|
||||
chromium-sandbox \
|
||||
fonts-ipafont-gothic \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-thai-tlwg \
|
||||
fonts-kacst \
|
||||
fonts-freefont-ttf \
|
||||
libxss1 \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set environment variables for Puppeteer
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||
|
||||
# Set terminal to UTF-8 to suport QR Code characters
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm install --omit=dev
|
||||
|
||||
# Copy application files
|
||||
COPY . .
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p session logs
|
||||
|
||||
CMD ["node", "src/main.js"]
|
||||
695
LICENSE
695
LICENSE
@@ -1,674 +1,21 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 SyntaxError!
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
197
README.md
197
README.md
@@ -1,194 +1,15 @@
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
<p>
|
||||
<strong>Bot para WhatsApp 100% local, sem API oficial</strong>
|
||||
</p>
|
||||
Criei esse bot para servir um grupo de amigos. Meu foco não é fazer ele funcionar para todo mundo.
|
||||
|
||||
<p>
|
||||
<a href="#-recursos">Recursos</a> .
|
||||
<a href="#-instalação-rápida">Instalação</a> .
|
||||
<a href="#-uso">Uso</a> .
|
||||
<a href="#-plugins">Plugins</a> .
|
||||
<a href="#-documentação">Documentação</a>
|
||||
</p>
|
||||
Ele é 100% local e gratuito, sem necessidade de APIs burocraticas. Usufrui da biblioteca `whatsapp-web.js`, que permite bastante coisa mesmo sem a API oficial.
|
||||
|
||||
<p>
|
||||
🇧🇷 Português · <a href="README_EN.md">🇺🇸 English</a>
|
||||
</p>
|
||||
Você consegue totalmente clonar esse repoistório e rodar seu próprio ManyBot. A licenca MIT permite que você modifique o que quiser e faça seu próprio bot.
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
|
||||
<img src="https://img.shields.io/badge/npm-9+-CB3837?logo=npm&logoColor=white" alt="npm 9+">
|
||||
<img src="https://img.shields.io/badge/License-GPL--v3-blue.svg" alt="License: GPL v3">
|
||||
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows-lightgrey" alt="Platform">
|
||||
</p>
|
||||
Algumas funcionalidades desse bot inclui:
|
||||
- Funciona em multiplos chats em apenas uma única sessão
|
||||
- Comandos de jogos e download com yt-dlp
|
||||
- Gerador de figurinhas
|
||||
- Ferramenta para pegar IDs dos chats
|
||||
- Entre outros
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/whatsapp--web.js-%2325D366?logo=whatsapp&logoColor=white" alt="whatsapp-web.js">
|
||||
<img src="https://img.shields.io/badge/headless-Automated-green" alt="Headless">
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
> **Versão Oficial Online**
|
||||
> Quer usar o ManyBot sem instalar? Adicione o bot oficial:
|
||||
>
|
||||
> **+55 (16) 99459-1903**
|
||||
>
|
||||
> Online 24h (quando possível) - Disponibilidade não garantida
|
||||
>
|
||||
> Ao adicionar, você concorda com os [Termos de Uso](TERMOS_pt-br.md)
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Recursos
|
||||
|
||||
- **100% Local** - Sem depender da API oficial do WhatsApp
|
||||
- **Multi-chat** - Suporte a múltiplos chats em uma única sessão
|
||||
- **Sistema de Plugins** - Adicione, remova ou crie funcionalidades sem mexer no núcleo
|
||||
- **Headless** - Funciona em segundo plano sem interface gráfica
|
||||
- **Fácil Configuração** - Arquivo de config simples e intuitivo
|
||||
|
||||
---
|
||||
|
||||
## Instalação Rápida
|
||||
|
||||
### Opção 1: Usar o Bot Oficial (Sem instalar)
|
||||
|
||||
Adicione o número **+55 (16) 99459-1903** aos seus contatos e envie `!many` para ver os comandos disponíveis.
|
||||
|
||||
**Status:** 🟢 Online (24h quando possível, mas sem garantia)
|
||||
|
||||
> ⚠️ **Importante:** Ao usar o bot oficial, você concorda com os [Termos de Uso](TERMOS_pt-br.md). Leia antes de adicionar!
|
||||
|
||||
---
|
||||
|
||||
### Opção 2: Instalar sua Própria Versão
|
||||
|
||||
```bash
|
||||
# 1. Clone o repositório
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
|
||||
# 2. Crie o arquivo de configuração
|
||||
cp manybot.conf.example manybot.conf
|
||||
|
||||
# 3. Configure conforme sua necessidade (veja a documentação)
|
||||
nano manybot.conf
|
||||
|
||||
# 4. Execute o script de instalação
|
||||
bash ./setup
|
||||
|
||||
# 5. Rode o bot
|
||||
node ./src/main.js
|
||||
```
|
||||
|
||||
📱 **Escaneie o QR Code** no WhatsApp: Menu → Dispositivos conectados → Conectar um dispositivo
|
||||
|
||||
> **⚡ Pronto!** Veja a [documentação completa](docs/INSTALACAO.md) para mais detalhes.
|
||||
|
||||
---
|
||||
|
||||
## 💻 Uso
|
||||
|
||||
```bash
|
||||
# Iniciar o bot
|
||||
node ./src/main.js
|
||||
|
||||
# Atualizar para a versão mais recente
|
||||
bash ./update
|
||||
|
||||
# Descobrir IDs de chats
|
||||
node src/utils/get_id.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Plugins
|
||||
|
||||
O ManyBot é construído em torno de um sistema de plugins. O kernel apenas conecta ao WhatsApp e distribui as mensagens — os plugins decidem o que fazer.
|
||||
|
||||
### Gerenciando Plugins com ManyPlug
|
||||
|
||||
Instale e gerencie plugins usando o **ManyPlug CLI**:
|
||||
|
||||
```bash
|
||||
# Instalar o gerenciador
|
||||
npm install -g @freakk.dev/manyplug
|
||||
|
||||
# Criar um novo plugin
|
||||
cd src/plugins
|
||||
manyplug init meu-plugin --category utility
|
||||
|
||||
# Instalar de outro diretório
|
||||
manyplug install --local ../outro-plugin
|
||||
|
||||
# Listar plugins instalados
|
||||
manyplug list
|
||||
```
|
||||
|
||||
### Criar um Plugin
|
||||
|
||||
```javascript
|
||||
// plugins/meu-plugin/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||
await msg.reply("Olá! 👋");
|
||||
}
|
||||
```
|
||||
|
||||
Veja mais na [documentação de plugins](docs/PLUGINS.md).
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentação
|
||||
|
||||
- [📥 Instalação Completa](docs/INSTALACAO.md) — Linux, Windows, Termux
|
||||
- [⚙️ Configuração](docs/CONFIGURACAO.md) — Todas as opções do `manybot.conf`
|
||||
- [🔌 Criando Plugins](docs/PLUGINS.md) — Guia completo de desenvolvimento
|
||||
- [🛠️ API de Plugins](docs/API.md) — Referência de objetos `msg` e `api`
|
||||
|
||||
## 🌍 Internacionalização
|
||||
|
||||
O ManyBot suporta múltiplos idiomas. Configure no `manybot.conf`:
|
||||
|
||||
```bash
|
||||
LANGUAGE=pt # Português
|
||||
LANGUAGE=en # English
|
||||
LANGUAGE=es # Español
|
||||
```
|
||||
|
||||
- **Padrão:** Inglês (`en`)
|
||||
- **Fallback:** Se o idioma selecionado não existir, o bot usa inglês
|
||||
|
||||
---
|
||||
|
||||
## 📋 Requisitos
|
||||
|
||||
- **Node.js** 18+
|
||||
- **NPM** 9+
|
||||
- **Linux** ou **Windows** (via Git Bash)
|
||||
|
||||
> ⚠️ Android/iOS e Termux têm suporte experimental sem garantias.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Licença
|
||||
|
||||
Distribuído sob a licença **GPLv3**. Veja [LICENSE](LICENSE) para mais detalhes.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**[⬆ Voltar ao topo](#)**
|
||||
|
||||
</div>
|
||||
|
||||
194
README_EN.md
194
README_EN.md
@@ -1,194 +0,0 @@
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
<p>
|
||||
<strong>100% Local WhatsApp Bot, no official API</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#-features">Features</a> •
|
||||
<a href="#-quick-install">Install</a> •
|
||||
<a href="#-usage">Usage</a> •
|
||||
<a href="#-plugins">Plugins</a> •
|
||||
<a href="#-documentation">Documentation</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="README.md">🇧🇷 Português</a> · <a href="README_EN.md">🇺🇸 English</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
|
||||
<img src="https://img.shields.io/badge/npm-9+-CB3837?logo=npm&logoColor=white" alt="npm 9+">
|
||||
<img src="https://img.shields.io/badge/License-GPL--v3-blue.svg" alt="License: GPL v3">
|
||||
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows-lightgrey" alt="Platform">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/whatsapp--web.js-%2325D366?logo=whatsapp&logoColor=white" alt="whatsapp-web.js">
|
||||
<img src="https://img.shields.io/badge/headless-Automated-green" alt="Headless">
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
> 🟢 **Official Instance Online**
|
||||
> Want to use ManyBot without installing? Add the official bot:
|
||||
>
|
||||
> **+55 (16) 99459-1903**
|
||||
>
|
||||
> Online 24h (when possible) · Availability not guaranteed
|
||||
>
|
||||
> By adding, you agree to the [Terms of Use](TERMS_en-us.md)
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **100% Local** — No dependency on the official WhatsApp API
|
||||
- **Multi-chat** — Support for multiple chats in a single session
|
||||
- **Plugin System** — Add, remove, or create features without touching the core
|
||||
- **Headless** — Runs in the background without a GUI
|
||||
- **Easy Configuration** — Simple and intuitive config file
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Install
|
||||
|
||||
### Option 1: Use the Official Bot (No install)
|
||||
|
||||
Add the number **+55 (16) 99459-1903** to your contacts and send `!many` to see available commands.
|
||||
|
||||
**Status:** 🟢 Online (24h when possible, but no guarantee)
|
||||
|
||||
> ⚠️ **Important:** By using the official bot, you agree to the [Terms of Use](TERMS_en-us.md). Read before adding!
|
||||
|
||||
---
|
||||
|
||||
### Option 2: Install Your Own Version
|
||||
|
||||
```bash
|
||||
# 1. Clone the repository
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
|
||||
# 2. Create the config file
|
||||
cp manybot.conf.example manybot.conf
|
||||
|
||||
# 3. Configure as needed (see documentation)
|
||||
nano manybot.conf
|
||||
|
||||
# 4. Run the install script
|
||||
bash ./setup
|
||||
|
||||
# 5. Run the bot
|
||||
node ./src/main.js
|
||||
```
|
||||
|
||||
📱 **Scan the QR Code** on WhatsApp: Menu → Linked Devices → Link a Device
|
||||
|
||||
> **⚡ Done!** See the [full documentation](docs/INSTALLATION.md) for more details.
|
||||
|
||||
---
|
||||
|
||||
## 💻 Usage
|
||||
|
||||
```bash
|
||||
# Start the bot
|
||||
node ./src/main.js
|
||||
|
||||
# Update to the latest version
|
||||
bash ./update
|
||||
|
||||
# Discover chat IDs
|
||||
node src/utils/get_id.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Plugins
|
||||
|
||||
ManyBot is built around a plugin system. The kernel only connects to WhatsApp and distributes messages — plugins decide what to do.
|
||||
|
||||
### Managing Plugins with ManyPlug
|
||||
|
||||
Install and manage plugins using the **ManyPlug CLI**:
|
||||
|
||||
```bash
|
||||
# Install the plugin manager
|
||||
npm install -g @freakk.dev/manyplug
|
||||
|
||||
# Create a new plugin
|
||||
cd src/plugins
|
||||
manyplug init my-plugin --category utility
|
||||
|
||||
# Install from another directory
|
||||
manyplug install --local ../another-plugin
|
||||
|
||||
# List installed plugins
|
||||
manyplug list
|
||||
```
|
||||
|
||||
### Create a Plugin
|
||||
|
||||
```javascript
|
||||
// plugins/my-plugin/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "hello")) return;
|
||||
await msg.reply("Hello! 👋");
|
||||
}
|
||||
```
|
||||
|
||||
See more in the [plugin documentation](docs/PLUGINS_EN.md).
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- [📥 Full Installation](docs/INSTALLATION.md) — Linux, Windows, Termux
|
||||
- [⚙️ Configuration](docs/CONFIGURATION.md) — All `manybot.conf` options
|
||||
- [🔌 Creating Plugins](docs/PLUGINS.md) — Complete development guide
|
||||
- [🛠️ Plugin API](docs/API.md) — Reference for `msg` and `api` objects
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
ManyBot supports multiple languages. Configure in `manybot.conf`:
|
||||
|
||||
```bash
|
||||
LANGUAGE=pt # Português
|
||||
LANGUAGE=en # English
|
||||
LANGUAGE=es # Español
|
||||
```
|
||||
|
||||
- **Default:** English (`en`)
|
||||
- **Fallback:** If selected language doesn't exist, bot falls back to English
|
||||
|
||||
---
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- **Node.js** 18+
|
||||
- **NPM** 9+
|
||||
- **Linux** or **Windows** (via Git Bash)
|
||||
|
||||
> ⚠️ Android/iOS and Termux have experimental support with no guarantees.
|
||||
|
||||
---
|
||||
|
||||
## 📝 License
|
||||
|
||||
Distributed under the **GPLv3** license. See [LICENSE](LICENSE) for details.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**[⬆ Back to top](#)**
|
||||
|
||||
</div>
|
||||
219
TERMOS_pt-br.md
219
TERMOS_pt-br.md
@@ -1,219 +0,0 @@
|
||||
## Termos de Uso
|
||||
|
||||
> Este arquivo poderá ser movido no futuro, assim que o site oficial for lançado.
|
||||
|
||||
### 1. Natureza do Software
|
||||
|
||||
Este projeto é uma ferramenta de automação de código aberto para plataformas de mensagens.
|
||||
|
||||
Por padrão, o sistema principal não gera mídia nem conteúdo. No entanto, ele pode suportar plug-ins, extensões ou modificações que adicionem tais funcionalidades (incluindo a geração de figurinhas ou mídia).
|
||||
|
||||
Ele pode ser usado:
|
||||
|
||||
* Como uma instância auto-hospedada (local)
|
||||
* Por meio de uma instância hospedada oficialmente operada pelo desenvolvedor
|
||||
|
||||
O desenvolvedor fornece este software estritamente como uma ferramenta técnica e não cria, controla ou endossa conteúdo gerado pelo usuário.
|
||||
|
||||
---
|
||||
|
||||
### 2. Âmbito de Responsabilidade
|
||||
|
||||
#### 2.1 Instância Hospedada Oficialmente
|
||||
|
||||
Ao usar a instância oficial:
|
||||
|
||||
* Mensagens e mídia podem passar pela infraestrutura do desenvolvedor
|
||||
* O conteúdo é processado automaticamente e não é monitorado ativamente
|
||||
* Os dados são processados e, em seguida, descartados permanentemente
|
||||
|
||||
O desenvolvedor não assume responsabilidade por qualquer conteúdo processado pelo sistema.
|
||||
|
||||
#### 2.2 Versões auto-hospedadas / modificadas
|
||||
|
||||
Se você executar, modificar ou distribuir sua própria versão:
|
||||
|
||||
* Você é totalmente responsável pela sua instância
|
||||
* O desenvolvedor não tem controle sobre sua implantação
|
||||
* Quaisquer plug-ins, modificações ou extensões são de sua responsabilidade
|
||||
|
||||
---
|
||||
|
||||
### 3. Responsabilidade do usuário
|
||||
|
||||
Ao utilizar este software, você concorda que:
|
||||
|
||||
* Você é o único responsável por todo o conteúdo que gerar, processar ou compartilhar
|
||||
* Você cumprirá todas as leis e regulamentos aplicáveis
|
||||
* Você não utilizará este software para fins ilegais, prejudiciais ou abusivos
|
||||
|
||||
---
|
||||
|
||||
### 4. Usos proibidos
|
||||
|
||||
Os seguintes usos são estritamente proibidos:
|
||||
|
||||
* Atividades ilegais de qualquer tipo
|
||||
* Conteúdo que viole as leis aplicáveis
|
||||
* Conteúdo abusivo, prejudicial ou explorador
|
||||
* Uso indevido da plataforma para prejudicar terceiros
|
||||
|
||||
O desenvolvedor não tolera tal uso, mesmo que não seja possível evitá-lo totalmente.
|
||||
|
||||
---
|
||||
|
||||
### 5. Isenção de Responsabilidade
|
||||
|
||||
O desenvolvedor não se responsabiliza por:
|
||||
|
||||
* Qualquer conteúdo gerado pelos usuários
|
||||
* Qualquer uso indevido do software
|
||||
* Quaisquer danos ou consequências legais resultantes do uso
|
||||
|
||||
Isso inclui, mas não se limita a:
|
||||
|
||||
* Conteúdo ofensivo ou inadequado
|
||||
* Conteúdo ilegal criado ou distribuído pelos usuários
|
||||
|
||||
---
|
||||
|
||||
### 6. Ausência de monitoramento ativo
|
||||
|
||||
O sistema opera automaticamente e não é monitorado ativamente o tempo todo.
|
||||
|
||||
O desenvolvedor não é obrigado a revisar todas as atividades, mas pode tomar medidas quando for relatado ou detectado uso indevido.
|
||||
|
||||
O tempo de resposta não é garantido.
|
||||
|
||||
---
|
||||
|
||||
### 7. Denúncias e aplicação de medidas
|
||||
|
||||
Os usuários podem denunciar o uso indevido por meio do canal de contato fornecido (por exemplo, e-mail).
|
||||
|
||||
Ao receber denúncias válidas, o desenvolvedor pode, a seu exclusivo critério:
|
||||
|
||||
* Bloquear usuários
|
||||
* Remover o bot de grupos
|
||||
* Restringir o acesso ao serviço
|
||||
|
||||
Com exceção de instâncias auto-hospedadas.
|
||||
|
||||
---
|
||||
|
||||
### 8. Tratamento de dados e privacidade
|
||||
|
||||
* Mensagens e arquivos de mídia podem ser processados temporariamente
|
||||
* O conteúdo não é armazenado permanentemente pelo sistema principal
|
||||
* Os registros podem incluir metadados e/ou conteúdo para fins operacionais
|
||||
* Alguns plug-ins ou modificações de terceiros podem armazenar dados de forma independente
|
||||
|
||||
O desenvolvedor não se responsabiliza pelo tratamento de dados realizado por plug-ins de terceiros ou versões modificadas.
|
||||
|
||||
---
|
||||
|
||||
### 9. Serviços de terceiros
|
||||
|
||||
Este software depende de plataformas e bibliotecas de terceiros (por exemplo, ferramentas de automação do WhatsApp Web).
|
||||
|
||||
O uso deste software pode violar os termos de serviço dessas plataformas.
|
||||
|
||||
O desenvolvedor não se responsabiliza por:
|
||||
|
||||
* Banimentos de conta
|
||||
* Restrições de serviço
|
||||
* Quaisquer consequências impostas por plataformas de terceiros
|
||||
|
||||
---
|
||||
|
||||
### 10. Plug-ins e modificações
|
||||
|
||||
Este software suporta plug-ins e permite forks do código-fonte.
|
||||
|
||||
Esses plug-ins podem introduzir recursos, incluindo, mas não se limitando a, geração de mídia ou figurinhas.
|
||||
|
||||
O desenvolvedor:
|
||||
|
||||
* Analisa o código-fonte de todos os plug-ins no repositório oficial
|
||||
* Não analisa bifurcações, versões de terceiros e repositórios
|
||||
* Aprova apenas plug-ins que concordem com os termos
|
||||
|
||||
O uso de plug-ins e forks de terceiros é de sua responsabilidade.
|
||||
|
||||
---
|
||||
|
||||
### 11. Uso comercial
|
||||
|
||||
Este software é fornecido como freeware (software gratuito).
|
||||
|
||||
O uso comercial não é permitido, a menos que o software seja significativamente modificado e renomeado.
|
||||
|
||||
---
|
||||
|
||||
### 12. Marca e Identidade
|
||||
|
||||
O nome do projeto, a identidade e os elementos associados são de uso exclusivo.
|
||||
|
||||
É proibido o uso não autorizado da identidade, da marca ou da representação do projeto como uma instância oficial.
|
||||
|
||||
---
|
||||
|
||||
### 13. Disponibilidade do Serviço oficial
|
||||
|
||||
O serviço hospedado oficial pode:
|
||||
|
||||
* Estar indisponível a qualquer momento
|
||||
* Conter bugs ou erros
|
||||
* Ser modificado ou descontinuado sem aviso prévio
|
||||
|
||||
Não são fornecidas garantias de tempo de atividade ou confiabilidade.
|
||||
|
||||
Alertas de alterações e manutenção são enviados para os chats onde o bot está instalado.
|
||||
|
||||
---
|
||||
|
||||
### 14. Cessação do Acesso
|
||||
|
||||
O desenvolvedor reserva-se o direito de:
|
||||
|
||||
* Bloquear usuários
|
||||
* Negar acesso
|
||||
* Desativar o serviço a qualquer momento
|
||||
|
||||
---
|
||||
|
||||
### 15. Ausência de Garantia
|
||||
|
||||
Este software é fornecido “no estado em que se encontra”, sem qualquer tipo de garantia.
|
||||
|
||||
Não são oferecidas garantias quanto a:
|
||||
|
||||
* Funcionalidade
|
||||
* Disponibilidade
|
||||
* Segurança
|
||||
* Adequação a qualquer finalidade
|
||||
|
||||
---
|
||||
|
||||
### 16. Aceitação dos Termos
|
||||
|
||||
Ao utilizar este software ou qualquer instância oficial, você concorda com estes Termos de Uso.
|
||||
|
||||
Caso não concorde, você não deve utilizar o software.
|
||||
|
||||
---
|
||||
|
||||
### 17. Alterações aos Termos
|
||||
|
||||
Estes termos podem ser atualizados a qualquer momento sem aviso prévio.
|
||||
|
||||
O uso continuado do software implica a aceitação dos termos atualizados, mesmo em versões antigas.
|
||||
|
||||
---
|
||||
|
||||
### 18. Jurisdição
|
||||
|
||||
Estes termos serão regidos e interpretados de acordo com as leis do Brasil.
|
||||
|
||||
Quaisquer disputas decorrentes do uso deste software estarão sujeitas à jurisdição aplicável no Brasil.
|
||||
|
||||
218
TERMS_en-us.md
218
TERMS_en-us.md
@@ -1,218 +0,0 @@
|
||||
## Terms of Use
|
||||
|
||||
> This is file may be moved in the future as the official webpage is launched.
|
||||
|
||||
### 1. Nature of the Software
|
||||
|
||||
This project is an open-source automation tool for messaging platforms.
|
||||
|
||||
By default, the core system does not generate media or content. However, it may support plugins, extensions, or modifications that add such functionality (including sticker or media generation).
|
||||
|
||||
It may be used:
|
||||
|
||||
* As a self-hosted (local) instance
|
||||
* Through an official hosted instance operated by the developer
|
||||
|
||||
The developer provides this software strictly as a technical tool and does not create, control, or endorse user-generated content.
|
||||
|
||||
---
|
||||
|
||||
### 2. Scope of Responsibility
|
||||
|
||||
#### 2.1 Official Hosted Instance
|
||||
|
||||
When using the official hosted bot:
|
||||
|
||||
* Messages and media may pass through the developer's infrastructure
|
||||
* Content is processed automatically and is not actively monitored
|
||||
* Data is processed and then permanently discarded
|
||||
|
||||
The developer does not assume responsibility for any content processed by the system.
|
||||
|
||||
#### 2.2 Self-Hosted / Modified Versions
|
||||
|
||||
If you run, modify, or distribute your own version:
|
||||
|
||||
* You are fully responsible for your instance
|
||||
* The developer has no control over your deployment
|
||||
* Any plugins, modifications, or extensions are your responsibility
|
||||
|
||||
---
|
||||
|
||||
### 3. User Responsibility
|
||||
|
||||
By using this software, you agree that:
|
||||
|
||||
* You are solely responsible for all content you generate, process, or share
|
||||
* You will comply with all applicable laws and regulations
|
||||
* You will not use this software for illegal, harmful, or abusive purposes
|
||||
|
||||
---
|
||||
|
||||
### 4. Prohibited Use
|
||||
|
||||
The following uses are strictly prohibited:
|
||||
|
||||
* Illegal activities of any kind
|
||||
* Content that violates applicable laws
|
||||
* Abusive, harmful, or exploitative content
|
||||
* Misuse of the platform to harm others
|
||||
|
||||
The developer does not tolerate such use, even if it cannot be fully prevented.
|
||||
|
||||
---
|
||||
|
||||
### 5. No Liability
|
||||
|
||||
The developer shall not be held liable for:
|
||||
|
||||
* Any content generated by users
|
||||
* Any misuse of the software
|
||||
* Any damages or legal consequences resulting from usage
|
||||
|
||||
This includes, but is not limited to:
|
||||
|
||||
* Offensive or inappropriate content
|
||||
* Illegal content created or distributed by users
|
||||
|
||||
---
|
||||
|
||||
### 6. No Active Monitoring
|
||||
|
||||
The system operates automatically and is not actively monitored at all times.
|
||||
|
||||
The developer is not required to review all activity but may take action when misuse is reported or detected.
|
||||
|
||||
Response time is not guaranteed.
|
||||
|
||||
---
|
||||
|
||||
### 7. Reporting and Enforcement
|
||||
|
||||
Users may report misuse through the provided contact channel (e.g., email).
|
||||
|
||||
Upon receiving valid reports, the developer may, at their sole discretion:
|
||||
|
||||
* Block users
|
||||
* Remove the bot from groups
|
||||
* Restrict access to the service
|
||||
|
||||
Except for self-hosted instances.
|
||||
|
||||
---
|
||||
|
||||
### 8. Data Handling and Privacy
|
||||
|
||||
* Messages and media may be temporarily processed
|
||||
* Content is not permanently stored by the core system
|
||||
* Logs may include metadata and/or content for operational purposes
|
||||
* Some plugins or third-party modifications may store data independently
|
||||
|
||||
The developer is not responsible for data handling performed by third-party plugins or modified versions.
|
||||
|
||||
---
|
||||
|
||||
### 9. Third-Party Services
|
||||
|
||||
This software depends on third-party platforms and libraries (e.g., WhatsApp Web automation tools).
|
||||
|
||||
Use of this software may violate the terms of service of such platforms.
|
||||
|
||||
The developer is not responsible for:
|
||||
|
||||
* Account bans
|
||||
* Service restrictions
|
||||
* Any consequences imposed by third-party platforms
|
||||
|
||||
---
|
||||
|
||||
### 10. Plugins and Modifications
|
||||
|
||||
This software supports plugins and allows forks fo the source code.
|
||||
|
||||
Such plugins may introduce features including, but not limited to, media or sticker generation.
|
||||
|
||||
The developer:
|
||||
|
||||
* Reviews the source code of all plugins on the official repository
|
||||
* Does not reviews forks and third-party versions and repositories
|
||||
* Approves only plugins who agree with the terms
|
||||
|
||||
The use of plugins and third-party forks is your responsibility.
|
||||
|
||||
---
|
||||
|
||||
### 11. Commercial Use
|
||||
|
||||
This software is provided as freeware.
|
||||
|
||||
Commercial use is not permitted unless the software is significantly modified and rebranded.
|
||||
|
||||
---
|
||||
|
||||
### 12. Branding and Identity
|
||||
|
||||
The project name, identity, and associated elements are reserved.
|
||||
|
||||
Unauthorized use of the project's identity, branding, or representation as an official instance is prohibited.
|
||||
|
||||
---
|
||||
|
||||
### 13. Service Availability
|
||||
|
||||
The official hosted service may:
|
||||
|
||||
* Be unavailable at any time
|
||||
* Contain bugs or errors
|
||||
* Be modified or discontinued
|
||||
|
||||
No uptime or reliability guarantees are provided.
|
||||
|
||||
Change and maintenance alerts are sent to the chats where the bot is installed.
|
||||
|
||||
---
|
||||
|
||||
### 14. Termination of Access
|
||||
|
||||
The developer reserves the right to:
|
||||
|
||||
* Block users
|
||||
* Deny access
|
||||
* Shut down the service at any time
|
||||
|
||||
---
|
||||
|
||||
### 15. No Warranty
|
||||
|
||||
This software is provided "as is", without warranty of any kind.
|
||||
|
||||
No guarantees are made regarding:
|
||||
|
||||
* Functionality
|
||||
* Availability
|
||||
* Security
|
||||
* Fitness for any purpose
|
||||
|
||||
---
|
||||
|
||||
### 16. Acceptance of Terms
|
||||
|
||||
By using this software or any official instance, you agree to these Terms of Use.
|
||||
|
||||
If you do not agree, you must not use the software.
|
||||
|
||||
---
|
||||
|
||||
### 17. Changes to Terms
|
||||
|
||||
These terms may be updated at any time without prior notice.
|
||||
|
||||
Continued use of the software implies acceptance of the updated terms, even in old versions.
|
||||
|
||||
---
|
||||
|
||||
### 18. Jurisdiction
|
||||
|
||||
These terms shall be governed by and interpreted in accordance with the laws of Brazil.
|
||||
|
||||
Any disputes arising from the use of this software shall be subject to the applicable jurisdiction within Brazil.
|
||||
368
deploy.sh
368
deploy.sh
@@ -1,348 +1,44 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
# =============================================================================
|
||||
# deploy — script de release com gates de qualidade e versionamento semântico
|
||||
#
|
||||
# Uso:
|
||||
# ./deploy → commit automático + push (branch atual)
|
||||
# ./deploy --release patch → bump patch + tag + push para master
|
||||
# ./deploy --release minor → bump minor + tag + push para master
|
||||
# ./deploy --release major → bump major + tag + push para master
|
||||
# ./deploy --dry-run → simula tudo sem executar nada
|
||||
# ./deploy --help → mostra esta ajuda
|
||||
#
|
||||
# Conventional Commits esperados:
|
||||
# feat: nova funcionalidade → candidate a minor bump
|
||||
# fix: correção de bug → candidate a patch bump
|
||||
# docs: só documentação → sem bump de versão
|
||||
# chore: manutenção → sem bump de versão
|
||||
# BREAKING CHANGE: no rodapé → candidate a major bump
|
||||
# =============================================================================
|
||||
# development tool
|
||||
# ./deploy <commit> <branch> <version (if branch=master)>
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Cores e utilitários
|
||||
# ──────────────────────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
COMMIT_MSG="$1"
|
||||
BRANCH="$2"
|
||||
VERSION="$3"
|
||||
|
||||
info() { echo -e "${BLUE}▸${RESET} $*"; }
|
||||
success() { echo -e "${GREEN}✔${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}⚠${RESET} $*"; }
|
||||
error() { echo -e "${RED}✖${RESET} $*" >&2; }
|
||||
die() { error "$*"; exit 1; }
|
||||
step() { echo -e "\n${BOLD}$*${RESET}"; }
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Argumentos
|
||||
# ──────────────────────────────────────────
|
||||
RELEASE_TYPE=""
|
||||
DRY_RUN=false
|
||||
|
||||
show_help() {
|
||||
sed -n '/^# Uso:/,/^# =/p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--release)
|
||||
[[ -z "${2-}" ]] && die "--release requer: patch | minor | major"
|
||||
RELEASE_TYPE="$2"
|
||||
[[ "$RELEASE_TYPE" =~ ^(patch|minor|major)$ ]] || die "Tipo inválido: '$RELEASE_TYPE'. Use patch, minor ou major."
|
||||
shift 2
|
||||
;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--help|-h) show_help ;;
|
||||
*) die "Argumento desconhecido: '$1'. Use --help." ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Wrapper para dry-run
|
||||
# ──────────────────────────────────────────
|
||||
run() {
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${YELLOW}[dry-run]${RESET} $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# GATE 1 — Ambiente Git
|
||||
# ──────────────────────────────────────────
|
||||
step "[ 1/6 ] Verificando ambiente Git..."
|
||||
|
||||
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
||||
|| die "Não é um repositório Git."
|
||||
|
||||
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) \
|
||||
|| die "Não foi possível detectar a branch atual (HEAD detached?)."
|
||||
|
||||
# Releases só saem da master/main
|
||||
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||
[[ "$BRANCH" =~ ^(master|main)$ ]] \
|
||||
|| die "--release só pode ser executado na branch master/main (atual: $BRANCH)."
|
||||
if [ -z "$COMMIT_MSG" ] || [ -z "$BRANCH" ]; then
|
||||
echo "Uso: ./deploy <commit> <branch> [version if branch=master]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Branch: ${BOLD}$BRANCH${RESET}"
|
||||
echo "Rewriting config.js"
|
||||
cat > "src/config.js" << 'EOF'
|
||||
export const CLIENT_ID = "bot_permanente";
|
||||
export const BOT_PREFIX = "🤖 *ManyBot:* ";
|
||||
export const CHATS = [
|
||||
// coloque os chats que quer aqui
|
||||
];
|
||||
EOF
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# GATE 2 — Working tree limpa
|
||||
# ──────────────────────────────────────────
|
||||
function worktree() {
|
||||
# Em modo release, working tree tem de estar absolutamente limpa
|
||||
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||
warn "Arquivos não commitados:"
|
||||
[[ -n "$STAGED" ]] && echo -e " ${GREEN}Staged:${RESET}\n$(echo "$STAGED" | sed 's/^/ /')"
|
||||
[[ -n "$MODIFIED" ]] && echo -e " ${YELLOW}Modificados:${RESET}\n$(echo "$MODIFIED" | sed 's/^/ /')"
|
||||
[[ -n "$UNTRACKED" ]] && echo -e " ${RED}Não rastreados:${RESET}\n$(echo "$UNTRACKED" | sed 's/^/ /')"
|
||||
die "Working tree suja. Faça commit ou stash antes de um release."
|
||||
fi
|
||||
# mudar para a branch
|
||||
git checkout $BRANCH || { echo "Error ao change to $BRANCH"; exit 1; }
|
||||
|
||||
# ── Seletor interativo de arquivos ──────────────────────────────────────
|
||||
# Monta lista indexada: staged (S), modificados (M), não rastreados (?)
|
||||
declare -a ALL_FILES
|
||||
declare -a ALL_LABELS
|
||||
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${GREEN}staged${RESET}"); done <<< "$STAGED"
|
||||
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${YELLOW}modificado${RESET}"); done <<< "$MODIFIED"
|
||||
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${RED}novo${RESET}"); done <<< "$UNTRACKED"
|
||||
# adicionar alterações e commit
|
||||
git add .
|
||||
git commit -m "$COMMIT_MSG"
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Mudanças detectadas:${RESET}"
|
||||
for i in "${!ALL_FILES[@]}"; do
|
||||
printf " %2d) %b %s\n" "$((i+1))" "${ALL_LABELS[$i]}" "${ALL_FILES[$i]}"
|
||||
done
|
||||
echo ""
|
||||
# push
|
||||
git push origin $BRANCH
|
||||
|
||||
declare -a SELECTED_FILES
|
||||
|
||||
echo -e "Digite os números para adicionar ao commit, ${BOLD}all${RESET} para todos, ou ${BOLD}done${RESET} para encerrar:"
|
||||
while true; do
|
||||
echo -ne "${BLUE}▸${RESET} "
|
||||
read -r INPUT
|
||||
|
||||
case "$INPUT" in
|
||||
done|"")
|
||||
[[ ${#SELECTED_FILES[@]} -eq 0 ]] && die "Nenhum arquivo selecionado. Operação cancelada."
|
||||
break
|
||||
;;
|
||||
all)
|
||||
SELECTED_FILES=("${ALL_FILES[@]}")
|
||||
echo -e " ${GREEN}✔${RESET} Todos os arquivos adicionados."
|
||||
break
|
||||
;;
|
||||
*)
|
||||
# Aceita múltiplos números na mesma linha (ex: "1 3 5")
|
||||
for TOKEN in $INPUT; do
|
||||
if [[ "$TOKEN" =~ ^[0-9]+$ ]] && (( TOKEN >= 1 && TOKEN <= ${#ALL_FILES[@]} )); then
|
||||
FILE="${ALL_FILES[$((TOKEN-1))]}"
|
||||
# Evita duplicatas
|
||||
if printf '%s\n' "${SELECTED_FILES[@]+"${SELECTED_FILES[@]}"}" | grep -qxF "$FILE"; then
|
||||
warn "$FILE já está na lista."
|
||||
else
|
||||
SELECTED_FILES+=("$FILE")
|
||||
echo -e " ${GREEN}✔${RESET} adicionado: ${BOLD}$FILE${RESET}"
|
||||
fi
|
||||
else
|
||||
warn "Entrada inválida: '$TOKEN' — use um número entre 1 e ${#ALL_FILES[@]}, 'all' ou 'done'."
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
info "Arquivos no commit:"
|
||||
for f in "${SELECTED_FILES[@]}"; do echo " $f"; done
|
||||
echo ""
|
||||
|
||||
# ── Mensagem de commit ──────────────────────────────────────────────────
|
||||
echo "Tipos de commit (Conventional Commits):"
|
||||
echo " feat → nova funcionalidade"
|
||||
echo " fix → correção de bug"
|
||||
echo " docs → só documentação"
|
||||
echo " refactor → refatoração sem mudança de comportamento"
|
||||
echo " test → adição/correção de testes"
|
||||
echo " chore → manutenção, dependências, CI"
|
||||
echo " style → formatação, whitespace"
|
||||
echo ""
|
||||
echo -n "Tipo: "
|
||||
read -r COMMIT_TYPE
|
||||
[[ "$COMMIT_TYPE" =~ ^(feat|fix|docs|refactor|test|chore|style)$ ]] \
|
||||
|| die "Tipo inválido. Use um dos listados acima."
|
||||
|
||||
echo -n "Escopo (opcional, ex: auth, api, ui — Enter para pular): "
|
||||
read -r COMMIT_SCOPE
|
||||
|
||||
echo -n "Descrição curta: "
|
||||
read -r COMMIT_DESC
|
||||
[[ -z "$COMMIT_DESC" ]] && die "Descrição não pode ser vazia."
|
||||
|
||||
echo -n "Há breaking changes? [s/N]: "
|
||||
read -r BREAKING
|
||||
BREAKING_FOOTER=""
|
||||
if [[ "$BREAKING" =~ ^[sS]$ ]]; then
|
||||
echo -n "Descreva o breaking change: "
|
||||
read -r BREAKING_MSG
|
||||
BREAKING_FOOTER=$'\n\nBREAKING CHANGE: '"$BREAKING_MSG"
|
||||
fi
|
||||
|
||||
if [[ -n "$COMMIT_SCOPE" ]]; then
|
||||
COMMIT_MSG="${COMMIT_TYPE}(${COMMIT_SCOPE}): ${COMMIT_DESC}${BREAKING_FOOTER}"
|
||||
else
|
||||
COMMIT_MSG="${COMMIT_TYPE}: ${COMMIT_DESC}${BREAKING_FOOTER}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
info "Mensagem: ${BOLD}${COMMIT_MSG}${RESET}"
|
||||
|
||||
for f in "${SELECTED_FILES[@]}"; do run git add -- "$f"; done
|
||||
run git commit -m "$COMMIT_MSG"
|
||||
success "Commit criado."
|
||||
}
|
||||
|
||||
step "[ 2/6 ] Verificando working tree..."
|
||||
|
||||
function thereschanges(){
|
||||
UNTRACKED=$(git ls-files --others --exclude-standard)
|
||||
MODIFIED=$(git diff --name-only)
|
||||
STAGED=$(git diff --cached --name-only)
|
||||
if [[ -n "$UNTRACKED" || -n "$MODIFIED" || -n "$STAGED" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if thereschanges; then
|
||||
worktree
|
||||
if thereschanges; then
|
||||
echo -n "Deseja criar outro commit? [s/N]: "
|
||||
read -r OTHERCOMMIT
|
||||
[[ "$OTHERCOMMIT" =~ ^[sS]$ ]] && worktree
|
||||
fi
|
||||
else
|
||||
success "Working tree limpa."
|
||||
# se for master, atualizar versão
|
||||
if [ "$BRANCH" == "master" ] && [ -n "$VERSION" ]; then
|
||||
echo "Updating version to $VERSION"
|
||||
git tag $VERSION
|
||||
npm version $VERSION --no-git-tag-version
|
||||
git add package.json
|
||||
git commit -m "Bump version to $VERSION"
|
||||
git push origin $VERSION
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# GATE 3 — Testes e qualidade
|
||||
# ──────────────────────────────────────────
|
||||
step "[ 3/6 ] Executando gates de qualidade..."
|
||||
|
||||
run_if_exists() {
|
||||
local label="$1"
|
||||
local cmd="$2"
|
||||
if eval "$cmd" >/dev/null 2>&1; then
|
||||
info "$label encontrado. Executando..."
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${YELLOW}[dry-run]${RESET} $cmd"
|
||||
else
|
||||
eval "$cmd" || die "$label falhou. Corrija antes de continuar."
|
||||
fi
|
||||
success "$label passou."
|
||||
else
|
||||
warn "$label não encontrado — pulando."
|
||||
fi
|
||||
}
|
||||
|
||||
# Lint
|
||||
if [[ -f "package.json" ]]; then
|
||||
if node -e "require('./package.json').scripts?.lint" >/dev/null 2>&1; then
|
||||
run_if_exists "Lint (npm)" "npm run lint --if-present"
|
||||
fi
|
||||
fi
|
||||
[[ -f ".flake8" || -f "setup.cfg" || -f "pyproject.toml" ]] && \
|
||||
run_if_exists "Lint (flake8)" "command -v flake8 && flake8 ."
|
||||
|
||||
# Testes
|
||||
run_if_exists "Testes (npm)" "node -e \"require('./package.json').scripts?.test\" && npm test --if-present"
|
||||
|
||||
# Build (só bloqueia em release)
|
||||
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||
run_if_exists "Build (npm)" "node -e \"require('./package.json').scripts?.build\" && npm run build"
|
||||
fi
|
||||
|
||||
success "Gates de qualidade concluídos."
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# GATE 4 — Sincronia com remoto
|
||||
# ──────────────────────────────────────────
|
||||
step "[ 4/6 ] Verificando sincronia com remoto..."
|
||||
|
||||
if git remote get-url origin >/dev/null 2>&1; then
|
||||
run git fetch origin --quiet
|
||||
|
||||
LOCAL=$(git rev-parse HEAD)
|
||||
REMOTE=$(git rev-parse "origin/$BRANCH" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$REMOTE" && "$LOCAL" != "$REMOTE" ]]; then
|
||||
BEHIND=$(git rev-list --count HEAD.."origin/$BRANCH")
|
||||
AHEAD=$(git rev-list --count "origin/$BRANCH"..HEAD)
|
||||
|
||||
[[ "$BEHIND" -gt 0 ]] && die "Branch está $BEHIND commit(s) atrás do remoto. Faça pull antes."
|
||||
[[ "$AHEAD" -gt 0 ]] && info "Branch está $AHEAD commit(s) à frente do remoto — ok, será enviado."
|
||||
else
|
||||
success "Branch sincronizada com o remoto."
|
||||
fi
|
||||
else
|
||||
warn "Nenhum remoto 'origin' configurado — pulando verificação."
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# GATE 5 — Versionamento (somente --release)
|
||||
# ──────────────────────────────────────────
|
||||
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||
step "[ 5/6 ] Versionando release ($RELEASE_TYPE)..."
|
||||
|
||||
if [[ -f "package.json" ]]; then
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "desconhecida")
|
||||
info "Versão atual: ${BOLD}$CURRENT_VERSION${RESET}"
|
||||
run npm version "$RELEASE_TYPE" -m "chore(release): %s"
|
||||
NEW_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "nova")
|
||||
success "Nova versão: ${BOLD}$NEW_VERSION${RESET}"
|
||||
else
|
||||
# Fallback: tag Git manual
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
info "Última tag: $LAST_TAG"
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "${LAST_TAG#v}"
|
||||
case "$RELEASE_TYPE" in
|
||||
major) MAJOR=$((MAJOR+1)); MINOR=0; PATCH=0 ;;
|
||||
minor) MINOR=$((MINOR+1)); PATCH=0 ;;
|
||||
patch) PATCH=$((PATCH+1)) ;;
|
||||
esac
|
||||
NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
|
||||
run git tag -a "$NEW_TAG" -m "chore(release): $NEW_TAG"
|
||||
success "Tag criada: ${BOLD}$NEW_TAG${RESET}"
|
||||
fi
|
||||
else
|
||||
step "[ 5/6 ] Versionamento..."
|
||||
info "Modo push simples (sem --release). Nenhuma tag será criada."
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# GATE 6 — Push
|
||||
# ──────────────────────────────────────────
|
||||
step "[ 6/6 ] Enviando para o remoto..."
|
||||
|
||||
run git push origin "$BRANCH" --follow-tags
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Resumo final
|
||||
# ──────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${GREEN}${BOLD}═════════════════════════════════${RESET}"
|
||||
echo -e "${GREEN}${BOLD} Deploy concluído com sucesso!${RESET}"
|
||||
echo -e "${GREEN}${BOLD}═════════════════════════════════${RESET}"
|
||||
echo -e " Branch : ${BOLD}$BRANCH${RESET}"
|
||||
[[ -n "$RELEASE_TYPE" ]] && \
|
||||
echo -e " Release: ${BOLD}$RELEASE_TYPE${RESET} → ${BOLD}${NEW_VERSION:-$NEW_TAG}${RESET}"
|
||||
$DRY_RUN && echo -e "\n ${YELLOW}(dry-run: nenhuma alteração foi feita de fato)${RESET}"
|
||||
echo ""
|
||||
echo "Deploy completed."
|
||||
@@ -1,15 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
manybot:
|
||||
build: .
|
||||
container_name: manybot
|
||||
volumes:
|
||||
- ./session:/app/session
|
||||
- ./manybot.conf:/app/manybot.conf:ro
|
||||
- ./src/plugins:/app/src/plugins
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
277
docs/API (en).md
277
docs/API (en).md
@@ -1,277 +0,0 @@
|
||||
# 🛠️ API Reference
|
||||
|
||||
Complete documentation of objects available in plugins.
|
||||
|
||||
---
|
||||
|
||||
## The `msg` Object
|
||||
|
||||
Contains information about the received message.
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `msg.body` | `string` | Full text of the message |
|
||||
| `msg.args` | `string[]` | Message tokens. E.g.: `"!video url"` → `["!video", "url"]` |
|
||||
| `msg.type` | `string` | Message type: `chat`, `image`, `video`, `audio`, `sticker`, `ptt` (voice), `document`, `location` |
|
||||
| `msg.sender` | `string` | Sender ID (format: `NUMBER@c.us` or `NUMBER@g.us`) |
|
||||
| `msg.senderName` | `string` | Display name of the sender |
|
||||
| `msg.fromMe` | `boolean` | `true` if the bot itself sent the message |
|
||||
| `msg.hasMedia` | `boolean` | `true` if the message contains media |
|
||||
| `msg.hasReply` | `boolean` | `true` if the message is a reply to another |
|
||||
| `msg.isGif` | `boolean` | `true` if the media is a GIF |
|
||||
| `msg.timestamp` | `number` | Unix timestamp of the message |
|
||||
|
||||
### Methods
|
||||
|
||||
#### `msg.is(cmd)`
|
||||
|
||||
Checks whether the message starts with the specified command.
|
||||
|
||||
```javascript
|
||||
if (msg.is(CMD_PREFIX + "video")) {
|
||||
// Execute command
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:** `boolean`
|
||||
|
||||
---
|
||||
|
||||
#### `msg.reply(text)`
|
||||
|
||||
Replies to the current message with a quote (citation).
|
||||
|
||||
```javascript
|
||||
await msg.reply("Reply with citation!");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `text` (string): Text of the reply
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `msg.downloadMedia()`
|
||||
|
||||
Downloads the media from the message.
|
||||
|
||||
```javascript
|
||||
const media = await msg.downloadMedia();
|
||||
// Returns: { mimetype: "image/jpeg", data: "base64string..." }
|
||||
```
|
||||
|
||||
**Returns:** `Promise<{ mimetype: string, data: string } | null>`
|
||||
|
||||
---
|
||||
|
||||
#### `msg.getReply()`
|
||||
|
||||
Returns the message that was quoted.
|
||||
|
||||
```javascript
|
||||
const quotedMsg = msg.getReply();
|
||||
if (quotedMsg) {
|
||||
console.log(quotedMsg.body);
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:** `msg object | null`
|
||||
|
||||
---
|
||||
|
||||
## The `api` Object
|
||||
|
||||
Contains methods for interacting with WhatsApp and other plugins.
|
||||
|
||||
### Properties
|
||||
|
||||
#### `api.chat`
|
||||
|
||||
Information about the current chat.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `api.chat.id` | `string` | Chat ID |
|
||||
| `api.chat.name` | `string` | Chat name |
|
||||
| `api.chat.isGroup` | `boolean` | `true` if it is a group |
|
||||
|
||||
---
|
||||
|
||||
### Send Methods
|
||||
|
||||
#### `api.send(text)`
|
||||
|
||||
Sends a text message.
|
||||
|
||||
```javascript
|
||||
await api.send("Message sent!");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `text` (string): Text to send
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendVideo(filePath)`
|
||||
|
||||
Sends a video from the file system.
|
||||
|
||||
```javascript
|
||||
await api.sendVideo("/path/to/video.mp4");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `filePath` (string): Path to the file
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendAudio(filePath)`
|
||||
|
||||
Sends audio as a voice message (PTT).
|
||||
|
||||
```javascript
|
||||
await api.sendAudio("/path/to/audio.mp3");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `filePath` (string): Path to the file
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendImage(filePath, caption?)`
|
||||
|
||||
Sends an image.
|
||||
|
||||
```javascript
|
||||
await api.sendImage("/path/to/image.jpg", "Optional caption");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `filePath` (string): Path to the file
|
||||
- `caption` (string, optional): Image caption
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendSticker(bufferOrPath)`
|
||||
|
||||
Sends a sticker. Accepts a `Buffer` or a file path.
|
||||
|
||||
```javascript
|
||||
// With Buffer
|
||||
const buffer = fs.readFileSync("image.png");
|
||||
await api.sendSticker(buffer);
|
||||
|
||||
// With path
|
||||
await api.sendSticker("/path/to/image.png");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `bufferOrPath` (`Buffer` | `string`): Image data or file path
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
### Plugin Methods
|
||||
|
||||
#### `api.getPlugin(name)`
|
||||
|
||||
Accesses the public API of another plugin.
|
||||
|
||||
```javascript
|
||||
const utils = api.getPlugin("utilities");
|
||||
const data = utils.formatDate(new Date());
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name` (string): Plugin name
|
||||
|
||||
**Returns:** `object | undefined`
|
||||
|
||||
---
|
||||
|
||||
### Log Methods
|
||||
|
||||
#### `api.log.info(...args)`
|
||||
|
||||
Informational log.
|
||||
|
||||
```javascript
|
||||
api.log.info("Message received:", msg.body);
|
||||
```
|
||||
|
||||
#### `api.log.warn(...args)`
|
||||
|
||||
Warning log.
|
||||
|
||||
```javascript
|
||||
api.log.warn("Missing config, using default");
|
||||
```
|
||||
|
||||
#### `api.log.error(...args)`
|
||||
|
||||
Error log.
|
||||
|
||||
```javascript
|
||||
api.log.error("Failed to process:", error);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Object
|
||||
|
||||
Import settings from `manybot.conf`:
|
||||
|
||||
```javascript
|
||||
import { CMD_PREFIX, CLIENT_ID, CHATS, PLUGINS } from "../../config.js";
|
||||
|
||||
// Custom configurations also work
|
||||
import { MY_PREFIX } from "../../config.js";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Full Example
|
||||
|
||||
```javascript
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import fs from "fs";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
// Ignore messages from the bot itself
|
||||
if (msg.fromMe) return;
|
||||
|
||||
// Command: !echo
|
||||
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||
|
||||
api.log.info("Echo command received from:", msg.senderName);
|
||||
|
||||
// If it has media, download and resend
|
||||
if (msg.hasMedia) {
|
||||
const media = await msg.downloadMedia();
|
||||
await api.sendSticker(media.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a reply, echo the quoted message
|
||||
if (msg.hasReply) {
|
||||
const quoted = msg.getReply();
|
||||
await msg.reply(`You quoted: "${quoted.body}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default response
|
||||
await msg.reply("Send media or reply to a message!");
|
||||
}
|
||||
```
|
||||
277
docs/API.md
277
docs/API.md
@@ -1,277 +0,0 @@
|
||||
# 🛠️ Referência da API
|
||||
|
||||
Documentação completa dos objetos disponíveis nos plugins.
|
||||
|
||||
---
|
||||
|
||||
## Objeto `msg`
|
||||
|
||||
Contém informações da mensagem recebida.
|
||||
|
||||
### Propriedades
|
||||
|
||||
| Propriedade | Tipo | Descrição |
|
||||
|-------------|------|-----------|
|
||||
| `msg.body` | `string` | Texto completo da mensagem |
|
||||
| `msg.args` | `string[]` | Tokens da mensagem. Ex: `"!video url"` → `["!video", "url"]` |
|
||||
| `msg.type` | `string` | Tipo da mensagem: `chat`, `image`, `video`, `audio`, `sticker`, `ptt` (voz), `document`, `location` |
|
||||
| `msg.sender` | `string` | ID do remetente (formato: `NUMERO@c.us` ou `NUMERO@g.us`) |
|
||||
| `msg.senderName` | `string` | Nome de exibição do remetente |
|
||||
| `msg.fromMe` | `boolean` | `true` se o próprio bot enviou a mensagem |
|
||||
| `msg.hasMedia` | `boolean` | `true` se a mensagem contém mídia |
|
||||
| `msg.hasReply` | `boolean` | `true` se é uma resposta a outra mensagem |
|
||||
| `msg.isGif` | `boolean` | `true` se a mídia é um GIF |
|
||||
| `msg.timestamp` | `number` | Timestamp Unix da mensagem |
|
||||
|
||||
### Métodos
|
||||
|
||||
#### `msg.is(cmd)`
|
||||
|
||||
Verifica se a mensagem começa com o comando especificado.
|
||||
|
||||
```javascript
|
||||
if (msg.is(CMD_PREFIX + "video")) {
|
||||
// Executa comando
|
||||
}
|
||||
```
|
||||
|
||||
**Retorno:** `boolean`
|
||||
|
||||
---
|
||||
|
||||
#### `msg.reply(text)`
|
||||
|
||||
Responde à mensagem atual com quote (citação).
|
||||
|
||||
```javascript
|
||||
await msg.reply("Resposta com citação!");
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `text` (string): Texto da resposta
|
||||
|
||||
**Retorno:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `msg.downloadMedia()`
|
||||
|
||||
Baixa a mídia da mensagem.
|
||||
|
||||
```javascript
|
||||
const media = await msg.downloadMedia();
|
||||
// Retorna: { mimetype: "image/jpeg", data: "base64string..." }
|
||||
```
|
||||
|
||||
**Retorno:** `Promise<{ mimetype: string, data: string } | null>`
|
||||
|
||||
---
|
||||
|
||||
#### `msg.getReply()`
|
||||
|
||||
Retorna a mensagem que foi citada.
|
||||
|
||||
```javascript
|
||||
const mensagemCitada = msg.getReply();
|
||||
if (mensagemCitada) {
|
||||
console.log(mensagemCitada.body);
|
||||
}
|
||||
```
|
||||
|
||||
**Retorno:** `msg object | null`
|
||||
|
||||
---
|
||||
|
||||
## Objeto `api`
|
||||
|
||||
Contém métodos para interagir com o WhatsApp e outros plugins.
|
||||
|
||||
### Propriedades
|
||||
|
||||
#### `api.chat`
|
||||
|
||||
Informações do chat atual.
|
||||
|
||||
| Propriedade | Tipo | Descrição |
|
||||
|-------------|------|-----------|
|
||||
| `api.chat.id` | `string` | ID do chat |
|
||||
| `api.chat.name` | `string` | Nome do chat |
|
||||
| `api.chat.isGroup` | `boolean` | `true` se é grupo |
|
||||
|
||||
---
|
||||
|
||||
### Métodos de Envio
|
||||
|
||||
#### `api.send(text)`
|
||||
|
||||
Envia uma mensagem de texto.
|
||||
|
||||
```javascript
|
||||
await api.send("Mensagem enviada!");
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `text` (string): Texto a enviar
|
||||
|
||||
**Retorno:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendVideo(filePath)`
|
||||
|
||||
Envia um vídeo do sistema de arquivos.
|
||||
|
||||
```javascript
|
||||
await api.sendVideo("/caminho/para/video.mp4");
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `filePath` (string): Caminho para o arquivo
|
||||
|
||||
**Retorno:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendAudio(filePath)`
|
||||
|
||||
Envia um áudio como mensagem de voz (PTT).
|
||||
|
||||
```javascript
|
||||
await api.sendAudio("/caminho/para/audio.mp3");
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `filePath` (string): Caminho para o arquivo
|
||||
|
||||
**Retorno:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendImage(filePath, caption?)`
|
||||
|
||||
Envia uma imagem.
|
||||
|
||||
```javascript
|
||||
await api.sendImage("/caminho/para/imagem.jpg", "Legenda opcional");
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `filePath` (string): Caminho para o arquivo
|
||||
- `caption` (string, opcional): Legenda da imagem
|
||||
|
||||
**Retorno:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
#### `api.sendSticker(bufferOuPath)`
|
||||
|
||||
Envia uma figurinha. Aceita `Buffer` ou caminho para arquivo.
|
||||
|
||||
```javascript
|
||||
// Com Buffer
|
||||
const buffer = fs.readFileSync("imagem.png");
|
||||
await api.sendSticker(buffer);
|
||||
|
||||
// Com caminho
|
||||
await api.sendSticker("/caminho/para/imagem.png");
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `bufferOuPath` (`Buffer` | `string`): Dados da imagem ou caminho
|
||||
|
||||
**Retorno:** `Promise<void>`
|
||||
|
||||
---
|
||||
|
||||
### Métodos de Plugin
|
||||
|
||||
#### `api.getPlugin(name)`
|
||||
|
||||
Acessa a API pública de outro plugin.
|
||||
|
||||
```javascript
|
||||
const utils = api.getPlugin("utilidades");
|
||||
const data = utils.formatarData(new Date());
|
||||
```
|
||||
|
||||
**Parâmetros:**
|
||||
- `name` (string): Nome do plugin
|
||||
|
||||
**Retorno:** `object | undefined`
|
||||
|
||||
---
|
||||
|
||||
### Métodos de Log
|
||||
|
||||
#### `api.log.info(...args)`
|
||||
|
||||
Log informativo.
|
||||
|
||||
```javascript
|
||||
api.log.info("Mensagem recebida:", msg.body);
|
||||
```
|
||||
|
||||
#### `api.log.warn(...args)`
|
||||
|
||||
Log de aviso.
|
||||
|
||||
```javascript
|
||||
api.log.warn("Configuração ausente, usando padrão");
|
||||
```
|
||||
|
||||
#### `api.log.error(...args)`
|
||||
|
||||
Log de erro.
|
||||
|
||||
```javascript
|
||||
api.log.error("Falha ao processar:", erro);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Objeto de Configuração
|
||||
|
||||
Importe configurações do `manybot.conf`:
|
||||
|
||||
```javascript
|
||||
import { CMD_PREFIX, CLIENT_ID, CHATS, PLUGINS } from "../../config.js";
|
||||
|
||||
// Configurações personalizadas também funcionam
|
||||
import { MEU_PREFIXO } from "../../config.js";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exemplo Completo
|
||||
|
||||
```javascript
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import fs from "fs";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
// Ignora mensagens do próprio bot
|
||||
if (msg.fromMe) return;
|
||||
|
||||
// Comando: !eco
|
||||
if (!msg.is(CMD_PREFIX + "eco")) return;
|
||||
|
||||
api.log.info("Comando eco recebido de:", msg.senderName);
|
||||
|
||||
// Se tem mídia, baixa e reenvia
|
||||
if (msg.hasMedia) {
|
||||
const media = await msg.downloadMedia();
|
||||
await api.sendSticker(media.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Se é resposta, ecoa a mensagem citada
|
||||
if (msg.hasReply) {
|
||||
const citada = msg.getReply();
|
||||
await msg.reply(`Você citou: "${citada.body}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Resposta padrão
|
||||
await msg.reply("Envie uma mídia ou responda uma mensagem!");
|
||||
}
|
||||
```
|
||||
@@ -1,146 +0,0 @@
|
||||
# ⚙️ Configuração
|
||||
|
||||
Guia completo do arquivo `manybot.conf`.
|
||||
|
||||
---
|
||||
|
||||
## Estrutura Básica
|
||||
|
||||
```bash
|
||||
# Comentários começam com '#'
|
||||
|
||||
CLIENT_ID=bot_permanente
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=pt
|
||||
CHATS=[]
|
||||
PLUGINS=[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opções
|
||||
|
||||
### CLIENT_ID
|
||||
|
||||
Identificador único da sessão do bot.
|
||||
|
||||
```bash
|
||||
CLIENT_ID=bot_permanente
|
||||
```
|
||||
|
||||
- **Padrão:** `bot_permanente`
|
||||
- **Uso:** Cria uma pasta `session/` com esse nome para armazenar dados de autenticação
|
||||
|
||||
### CMD_PREFIX
|
||||
|
||||
Caractere que indica o início de um comando.
|
||||
|
||||
```bash
|
||||
CMD_PREFIX=!
|
||||
```
|
||||
|
||||
- **Padrão:** `!`
|
||||
- **Exemplo:** Com prefixo `!`, o comando é `!figurinha`. Com `.`, seria `.figurinha`.
|
||||
|
||||
### LANGUAGE
|
||||
|
||||
Idioma das mensagens do bot.
|
||||
|
||||
```bash
|
||||
LANGUAGE=pt
|
||||
```
|
||||
|
||||
- **Padrão:** `en` (inglês)
|
||||
- **Opções:** `en` (inglês), `pt` (português), `es` (espanhol)
|
||||
- **Nota:** Se o idioma selecionado não existir, o bot usará inglês como fallback
|
||||
|
||||
### CHATS
|
||||
|
||||
Lista de IDs de chats onde o bot responderá.
|
||||
|
||||
```bash
|
||||
CHATS=[
|
||||
123456789@c.us, # Chat privado
|
||||
123456789@g.us # Grupo
|
||||
]
|
||||
```
|
||||
|
||||
- **Padrão:** `[]` (vazio = responde em todos)
|
||||
- **Formato:**
|
||||
- Privado: `NUMERO@c.us`
|
||||
- Grupo: `NUMERO@g.us`
|
||||
|
||||
#### Como descobrir o ID
|
||||
|
||||
```bash
|
||||
node src/utils/get_id.js
|
||||
```
|
||||
|
||||
Escaneie o QR Code e mande uma mensagem no chat. O ID aparecerá no terminal.
|
||||
|
||||
> Nota: O utilitário usa um `CLIENT_ID` separado para não conflitar com a sessão principal.
|
||||
|
||||
### PLUGINS
|
||||
|
||||
Lista de plugins a serem carregados.
|
||||
|
||||
```bash
|
||||
PLUGINS=[
|
||||
video,
|
||||
audio,
|
||||
figurinha,
|
||||
adivinhacao,
|
||||
forca,
|
||||
many,
|
||||
obrigado
|
||||
]
|
||||
```
|
||||
|
||||
- **Padrão:** `[]` (nenhum)
|
||||
- Cada nome corresponde a uma pasta em `src/plugins/`
|
||||
- Remova ou comente para desativar sem apagar
|
||||
|
||||
---
|
||||
|
||||
## Configurações Personalizadas
|
||||
|
||||
Você pode adicionar suas próprias variáveis para plugins:
|
||||
|
||||
```bash
|
||||
# manybot.conf
|
||||
MEU_PREFIXO=>
|
||||
API_KEY=minha_chave
|
||||
```
|
||||
|
||||
E acessar no código do plugin:
|
||||
|
||||
```javascript
|
||||
import { MEU_PREFIXO, API_KEY } from "../../config.js";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exemplo Completo
|
||||
|
||||
```bash
|
||||
# ManyBot Configuration
|
||||
|
||||
CLIENT_ID=meu_bot_prod
|
||||
CMD_PREFIX=/
|
||||
LANGUAGE=pt
|
||||
|
||||
CHATS=[
|
||||
5511999999999@c.us,
|
||||
5511888888888-123456789@g.us
|
||||
]
|
||||
|
||||
PLUGINS=[
|
||||
figurinha,
|
||||
video,
|
||||
audio,
|
||||
many
|
||||
]
|
||||
|
||||
# Configurações extras
|
||||
ADMIN_NUMBER=5511999999999@c.us
|
||||
```
|
||||
@@ -1,146 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
Complete guide for the `manybot.conf` file.
|
||||
|
||||
---
|
||||
|
||||
## Basic Structure
|
||||
|
||||
```bash
|
||||
# Comments start with '#'
|
||||
|
||||
CLIENT_ID=bot_permanente
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=en
|
||||
CHATS=[]
|
||||
PLUGINS=[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Options
|
||||
|
||||
### CLIENT_ID
|
||||
|
||||
Unique identifier for the bot session.
|
||||
|
||||
```bash
|
||||
CLIENT_ID=my_bot
|
||||
```
|
||||
|
||||
- **Default:** `bot_permanente`
|
||||
- **Usage:** Creates a `session/` folder with this name to store authentication data
|
||||
|
||||
### CMD_PREFIX
|
||||
|
||||
Character that indicates the start of a command.
|
||||
|
||||
```bash
|
||||
CMD_PREFIX=!
|
||||
```
|
||||
|
||||
- **Default:** `!`
|
||||
- **Example:** With prefix `!`, the command is `!sticker`. With `.`, it would be `.sticker`.
|
||||
|
||||
### LANGUAGE
|
||||
|
||||
Bot message language.
|
||||
|
||||
```bash
|
||||
LANGUAGE=en
|
||||
```
|
||||
|
||||
- **Default:** `en` (English)
|
||||
- **Options:** `en` (English), `pt` (Portuguese), `es` (Spanish)
|
||||
- **Note:** If the selected language doesn't exist, the bot will fall back to English
|
||||
|
||||
### CHATS
|
||||
|
||||
List of chat IDs where the bot will respond.
|
||||
|
||||
```bash
|
||||
CHATS=[
|
||||
123456789@c.us, # Private chat
|
||||
123456789@g.us # Group
|
||||
]
|
||||
```
|
||||
|
||||
- **Default:** `[]` (empty = responds to all)
|
||||
- **Format:**
|
||||
- Private: `NUMBER@c.us`
|
||||
- Group: `NUMBER@g.us`
|
||||
|
||||
#### How to discover the ID
|
||||
|
||||
```bash
|
||||
node src/utils/get_id.js
|
||||
```
|
||||
|
||||
Scan the QR Code and send a message in the chat. The ID will appear in the terminal.
|
||||
|
||||
> Note: The utility uses a separate `CLIENT_ID` to avoid conflicting with the main session.
|
||||
|
||||
### PLUGINS
|
||||
|
||||
List of plugins to be loaded.
|
||||
|
||||
```bash
|
||||
PLUGINS=[
|
||||
video,
|
||||
audio,
|
||||
sticker,
|
||||
guess,
|
||||
hangman,
|
||||
many,
|
||||
thanks
|
||||
]
|
||||
```
|
||||
|
||||
- **Default:** `[]` (none)
|
||||
- Each name corresponds to a folder in `src/plugins/`
|
||||
- Remove or comment to disable without deleting
|
||||
|
||||
---
|
||||
|
||||
## Custom Settings
|
||||
|
||||
You can add your own variables for plugins:
|
||||
|
||||
```bash
|
||||
# manybot.conf
|
||||
MY_PREFIX=>
|
||||
API_KEY=my_key
|
||||
```
|
||||
|
||||
And access in the plugin code:
|
||||
|
||||
```javascript
|
||||
import { MY_PREFIX, API_KEY } from "../../config.js";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Example
|
||||
|
||||
```bash
|
||||
# ManyBot Configuration
|
||||
|
||||
CLIENT_ID=my_bot_prod
|
||||
CMD_PREFIX=/
|
||||
LANGUAGE=en
|
||||
|
||||
CHATS=[
|
||||
5511999999999@c.us,
|
||||
5511888888888-123456789@g.us
|
||||
]
|
||||
|
||||
PLUGINS=[
|
||||
sticker,
|
||||
video,
|
||||
audio,
|
||||
many
|
||||
]
|
||||
|
||||
# Extra settings
|
||||
ADMIN_NUMBER=5511999999999@c.us
|
||||
```
|
||||
@@ -1,190 +0,0 @@
|
||||
# 📥 Instalação
|
||||
|
||||
Guia completo de instalação do ManyBot em diferentes plataformas.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Índice
|
||||
|
||||
- [Docker](#docker) (Recomendado)
|
||||
- [Linux](#linux)
|
||||
- [Windows](#windows)
|
||||
- [Termux (Android)](#termux-android)
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
A maneira mais fácil e recomendada de rodar o ManyBot.
|
||||
|
||||
### Pré-requisitos
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
### Instalação
|
||||
|
||||
```bash
|
||||
# 1. Clone o repositório
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
|
||||
# 2. Crie o arquivo de configuração
|
||||
cp manybot.conf.example manybot.conf
|
||||
nano manybot.conf
|
||||
|
||||
# 3. Inicie com Docker
|
||||
docker-compose up -d
|
||||
|
||||
# 4. Veja os logs para escanear o QR Code
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
**Escaneie o QR Code** que aparecerá nos logs.
|
||||
|
||||
### Comandos úteis
|
||||
|
||||
```bash
|
||||
# Ver logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Parar o bot
|
||||
docker-compose down
|
||||
|
||||
# Atualizar
|
||||
git pull
|
||||
docker-compose up --build -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Linux
|
||||
|
||||
### 1. Clone o repositório
|
||||
|
||||
```bash
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
```
|
||||
|
||||
### 2. Configure o bot
|
||||
|
||||
Crie o arquivo de configuração:
|
||||
|
||||
```bash
|
||||
touch manybot.conf
|
||||
nano manybot.conf
|
||||
```
|
||||
|
||||
Exemplo de configuração:
|
||||
|
||||
```bash
|
||||
# Comentários com '#'
|
||||
|
||||
CLIENT_ID=bot_permanente
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=pt
|
||||
CHATS=[
|
||||
123456789@c.us,
|
||||
123456789@g.us
|
||||
]
|
||||
PLUGINS=[
|
||||
video,
|
||||
audio,
|
||||
figurinha,
|
||||
adivinhacao
|
||||
]
|
||||
```
|
||||
|
||||
**Detalhes:**
|
||||
- `CLIENT_ID`: ID da sessão (padrão: `bot_permanente`)
|
||||
- `CMD_PREFIX`: Prefixo dos comandos (padrão: `!`)
|
||||
- `LANGUAGE`: Idioma do bot - `pt`, `en` ou `es` (padrão: `en`)
|
||||
- `CHATS`: IDs dos chats permitidos (deixe vazio para todos)
|
||||
- `PLUGINS`: Lista de plugins ativos
|
||||
|
||||
### 3. Execute a instalação
|
||||
|
||||
```bash
|
||||
bash ./setup
|
||||
```
|
||||
|
||||
### 4. Primeira execução
|
||||
|
||||
```bash
|
||||
node ./src/main.js
|
||||
```
|
||||
|
||||
Escaneie o QR Code no WhatsApp:
|
||||
|
||||
**Menu → Dispositivos conectados → Conectar um dispositivo**
|
||||
|
||||
---
|
||||
|
||||
## Windows
|
||||
|
||||
O ManyBot foi pensado para Linux, mas funciona no Windows via **Git Bash**.
|
||||
|
||||
### Pré-requisitos
|
||||
|
||||
1. **Git Bash**: https://git-scm.com/download/win
|
||||
2. **Node.js**: https://nodejs.org (escolha "Instalador Windows (.msi)")
|
||||
|
||||
### Instalação
|
||||
|
||||
Após instalar ambos, abra o **Git Bash** e siga os mesmos passos da [instalação Linux](#linux).
|
||||
|
||||
---
|
||||
|
||||
## Termux (Android)
|
||||
|
||||
> ⚠️ **Aviso:** Suporte experimental. Não há garantia de funcionamento.
|
||||
|
||||
```bash
|
||||
# Instale o Termux pela F-Droid (não use Play Store)
|
||||
# https://f-droid.org/packages/com.termux/
|
||||
|
||||
# Atualize pacotes
|
||||
pkg update && pkg upgrade
|
||||
|
||||
# Instale dependências
|
||||
pkg install nodejs git
|
||||
|
||||
# Clone e instale
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
```
|
||||
|
||||
Siga os passos de configuração Linux a partir do passo 2.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Resolução de Problemas
|
||||
|
||||
### Erro ao escanear QR Code
|
||||
|
||||
- Limpe os dados do Chrome/Chromium do Termux
|
||||
- Delete a pasta `session/` e tente novamente
|
||||
|
||||
### Bot não responde comandos
|
||||
|
||||
- Verifique o `CMD_PREFIX` no `manybot.conf`
|
||||
- Confira se o plugin está na lista `PLUGINS`
|
||||
|
||||
### Erros de instalação
|
||||
|
||||
```bash
|
||||
# Limpe a cache do npm
|
||||
npm cache clean --force
|
||||
|
||||
# Reinstale dependências
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Próximos Passos
|
||||
|
||||
- [Configuração avançada](./CONFIGURACAO.md)
|
||||
- [Criando plugins](./PLUGINS.md)
|
||||
@@ -1,190 +0,0 @@
|
||||
# Installation
|
||||
|
||||
Complete installation guide for ManyBot on different platforms.
|
||||
|
||||
---
|
||||
|
||||
## Index
|
||||
|
||||
- [Docker](#docker) (Recommended)
|
||||
- [Linux](#linux)
|
||||
- [Windows](#windows)
|
||||
- [Termux (Android)](#termux-android)
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
The easiest and recommended way to run ManyBot.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# 1. Clone the repository
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
|
||||
# 2. Create the configuration file
|
||||
cp manybot.conf.example manybot.conf
|
||||
nano manybot.conf
|
||||
|
||||
# 3. Start with Docker
|
||||
docker-compose up -d
|
||||
|
||||
# 4. Watch logs to scan QR Code
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
**Scan the QR Code** that appears in the logs.
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop the bot
|
||||
docker-compose down
|
||||
|
||||
# Update
|
||||
git pull
|
||||
docker-compose up --build -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Linux
|
||||
|
||||
### 1. Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
```
|
||||
|
||||
### 2. Configure the bot
|
||||
|
||||
Create the configuration file:
|
||||
|
||||
```bash
|
||||
touch manybot.conf
|
||||
nano manybot.conf
|
||||
```
|
||||
|
||||
Example configuration:
|
||||
|
||||
```bash
|
||||
# Comments with '#'
|
||||
|
||||
CLIENT_ID=bot_permanente
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=en
|
||||
CHATS=[
|
||||
123456789@c.us,
|
||||
123456789@g.us
|
||||
]
|
||||
PLUGINS=[
|
||||
video,
|
||||
audio,
|
||||
sticker,
|
||||
guess
|
||||
]
|
||||
```
|
||||
|
||||
**Details:**
|
||||
- `CLIENT_ID`: Session ID (default: `bot_permanente`)
|
||||
- `CMD_PREFIX`: Command prefix (default: `!`)
|
||||
- `LANGUAGE`: Bot language - `pt`, `en`, or `es` (default: `en`)
|
||||
- `CHATS`: Allowed chat IDs (leave empty for all)
|
||||
- `PLUGINS`: List of active plugins
|
||||
|
||||
### 3. Run installation
|
||||
|
||||
```bash
|
||||
bash ./setup
|
||||
```
|
||||
|
||||
### 4. First run
|
||||
|
||||
```bash
|
||||
node ./src/main.js
|
||||
```
|
||||
|
||||
Scan the QR Code in WhatsApp:
|
||||
|
||||
**Menu → Linked Devices → Link a Device**
|
||||
|
||||
---
|
||||
|
||||
## Windows
|
||||
|
||||
ManyBot was designed for Linux, but works on Windows via **Git Bash**.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Git Bash**: https://git-scm.com/download/win
|
||||
2. **Node.js**: https://nodejs.org (choose "Windows Installer (.msi)")
|
||||
|
||||
### Installation
|
||||
|
||||
After installing both, open **Git Bash** and follow the same steps as the [Linux installation](#linux).
|
||||
|
||||
---
|
||||
|
||||
## Termux (Android)
|
||||
|
||||
> ⚠️ **Warning:** Experimental support. No guarantee of functionality.
|
||||
|
||||
```bash
|
||||
# Install Termux from F-Droid (don't use Play Store)
|
||||
# https://f-droid.org/packages/com.termux/
|
||||
|
||||
# Update packages
|
||||
pkg update && pkg upgrade
|
||||
|
||||
# Install dependencies
|
||||
pkg install nodejs git
|
||||
|
||||
# Clone and install
|
||||
git clone https://git.maneos.net/synt-xerror/manybot
|
||||
cd manybot
|
||||
```
|
||||
|
||||
Follow the Linux configuration steps from step 2.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### QR Code scanning error
|
||||
|
||||
- Clear Chrome/Chromium data from Termux
|
||||
- Delete the `session/` folder and try again
|
||||
|
||||
### Bot not responding to commands
|
||||
|
||||
- Check `CMD_PREFIX` in `manybot.conf`
|
||||
- Make sure the plugin is in the `PLUGINS` list
|
||||
|
||||
### Installation errors
|
||||
|
||||
```bash
|
||||
# Clean npm cache
|
||||
npm cache clean --force
|
||||
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Advanced configuration](./CONFIGURATION.md)
|
||||
- [Creating plugins](./PLUGINS_EN.md)
|
||||
@@ -1,317 +0,0 @@
|
||||
# 🔌 Creating Plugins
|
||||
|
||||
Complete guide to creating plugins in ManyBot.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Index
|
||||
|
||||
- [Basic Structure](#basic-structure)
|
||||
- [Plugin Manifest](#plugin-manifest-manyplug-json)
|
||||
- [Creating Your First Plugin](#creating-your-first-plugin)
|
||||
- [Object API](#object-api)
|
||||
- [Exposing API](#exposing-api-to-other-plugins)
|
||||
- [Translating Your Plugin](#translating-your-plugin)
|
||||
- [Error Handling](#error-handling)
|
||||
|
||||
---
|
||||
|
||||
## Basic Structure
|
||||
|
||||
```
|
||||
src/plugins/
|
||||
└── my-plugin/
|
||||
├── index.js
|
||||
├── manyplug.json
|
||||
└── locale/ (optional)
|
||||
├── en.json
|
||||
├── pt.json
|
||||
└── es.json
|
||||
```
|
||||
|
||||
`index.js` must export a `default` function that receives `{ msg, api }`:
|
||||
|
||||
```javascript
|
||||
export default async function ({ msg, api }) {
|
||||
// Your logic here
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugin Manifest (manyplug.json)
|
||||
|
||||
Every plugin should have a `manyplug.json` at its root. It describes the plugin and declares any extra npm dependencies it needs.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-plugin",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `string` | Plugin identifier, must match the folder name |
|
||||
| `version` | `string` | Semantic version (e.g., `"1.0.0"`) |
|
||||
| `category` | `string` | Plugin category: `utility`, `media`, `game`, `humor`, `info` |
|
||||
| `service` | `boolean` | `true` if the plugin runs in the background (scheduler, listener). `false` if triggered by a command or event |
|
||||
| `dependencies` | `object` | Extra npm packages required by the plugin, same format as `package.json` |
|
||||
|
||||
### Example with dependencies
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "weather",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After adding dependencies, run `npm install` at the project root to install them.
|
||||
|
||||
---
|
||||
|
||||
## Creating Your First Plugin
|
||||
|
||||
### Example 1: Simple command
|
||||
|
||||
```javascript
|
||||
// plugins/greeting/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
// Only responds if the message starts with "!hi"
|
||||
if (!msg.is(CMD_PREFIX + "hi")) return;
|
||||
|
||||
await msg.reply("Hello! 👋");
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Command with arguments
|
||||
|
||||
```javascript
|
||||
// plugins/calculate/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "calculate")) return;
|
||||
|
||||
// msg.args = ["!calculate", "5", "+", "3"]
|
||||
const [, a, operator, b] = msg.args;
|
||||
|
||||
let result;
|
||||
switch (operator) {
|
||||
case "+": result = Number(a) + Number(b); break;
|
||||
case "-": result = Number(a) - Number(b); break;
|
||||
case "*": result = Number(a) * Number(b); break;
|
||||
case "/": result = Number(a) / Number(b); break;
|
||||
default: return msg.reply("Invalid operator!");
|
||||
}
|
||||
|
||||
await msg.reply(`Result: ${result}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Processing media
|
||||
|
||||
```javascript
|
||||
// plugins/echo-media/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||
|
||||
// Checks if the message has media
|
||||
if (!msg.hasMedia) {
|
||||
return msg.reply("Send a media file with the command!");
|
||||
}
|
||||
|
||||
// Downloads the media
|
||||
const media = await msg.downloadMedia();
|
||||
|
||||
// Resends in the chat
|
||||
await api.sendSticker(media.data);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Object API
|
||||
|
||||
### `msg` Object
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `msg.body` | `string` | Message text |
|
||||
| `msg.args` | `string[]` | Message tokens |
|
||||
| `msg.type` | `string` | Type: `chat`, `image`, `video`, `audio`, `sticker` |
|
||||
| `msg.sender` | `string` | Sender ID |
|
||||
| `msg.senderName` | `string` | Sender name |
|
||||
| `msg.fromMe` | `boolean` | Whether the bot sent it |
|
||||
| `msg.hasMedia` | `boolean` | Whether it has media |
|
||||
| `msg.hasReply` | `boolean` | Whether it is a reply |
|
||||
| `msg.isGif` | `boolean` | Whether it is a GIF |
|
||||
| `msg.is(cmd)` | `function` | Checks if starts with command |
|
||||
| `msg.reply(text)` | `function` | Replies with quote |
|
||||
| `msg.downloadMedia()` | `function` | Returns `{ mimetype, data }` |
|
||||
| `msg.getReply()` | `function` | Returns quoted message |
|
||||
|
||||
### `api` Object
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `api.send(text)` | Sends text |
|
||||
| `api.sendVideo(path)` | Sends video |
|
||||
| `api.sendAudio(path)` | Sends audio (voice) |
|
||||
| `api.sendImage(path, caption?)` | Sends image |
|
||||
| `api.sendSticker(bufferOrPath)` | Sends sticker |
|
||||
| `api.getPlugin(name)` | Accesses another plugin |
|
||||
| `api.chat.id` | Chat ID |
|
||||
| `api.chat.name` | Chat name |
|
||||
| `api.chat.isGroup` | Whether it is a group |
|
||||
| `api.log.info(...)` | Info log |
|
||||
| `api.log.warn(...)` | Warning log |
|
||||
| `api.log.error(...)` | Error log |
|
||||
|
||||
---
|
||||
|
||||
## Exposing API to Other Plugins
|
||||
|
||||
A plugin can export functions for others to use:
|
||||
|
||||
```javascript
|
||||
// plugins/utilities/index.js
|
||||
|
||||
// Public API
|
||||
export const api = {
|
||||
formatDate: (date) => date.toLocaleDateString("en-US"),
|
||||
formatCurrency: (value) => `$${value.toFixed(2)}`,
|
||||
wait: (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
};
|
||||
|
||||
// Normal plugin logic
|
||||
export default async function ({ msg }) {
|
||||
if (msg.is("!ping")) {
|
||||
await msg.reply("pong!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Another plugin using it:
|
||||
|
||||
```javascript
|
||||
// plugins/other/index.js
|
||||
export default async function ({ msg, api }) {
|
||||
const utils = api.getPlugin("utilities");
|
||||
|
||||
const date = utils.formatDate(new Date());
|
||||
await msg.reply(`Today is ${date}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translating Your Plugin
|
||||
|
||||
Each plugin can have its own translations, completely independent from the bot core. The bot locale (set in `manybot.conf`) is used automatically.
|
||||
|
||||
### Structure
|
||||
|
||||
```
|
||||
src/plugins/
|
||||
└── my-plugin/
|
||||
├── index.js
|
||||
└── locale/
|
||||
├── en.json
|
||||
├── pt.json
|
||||
└── es.json
|
||||
```
|
||||
|
||||
### locale/en.json
|
||||
|
||||
```json
|
||||
{
|
||||
"hello": "Hello, {{name}}! 👋",
|
||||
"error": {
|
||||
"notFound": "Item not found."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### index.js
|
||||
|
||||
```javascript
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
export default async function ({ msg }) {
|
||||
if (!msg.is(CMD_PREFIX + "hi")) return;
|
||||
|
||||
// Simple key
|
||||
await msg.reply(t("hello", { name: msg.senderName }));
|
||||
|
||||
// Nested key
|
||||
await msg.reply(t("error.notFound"));
|
||||
}
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- If the configured locale has no translation file, falls back to `en.json`.
|
||||
- If the key doesn't exist in any file, the key itself is returned as-is.
|
||||
- Use `{{variable}}` syntax for interpolation.
|
||||
- Each plugin manages its own translations — never import `t` from the bot core.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
If a plugin throws an error, the kernel automatically disables it:
|
||||
|
||||
```javascript
|
||||
export default async function ({ msg, api }) {
|
||||
try {
|
||||
// Code that might fail
|
||||
const result = await somethingRisky();
|
||||
await msg.reply(result);
|
||||
} catch (error) {
|
||||
// Logs the error and notifies
|
||||
api.log.error("Plugin error:", error);
|
||||
await msg.reply("Oops! Something went wrong.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enabling the Plugin
|
||||
|
||||
After creating it, add to `manybot.conf`:
|
||||
|
||||
```bash
|
||||
PLUGINS=[
|
||||
# ... other plugins
|
||||
my-plugin
|
||||
]
|
||||
```
|
||||
|
||||
Restart the bot to load it.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [API Reference](./API.md)
|
||||
- [Plugin examples](../src/plugins/)
|
||||
360
docs/PLUGINS.md
360
docs/PLUGINS.md
@@ -1,360 +0,0 @@
|
||||
# 🔌 Criando Plugins
|
||||
|
||||
Guia completo para criar plugins no ManyBot.
|
||||
|
||||
---
|
||||
|
||||
## ManyPlug CLI
|
||||
|
||||
**ManyPlug** é a ferramenta oficial para gerenciar plugins do ManyBot. Com ela você pode criar, instalar e validar plugins facilmente.
|
||||
|
||||
### Instalação
|
||||
|
||||
```bash
|
||||
npm install -g @freakk.dev/manyplug
|
||||
```
|
||||
|
||||
Ou para desenvolvimento:
|
||||
```bash
|
||||
git clone https://git.maneos.net/synt-xerror/manyplug
|
||||
cd manyplug
|
||||
npm link
|
||||
```
|
||||
|
||||
### Comandos
|
||||
|
||||
| Comando | Descrição |
|
||||
|---------|-----------|
|
||||
| `manyplug init <nome>` | Cria estrutura de um novo plugin |
|
||||
| `manyplug install [nome]` | Instala do registro ou `--local <caminho>` |
|
||||
| `manyplug list` | Lista plugins instalados |
|
||||
| `manyplug validate [caminho]` | Valida o manyplug.json |
|
||||
|
||||
### Exemplos
|
||||
|
||||
```bash
|
||||
# Criar novo plugin
|
||||
cd src/plugins
|
||||
manyplug init meu-plugin --category utility
|
||||
|
||||
# Instalar de outro diretório
|
||||
manyplug install --local ../outro-plugin
|
||||
|
||||
# Validar manifesto
|
||||
manyplug validate ./meu-plugin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📑 Índice
|
||||
|
||||
- [ManyPlug CLI](#manyplug-cli)
|
||||
- [Estrutura Básica](#estrutura-básica)
|
||||
- [Manifesto do Plugin](#manifesto-do-plugin-manyplugjson)
|
||||
- [Criando Seu Primeiro Plugin](#criando-seu-primeiro-plugin)
|
||||
- [API de Objetos](#api-de-objetos)
|
||||
- [Expondo API](#expondo-api-para-outros-plugins)
|
||||
- [Traduzindo Seu Plugin](#traduzindo-seu-plugin)
|
||||
- [Tratamento de Erros](#tratamento-de-erros)
|
||||
|
||||
---
|
||||
|
||||
## Estrutura Básica
|
||||
|
||||
```
|
||||
src/plugins/
|
||||
└── meu-plugin/
|
||||
├── index.js
|
||||
├── manyplug.json
|
||||
└── locale/ (opcional)
|
||||
├── en.json
|
||||
├── pt.json
|
||||
└── es.json
|
||||
```
|
||||
|
||||
O `index.js` deve exportar uma função `default` que recebe `{ msg, api }`:
|
||||
|
||||
```javascript
|
||||
export default async function ({ msg, api }) {
|
||||
// Sua lógica aqui
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manifesto do Plugin (manyplug.json)
|
||||
|
||||
Todo plugin deve ter um `manyplug.json` na raiz. Ele descreve o plugin e declara dependências npm extras que ele precisar.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "meu-plugin",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Campos
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `name` | `string` | Identificador do plugin, deve ser igual ao nome da pasta |
|
||||
| `version` | `string` | Versão semântica (ex: `"1.0.0"`) |
|
||||
| `category` | `string` | Categoria do plugin: `utility`, `media`, `game`, `humor`, `info` |
|
||||
| `service` | `boolean` | `true` se o plugin roda em segundo plano (agendador, listener). `false` se acionado por comando ou evento |
|
||||
| `dependencies` | `object` | Pacotes npm extras necessários, mesmo formato do `package.json` |
|
||||
|
||||
### Exemplo com dependências
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "clima",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Após adicionar dependências, rode `npm install` na raiz do projeto para instalá-las.
|
||||
|
||||
---
|
||||
|
||||
## Criando Seu Primeiro Plugin
|
||||
|
||||
### Exemplo 1: Comando simples
|
||||
|
||||
```javascript
|
||||
// plugins/saudacao/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
// Só responde se a mensagem começar com "!oi"
|
||||
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||
|
||||
await msg.reply("Olá! 👋");
|
||||
}
|
||||
```
|
||||
|
||||
### Exemplo 2: Comando com argumentos
|
||||
|
||||
```javascript
|
||||
// plugins/calcular/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "calcular")) return;
|
||||
|
||||
// msg.args = ["!calcular", "5", "+", "3"]
|
||||
const [, a, operador, b] = msg.args;
|
||||
|
||||
let resultado;
|
||||
switch (operador) {
|
||||
case "+": resultado = Number(a) + Number(b); break;
|
||||
case "-": resultado = Number(a) - Number(b); break;
|
||||
case "*": resultado = Number(a) * Number(b); break;
|
||||
case "/": resultado = Number(a) / Number(b); break;
|
||||
default: return msg.reply("Operador inválido!");
|
||||
}
|
||||
|
||||
await msg.reply(`Resultado: ${resultado}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Exemplo 3: Processando mídia
|
||||
|
||||
```javascript
|
||||
// plugins/echo-media/index.js
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||
|
||||
// Verifica se a mensagem tem mídia
|
||||
if (!msg.hasMedia) {
|
||||
return msg.reply("Envie uma mídia com o comando!");
|
||||
}
|
||||
|
||||
// Baixa a mídia
|
||||
const media = await msg.downloadMedia();
|
||||
|
||||
// Reenvia no chat
|
||||
await api.sendSticker(media.data);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API de Objetos
|
||||
|
||||
### Objeto `msg`
|
||||
|
||||
| Propriedade | Tipo | Descrição |
|
||||
|-------------|------|-----------|
|
||||
| `msg.body` | `string` | Texto da mensagem |
|
||||
| `msg.args` | `string[]` | Tokens da mensagem |
|
||||
| `msg.type` | `string` | Tipo: `chat`, `image`, `video`, `audio`, `sticker` |
|
||||
| `msg.sender` | `string` | ID do remetente |
|
||||
| `msg.senderName` | `string` | Nome do remetente |
|
||||
| `msg.fromMe` | `boolean` | Se o bot enviou |
|
||||
| `msg.hasMedia` | `boolean` | Se tem mídia |
|
||||
| `msg.hasReply` | `boolean` | Se é resposta |
|
||||
| `msg.isGif` | `boolean` | Se é GIF |
|
||||
| `msg.is(cmd)` | `function` | Verifica se começa com comando |
|
||||
| `msg.reply(text)` | `function` | Responde com quote |
|
||||
| `msg.downloadMedia()` | `function` | Retorna `{ mimetype, data }` |
|
||||
| `msg.getReply()` | `function` | Retorna mensagem citada |
|
||||
|
||||
### Objeto `api`
|
||||
|
||||
| Método | Descrição |
|
||||
|--------|-----------|
|
||||
| `api.send(text)` | Envia texto |
|
||||
| `api.sendVideo(path)` | Envia vídeo |
|
||||
| `api.sendAudio(path)` | Envia áudio (voz) |
|
||||
| `api.sendImage(path, caption?)` | Envia imagem |
|
||||
| `api.sendSticker(bufferOrPath)` | Envia figurinha |
|
||||
| `api.getPlugin(name)` | Acessa outro plugin |
|
||||
| `api.chat.id` | ID do chat |
|
||||
| `api.chat.name` | Nome do chat |
|
||||
| `api.chat.isGroup` | Se é grupo |
|
||||
| `api.log.info(...)` | Log informativo |
|
||||
| `api.log.warn(...)` | Log de aviso |
|
||||
| `api.log.error(...)` | Log de erro |
|
||||
|
||||
---
|
||||
|
||||
## Expondo API para Outros Plugins
|
||||
|
||||
Um plugin pode exportar funções para outros usarem:
|
||||
|
||||
```javascript
|
||||
// plugins/utilidades/index.js
|
||||
|
||||
// API pública
|
||||
export const api = {
|
||||
formatarData: (date) => date.toLocaleDateString("pt-BR"),
|
||||
formatarMoeda: (valor) => `R$ ${valor.toFixed(2).replace(".", ",")}`,
|
||||
esperar: (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
};
|
||||
|
||||
// Lógica normal do plugin
|
||||
export default async function ({ msg }) {
|
||||
if (msg.is("!ping")) {
|
||||
await msg.reply("pong!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Outro plugin usando:
|
||||
|
||||
```javascript
|
||||
// plugins/outro/index.js
|
||||
export default async function ({ msg, api }) {
|
||||
const utils = api.getPlugin("utilidades");
|
||||
|
||||
const data = utils.formatarData(new Date());
|
||||
await msg.reply(`Hoje é ${data}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Traduzindo Seu Plugin
|
||||
|
||||
Cada plugin pode ter suas próprias traduções, completamente independentes do core do bot. O locale do bot (definido no `manybot.conf`) é usado automaticamente.
|
||||
|
||||
### Estrutura
|
||||
|
||||
```
|
||||
src/plugins/
|
||||
└── meu-plugin/
|
||||
├── index.js
|
||||
└── locale/
|
||||
├── en.json
|
||||
├── pt.json
|
||||
└── es.json
|
||||
```
|
||||
|
||||
### locale/pt.json
|
||||
|
||||
```json
|
||||
{
|
||||
"ola": "Olá, {{nome}}! 👋",
|
||||
"erro": {
|
||||
"naoEncontrado": "Item não encontrado."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### index.js
|
||||
|
||||
```javascript
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
export default async function ({ msg }) {
|
||||
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||
|
||||
// Chave simples
|
||||
await msg.reply(t("ola", { nome: msg.senderName }));
|
||||
|
||||
// Chave aninhada
|
||||
await msg.reply(t("erro.naoEncontrado"));
|
||||
}
|
||||
```
|
||||
|
||||
### Observações
|
||||
|
||||
- Se o locale configurado não tiver arquivo de tradução, cai automaticamente para `en.json`.
|
||||
- Se a chave não existir em nenhum arquivo, a própria chave é retornada.
|
||||
- Use a sintaxe `{{variavel}}` para interpolação.
|
||||
- Cada plugin gerencia suas próprias traduções — nunca importe `t` do core do bot.
|
||||
|
||||
---
|
||||
|
||||
## Tratamento de Erros
|
||||
|
||||
Se um plugin lançar erro, o kernel o desativa automaticamente:
|
||||
|
||||
```javascript
|
||||
export default async function ({ msg, api }) {
|
||||
try {
|
||||
// Código que pode falhar
|
||||
const resultado = await algoPerigoso();
|
||||
await msg.reply(resultado);
|
||||
} catch (erro) {
|
||||
// Loga o erro e notifica
|
||||
api.log.error("Erro no plugin:", erro);
|
||||
await msg.reply("Ops! Algo deu errado.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ativando o Plugin
|
||||
|
||||
Depois de criar, adicione ao `manybot.conf`:
|
||||
|
||||
```bash
|
||||
PLUGINS=[
|
||||
# ... outros plugins
|
||||
meu-plugin
|
||||
]
|
||||
```
|
||||
|
||||
Reinicie o bot para carregar.
|
||||
|
||||
---
|
||||
|
||||
## Veja Também
|
||||
|
||||
- [Referência da API](./API.md)
|
||||
- [Exemplos de plugins](../src/plugins/)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 620 KiB |
@@ -1,267 +0,0 @@
|
||||
.TH MANYBOT-PLUGIN 1 "April 2026" "ManyBot 2.4.3" "User Commands"
|
||||
.SH NAME
|
||||
manybot-plugin \- ManyBot plugin development guide
|
||||
.SH SYNOPSIS
|
||||
.B manyplug.json
|
||||
.I manifest file
|
||||
.br
|
||||
.I src/plugins/
|
||||
.B plugin directory
|
||||
.SH DESCRIPTION
|
||||
ManyBot plugins extend the bot's functionality without modifying the core
|
||||
kernel. The kernel connects to WhatsApp and distributes messages to plugins,
|
||||
which decide how to respond.
|
||||
.PP
|
||||
Each plugin lives in its own folder under
|
||||
.I src/plugins/
|
||||
with a
|
||||
.B manyplug.json
|
||||
manifest file and an
|
||||
.B index.js
|
||||
entry point.
|
||||
.SH PLUGIN STRUCTURE
|
||||
.nf
|
||||
src/plugins/
|
||||
\(do__ my-plugin/
|
||||
\(bu__ manyplug.json # Plugin manifest
|
||||
\(bu__ index.js # Main entry point
|
||||
\(bu__ locale/ # Translations (optional)
|
||||
\(bu__ en.json
|
||||
\(bu__ pt.json
|
||||
\(bu__ es.json
|
||||
.fi
|
||||
.SH MANIFEST (manyplug.json)
|
||||
Every plugin must include a manifest describing its metadata:
|
||||
.PP
|
||||
.nf
|
||||
{
|
||||
"name": "my-plugin",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0"
|
||||
}
|
||||
}
|
||||
.fi
|
||||
.TP
|
||||
.B name
|
||||
Plugin identifier. Must match the folder name.
|
||||
.TP
|
||||
.B version
|
||||
Semantic version (e.g., "1.0.0").
|
||||
.TP
|
||||
.B category
|
||||
Plugin type: \fButility\fR, \fBmedia\fR, \fBgame\fR, \fBhumor\fR, or \fBinfo\fR.
|
||||
.TP
|
||||
.B service
|
||||
\fBtrue\fR for background plugins (schedulers, listeners).
|
||||
\fBfalse\fR for command/event-triggered plugins.
|
||||
.TP
|
||||
.B dependencies
|
||||
Extra npm packages required. Install with \fBnpm install\fR from project root.
|
||||
.SH PLUGIN ENTRY POINT
|
||||
The
|
||||
.I index.js
|
||||
file must export a default async function:
|
||||
.PP
|
||||
.nf
|
||||
.B "export default async function ({ msg, api }) {"
|
||||
// Plugin logic here
|
||||
.B "}"
|
||||
.fi
|
||||
.SS Parameters
|
||||
.TP
|
||||
.B msg
|
||||
Message object containing:
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
\fBbody\fR - Full message text
|
||||
.IP \(bu 2
|
||||
\fBargs\fR - Array of message tokens
|
||||
.IP \(bu 2
|
||||
\fBtype\fR - Message type: chat, image, video, audio, sticker, ptt, document, location
|
||||
.IP \(bu 2
|
||||
\fBsender\fR - Sender ID (NUMBER@c.us or NUMBER@g.us)
|
||||
.IP \(bu 2
|
||||
\fBsenderName\fR - Display name
|
||||
.IP \(bu 2
|
||||
\fBfromMe\fR - True if bot sent the message
|
||||
.IP \(bu 2
|
||||
\fBhasMedia\fR - True if contains media
|
||||
.IP \(bu 2
|
||||
\fBhasReply\fR - True if it's a reply
|
||||
.IP \(bu 2
|
||||
\fBisGif\fR - True if media is GIF
|
||||
.IP \(bu 2
|
||||
\fBtimestamp\fR - Unix timestamp
|
||||
.IP \(bu 2
|
||||
\fBis(cmd)\fR - Check if message starts with command
|
||||
.IP \(bu 2
|
||||
\fBreply(text)\fR - Reply with quote
|
||||
.IP \(bu 2
|
||||
\fBdownloadMedia()\fR - Download media (returns {mimetype, data})
|
||||
.IP \(bu 2
|
||||
\fBgetReply()\fR - Get quoted message object
|
||||
.RE
|
||||
.TP
|
||||
.B api
|
||||
API object containing:
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
\fBsend(text)\fR - Send text message
|
||||
.IP \(bu 2
|
||||
\fBsendVideo(path)\fR - Send video file
|
||||
.IP \(bu 2
|
||||
\fBsendAudio(path)\fR - Send audio as voice message
|
||||
.IP \(bu 2
|
||||
\fBsendImage(path, caption?)\fR - Send image with optional caption
|
||||
.IP \(bu 2
|
||||
\fBsendSticker(bufferOrPath)\fR - Send sticker from buffer or file
|
||||
.IP \(bu 2
|
||||
\fBgetPlugin(name)\fR - Access another plugin's public API
|
||||
.IP \(bu 2
|
||||
\fBchat\fR - Chat info: \fBid\fR, \fBname\fR, \fBisGroup\fR
|
||||
.IP \(bu 2
|
||||
\fBlog.info(...), log.warn(...), log.error(...)\fR - Logging methods
|
||||
.RE
|
||||
.SH EXAMPLES
|
||||
.SS Simple Command
|
||||
.nf
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "hi")) return;
|
||||
await msg.reply("Hello! 👋");
|
||||
}
|
||||
.fi
|
||||
.SS Command with Arguments
|
||||
.nf
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "calc")) return;
|
||||
|
||||
const [, a, op, b] = msg.args;
|
||||
let result;
|
||||
|
||||
switch (op) {
|
||||
case "+": result = Number(a) + Number(b); break;
|
||||
case "-": result = Number(a) - Number(b); break;
|
||||
default: return msg.reply("Invalid operator!");
|
||||
}
|
||||
|
||||
await msg.reply(`Result: ${result}`);
|
||||
}
|
||||
.fi
|
||||
.SS Processing Media
|
||||
.nf
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||
|
||||
if (!msg.hasMedia) {
|
||||
return msg.reply("Send media with the command!");
|
||||
}
|
||||
|
||||
const media = await msg.downloadMedia();
|
||||
await api.sendSticker(media.data);
|
||||
}
|
||||
.fi
|
||||
.SS Exposing API to Other Plugins
|
||||
.nf
|
||||
// Public API for other plugins
|
||||
export const api = {
|
||||
formatDate: (d) => d.toLocaleDateString("en-US"),
|
||||
wait: (ms) => new Promise(r => setTimeout(r, ms))
|
||||
};
|
||||
|
||||
// Normal plugin logic
|
||||
export default async function ({ msg }) {
|
||||
if (msg.is("!ping")) await msg.reply("pong!");
|
||||
}
|
||||
.fi
|
||||
.PP
|
||||
Used by another plugin:
|
||||
.nf
|
||||
export default async function ({ msg, api }) {
|
||||
const utils = api.getPlugin("utilities");
|
||||
const date = utils.formatDate(new Date());
|
||||
await msg.reply(`Today is ${date}`);
|
||||
}
|
||||
.fi
|
||||
.SH TRANSLATIONS
|
||||
Plugins can include their own translations:
|
||||
.PP
|
||||
.nf
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
export default async function ({ msg }) {
|
||||
if (!msg.is(CMD_PREFIX + "hello")) return;
|
||||
await msg.reply(t("greeting", { name: msg.senderName }));
|
||||
}
|
||||
.fi
|
||||
.PP
|
||||
Locale file (\fIlocale/en.json\fR):
|
||||
.nf
|
||||
{
|
||||
"greeting": "Hello, {{name}}! 👋"
|
||||
}
|
||||
.fi
|
||||
.PP
|
||||
If the configured locale has no translation file, falls back to \fBen.json\fR.
|
||||
Use \fB{{variable}}\fR syntax for interpolation.
|
||||
.SH ENABLING A PLUGIN
|
||||
Add the plugin folder name to
|
||||
.I manybot.confR:
|
||||
.PP
|
||||
.nf
|
||||
PLUGINS=[
|
||||
many,
|
||||
figurinha,
|
||||
my-plugin
|
||||
]
|
||||
.fi
|
||||
.PP
|
||||
Restart ManyBot to load the plugin.
|
||||
.SH ERROR HANDLING
|
||||
If a plugin throws an error, the kernel automatically disables it.
|
||||
Use try/catch for graceful error handling:
|
||||
.PP
|
||||
.nf
|
||||
export default async function ({ msg, api }) {
|
||||
try {
|
||||
const result = await riskyOperation();
|
||||
await msg.reply(result);
|
||||
} catch (error) {
|
||||
api.log.error("Plugin error:", error);
|
||||
await msg.reply("Oops! Something went wrong.");
|
||||
}
|
||||
}
|
||||
.fi
|
||||
.SH CONFIGURATION ACCESS
|
||||
Import settings from the main config:
|
||||
.PP
|
||||
.nf
|
||||
import { CMD_PREFIX, CLIENT_ID, CHATS, PLUGINS } from "../../config.js";
|
||||
|
||||
// Custom config values also work
|
||||
import { MY_API_KEY } from "../../config.js";
|
||||
.fi
|
||||
.PP
|
||||
Add custom values to
|
||||
.I manybot.confR:
|
||||
.nf
|
||||
MY_API_KEY=secret_key_here
|
||||
.fi
|
||||
.SH SEE ALSO
|
||||
.BR manybot (1),
|
||||
.BR manyplug (1),
|
||||
.BR manybot.conf (5)
|
||||
.SH AUTHOR
|
||||
Written by synt-xerror.
|
||||
.SH REPORTING BUGS
|
||||
Report bugs at: https://github.com/synt-xerror/manybot/issues
|
||||
@@ -1,193 +0,0 @@
|
||||
.TH MANYBOT 1 "April 2026" "ManyBot 2.4.3" "User Commands"
|
||||
.SH NAME
|
||||
manybot \- local WhatsApp bot with plugin system
|
||||
.SH SYNOPSIS
|
||||
.B node
|
||||
.I ./src/main.js
|
||||
.br
|
||||
.B node
|
||||
.I src/utils/get_id.js
|
||||
.br
|
||||
.B bash
|
||||
.I ./setup
|
||||
.br
|
||||
.B bash
|
||||
.I ./update
|
||||
.SH DESCRIPTION
|
||||
ManyBot is a 100%% local WhatsApp bot that operates without relying on the
|
||||
official WhatsApp API. It uses whatsapp-web.js to connect as a regular
|
||||
WhatsApp client and provides a modular plugin system for extending functionality.
|
||||
.PP
|
||||
The bot supports multiple chats in a single session, runs headless (without
|
||||
a GUI), and can be configured through a simple configuration file.
|
||||
.SH COMMANDS
|
||||
.TP
|
||||
.B !many
|
||||
List all available commands from loaded plugins.
|
||||
.TP
|
||||
.B !figurinha
|
||||
Convert images, GIFs, and videos to stickers.
|
||||
.TP
|
||||
.B !video \fIURL\fR
|
||||
Download videos from supported platforms.
|
||||
.TP
|
||||
.B !audio \fIURL\fR
|
||||
Download audio from videos and send as voice message.
|
||||
.TP
|
||||
.B !adivinhacao comecar
|
||||
Start a guessing game (1-100).
|
||||
.TP
|
||||
.B !forca comecar
|
||||
Start a hangman game.
|
||||
.TP
|
||||
.B !obrigado
|
||||
Bot responds with a thank-you message.
|
||||
.PP
|
||||
Command prefix can be configured via
|
||||
.B CMD_PREFIX
|
||||
in
|
||||
.IR manybot.conf .
|
||||
Default is
|
||||
.BR ! .
|
||||
.SH CONFIGURATION
|
||||
ManyBot uses a configuration file
|
||||
.I manybot.conf
|
||||
in the project root. Key options:
|
||||
.TP
|
||||
.B CLIENT_ID=\fIname\fR
|
||||
Unique identifier for the bot session. Creates a session/\fIname\fR folder
|
||||
for authentication data. Default: \fIbot_permanente\fR.
|
||||
.TP
|
||||
.B CMD_PREFIX=\fIchar\fR
|
||||
Character prefixing all commands. Default: \fI!\fR.
|
||||
.TP
|
||||
.B LANGUAGE=\fIcode\fR
|
||||
Bot interface language: \fBen\fR (English), \fBpt\fR (Portuguese), or \fBes\fR (Spanish).
|
||||
Default: \fBen\fR.
|
||||
.TP
|
||||
.B CHATS=[\fIid1\fR, \fIid2\fR, ...]
|
||||
List of allowed chat IDs. Empty array allows all chats.
|
||||
Format: \fBnumber@c.us\fR for private chats, \fBnumber@g.us\fR for groups.
|
||||
.TP
|
||||
.B PLUGINS=[\fIname1\fR, \fIname2\fR, ...]
|
||||
List of plugins to load at startup. Each name corresponds to a folder in
|
||||
.IR src/plugins/ .
|
||||
.PP
|
||||
See
|
||||
.BR manybot.conf (5)
|
||||
for complete configuration reference.
|
||||
.SH PLUGINS
|
||||
Plugins extend ManyBot functionality without modifying the kernel. Each plugin
|
||||
is a folder under
|
||||
.I src/plugins/
|
||||
containing:
|
||||
.TP
|
||||
.I index.js
|
||||
Main plugin file exporting a default async function receiving \fB{ msg, api }\fR.
|
||||
.TP
|
||||
.I manyplug.json
|
||||
Plugin manifest describing name, version, category, service status, and dependencies.
|
||||
.TP
|
||||
.I locale/
|
||||
Optional translation files (\fBen.json\fR, \fBpt.json\fR, \fBes.json\fR).
|
||||
.PP
|
||||
Plugins receive two objects:
|
||||
.TP
|
||||
.B msg
|
||||
Message information including \fBbody\fR, \fBargs\fR, \fBtype\fR, \fBsender\fR,
|
||||
\fBhasMedia\fR, methods \fBis()\fR, \fBreply()\fR, \fBdownloadMedia()\fR.
|
||||
.TP
|
||||
.B api
|
||||
Interaction methods including \fBsend()\fR, \fBsendVideo()\fR, \fBsendAudio()\fR,
|
||||
\fBsendImage()\fR, \fBsendSticker()\fR, \fBgetPlugin()\fR, and \fBlog\fR methods.
|
||||
.SH MANYPLUG
|
||||
ManyPlug is the official CLI for managing ManyBot plugins. Install it with:
|
||||
.PP
|
||||
.nf
|
||||
$ npm install -g @freakk.dev/manyplug
|
||||
.fi
|
||||
.PP
|
||||
Common commands:
|
||||
.TP
|
||||
.B manyplug init \fIname\fR
|
||||
Create a new plugin boilerplate in the current directory.
|
||||
.TP
|
||||
.B manyplug install [\fIname\fR]
|
||||
Install a plugin from the registry or use \fB--local \fIpath\fR to install from a local directory.
|
||||
.TP
|
||||
.B manyplug list
|
||||
List all installed plugins in \fIsrc/plugins/\fR.
|
||||
.TP
|
||||
.B manyplug validate [\fIpath\fR]
|
||||
Validate the \fImanyplug.json\fR manifest file.
|
||||
.PP
|
||||
See \fBmanyplug(1)\fR for complete documentation.
|
||||
.SH FILES
|
||||
.TP
|
||||
.I manybot.conf
|
||||
Main configuration file. See \fBmanybot.conf(5)\fR.
|
||||
.TP
|
||||
.I session/
|
||||
Authentication data and WhatsApp session storage.
|
||||
.TP
|
||||
.I src/plugins/
|
||||
Plugin directory containing all installed plugins.
|
||||
.TP
|
||||
.I src/main.js
|
||||
Bot entry point.
|
||||
.TP
|
||||
.I logs/
|
||||
Log files directory.
|
||||
.TP
|
||||
.I update.log
|
||||
Update script log output.
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B NODE_ENV
|
||||
Set to \fBproduction\fR to disable development features.
|
||||
.SH EXIT STATUS
|
||||
.TP
|
||||
.B 0
|
||||
Success
|
||||
.TP
|
||||
.B 1
|
||||
General error
|
||||
.TP
|
||||
.B 130
|
||||
Interrupted by user (Ctrl+C)
|
||||
.SH EXAMPLES
|
||||
.SS First run
|
||||
.nf
|
||||
$ node ./src/main.js
|
||||
# Scan QR code with WhatsApp:
|
||||
# Menu \-> Linked Devices \-> Link a Device
|
||||
.fi
|
||||
.SS Get chat IDs
|
||||
.nf
|
||||
$ node src/utils/get_id.js
|
||||
# Send a message in the target chat to see the ID
|
||||
.fi
|
||||
.SS Update to latest version
|
||||
.nf
|
||||
$ bash ./update
|
||||
.fi
|
||||
.SH SECURITY
|
||||
\(bu Bot runs with same privileges as the user running it
|
||||
.br
|
||||
\(bu Session data stored in \fIsession/\fR should be protected (chmod 700)
|
||||
.br
|
||||
\(bu CHATS whitelist recommended to limit bot exposure
|
||||
.br
|
||||
\(bu No official WhatsApp API keys required or used
|
||||
.SH SEE ALSO
|
||||
.BR manybot.conf (5),
|
||||
.BR manybot-plugin (1),
|
||||
.BR manyplug (1)
|
||||
.SH AUTHOR
|
||||
Written by synt-xerror.
|
||||
.SH COPYRIGHT
|
||||
Licensed under GPLv3. See LICENSE file for details.
|
||||
.br
|
||||
https://github.com/synt-xerror/manybot
|
||||
.SH BUGS
|
||||
Report bugs at: https://github.com/synt-xerror/manybot/issues
|
||||
@@ -1,225 +0,0 @@
|
||||
.TH MANYBOT.CONF 5 "April 2026" "ManyBot 2.4.3" "File Formats"
|
||||
.SH NAME
|
||||
manybot.conf \- ManyBot configuration file
|
||||
.SH SYNOPSIS
|
||||
.I manybot.conf
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.I manybot.conf
|
||||
file configures the ManyBot WhatsApp bot. It uses a simple key-value format
|
||||
with support for multiline lists. Comments start with \fB#\fR.
|
||||
.PP
|
||||
The file must be located in the project root directory, alongside
|
||||
.IR package.json .
|
||||
.SH FORMAT
|
||||
.nf
|
||||
# Comments start with '#'
|
||||
|
||||
KEY=value
|
||||
KEY=[item1, item2, item3]
|
||||
.fi
|
||||
.SS Key-Value Pairs
|
||||
Simple configuration values:
|
||||
.PP
|
||||
.nf
|
||||
CLIENT_ID=my_bot
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=en
|
||||
.fi
|
||||
.SS Multiline Lists
|
||||
Arrays spanning multiple lines:
|
||||
.PP
|
||||
.nf
|
||||
CHATS=[
|
||||
123456789@c.us,
|
||||
123456789@g.us
|
||||
]
|
||||
|
||||
PLUGINS=[
|
||||
many,
|
||||
figurinha,
|
||||
audio,
|
||||
video
|
||||
]
|
||||
.fi
|
||||
.SH OPTIONS
|
||||
.SS Core Settings
|
||||
.TP
|
||||
.B CLIENT_ID=\fIstring\fR
|
||||
Unique identifier for the bot session.
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
Default: \fBbot_permanente\fR
|
||||
.IP \(bu 2
|
||||
Creates a \fIsession/CLIENT_ID/\fR folder for authentication data
|
||||
.IP \(bu 2
|
||||
Changing this starts a new session (requires QR code rescan)
|
||||
.RE
|
||||
.TP
|
||||
.B CMD_PREFIX=\fIcharacter\fR
|
||||
Character that prefixes all bot commands.
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
Default: \fB!\fR
|
||||
.IP \(bu 2
|
||||
Example: \fB!\fR makes commands like \fB!video\fR, \fB!audio\fR
|
||||
.IP \(bu 2
|
||||
Changing to \fB.\fR would make commands \fB.video\fR, \fB.audio\fR
|
||||
.RE
|
||||
.TP
|
||||
.B LANGUAGE=\fIcode\fR
|
||||
Bot interface language.
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
Default: \fBen\fR (English)
|
||||
.IP \(bu 2
|
||||
Options: \fBen\fR, \fBpt\fR (Portuguese), \fBes\fR (Spanish)
|
||||
.IP \(bu 2
|
||||
Fallback to English if selected language not found
|
||||
.RE
|
||||
.SS Chat Settings
|
||||
.TP
|
||||
.B CHATS=[\fIid1\fR, \fIid2\fR, ...]
|
||||
Whitelist of chat IDs where the bot responds.
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
Default: \fB[]\fR (empty = respond to all chats)
|
||||
.IP \(bu 2
|
||||
Private chat format: \fBnumber@c.us\fR
|
||||
.IP \(bu 2
|
||||
Group format: \fBnumber@g.us\fR or \fBnumber-number@g.us\fR
|
||||
.IP \(bu 2
|
||||
Use \fBnode src/utils/get_id.js\fR to discover chat IDs
|
||||
.RE
|
||||
.SS Plugin Settings
|
||||
.TP
|
||||
.B PLUGINS=[\fIname1\fR, \fIname2\fR, ...]
|
||||
List of plugins to load at startup.
|
||||
.RS
|
||||
.IP \(bu 2
|
||||
Default: \fB[]\fR (no plugins loaded)
|
||||
.IP \(bu 2
|
||||
Each name must match a folder in \fIsrc/plugins/\fR
|
||||
.IP \(bu 2
|
||||
Order matters: plugins load in listed order
|
||||
.IP \(bu 2
|
||||
Comment out or remove to disable without deleting files
|
||||
.RE
|
||||
.SS Built-in Plugins
|
||||
.TP
|
||||
.B many
|
||||
Lists all available commands. Required for \fB!many\fR to work.
|
||||
.TP
|
||||
.B figurinha
|
||||
Converts images/GIFs/videos to WhatsApp stickers.
|
||||
.TP
|
||||
.B video
|
||||
Downloads videos from URLs.
|
||||
.TP
|
||||
.B audio
|
||||
Downloads audio from videos as voice messages.
|
||||
.TP
|
||||
.B adivinha\(,c\(oao
|
||||
Guessing game (1-100).
|
||||
.TP
|
||||
.B forca
|
||||
Hangman game.
|
||||
.TP
|
||||
.B obrigado
|
||||
Responds to thank-you messages.
|
||||
.SH CUSTOM SETTINGS
|
||||
You can add any custom key-value pairs for use by plugins:
|
||||
.PP
|
||||
.nf
|
||||
# In manybot.conf
|
||||
ADMIN_NUMBER=5511999999999@c.us
|
||||
API_KEY=your_secret_key
|
||||
MAX_DOWNLOAD_SIZE=50MB
|
||||
.fi
|
||||
.PP
|
||||
Access in plugins:
|
||||
.nf
|
||||
import { ADMIN_NUMBER, API_KEY, MAX_DOWNLOAD_SIZE } from "../../config.js";
|
||||
.fi
|
||||
.SH EXAMPLES
|
||||
.SS Minimal Configuration
|
||||
.nf
|
||||
# Basic bot setup
|
||||
CLIENT_ID=my_bot
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=en
|
||||
|
||||
PLUGINS=[
|
||||
many
|
||||
]
|
||||
.fi
|
||||
.SS Production Configuration
|
||||
.nf
|
||||
# Production bot with whitelist
|
||||
CLIENT_ID=bot_prod
|
||||
CMD_PREFIX=/
|
||||
LANGUAGE=pt
|
||||
|
||||
CHATS=[
|
||||
5511999999999@c.us,
|
||||
5511888888888-123456789@g.us
|
||||
]
|
||||
|
||||
PLUGINS=[
|
||||
many,
|
||||
figurinha,
|
||||
video,
|
||||
audio,
|
||||
obrigado
|
||||
]
|
||||
|
||||
# Custom settings
|
||||
ADMIN_NUMBER=5511999999999@c.us
|
||||
LOG_LEVEL=info
|
||||
.fi
|
||||
.SS Development Configuration
|
||||
.nf
|
||||
# Debug/development setup
|
||||
CLIENT_ID=bot_dev
|
||||
CMD_PREFIX=!
|
||||
LANGUAGE=en
|
||||
|
||||
# Respond to all chats
|
||||
CHATS=[]
|
||||
|
||||
# All plugins for testing
|
||||
PLUGINS=[
|
||||
many,
|
||||
figurinha,
|
||||
video,
|
||||
audio,
|
||||
adivinha\(,c\(oao,
|
||||
forca,
|
||||
obrigado
|
||||
]
|
||||
.fi
|
||||
.SH FILES
|
||||
.TP
|
||||
.I manybot.conf
|
||||
Main configuration file (must be created by user)
|
||||
.TP
|
||||
.I manybot.conf.example
|
||||
Example configuration with documentation comments
|
||||
.SH NOTES
|
||||
\(bu Keys are case-sensitive
|
||||
.br
|
||||
\(bu Values are read as strings unless they're list syntax
|
||||
.br
|
||||
\(bu Inline comments supported: \fBKEY=value # comment\fR
|
||||
.br
|
||||
\(bu Multiline lists must end with \fB]\fR on its own line or last item line
|
||||
.br
|
||||
\(bu Whitespace in values is trimmed
|
||||
.br
|
||||
\(u Missing optional values use built-in defaults
|
||||
.SH SEE ALSO
|
||||
.BR manybot (1),
|
||||
.BR manybot-plugin (1),
|
||||
.BR manyplug (1)
|
||||
.SH AUTHOR
|
||||
Written by synt-xerror.
|
||||
@@ -1,30 +0,0 @@
|
||||
# ManyBot Configuration File
|
||||
# Copy this file to manybot.conf and customize as needed
|
||||
|
||||
# Bot identification
|
||||
CLIENT_ID=meu_bot
|
||||
|
||||
# Command prefix (default: !)
|
||||
CMD_PREFIX=!
|
||||
|
||||
# Language setting (en, pt, es)
|
||||
# Default: en (English)
|
||||
LANGUAGE=en
|
||||
|
||||
# Allowed chats (leave empty for all chats)
|
||||
# Format: chatId1@g.us, chatId2@g.us for groups
|
||||
# Format: number@c.us for private chats
|
||||
CHATS=[
|
||||
]
|
||||
|
||||
# Active plugins
|
||||
PLUGINS=[
|
||||
many,
|
||||
figurinha,
|
||||
audio,
|
||||
video,
|
||||
adivinhação,
|
||||
forca,
|
||||
obrigado,
|
||||
a
|
||||
]
|
||||
668
package-lock.json
generated
668
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "manybot",
|
||||
"version": "2.4.3",
|
||||
"name": "whatsapp-bot",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manybot",
|
||||
"version": "2.4.3",
|
||||
"name": "whatsapp-bot",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7",
|
||||
"node-gyp": "^12.2.0",
|
||||
@@ -14,9 +14,6 @@
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"wa-sticker-formatter": "^4.4.4",
|
||||
"whatsapp-web.js": "^1.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"conventional-changelog-cli": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -42,33 +39,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@conventional-changelog/git-client": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.6.0.tgz",
|
||||
"integrity": "sha512-T+uPDciKf0/ioNNDpMGc8FDsehJClZP0yR3Q5MN6wE/Y/1QZ7F+80OgznnTCOlMEG4AV0LvH2UJi3C/nBnaBUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@simple-libs/child-process-utils": "^1.0.0",
|
||||
"@simple-libs/stream-utils": "^1.2.0",
|
||||
"semver": "^7.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"conventional-commits-filter": "^5.0.0",
|
||||
"conventional-commits-parser": "^6.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"conventional-commits-filter": {
|
||||
"optional": true
|
||||
},
|
||||
"conventional-commits-parser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@gar/promise-retry": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz",
|
||||
@@ -81,16 +51,6 @@
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hutson/parse-repository-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-5.0.0.tgz",
|
||||
"integrity": "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
@@ -167,35 +127,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@simple-libs/child-process-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@simple-libs/stream-utils": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/dangreen"
|
||||
}
|
||||
},
|
||||
"node_modules/@simple-libs/stream-utils": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
||||
"integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/dangreen"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
@@ -218,13 +149,6 @@
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/normalize-package-data": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
|
||||
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
@@ -256,13 +180,6 @@
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/add-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
@@ -376,13 +293,6 @@
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/array-ify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
|
||||
"integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ast-types": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
||||
@@ -836,17 +746,6 @@
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/compare-func": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
|
||||
"integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"array-ify": "^1.0.0",
|
||||
"dot-prop": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compress-commons": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
||||
@@ -870,227 +769,6 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/conventional-changelog": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-6.0.0.tgz",
|
||||
"integrity": "sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"conventional-changelog-angular": "^8.0.0",
|
||||
"conventional-changelog-atom": "^5.0.0",
|
||||
"conventional-changelog-codemirror": "^5.0.0",
|
||||
"conventional-changelog-conventionalcommits": "^8.0.0",
|
||||
"conventional-changelog-core": "^8.0.0",
|
||||
"conventional-changelog-ember": "^5.0.0",
|
||||
"conventional-changelog-eslint": "^6.0.0",
|
||||
"conventional-changelog-express": "^5.0.0",
|
||||
"conventional-changelog-jquery": "^6.0.0",
|
||||
"conventional-changelog-jshint": "^5.0.0",
|
||||
"conventional-changelog-preset-loader": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-angular": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz",
|
||||
"integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"compare-func": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-atom": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-5.1.0.tgz",
|
||||
"integrity": "sha512-fw7GpI9jHNCWGBnTsPRI452ypQbNupGwsjrXfozvRNE0c92pJRpoj9rXfzDKUYJcsmk0H4XKaQjhjelwI9z27w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-cli": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-5.0.0.tgz",
|
||||
"integrity": "sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ==",
|
||||
"deprecated": "This package is no longer maintained. Please use the conventional-changelog package instead.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"add-stream": "^1.0.0",
|
||||
"conventional-changelog": "^6.0.0",
|
||||
"meow": "^13.0.0",
|
||||
"tempfile": "^5.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"conventional-changelog": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-codemirror": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-5.1.0.tgz",
|
||||
"integrity": "sha512-iXhy63YczB+yWA9DrsYbquSYLvWKsK9M3WC+xQPEm8cOn4oXzKpmTp2uH3qi7+i10oTcGJTvq9lsBpZmMADaNg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-conventionalcommits": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz",
|
||||
"integrity": "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"compare-func": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-core": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-8.0.0.tgz",
|
||||
"integrity": "sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hutson/parse-repository-url": "^5.0.0",
|
||||
"add-stream": "^1.0.0",
|
||||
"conventional-changelog-writer": "^8.0.0",
|
||||
"conventional-commits-parser": "^6.0.0",
|
||||
"git-raw-commits": "^5.0.0",
|
||||
"git-semver-tags": "^8.0.0",
|
||||
"hosted-git-info": "^7.0.0",
|
||||
"normalize-package-data": "^6.0.0",
|
||||
"read-package-up": "^11.0.0",
|
||||
"read-pkg": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-ember": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-5.1.0.tgz",
|
||||
"integrity": "sha512-XNcgGcdJt7wh341BBML0CI8DKpqE5lKD1WahzFHGZFvKTzJr1rZW976cw7beqKLOBbzdrH9ZIkE/s2TfbOuM3g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-eslint": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-6.1.0.tgz",
|
||||
"integrity": "sha512-beWr3qzuEMN9gznMWa8PhTVfGkGXoq+XnUzViNXg5KygrgV728ZRqZngz3uPhz5+ayUhPrpNFYqIE0qHWz9NAw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-express": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-5.1.0.tgz",
|
||||
"integrity": "sha512-g/s9eLohrefYTSNQaB6+k0ONbiVx41YOKBbIOIM3ST/NtedAgppCJnrpKXVN9sOmpPkN4vjFwURlfvpEDUjoeg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-jquery": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-6.1.0.tgz",
|
||||
"integrity": "sha512-/sFhULybhFrMg+qc8MHHHSj7kTVMfx5C7rSM6Z9EjduVoAQJdGRq/wpv/SWPMQ+KPNSYHqDLwm/x2Z5hOcYvqQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-jshint": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-5.2.0.tgz",
|
||||
"integrity": "sha512-OaatyvHXP1fjI7Mx0b1IkmhbhTsVHsytnsQSkOj4rhGbFMoTcfvbwm/vAtCzRMXOxojK1EDMBBmBj1pM9KNy/Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"compare-func": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-preset-loader": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz",
|
||||
"integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-writer": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.4.0.tgz",
|
||||
"integrity": "sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@simple-libs/stream-utils": "^1.2.0",
|
||||
"conventional-commits-filter": "^5.0.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"meow": "^13.0.0",
|
||||
"semver": "^7.5.2"
|
||||
},
|
||||
"bin": {
|
||||
"conventional-changelog-writer": "dist/cli/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-commits-filter": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz",
|
||||
"integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-commits-parser": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz",
|
||||
"integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@simple-libs/stream-utils": "^1.2.0",
|
||||
"meow": "^13.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"conventional-commits-parser": "dist/cli/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
@@ -1230,19 +908,6 @@
|
||||
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/dot-prop": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
||||
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-obj": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer2": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
||||
@@ -1491,19 +1156,6 @@
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up-simple": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz",
|
||||
"integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/fluent-ffmpeg": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
|
||||
@@ -1637,40 +1289,6 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/git-raw-commits": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz",
|
||||
"integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@conventional-changelog/git-client": "^2.6.0",
|
||||
"meow": "^13.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"git-raw-commits": "src/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/git-semver-tags": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-8.0.1.tgz",
|
||||
"integrity": "sha512-zMbamckSNdlT4U48IMFa2Cn6FTzM+2yF6/gEmStPJI8PiLxd/bT6dw10+mc6u5Qe4fhrc/y9nU290FWjQhAV7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@conventional-changelog/git-client": "^2.6.0",
|
||||
"meow": "^13.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"git-semver-tags": "src/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
@@ -1705,48 +1323,6 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.9",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
|
||||
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
|
||||
"integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.14.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
@@ -1856,19 +1432,6 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/index-to-position": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
|
||||
"integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -1917,16 +1480,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-obj": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
@@ -2101,19 +1654,6 @@
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/meow": {
|
||||
"version": "13.2.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
|
||||
"integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
@@ -2328,13 +1868,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/netmask": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
||||
@@ -2451,21 +1984,6 @@
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-package-data": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
|
||||
"integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"hosted-git-info": "^7.0.0",
|
||||
"semver": "^7.3.5",
|
||||
"validate-npm-package-license": "^3.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.14.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@@ -2816,62 +2334,6 @@
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/read-package-up": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
|
||||
"integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-up-simple": "^1.0.0",
|
||||
"read-pkg": "^9.0.0",
|
||||
"type-fest": "^4.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz",
|
||||
"integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/normalize-package-data": "^2.4.3",
|
||||
"normalize-package-data": "^6.0.0",
|
||||
"parse-json": "^8.0.0",
|
||||
"type-fest": "^4.6.0",
|
||||
"unicorn-magic": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg/node_modules/parse-json": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
|
||||
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"index-to-position": "^1.1.0",
|
||||
"type-fest": "^4.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -3211,48 +2673,12 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-correct": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
|
||||
"dev": true,
|
||||
"license": "CC-BY-3.0"
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
|
||||
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"spdx-exceptions": "^2.1.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.23",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz",
|
||||
"integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/ssri": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz",
|
||||
@@ -3404,32 +2830,6 @@
|
||||
"streamx": "^2.12.5"
|
||||
}
|
||||
},
|
||||
"node_modules/temp-dir": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz",
|
||||
"integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/tempfile": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tempfile/-/tempfile-5.0.0.tgz",
|
||||
"integrity": "sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"temp-dir": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/text-decoder": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
|
||||
@@ -3506,39 +2906,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-query-selector": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
|
||||
"integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
@@ -3546,19 +2919,6 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/unicorn-magic": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
|
||||
"integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/unique-filename": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz",
|
||||
@@ -3650,17 +3010,6 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wa-sticker-formatter": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/wa-sticker-formatter/-/wa-sticker-formatter-4.4.4.tgz",
|
||||
@@ -3738,13 +3087,6 @@
|
||||
"which": "bin/which"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "manybot",
|
||||
"version": "2.4.3",
|
||||
"name": "whatsapp-bot",
|
||||
"version": "2.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7",
|
||||
@@ -9,11 +9,5 @@
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"wa-sticker-formatter": "^4.4.4",
|
||||
"whatsapp-web.js": "^1.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"conventional-changelog-cli": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
|
||||
}
|
||||
}
|
||||
|
||||
73
setup
73
setup
@@ -1,9 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Salvando diretório para evitar problemas
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ------------------------
|
||||
# Cores
|
||||
# ------------------------
|
||||
@@ -42,15 +39,16 @@ log_debug() { log "[DBG]" "$GRAY" "$@"; }
|
||||
print_banner() {
|
||||
echo -e "${MAGENTA}${BOLD}"
|
||||
cat << "EOF"
|
||||
_____ _____ _
|
||||
| |___ ___ _ _| __ |___| |_
|
||||
_____ _____ _
|
||||
| |___ ___ _ _| __ |___| |_
|
||||
| | | | .'| | | | __ -| . | _|
|
||||
|_|_|_|__,|_|_|_ |_____|___|_|
|
||||
|___|
|
||||
|_|_|_|__,|_|_|_ |_____|___|_|
|
||||
|___|
|
||||
|
||||
website: www.mlplovers.com.br/manybot
|
||||
repos: git.maneos.net/synt-xerror/manybot
|
||||
codeberg.org/synt-xerror/manybot
|
||||
repo: github.com/synt-xerror/manybot
|
||||
|
||||
A Amizade é Mágica!
|
||||
|
||||
EOF
|
||||
echo -e "${RESET}"
|
||||
@@ -164,40 +162,43 @@ export PUPPETEER_SKIP_DOWNLOAD=1
|
||||
run_cmd npm install
|
||||
|
||||
# ------------------------
|
||||
# Chrome Puppeeter
|
||||
# Diretórios
|
||||
# ------------------------
|
||||
log_info "Instalando Chrome"
|
||||
|
||||
npx puppeteer browsers install chrome
|
||||
log_info "Preparando diretórios"
|
||||
mkdir -p bin
|
||||
log_debug "Diretório bin garantido"
|
||||
|
||||
# ------------------------
|
||||
# ManyPlug CLI
|
||||
# Arquivos por plataforma
|
||||
# ------------------------
|
||||
log_info "Instalando ManyPlug CLI"
|
||||
log_info "Selecionando dependências binárias"
|
||||
|
||||
if ! command -v manyplug &>/dev/null; then
|
||||
log_info "ManyPlug não encontrado, instalando globalmente..."
|
||||
npm install -g @freakk.dev/manyplug
|
||||
log_ok "ManyPlug instalado com sucesso"
|
||||
files=()
|
||||
if [[ "$PLATFORM" == "win" ]]; then
|
||||
log_debug "Usando binários Windows"
|
||||
files=(
|
||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/yt-dlp.exe bin/yt-dlp.exe"
|
||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/ffmpeg.exe bin/ffmpeg.exe"
|
||||
)
|
||||
else
|
||||
log_ok "ManyPlug já está instalado"
|
||||
log_debug "Usando binários Unix"
|
||||
files=(
|
||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/yt-dlp bin/yt-dlp"
|
||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/ffmpeg bin/ffmpeg"
|
||||
)
|
||||
fi
|
||||
|
||||
# ------------------------
|
||||
# Configuração de exemplo
|
||||
# ------------------------
|
||||
log_info "Verificando configuração"
|
||||
log_debug "Total de arquivos para baixar: ${#files[@]}"
|
||||
|
||||
if [[ ! -f "$SCRIPT_DIR/manybot.conf" ]]; then
|
||||
if [[ -f "$SCRIPT_DIR/manybot.conf.example" ]]; then
|
||||
cp "$SCRIPT_DIR/manybot.conf.example" "$SCRIPT_DIR/manybot.conf"
|
||||
log_ok "Arquivo manybot.conf criado a partir do exemplo"
|
||||
log_warn "Edite o manybot.conf para configurar seu bot antes de executar"
|
||||
else
|
||||
log_warn "Arquivo manybot.conf.example não encontrado"
|
||||
fi
|
||||
else
|
||||
log_ok "manybot.conf já existe"
|
||||
fi
|
||||
# ------------------------
|
||||
# Download
|
||||
# ------------------------
|
||||
for file in "${files[@]}"; do
|
||||
url="${file%% *}"
|
||||
dest="${file##* }"
|
||||
|
||||
log_ok "Setup concluído com sucesso.\nRode sempre na raíz: 'node src/main.js' para rodar o bot."
|
||||
log_info "Processando dependência"
|
||||
download_file "$url" "$dest"
|
||||
done
|
||||
|
||||
log_ok "Setup concluído com sucesso"
|
||||
@@ -1,48 +0,0 @@
|
||||
import { c } from "../logger/formatter.js";
|
||||
|
||||
export function printBanner() {
|
||||
|
||||
const banner = [
|
||||
` _____ _____ _ `,
|
||||
` | |___ ___ _ _| __ |___| |_ `,
|
||||
` | | | | . | | | | __ -| . | _|`,
|
||||
` |_|_|_|__,|_|_|_ |_____|___|_| `,
|
||||
` |___| `
|
||||
];
|
||||
|
||||
const pony = [
|
||||
` ⠴⢮⠭⠍⠉⠉⠒⠤⣀`,
|
||||
` ⢀⢊ ⢱⠊⠑⡀`,
|
||||
` ⠋⡎ ⣀⡠⠤⠠⠖⠋⢉⠉ ⡄⢸`,
|
||||
` ⣘⡠⠊⣩⡅ ⣴⡟⣯⠙⣊ ⢁⠜`,
|
||||
` ⣿⡇⢸⣿⣷⡿⢀⠇⢀⢎`,
|
||||
` ⠰⡉ ⠈⠛⠛⠋⠁⢀⠜ ⢂`,
|
||||
` ⠈⠒⠒⡲⠂⣠⣔⠁ ⡇ ⢀⡴⣾⣛⡛⠻⣦`,
|
||||
` ⢠⠃ ⢠⠞ ⡸⠉⠲⣿⠿⢿⣿⣿⣷⡌⢷`,
|
||||
` ⢀⠔⠂⢼ ⡎⡔⡄⠰⠃ ⢣ ⢻⣿⣿⣿⠘⣷`,
|
||||
` ⡐⠁ ⠸⡀ ⠏ ⠈⠃ ⢸ ⣿⣿⣿⡇⣿⡇`,
|
||||
` ⡇ ⡎⠉⠉⢳ ⡤⠤⡤⠲⡀ ⢇ ⣿⣿⣿⣇⣿⣷`,
|
||||
` ⡇ ⡠⠃ ⡸ ⡇ ⡇ ⢱⡀ ⢣ ⣿⣿⣿⣿⣿⡄`,
|
||||
` ⠑⠊ ⢰ ⠇ ⢸ ⡇⡇ ⡇ ⢳⣿⣿⣿⣿⡇`,
|
||||
` ⢠⠃ ⡸ ⡎ ⡜⡇ ⡇ ⠻⡏⠻⣿⣿⣄`,
|
||||
` ⣔⣁⣀⣀⡠⠁ ⠈⠉⠉⠁⣎⣀⣀⣀⡸`
|
||||
];
|
||||
|
||||
|
||||
|
||||
console.log(`${c.blue}${c.bold}`);
|
||||
|
||||
const max = Math.max(banner.length, pony.length);
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
const left = banner[i] || " ".repeat(banner[0].length);
|
||||
const right = pony[i] || "";
|
||||
console.log(left + " " + right);
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(` website : ${c.reset}${c.cyan}www.mlplovers.com.br/manybot${c.reset}`);
|
||||
console.log(` repo : ${c.reset}${c.cyan}github.com/synt-xerror/manybot${c.reset}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import os from "os";
|
||||
|
||||
/**
|
||||
* Detect if running inside Termux.
|
||||
*/
|
||||
export const isTermux =
|
||||
(os.platform() === "linux" || os.platform() === "android") &&
|
||||
process.env.PREFIX?.startsWith("/data/data/com.termux");
|
||||
|
||||
/**
|
||||
* Return Puppeteer config suitable for the environment.
|
||||
* @returns {import("puppeteer").LaunchOptions}
|
||||
*/
|
||||
export function resolvePuppeteerConfig() {
|
||||
if (!isTermux) return {};
|
||||
|
||||
return {
|
||||
executablePath: "/data/data/com.termux/files/usr/bin/chromium-browser",
|
||||
args: [
|
||||
"--headless=new", "--no-sandbox", "--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage", "--disable-gpu", "--single-process",
|
||||
"--no-zygote", "--disable-software-rasterizer",
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import qrcode from "qrcode-terminal";
|
||||
import path from "path";
|
||||
import { logger } from "../logger/logger.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
import { isTermux } from "./environment.js";
|
||||
|
||||
const QR_PATH = path.resolve("qr.png");
|
||||
|
||||
/**
|
||||
* Display or save QR Code based on environment.
|
||||
* @param {string} qr — raw string from "qr" event
|
||||
*/
|
||||
export async function handleQR(qr) {
|
||||
if (isTermux) {
|
||||
try {
|
||||
await QRCode.toFile(QR_PATH, qr, { width: 400 });
|
||||
logger.info(t("system.qrSaved", { path: QR_PATH }));
|
||||
logger.info(t("system.qrOpen"));
|
||||
} catch (err) {
|
||||
logger.error(t("system.qrSaveFailed"), err.message);
|
||||
}
|
||||
} else {
|
||||
logger.info(t("system.qrScan"));
|
||||
qrcode.generate(qr, { small: true });
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,100 @@
|
||||
import pkg from "whatsapp-web.js";
|
||||
import { CLIENT_ID } from "../config.js";
|
||||
import { logger } from "../logger/logger.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
import { isTermux, resolvePuppeteerConfig } from "./environment.js";
|
||||
import { handleQR } from "./qrHandler.js";
|
||||
import { printBanner } from "./banner.js";
|
||||
import pkg from "whatsapp-web.js";
|
||||
import qrcode from "qrcode-terminal";
|
||||
import { exec } from "child_process";
|
||||
import { CLIENT_ID } from "../config.js";
|
||||
import os from "os";
|
||||
|
||||
export const { Client, LocalAuth, MessageMedia } = pkg;
|
||||
|
||||
// ── Environment ───────────────────────────────────────────────
|
||||
// ── Logger ──────────────────────────────────────────────────
|
||||
const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
|
||||
blue: "\x1b[34m", magenta: "\x1b[35m",
|
||||
};
|
||||
|
||||
const now = () =>
|
||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "short" });
|
||||
|
||||
export const logger = {
|
||||
info: (...a) => console.log(`${c.gray}${now()}${c.reset} ℹ️ `, ...a),
|
||||
success: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.green}✅${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.yellow}⚠️ ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.red}❌${c.reset}`, ...a),
|
||||
bot: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.magenta}🤖${c.reset}`, ...a),
|
||||
};
|
||||
|
||||
// ── Banner ───────────────────────────────────────────────────
|
||||
function printBanner() {
|
||||
console.log(`${c.blue}${c.bold}`);
|
||||
console.log(` _____ _____ _ `);
|
||||
console.log(`| |___ ___ _ _| __ |___| |_ `);
|
||||
console.log(`| | | | .'| | | | __ -| . | _|`);
|
||||
console.log(`|_|_|_|__,|_|_|_ |_____|___|_| `);
|
||||
console.log(` |___| `);
|
||||
console.log();
|
||||
console.log(` website : ${c.reset}${c.cyan}www.mlplovers.com.br/manybot${c.reset}`);
|
||||
console.log(` repo : ${c.reset}${c.cyan}github.com/synt-xerror/manybot${c.reset}`);
|
||||
console.log();
|
||||
console.log(` ${c.bold}✨ A Amizade é Mágica!${c.reset}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ── Ambiente ─────────────────────────────────────────────────
|
||||
const isTermux =
|
||||
(os.platform() === "linux" || os.platform() === "android") &&
|
||||
process.env.PREFIX?.startsWith("/data/data/com.termux");
|
||||
|
||||
logger.info(isTermux
|
||||
? t("system.environmentTermux")
|
||||
: t("system.environment", { platform: process.platform, puppeteer: "system Puppeteer" })
|
||||
? `Ambiente: ${c.yellow}${c.bold}Termux${c.reset} — usando Chromium do sistema`
|
||||
: `Ambiente: ${c.blue}${c.bold}${os.platform()}${c.reset} — usando Puppeteer padrão`
|
||||
);
|
||||
|
||||
// ── Instance ──────────────────────────────────────────────────
|
||||
const puppeteerConfig = isTermux
|
||||
? {
|
||||
executablePath: "/data/data/com.termux/files/usr/bin/chromium-browser",
|
||||
args: [
|
||||
"--headless=new", "--no-sandbox", "--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage", "--disable-gpu", "--single-process",
|
||||
"--no-zygote", "--disable-software-rasterizer",
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
// ── Cliente ──────────────────────────────────────────────────
|
||||
export const client = new Client({
|
||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
...(resolvePuppeteerConfig().args || [])
|
||||
],
|
||||
...resolvePuppeteerConfig()
|
||||
},
|
||||
puppeteer: { headless: true, ...puppeteerConfig },
|
||||
});
|
||||
|
||||
// ── Events ────────────────────────────────────────────────────
|
||||
client.on("qr", handleQR);
|
||||
client.on("qr", async qr => {
|
||||
if (isTermux) {
|
||||
try {
|
||||
await QRCode.toFile(QR_PATH, qr, { width: 400 });
|
||||
logger.bot(`QR Code salvo em: ${c.cyan}${c.bold}${QR_PATH}${c.reset}`);
|
||||
logger.bot(`Abra com: ${c.yellow}termux-open qr.png${c.reset}`);
|
||||
} catch (err) {
|
||||
logger.error("Falha ao salvar QR Code:", err.message);
|
||||
}
|
||||
} else {
|
||||
logger.bot(`Escaneie o ${c.yellow}${c.bold}QR Code${c.reset} abaixo:`);
|
||||
qrcode.generate(qr, { small: true });
|
||||
}
|
||||
});
|
||||
|
||||
client.on("ready", () => {
|
||||
exec("clear");
|
||||
printBanner();
|
||||
logger.success(t("system.connected"));
|
||||
logger.info(t("system.clientId", { id: CLIENT_ID }));
|
||||
logger.success(`${c.green}${c.bold}WhatsApp conectado e pronto!${c.reset}`);
|
||||
logger.info(`Client ID: ${c.cyan}${CLIENT_ID}${c.reset}`);
|
||||
});
|
||||
|
||||
client.on("disconnected", (reason) => {
|
||||
logger.warn(t("system.disconnected", { reason }));
|
||||
logger.info(t("system.reconnecting", { seconds: 5 }));
|
||||
client.on("disconnected", reason => {
|
||||
logger.warn(`Desconectado — motivo: ${c.yellow}${reason}${c.reset}`);
|
||||
logger.info(`Reconectando em ${c.cyan}5s${c.reset}...`);
|
||||
setTimeout(() => {
|
||||
logger.info(t("system.reinitializing"));
|
||||
logger.bot("Reinicializando cliente...");
|
||||
client.initialize();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
256
src/commands/figurinha.js
Normal file
256
src/commands/figurinha.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
import pkg from "whatsapp-web.js";
|
||||
import { createSticker } from "wa-sticker-formatter";
|
||||
|
||||
import { client } from "../client/whatsappClient.js";
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
import { emptyFolder } from "../utils/file.js";
|
||||
import { stickerSessions } from "./index.js";
|
||||
|
||||
const { MessageMedia } = pkg;
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
const DOWNLOADS_DIR = path.resolve("downloads");
|
||||
const FFMPEG = os.platform() === "win32"
|
||||
? ".\\bin\\ffmpeg.exe"
|
||||
: "./bin/ffmpeg";
|
||||
|
||||
const MAX_STICKER_SIZE = 900 * 1024;
|
||||
const SESSION_TIMEOUT = 2 * 60 * 1000;
|
||||
const MAX_MEDIA = 10;
|
||||
|
||||
// ───────────────── Helpers ─────────────────
|
||||
|
||||
function ensureDownloadsDir() {
|
||||
if (!fs.existsSync(DOWNLOADS_DIR)) {
|
||||
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupFiles(...files) {
|
||||
for (const f of files) {
|
||||
if (f && fs.existsSync(f)) fs.unlinkSync(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Converte vídeo/gif → GIF 512x512 com paleta preservada
|
||||
async function convertVideoToGif(inputPath, outputPath, fps = 12) {
|
||||
|
||||
const clampedFps = Math.min(fps, 12);
|
||||
|
||||
const filter = [
|
||||
`fps=${clampedFps},scale=512:512:flags=lanczos,split[s0][s1]`,
|
||||
`[s0]palettegen=max_colors=256:reserve_transparent=1[p]`,
|
||||
`[s1][p]paletteuse=dither=bayer`
|
||||
].join(";");
|
||||
|
||||
await execFileAsync(FFMPEG, [
|
||||
"-i", inputPath,
|
||||
"-filter_complex", filter, // <-- era -vf, tem que ser -filter_complex pro split funcionar
|
||||
"-loop", "0",
|
||||
"-y",
|
||||
outputPath
|
||||
]);
|
||||
}
|
||||
|
||||
// Força imagem estática para 512x512
|
||||
async function resizeToSticker(inputPath, outputPath) {
|
||||
|
||||
await execFileAsync(FFMPEG, [
|
||||
"-i", inputPath,
|
||||
"-vf", "scale=512:512:flags=lanczos", // lanczos = melhor qualidade no resize
|
||||
"-y",
|
||||
outputPath
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
async function createStickerWithFallback(stickerInputPath, isAnimated) {
|
||||
|
||||
const qualities = [80, 60, 40, 20];
|
||||
|
||||
for (const quality of qualities) {
|
||||
|
||||
const buffer = await createSticker(
|
||||
fs.readFileSync(stickerInputPath),
|
||||
{
|
||||
pack: "Criada por ManyBot\n",
|
||||
author: "\ngithub.com/synt-xerror/manybot",
|
||||
type: isAnimated ? "FULL" : "STATIC",
|
||||
categories: ["🤖"],
|
||||
quality,
|
||||
}
|
||||
);
|
||||
|
||||
if (buffer.length <= MAX_STICKER_SIZE) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new Error("Não foi possível reduzir o sticker para menos de 900 KB.");
|
||||
}
|
||||
|
||||
// ───────────────── Sessão ─────────────────
|
||||
|
||||
export function iniciarSessao(chatId, author) {
|
||||
|
||||
if (stickerSessions.has(chatId)) return false;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
|
||||
stickerSessions.delete(chatId);
|
||||
client.sendMessage(chatId, botMsg("Sessão de figurinha expirou."));
|
||||
|
||||
}, SESSION_TIMEOUT);
|
||||
|
||||
stickerSessions.set(chatId, {
|
||||
author,
|
||||
medias: [],
|
||||
timeout
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// ───────────────── Coleta de mídia ─────────────────
|
||||
|
||||
export async function coletarMidia(msg) {
|
||||
|
||||
const chat = await msg.getChat();
|
||||
const chatId = chat.id._serialized;
|
||||
|
||||
const session = stickerSessions.get(chatId);
|
||||
if (!session) return;
|
||||
|
||||
const sender = msg.author || msg.from;
|
||||
if (sender !== session.author) return;
|
||||
if (!msg.hasMedia) return;
|
||||
|
||||
const media = await msg.downloadMedia();
|
||||
if (!media) return;
|
||||
|
||||
const isGif =
|
||||
media.mimetype === "image/gif" ||
|
||||
(media.mimetype === "video/mp4" && msg._data?.isGif);
|
||||
|
||||
if (
|
||||
!media.mimetype ||
|
||||
(!media.mimetype.startsWith("image/") &&
|
||||
!media.mimetype.startsWith("video/") &&
|
||||
!isGif)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.medias.length < MAX_MEDIA) {
|
||||
session.medias.push(media);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ───────────────── Criar stickers ─────────────────
|
||||
|
||||
export async function gerarSticker(msg, chatId) {
|
||||
|
||||
const sender = msg.author || msg.from;
|
||||
const session = stickerSessions.get(chatId);
|
||||
|
||||
if (!session) {
|
||||
return msg.reply(botMsg("Nenhuma sessão de figurinha ativa."));
|
||||
}
|
||||
|
||||
if (session.author !== sender) {
|
||||
return msg.reply(botMsg("Apenas quem iniciou a sessão pode criar as figurinhas."));
|
||||
}
|
||||
|
||||
const medias = session.medias;
|
||||
|
||||
if (!medias.length) {
|
||||
return msg.reply(botMsg("Nenhuma imagem recebida."));
|
||||
}
|
||||
|
||||
clearTimeout(session.timeout);
|
||||
|
||||
console.log("midias:", medias.length);
|
||||
|
||||
await msg.reply(botMsg("Aguarde! Estou criando as suas figurinhas..."));
|
||||
|
||||
ensureDownloadsDir();
|
||||
|
||||
for (const media of medias) {
|
||||
try {
|
||||
const ext = media.mimetype.split("/")[1];
|
||||
const isVideo = media.mimetype.startsWith("video/");
|
||||
const isGif = media.mimetype === "image/gif";
|
||||
const isAnimated = isVideo || isGif;
|
||||
|
||||
const id = Date.now() + "-" + Math.random().toString(36).slice(2);
|
||||
const inputPath = path.join(DOWNLOADS_DIR, `${id}.${ext}`);
|
||||
const gifPath = path.join(DOWNLOADS_DIR, `${id}.gif`);
|
||||
const resizedPath = path.join(DOWNLOADS_DIR, `${id}-scaled.${ext}`);
|
||||
|
||||
fs.writeFileSync(inputPath, Buffer.from(media.data, "base64"));
|
||||
|
||||
// LOG 1 — arquivo de entrada
|
||||
const inputSize = fs.statSync(inputPath).size;
|
||||
console.log(`[1] mimetype: ${media.mimetype} | isAnimated: ${isAnimated} | inputPath: ${inputPath} | size: ${inputSize} bytes`);
|
||||
|
||||
let stickerInputPath = inputPath;
|
||||
|
||||
if (isAnimated) {
|
||||
console.log("[2] Convertendo para GIF...");
|
||||
await convertVideoToGif(inputPath, gifPath, isVideo ? 12 : 24);
|
||||
|
||||
// LOG 2 — gif gerado
|
||||
if (fs.existsSync(gifPath)) {
|
||||
console.log(`[2] GIF gerado: ${fs.statSync(gifPath).size} bytes`);
|
||||
} else {
|
||||
console.error("[2] ERRO: gifPath não foi criado pelo ffmpeg!");
|
||||
}
|
||||
|
||||
stickerInputPath = gifPath;
|
||||
} else {
|
||||
console.log("[2] Redimensionando imagem estática...");
|
||||
await resizeToSticker(inputPath, resizedPath);
|
||||
|
||||
if (fs.existsSync(resizedPath)) {
|
||||
console.log(`[2] Resized gerado: ${fs.statSync(resizedPath).size} bytes`);
|
||||
} else {
|
||||
console.error("[2] ERRO: resizedPath não foi criado!");
|
||||
}
|
||||
|
||||
stickerInputPath = resizedPath;
|
||||
}
|
||||
|
||||
// LOG 3 — antes de criar o sticker
|
||||
console.log(`[3] stickerInputPath: ${stickerInputPath} | exists: ${fs.existsSync(stickerInputPath)} | size: ${fs.existsSync(stickerInputPath) ? fs.statSync(stickerInputPath).size : "N/A"} bytes`);
|
||||
|
||||
const stickerBuffer = await createStickerWithFallback(stickerInputPath, isAnimated);
|
||||
|
||||
// LOG 4 — sticker gerado
|
||||
console.log(`[4] Sticker buffer: ${stickerBuffer.length} bytes`);
|
||||
|
||||
const stickerMedia = new MessageMedia("image/webp", stickerBuffer.toString("base64"));
|
||||
await client.sendMessage(chatId, stickerMedia, { sendMediaAsSticker: true });
|
||||
|
||||
cleanupFiles(inputPath, gifPath, resizedPath);
|
||||
|
||||
} catch (err) {
|
||||
console.error("Erro ao gerar sticker:", err);
|
||||
await msg.reply(botMsg("Erro ao gerar uma das figurinhas."));
|
||||
}
|
||||
}
|
||||
|
||||
await msg.reply(botMsg("Figurinhas geradas com sucesso!"));
|
||||
|
||||
stickerSessions.delete(chatId);
|
||||
emptyFolder("downloads");
|
||||
|
||||
}
|
||||
129
src/commands/index.js
Normal file
129
src/commands/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { enqueueDownload } from "../download/queue.js";
|
||||
import { iniciarSessao, gerarSticker } from "./figurinha.js";
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
import { iniciarJogo, pararJogo } from "../games/adivinhacao.js";
|
||||
import { processarInfo } from "./info.js";
|
||||
|
||||
export const stickerSessions = new Map();
|
||||
|
||||
const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m",
|
||||
};
|
||||
|
||||
const now = () =>
|
||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" });
|
||||
|
||||
const log = {
|
||||
cmd: (cmd, ...a) => console.log(`${c.gray}${now()}${c.reset} ${c.cyan}⚙️ ${c.bold}${cmd}${c.reset}`, ...a),
|
||||
ok: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.green}✅${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.yellow}⚠️ ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.red}❌${c.reset}`, ...a),
|
||||
};
|
||||
|
||||
export async function processarComando(msg, chat, chatId) {
|
||||
const tokens = msg.body.trim().split(/\s+/);
|
||||
const cmd = tokens[0]?.toLowerCase();
|
||||
|
||||
if (!cmd?.startsWith("!") && cmd !== "a") return;
|
||||
|
||||
log.cmd(cmd);
|
||||
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "!many":
|
||||
await chat.sendMessage(botMsg(
|
||||
"Comandos:\n\n" +
|
||||
"- `!ping`\n" +
|
||||
"- `!video <link>`\n" +
|
||||
"- `!audio <link>`\n" +
|
||||
"- `!figurinha`\n" +
|
||||
"- `!adivinhação começar|parar`\n" +
|
||||
"- `!info <comando>`"
|
||||
));
|
||||
break;
|
||||
|
||||
case "!ping":
|
||||
await msg.reply(botMsg("pong 🏓"));
|
||||
log.ok("pong enviado");
|
||||
break;
|
||||
|
||||
case "!video":
|
||||
if (!tokens[1]) { log.warn("!video sem link"); return; }
|
||||
await msg.reply(botMsg("⏳ Baixando vídeo..."));
|
||||
enqueueDownload("video", tokens[1], msg, chatId);
|
||||
log.ok("vídeo enfileirado →", tokens[1]);
|
||||
break;
|
||||
|
||||
case "!audio":
|
||||
if (!tokens[1]) { log.warn("!audio sem link"); return; }
|
||||
await msg.reply(botMsg("⏳ Baixando áudio..."));
|
||||
enqueueDownload("audio", tokens[1], msg, chatId);
|
||||
log.ok("áudio enfileirado →", tokens[1]);
|
||||
break;
|
||||
|
||||
case "!figurinha":
|
||||
const author = msg.author || msg.from;
|
||||
|
||||
if (tokens[1] === "criar") {
|
||||
await gerarSticker(msg, chatId);
|
||||
} else {
|
||||
if (stickerSessions.has(chatId)) {
|
||||
return msg.reply("Já existe uma sessão ativa.");
|
||||
}
|
||||
|
||||
iniciarSessao(chatId, author);
|
||||
|
||||
await msg.reply(
|
||||
`Sessão de figurinha iniciada por @${author.split("@")[0]}. Envie no máximo 10 imagens, quando estiver pronto mande \`!figurinha criar\``,
|
||||
null,
|
||||
{ mentions: [author] }
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case "!adivinhação":
|
||||
if (!tokens[1]) {
|
||||
await chat.sendMessage(botMsg("`!adivinhação começar`\n`!adivinhação parar`"));
|
||||
return;
|
||||
}
|
||||
if (tokens[1] === "começar") {
|
||||
iniciarJogo();
|
||||
await chat.sendMessage(botMsg("Jogo iniciado! Tente adivinhar o número de 1 a 100."));
|
||||
log.ok("jogo iniciado");
|
||||
} else if (tokens[1] === "parar") {
|
||||
pararJogo();
|
||||
await chat.sendMessage(botMsg("Jogo parado."));
|
||||
log.ok("jogo parado");
|
||||
} else {
|
||||
log.warn("!adivinhação — subcomando desconhecido:", tokens[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case "!info":
|
||||
if (!tokens[1]) {
|
||||
await chat.sendMessage(botMsg("Use:\n`!info <comando>`"));
|
||||
return;
|
||||
}
|
||||
processarInfo(tokens[1], chat);
|
||||
log.ok("info →", tokens[1]);
|
||||
break;
|
||||
|
||||
case "!obrigado":
|
||||
case "!valeu":
|
||||
case "!brigado":
|
||||
await msg.reply(botMsg("Por nada!"));
|
||||
break;
|
||||
|
||||
case "a":
|
||||
if (!tokens[1]) await msg.reply(botMsg("B"));
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
log.error("Falha em", cmd, "—", err.message);
|
||||
await chat.sendMessage(botMsg("Erro:\n`" + err.message + "`"));
|
||||
}
|
||||
}
|
||||
20
src/commands/info.js
Normal file
20
src/commands/info.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
|
||||
export async function processarInfo(cmd, chat) {
|
||||
switch(cmd) {
|
||||
case "ping":
|
||||
await chat.sendMessage(botMsg("> `!ping`\nResponde pong."));
|
||||
break;
|
||||
case "video":
|
||||
await chat.sendMessage(botMsg("> `!video <link>`\nBaixa vídeo da internet."));
|
||||
break;
|
||||
case "audio":
|
||||
await chat.sendMessage(botMsg("> `!audio <link>`\nBaixa áudio da internet."));
|
||||
break;
|
||||
case "figurinha":
|
||||
await chat.sendMessage(botMsg("`!figurinha`\nTransforma imagem/GIF em sticker."));
|
||||
break;
|
||||
default:
|
||||
await chat.sendMessage(botMsg(`❌ Comando '${tokens[1]}' não encontrado.`));
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,5 @@
|
||||
/**
|
||||
* config.js
|
||||
*
|
||||
* Reads and parses manybot.conf.
|
||||
* Supports multiline lists and inline comments.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
function parseConf(raw) {
|
||||
const lines = raw.split("\n");
|
||||
|
||||
const cleaned = [];
|
||||
let insideList = false;
|
||||
let buffer = "";
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.replace(/#.*$/, "").trim();
|
||||
if (!line) continue;
|
||||
|
||||
if (!insideList) {
|
||||
if (line.includes("=[") && !line.includes("]")) {
|
||||
insideList = true;
|
||||
buffer = line;
|
||||
} else {
|
||||
cleaned.push(line);
|
||||
}
|
||||
} else {
|
||||
buffer += line;
|
||||
if (line.includes("]")) {
|
||||
insideList = false;
|
||||
cleaned.push(buffer);
|
||||
buffer = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = {};
|
||||
for (const line of cleaned) {
|
||||
const eqIdx = line.indexOf("=");
|
||||
if (eqIdx === -1) continue;
|
||||
|
||||
const key = line.slice(0, eqIdx).trim();
|
||||
const raw = line.slice(eqIdx + 1).trim();
|
||||
|
||||
if (raw.startsWith("[") && raw.endsWith("]")) {
|
||||
result[key] = raw
|
||||
.slice(1, -1)
|
||||
.split(",")
|
||||
.map(x => x.trim())
|
||||
.filter(Boolean);
|
||||
} else {
|
||||
result[key] = raw;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const filePath = path.join(__dirname, "../manybot.conf");
|
||||
|
||||
const raw = fs.readFileSync(filePath, "utf8");
|
||||
const config = parseConf(raw);
|
||||
|
||||
export const CLIENT_ID = config.CLIENT_ID ?? "bot_permanente";
|
||||
export const CMD_PREFIX = config.CMD_PREFIX ?? "!";
|
||||
export const CHATS = config.CHATS ?? [];
|
||||
|
||||
/** Active plugin list — e.g., PLUGINS=[video, audio, hello] */
|
||||
export const PLUGINS = config.PLUGINS ?? [];
|
||||
|
||||
/** Bot language — e.g., LANGUAGE=en (fallback: en) */
|
||||
export const LANGUAGE = config.LANGUAGE ?? "en";
|
||||
|
||||
/** Export full config for plugins that need custom values */
|
||||
export const CONFIG = config;
|
||||
export const CLIENT_ID = "bot_permanente";
|
||||
export const BOT_PREFIX = "🤖 *ManyBot:* ";
|
||||
export const CHATS = [
|
||||
// coloque os chats que quer aqui
|
||||
];
|
||||
|
||||
24
src/download/audio.js
Normal file
24
src/download/audio.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { get_video } from "./video.js";
|
||||
import { spawn } from "child_process";
|
||||
import os from "os";
|
||||
|
||||
const so = os.platform();
|
||||
|
||||
export async function get_audio(url, id) {
|
||||
const video = await get_video(url, id);
|
||||
const output = `downloads/${id}.mp3`;
|
||||
const cmd = so === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg";
|
||||
const args = ['-i', video, '-vn', '-acodec', 'libmp3lame', '-q:a', '2', output];
|
||||
|
||||
await runCmd(cmd, args);
|
||||
return output;
|
||||
}
|
||||
|
||||
async function runCmd(cmd, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn(cmd, args);
|
||||
proc.stdout.on("data", data => console.log("[cmd]", data.toString()));
|
||||
proc.stderr.on("data", data => console.error("[cmd ERR]", data.toString()));
|
||||
proc.on("close", code => code === 0 ? resolve() : reject(new Error("Processo saiu com código "+code)));
|
||||
});
|
||||
}
|
||||
@@ -1,55 +1,42 @@
|
||||
/**
|
||||
* src/download/queue.js
|
||||
*
|
||||
* Sequential execution queue for heavy jobs (downloads, conversions).
|
||||
* Ensures only one job runs at a time — without overloading yt-dlp or ffmpeg.
|
||||
*
|
||||
* Plugin passes a `workFn` that does everything: download, convert, send.
|
||||
* Queue only handles sequence and error handling.
|
||||
*
|
||||
* Usage:
|
||||
* import { enqueue } from "../../src/download/queue.js";
|
||||
* enqueue(async () => { ... all plugin logic ... }, onError);
|
||||
*/
|
||||
import { get_video } from "./video.js";
|
||||
import { get_audio } from "./audio.js";
|
||||
import pkg from "whatsapp-web.js";
|
||||
const { MessageMedia } = pkg;
|
||||
import fs from "fs";
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
import { emptyFolder } from "../utils/file.js";
|
||||
import client from "../client/whatsappClient.js";
|
||||
|
||||
import { logger } from "../logger/logger.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
let downloadQueue = [];
|
||||
let processingQueue = false;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* workFn: () => Promise<void>,
|
||||
* errorFn: (err: Error) => Promise<void>,
|
||||
* }} Job
|
||||
*/
|
||||
|
||||
/** @type {Job[]} */
|
||||
let queue = [];
|
||||
let processing = false;
|
||||
|
||||
/**
|
||||
* Add job to queue and start processing if idle.
|
||||
*
|
||||
* @param {Function} workFn — async () => void — all plugin logic
|
||||
* @param {Function} errorFn — async (err) => void — called if workFn throws
|
||||
*/
|
||||
export function enqueue(workFn, errorFn) {
|
||||
queue.push({ workFn, errorFn });
|
||||
if (!processing) processQueue();
|
||||
export function enqueueDownload(type, url, msg, chatId) {
|
||||
downloadQueue.push({ type, url, msg, chatId });
|
||||
if (!processingQueue) processQueue();
|
||||
}
|
||||
|
||||
async function processQueue() {
|
||||
processing = true;
|
||||
while (queue.length) {
|
||||
await processJob(queue.shift());
|
||||
}
|
||||
processing = false;
|
||||
}
|
||||
processingQueue = true;
|
||||
while (downloadQueue.length) {
|
||||
const job = downloadQueue.shift();
|
||||
try {
|
||||
let path;
|
||||
if (job.type === "video") path = await get_video(job.url, job.msg.id._serialized);
|
||||
else path = await get_audio(job.url, job.msg.id._serialized);
|
||||
|
||||
async function processJob({ workFn, errorFn }) {
|
||||
try {
|
||||
await workFn();
|
||||
} catch (err) {
|
||||
logger.error(t("system.downloadJobFailed", { message: err.message }));
|
||||
try { await errorFn(err); } catch { }
|
||||
}
|
||||
const file = fs.readFileSync(path);
|
||||
const media = new MessageMedia(
|
||||
job.type === "video" ? "video/mp4" : "audio/mpeg",
|
||||
file.toString("base64"),
|
||||
path.split("/").pop()
|
||||
);
|
||||
await client.sendMessage(job.chatId, media);
|
||||
fs.unlinkSync(path);
|
||||
emptyFolder("downloads");
|
||||
|
||||
} catch (err) {
|
||||
await client.sendMessage(job.chatId, botMsg(`❌ Erro ao baixar ${job.type}\n\`${err.message}\``));
|
||||
}
|
||||
}
|
||||
processingQueue = false;
|
||||
}
|
||||
55
src/download/video.js
Normal file
55
src/download/video.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { spawn } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
const platform = os.platform();
|
||||
|
||||
export async function get_video(url, id) {
|
||||
// garante que a pasta exista
|
||||
const downloadsDir = path.resolve("downloads");
|
||||
fs.mkdirSync(downloadsDir, { recursive: true });
|
||||
|
||||
const cmd = platform === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp";
|
||||
const args = [
|
||||
'--extractor-args', 'youtube:player_client=android',
|
||||
'-f', 'bv+ba/best',
|
||||
'--print', 'after_move:filepath',
|
||||
'--output', path.join(downloadsDir, `${id}.%(ext)s`),
|
||||
'--cookies', 'cookies.txt',
|
||||
'--add-header', 'User-Agent:Mozilla/5.0',
|
||||
'--add-header', 'Referer:https://www.youtube.com/',
|
||||
'--retries', '4',
|
||||
'--fragment-retries', '5',
|
||||
'--socket-timeout', '15',
|
||||
'--sleep-interval', '1', '--max-sleep-interval', '4',
|
||||
'--no-playlist',
|
||||
url
|
||||
];
|
||||
|
||||
return await runCmd(cmd, args);
|
||||
}
|
||||
|
||||
async function runCmd(cmd, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn(cmd, args);
|
||||
let stdout = "";
|
||||
|
||||
proc.stdout.on("data", data => stdout += data.toString());
|
||||
proc.stderr.on("data", data => console.error("[yt-dlp ERR]", data.toString()));
|
||||
|
||||
proc.on("close", code => {
|
||||
if (code !== 0) return reject(new Error("yt-dlp saiu com código " + code));
|
||||
|
||||
// Pega a última linha, que é o caminho final do arquivo
|
||||
const lines = stdout.trim().split("\n").filter(l => l.trim());
|
||||
const filepath = lines[lines.length - 1];
|
||||
|
||||
if (!fs.existsSync(filepath)) {
|
||||
return reject(new Error("Arquivo não encontrado: " + filepath));
|
||||
}
|
||||
|
||||
resolve(filepath);
|
||||
});
|
||||
});
|
||||
}
|
||||
29
src/games/adivinhacao.js
Normal file
29
src/games/adivinhacao.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
|
||||
let jogoAtivo = null;
|
||||
|
||||
export function iniciarJogo() {
|
||||
jogoAtivo = Math.floor(Math.random()*100)+1;
|
||||
return jogoAtivo;
|
||||
}
|
||||
|
||||
export function pararJogo() {
|
||||
jogoAtivo = null;
|
||||
}
|
||||
|
||||
export async function processarJogo(msg, chat) {
|
||||
if (!jogoAtivo) return;
|
||||
|
||||
const tentativa = msg.body.trim();
|
||||
if (!/^\d+$/.test(tentativa)) return;
|
||||
|
||||
const num = parseInt(tentativa);
|
||||
if (num === jogoAtivo) {
|
||||
await msg.reply(botMsg(`Acertou! Número: ${jogoAtivo}`));
|
||||
pararJogo();
|
||||
} else if (num > jogoAtivo) {
|
||||
await chat.sendMessage(botMsg("Menor."));
|
||||
} else {
|
||||
await chat.sendMessage(botMsg("Maior."));
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
/**
|
||||
* i18n/index.js
|
||||
*
|
||||
* Internationalization system for ManyBot.
|
||||
* Loads translations based on LANGUAGE configuration.
|
||||
* Fallback is always English (en).
|
||||
*
|
||||
* Plugins can use createPluginT() to have isolated i18n.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { CONFIG } from "../config.js";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LOCALES_DIR = path.join(__dirname, "..", "locales");
|
||||
|
||||
// Default language (fallback)
|
||||
const DEFAULT_LANG = "en";
|
||||
|
||||
// Cache of loaded translations
|
||||
const translations = new Map();
|
||||
|
||||
/**
|
||||
* Loads a translation JSON file
|
||||
* @param {string} lang - language code (en, pt, es)
|
||||
* @returns {object|null}
|
||||
*/
|
||||
function loadLocale(lang) {
|
||||
if (translations.has(lang)) {
|
||||
return translations.get(lang);
|
||||
}
|
||||
|
||||
const filePath = path.join(LOCALES_DIR, `${lang}.json`);
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const data = JSON.parse(content);
|
||||
translations.set(lang, data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error(`[i18n] Failed to load locale ${lang}:`, err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configured language or default
|
||||
* @returns {string}
|
||||
*/
|
||||
function getConfiguredLang() {
|
||||
const lang = CONFIG.LANGUAGE?.trim().toLowerCase();
|
||||
if (!lang) return DEFAULT_LANG;
|
||||
|
||||
// Check if file exists
|
||||
const filePath = path.join(LOCALES_DIR, `${lang}.json`);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.warn(`[i18n] Language "${lang}" not found, falling back to "${DEFAULT_LANG}"`);
|
||||
return DEFAULT_LANG;
|
||||
}
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
// Load languages
|
||||
const currentLang = getConfiguredLang();
|
||||
const currentTranslations = loadLocale(currentLang) || {};
|
||||
const fallbackTranslations = loadLocale(DEFAULT_LANG) || {};
|
||||
|
||||
/**
|
||||
* Gets a nested value from an object using dot path
|
||||
* @param {object} obj
|
||||
* @param {string} key - path like "system.connected"
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
function getNestedValue(obj, key) {
|
||||
const parts = key.split(".");
|
||||
let current = obj;
|
||||
|
||||
for (const part of parts) {
|
||||
if (current === null || current === undefined || typeof current !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces placeholders {{key}} with values from context
|
||||
* @param {string} str
|
||||
* @param {object} context
|
||||
* @returns {string}
|
||||
*/
|
||||
function interpolate(str, context = {}) {
|
||||
return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
||||
return context[key] !== undefined ? String(context[key]) : match;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main translation function
|
||||
* @param {string} key - translation key (e.g., "system.connected")
|
||||
* @param {object} context - values to interpolate {{key}}
|
||||
* @returns {string}
|
||||
*/
|
||||
export function t(key, context = {}) {
|
||||
// Try current language first
|
||||
let value = getNestedValue(currentTranslations, key);
|
||||
|
||||
// Fallback to English if not found
|
||||
if (value === undefined) {
|
||||
value = getNestedValue(fallbackTranslations, key);
|
||||
}
|
||||
|
||||
// If still not found, return the key
|
||||
if (value === undefined) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// If not string, convert
|
||||
if (typeof value !== "string") {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
// Interpolate values
|
||||
return interpolate(value, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an isolated translation function for a plugin.
|
||||
* Plugins should have their own locale/ folder with en.json, es.json, etc.
|
||||
*
|
||||
* Usage in plugin:
|
||||
* import { createPluginT } from "../../i18n/index.js";
|
||||
* const { t } = createPluginT(import.meta.url);
|
||||
*
|
||||
* Folder structure:
|
||||
* myPlugin/
|
||||
* index.js
|
||||
* locale/
|
||||
* en.json
|
||||
* es.json
|
||||
* pt.json
|
||||
*
|
||||
* @param {string} pluginMetaUrl - import.meta.url from the plugin
|
||||
* @returns {{ t: Function, lang: string }}
|
||||
*/
|
||||
export function createPluginT(pluginMetaUrl) {
|
||||
const pluginDir = path.dirname(fileURLToPath(pluginMetaUrl));
|
||||
const pluginLocaleDir = path.join(pluginDir, "locale");
|
||||
|
||||
// Get bot's configured language
|
||||
const targetLang = currentLang;
|
||||
|
||||
// Load plugin translations
|
||||
let pluginTranslations = {};
|
||||
let pluginFallback = {};
|
||||
|
||||
try {
|
||||
// Try to load the configured language
|
||||
const targetPath = path.join(pluginLocaleDir, `${targetLang}.json`);
|
||||
if (fs.existsSync(targetPath)) {
|
||||
pluginTranslations = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
||||
}
|
||||
|
||||
// Always load English as fallback
|
||||
const fallbackPath = path.join(pluginLocaleDir, `${DEFAULT_LANG}.json`);
|
||||
if (fs.existsSync(fallbackPath)) {
|
||||
pluginFallback = JSON.parse(fs.readFileSync(fallbackPath, "utf8"));
|
||||
}
|
||||
} catch (err) {
|
||||
// Silent fail - plugin may not have translations
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin-specific translation function
|
||||
* @param {string} key
|
||||
* @param {object} context
|
||||
* @returns {string}
|
||||
*/
|
||||
function pluginT(key, context = {}) {
|
||||
// Try plugin's target language first
|
||||
let value = getNestedValue(pluginTranslations, key);
|
||||
|
||||
// Fallback to plugin's English
|
||||
if (value === undefined) {
|
||||
value = getNestedValue(pluginFallback, key);
|
||||
}
|
||||
|
||||
// If still not found, return the key
|
||||
if (value === undefined) {
|
||||
return key;
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
return interpolate(value, context);
|
||||
}
|
||||
|
||||
return { t: pluginT, lang: targetLang };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads translations (useful for hot-reload)
|
||||
*/
|
||||
export function reloadTranslations() {
|
||||
translations.clear();
|
||||
const lang = getConfiguredLang();
|
||||
const newTranslations = loadLocale(lang) || {};
|
||||
const newFallback = loadLocale(DEFAULT_LANG) || {};
|
||||
|
||||
// Update references
|
||||
Object.assign(currentTranslations, newTranslations);
|
||||
Object.assign(fallbackTranslations, newFallback);
|
||||
|
||||
console.log(`[i18n] Translations reloaded for language: ${lang}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current language
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getCurrentLang() {
|
||||
return currentLang;
|
||||
}
|
||||
|
||||
export default { t, createPluginT, reloadTranslations, getCurrentLang };
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* messageHandler.js
|
||||
*
|
||||
* Central pipeline for received messages.
|
||||
*
|
||||
* Order:
|
||||
* 1. Filter allowed chats (CHATS from .conf)
|
||||
* — if CHATS is empty, accepts all chats
|
||||
* 2. Log the message
|
||||
* 3. Pass context to all active plugins
|
||||
*
|
||||
* Kernel knows no commands — only distributes.
|
||||
* Each plugin decides on its own whether to act or ignore.
|
||||
*/
|
||||
|
||||
import { CHATS } from "../config.js";
|
||||
import { getChatId } from "../utils/getChatId.js";
|
||||
import { buildApi } from "./pluginApi.js";
|
||||
import { pluginRegistry } from "./pluginLoader.js";
|
||||
import { runPlugin } from "./pluginGuard.js";
|
||||
import { buildMessageContext } from "../logger/messageContext.js";
|
||||
import { logger } from "../logger/logger.js";
|
||||
import client from "../client/whatsappClient.js";
|
||||
|
||||
export async function handleMessage(msg) {
|
||||
const chat = await msg.getChat();
|
||||
const chatId = getChatId(chat);
|
||||
|
||||
// CHATS empty = accepts all chats
|
||||
if (CHATS.length > 0 && !CHATS.includes(chatId)) return;
|
||||
|
||||
const ctx = await buildMessageContext(msg, chat);
|
||||
logger.msg(ctx);
|
||||
|
||||
const api = buildApi({ msg, chat, client, pluginRegistry });
|
||||
const context = { msg: api.msg, chat: api.chat, api };
|
||||
|
||||
for (const plugin of pluginRegistry.values()) {
|
||||
await runPlugin(plugin, context);
|
||||
}
|
||||
|
||||
logger.done("message_create", `de +${ctx.senderNumber}`);
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
/**
|
||||
* pluginApi.js
|
||||
*
|
||||
* Builds the `api` object each plugin receives.
|
||||
* Plugins can only do what's here — never touch client directly.
|
||||
*
|
||||
* `chat` is already filtered by kernel (only allowed chats from .conf),
|
||||
* so plugins don't need and can't choose destination.
|
||||
*/
|
||||
|
||||
import { logger } from "../logger/logger.js";
|
||||
import pkg from "whatsapp-web.js";
|
||||
|
||||
const { MessageMedia } = pkg;
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {import("whatsapp-web.js").Message} params.msg
|
||||
* @param {import("whatsapp-web.js").Chat} params.chat
|
||||
* @param {Map<string, any>} params.pluginRegistry
|
||||
* @returns {object} api
|
||||
*/
|
||||
/**
|
||||
* Setup API — without message context.
|
||||
* Passed to plugin.setup(api) during initialization.
|
||||
* Only has sendTo variants, log and schedule.
|
||||
*/
|
||||
export function buildSetupApi(client) {
|
||||
return {
|
||||
async sendTo(chatId, text) {
|
||||
return client.sendMessage(chatId, text);
|
||||
},
|
||||
async sendVideoTo(chatId, filePath, caption = "") {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return client.sendMessage(chatId, media, { caption });
|
||||
},
|
||||
async sendAudioTo(chatId, filePath) {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return client.sendMessage(chatId, media, { sendAudioAsVoice: true });
|
||||
},
|
||||
async sendImageTo(chatId, filePath, caption = "") {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return client.sendMessage(chatId, media, { caption });
|
||||
},
|
||||
async sendStickerTo(chatId, source) {
|
||||
const media = typeof source === "string"
|
||||
? MessageMedia.fromFilePath(source)
|
||||
: new MessageMedia("image/webp", source.toString("base64"));
|
||||
return client.sendMessage(chatId, media, { sendMediaAsSticker: true });
|
||||
},
|
||||
log: {
|
||||
info: (...a) => logger.info(...a),
|
||||
warn: (...a) => logger.warn(...a),
|
||||
error: (...a) => logger.error(...a),
|
||||
success: (...a) => logger.success(...a),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApi({ msg, chat, client, pluginRegistry }) {
|
||||
|
||||
const currentChat = chat;
|
||||
|
||||
return {
|
||||
|
||||
// ── Message reading ─────────────────────────────────────
|
||||
|
||||
msg: {
|
||||
/** Message body */
|
||||
body: msg.body ?? "",
|
||||
|
||||
/** Type: "chat", "image", "video", "audio", "ptt", "sticker", "document" */
|
||||
type: msg.type,
|
||||
|
||||
/** true if message came from bot itself */
|
||||
fromMe: msg.fromMe,
|
||||
|
||||
/** Sender ID (ex: "5511999999999@c.us") */
|
||||
sender: msg.author || msg.from,
|
||||
|
||||
/** Display name of sender */
|
||||
senderName: msg._data?.notifyName || String(msg.from).replace(/(:\d+)?@.*$/, ""),
|
||||
|
||||
/** Tokens: ["!video", "https://..."] */
|
||||
args: msg.body?.trim().split(/\s+/) ?? [],
|
||||
|
||||
/**
|
||||
* Check if message is a specific command.
|
||||
* @param {string} cmd — ex: "!hello"
|
||||
*/
|
||||
is(cmd) {
|
||||
return msg.body?.trim().toLowerCase().startsWith(cmd.toLowerCase());
|
||||
},
|
||||
|
||||
/** true if message has attached media */
|
||||
hasMedia: msg.hasMedia,
|
||||
|
||||
/** true if media is a GIF (short looping video) */
|
||||
isGif: msg._data?.isGif ?? false,
|
||||
|
||||
/**
|
||||
* Download message media.
|
||||
* Returns neutral object { mimetype, data } — without exposing MessageMedia.
|
||||
* @returns {Promise<{ mimetype: string, data: string } | null>}
|
||||
*/
|
||||
async downloadMedia() {
|
||||
const media = await msg.downloadMedia();
|
||||
if (!media) return null;
|
||||
return { mimetype: media.mimetype, data: media.data };
|
||||
},
|
||||
|
||||
/** true if message is a reply to another */
|
||||
hasReply: msg.hasQuotedMsg,
|
||||
|
||||
/**
|
||||
* Returns quoted message if exists.
|
||||
* @returns {Promise<import("whatsapp-web.js").Message|null>}
|
||||
*/
|
||||
async getReply() {
|
||||
if (!msg.hasQuotedMsg) return null;
|
||||
return msg.getQuotedMessage();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reply directly to message (with quote).
|
||||
* @param {string} text
|
||||
*/
|
||||
async reply(text) {
|
||||
return msg.reply(text);
|
||||
},
|
||||
},
|
||||
|
||||
// ── Send to current chat ─────────────────────────────────
|
||||
|
||||
/**
|
||||
* Send plain text.
|
||||
* @param {string} text
|
||||
*/
|
||||
async send(text) {
|
||||
return currentChat.sendMessage(text);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send media (image, video, audio, document).
|
||||
* @param {import("whatsapp-web.js").MessageMedia} media
|
||||
* @param {string} [caption]
|
||||
*/
|
||||
async sendMedia(media, caption = "") {
|
||||
return currentChat.sendMessage(media, { caption });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send video file from local path.
|
||||
* @param {string} filePath
|
||||
* @param {string} [caption]
|
||||
*/
|
||||
async sendVideo(filePath, caption = "") {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return currentChat.sendMessage(media, { caption });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send audio file from local path.
|
||||
* @param {string} filePath
|
||||
*/
|
||||
async sendAudio(filePath) {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return currentChat.sendMessage(media, { sendAudioAsVoice: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send image from local path.
|
||||
* @param {string} filePath
|
||||
* @param {string} [caption]
|
||||
*/
|
||||
async sendImage(filePath, caption = "") {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return currentChat.sendMessage(media, { caption });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a sticker.
|
||||
* Accepts filePath (string) or buffer (Buffer) — plugin never needs
|
||||
* to know MessageMedia exists.
|
||||
* @param {string | Buffer} source
|
||||
*/
|
||||
async sendSticker(source) {
|
||||
const media = typeof source === "string"
|
||||
? MessageMedia.fromFilePath(source)
|
||||
: new MessageMedia("image/webp", source.toString("base64"));
|
||||
return currentChat.sendMessage(media, { sendMediaAsSticker: true });
|
||||
},
|
||||
|
||||
// ── Send to specific chat ───────────────────────────────
|
||||
|
||||
/**
|
||||
* Send text to specific chat by ID.
|
||||
* @param {string} chatId
|
||||
* @param {string} text
|
||||
*/
|
||||
async sendTo(chatId, text) {
|
||||
return client.sendMessage(chatId, text);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send video to specific chat by ID.
|
||||
* @param {string} chatId
|
||||
* @param {string} filePath
|
||||
* @param {string} [caption]
|
||||
*/
|
||||
async sendVideoTo(chatId, filePath, caption = "") {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return client.sendMessage(chatId, media, { caption });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send audio to specific chat by ID.
|
||||
* @param {string} chatId
|
||||
* @param {string} filePath
|
||||
*/
|
||||
async sendAudioTo(chatId, filePath) {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return client.sendMessage(chatId, media, { sendAudioAsVoice: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send image to specific chat by ID.
|
||||
* @param {string} chatId
|
||||
* @param {string} filePath
|
||||
* @param {string} [caption]
|
||||
*/
|
||||
async sendImageTo(chatId, filePath, caption = "") {
|
||||
const media = MessageMedia.fromFilePath(filePath);
|
||||
return client.sendMessage(chatId, media, { caption });
|
||||
},
|
||||
|
||||
/**
|
||||
* Send sticker to specific chat by ID.
|
||||
* @param {string} chatId
|
||||
* @param {string | Buffer} source
|
||||
*/
|
||||
async sendStickerTo(chatId, source) {
|
||||
const media = typeof source === "string"
|
||||
? MessageMedia.fromFilePath(source)
|
||||
: new MessageMedia("image/webp", source.toString("base64"));
|
||||
return client.sendMessage(chatId, media, { sendMediaAsSticker: true });
|
||||
},
|
||||
|
||||
// ── Access other plugins ─────────────────────────────────
|
||||
|
||||
/**
|
||||
* Return public API of another plugin (what it exported in `exports`).
|
||||
* Returns null if plugin doesn't exist or is disabled.
|
||||
* @param {string} name — plugin name (folder in /plugins)
|
||||
* @returns {any|null}
|
||||
*/
|
||||
getPlugin(name) {
|
||||
return pluginRegistry.get(name)?.exports ?? null;
|
||||
},
|
||||
|
||||
// ── Logger ───────────────────────────────────────────────
|
||||
|
||||
log: {
|
||||
info: (...a) => logger.info(...a),
|
||||
warn: (...a) => logger.warn(...a),
|
||||
error: (...a) => logger.error(...a),
|
||||
success: (...a) => logger.success(...a),
|
||||
},
|
||||
|
||||
// ── Current chat info ────────────────────────────────────
|
||||
|
||||
chat: {
|
||||
id: currentChat.id._serialized,
|
||||
name: currentChat.name || currentChat.id.user,
|
||||
isGroup: /@g\.us$/.test(currentChat.id._serialized),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* pluginGuard.js
|
||||
*
|
||||
* Runs a plugin safely.
|
||||
* If plugin throws an error:
|
||||
* - Logs error with context
|
||||
* - Marks plugin as "error" in registry
|
||||
* - Never crashes the bot
|
||||
*
|
||||
* Disabled or errored plugins are silently ignored.
|
||||
*/
|
||||
|
||||
import { logger } from "../logger/logger.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
import { pluginRegistry } from "./pluginLoader.js";
|
||||
|
||||
/**
|
||||
* @param {object} plugin — pluginRegistry entry
|
||||
* @param {object} context — { msg, chat, api }
|
||||
*/
|
||||
export async function runPlugin(plugin, context) {
|
||||
if (plugin.status !== "active") return;
|
||||
|
||||
try {
|
||||
await plugin.run(context);
|
||||
} catch (err) {
|
||||
// Disable plugin to prevent further breakage
|
||||
plugin.status = "error";
|
||||
plugin.error = err;
|
||||
pluginRegistry.set(plugin.name, plugin);
|
||||
|
||||
logger.error(
|
||||
t("system.pluginDisabledAfterError", { name: plugin.name, message: err.message }),
|
||||
`\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* pluginLoader.js
|
||||
*
|
||||
* Responsible for:
|
||||
* 1. Reading active plugins from manybot.conf (PLUGINS=[...])
|
||||
* 2. Loading each plugin from /plugins folder
|
||||
* 3. Registering in pluginRegistry with status and public exports
|
||||
* 4. Exposing pluginRegistry to kernel and pluginApi
|
||||
*
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { logger } from "../logger/logger.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
|
||||
const PLUGINS_DIR = path.resolve("src/plugins");
|
||||
|
||||
/**
|
||||
* Each entry in registry:
|
||||
* {
|
||||
* name: string,
|
||||
* status: "active" | "disabled" | "error",
|
||||
* run: async function({ msg, chat, api }) — plugin default function
|
||||
* exports: any — what plugin exposed via `export const api = { ... }`
|
||||
* error: Error | null
|
||||
* }
|
||||
*
|
||||
* @type {Map<string, object>}
|
||||
*/
|
||||
export const pluginRegistry = new Map();
|
||||
|
||||
/**
|
||||
* Load all active plugins listed in `activePlugins`.
|
||||
* Called once during bot initialization.
|
||||
*
|
||||
* @param {string[]} activePlugins — active plugin names (from .conf)
|
||||
*/
|
||||
export async function loadPlugins(activePlugins) {
|
||||
if (!fs.existsSync(PLUGINS_DIR)) {
|
||||
logger.warn(t("system.pluginsFolderNotFound"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const name of activePlugins) {
|
||||
await loadPlugin(name);
|
||||
}
|
||||
|
||||
const total = pluginRegistry.size;
|
||||
const ativos = [...pluginRegistry.values()].filter(p => p.status === "active").length;
|
||||
const erros = total - ativos;
|
||||
|
||||
logger.success(t("system.pluginsLoaded", {
|
||||
count: ativos,
|
||||
errors: erros ? t("system.pluginsLoadedWithErrors", { count: erros }) : ""
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setup(api) on all plugins that export it.
|
||||
* Executed once after bot connects to WhatsApp.
|
||||
*
|
||||
* @param {object} api — api without message context (only sendTo, log, schedule...)
|
||||
*/
|
||||
export async function setupPlugins(api) {
|
||||
for (const plugin of pluginRegistry.values()) {
|
||||
if (plugin.status !== "active" || !plugin.setup) continue;
|
||||
try {
|
||||
await plugin.setup(api);
|
||||
} catch (err) {
|
||||
logger.error(t("system.pluginSetupFailed", { name: plugin.name, message: err.message }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carrega um único plugin pelo nome.
|
||||
* @param {string} name
|
||||
*/
|
||||
async function loadPlugin(name) {
|
||||
const pluginPath = path.join(PLUGINS_DIR, name, "index.js");
|
||||
|
||||
if (!fs.existsSync(pluginPath)) {
|
||||
logger.warn(t("system.pluginNotFound", { name, path: pluginPath }));
|
||||
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const mod = await import(pluginPath);
|
||||
|
||||
// Plugin must export a default function — this is called on every message
|
||||
if (typeof mod.default !== "function") {
|
||||
throw new Error(`Plugin "${name}" does not export a default function`);
|
||||
}
|
||||
|
||||
pluginRegistry.set(name, {
|
||||
name,
|
||||
status: "active",
|
||||
run: mod.default,
|
||||
setup: mod.setup ?? null, // opcional — chamado uma vez na inicialização
|
||||
exports: mod.api ?? null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
logger.info(t("system.pluginLoaded", { name }));
|
||||
} catch (err) {
|
||||
logger.error(t("system.pluginLoadFailed", { name, message: err.message }));
|
||||
pluginRegistry.set(name, { name, status: "error", run: null, exports: null, error: err });
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* pluginState.js
|
||||
*
|
||||
* Tracks plugin execution state per chat.
|
||||
* Used to implement the service vs non-service behavior:
|
||||
* - Services (service: true) can run regardless of state
|
||||
* - Non-services are blocked when another plugin is running in the same chat
|
||||
*/
|
||||
|
||||
import { logger } from "../logger/logger.js";
|
||||
|
||||
/**
|
||||
* Map<chatId, { pluginName: string, startedAt: Date }>
|
||||
* Tracks which plugin is currently "holding the lock" in each chat
|
||||
*/
|
||||
const runningPlugins = new Map();
|
||||
|
||||
/**
|
||||
* Check if any plugin is currently running in a specific chat
|
||||
* @param {string} chatId - Chat ID (serialized)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPluginRunning(chatId) {
|
||||
return runningPlugins.has(chatId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info about the plugin running in a chat
|
||||
* @param {string} chatId - Chat ID (serialized)
|
||||
* @returns {{ pluginName: string, startedAt: Date } | null}
|
||||
*/
|
||||
export function getRunningPlugin(chatId) {
|
||||
return runningPlugins.get(chatId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a plugin as running in a chat
|
||||
* @param {string} chatId - Chat ID (serialized)
|
||||
* @param {string} pluginName - Name of the plugin taking the lock
|
||||
*/
|
||||
export function startPluginRun(chatId, pluginName) {
|
||||
runningPlugins.set(chatId, {
|
||||
pluginName,
|
||||
startedAt: new Date()
|
||||
});
|
||||
logger.debug(`Plugin "${pluginName}" started in chat ${chatId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a plugin as finished in a chat
|
||||
* @param {string} chatId - Chat ID (serialized)
|
||||
* @param {string} pluginName - Name of the plugin releasing the lock
|
||||
*/
|
||||
export function endPluginRun(chatId, pluginName) {
|
||||
const current = runningPlugins.get(chatId);
|
||||
if (current && current.pluginName === pluginName) {
|
||||
runningPlugins.delete(chatId);
|
||||
logger.debug(`Plugin "${pluginName}" ended in chat ${chatId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force clear the running state for a chat
|
||||
* Useful for cleanup or admin commands
|
||||
* @param {string} chatId - Chat ID (serialized)
|
||||
*/
|
||||
export function clearPluginRun(chatId) {
|
||||
runningPlugins.delete(chatId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all chats where a specific plugin is running
|
||||
* @param {string} pluginName - Plugin name
|
||||
* @returns {string[]} Array of chat IDs
|
||||
*/
|
||||
export function getChatsWithPlugin(pluginName) {
|
||||
const chats = [];
|
||||
for (const [chatId, info] of runningPlugins.entries()) {
|
||||
if (info.pluginName === pluginName) {
|
||||
chats.push(chatId);
|
||||
}
|
||||
}
|
||||
return chats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats about running plugins
|
||||
* @returns {{ total: number, byPlugin: Record<string, number> }}
|
||||
*/
|
||||
export function getStats() {
|
||||
const byPlugin = {};
|
||||
for (const info of runningPlugins.values()) {
|
||||
byPlugin[info.pluginName] = (byPlugin[info.pluginName] || 0) + 1;
|
||||
}
|
||||
return {
|
||||
total: runningPlugins.size,
|
||||
byPlugin
|
||||
};
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* scheduler.js
|
||||
*
|
||||
* Allows plugins to register scheduled tasks via cron.
|
||||
* Uses node-cron underneath, but plugins never import node-cron directly —
|
||||
* they only call api.schedule(cron, fn).
|
||||
*
|
||||
* Usage in plugin:
|
||||
* import { schedule } from "many";
|
||||
* schedule("0 9 * * 1", async () => { await api.send("Good morning!"); });
|
||||
*/
|
||||
|
||||
import cron from "node-cron";
|
||||
import { logger } from "../logger/logger.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
|
||||
/** List of active tasks (for eventual teardown) */
|
||||
const tasks = [];
|
||||
|
||||
/**
|
||||
* Register a cron task.
|
||||
* @param {string} expression — cron expression e.g., "0 9 * * 1"
|
||||
* @param {Function} fn — async function to execute
|
||||
* @param {string} pluginName — plugin name (for logging)
|
||||
*/
|
||||
export function schedule(expression, fn, pluginName = "unknown") {
|
||||
if (!cron.validate(expression)) {
|
||||
logger.warn(t("system.schedulerInvalidCron", { name: pluginName, expression }));
|
||||
return;
|
||||
}
|
||||
|
||||
const task = cron.schedule(expression, async () => {
|
||||
try {
|
||||
await fn();
|
||||
} catch (err) {
|
||||
logger.error(t("system.schedulerError", { name: pluginName, message: err.message }));
|
||||
}
|
||||
});
|
||||
|
||||
tasks.push({ pluginName, expression, task });
|
||||
logger.info(t("system.schedulerRegistered", { name: pluginName, expression }));
|
||||
}
|
||||
|
||||
/** Stop all schedules (useful for shutdown) */
|
||||
export function stopAll() {
|
||||
tasks.forEach(({ task }) => task.stop());
|
||||
tasks.length = 0;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"bot": {
|
||||
"starting": "Starting ManyBot...",
|
||||
"initialized": "Client initialized. Waiting for WhatsApp connection...",
|
||||
"ready": "Bot is ready!",
|
||||
"error": {
|
||||
"uncaught": "Uncaught exception",
|
||||
"unhandled": "Unhandled rejection"
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"info": "INFO",
|
||||
"success": "OK",
|
||||
"warn": "WARN",
|
||||
"error": "ERROR",
|
||||
"msg": "MSG",
|
||||
"cmd": "CMD",
|
||||
"done": "DONE",
|
||||
"context": {
|
||||
"group": "group",
|
||||
"from": "From",
|
||||
"type": "Type",
|
||||
"replyTo": "replies to"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"environment": "Environment: {{platform}} — using {{puppeteer}}",
|
||||
"environmentTermux": "Environment: Termux — using system Chromium",
|
||||
"connected": "WhatsApp connected and ready!",
|
||||
"disconnected": "Disconnected — reason: {{reason}}",
|
||||
"reconnecting": "Reconnecting in {{seconds}}s...",
|
||||
"reinitializing": "Reinitializing client...",
|
||||
"qrSaved": "QR Code saved to: {{path}}",
|
||||
"qrOpen": "Open with: termux-open qr.png",
|
||||
"qrSaveFailed": "Failed to save QR Code:",
|
||||
"qrScan": "Scan the QR Code below:",
|
||||
"clientId": "Client ID: {{id}}",
|
||||
"pluginsFolderNotFound": "Plugins folder not found. No plugins loaded.",
|
||||
"pluginsLoaded": "Plugins loaded: {{count}} active{{errors}}",
|
||||
"pluginsLoadedWithErrors": ", {{count}} with error",
|
||||
"pluginSetupFailed": "Plugin \"{{name}}\" setup failed: {{message}}",
|
||||
"pluginNotFound": "Plugin \"{{name}}\" not found at {{path}}",
|
||||
"pluginLoaded": "Plugin loaded: {{name}}",
|
||||
"pluginLoadFailed": "Failed to load plugin \"{{name}}\": {{message}}",
|
||||
"pluginDisabledAfterError": "Plugin \"{{name}}\" disabled after error: {{message}}",
|
||||
"schedulerInvalidCron": "Plugin \"{{name}}\" registered invalid cron expression: \"{{expression}}\"",
|
||||
"schedulerError": "Plugin \"{{name}}\" scheduling error: {{message}}",
|
||||
"schedulerRegistered": "Schedule registered — plugin \"{{name}}\" → \"{{expression}}\"",
|
||||
"downloadJobFailed": "Download job failed — {{message}}"
|
||||
},
|
||||
"errors": {
|
||||
"pluginLoad": "Failed to load plugin",
|
||||
"messageProcess": "Failed to process message",
|
||||
"stack": "Stack"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"bot": {
|
||||
"starting": "Iniciando ManyBot...",
|
||||
"initialized": "Cliente inicializado. Esperando conexión con WhatsApp...",
|
||||
"ready": "¡Bot está listo!",
|
||||
"error": {
|
||||
"uncaught": "Excepción no capturada",
|
||||
"unhandled": "Rechazo no manejado"
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"info": "INFO",
|
||||
"success": "OK",
|
||||
"warn": "WARN",
|
||||
"error": "ERROR",
|
||||
"msg": "MSG",
|
||||
"cmd": "CMD",
|
||||
"done": "DONE",
|
||||
"context": {
|
||||
"group": "grupo",
|
||||
"from": "De",
|
||||
"type": "Tipo",
|
||||
"replyTo": "Responde a"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"environment": "Entorno: {{platform}} — usando {{puppeteer}}",
|
||||
"environmentTermux": "Entorno: Termux — usando Chromium del sistema",
|
||||
"connected": "¡WhatsApp conectado y listo!",
|
||||
"disconnected": "Desconectado — motivo: {{reason}}",
|
||||
"reconnecting": "Reconectando en {{seconds}}s...",
|
||||
"reinitializing": "Reinicializando cliente...",
|
||||
"qrSaved": "Código QR guardado en: {{path}}",
|
||||
"qrOpen": "Abrir con: termux-open qr.png",
|
||||
"qrSaveFailed": "Error al guardar el Código QR:",
|
||||
"qrScan": "Escanea el Código QR abajo:",
|
||||
"clientId": "Client ID: {{id}}",
|
||||
"pluginsFolderNotFound": "Carpeta de plugins no encontrada. Ningún plugin cargado.",
|
||||
"pluginsLoaded": "Plugins cargados: {{count}} activos{{errors}}",
|
||||
"pluginsLoadedWithErrors": ", {{count}} con error",
|
||||
"pluginSetupFailed": "Error en la configuración del plugin \"{{name}}\": {{message}}",
|
||||
"pluginNotFound": "Plugin \"{{name}}\" no encontrado en {{path}}",
|
||||
"pluginLoaded": "Plugin cargado: {{name}}",
|
||||
"pluginLoadFailed": "Error al cargar el plugin \"{{name}}\": {{message}}",
|
||||
"pluginDisabledAfterError": "Plugin \"{{name}}\" desactivado después del error: {{message}}",
|
||||
"schedulerInvalidCron": "Plugin \"{{name}}\" registró expresión cron inválida: \"{{expression}}\"",
|
||||
"schedulerError": "Error en la programación del plugin \"{{name}}\": {{message}}",
|
||||
"schedulerRegistered": "Programación registrada — plugin \"{{name}}\" → \"{{expression}}\"",
|
||||
"downloadJobFailed": "Error en el trabajo de descarga — {{message}}"
|
||||
},
|
||||
"errors": {
|
||||
"pluginLoad": "Error al cargar el plugin",
|
||||
"messageProcess": "Error al procesar el mensaje",
|
||||
"stack": "Stack"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"bot": {
|
||||
"starting": "Iniciando ManyBot...",
|
||||
"initialized": "Cliente inicializado. Aguardando conexão com WhatsApp...",
|
||||
"ready": "Bot está pronto!",
|
||||
"error": {
|
||||
"uncaught": "Exceção não capturada",
|
||||
"unhandled": "Rejeição não tratada"
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"info": "INFO",
|
||||
"success": "OK",
|
||||
"warn": "WARN",
|
||||
"error": "ERRO",
|
||||
"msg": "MSG",
|
||||
"cmd": "CMD",
|
||||
"done": "DONE",
|
||||
"context": {
|
||||
"group": "grupo",
|
||||
"from": "De",
|
||||
"type": "Tipo",
|
||||
"replyTo": "Responde"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"environment": "Ambiente: {{platform}} — usando {{puppeteer}}",
|
||||
"environmentTermux": "Ambiente: Termux — usando Chromium do sistema",
|
||||
"connected": "WhatsApp conectado e pronto!",
|
||||
"disconnected": "Desconectado — motivo: {{reason}}",
|
||||
"reconnecting": "Reconectando em {{seconds}}s...",
|
||||
"reinitializing": "Reinicializando cliente...",
|
||||
"qrSaved": "QR Code salvo em: {{path}}",
|
||||
"qrOpen": "Abra com: termux-open qr.png",
|
||||
"qrSaveFailed": "Falha ao salvar QR Code:",
|
||||
"qrScan": "Escaneie o QR Code abaixo:",
|
||||
"clientId": "Client ID: {{id}}",
|
||||
"pluginsFolderNotFound": "Pasta de plugins não encontrada. Nenhum plugin carregado.",
|
||||
"pluginsLoaded": "Plugins carregados: {{count}} ativos{{errors}}",
|
||||
"pluginsLoadedWithErrors": ", {{count}} com erro",
|
||||
"pluginSetupFailed": "Falha na configuração do plugin \"{{name}}\": {{message}}",
|
||||
"pluginNotFound": "Plugin \"{{name}}\" não encontrado em {{path}}",
|
||||
"pluginLoaded": "Plugin carregado: {{name}}",
|
||||
"pluginLoadFailed": "Falha ao carregar plugin \"{{name}}\": {{message}}",
|
||||
"pluginDisabledAfterError": "Plugin \"{{name}}\" desativado após erro: {{message}}",
|
||||
"schedulerInvalidCron": "Plugin \"{{name}}\" registrou expressão cron inválida: \"{{expression}}\"",
|
||||
"schedulerError": "Erro no agendamento do plugin \"{{name}}\": {{message}}",
|
||||
"schedulerRegistered": "Agendamento registrado — plugin \"{{name}}\" → \"{{expression}}\"",
|
||||
"downloadJobFailed": "Falha no job de download — {{message}}"
|
||||
},
|
||||
"errors": {
|
||||
"pluginLoad": "Falha ao carregar plugin",
|
||||
"messageProcess": "Falha ao processar mensagem",
|
||||
"stack": "Stack"
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// ── ANSI Palette ─────────────────────────────────────────────
|
||||
export const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
|
||||
blue: "\x1b[34m", magenta: "\x1b[35m",
|
||||
};
|
||||
|
||||
export const SEP = `${c.gray}${"─".repeat(52)}${c.reset}`;
|
||||
|
||||
export const now = () =>
|
||||
`[${new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" })}]`;
|
||||
|
||||
export const formatType = (type) => ({
|
||||
sticker: `${c.magenta}sticker${c.reset}`,
|
||||
image: `${c.cyan}imagen${c.reset}`,
|
||||
video: `${c.cyan}video${c.reset}`,
|
||||
audio: `${c.cyan}audio${c.reset}`,
|
||||
ptt: `${c.cyan}audio${c.reset}`,
|
||||
document: `${c.cyan}archivo${c.reset}`,
|
||||
chat: `${c.white}texto${c.reset}`,
|
||||
}[type] ?? `${c.gray}${type}${c.reset}`);
|
||||
|
||||
export const formatContext = (chatName, isGroup) =>
|
||||
isGroup
|
||||
? `${c.bold}${chatName}${c.reset} ${c.dim}(group)${c.reset}`
|
||||
: `${c.bold}${chatName}${c.reset} ${c.dim}(private)${c.reset}`;
|
||||
|
||||
export const formatBody = (body, isCommand) =>
|
||||
body?.trim()
|
||||
? `${isCommand ? c.yellow : c.green}"${body.length > 200 ? body.slice(0, 200) + "..." : body}"${c.reset}`
|
||||
: `${c.dim}<media>${c.reset}`;
|
||||
|
||||
export const formatReply = (quotedName, quotedNumber, quotedPreview) =>
|
||||
`\n${c.gray} ↩ Para: ${c.reset}${c.white}${quotedName}${c.reset} ${c.dim}+${quotedNumber}${c.reset}` +
|
||||
`\n${c.gray} ↩ Msg: ${c.reset}${c.dim}${quotedPreview}${c.reset}`;
|
||||
@@ -1,41 +0,0 @@
|
||||
import {
|
||||
c, now,
|
||||
formatType, formatContext, formatBody, formatReply,
|
||||
} from "./formatter.js";
|
||||
import { t } from "../i18n/index.js";
|
||||
|
||||
/**
|
||||
* ManyBot central logger.
|
||||
* Each method only handles output — no business logic or external I/O.
|
||||
*/
|
||||
export const logger = {
|
||||
info: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.cyan}INFO ${c.reset}`, ...a),
|
||||
success: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.green}OK ${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.yellow}WARN ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.red}ERROR ${c.reset}`, ...a),
|
||||
|
||||
/**
|
||||
* Log a received message from a resolved context.
|
||||
* @param {import("./messageContext.js").MessageContext} ctx
|
||||
*/
|
||||
msg(ctx) {
|
||||
const { chatName, isGroup, senderName, senderNumber, type, body, quoted } = ctx;
|
||||
const context = isGroup ? `${chatName} (${t("log.context.group")})` : chatName;
|
||||
const reply = quoted ? ` → ${t("log.context.replyTo")} ${quoted.name} +${quoted.number}: "${quoted.preview}"` : "";
|
||||
console.log(`\n${c.gray}${now()}${c.reset}${c.cyan}MSG${c.reset} ${context} ${c.gray}— ${t("log.context.from")}:${c.reset} ${c.white}${senderName}${c.reset} ${c.dim}+${senderNumber}${c.reset} ${c.gray}— ${t("log.context.type")}:${c.reset} ${type} — ${c.green}"${body}"${c.reset}${c.gray}${reply}${c.reset}`);
|
||||
},
|
||||
|
||||
cmd: (cmd, extra = "") =>
|
||||
console.log(
|
||||
`${c.gray}${now()}${c.reset}${c.yellow}CMD ${c.reset}` +
|
||||
`${c.bold}${cmd}${c.reset}` +
|
||||
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
||||
),
|
||||
|
||||
done: (cmd, detail = "") =>
|
||||
console.log(
|
||||
`${c.gray}${now()}${c.reset}${c.green}DONE ${c.reset}` +
|
||||
`${c.dim}${cmd}${c.reset}` +
|
||||
(detail ? ` — ${detail}` : "")
|
||||
),
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
import client from "../client/whatsappClient.js";
|
||||
|
||||
/**
|
||||
* Extract clean number from message.
|
||||
* @param {import("whatsapp-web.js").Message} msg
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function getNumber(msg) {
|
||||
if (msg.fromMe) return String(msg.from).split("@")[0];
|
||||
const contact = await msg.getContact();
|
||||
return contact.number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build full message context for logging.
|
||||
* Resolves contact, quoted message and chat metadata.
|
||||
*
|
||||
* @param {import("whatsapp-web.js").Message} msg
|
||||
* @param {import("whatsapp-web.js").Chat} chat
|
||||
* @param {string} botPrefix
|
||||
* @returns {Promise<MessageContext>}
|
||||
*
|
||||
* @typedef {Object} MessageContext
|
||||
* @property {string} chatName
|
||||
* @property {string} chatId
|
||||
* @property {boolean} isGroup
|
||||
* @property {string} senderName
|
||||
* @property {string} senderNumber
|
||||
* @property {string} type
|
||||
* @property {string} body
|
||||
* @property {{ name: string, number: string, preview: string } | null} quoted
|
||||
*/
|
||||
export async function buildMessageContext(msg, chat, botPrefix) {
|
||||
const chatId = chat.id._serialized;
|
||||
const isGroup = /@g\.us$/.test(chatId);
|
||||
const number = await getNumber(msg);
|
||||
const name = msg._data?.notifyName || String(msg.from).replace(/(:\d+)?@.*$/, "");
|
||||
|
||||
const quoted = await resolveQuotedMessage(msg);
|
||||
|
||||
return {
|
||||
chatName: chat.name || chat.id.user,
|
||||
chatId,
|
||||
isGroup,
|
||||
senderName: name,
|
||||
senderNumber: number,
|
||||
type: msg?.type || "text",
|
||||
body: msg.body,
|
||||
quoted,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve quoted message data if exists.
|
||||
* Returns null on error or if not present.
|
||||
*
|
||||
* @param {import("whatsapp-web.js").Message} msg
|
||||
* @returns {Promise<{ name: string, number: string, preview: string } | null>}
|
||||
*/
|
||||
async function resolveQuotedMessage(msg) {
|
||||
if (!msg?.hasQuotedMsg) return null;
|
||||
|
||||
try {
|
||||
const quoted = await msg.getQuotedMessage();
|
||||
const quotedNumber = String(quoted.from).split("@")[0];
|
||||
|
||||
let quotedName = quotedNumber;
|
||||
try {
|
||||
const contact = await client.getContactById(quoted.from);
|
||||
quotedName = contact?.pushname || contact?.formattedName || quotedNumber;
|
||||
} catch { /* contact not found — use number */ }
|
||||
|
||||
const quotedPreview = quoted.body?.trim()
|
||||
? `"${quoted.body.length > 80 ? quoted.body.slice(0, 80) + "…" : quoted.body}"`
|
||||
: `<${quoted.type}>`;
|
||||
|
||||
return { name: quotedName, number: quotedNumber, preview: quotedPreview };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
153
src/main.js
153
src/main.js
@@ -1,47 +1,134 @@
|
||||
/**
|
||||
* main.js
|
||||
*
|
||||
* ManyBot entry point.
|
||||
* Initializes WhatsApp client and loads plugins.
|
||||
*/
|
||||
import client from "./client/whatsappClient.js";
|
||||
import { CHATS, BOT_PREFIX } from "./config.js"; // <- importar PREFIX
|
||||
import { processarComando } from "./commands/index.js";
|
||||
import { coletarMidia } from "./commands/figurinha.js";
|
||||
import { processarJogo } from "./games/adivinhacao.js";
|
||||
import { getChatId } from "./utils/getChatId.js";
|
||||
|
||||
import client from "./client/whatsappClient.js";
|
||||
import { handleMessage } from "./kernel/messageHandler.js";
|
||||
import { loadPlugins, setupPlugins } from "./kernel/pluginLoader.js";
|
||||
import { buildSetupApi } from "./kernel/pluginApi.js";
|
||||
import { logger } from "./logger/logger.js";
|
||||
import { PLUGINS } from "./config.js";
|
||||
import { t } from "./i18n/index.js";
|
||||
// ── Cores ────────────────────────────────────────────────────
|
||||
const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
|
||||
blue: "\x1b[34m", magenta: "\x1b[35m",
|
||||
};
|
||||
|
||||
logger.info(t("bot.starting"));
|
||||
const now = () =>
|
||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" });
|
||||
|
||||
// Global safety net — no error should crash the bot
|
||||
process.on("uncaughtException", (err) => {
|
||||
logger.error(`${t("bot.error.uncaught")} — ${err.message}`, `\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`);
|
||||
});
|
||||
const SEP = `${c.gray}${"─".repeat(52)}${c.reset}`;
|
||||
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
const msg = reason instanceof Error ? reason.message : String(reason);
|
||||
logger.error(`${t("bot.error.unhandled")} — ${msg}`);
|
||||
});
|
||||
// ── Logger ───────────────────────────────────────────────────
|
||||
const logger = {
|
||||
info: (...a) => console.log(`${SEP}\n${c.gray}[${now()}]${c.reset} ${c.cyan}INFO ${c.reset}`, ...a),
|
||||
success: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.green}OK ${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.yellow}WARN ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.red}ERROR ${c.reset}`, ...a),
|
||||
|
||||
// Load plugins before connecting
|
||||
await loadPlugins(PLUGINS);
|
||||
msg: async (chatName, chatId, from, body, msg = {}) => {
|
||||
const number = String(from).split("@")[0];
|
||||
const isGroup = /@g\.us$/.test(chatId);
|
||||
let name = msg?.notifyName || number;
|
||||
|
||||
client.on("message_create", async (msg) => {
|
||||
try {
|
||||
if (typeof client?.getContactById === "function") {
|
||||
const contact = await client.getContactById(from);
|
||||
name = contact?.pushname || contact?.formattedName || name;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const type = msg?.type || "text";
|
||||
const typeLabel =
|
||||
type === "sticker" ? `${c.magenta}sticker${c.reset}` :
|
||||
type === "image" ? `${c.cyan}imagem${c.reset}` :
|
||||
type === "video" ? `${c.cyan}vídeo${c.reset}` :
|
||||
type === "audio" ? `${c.cyan}áudio${c.reset}` :
|
||||
type === "ptt" ? `${c.cyan}áudio${c.reset}` :
|
||||
type === "document" ? `${c.cyan}arquivo${c.reset}` :
|
||||
type === "chat" ? `${c.white}texto${c.reset}` :
|
||||
`${c.gray}${type}${c.reset}`;
|
||||
|
||||
const isCommand = body?.trimStart().startsWith(BOT_PREFIX);
|
||||
|
||||
const context = isGroup
|
||||
? `${c.bold}${chatName}${c.reset} ${c.dim}(grupo)${c.reset}`
|
||||
: `${c.bold}${chatName}${c.reset} ${c.dim}(privado)${c.reset}`;
|
||||
|
||||
const bodyPreview = body?.trim()
|
||||
? `${isCommand ? c.yellow : c.green}"${body.length > 80 ? body.slice(0, 80) + "…" : body}"${c.reset}`
|
||||
: `${c.dim}<${typeLabel}>${c.reset}`;
|
||||
|
||||
// Resolve reply
|
||||
let replyLine = "";
|
||||
if (msg?.hasQuotedMsg) {
|
||||
try {
|
||||
const quoted = await msg.getQuotedMessage();
|
||||
const quotedNumber = String(quoted.from).split("@")[0];
|
||||
let quotedName = quotedNumber;
|
||||
try {
|
||||
const quotedContact = await client.getContactById(quoted.from);
|
||||
quotedName = quotedContact?.pushname || quotedContact?.formattedName || quotedNumber;
|
||||
} catch {}
|
||||
const quotedPreview = quoted.body?.trim()
|
||||
? `"${quoted.body.length > 60 ? quoted.body.slice(0, 60) + "…" : quoted.body}"`
|
||||
: `<${quoted.type}>`;
|
||||
replyLine =
|
||||
`\n${c.gray} ↩ Para: ${c.reset}${c.white}${quotedName}${c.reset} ${c.dim}+${quotedNumber}${c.reset}` +
|
||||
`\n${c.gray} ↩ Msg: ${c.reset}${c.dim}${quotedPreview}${c.reset}`;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${SEP}\n` +
|
||||
`${c.gray}[${now()}]${c.reset} ${c.cyan}MSG ${c.reset}${context}\n` +
|
||||
`${c.gray} De: ${c.reset}${c.white}${name}${c.reset} ${c.dim}+${number}${c.reset}\n` +
|
||||
`${c.gray} Tipo: ${c.reset}${typeLabel}${isCommand ? ` ${c.yellow}(bot)${c.reset}` : ""}\n` +
|
||||
`${c.gray} Text: ${c.reset}${bodyPreview}` +
|
||||
replyLine
|
||||
);
|
||||
},
|
||||
|
||||
cmd: (cmd, extra = "") =>
|
||||
console.log(
|
||||
`${c.gray}[${now()}]${c.reset} ${c.yellow}CMD ${c.reset}` +
|
||||
`${c.bold}${cmd}${c.reset}` +
|
||||
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
||||
),
|
||||
|
||||
done: (cmd, detail = "") =>
|
||||
console.log(
|
||||
`${c.gray}[${now()}]${c.reset} ${c.green}DONE ${c.reset}` +
|
||||
`${c.dim}${cmd}${c.reset}` +
|
||||
(detail ? ` — ${detail}` : "")
|
||||
),
|
||||
};
|
||||
|
||||
export { logger };
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────
|
||||
logger.info("Iniciando ManyBot...");
|
||||
|
||||
client.on("message_create", async msg => {
|
||||
try {
|
||||
await handleMessage(msg);
|
||||
const chat = await msg.getChat();
|
||||
const chatId = getChatId(chat);
|
||||
|
||||
if (!CHATS.includes(chatId)) return;
|
||||
|
||||
await logger.msg(chat.name || chat.id.user, chatId, msg.from, msg.body, msg);
|
||||
|
||||
await coletarMidia(msg);
|
||||
await processarComando(msg, chat, chatId);
|
||||
await processarJogo(msg, chat);
|
||||
|
||||
logger.done("message_create", `de +${String(msg.from).split("@")[0]}`);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`${t("errors.messageProcess")} — ${err.message}`,
|
||||
`\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||
`Falha ao processar — ${err.message}`,
|
||||
`\n Stack: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
client.on("ready", async () => {
|
||||
await setupPlugins(buildSetupApi(client));
|
||||
});
|
||||
client.initialize();
|
||||
console.log("\n");
|
||||
logger.info(t("bot.initialized"));
|
||||
logger.info("Cliente inicializado. Aguardando conexão com WhatsApp...");
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BOT_PREFIX } from "../config.js";
|
||||
|
||||
export function botMsg(text) {
|
||||
return `${BOT_PREFIX}\n${text}`;
|
||||
export function botMsg(texto) {
|
||||
return `${BOT_PREFIX}\n${texto}`;
|
||||
}
|
||||
@@ -1,63 +1,58 @@
|
||||
/**
|
||||
* CLI utility to discover chat/group IDs.
|
||||
* Usage: node src/utils/get_id.js groups|contacts|<name>
|
||||
*/
|
||||
import pkg from "whatsapp-web.js";
|
||||
import qrcode from "qrcode-terminal";
|
||||
import { resolvePuppeteerConfig } from "../client/environment.js";
|
||||
|
||||
const CLIENT_ID="getId"
|
||||
const { Client, LocalAuth } = pkg;
|
||||
|
||||
const arg = process.argv[2];
|
||||
// get_id.js
|
||||
|
||||
const arg = process.argv[2]; // argumento passado no node
|
||||
if (!arg) {
|
||||
console.log("Usage: node get_id.js groups|contacts|<name>");
|
||||
process.exit(0);
|
||||
console.log("Use: node get_id.js grupos|contatos|<nome>");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("[PESQUISANDO] Aguarde...");
|
||||
|
||||
import pkg from 'whatsapp-web.js';
|
||||
const { Client, LocalAuth } = pkg;
|
||||
import qrcode from 'qrcode-terminal';
|
||||
|
||||
const CLIENT_ID = "bot_permanente"; // sempre o mesmo
|
||||
|
||||
const client = new Client({
|
||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
...(resolvePuppeteerConfig().args || [])
|
||||
],
|
||||
...resolvePuppeteerConfig()
|
||||
},
|
||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||
puppeteer: { headless: true }
|
||||
});
|
||||
|
||||
client.on("qr", (qr) => {
|
||||
console.log("[QR] Scan to authenticate:");
|
||||
qrcode.generate(qr, { small: true });
|
||||
client.on('qr', qr => {
|
||||
console.log("[WPP] QR Code gerado. Escaneie apenas uma vez:");
|
||||
qrcode.generate(qr, { small: true });
|
||||
});
|
||||
|
||||
client.on("ready", async () => {
|
||||
console.log("[OK] Connected. Searching chats...\n");
|
||||
client.on('ready', async () => {
|
||||
console.log("[WPP] Conectado");
|
||||
|
||||
const chats = await client.getChats();
|
||||
const search = arg.toLowerCase();
|
||||
const chats = await client.getChats(); // <- precisa do await
|
||||
|
||||
const filtered =
|
||||
search === "groups" ? chats.filter(c => c.isGroup) :
|
||||
search === "contacts" ? chats.filter(c => !c.isGroup) :
|
||||
chats.filter(c => (c.name || c.id.user).toLowerCase().includes(search));
|
||||
let filtered = [];
|
||||
|
||||
if (!filtered.length) {
|
||||
console.log("No results found.");
|
||||
} else {
|
||||
filtered.forEach(c => {
|
||||
console.log("─".repeat(40));
|
||||
console.log("Name: ", c.name || c.id.user);
|
||||
console.log("ID: ", c.id._serialized);
|
||||
console.log("Group: ", c.isGroup);
|
||||
});
|
||||
}
|
||||
if (arg.toLowerCase() === "grupos") {
|
||||
filtered = chats.filter(c => c.isGroup);
|
||||
} else if (arg.toLowerCase() === "contatos") {
|
||||
filtered = chats.filter(c => !c.isGroup);
|
||||
} else {
|
||||
const search = arg.toLowerCase();
|
||||
filtered = chats.filter(c => (c.name || c.id.user).toLowerCase().includes(search));
|
||||
}
|
||||
|
||||
await client.destroy();
|
||||
process.exit(0);
|
||||
if (filtered.length === 0) {
|
||||
console.log("Nenhum chat encontrado com esse filtro.");
|
||||
} else {
|
||||
console.log(`Encontrados ${filtered.length} chats:`);
|
||||
filtered.forEach(c => {
|
||||
console.log("================================");
|
||||
console.log("NAME:", c.name || c.id.user);
|
||||
console.log("ID:", c.id._serialized);
|
||||
console.log("GROUP:", c.isGroup);
|
||||
});
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
client.initialize();
|
||||
@@ -1,129 +0,0 @@
|
||||
/**
|
||||
* src/utils/pluginI18n.js
|
||||
*
|
||||
* Independent i18n system for plugins.
|
||||
* Plugins load their own translations from locale/ folder.
|
||||
* Completely separate from bot core i18n.
|
||||
*
|
||||
* Usage in plugin:
|
||||
* import { createPluginI18n } from "../utils/pluginI18n.js";
|
||||
* const { t } = createPluginI18n(import.meta.url);
|
||||
*
|
||||
* Folder structure:
|
||||
* myPlugin/
|
||||
* index.js
|
||||
* locale/
|
||||
* en.json (required - fallback)
|
||||
* pt.json
|
||||
* es.json
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { LANGUAGE } from "../config.js";
|
||||
|
||||
// Default/fallback language
|
||||
const DEFAULT_LANG = "en";
|
||||
|
||||
/**
|
||||
* Gets a nested value from an object using dot path
|
||||
* @param {object} obj
|
||||
* @param {string} key - path like "error.notFound"
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
function getNestedValue(obj, key) {
|
||||
const parts = key.split(".");
|
||||
let current = obj;
|
||||
|
||||
for (const part of parts) {
|
||||
if (current === null || current === undefined || typeof current !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces placeholders {{key}} with values from context
|
||||
* @param {string} str
|
||||
* @param {object} context
|
||||
* @returns {string}
|
||||
*/
|
||||
function interpolate(str, context = {}) {
|
||||
return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
||||
return context[key] !== undefined ? String(context[key]) : match;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load translations for a plugin
|
||||
* @param {string} localeDir - path to plugin's locale folder
|
||||
* @param {string} lang - target language
|
||||
* @returns {{ translations: object, fallback: object }}
|
||||
*/
|
||||
function loadTranslations(localeDir, lang) {
|
||||
let translations = {};
|
||||
let fallback = {};
|
||||
|
||||
try {
|
||||
const targetPath = path.join(localeDir, `${lang}.json`);
|
||||
if (fs.existsSync(targetPath)) {
|
||||
translations = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
||||
}
|
||||
|
||||
const fallbackPath = path.join(localeDir, `${DEFAULT_LANG}.json`);
|
||||
if (fs.existsSync(fallbackPath)) {
|
||||
fallback = JSON.parse(fs.readFileSync(fallbackPath, "utf8"));
|
||||
}
|
||||
} catch {
|
||||
// Silent fail - plugin may not have translations
|
||||
}
|
||||
|
||||
return { translations, fallback };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an isolated translation function for a plugin.
|
||||
* Language priority: PLUGIN_LANG env var > manybot.conf LANGUAGE > en
|
||||
*
|
||||
* @param {string} pluginMetaUrl - import.meta.url from the plugin
|
||||
* @returns {{ t: Function, lang: string }}
|
||||
*/
|
||||
export function createPluginI18n(pluginMetaUrl) {
|
||||
const pluginDir = path.dirname(fileURLToPath(pluginMetaUrl));
|
||||
const localeDir = path.join(pluginDir, "locale");
|
||||
|
||||
const targetLang =
|
||||
process.env.PLUGIN_LANG?.trim().toLowerCase() ||
|
||||
LANGUAGE?.trim().toLowerCase() ||
|
||||
DEFAULT_LANG;
|
||||
|
||||
const { translations, fallback } = loadTranslations(localeDir, targetLang);
|
||||
|
||||
/**
|
||||
* Translation function
|
||||
* @param {string} key - translation key (e.g., "error.notFound")
|
||||
* @param {object} context - values to interpolate {{key}}
|
||||
* @returns {string}
|
||||
*/
|
||||
function t(key, context = {}) {
|
||||
let value = getNestedValue(translations, key);
|
||||
|
||||
if (value === undefined) {
|
||||
value = getNestedValue(fallback, key);
|
||||
}
|
||||
|
||||
if (value === undefined) return key;
|
||||
|
||||
if (typeof value !== "string") return String(value);
|
||||
|
||||
return interpolate(value, context);
|
||||
}
|
||||
|
||||
return { t, lang: targetLang };
|
||||
}
|
||||
|
||||
export default { createPluginI18n };
|
||||
9
todo.txt
Normal file
9
todo.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Deixar o log mais claro, permanecendo limpo e simples
|
||||
|
||||
Possibilidade de tirar fundo de figurinhas
|
||||
|
||||
Salvar mensagens num banco de dados
|
||||
|
||||
Mudar a licença para GPLv3
|
||||
|
||||
Possibilidade de baixar playlists do youtube
|
||||
141
update
141
update
@@ -1,141 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# update.sh — Script de atualização segura do ManyBot
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Configuração
|
||||
# ------------------------------------------------------------------------------
|
||||
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
tmp_dir="$dir/tmp"
|
||||
log_file="$dir/update.log"
|
||||
config_items=(".wwebjs_auth" ".wwebjs_cache")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Logging
|
||||
# ------------------------------------------------------------------------------
|
||||
log() {
|
||||
local level="$1"; shift
|
||||
local msg="$*"
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "[$timestamp] [$level] $msg" | tee -a "$log_file"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Trap: executa em caso de qualquer erro
|
||||
# ------------------------------------------------------------------------------
|
||||
cleanup_on_error() {
|
||||
local exit_code=$?
|
||||
log "ERROR" "Falha durante a atualização (código: $exit_code)."
|
||||
if [ -d "$tmp_dir" ] && [ "$(ls -A "$tmp_dir" 2>/dev/null)" ]; then
|
||||
log "WARN" "Arquivos de configuração preservados em: $tmp_dir"
|
||||
log "WARN" "Restaure manualmente com: mv \"$tmp_dir\"/* \"$dir\"/"
|
||||
fi
|
||||
exit $exit_code
|
||||
}
|
||||
trap cleanup_on_error ERR
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Validações iniciais
|
||||
# ------------------------------------------------------------------------------
|
||||
log "INFO" "Iniciando atualização do ManyBot..."
|
||||
|
||||
# Verifica dependências obrigatórias
|
||||
for cmd in git npm; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
log "ERROR" "Dependência ausente: '$cmd' não encontrado no PATH."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verifica se está em um repositório git
|
||||
if ! git -C "$dir" rev-parse --is-inside-work-tree &>/dev/null; then
|
||||
log "ERROR" "'$dir' não é um repositório git."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Exibe o remote para transparência
|
||||
remote_url=$(git -C "$dir" remote get-url origin 2>/dev/null || echo "(não definido)")
|
||||
log "INFO" "Remote: $remote_url"
|
||||
|
||||
# Verifica se há alterações locais não commitadas
|
||||
if ! git -C "$dir" diff --quiet || ! git -C "$dir" diff --cached --quiet; then
|
||||
log "WARN" "Existem alterações locais não commitadas. Elas serão descartadas pelo reset."
|
||||
read -r -p "Deseja continuar mesmo assim? [s/N] " confirm
|
||||
confirm="${confirm:-N}"
|
||||
if [[ ! "$confirm" =~ ^[sS]$ ]]; then
|
||||
log "INFO" "Atualização cancelada pelo usuário."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Backup dos arquivos de configuração
|
||||
# ------------------------------------------------------------------------------
|
||||
rm -rf "$tmp_dir"
|
||||
mkdir -p "$tmp_dir"
|
||||
|
||||
backed_up=()
|
||||
for item in "${config_items[@]}"; do
|
||||
src="$dir/$item"
|
||||
if [ -e "$src" ]; then
|
||||
mv "$src" "$tmp_dir/"
|
||||
backed_up+=("$item")
|
||||
log "INFO" "Backup: $item → tmp/"
|
||||
fi
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Atualização do repositório
|
||||
# ------------------------------------------------------------------------------
|
||||
log "INFO" "Buscando atualizações..."
|
||||
git -C "$dir" fetch origin
|
||||
|
||||
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD)
|
||||
log "INFO" "Branch atual: $branch"
|
||||
|
||||
# Registra o hash antes e depois para auditoria
|
||||
hash_before=$(git -C "$dir" rev-parse HEAD)
|
||||
git -C "$dir" reset --hard "origin/$branch"
|
||||
hash_after=$(git -C "$dir" rev-parse HEAD)
|
||||
|
||||
if [ "$hash_before" = "$hash_after" ]; then
|
||||
log "INFO" "Repositório já estava atualizado (sem mudanças)."
|
||||
else
|
||||
log "INFO" "Atualizado: $hash_before → $hash_after"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Instalação de dependências Node
|
||||
# ------------------------------------------------------------------------------
|
||||
log "INFO" "Instalando dependências Node..."
|
||||
npm ci --omit=dev 2>&1 | tee -a "$log_file"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Restauração dos arquivos de configuração
|
||||
# ------------------------------------------------------------------------------
|
||||
if [ ${#backed_up[@]} -gt 0 ]; then
|
||||
for item in "${backed_up[@]}"; do
|
||||
src="$tmp_dir/$item"
|
||||
dst="$dir/$item"
|
||||
if [ -e "$src" ]; then
|
||||
# Remove o que npm possa ter criado (ex: node_modules)
|
||||
rm -rf "$dst"
|
||||
mv "$src" "$dst"
|
||||
log "INFO" "Restaurado: $item"
|
||||
else
|
||||
log "WARN" "Item esperado no backup não encontrado: $item"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Limpa tmp apenas após restauração bem-sucedida
|
||||
rm -rf "$tmp_dir"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Concluído
|
||||
# ------------------------------------------------------------------------------
|
||||
log "INFO" "ManyBot atualizado com sucesso."
|
||||
Reference in New Issue
Block a user