new sonorancad
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -0,0 +1,8 @@
|
||||
.vscode
|
||||
*.zip
|
||||
wk_wars2x/config.lua
|
||||
sonorancad/stream/*.ydr
|
||||
sonorancad/stream/data/*.ytyp
|
||||
tablet/config.lua
|
||||
sonoran_updatehelper/run.lock
|
||||
filestore/**/*
|
||||
@@ -0,0 +1,6 @@
|
||||
[submodule "sonoran_idcard"]
|
||||
path = sonoran_idcard
|
||||
url = https://github.com/Sonoran-Software/id_card_ui.git
|
||||
[submodule "wk_wars2x"]
|
||||
path = wk_wars2x
|
||||
url = https://github.com/Sonoran-Software/wk_wars2x.git
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,20 @@
|
||||
# SonoranCAD FiveM Integration
|
||||
|
||||
The Sonoran CAD FiveM integration core includes many drag-and-drop integration scripts for your gaming server.
|
||||
|
||||
## Installation
|
||||
|
||||
Please view the [installation guide](https://info.sonorancad.com/integration-plugins/in-game-integration/fivem-installation) for more details.
|
||||
|
||||
## Updating from v2.x.x?
|
||||
|
||||
See our documentation [here](https://info.sonorancad.com/integration-plugins/in-game-integration/fivem-installation#update-from-v2.x.x-or-earlier).
|
||||
|
||||
## API Resources
|
||||
|
||||
Sonoran CAD's API offers direct access to your CAD data, making it very useful to use for integration with various scripts. Detailed API and push event information can be found [here](https://info.sonorancad.com/sonoran-cad/api-integration/api-endpoints/).
|
||||
|
||||
## Development Bounties
|
||||
Get PAID to contribute to the open source project!!!
|
||||
|
||||
Sonoran Software offers 💵CASH💵 for contributing to the open source integration framework plugin library. Check out more information [here](https://info.sonorancad.com/sonoran-cad/developer-bounties).
|
||||
@@ -0,0 +1,17 @@
|
||||
description "Simple Notification Script using https://notifyjs.com/"
|
||||
|
||||
ui_page "html/index.html"
|
||||
|
||||
client_script "cl_notify.lua"
|
||||
|
||||
export "SetQueueMax"
|
||||
export "SendNotification"
|
||||
|
||||
files {
|
||||
"html/index.html",
|
||||
"html/pNotify.js",
|
||||
"html/noty.js",
|
||||
"html/noty.css",
|
||||
"html/themes.css",
|
||||
"html/sound-example.wav"
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
--[[
|
||||
Complete List of Options:
|
||||
type
|
||||
layout
|
||||
theme
|
||||
text
|
||||
timeout
|
||||
progressBar
|
||||
closeWith
|
||||
animation = {
|
||||
open
|
||||
close
|
||||
}
|
||||
sounds = {
|
||||
volume
|
||||
conditions
|
||||
sources
|
||||
}
|
||||
docTitle = {
|
||||
conditions
|
||||
}
|
||||
modal
|
||||
id
|
||||
force
|
||||
queue
|
||||
killer
|
||||
container
|
||||
buttons
|
||||
|
||||
More details below or visit the creators website http://ned.im/noty/options.html
|
||||
|
||||
Layouts:
|
||||
top
|
||||
topLeft
|
||||
topCenter
|
||||
topRight
|
||||
center
|
||||
centerLeft
|
||||
centerRight
|
||||
bottom
|
||||
bottomLeft
|
||||
bottomCenter
|
||||
bottomRight
|
||||
|
||||
Types:
|
||||
alert
|
||||
success
|
||||
error
|
||||
warning
|
||||
info
|
||||
|
||||
Themes: -- You can create more themes inside html/themes.css, use the gta theme as a template.
|
||||
gta
|
||||
mint
|
||||
relax
|
||||
metroui
|
||||
|
||||
Animations:
|
||||
open:
|
||||
noty_effects_open
|
||||
gta_effects_open
|
||||
gta_effects_open_left
|
||||
gta_effects_fade_in
|
||||
close:
|
||||
noty_effects_close
|
||||
gta_effects_close
|
||||
gta_effects_close_left
|
||||
gta_effects_fade_out
|
||||
|
||||
closeWith: -- array, You will probably never use this.
|
||||
click
|
||||
button
|
||||
|
||||
sounds:
|
||||
volume: 0.0 - 1.0
|
||||
conditions: -- array
|
||||
docVisible
|
||||
docHidden
|
||||
sources: -- array of sound files
|
||||
|
||||
modal:
|
||||
true
|
||||
false
|
||||
|
||||
force:
|
||||
true
|
||||
false
|
||||
|
||||
queue: -- default is global, you can make it what ever you want though.
|
||||
global
|
||||
|
||||
killer: -- will close all visible notifications and show only this one
|
||||
true
|
||||
false
|
||||
|
||||
visit the creators website http://ned.im/noty/options.html for more information
|
||||
--]]
|
||||
|
||||
function SetQueueMax(queue, max)
|
||||
local tmp = {
|
||||
queue = tostring(queue),
|
||||
max = tonumber(max)
|
||||
}
|
||||
|
||||
SendNUIMessage({maxNotifications = tmp})
|
||||
end
|
||||
|
||||
function SendNotification(options)
|
||||
options.animation = options.animation or {}
|
||||
options.sounds = options.sounds or {}
|
||||
options.docTitle = options.docTitle or {}
|
||||
|
||||
local options = {
|
||||
type = options.type or "success",
|
||||
layout = options.layout or "topRight",
|
||||
theme = options.theme or "gta",
|
||||
text = options.text or "Empty Notification",
|
||||
timeout = options.timeout or 5000,
|
||||
progressBar = options.progressBar ~= false and true or false,
|
||||
closeWith = options.closeWith or {},
|
||||
animation = {
|
||||
open = options.animation.open or "gta_effects_open",
|
||||
close = options.animation.close or "gta_effects_close"
|
||||
},
|
||||
sounds = {
|
||||
volume = options.sounds.volume or 1,
|
||||
conditions = options.sounds.conditions or {},
|
||||
sources = options.sounds.sources or {}
|
||||
},
|
||||
docTitle = {
|
||||
conditions = options.docTitle.conditions or {}
|
||||
},
|
||||
modal = options.modal or false,
|
||||
id = options.id or false,
|
||||
force = options.force or false,
|
||||
queue = options.queue or "global",
|
||||
killer = options.killer or false,
|
||||
container = options.container or false,
|
||||
buttons = options.button or false
|
||||
}
|
||||
|
||||
SendNUIMessage({options = options})
|
||||
end
|
||||
|
||||
RegisterNetEvent("pNotify:SendNotification")
|
||||
AddEventHandler("pNotify:SendNotification", function(options)
|
||||
SendNotification(options)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("pNotify:SetQueueMax")
|
||||
AddEventHandler("pNotify:SetQueueMax", function(queue, max)
|
||||
SetQueueMax(queue, max)
|
||||
end)
|
||||
|
||||
--[[RegisterNetEvent("chatMessage")
|
||||
AddEventHandler("chatMessage", function(author, color, text)
|
||||
TriggerEvent("pNotify:SendNotification", {text = "<span style='font-weight: 900'>" .. text .. "</span>",
|
||||
layout = "centerLeft",
|
||||
timeout = 2000,
|
||||
progressBar = false,
|
||||
type = "error",
|
||||
animation = {
|
||||
open = "gta_effects_fade_in",
|
||||
close = "gta_effects_fade_out"
|
||||
}})
|
||||
end)]]
|
||||
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>pNotify</title>
|
||||
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
|
||||
<script src="pNotify.js" type="text/javascript"></script>
|
||||
<link href="noty.css" rel="stylesheet"></script>
|
||||
<link href="themes.css" rel="stylesheet"></script>
|
||||
<script src="noty.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,650 @@
|
||||
.noty_layout_mixin, #noty_layout__top, #noty_layout__topLeft, #noty_layout__topCenter, #noty_layout__topRight, #noty_layout__bottom, #noty_layout__bottomLeft, #noty_layout__bottomCenter, #noty_layout__bottomRight, #noty_layout__center, #noty_layout__centerLeft, #noty_layout__centerRight {
|
||||
position: fixed;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: 9999999;
|
||||
-webkit-transform: translateZ(0) scale(1, 1);
|
||||
transform: translateZ(0) scale(1, 1);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
filter: blur(0);
|
||||
-webkit-filter: blur(0);
|
||||
max-width: 90%; }
|
||||
|
||||
#noty_layout__top {
|
||||
top: 0;
|
||||
left: 5%;
|
||||
width: 90%; }
|
||||
|
||||
#noty_layout__topLeft {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 325px; }
|
||||
|
||||
#noty_layout__topCenter {
|
||||
top: 5%;
|
||||
left: 50%;
|
||||
width: 325px;
|
||||
-webkit-transform: translate(-webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
|
||||
transform: translate(calc(-50% - .5px)) translateZ(0) scale(1, 1); }
|
||||
|
||||
#noty_layout__topRight {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 325px; }
|
||||
|
||||
#noty_layout__bottom {
|
||||
bottom: 0;
|
||||
left: 5%;
|
||||
width: 90%; }
|
||||
|
||||
#noty_layout__bottomLeft {
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 325px; }
|
||||
|
||||
#noty_layout__bottomCenter {
|
||||
bottom: 5%;
|
||||
left: 50%;
|
||||
width: 325px;
|
||||
-webkit-transform: translate(-webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
|
||||
transform: translate(calc(-50% - .5px)) translateZ(0) scale(1, 1); }
|
||||
|
||||
#noty_layout__bottomRight {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 325px; }
|
||||
|
||||
#noty_layout__center {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 325px;
|
||||
-webkit-transform: translate(-webkit-calc(-50% - .5px), -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
|
||||
transform: translate(calc(-50% - .5px), calc(-50% - .5px)) translateZ(0) scale(1, 1); }
|
||||
|
||||
#noty_layout__centerLeft {
|
||||
top: 50%;
|
||||
left: 20px;
|
||||
width: 325px;
|
||||
-webkit-transform: translate(0, -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
|
||||
transform: translate(0, calc(-50% - .5px)) translateZ(0) scale(1, 1); }
|
||||
|
||||
#noty_layout__centerRight {
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
width: 325px;
|
||||
-webkit-transform: translate(0, -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
|
||||
transform: translate(0, calc(-50% - .5px)) translateZ(0) scale(1, 1); }
|
||||
|
||||
.noty_progressbar {
|
||||
display: none; }
|
||||
|
||||
.noty_has_timeout .noty_progressbar {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
background-color: #646464;
|
||||
opacity: 0.2;
|
||||
filter: alpha(opacity=10); }
|
||||
|
||||
.noty_bar {
|
||||
-webkit-backface-visibility: hidden;
|
||||
-webkit-transform: translate(0, 0) translateZ(0) scale(1, 1);
|
||||
-ms-transform: translate(0, 0) scale(1, 1);
|
||||
transform: translate(0, 0) scale(1, 1);
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
overflow: hidden; }
|
||||
|
||||
.noty_effects_open {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate(50%);
|
||||
-ms-transform: translate(50%);
|
||||
transform: translate(50%);
|
||||
-webkit-animation: noty_anim_in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
animation: noty_anim_in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards; }
|
||||
|
||||
.noty_effects_close {
|
||||
-webkit-animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards; }
|
||||
|
||||
.noty_fix_effects_height {
|
||||
-webkit-animation: noty_anim_height 75ms ease-out;
|
||||
animation: noty_anim_height 75ms ease-out; }
|
||||
|
||||
.noty_close_with_click {
|
||||
cursor: pointer; }
|
||||
|
||||
.noty_close_button {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
font-weight: bold;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all .2s ease-out;
|
||||
transition: all .2s ease-out; }
|
||||
|
||||
.noty_close_button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1); }
|
||||
|
||||
.noty_modal {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
z-index: 10000;
|
||||
opacity: .3;
|
||||
left: 0;
|
||||
top: 0; }
|
||||
|
||||
.noty_modal.noty_modal_open {
|
||||
opacity: 0;
|
||||
-webkit-animation: noty_modal_in .3s ease-out;
|
||||
animation: noty_modal_in .3s ease-out; }
|
||||
|
||||
.noty_modal.noty_modal_close {
|
||||
-webkit-animation: noty_modal_out .3s ease-out;
|
||||
animation: noty_modal_out .3s ease-out;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards; }
|
||||
|
||||
@-webkit-keyframes noty_modal_in {
|
||||
100% {
|
||||
opacity: .3; } }
|
||||
|
||||
@keyframes noty_modal_in {
|
||||
100% {
|
||||
opacity: .3; } }
|
||||
|
||||
@-webkit-keyframes noty_modal_out {
|
||||
100% {
|
||||
opacity: 0; } }
|
||||
|
||||
@keyframes noty_modal_out {
|
||||
100% {
|
||||
opacity: 0; } }
|
||||
|
||||
@keyframes noty_modal_out {
|
||||
100% {
|
||||
opacity: 0; } }
|
||||
|
||||
@-webkit-keyframes noty_anim_in {
|
||||
100% {
|
||||
-webkit-transform: translate(0);
|
||||
transform: translate(0);
|
||||
opacity: 1; } }
|
||||
|
||||
@keyframes noty_anim_in {
|
||||
100% {
|
||||
-webkit-transform: translate(0);
|
||||
transform: translate(0);
|
||||
opacity: 1; } }
|
||||
|
||||
@-webkit-keyframes noty_anim_out {
|
||||
100% {
|
||||
-webkit-transform: translate(50%);
|
||||
transform: translate(50%);
|
||||
opacity: 0; } }
|
||||
|
||||
@keyframes noty_anim_out {
|
||||
100% {
|
||||
-webkit-transform: translate(50%);
|
||||
transform: translate(50%);
|
||||
opacity: 0; } }
|
||||
|
||||
@-webkit-keyframes noty_anim_height {
|
||||
100% {
|
||||
height: 0; } }
|
||||
|
||||
@keyframes noty_anim_height {
|
||||
100% {
|
||||
height: 0; } }
|
||||
|
||||
.noty_theme__relax.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
position: relative; }
|
||||
.noty_theme__relax.noty_bar .noty_body {
|
||||
padding: 10px; }
|
||||
.noty_theme__relax.noty_bar .noty_buttons {
|
||||
border-top: 1px solid #e7e7e7;
|
||||
padding: 5px 10px; }
|
||||
|
||||
.noty_theme__relax.noty_type__alert,
|
||||
.noty_theme__relax.noty_type__notification {
|
||||
background-color: #fff;
|
||||
border: 1px solid #dedede;
|
||||
color: #444; }
|
||||
|
||||
.noty_theme__relax.noty_type__warning {
|
||||
background-color: #FFEAA8;
|
||||
border: 1px solid #FFC237;
|
||||
color: #826200; }
|
||||
.noty_theme__relax.noty_type__warning .noty_buttons {
|
||||
border-color: #dfaa30; }
|
||||
|
||||
.noty_theme__relax.noty_type__error {
|
||||
background-color: #FF8181;
|
||||
border: 1px solid #e25353;
|
||||
color: #FFF; }
|
||||
.noty_theme__relax.noty_type__error .noty_buttons {
|
||||
border-color: darkred; }
|
||||
|
||||
.noty_theme__relax.noty_type__info,
|
||||
.noty_theme__relax.noty_type__information {
|
||||
background-color: #78C5E7;
|
||||
border: 1px solid #3badd6;
|
||||
color: #FFF; }
|
||||
.noty_theme__relax.noty_type__info .noty_buttons,
|
||||
.noty_theme__relax.noty_type__information .noty_buttons {
|
||||
border-color: #0B90C4; }
|
||||
|
||||
.noty_theme__relax.noty_type__success {
|
||||
background-color: #BCF5BC;
|
||||
border: 1px solid #7cdd77;
|
||||
color: darkgreen; }
|
||||
.noty_theme__relax.noty_type__success .noty_buttons {
|
||||
border-color: #50C24E; }
|
||||
|
||||
.noty_theme__metroui.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow: rgba(0, 0, 0, 0.298039) 0 0 5px 0; }
|
||||
.noty_theme__metroui.noty_bar .noty_progressbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
opacity: 0.2;
|
||||
filter: alpha(opacity=20); }
|
||||
.noty_theme__metroui.noty_bar .noty_body {
|
||||
padding: 1.25em;
|
||||
font-size: 14px; }
|
||||
.noty_theme__metroui.noty_bar .noty_buttons {
|
||||
padding: 0 10px .5em 10px; }
|
||||
|
||||
.noty_theme__metroui.noty_type__alert,
|
||||
.noty_theme__metroui.noty_type__notification {
|
||||
background-color: #fff;
|
||||
color: #1d1d1d; }
|
||||
|
||||
.noty_theme__metroui.noty_type__warning {
|
||||
background-color: #FA6800;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__metroui.noty_type__error {
|
||||
background-color: #CE352C;
|
||||
color: #FFF; }
|
||||
|
||||
.noty_theme__metroui.noty_type__info,
|
||||
.noty_theme__metroui.noty_type__information {
|
||||
background-color: #1BA1E2;
|
||||
color: #FFF; }
|
||||
|
||||
.noty_theme__metroui.noty_type__success {
|
||||
background-color: #60A917;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__mint.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
position: relative; }
|
||||
.noty_theme__mint.noty_bar .noty_body {
|
||||
padding: 10px;
|
||||
font-size: 14px; }
|
||||
.noty_theme__mint.noty_bar .noty_buttons {
|
||||
padding: 10px; }
|
||||
|
||||
.noty_theme__mint.noty_type__alert,
|
||||
.noty_theme__mint.noty_type__notification {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #D1D1D1;
|
||||
color: #2F2F2F; }
|
||||
|
||||
.noty_theme__mint.noty_type__warning {
|
||||
background-color: #FFAE42;
|
||||
border-bottom: 1px solid #E89F3C;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__mint.noty_type__error {
|
||||
background-color: #DE636F;
|
||||
border-bottom: 1px solid #CA5A65;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__mint.noty_type__info,
|
||||
.noty_theme__mint.noty_type__information {
|
||||
background-color: #7F7EFF;
|
||||
border-bottom: 1px solid #7473E8;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__mint.noty_type__success {
|
||||
background-color: #AFC765;
|
||||
border-bottom: 1px solid #A0B55C;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__sunset.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
position: relative; }
|
||||
.noty_theme__sunset.noty_bar .noty_body {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); }
|
||||
.noty_theme__sunset.noty_bar .noty_buttons {
|
||||
padding: 10px; }
|
||||
|
||||
.noty_theme__sunset.noty_type__alert,
|
||||
.noty_theme__sunset.noty_type__notification {
|
||||
background-color: #073B4C;
|
||||
color: #fff; }
|
||||
.noty_theme__sunset.noty_type__alert .noty_progressbar,
|
||||
.noty_theme__sunset.noty_type__notification .noty_progressbar {
|
||||
background-color: #fff; }
|
||||
|
||||
.noty_theme__sunset.noty_type__warning {
|
||||
background-color: #FFD166;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__sunset.noty_type__error {
|
||||
background-color: #EF476F;
|
||||
color: #fff; }
|
||||
.noty_theme__sunset.noty_type__error .noty_progressbar {
|
||||
opacity: .4; }
|
||||
|
||||
.noty_theme__sunset.noty_type__info,
|
||||
.noty_theme__sunset.noty_type__information {
|
||||
background-color: #118AB2;
|
||||
color: #fff; }
|
||||
.noty_theme__sunset.noty_type__info .noty_progressbar,
|
||||
.noty_theme__sunset.noty_type__information .noty_progressbar {
|
||||
opacity: .6; }
|
||||
|
||||
.noty_theme__sunset.noty_type__success {
|
||||
background-color: #06D6A0;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__bootstrap-v3.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px; }
|
||||
.noty_theme__bootstrap-v3.noty_bar .noty_body {
|
||||
padding: 15px; }
|
||||
.noty_theme__bootstrap-v3.noty_bar .noty_buttons {
|
||||
padding: 10px; }
|
||||
.noty_theme__bootstrap-v3.noty_bar .noty_close_button {
|
||||
font-size: 21px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #000;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
filter: alpha(opacity=20);
|
||||
opacity: .2;
|
||||
background: transparent; }
|
||||
.noty_theme__bootstrap-v3.noty_bar .noty_close_button:hover {
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5; }
|
||||
|
||||
.noty_theme__bootstrap-v3.noty_type__alert,
|
||||
.noty_theme__bootstrap-v3.noty_type__notification {
|
||||
background-color: #fff;
|
||||
color: inherit; }
|
||||
|
||||
.noty_theme__bootstrap-v3.noty_type__warning {
|
||||
background-color: #fcf8e3;
|
||||
color: #8a6d3b;
|
||||
border-color: #faebcc; }
|
||||
|
||||
.noty_theme__bootstrap-v3.noty_type__error {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
border-color: #ebccd1; }
|
||||
|
||||
.noty_theme__bootstrap-v3.noty_type__info,
|
||||
.noty_theme__bootstrap-v3.noty_type__information {
|
||||
background-color: #d9edf7;
|
||||
color: #31708f;
|
||||
border-color: #bce8f1; }
|
||||
|
||||
.noty_theme__bootstrap-v3.noty_type__success {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
border-color: #d6e9c6; }
|
||||
|
||||
.noty_theme__bootstrap-v4.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
border-radius: .25rem; }
|
||||
.noty_theme__bootstrap-v4.noty_bar .noty_body {
|
||||
padding: .75rem 1.25rem; }
|
||||
.noty_theme__bootstrap-v4.noty_bar .noty_buttons {
|
||||
padding: 10px; }
|
||||
.noty_theme__bootstrap-v4.noty_bar .noty_close_button {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #000;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
filter: alpha(opacity=20);
|
||||
opacity: .5;
|
||||
background: transparent; }
|
||||
.noty_theme__bootstrap-v4.noty_bar .noty_close_button:hover {
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .75; }
|
||||
|
||||
.noty_theme__bootstrap-v4.noty_type__alert,
|
||||
.noty_theme__bootstrap-v4.noty_type__notification {
|
||||
background-color: #fff;
|
||||
color: inherit; }
|
||||
|
||||
.noty_theme__bootstrap-v4.noty_type__warning {
|
||||
background-color: #fcf8e3;
|
||||
color: #8a6d3b;
|
||||
border-color: #faebcc; }
|
||||
|
||||
.noty_theme__bootstrap-v4.noty_type__error {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
border-color: #ebccd1; }
|
||||
|
||||
.noty_theme__bootstrap-v4.noty_type__info,
|
||||
.noty_theme__bootstrap-v4.noty_type__information {
|
||||
background-color: #d9edf7;
|
||||
color: #31708f;
|
||||
border-color: #bce8f1; }
|
||||
|
||||
.noty_theme__bootstrap-v4.noty_type__success {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
border-color: #d6e9c6; }
|
||||
|
||||
.noty_theme__semanticui.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
font-size: 1em;
|
||||
border-radius: .28571429rem;
|
||||
box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.22) inset, 0 0 0 0 transparent; }
|
||||
.noty_theme__semanticui.noty_bar .noty_body {
|
||||
padding: 1em 1.5em;
|
||||
line-height: 1.4285em; }
|
||||
.noty_theme__semanticui.noty_bar .noty_buttons {
|
||||
padding: 10px; }
|
||||
|
||||
.noty_theme__semanticui.noty_type__alert,
|
||||
.noty_theme__semanticui.noty_type__notification {
|
||||
background-color: #f8f8f9;
|
||||
color: rgba(0, 0, 0, 0.87); }
|
||||
|
||||
.noty_theme__semanticui.noty_type__warning {
|
||||
background-color: #fffaf3;
|
||||
color: #573a08;
|
||||
box-shadow: 0 0 0 1px #c9ba9b inset, 0 0 0 0 transparent; }
|
||||
|
||||
.noty_theme__semanticui.noty_type__error {
|
||||
background-color: #fff6f6;
|
||||
color: #9f3a38;
|
||||
box-shadow: 0 0 0 1px #e0b4b4 inset, 0 0 0 0 transparent; }
|
||||
|
||||
.noty_theme__semanticui.noty_type__info,
|
||||
.noty_theme__semanticui.noty_type__information {
|
||||
background-color: #f8ffff;
|
||||
color: #276f86;
|
||||
box-shadow: 0 0 0 1px #a9d5de inset, 0 0 0 0 transparent; }
|
||||
|
||||
.noty_theme__semanticui.noty_type__success {
|
||||
background-color: #fcfff5;
|
||||
color: #2c662d;
|
||||
box-shadow: 0 0 0 1px #a3c293 inset, 0 0 0 0 transparent; }
|
||||
|
||||
.noty_theme__nest.noty_bar {
|
||||
margin: 0 0 15px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
box-shadow: rgba(0, 0, 0, 0.098039) 5px 4px 10px 0; }
|
||||
.noty_theme__nest.noty_bar .noty_body {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); }
|
||||
.noty_theme__nest.noty_bar .noty_buttons {
|
||||
padding: 10px; }
|
||||
|
||||
.noty_layout .noty_theme__nest.noty_bar {
|
||||
z-index: 5; }
|
||||
|
||||
.noty_layout .noty_theme__nest.noty_bar:nth-child(2) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-top: 4px;
|
||||
margin-right: -4px;
|
||||
margin-left: 4px;
|
||||
z-index: 4;
|
||||
width: 100%; }
|
||||
|
||||
.noty_layout .noty_theme__nest.noty_bar:nth-child(3) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-top: 8px;
|
||||
margin-right: -8px;
|
||||
margin-left: 8px;
|
||||
z-index: 3;
|
||||
width: 100%; }
|
||||
|
||||
.noty_layout .noty_theme__nest.noty_bar:nth-child(4) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-top: 12px;
|
||||
margin-right: -12px;
|
||||
margin-left: 12px;
|
||||
z-index: 2;
|
||||
width: 100%; }
|
||||
|
||||
.noty_layout .noty_theme__nest.noty_bar:nth-child(5) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-top: 16px;
|
||||
margin-right: -16px;
|
||||
margin-left: 16px;
|
||||
z-index: 1;
|
||||
width: 100%; }
|
||||
|
||||
.noty_layout .noty_theme__nest.noty_bar:nth-child(n+6) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-top: 20px;
|
||||
margin-right: -20px;
|
||||
margin-left: 20px;
|
||||
z-index: -1;
|
||||
width: 100%; }
|
||||
|
||||
#noty_layout__bottomLeft .noty_theme__nest.noty_bar:nth-child(2),
|
||||
#noty_layout__topLeft .noty_theme__nest.noty_bar:nth-child(2) {
|
||||
margin-top: 4px;
|
||||
margin-left: -4px;
|
||||
margin-right: 4px; }
|
||||
|
||||
#noty_layout__bottomLeft .noty_theme__nest.noty_bar:nth-child(3),
|
||||
#noty_layout__topLeft .noty_theme__nest.noty_bar:nth-child(3) {
|
||||
margin-top: 8px;
|
||||
margin-left: -8px;
|
||||
margin-right: 8px; }
|
||||
|
||||
#noty_layout__bottomLeft .noty_theme__nest.noty_bar:nth-child(4),
|
||||
#noty_layout__topLeft .noty_theme__nest.noty_bar:nth-child(4) {
|
||||
margin-top: 12px;
|
||||
margin-left: -12px;
|
||||
margin-right: 12px; }
|
||||
|
||||
#noty_layout__bottomLeft .noty_theme__nest.noty_bar:nth-child(5),
|
||||
#noty_layout__topLeft .noty_theme__nest.noty_bar:nth-child(5) {
|
||||
margin-top: 16px;
|
||||
margin-left: -16px;
|
||||
margin-right: 16px; }
|
||||
|
||||
#noty_layout__bottomLeft .noty_theme__nest.noty_bar:nth-child(n+6),
|
||||
#noty_layout__topLeft .noty_theme__nest.noty_bar:nth-child(n+6) {
|
||||
margin-top: 20px;
|
||||
margin-left: -20px;
|
||||
margin-right: 20px; }
|
||||
|
||||
.noty_theme__nest.noty_type__alert,
|
||||
.noty_theme__nest.noty_type__notification {
|
||||
background-color: #073B4C;
|
||||
color: #fff; }
|
||||
.noty_theme__nest.noty_type__alert .noty_progressbar,
|
||||
.noty_theme__nest.noty_type__notification .noty_progressbar {
|
||||
background-color: #fff; }
|
||||
|
||||
.noty_theme__nest.noty_type__warning {
|
||||
background-color: #FFD166;
|
||||
color: #fff; }
|
||||
|
||||
.noty_theme__nest.noty_type__error {
|
||||
background-color: #EF476F;
|
||||
color: #fff; }
|
||||
.noty_theme__nest.noty_type__error .noty_progressbar {
|
||||
opacity: .4; }
|
||||
|
||||
.noty_theme__nest.noty_type__info,
|
||||
.noty_theme__nest.noty_type__information {
|
||||
background-color: #118AB2;
|
||||
color: #fff; }
|
||||
.noty_theme__nest.noty_type__info .noty_progressbar,
|
||||
.noty_theme__nest.noty_type__information .noty_progressbar {
|
||||
opacity: .6; }
|
||||
|
||||
.noty_theme__nest.noty_type__success {
|
||||
background-color: #06D6A0;
|
||||
color: #fff; }
|
||||
|
||||
/*# sourceMappingURL=noty.css.map*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) 2012 Nedim Arabacı
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,11 @@
|
||||
$(function(){
|
||||
window.addEventListener("message", function(event){
|
||||
if(event.data.options){
|
||||
var options = event.data.options;
|
||||
new Noty(options).show();
|
||||
}else{
|
||||
var maxNotifications = event.data.maxNotifications;
|
||||
Noty.setMaxVisible(maxNotifications.max, maxNotifications.queue);
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
.noty_theme__gta.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 1px;
|
||||
font-family: sans-serif;
|
||||
position: relative;
|
||||
height: auto;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_bar .noty_body {
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_bar .noty_buttons {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_bar .noty_progressbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_type__alert,
|
||||
.noty_theme__gta.noty_type__notification {
|
||||
background-color: rgb(40, 40, 40);
|
||||
border-top: 2px solid #D1D1D1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_type__warning {
|
||||
background-color: rgb(40, 40, 40);
|
||||
border-top: 2px solid #E89F3C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_type__error {
|
||||
background-color: rgb(40, 40, 40);
|
||||
border-top: 2px solid #CA5A65;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_type__info,
|
||||
.noty_theme__gta.noty_type__information {
|
||||
background-color: rgb(40, 40, 40);
|
||||
border-top: 2px solid #7473E8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.noty_theme__gta.noty_type__success {
|
||||
background-color: rgb(40, 40, 40);
|
||||
border-top: 2px solid #A0B55C;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.gta_effects_open {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate(50%);
|
||||
-ms-transform: translate(50%);
|
||||
transform: translate(50%);
|
||||
-webkit-animation: noty_anim_in 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
animation: noty_anim_in 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.gta_effects_close {
|
||||
-webkit-animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@-webkit-keyframes noty_anim_out_left {
|
||||
100% {
|
||||
-webkit-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
opacity: 0; } }
|
||||
|
||||
@keyframes noty_anim_out_left {
|
||||
100% {
|
||||
-webkit-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
opacity: 0; } }
|
||||
|
||||
.gta_effects_open_left {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate(-50%);
|
||||
-ms-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
-webkit-animation: noty_anim_in 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
animation: noty_anim_in 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.gta_effects_close_left {
|
||||
-webkit-animation: noty_anim_out_left 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
animation: noty_anim_out_left 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
-webkit-animation-fill-mode: backwards;
|
||||
animation-fill-mode: backwards;
|
||||
}
|
||||
|
||||
@-webkit-keyframes noty_anim_fade_in {
|
||||
100% { opacity: 1; } }
|
||||
|
||||
@keyframes noty_anim_fade_in {
|
||||
100% { opacity: 1; } }
|
||||
|
||||
@-webkit-keyframes noty_anim_fade_out {
|
||||
100% { opacity: 0; } }
|
||||
|
||||
@keyframes noty_anim_fade_out {
|
||||
100% { opacity: 0; } }
|
||||
|
||||
.gta_effects_fade_in {
|
||||
opacity: 0;
|
||||
animation: noty_anim_fade_in 0.5s;
|
||||
}
|
||||
|
||||
.gta_effects_fade_out {
|
||||
opacity: 1;
|
||||
animation: noty_anim_fade_out 0.5s;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -0,0 +1,13 @@
|
||||
local time_before_close = 1000000
|
||||
|
||||
RegisterNetEvent("SonoranCAD::civint:DisplayID")
|
||||
AddEventHandler("SonoranCAD::civint:DisplayID", function(imgUrl, target, fullName, dob)
|
||||
SendNUIMessage({
|
||||
type = "SonoranCAD::civint:id",
|
||||
show = true,
|
||||
img = imgUrl,
|
||||
fullName = fullName,
|
||||
dob = dob,
|
||||
playerID = target
|
||||
})
|
||||
end)
|
||||
@@ -0,0 +1,17 @@
|
||||
fx_version 'cerulean'
|
||||
games { 'gta5' }
|
||||
|
||||
description 'UI Resource for SonoranCAD Civilian Integration'
|
||||
version '1.0.0'
|
||||
|
||||
client_script 'cl_main.lua'
|
||||
|
||||
files {
|
||||
'ui/index.html',
|
||||
'ui/img/*.png',
|
||||
'ui/img/*.jpeg',
|
||||
'ui/styles.css',
|
||||
'ui/script.js'
|
||||
}
|
||||
|
||||
ui_page 'ui/index.html'
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Pinyon+Script&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<title>Sonoran ID Card</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.data.type === "SonoranCAD::civint:id") {
|
||||
if (e.data.show) {
|
||||
const fullName = e.data.fullName;
|
||||
const nameArray = fullName.split(" ");
|
||||
const firstName = nameArray[0];
|
||||
const lastName = nameArray[1];
|
||||
const playerID = e.data.playerID;
|
||||
const dob = e.data.dob;
|
||||
|
||||
const html = `
|
||||
<div id="id-card">
|
||||
<div id="top-info">
|
||||
<h2 id="state-name">SAN ANDREAS</h2>
|
||||
<h2 id="top-id">IDENTIFICATION CARD</h2>
|
||||
</div>
|
||||
<div id="info-wrapper">
|
||||
<div id="id-photo">
|
||||
<img
|
||||
src="${e.data.img}"
|
||||
alt="PHOTO"
|
||||
id="id-pic"
|
||||
/>
|
||||
<h2 id="signature">${fullName}</h2>
|
||||
</div>
|
||||
<div id="personal-info">
|
||||
<h2>
|
||||
<span class="info-text">LN</span>
|
||||
<span id="last-name">${lastName}</span>
|
||||
</h2>
|
||||
<h2>
|
||||
<span class="info-text">FN</span>
|
||||
<span id="first-name">${firstName}</span>
|
||||
</h2>
|
||||
<h2>
|
||||
<span class="info-text">DOB</span
|
||||
><span class="number" id="dob"> ${dob}</span>
|
||||
</h2>
|
||||
<h2>
|
||||
<span class="info-text">PLAYER ID</span>
|
||||
<span class="number" id="player-id"> ${playerID}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.querySelector("body").innerHTML = html;
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelector("body").innerHTML = "";
|
||||
}, 6000);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
* {
|
||||
font-family: "Roboto", sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#id-card {
|
||||
background-image: url("img/id_background.jpeg");
|
||||
border-radius: 23px;
|
||||
margin: 5vh;
|
||||
width: 50vh;
|
||||
height: 30vh;
|
||||
}
|
||||
|
||||
#top-info {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
color: #223663;
|
||||
border-bottom: solid 1px #1c2c52;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#state-name,
|
||||
#top-id {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#info-wrapper {
|
||||
display: flex;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
#id-photo {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#id-photo img {
|
||||
min-width: 200px;
|
||||
min-height: 200px;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#signature {
|
||||
text-align: center;
|
||||
font-family: "Pinyon Script", cursive;
|
||||
}
|
||||
|
||||
#personal-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space;
|
||||
}
|
||||
|
||||
#personal-info h2 {
|
||||
padding: 0;
|
||||
margin: 7px 5px;
|
||||
}
|
||||
|
||||
#personal-info .info-text {
|
||||
color: #234eb1;
|
||||
}
|
||||
|
||||
#personal-info .number {
|
||||
color: #df1313;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fx_version 'bodacious'
|
||||
games {'gta5'}
|
||||
server_only 'yes'
|
||||
server_script 's.lua'
|
||||
@@ -0,0 +1,35 @@
|
||||
ManagedResources = { "wk_wars2x", "tablet", "sonorancad"}
|
||||
|
||||
CreateThread(function()
|
||||
file = io.open(GetResourcePath(GetCurrentResourceName()).."/run.lock", "a+")
|
||||
io.input(file)
|
||||
line = io.read()
|
||||
file:close()
|
||||
if line == "core" or line == "plugin" then
|
||||
ExecuteCommand("refresh")
|
||||
Wait(1000)
|
||||
if line == "core" then
|
||||
for k, v in pairs(ManagedResources) do
|
||||
if GetResourceState(v) ~= "started" then
|
||||
print(("Not restarting resource %s as it is not started. This may be fine. State: %s"):format(v, GetResourceState(v)))
|
||||
else
|
||||
ExecuteCommand("restart "..v)
|
||||
Wait(1000)
|
||||
end
|
||||
end
|
||||
elseif line == "plugin" then
|
||||
print("Restarting sonorancad resource for plugin updates...")
|
||||
if GetResourceState("sonorancad") ~= "started" then
|
||||
print(("Not restarting resource %s as it is not in the started state to avoid server crashing. State: %s"):format("sonorancad", GetResourceState("sonorancad")))
|
||||
print("If you are seeing this message, you have started sonoran_updatehelper in your configuration which is incorrect. Please do not start sonoran_updatehelper manually.")
|
||||
return
|
||||
else
|
||||
ExecuteCommand("restart sonorancad")
|
||||
end
|
||||
end
|
||||
else
|
||||
os.remove(GetResourcePath(GetCurrentResourceName()).."/run.lock")
|
||||
print("sonoran_updatehelper is for internal use and should not be started as a resource.")
|
||||
end
|
||||
os.remove(GetResourcePath(GetCurrentResourceName()).."/run.lock")
|
||||
end)
|
||||
@@ -0,0 +1,8 @@
|
||||
config.json
|
||||
update.zip
|
||||
unzip/
|
||||
.vscode
|
||||
.git
|
||||
stream/*
|
||||
!stream/.gitkeep
|
||||
npm-shrinkwrap.json
|
||||
@@ -0,0 +1,39 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "callcommands", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "2.1",
|
||||
-- put your configuration options below
|
||||
callTypes = {
|
||||
{
|
||||
command = "911",
|
||||
isEmergency = true,
|
||||
suggestionText = "Sends a emergency call to your SonoranCAD",
|
||||
descriptionPrefix = ""
|
||||
}, {
|
||||
command = "311",
|
||||
isEmergency = true,
|
||||
suggestionText = "Sends a non-emergency call to your SonoranCAD",
|
||||
descriptionPrefix = "(311)"
|
||||
}, {
|
||||
command = "511",
|
||||
isEmergency = true,
|
||||
suggestionText = "Sends a call for a towing service.",
|
||||
descriptionPrefix = "(511)"
|
||||
}
|
||||
},
|
||||
enablePanic = true,
|
||||
-- adds an emergency call when panic button is pressed
|
||||
addPanicCall = true,
|
||||
|
||||
usePositionForMetadata = false
|
||||
}
|
||||
|
||||
if config.enabled then Config.RegisterPluginConfig(config.pluginName, config) end
|
||||
@@ -0,0 +1,32 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
configVersion = "1.1",
|
||||
pluginName = "civintegration", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
-- time to cache characters in seconds
|
||||
cacheTime = 3600 -- one hour
|
||||
|
||||
-- allow civilians to use /setid and set a custom ID (for characters not registered in the CAD)
|
||||
,allowCustomIds = true
|
||||
|
||||
-- allow players to use /refreshid which causes the next /showid to re-fetch from the CAD. Useful if the player swaps characters.
|
||||
,allowPurge = true
|
||||
|
||||
-- if false, disables the built-in commands of this plugin so it can be used in custom code instead.
|
||||
,enableCommands = true
|
||||
|
||||
-- if true, you must have the sonoran_idcard resource started in your server in order for it to work
|
||||
,enableIDCardUI = true
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,177 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
configVersion = "3.0",
|
||||
pluginName = "dispatchnotify", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
requiresPlugins = {
|
||||
{name = "locations", critical = true},
|
||||
{name = "callcommands", critical = true},
|
||||
{name = "postals", critical = true}
|
||||
}, -- required plugins for this plugin to work, separated by commas
|
||||
|
||||
--[[
|
||||
Enable incoming 911 call notifications
|
||||
]]
|
||||
enableUnitNotify = true,
|
||||
--[[
|
||||
Specifies what emergency calls are displayed as. Some countries use different numbers (like 999)
|
||||
]]
|
||||
emergencyCallType = "911",
|
||||
--[[
|
||||
Specifies non-emergency call types. If unused, set to blank ("")
|
||||
]]
|
||||
civilCallType = "311",
|
||||
--[[
|
||||
Some communities use 511 for tow calls. Specify below, or set blank ("") to disable
|
||||
]]
|
||||
dotCallType = "511",
|
||||
|
||||
--[[
|
||||
Command to respond to calls with
|
||||
]]
|
||||
respondCommandName = "rcall",
|
||||
|
||||
--[[
|
||||
Enable call responding (self-dispatching)
|
||||
|
||||
If disabled, running commandName will return an error to the unit
|
||||
]]
|
||||
enableUnitResponse = true,
|
||||
|
||||
--[[
|
||||
If a dispatcher is detected to be online, automatically disable the response command.
|
||||
]]
|
||||
dispatchDisablesSelfResponse = false,
|
||||
|
||||
--[[
|
||||
Enable "units are on the way" notifications
|
||||
]]
|
||||
enableCallerNotify = true,
|
||||
--[[
|
||||
notifyMethod: how should the caller be notified?
|
||||
none: disable notification
|
||||
chat: Sends a message in chat
|
||||
pnotify: Uses pNotify to show a notification
|
||||
custom: Use the custom event instead (see docs)
|
||||
]]
|
||||
callerNotifyMethod = "chat",
|
||||
--[[
|
||||
notifyMessage: Message template to use when sending to the player
|
||||
|
||||
You can use the following replacements:
|
||||
{officer} - officer name
|
||||
]]
|
||||
notifyMessage = "Officer {officer} is responding to your call!",
|
||||
|
||||
--[[
|
||||
unitNotifyMethod: how should units be notified?
|
||||
none: disable notification
|
||||
chat: Sends a message in chat
|
||||
pnotify: Uses pNotify to show a notification
|
||||
custom: Use the custom event instead (see docs)
|
||||
]]
|
||||
unitNotifyMethod = "chat",
|
||||
--[[
|
||||
incomingCallMessage: how should officers be notified of a new 911 call?
|
||||
|
||||
Parameters:
|
||||
{location} - location of call (street + postal)
|
||||
{description} - description as given by civilian
|
||||
{caller} - caller's name
|
||||
{callId} - ID of the call so LEO can respond with /r911 <id>
|
||||
{command} - The command to use
|
||||
|
||||
Note: pNotify uses HTML (commented below), chat uses special codes.
|
||||
]]
|
||||
-- incomingCallMessage = "<b>Incoming Call!</b><br/>Location: {location}<br/>Description: {description}<br/>Use command /r911 <b>{callId}</b> to respond!",
|
||||
incomingCallMessage = "Incoming call from ^*{caller}^r! Location: ^3{location}^0 Description: ^3{description}^0 - Use /{command} ^*{callId}^r to respond!",
|
||||
|
||||
--[[
|
||||
unitDutyMethod: How to detect if units are online?
|
||||
incad: units must be logged into the CAD
|
||||
permissions: units must have the "sonorancad.dispatchnotify" ACE permission (see docs)
|
||||
esxjob: requires esxsupport plugin, use jobs instead for on duty detection
|
||||
custom: Use custom function (defined below as unitDutyCustom)
|
||||
]]
|
||||
unitDutyMethod = "incad",
|
||||
|
||||
--[[
|
||||
esxJobsAllowed: What jobs should count as being on duty?
|
||||
]]
|
||||
esxJobsAllowed = {["police"] = true, ["ambulance"] = true, ["fire"] = true},
|
||||
|
||||
--[[
|
||||
waypointType: Type of waypoint to use when officer is attached
|
||||
postal: set gps to caller's postal (less accurate, more realistic) - REQUIRES CONFIGURED POSTAL PLUGIN
|
||||
exact: set gps to caller's position (less realistic)
|
||||
none: disable waypointing
|
||||
]]
|
||||
waypointType = "postal",
|
||||
|
||||
--[[
|
||||
waypointFallbackEnabled: Fall back to postal if exact coordinates cannot be found (for self-generated calls)
|
||||
]]
|
||||
waypointFallbackEnabled = true,
|
||||
--[[
|
||||
callTitle: Customize the title of a call made
|
||||
]]
|
||||
callTitle = "OFFICER RESPONSE",
|
||||
--[[
|
||||
sendNotesToUnits: Whether the script will fire events related to call notes.
|
||||
]]
|
||||
sendNotesToUnits = true,
|
||||
--[[
|
||||
noteNotifyMethod:
|
||||
chat: send new notes via chat
|
||||
pnotify: send new notes via a pNotify popup (requires pNotify resource)
|
||||
custom: fire a client-side event that your script will consume (each active unit gets SonoranCAD::dispatchnotify:NewCallNote with an object containing callId and note)
|
||||
]]
|
||||
noteNotifyMethod = "chat",
|
||||
--[[
|
||||
noteMessage: Message to send to officers when a note is added, using the placeholders:
|
||||
{callid} - the call ID
|
||||
{note} - the note added
|
||||
]]
|
||||
noteMessage = "New note added for call ^*{callid}^r: {note}",
|
||||
--[[
|
||||
enableAddNote: Whether or not to enable the addnote command, allowing units attached to calls to add notes to their call.
|
||||
]]
|
||||
enableAddNote = true,
|
||||
--[[
|
||||
addNoteCommand: The command to create for adding notes.
|
||||
]]
|
||||
addNoteCommand = "addnote",
|
||||
--[[
|
||||
enableAddPlate: Enable the addplate command, allowing units to send locked plate data as a note to their current call. Will require the wraithv2 plugin to work.
|
||||
]]
|
||||
enableAddPlate = true,
|
||||
--[[
|
||||
addPlateCommand: The command to create for sending plate data
|
||||
]]
|
||||
addPlateCommand = "addplate",
|
||||
|
||||
--[[
|
||||
onSceneHandler: Enables automatically disabling waypointing when marked on scene
|
||||
]]
|
||||
onSceneHandler = true,
|
||||
|
||||
--[[
|
||||
onSceneIndex: Usually don't have to touch this. Controls which button is "on scene"
|
||||
]]
|
||||
onSceneIndex = 4
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
|
||||
function unitDutyCustom(player) return false end
|
||||
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
--[[
|
||||
SonoranCAD FivePD Plugin
|
||||
Plugin Configuration
|
||||
]]
|
||||
local config = {
|
||||
enabled = false,
|
||||
configVersion = "1.0",
|
||||
pluginName = "fivepd", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
|
||||
-- put your configuration options below
|
||||
origin = 1, -- Call Origin: 0=CALLER/1=RADIO DISPATCH/2=OBSERVED/3=WALK_UP
|
||||
status = 1, -- Call Status: 0=PENDING/1=ACTIVE/2=CLOSED
|
||||
code = "" -- Not Used Yet: TODO: Map Callout Id to Code
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
configVersion = "1.1",
|
||||
pluginName = "forcereg", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
requiresPlugins = {}, -- required plugins for this plugin to work, separated by commas
|
||||
|
||||
--[[
|
||||
Below defines the "captive" option to use:
|
||||
|
||||
Nag: Simply nags the user with a big notification across the top of their screen.
|
||||
Freeze: Freezes the player at their spawn point with a big notification.
|
||||
Whitelist: Prevents connection to the server entirely via deferrals (WARNING: NOT COMPATIBLE WITH ADAPTIVE CARD RESOURCES)
|
||||
]]
|
||||
captiveOption = "Nag",
|
||||
|
||||
-- If using Nag, should the text be centered in the users screen or at the top? ('Center' or 'Top')
|
||||
nagDrawTextLocation = "Top",
|
||||
|
||||
-- What message to show with the above options? Nag, Freeze, and Captive can use colors.
|
||||
captiveMessage = "You must ~r~register~s~ with our CAD before playing! Visit ~r~http://yourwebsite.here~s~ to do so.",
|
||||
|
||||
-- What message to show the /verifycad command? This displays under the notice.
|
||||
verifyMessage = "Type ~r~/verifycad~s~ in chat when finished.",
|
||||
|
||||
-- What does the user do once they log in?
|
||||
instructionalMessage = "Head over to settings once logged in, and enter the ~g~API ID~w~ given below in the API ID field.",
|
||||
|
||||
-- Would you like to only show this message to players who are whitelisted?
|
||||
whitelist = {
|
||||
enabled = false,
|
||||
mode = "qb-core", -- qb-core, esx, ace
|
||||
aces = { -- ace permissions will see the message
|
||||
"forcereg.whitelist"
|
||||
},
|
||||
jobs = { -- QB or ESX jobs will see the message
|
||||
"police"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
--[[
|
||||
|
||||
Sonoran Plugins
|
||||
|
||||
frameworksupport Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
|
||||
]]
|
||||
local config = {
|
||||
enabled = false,
|
||||
configVersion = "1.2",
|
||||
pluginName = "frameworksupport", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
requiresPlugins = {}, -- required plugins for this plugin to work, separated by commas
|
||||
|
||||
-- Newer ESX versions use license instead of steam for identity, specify the other below if different
|
||||
identityType = "license",
|
||||
-- Some ESX versions don't use the prefix (such as license:abcdef), set to false to disable the prefix
|
||||
usePrefix = true,
|
||||
-- If you are using QBCore set this to true
|
||||
usingQBCore = true,
|
||||
-- If using qb-management for LEO set this to true
|
||||
usingQBManagement = false,
|
||||
-- Setup the qb-management account names dependent on department issuing fine
|
||||
qbManagementAccountNames = {
|
||||
['LSPD'] = 'police',
|
||||
['SAHP'] = 'sahp'
|
||||
-- ['DEPARTMENT ABBREVIATION IN CAD ADMIN>CUSTOMIZATION>DEPARTMENTS'] = 'qb-management_account_name'
|
||||
},
|
||||
qbNotifyFinedPlayer = true,
|
||||
-- Placeholders $AMOUNT and $OFFICER_NAME where $AMOUNT is the fine total and $OFFICER_NAME is the Unit Name of the officer issuing the fine
|
||||
qbFineMessage = "You have been fined $$AMOUNT by $OFFICER_NAME",
|
||||
|
||||
-- Fine payment system
|
||||
issueFines = true, -- Use the fine system
|
||||
fineNotify = false, -- Send a message in chat when someone is fined.
|
||||
fineableForms = {"Arrest Report", "General Citation"}, -- List of form names that should issue fines (Don't Include Warrants or Bolos)
|
||||
|
||||
-- ESX Legacy Support (Created for and tested using ESX v1.1.0 esx_identity v1.0.2)
|
||||
legacyESX = false -- Set to true if default settings do not get character name properly (older esx_identity/ESX legacy versions)
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "kick", -- name your plugin here
|
||||
pluginAuthor = "TaylorMade#4860", -- author
|
||||
configVersion = "1.0", -- version of the plugin
|
||||
requiresPlugins = {}, -- required plugins for this plugin to work, separated by commas
|
||||
|
||||
-- put your configuration options below
|
||||
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "locations", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "1.1", -- version of your plugin
|
||||
requiresPlugins = {},
|
||||
-- put your configuration options below
|
||||
checkTime = 5000, -- how frequently to send locations to the server
|
||||
prefixPostal = true -- prefix postal code on locations sent, requires postal plugin
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "lookups", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "1.0",
|
||||
requiresPlugins = {}, -- required plugins for this plugin to work, separated by commas
|
||||
|
||||
-- put your configuration options below
|
||||
maxCacheTime = 120, -- max time to cache a plate hit, in seconds
|
||||
stalePurgeTimer = 600, -- delay between garbage collection, default 10 minutes
|
||||
autoLookupEnabled = true
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "postals", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "1.3.0",
|
||||
requiresPlugins = {{name = "locations", critical = true}},
|
||||
-- put your configuration options below
|
||||
sendTimer = 950, -- how often to send postal to client
|
||||
shouldSendPostalData = true, -- toggles this plugin on/off
|
||||
|
||||
nearestPostalResourceName = "nearest-postal", -- if using nearestpostal, specify the name of the resource here if you changed it
|
||||
-- optionally use an event fired by another resource, set mode to "event" and add the name of the event below, set mode to "file" if you are using a custom postal file
|
||||
mode = "resource",
|
||||
nearestPostalEvent = "",
|
||||
|
||||
-- if not using nearest-postal, place a json file containing the postals in the plugin's folder and specify a name below
|
||||
customPostalCodesFile = ""
|
||||
}
|
||||
|
||||
if config.enabled then Config.RegisterPluginConfig(config.pluginName, config) end
|
||||
@@ -0,0 +1,23 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
configVersion = "1.0",
|
||||
pluginName = "sonrad", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
requiresPlugins = {},
|
||||
-- put your configuration options below
|
||||
|
||||
-- Should radio panics generate CAD calls?
|
||||
addPanicCall = true
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "trafficstop", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
requiresPlugins = {
|
||||
{name = "locations", critical = true},
|
||||
{name = "callcommands", critical = true},
|
||||
{name = "postals", critical = false}
|
||||
}, -- required plugins for this plugin to work, separated by commas
|
||||
configVersion = "1.2.0",
|
||||
|
||||
-- put your configuration options below
|
||||
origin = 2, -- 0 = CALLER / 1 = RADIO DISPATCH / 2 = OBSERVED / 3 = WALK_UP
|
||||
status = 1, -- 0 = PENDING / 1 = ACTIVE / 2 = CLOSED
|
||||
priority = 1, -- 1, 2, or 3
|
||||
title = "Traffic Stop", -- This is the title of the call by default it is sent as "Traffic Stop"
|
||||
code = "10-11 - Traffic Stop", -- Change this to reflect your communities 10 Code for a Traffic Stop
|
||||
trafficCommand = "ts", -- command to trigger the traffic stop
|
||||
usePermissions = true -- if true, user will need the permission "command.ts" to run the command.
|
||||
}
|
||||
|
||||
if config.enabled then Config.RegisterPluginConfig(config.pluginName, config) end
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"note": "If you do not wish to set the ts3server variable here you may leave these blank and utilize convars in your server.cfg instead",
|
||||
"note2": "Convars are as follows: sonorants3_server_host, sonorants3_server_port, sonorants3_server_qport, sonorants3_server_user, sonorants3_server_pass",
|
||||
"ts3server_host": "127.0.0.1",
|
||||
"ts3server_port": 9987,
|
||||
"ts3server_qport": 10011,
|
||||
"ts3server_user": "",
|
||||
"ts3server_pass": "",
|
||||
"onduty_servergroup": "On Duty",
|
||||
"enforced_channels": ["test", "example"],
|
||||
"logoutGraceTime": 5000,
|
||||
"loginGraceTime": 5000
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
This plugin has no configuration. It only exists to add the plugin to the loaded list.
|
||||
]]
|
||||
|
||||
local config = {
|
||||
enabled = false,
|
||||
pluginName = "ts3integration", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "1.0",
|
||||
requiresPlugins = {}, -- required plugins for this plugin to work, separated by commas
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "unitstatus", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "1.0",
|
||||
requiresPlugins = {}, -- required plugins for this plugin to work, separated by commas
|
||||
setStatusCommand = "setstatus", -- user command for setting their own status, leave blank to not use
|
||||
-- put your configuration options below
|
||||
statusCodes = {
|
||||
["UNAVAILABLE"] = 0,
|
||||
["BUSY"] = 1,
|
||||
["AVAILABLE"] = 2,
|
||||
["ENROUTE"] = 3,
|
||||
["ON_SCENE"] = 4
|
||||
},
|
||||
enableAceCheck = true -- restrict command via ace permission
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
Plugin Configuration
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "vehreg", -- name your plugin here
|
||||
pluginAuthor = "Jordan.#2139", -- author
|
||||
configVersion = "1.2",
|
||||
|
||||
reigsterCommand = "reg", -- Command to register car
|
||||
defaultRegExpire = '01/02/2030', -- The default date that all registrations will expire
|
||||
defaultRegStatus = 'VALID', -- The default status that all registrations will have | MUST BE IN CAPS
|
||||
|
||||
language = {
|
||||
notInVeh = "Player Not In Vehicle... Please Ensure You're In A Vehicle And Try Again!",
|
||||
noApiId = "API ID NOT LINKED TO AN ACCOUNT IN THIS COMMUNITY",
|
||||
plateAlrRegisted = "This plate has already been registered to another person",
|
||||
helpMsg = 'Register your current vehicle in CAD',
|
||||
noCharFound = "No character found. Please ensure you are logged in to a character.",
|
||||
incompleteCharData = "Character data is incomplete. Please ensure you have all required data filled out in CAD. Unable to register vehicle.",
|
||||
--[[
|
||||
Placeholders:
|
||||
{{PLATE}} = The plate of the vehicle
|
||||
{{FIRST}} = The first name of the charactes currently active in CAD
|
||||
{{LAST}} = The first name of the charactes currently active in CAD
|
||||
]]
|
||||
successReg = "Vehicle ({{PLATE}}) successfully registered to ^2{{FIRST}} {{LAST}}"
|
||||
},
|
||||
-- If you have changed your field UID's in CAD please update the corresponding values here. (Field UID goes in the quotes on the right)
|
||||
recordData = {
|
||||
colorUid = "color",
|
||||
plateUid = "plate",
|
||||
typeUid = "type",
|
||||
modelUid = "model",
|
||||
statusUid = "status",
|
||||
expiresUid = "_imtoih149",
|
||||
}
|
||||
}
|
||||
|
||||
if config.enabled then
|
||||
Config.RegisterPluginConfig(config.pluginName, config)
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
--[[
|
||||
Sonoran Plugins
|
||||
|
||||
Plugin Configuration
|
||||
|
||||
Put all needed configuration in this file.
|
||||
]]
|
||||
local config = {
|
||||
enabled = true,
|
||||
pluginName = "wraithv2", -- name your plugin here
|
||||
pluginAuthor = "SonoranCAD", -- author
|
||||
configVersion = "1.6",
|
||||
requiresPlugins = {{name = "lookups", critical = true}}, -- required plugins for this plugin to work, separated by commas
|
||||
-- use vehicle registration expirations, or not
|
||||
useExpires = true,
|
||||
-- use middle initials?
|
||||
useMiddleInitial = true,
|
||||
-- alert if no registration was found on scan?
|
||||
alertNoRegistration = true,
|
||||
-- if your custom vehicle record is different, change the below
|
||||
statusUid = "status",
|
||||
expiresUid = "expiration",
|
||||
-- statuses to flag on when scanned
|
||||
flagOnStatuses = {"STOLEN", "EXPIRED", "PENDING", "SUSPENDED"}
|
||||
-- Vehicle classes that will NOT get ran through CAD | Classes: https://docs.fivem.net/natives/?_0x29439776AAA00A62
|
||||
,vehTypeFilter = { 13, 14, 15, 16, 21, 22 }
|
||||
,notificationTimers = {
|
||||
validReg = 30000, -- 30 seconds for valid registration
|
||||
warrant = 20000, -- 20 seconds for warrant
|
||||
bolo = 20000, -- 20 seconds for bolo
|
||||
noReg = 5000, -- 5 seconds for no registration
|
||||
}
|
||||
}
|
||||
|
||||
if config.enabled then Config.RegisterPluginConfig(config.pluginName, config) end
|
||||
@@ -0,0 +1,108 @@
|
||||
registerApiType("CHECK_APIID", "general")
|
||||
|
||||
function cadApiIdExists(apiId, callback)
|
||||
if apiId == "" or apiId == nil then
|
||||
debugLog("cadApiIdExists: No API ID specified, assuming false.")
|
||||
callback(false)
|
||||
else
|
||||
performApiRequest({{["apiId"] = apiId}}, "CHECK_APIID", function(res, exists)
|
||||
callback(exists)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
RegisterCommand("forcecheck", function(source, args, rawCommand)
|
||||
performApiRequest({{["apiId"] = args[1]}}, "CHECK_APIID", function(res, exists)
|
||||
print("exists: "..tostring(exists))
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterServerEvent("SonoranCAD::apicheck:CheckPlayerLinked")
|
||||
AddEventHandler("SonoranCAD::apicheck:CheckPlayerLinked", function(player)
|
||||
local identifier = GetIdentifiers(player)[Config.primaryIdentifier]
|
||||
cadApiIdExists(identifier, function(exists)
|
||||
TriggerEvent("SonoranCAD::apicheck:CheckPlayerLinkedResponse", player, identifier, exists)
|
||||
end)
|
||||
end)
|
||||
|
||||
exports('CadIsPlayerLinked', cadApiIdExists)
|
||||
|
||||
RegisterCommand("apiid", function(source, args, rawCommand)
|
||||
local identifiers = GetIdentifiers(source)
|
||||
local pid = nil
|
||||
if isPluginLoaded("esxsupport") then
|
||||
local type = Config.plugins["esxsupport"].identityType
|
||||
if identifiers[type] ~= nil then
|
||||
if Config.plugins["esxsupport"].usePrefix then
|
||||
pid = ("%s:%s"):format(type, identifiers[type])
|
||||
else
|
||||
pid = identifiers[type]
|
||||
end
|
||||
end
|
||||
elseif isPluginLoaded("frameworksupport") then
|
||||
local type = Config.plugins["frameworksupport"].identityType
|
||||
if identifiers[type] ~= nil then
|
||||
if Config.plugins["frameworksupport"].usePrefix then
|
||||
pid = ("%s:%s"):format(type, identifiers[type])
|
||||
else
|
||||
pid = identifiers[type]
|
||||
end
|
||||
end
|
||||
else
|
||||
if identifiers[Config.primaryIdentifier] ~= nil then
|
||||
pid = identifiers[Config.primaryIdentifier]
|
||||
end
|
||||
end
|
||||
if pid ~= nil then
|
||||
print("Your API ID: "..tostring(pid))
|
||||
else
|
||||
print("API ID not found")
|
||||
end
|
||||
end)
|
||||
|
||||
if Config.forceSetApiId == nil then Config.forceSetApiId = false end
|
||||
|
||||
if Config.forceSetApiId then
|
||||
debugLog("forceSetApiId enabled")
|
||||
RegisterNetEvent("sonoran:tablet:forceCheckApiId")
|
||||
AddEventHandler("sonoran:tablet:forceCheckApiId", function()
|
||||
local identifier=GetIdentifiers(source)[Config.primaryIdentifier]
|
||||
local plid=source
|
||||
|
||||
cadApiIdExists(identifier, function(exists)
|
||||
if not exists then
|
||||
TriggerClientEvent("sonoran:tablet:apiIdNotFound", plid)
|
||||
else
|
||||
TriggerClientEvent("sonoran:tablet:apiIdFound", plid)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sonoran:tablet:setApiId")
|
||||
AddEventHandler("sonoran:tablet:setApiId", function(session,username)
|
||||
local identifier=GetIdentifiers(source)[Config.primaryIdentifier]
|
||||
local source = source
|
||||
cadApiIdExists(identifier, function(exists)
|
||||
if not exists then
|
||||
|
||||
registerApiType("SET_API_ID", "general")
|
||||
|
||||
local data = {{
|
||||
["apiIds"] = { identifier },
|
||||
["sessionId"] = session,
|
||||
["username"] = username
|
||||
}}
|
||||
|
||||
performApiRequest(data, "SET_API_ID", function(res, flag)
|
||||
if (not flag) then
|
||||
TriggerClientEvent("sonoran:tablet:failed", source, res)
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
end)
|
||||
|
||||
end
|
||||
@@ -0,0 +1,439 @@
|
||||
Config = {plugins = {}}
|
||||
Plugins = {}
|
||||
|
||||
bodyCamOn = false;
|
||||
bodyCamFrequency = 2000;
|
||||
local bodyCamConfigReady = false;
|
||||
|
||||
Config.RegisterPluginConfig = function(pluginName, configs)
|
||||
Config.plugins[pluginName] = {}
|
||||
for k, v in pairs(configs) do Config.plugins[pluginName][k] = v end
|
||||
table.insert(Plugins, pluginName)
|
||||
end
|
||||
|
||||
--[[
|
||||
@function getApiMode
|
||||
@description Returns the API mode for the current server. 0 = Development, 1 = Production
|
||||
@returns int
|
||||
]]
|
||||
function getApiMode()
|
||||
if Config.mode == nil then
|
||||
return 1
|
||||
elseif Config.mode == 'development' then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
exports('getApiMode', getApiMode)
|
||||
|
||||
Config.GetPluginConfig = function(pluginName)
|
||||
local correctConfig = nil
|
||||
if Config.plugins[pluginName] ~= nil then
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
elseif Config.plugins[pluginName].enabled == false then
|
||||
Config.plugins[pluginName].disableReason = 'Disabled'
|
||||
end
|
||||
return Config.plugins[pluginName]
|
||||
else
|
||||
if pluginName == 'apicheck' or pluginName == 'livemap' or pluginName ==
|
||||
'smartsigns' then
|
||||
return {enabled = false, disableReason = 'deprecated plugin'}
|
||||
end
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.lua')
|
||||
if not correctConfig then
|
||||
warnLog(
|
||||
('Plugin %s is missing critical configuration. Please check our plugin install guide at https://info.sonorancad.com/integration-submodules/integration-submodules/plugin-installation for steps to properly install.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
else
|
||||
local configChunk = correctConfig:match("local config = {.-\n}") ..
|
||||
"\nreturn config"
|
||||
if not configChunk then
|
||||
errorLog("No config table found in the string.")
|
||||
end
|
||||
local tempEnv = {}
|
||||
setmetatable(tempEnv, {__index = _G}) -- Allow access to global functions if needed
|
||||
local loadedPlugin, pluginError =
|
||||
load(configChunk, 'config', 't', tempEnv)
|
||||
if loadedPlugin then
|
||||
-- Execute and capture the returned config table
|
||||
local success, res = pcall(loadedPlugin)
|
||||
if not success then
|
||||
errorLog(
|
||||
('Plugin %s failed to load due to error: %s'):format(
|
||||
pluginName, res))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
if res and type(res) == "table" then
|
||||
-- Assign the extracted config to Config.plugins[pluginName]
|
||||
Config.plugins[pluginName] = res
|
||||
else
|
||||
-- Handle case where config is not available
|
||||
errorLog(
|
||||
('Plugin %s did not define a valid config table.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
end
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
elseif Config.plugins[pluginName].enabled == false then
|
||||
Config.plugins[pluginName].disableReason = 'Disabled'
|
||||
end
|
||||
else
|
||||
errorLog(('Plugin %s failed to load due to error: %s'):format(
|
||||
pluginName, pluginError))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
return Config.plugins[pluginName]
|
||||
end
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Missing configuration file'}
|
||||
end
|
||||
end
|
||||
|
||||
Config.LoadPlugin = function(pluginName, cb)
|
||||
local correctConfig = nil
|
||||
while Config.apiVersion == -1 do Wait(1) end
|
||||
if Config.plugins[pluginName] ~= nil then
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
elseif Config.plugins[pluginName].enabled == false then
|
||||
Config.plugins[pluginName].disableReason = 'Disabled'
|
||||
end
|
||||
return cb(Config.plugins[pluginName])
|
||||
else
|
||||
if pluginName == 'yourpluginname' then
|
||||
return cb({enabled = false, disableReason = 'Template plugin'})
|
||||
end
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.lua')
|
||||
if not correctConfig then
|
||||
warnLog(
|
||||
('Submodule %s is missing critical configuration. Please check our submodule install guide at https://info.sonorancad.com/integration-plugins/in-game-integration/fivem-installation/submodule-configuration#activating-a-submodule for steps to properly install.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
else
|
||||
local configChunk = correctConfig:match("local config = {.-\n}") ..
|
||||
"\nreturn config"
|
||||
if not configChunk then
|
||||
errorLog("No config table found in the string.")
|
||||
end
|
||||
local tempEnv = {}
|
||||
setmetatable(tempEnv, {__index = _G}) -- Allow access to global functions if needed
|
||||
local loadedPlugin, pluginError =
|
||||
load(configChunk, 'config', 't', tempEnv)
|
||||
if loadedPlugin then
|
||||
-- Execute and capture the returned config table
|
||||
local success, res = pcall(loadedPlugin)
|
||||
if not success then
|
||||
errorLog(
|
||||
('Submodule %s failed to load due to error: %s'):format(
|
||||
pluginName, res))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
if res and type(res) == "table" then
|
||||
-- Assign the extracted config to Config.plugins[pluginName]
|
||||
Config.plugins[pluginName] = res
|
||||
else
|
||||
-- Handle case where config is not available
|
||||
errorLog(
|
||||
('Submodule %s did not define a valid config table.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
end
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
elseif Config.plugins[pluginName].enabled == false then
|
||||
Config.plugins[pluginName].disableReason = 'Disabled'
|
||||
end
|
||||
else
|
||||
errorLog(('Submodule %s failed to load due to error: %s'):format(
|
||||
pluginName, pluginError))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
return Config.plugins[pluginName]
|
||||
end
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
return cb({
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
while not NetworkIsPlayerActive(PlayerId()) do Wait(1) end
|
||||
TriggerServerEvent('SonoranCAD::core:sendClientConfig')
|
||||
end)
|
||||
|
||||
RegisterNetEvent('SonoranCAD::core:recvClientConfig')
|
||||
AddEventHandler('SonoranCAD::core:recvClientConfig', function(config)
|
||||
for k, v in pairs(config) do Config[k] = v end
|
||||
Config.inited = true
|
||||
debugLog('Configuration received')
|
||||
debugLog('Bodycam config ready')
|
||||
end)
|
||||
|
||||
--[[
|
||||
SonoranCAD Bodycam Callback if unit is not found in CAD
|
||||
]]
|
||||
RegisterNetEvent('SonoranCAD::core::ScreenshotOff', function()
|
||||
if Config.bodycamEnabled then
|
||||
bodyCamOn = false
|
||||
if Config.bodycamOverlayEnabled then
|
||||
SendNUIMessage({type = 'toggleGif'})
|
||||
end
|
||||
TriggerEvent('chat:addMessage', {
|
||||
args = {
|
||||
'Sonoran Bodycam',
|
||||
'Bodycam disabled - You must be in CAD to enable bodycam'
|
||||
}
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('SonoranCAD::Core::InitBodycam', function(isReady, apiVersion)
|
||||
if isReady == 0 then
|
||||
CreateThread(function()
|
||||
-- still waiting, request again in 10s
|
||||
debugLog('Bodycam not ready, retrying in 10s')
|
||||
Wait(10000)
|
||||
TriggerServerEvent('SonoranCAD::Core::RequestBodycam')
|
||||
end)
|
||||
return
|
||||
end
|
||||
if apiVersion ~= -1 then
|
||||
Config.apiVersion = apiVersion
|
||||
end
|
||||
if Config.bodycamEnabled then
|
||||
print('Bodycam init')
|
||||
-- Command to toggle bodycam on and off
|
||||
RegisterCommand(Config.bodycamCommandToggle,
|
||||
function(source, args, rawCommand)
|
||||
if Config.apiVersion < 4 then
|
||||
errorLog('Bodycam is only enabled with Sonoran CAD Pro.')
|
||||
TriggerEvent('chat:addMessage', {
|
||||
args = {
|
||||
'Sonoran Bodycam',
|
||||
'Bodycam is only enabled with Sonoran CAD Pro.'
|
||||
}
|
||||
})
|
||||
return
|
||||
end
|
||||
if bodyCamOn then
|
||||
bodyCamOn = false
|
||||
TriggerServerEvent('SonoranCAD::core::bodyCamOff')
|
||||
TriggerEvent('chat:addMessage',
|
||||
{args = {'Sonoran Bodycam', 'Bodycam disabled.'}})
|
||||
if Config.bodycamOverlayEnabled then
|
||||
SendNUIMessage({
|
||||
type = 'toggleGif',
|
||||
location = Config.bodycamOverlayLocation
|
||||
})
|
||||
end
|
||||
else
|
||||
bodyCamOn = true
|
||||
TriggerEvent('chat:addMessage',
|
||||
{args = {'Sonoran Bodycam', 'Bodycam enabled.'}})
|
||||
if Config.bodycamOverlayEnabled then
|
||||
SendNUIMessage({
|
||||
type = 'toggleGif',
|
||||
location = Config.bodycamOverlayLocation
|
||||
})
|
||||
end
|
||||
end
|
||||
end, false)
|
||||
-- Command to change the frequency of bodycam screenshots
|
||||
RegisterCommand(Config.bodycamCommandChangeFrequncy,
|
||||
function(source, args, rawCommand)
|
||||
if Config.apiVersion < 4 then
|
||||
errorLog('Bodycam is only enabled with Sonoran CAD Pro.')
|
||||
TriggerEvent('chat:addMessage', {
|
||||
args = {
|
||||
'Sonoran Bodycam',
|
||||
'Bodycam is only enabled with Sonoran CAD Pro.'
|
||||
}
|
||||
})
|
||||
return
|
||||
end
|
||||
if args[1] then
|
||||
args[1] = tonumber(args[1])
|
||||
if not args[1] or args[1] <= 0 or args[1] > 10 then
|
||||
errorLog(
|
||||
'Frequency must a number greater than 0 and less than than 10 seconds.')
|
||||
TriggerEvent('chat:addMessage', {
|
||||
args = {
|
||||
'Sonoran Bodycam',
|
||||
'Frequency must a number greater than 0 and less than than 10 seconds.'
|
||||
}
|
||||
})
|
||||
return
|
||||
end
|
||||
bodyCamFrequency = (tonumber(args[1]) * 1000)
|
||||
TriggerEvent('chat:addMessage', {
|
||||
args = {
|
||||
'Sonoran Bodycam',
|
||||
('Frequency set to %s.'):format(
|
||||
(bodyCamFrequency / 1000))
|
||||
}
|
||||
})
|
||||
else
|
||||
TriggerEvent('chat:addMessage', {
|
||||
args = {
|
||||
'Sonoran Bodycam',
|
||||
('Current bodycam frequency is %s.'):format(
|
||||
(bodyCamFrequency / 1000))
|
||||
}
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
-- Add suggestions to the chat
|
||||
TriggerEvent('chat:addSuggestion', '/' .. Config.bodycamCommandToggle,
|
||||
'Enable or disable bodycam mode.')
|
||||
TriggerEvent('chat:addSuggestion',
|
||||
'/' .. Config.bodycamCommandChangeFrequncy,
|
||||
'Change the frequency of bodycam screenshots.',
|
||||
{{name = 'frequency', help = 'Frequency in seconds.'}})
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
while not Config.inited do Wait(10) end
|
||||
if Config.devHiddenSwitch then
|
||||
debugLog('Spawned discord thread')
|
||||
SetDiscordAppId(867548404724531210)
|
||||
SetDiscordRichPresenceAsset('icon')
|
||||
SetDiscordRichPresenceAssetSmall('icon')
|
||||
while true do
|
||||
SetRichPresence('Developing SonoranCAD!')
|
||||
Wait(5000)
|
||||
SetRichPresence('sonorancad.com')
|
||||
Wait(5000)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local inited = false
|
||||
AddEventHandler('playerSpawned', function()
|
||||
TriggerServerEvent('SonoranCAD::core:PlayerReady')
|
||||
inited = true
|
||||
TriggerServerEvent('SonoranCAD::Core::RequestBodycam')
|
||||
end)
|
||||
|
||||
AddEventHandler('onClientResourceStart', function(resourceName) --When resource starts, stop the GUI showing.
|
||||
if(GetCurrentResourceName() ~= resourceName) then
|
||||
return
|
||||
end
|
||||
Wait(10000)
|
||||
if not inited then
|
||||
TriggerServerEvent('SonoranCAD::core:PlayerReady')
|
||||
inited = true
|
||||
TriggerServerEvent('SonoranCAD::Core::RequestBodycam')
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('SonoranCAD::core:debugModeToggle')
|
||||
AddEventHandler('SonoranCAD::core:debugModeToggle',
|
||||
function(toggle) Config.debugMode = toggle end)
|
||||
|
||||
RegisterNetEvent('SonoranCAD::core:AddPlayer')
|
||||
RegisterNetEvent('SonoranCAD::core:RemovePlayer')
|
||||
|
||||
--[[
|
||||
SonoranCAD Bodycam Plugin
|
||||
]]
|
||||
|
||||
-- Main bodycam loops
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1)
|
||||
if bodyCamOn then
|
||||
TriggerServerEvent('SonoranCAD::core:TakeScreenshot')
|
||||
Wait(bodyCamFrequency)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1)
|
||||
if Config.bodycamPlayBeeps then
|
||||
if bodyCamOn then
|
||||
SendNUIMessage({
|
||||
type = 'playSound',
|
||||
transactionFile = 'sounds/beeps.mp3',
|
||||
transactionVolume = 0.3
|
||||
})
|
||||
Wait(Config.bodycamBeepFrequency)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 813 KiB |
@@ -0,0 +1,84 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="nui://game/ui/jquery.js" type="text/javascript"></script>
|
||||
<script src="js/http.js" tyle="text/javascript"></script>
|
||||
</head>
|
||||
<body style="display: none">
|
||||
<script>
|
||||
// CRED: https://stackoverflow.com/questions/6150289/how-can-i-convert-an-image-into-base64-string-using-javascript/20285053#20285053
|
||||
function toDataUrl(url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = function () {
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
callback(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(xhr.response);
|
||||
};
|
||||
xhr.open("GET", url);
|
||||
xhr.responseType = "blob";
|
||||
xhr.send();
|
||||
}
|
||||
var audioPlayer = null;
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event.data.type === "convert_base64") {
|
||||
toDataUrl(event.data.img, function (base64) {
|
||||
fetch(`https://${GetParentResourceName()}/base64`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
||||
body: JSON.stringify({
|
||||
base64: base64,
|
||||
handle: event.data.handle,
|
||||
id: event.data.id,
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.1.1/howler.min.js" type="text/javascript"></script>
|
||||
<script>
|
||||
var audioPlayer = null;
|
||||
var showGif = false;
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event.data.type == "playSound") {
|
||||
if (audioPlayer != null) {
|
||||
audioPlayer.pause();
|
||||
}
|
||||
audioPlayer = new Howl({
|
||||
src: [event.data.transactionFile],
|
||||
});
|
||||
audioPlayer.volume(event.data.transactionVolume);
|
||||
audioPlayer.play();
|
||||
}
|
||||
if (event.data.type == "toggleGif") {
|
||||
showGif = !showGif;
|
||||
if (showGif) {
|
||||
document.body.style.display = "block";
|
||||
switch (event.data.location) {
|
||||
case "top-left":
|
||||
document.getElementById("overlay").style.top = "0";
|
||||
document.getElementById("overlay").style.left = "0";
|
||||
break;
|
||||
case "top-right":
|
||||
document.getElementById("overlay").style.top = "0";
|
||||
document.getElementById("overlay").style.right = "0";
|
||||
break;
|
||||
case "bottom-left":
|
||||
document.getElementById("overlay").style.bottom = "0";
|
||||
document.getElementById("overlay").style.left = "0";
|
||||
break;
|
||||
case "bottom-right":
|
||||
document.getElementById("overlay").style.bottom = "0";
|
||||
document.getElementById("overlay").style.right = "0";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
document.body.style.display = "none";
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<img src="./img/logo.gif" alt="header" style="height: 6vh; position: absolute" id="overlay" />
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
$(function () {
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.data.type == "light_event") {
|
||||
$.post("http://localhost:" + event.data.port + "/lighting", JSON.stringify({ state: event.data.event }))
|
||||
}
|
||||
});
|
||||
});
|
||||
Binary file not shown.
@@ -0,0 +1,245 @@
|
||||
--[[
|
||||
SonoranCAD FiveM Integration
|
||||
|
||||
Commands Module
|
||||
|
||||
Provides /sonoran command for console control
|
||||
]]
|
||||
|
||||
--[[ /sonoran
|
||||
debugmode - old caddebug toggle
|
||||
info - dump version info, configuration
|
||||
support - dump useful data for support staff
|
||||
verify - run hash checks to confirm all files are untampered
|
||||
plugin <name> - show info about a plugin (config)
|
||||
update - attempt to auto-update
|
||||
]]
|
||||
|
||||
registerApiType("UPLOAD_LOGS", "support")
|
||||
|
||||
function dumpInfo()
|
||||
local version = GetResourceMetadata(GetCurrentResourceName(), "version", 0)
|
||||
local pluginList, loadedPlugins, disabledPlugins = GetPluginLists()
|
||||
local pluginVersions = {}
|
||||
local cadVariables = { ["netPort"] = GetConvar("netPort", "Unknown")}
|
||||
local variableList = ""
|
||||
for k, v in pairs(cadVariables) do
|
||||
variableList = ("%s%s = %s\n"):format(variableList, k, v)
|
||||
end
|
||||
for k, v in pairs(pluginList) do
|
||||
if Config.plugins[v] then
|
||||
table.insert(pluginVersions, ("%s [%s/%s]"):format(v, Config.plugins[v].version, Config.plugins[v].latestVersion))
|
||||
end
|
||||
end
|
||||
local coreConfig = {}
|
||||
for k, v in pairs(Config) do
|
||||
if (k == "plugins") then goto continue end
|
||||
if type(v) == "function" then goto continue end
|
||||
if type(v) == "table" then
|
||||
table.insert(coreConfig, ("%s = %s"):format(k, json.encode(v)))
|
||||
goto continue
|
||||
end
|
||||
if type(v) == "thread" then goto continue end
|
||||
table.insert(coreConfig, ("%s = %s"):format(k, v))
|
||||
coreConfig[k] = v
|
||||
::continue::
|
||||
end
|
||||
return ([[
|
||||
SonoranCAD
|
||||
Version: %s - Latest: %s
|
||||
FXS Version: %s
|
||||
Available Submodules
|
||||
%s
|
||||
Loaded Submodules
|
||||
%s
|
||||
Disabled Submodules
|
||||
%s
|
||||
Relevant Variables
|
||||
%s
|
||||
Core Configuration
|
||||
%s
|
||||
]]):format(version, Config.latestVersion, getServerVersion(), table.concat(pluginVersions, ", "), table.concat(loadedPlugins, ", "), table.concat(disabledPlugins, ", "), variableList, table.concat(coreConfig, "\n"))
|
||||
end
|
||||
|
||||
function dumpPlugin(name)
|
||||
local pluginDetail = {}
|
||||
if not Config.plugins[name] then
|
||||
print("Bad plugin: "..name)
|
||||
return nil
|
||||
end
|
||||
for k, v in pairs(Config.plugins[name]) do
|
||||
table.insert(pluginDetail, ("%s = %s"):format(k, v))
|
||||
end
|
||||
return ([[
|
||||
Plugin: %s
|
||||
Version: %s
|
||||
Configuration:
|
||||
%s
|
||||
]]):format(name, Config.plugins[name].version, table.concat(pluginDetail, "\n "))
|
||||
end
|
||||
|
||||
local function sendSupportLogs(key)
|
||||
infoLog("Please wait, gathering required data...")
|
||||
local cadOutput = {}
|
||||
cadOutput.key = tonumber(key)
|
||||
if cadOutput.key == nil then
|
||||
errorLog("Invalid support key.")
|
||||
return
|
||||
end
|
||||
local plugins = {}
|
||||
for name, config in pairs(Config.plugins) do
|
||||
pluginData = {}
|
||||
pluginData.name = name
|
||||
pluginData.version = config.version
|
||||
pluginData.config = config
|
||||
table.insert(plugins, pluginData)
|
||||
end
|
||||
cadOutput.plugins = plugins
|
||||
cadOutput.logs = ([[
|
||||
SonoranCAD Support Output
|
||||
---------------------------------------
|
||||
Configuration Information
|
||||
---
|
||||
%s
|
||||
|
||||
---------------------------------------
|
||||
Console Buffer
|
||||
------
|
||||
%s
|
||||
---------------------------------------
|
||||
Last 50 Debug Messages
|
||||
----------------------
|
||||
%s
|
||||
]]):format(dumpInfo(), GetConsoleBuffer(), table.concat(getDebugBuffer(), "\n"))
|
||||
Config.debugMode = false
|
||||
performApiRequest({cadOutput}, "UPLOAD_LOGS", function(data)
|
||||
if data == "LOGS UPDATED" then
|
||||
infoLog("Support logs have been successfully uploaded. Debug mode was disabled during the upload.")
|
||||
else
|
||||
errorLog(("Failed to upload support logs: %s"):format(data))
|
||||
end
|
||||
end)
|
||||
end
|
||||
RegisterCommand("sonoran", function(source, args, rawCommand)
|
||||
if source ~= 0 then
|
||||
print("Console only command")
|
||||
return
|
||||
end
|
||||
if not args[1] then
|
||||
print("Missing command. Try \"sonoran help\" for help.")
|
||||
return
|
||||
end
|
||||
if args[1] == "help" then
|
||||
print([[
|
||||
SonoranCAD Help
|
||||
debugmode - Toggles debugging mode
|
||||
info - dump version info, configuration
|
||||
support - dump useful data for support staff
|
||||
errors - display all error/warning messages since last startup
|
||||
plugin <name> - show info about a plugin (config)
|
||||
update - Run core updater
|
||||
pluginupdate - Run plugin updater
|
||||
viewcaches - View the current unit and call cache, for troubleshooting
|
||||
getclientlog <playerId> - Get a log buffer from a given client
|
||||
dumpconsole - Dumps current console buffer to file
|
||||
]])
|
||||
elseif args[1] == "debugmode" then
|
||||
Config.debugMode = not Config.debugMode
|
||||
local convarString = ""
|
||||
if Config.debugMode then
|
||||
convarString = "true"
|
||||
else
|
||||
convarString = "false"
|
||||
end
|
||||
SetConvar("sonoran_debugMode", convarString)
|
||||
infoLog(("Debug mode toggled to %s"):format(convarString))
|
||||
TriggerClientEvent("SonoranCAD::core:debugModeToggle", -1, Config.debugMode)
|
||||
elseif args[1] == "info" then
|
||||
print(dumpInfo())
|
||||
elseif args[1] == "support" and args[2] ~= nil then
|
||||
sendSupportLogs(args[2])
|
||||
elseif args[1] == "plugin" and args[2] then
|
||||
if Config.plugins[args[2]] then
|
||||
print(dumpPlugin(args[2]))
|
||||
else
|
||||
errorLog("Invalid plugin")
|
||||
end
|
||||
elseif args[1] == "update" then --update - attempt to auto-update
|
||||
infoLog("Checking for core update...")
|
||||
RunAutoUpdater(true)
|
||||
elseif args[1] == "dumpconsole" then
|
||||
local savePath = GetResourcePath(GetCurrentResourceName()).."/buffer.log"
|
||||
local f = assert(io.open(savePath, 'wb'))
|
||||
f:write(GetConsoleBuffer())
|
||||
f:close()
|
||||
infoLog("Wrote buffer to "..savePath)
|
||||
elseif args[1] == "pluginupdate" then
|
||||
infoLog("Scanning for plugin updates...")
|
||||
for k, v in pairs(Config.plugins) do
|
||||
CheckForPluginUpdate(k, true)
|
||||
end
|
||||
elseif args[1] == "viewcaches" then
|
||||
local units = GetUnitCache()
|
||||
local calls = GetCallCache()
|
||||
print(("Units: %s\r\nCalls: %s"):format(json.encode(units), json.encode(calls)))
|
||||
print("Done")
|
||||
elseif args[1] == "getclientlog" then
|
||||
if args[2] then
|
||||
if GetPlayerName(args[2]) ~= nil then
|
||||
TriggerClientEvent("SonoranCAD::core:RequestLogBuffer", args[2])
|
||||
infoLog("Requested log buffer. Please wait...")
|
||||
else
|
||||
errorLog("Invalid player ID")
|
||||
end
|
||||
else
|
||||
errorLog("Invalid argument.")
|
||||
end
|
||||
elseif args[1] == "errors" then
|
||||
print("----ERROR/WARNING BUFFER START----")
|
||||
local buf = getErrorBuffer()
|
||||
for i=1, #buf do
|
||||
print(buf[i])
|
||||
end
|
||||
print("----ERROR/WARNING BUFFER END----")
|
||||
else
|
||||
print("Missing command. Try \"sonoran help\" for help.")
|
||||
end
|
||||
end, true)
|
||||
|
||||
function GetPluginLists()
|
||||
local pluginList = {}
|
||||
local loadedPlugins = {}
|
||||
local disabledPlugins = {}
|
||||
local disableFormatted = {}
|
||||
for name, v in pairs(Config.plugins) do
|
||||
table.insert(pluginList, name)
|
||||
if v.enabled then
|
||||
table.insert(loadedPlugins, name)
|
||||
else
|
||||
if v.disableReason == nil then
|
||||
v.disableReason = "disabled in config"
|
||||
end
|
||||
disabledPlugins[name] = v.disableReason
|
||||
end
|
||||
end
|
||||
for name, reason in pairs(disabledPlugins) do
|
||||
table.insert(disableFormatted, ("%s (%s)"):format(name, reason))
|
||||
end
|
||||
return pluginList, loadedPlugins, disableFormatted
|
||||
end
|
||||
|
||||
-- Support Push Event
|
||||
|
||||
AddEventHandler("SonoranCAD::pushevents:SendSupportLogs", function(key)
|
||||
infoLog("Support has requested logs to be uploaded. Collecting now...")
|
||||
sendSupportLogs(key)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("SonoranCAD::core:LogBuffer")
|
||||
AddEventHandler("SonoranCAD::core:LogBuffer", function(buffer)
|
||||
infoLog(("Incoming log buffer from player %s"):format(source))
|
||||
for i=1, #buffer do
|
||||
print((": %s"):format(buffer[i]))
|
||||
end
|
||||
infoLog("End of buffer")
|
||||
end)
|
||||
@@ -0,0 +1,571 @@
|
||||
Config = {
|
||||
communityID = nil,
|
||||
apiKey = nil,
|
||||
apiUrl = nil,
|
||||
postTime = nil,
|
||||
serverId = nil,
|
||||
primaryIdentifier = nil,
|
||||
apiSendEnabled = nil,
|
||||
debugMode = nil,
|
||||
updateBranch = nil,
|
||||
enableCanary = false,
|
||||
latestVersion = '',
|
||||
apiVersion = -1,
|
||||
plugins = {},
|
||||
proxyUrl = ''
|
||||
}
|
||||
|
||||
Config.RegisterPluginConfig = function(pluginName, configs)
|
||||
Config.plugins[pluginName] = {}
|
||||
for k, v in pairs(configs) do
|
||||
Config.plugins[pluginName][k] = v
|
||||
-- debugLog(("plugin %s set %s = %s"):format(pluginName, k, v))
|
||||
end
|
||||
table.insert(Plugins, pluginName)
|
||||
end
|
||||
|
||||
local function CopyFile(old_path, new_path)
|
||||
local old_file = io.open(old_path, 'rb')
|
||||
local new_file = io.open(new_path, 'wb')
|
||||
if not old_file then
|
||||
warnLog('Failed to open source file: ' .. old_path ..
|
||||
' - please check your folder permissions or rename file manually.')
|
||||
return false
|
||||
end
|
||||
if not new_file then
|
||||
warnLog('Failed to create target file: ' .. new_path ..
|
||||
' - please check your folder permissions or rename file manually.')
|
||||
old_file:close()
|
||||
return false
|
||||
end
|
||||
|
||||
local old_file_sz, new_file_sz
|
||||
while true do
|
||||
local block = old_file:read(2 ^ 13)
|
||||
if not block then
|
||||
old_file_sz = old_file:seek('end')
|
||||
break
|
||||
end
|
||||
new_file:write(block)
|
||||
end
|
||||
old_file:close()
|
||||
new_file_sz = new_file:seek('end')
|
||||
new_file:close()
|
||||
if new_file_sz ~= old_file_sz then
|
||||
print('File copy size mismatch')
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
exports('GetPluginConfig', function(pluginName)
|
||||
return Config.GetPluginConfig(pluginName)
|
||||
end)
|
||||
|
||||
Config.GetPluginConfig = function(pluginName)
|
||||
local correctConfig = nil
|
||||
if Config.plugins[pluginName] ~= nil then
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
end
|
||||
return Config.plugins[pluginName]
|
||||
else
|
||||
if pluginName == 'yourpluginname' then
|
||||
return {enabled = false, disableReason = 'Template plugin'}
|
||||
end
|
||||
if pluginName == 'apicheck' or pluginName == 'livemap' or pluginName ==
|
||||
'smartsigns' then
|
||||
return {enabled = false, disableReason = 'deprecated plugin'}
|
||||
end
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.lua')
|
||||
if not correctConfig then
|
||||
infoLog(
|
||||
('Submodule %s only has the default configurations file (%s_config.dist.lua)... Attempting to rename config to: %s_config.lua'):format(
|
||||
pluginName, pluginName, pluginName))
|
||||
if not CopyFile(GetResourcePath(GetCurrentResourceName()) ..
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.dist.lua',
|
||||
GetResourcePath(GetCurrentResourceName()) ..
|
||||
'/configuration/' .. pluginName .. '_config.lua') then
|
||||
warnLog(
|
||||
('Failed to rename %s_config.dist.lua to %s_config.lua'):format(
|
||||
pluginName, pluginName))
|
||||
warnLog(
|
||||
('Using default configurations for %s. Please rename %s_config.dist.lua to %s_config.lua to avoid seeing this message'):format(
|
||||
pluginName, pluginName, pluginName))
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.dist.lua')
|
||||
else
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.lua')
|
||||
end
|
||||
end
|
||||
if not correctConfig then
|
||||
warnLog(
|
||||
('Submodule %s is missing critical configuration. Please check our submodule install guide at https://info.sonorancad.com/integration-plugins/in-game-integration/fivem-installation/submodule-configuration#activating-a-submodule for steps to properly install.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
else
|
||||
local configChunk = correctConfig:match("local config = {.-\n}") .. "\nreturn config"
|
||||
if not configChunk then
|
||||
errorLog("No config table found in the string.")
|
||||
end
|
||||
local tempEnv = {}
|
||||
setmetatable(tempEnv, { __index = _G }) -- Allow access to global functions if needed
|
||||
local loadedPlugin, pluginError = load(configChunk, 'config', 't', tempEnv)
|
||||
if loadedPlugin then
|
||||
-- Execute and capture the returned config table
|
||||
local success, res = pcall(loadedPlugin)
|
||||
if not success then
|
||||
errorLog(('Submodule %s failed to load due to error: %s'):format(pluginName, res))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
if res and type(res) == "table" then
|
||||
-- Assign the extracted config to Config.plugins[pluginName]
|
||||
Config.plugins[pluginName] = res
|
||||
else
|
||||
-- Handle case where config is not available
|
||||
errorLog(
|
||||
('Plugin %s did not define a valid config table.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
end
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
elseif Config.plugins[pluginName].enabled == false then
|
||||
Config.plugins[pluginName].disableReason = 'Disabled'
|
||||
end
|
||||
else
|
||||
errorLog(('Plugin %s failed to load due to error: %s'):format(
|
||||
pluginName, pluginError))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
return Config.plugins[pluginName]
|
||||
end
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'disabled'
|
||||
}
|
||||
return {enabled = false, disableReason = 'disabled'}
|
||||
end
|
||||
end
|
||||
|
||||
Config.LoadPlugin = function(pluginName, cb)
|
||||
local correctConfig = nil
|
||||
while Config.apiVersion == -1 do Wait(1) end
|
||||
if Config.plugins[pluginName] ~= nil then
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
end
|
||||
return cb(Config.plugins[pluginName])
|
||||
else
|
||||
if pluginName == 'yourpluginname' then
|
||||
return cb({enabled = false, disableReason = 'Template plugin'})
|
||||
end
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.lua')
|
||||
if not correctConfig then
|
||||
infoLog(
|
||||
('Plugin %s only has the default configurations file (%s_config.dist.lua)... Attempting to rename config to: %s_config.lua'):format(
|
||||
pluginName, pluginName, pluginName))
|
||||
if not CopyFile(GetResourcePath(GetCurrentResourceName()) ..
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.dist.lua',
|
||||
GetResourcePath(GetCurrentResourceName()) ..
|
||||
'/configuration/' .. pluginName .. '_config.lua') then
|
||||
warnLog(
|
||||
('Failed to rename %s_config.dist.lua to %s_config.lua'):format(
|
||||
pluginName, pluginName))
|
||||
warnLog(
|
||||
('Using default configurations for %s. Please rename %s_config.dist.lua to %s_config.lua to avoid seeing this message'):format(
|
||||
pluginName, pluginName, pluginName))
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.dist.lua')
|
||||
else
|
||||
correctConfig = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/' .. pluginName ..
|
||||
'_config.lua')
|
||||
end
|
||||
end
|
||||
if not correctConfig then
|
||||
warnLog(
|
||||
('Plugin %s is missing critical configuration. Please check our plugin install guide at https://info.sonorancad.com/integration-submodules/integration-submodules/plugin-installation for steps to properly install.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
}
|
||||
return cb({
|
||||
enabled = false,
|
||||
disableReason = 'Missing configuration file'
|
||||
})
|
||||
else
|
||||
local configChunk = correctConfig:match("local config = {.-\n}") .. "\nreturn config"
|
||||
if not configChunk then
|
||||
errorLog("No config table found in the string.")
|
||||
end
|
||||
local tempEnv = {}
|
||||
setmetatable(tempEnv, { __index = _G }) -- Allow access to global functions if needed
|
||||
local loadedPlugin, pluginError = load(configChunk, 'config', 't', tempEnv)
|
||||
if loadedPlugin then
|
||||
-- Execute and capture the returned config table
|
||||
local success, res = pcall(loadedPlugin)
|
||||
if not success then
|
||||
errorLog(('Plugin %s failed to load due to error: %s'):format(pluginName, res))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return {enabled = false, disableReason = 'Failed to load'}
|
||||
end
|
||||
if res and type(res) == "table" then
|
||||
-- Assign the extracted config to Config.plugins[pluginName]
|
||||
Config.plugins[pluginName] = res
|
||||
else
|
||||
-- Handle case where config is not available
|
||||
errorLog(
|
||||
('Plugin %s did not define a valid config table.'):format(
|
||||
pluginName))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
return {
|
||||
enabled = false,
|
||||
disableReason = 'Invalid or missing config'
|
||||
}
|
||||
end
|
||||
if Config.critError then
|
||||
Config.plugins[pluginName].enabled = false
|
||||
Config.plugins[pluginName].disableReason = 'startup aborted'
|
||||
elseif Config.plugins[pluginName].enabled == nil then
|
||||
Config.plugins[pluginName].enabled = true
|
||||
elseif Config.plugins[pluginName].enabled == false then
|
||||
Config.plugins[pluginName].disableReason = 'Disabled'
|
||||
end
|
||||
else
|
||||
errorLog(('Plugin %s failed to load due to error: %s'):format(
|
||||
pluginName, pluginError))
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'Failed to load'
|
||||
}
|
||||
return cb({enabled = false, disableReason = 'Failed to load'})
|
||||
end
|
||||
return cb(Config.plugins[pluginName])
|
||||
end
|
||||
Config.plugins[pluginName] = {
|
||||
enabled = false,
|
||||
disableReason = 'disabled'
|
||||
}
|
||||
return cb({enabled = false, disableReason = 'disabled'})
|
||||
end
|
||||
end
|
||||
|
||||
local conf = LoadResourceFile(GetCurrentResourceName(),
|
||||
'/configuration/config.json')
|
||||
if conf == nil then
|
||||
errorLog(
|
||||
'CONFIG_ERROR: Unable to load configuration file. Ensure the file is named correctly (config.json). Check for extra extensions (like config.json.json).')
|
||||
Config.critError = true
|
||||
Config.apiSendEnabled = false
|
||||
return
|
||||
end
|
||||
local parsedConfig = json.decode(conf)
|
||||
if parsedConfig == nil then
|
||||
errorLog(
|
||||
'CONFIG_ERROR: Unable to parse configuration file. Ensure it is valid JSON.')
|
||||
Config.critError = true
|
||||
Config.apiSendEnabled = false
|
||||
return
|
||||
end
|
||||
for k, v in pairs(json.decode(conf)) do
|
||||
local cvar = GetConvar('sonoran_' .. k, 'NONE')
|
||||
local cvar_setter = GetConvar('sonoran_' .. k .. '_setter', 'NONE')
|
||||
local val = nil
|
||||
if cvar ~= 'NONE' and cvar ~= 'statusLabels' then
|
||||
if cvar_setter == 'NONE' or cvar_setter == 'server' then
|
||||
infoLog(
|
||||
('Configuration: Overriding config option %s with convar. New value: %s'):format(
|
||||
k, cvar))
|
||||
SetConvar('sonoran_' .. k .. '_setter', 'server')
|
||||
cvar_setter = 'server'
|
||||
else
|
||||
infoLog(
|
||||
('Configuration: Reusing config option %s from server boot. New value: %s, reboot the server if you made a change to this value...'):format(
|
||||
k, cvar))
|
||||
SetConvar('sonoran_' .. k .. '_setter', 'framework')
|
||||
cvar_setter = 'framework'
|
||||
end
|
||||
if cvar == 'true' then
|
||||
cvar = true
|
||||
elseif cvar == 'false' then
|
||||
cvar = false
|
||||
end
|
||||
Config[k] = cvar
|
||||
val = cvar
|
||||
else
|
||||
Config[k] = v
|
||||
val = v
|
||||
end
|
||||
if k ~= 'apiKey' then
|
||||
SetConvar('sonoran_' .. k, tostring(val))
|
||||
if cvar_setter == 'NONE' then
|
||||
SetConvar('sonoran_' .. k .. '_setter', 'framework')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Config.updateBranch == nil then Config.updateBranch = 'master' end
|
||||
|
||||
RegisterNetEvent('SonoranCAD::core:sendClientConfig')
|
||||
AddEventHandler('SonoranCAD::core:sendClientConfig', function()
|
||||
local config = {
|
||||
communityID = Config.communityID,
|
||||
postTime = Config.postTime,
|
||||
serverId = Config.serverId,
|
||||
primaryIdentifier = Config.primaryIdentifier,
|
||||
apiSendEnabled = Config.apiSendEnabled,
|
||||
debugMode = Config.debugMode,
|
||||
devHiddenSwitch = Config.devHiddenSwitch,
|
||||
statusLabels = Config.statusLabels,
|
||||
bodycamEnabled = Config.bodycamEnabled,
|
||||
bodycamBeepFrequency = Config.bodycamBeepFrequency,
|
||||
bodycamScreenshotFrequency = Config.bodycamScreenshotFrequency,
|
||||
bodycamPlayBeeps = Config.bodycamPlayBeeps,
|
||||
bodycamOverlayEnabled = Config.bodycamOverlayEnabled,
|
||||
bodycamOverlayLocation = Config.bodycamOverlayLocation,
|
||||
bodycamCommandToggle = Config.bodycamCommandToggle,
|
||||
bodycamCommandChangeFrequncy = Config.bodycamCommandChangeFrequncy,
|
||||
apiVersion = Config.apiVersion,
|
||||
mode = Config.mode
|
||||
}
|
||||
TriggerClientEvent('SonoranCAD::core:recvClientConfig', source, config)
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
Wait(2000) -- wait for server to settle
|
||||
if Config.critError then return end
|
||||
local serverId = Config.serverId
|
||||
while Config.apiVersion == -1 do Wait(10) end
|
||||
if not Config.apiSendEnabled or Config.apiVersion < 3 then
|
||||
debugLog('Too low version or API disabled, ignore this')
|
||||
return
|
||||
end
|
||||
performApiRequest({}, 'GET_SERVERS', function(response)
|
||||
local info = json.decode(response)
|
||||
for k, v in pairs(info.servers) do
|
||||
if tostring(v.id) == tostring(serverId) then
|
||||
ServerInfo = v
|
||||
break
|
||||
end
|
||||
end
|
||||
local needSetup = false
|
||||
local serverObj = {}
|
||||
if ServerInfo == nil then
|
||||
needSetup = true
|
||||
serverObj = {
|
||||
id = serverId,
|
||||
name = 'Server ' .. serverId,
|
||||
description = 'Server ' .. serverId,
|
||||
signal = '',
|
||||
listenerPort = GetConvar('netPort', '0'),
|
||||
mapIp = '',
|
||||
differingOutbound = false,
|
||||
outboundIp = '',
|
||||
enableMap = true,
|
||||
mapType = 'NORMAL'
|
||||
}
|
||||
else
|
||||
serverObj = ServerInfo
|
||||
end
|
||||
if serverObj.name == '' then
|
||||
serverObj.name = 'Server ' .. tostring(serverId)
|
||||
end
|
||||
if ServerInfo.listenerPort ~= GetConvar('netPort', '0') then
|
||||
infoLog(
|
||||
('Configuration information doesn\'t match, will attempt to auto-correct game port from %s to %s.'):format(
|
||||
ServerInfo.listenerPort, GetConvar('netPort', '0')))
|
||||
serverObj.listenerPort = GetConvar('netPort', '0')
|
||||
needSetup = true
|
||||
end
|
||||
PerformHttpRequest('https://api.ipify.org?format=json',
|
||||
function(errorCode, resultData, resultHeaders)
|
||||
local r = json.decode(resultData)
|
||||
if r ~= nil and r.ip ~= nil then
|
||||
debugLog(
|
||||
('IP DETECT - IP: %s - Detected: %s - Outbound set: %s - Outbound IP: %s'):format(
|
||||
ServerInfo.mapIp, r.ip, ServerInfo.differingOutbound,
|
||||
ServerInfo.outboundIp))
|
||||
if serverObj.mapIp == '' or serverObj.mapIp == nil then
|
||||
serverObj.mapIp = r.ip
|
||||
needSetup = true
|
||||
end
|
||||
if ServerInfo.mapIp ~= r.ip then
|
||||
if ServerInfo.differingOutbound and ServerInfo.outboundIp ==
|
||||
r.ip then
|
||||
infoLog(
|
||||
'Detected proper differing outbound IP configuration.')
|
||||
else
|
||||
if ServerInfo.differingOutbound then
|
||||
needSetup = true
|
||||
serverObj.outboundIp = r.ip
|
||||
else
|
||||
needSetup = true
|
||||
serverObj.outboundIp = r.ip
|
||||
serverObj.differingOutbound = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local disableOverride = (Config.disableOverride ~= nil and
|
||||
Config.disableOverride or false)
|
||||
if needSetup and not disableOverride then
|
||||
local payload = nil
|
||||
if ServerInfo == nil then
|
||||
payload = {['servers'] = {serverObj}}
|
||||
else
|
||||
payload = info
|
||||
for k, v in pairs(payload) do
|
||||
if v.id == serverId then
|
||||
payload[k] = serverObj
|
||||
end
|
||||
end
|
||||
end
|
||||
debugLog(('Send payload: %s'):format(json.encode(payload)))
|
||||
performApiRequest(json.encode(payload), 'SET_SERVERS', function(
|
||||
resp)
|
||||
debugLog('SET_SERVERS: ' .. tostring(resp))
|
||||
end)
|
||||
elseif disableOverride and not needSetup then
|
||||
warnLog(
|
||||
'disableOverride is true or there is no additional setup required, skipping any potential auto-IP/port fixing')
|
||||
end
|
||||
end, 'GET', nil, nil)
|
||||
end)
|
||||
|
||||
if isPluginLoaded('livemap') then
|
||||
warnLog(
|
||||
'The livemap plugin is no longer being used due to the map being native to the CAD. You can remove this plugin.')
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
-- attempt to fetch web_baseUrl
|
||||
local baseUrl = ''
|
||||
local counter = 0
|
||||
if Config.bodycamEnabled then
|
||||
local counter = 0
|
||||
while baseUrl == '' do
|
||||
Wait(1000)
|
||||
baseUrl = GetConvar('web_baseUrl', '')
|
||||
|
||||
-- Every 60 seconds, log a warning
|
||||
counter = counter + 1
|
||||
if counter % 60 == 0 then
|
||||
warnLog('Still waiting for web_baseUrl convar to be set...bodycam will not work until this is set.')
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Run the loop once
|
||||
baseUrl = GetConvar('web_baseUrl', '')
|
||||
if baseUrl == '' then
|
||||
warnLog('Bodycam is disabled and web_baseUrl is not set. Skipping loop.')
|
||||
end
|
||||
end
|
||||
Config.proxyUrl = ('https://%s/sonorancad/'):format(GetConvar('web_baseUrl',''))
|
||||
debugLog(('Set proxyUrl to %s'):format(Config.proxyUrl))
|
||||
TriggerClientEvent('SonoranCAD::Core::InitBodycam', -1, 1, Config.apiVersion)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('SonoranCAD::Core::RequestBodycam', function()
|
||||
if not Config.proxyUrl or Config.proxyUrl == '' then
|
||||
-- tell client we're not ready
|
||||
TriggerClientEvent('SonoranCAD::Core::InitBodycam', source, 0, Config.apiVersion)
|
||||
else
|
||||
-- tell client we're ready
|
||||
if Config.apiVersion == -1 then
|
||||
debugLog('API version not set, waiting for it to be set...')
|
||||
while Config.apiVersion == -1 do Wait(1000) end
|
||||
end
|
||||
TriggerClientEvent('SonoranCAD::Core::InitBodycam', source, 1, Config.apiVersion)
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
while Config.apiVersion == -1 do Wait(100) end
|
||||
if Config.critError then return end
|
||||
if isPluginLoaded('wraithv2') then
|
||||
if GetResourceState('wk_wars2x') ~= 'started' then
|
||||
warnLog(
|
||||
('Warning: wk_wars2x resource in bad start (%s). Ensure it is started to use the wraithv2 resource.'):format(
|
||||
GetResourceState('wk_wars2x')))
|
||||
end
|
||||
if GetResourceState('pNotify') ~= 'started' then
|
||||
warnLog(
|
||||
('Warning: pNotify is required to see notifications from the wraithv2 plugin but the resource in bad start (%s). Ensure it is started'):format(
|
||||
GetResourceState('pNotify')))
|
||||
end
|
||||
end
|
||||
if isPluginLoaded('smartsigns') then
|
||||
warnLog('smartsigns is now a standalone resource. Please update.')
|
||||
end
|
||||
-- smartsigns improper install check
|
||||
if file_exists(('%s/submodules/smartsigns/sv_smartsigns.lua'):format(
|
||||
GetResourcePath(GetCurrentResourceName()))) or
|
||||
file_exists(
|
||||
('%s/submodules/smartsigns/smartsigns/sv_smartsigns.lua'):format(
|
||||
GetResourcePath(GetCurrentResourceName()))) then
|
||||
errorLog('-----------------------')
|
||||
errorLog(
|
||||
'Smartsigns incorrect installation detected. This should be installed a standalone resource. If you still have the plugin, you MUST update! You will recieve a parse error in this state.')
|
||||
errorLog('-----------------------')
|
||||
end
|
||||
end)
|
||||
|
||||
function file_exists(name)
|
||||
local f = io.open(name, 'r')
|
||||
if f ~= nil then
|
||||
io.close(f)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
-- source: https://github.com/loaf-scripts/loaf_headshot_base64/blob/main/client.lua
|
||||
|
||||
local requests = {}
|
||||
|
||||
local function GenerateId()
|
||||
local id = ""
|
||||
for i = 1, 15 do
|
||||
id = id .. (math.random(1, 2) == 1 and string.char(math.random(97, 122)) or tostring(math.random(0,9)))
|
||||
end
|
||||
return id
|
||||
end
|
||||
|
||||
local function ClearHeadshots()
|
||||
for i = 1, 255 do
|
||||
if IsPedheadshotValid(i) then
|
||||
UnregisterPedheadshot(i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GetHeadshot(ped)
|
||||
ClearHeadshots()
|
||||
if not ped then ped = PlayerPedId() end
|
||||
if DoesEntityExist(ped) then
|
||||
local handle, timer = RegisterPedheadshot(ped), GetGameTimer() + 5000
|
||||
while not IsPedheadshotReady(handle) or not IsPedheadshotValid(handle) do
|
||||
Wait(50)
|
||||
if GetGameTimer() >= timer then
|
||||
return {success=false, error="Could not load ped headshot."}
|
||||
end
|
||||
end
|
||||
|
||||
local txd = GetPedheadshotTxdString(handle)
|
||||
local url = string.format("https://nui-img/%s/%s", txd, txd)
|
||||
return {success=true, url=url, txd=txd, handle=handle}
|
||||
end
|
||||
end
|
||||
|
||||
function GetBase64(ped)
|
||||
if not ped then ped = PlayerPedId() end
|
||||
local headshot = GetHeadshot(ped)
|
||||
if headshot.success then
|
||||
local requestId = GenerateId()
|
||||
requests[requestId] = nil
|
||||
SendNUIMessage({
|
||||
type = "convert_base64",
|
||||
img = headshot.url,
|
||||
handle = headshot.handle,
|
||||
id = requestId
|
||||
})
|
||||
|
||||
local timer = GetGameTimer() + 5000
|
||||
while not requests[requestId] do
|
||||
Wait(250)
|
||||
if GetGameTimer() >= timer then
|
||||
return {success=false, error="Waiting for base64 conversion timed out."}
|
||||
end
|
||||
end
|
||||
return {success=true, base64=requests[requestId]}
|
||||
else
|
||||
return headshot
|
||||
end
|
||||
end
|
||||
|
||||
RegisterNUICallback("base64", function(data, cb)
|
||||
if data.handle then
|
||||
UnregisterPedheadshot(data.handle)
|
||||
end
|
||||
if data.id then
|
||||
requests[data.id] = data.base64
|
||||
Wait(1500)
|
||||
requests[data.id] = nil
|
||||
end
|
||||
|
||||
cb({ok=true})
|
||||
end)
|
||||
|
||||
exports("getBase64", GetBase64)
|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SonoranCAD Info Page</title>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<div id="passwordDiv">
|
||||
<form>
|
||||
<p>API Key: <input type="text" id="password" name="password"/></p>
|
||||
<input type="button" id="apisubmit" value="Get Configuration"/> <br/><br/>
|
||||
Console Input: <input type="text" id="consoleinput" name="consoleinput" width="150"/> <input type="button" id="consolesubmit" value="Send Command"/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div id="errorDiv"></div>
|
||||
<div id="infoDiv"></div>
|
||||
<div id="consoleDiv"></div>
|
||||
<div id="debugHeader" style="display:none"><p>LAST 50 DEBUG MESSAGES</p></div>
|
||||
<div id="debugDiv"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const api = document.getElementById("apisubmit")
|
||||
const console = document.getElementById("consolesubmit")
|
||||
|
||||
console.addEventListener('click', e => {
|
||||
fetch('/sonorancad/console', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
password: document.querySelector("#password").value,
|
||||
command: document.querySelector("#consoleinput").value
|
||||
})
|
||||
}).then(res => res.json()).then(res => {
|
||||
let consoleDiv = document.getElementById("consoleDiv");
|
||||
if (res.error) {
|
||||
document.getElementById("errorDiv").innerHTML = res.error;
|
||||
} else {
|
||||
consoleDiv.innerHTML = res.output;
|
||||
document.getElementById("errorDiv").innerHTML = "";
|
||||
}
|
||||
document.getElementById("#consoleinput").innerHTML = "";
|
||||
})
|
||||
});
|
||||
api.addEventListener('click', e => {
|
||||
fetch('/sonorancad/info', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
password: document.querySelector("#password").value
|
||||
})
|
||||
}).then(res => res.json()).then(res => {
|
||||
let passDiv = document.getElementById("passwordDiv");
|
||||
let infoDiv = document.getElementById("infoDiv");
|
||||
let consoleDiv = document.getElementById("consoleDiv");
|
||||
let debugDiv = document.getElementById("debugDiv");
|
||||
let debugHeader = document.getElementById("debugHeader");
|
||||
if (res.error) {
|
||||
infoDiv.style.color = '#aa0000';
|
||||
infoDiv.innerHTML = "An error occurred: " + res.error;
|
||||
infoDiv.style.display == "none";
|
||||
} else {
|
||||
infoDiv.style.color = '#000000';
|
||||
infoDiv.style.display == "block";
|
||||
// passDiv.style.display == "none";
|
||||
infoDiv.innerHTML = res.cadInfo
|
||||
infoDiv.innerHTML += res.config
|
||||
consoleDiv.innerHTML = res.console
|
||||
debugDiv.innerHTML = res.debug
|
||||
debugHeader.style = "display:block;"
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
const { format } = require("path");
|
||||
|
||||
function byteCount(s) {
|
||||
return encodeURI(s).split(/%..|./).length - 1;
|
||||
}
|
||||
|
||||
exports('HandleHttpRequest', (dest, callback, method, data, headers) => {
|
||||
emit("SonoranCAD::core:writeLog", "debug", "[http] to: " + dest + " - data: " + dest, JSON.stringify(data));
|
||||
const urlObj = url.parse(dest)
|
||||
const options = {
|
||||
hostname: urlObj.hostname,
|
||||
path: urlObj.pathname,
|
||||
method: method,
|
||||
headers: headers
|
||||
}
|
||||
if (method == "POST") {
|
||||
options.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
else if (method != "GET") {
|
||||
console.error("Invalid request. Only GET/POST supported. Method: " + method);
|
||||
callback(500, "", {});
|
||||
return;
|
||||
}
|
||||
options.headers['X-SonoranCAD-Version'] = GetResourceMetadata(GetCurrentResourceName(), "version", 0)
|
||||
//console.debug("send to: " + dest);
|
||||
const req = https.request(options, (res) => {
|
||||
let output = "";
|
||||
res.on('data', (d) => {
|
||||
output += d.toString()
|
||||
}),
|
||||
res.on('end', () => {
|
||||
callback(res.statusCode, output, res.headers);
|
||||
})
|
||||
})
|
||||
|
||||
req.on('error', (error) => {
|
||||
let ignore_ids = ["EAI_AGAIN", "ETIMEOUT", "ENOTFOUND"]
|
||||
if (!ignore_ids.includes(error.code))
|
||||
console.debug("HTTP error caught: " + JSON.stringify(error));
|
||||
callback(error.errono, {}, {});
|
||||
})
|
||||
if (method == "POST") {
|
||||
req.write(data);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
@@ -0,0 +1,508 @@
|
||||
local PluginHttpHandlers = {}
|
||||
local PluginFilePaths = {}
|
||||
|
||||
function RegisterPluginHttpEvent(eventName, func)
|
||||
if PluginHttpHandlers[eventName] ~= nil then
|
||||
errorLog('Failed to register plugin event ' .. eventName .. ': Already Exists')
|
||||
return
|
||||
end
|
||||
PluginHttpHandlers[eventName] = func
|
||||
end
|
||||
|
||||
local PushEventHandler = {
|
||||
EVENT_UNIT_STATUS = function(body)
|
||||
if (not body.data.identIds) then
|
||||
return false, 'missing identIds'
|
||||
end
|
||||
if body.data.identIds ~= nil then
|
||||
for i = 1, #body.data.identIds do
|
||||
local unit = GetUnitObjectById(body.data.identIds[i])
|
||||
if unit then
|
||||
unit.status = body.data.status
|
||||
SetUnitCache(body.data.identIds[i], unit)
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitUpdate', unit, unit.status)
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitStatusUpdate', unit, unit.status)
|
||||
return true
|
||||
end
|
||||
debugLog(('EVENT_UNIT_STATUS: Unknown unit, idents: %s'):format(json.encode(body.data.identIds)))
|
||||
return false, 'unknown unit'
|
||||
end
|
||||
else
|
||||
return false, 'invalid, no idents'
|
||||
end
|
||||
return true
|
||||
end,
|
||||
EVENT_UNIT_LOGIN = function(body)
|
||||
if (not body.data.unit.id) then
|
||||
return false, 'missing ID'
|
||||
end
|
||||
local unit = body.data.unit
|
||||
debugLog('Got a unit: ' .. json.encode(unit))
|
||||
unit.isDispatch = body.data.isDispatch
|
||||
SetUnitCache(unit.id, unit)
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitLogin', unit)
|
||||
return true
|
||||
end,
|
||||
EVENT_UNIT_LOGOUT = function(body)
|
||||
if (not body.data.identId) then
|
||||
return false, 'missing identId'
|
||||
end
|
||||
debugLog('UNIT_LOGOUT: ' .. json.encode(body.data))
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitLogout', body.data.identId)
|
||||
SetUnitCache(GetUnitById(body.data.identId), nil)
|
||||
return true
|
||||
end,
|
||||
EVENT_DISPATCH_NEW = function(body)
|
||||
SetCallCache(body.data.dispatch.callId, {
|
||||
dispatch_type = 'CALL_NEW',
|
||||
dispatch = body.data.dispatch ~= nil and body.data.dispatch or body.data
|
||||
})
|
||||
TriggerEvent('SonoranCAD::pushevents:DispatchEvent', GetCallCache()[body.data.dispatch.callId])
|
||||
return true
|
||||
end,
|
||||
EVENT_DISPATCH_EDIT = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:DispatchEdit', GetCallCache()[body.data.dispatch.callId], body.data)
|
||||
SetCallCache(body.data.dispatch.callId, {
|
||||
dispatch_type = 'CALL_EDIT',
|
||||
dispatch = body.data.dispatch ~= nil and body.data.dispatch or body.data
|
||||
})
|
||||
TriggerEvent('SonoranCAD::pushevents:DispatchEvent', GetCallCache()[body.data.dispatch.callId])
|
||||
return true
|
||||
end,
|
||||
EVENT_DISPATCH_CLOSED = function(body)
|
||||
for i = 1, #body.data.callIds do
|
||||
local id = body.data.callIds[i]
|
||||
if GetCallCache()[id] ~= nil then
|
||||
local call = GetCallCache()[id].dispatch
|
||||
local d = {
|
||||
dispatch_type = 'CALL_CLOSE',
|
||||
dispatch = call.dispatch ~= nil and call.dispatch or call
|
||||
}
|
||||
d.dispatch.status = 2 -- make sure its updated to closed status
|
||||
SetCallCache(id, d)
|
||||
TriggerEvent('SonoranCAD::pushevents:DispatchEvent', d)
|
||||
return true
|
||||
else
|
||||
debugLog(('Unknown call close (call ID %s), current cache: %s'):format(id, json.encode(CallCache)))
|
||||
return false, 'unknown call close'
|
||||
end
|
||||
end
|
||||
end,
|
||||
EVENT_DISPATCH_NOTE = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:DispatchNote', GetCallCache()[body.data.callId], body.data)
|
||||
if GetCallCache()[body.data.callId] ~= nil then
|
||||
local call = GetCallCache()[body.data.callId].dispatch
|
||||
local newnotes = {}
|
||||
table.insert(newnotes, body.data.note)
|
||||
if call.notes ~= nil then
|
||||
for k, v in pairs(call.notes) do
|
||||
table.insert(newnotes, v)
|
||||
end
|
||||
end
|
||||
call.notes = newnotes
|
||||
SetCallCache(body.data.callId, {
|
||||
dispatch_type = 'CALL_EDIT',
|
||||
dispatch = call.dispatch ~= nil and call.dispatch or call
|
||||
})
|
||||
return true
|
||||
else
|
||||
debugLog(('Unknown call note update (call ID %s), current cache: %s'):format(body.data.callId, json.encode(CallCache)))
|
||||
return false, 'unknown call note'
|
||||
end
|
||||
end,
|
||||
EVENT_DISPATCH_UNIT_ATTACH = function(body)
|
||||
-- fetch the call and unit data
|
||||
local call = GetCallCache()[body.data.callId]
|
||||
if body.data.idents ~= nil then
|
||||
idents = body.data.idents
|
||||
elseif body.data.ident ~= nil then
|
||||
table.insert(idents, body.data.ident)
|
||||
end
|
||||
for i = 1, #idents do
|
||||
local unit = GetUnitById(idents[i])
|
||||
debugLog('UNIT: ' .. json.encode(unit))
|
||||
if call and unit then
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitAttach', call, GetUnitCache()[unit])
|
||||
local idx = nil
|
||||
for x = 1, #call.dispatch.idents do
|
||||
if call.dispatch.idents[x] == idents[i] then
|
||||
idx = x
|
||||
end
|
||||
end
|
||||
debugLog('INDEX VALUE: ' .. tostring(idx))
|
||||
if idx == nil then
|
||||
table.insert(call.dispatch.idents, idents[i])
|
||||
SetCallCache(body.data.callId, {
|
||||
dispatch_type = 'CALL_EDIT',
|
||||
dispatch = call.dispatch ~= nil and call.dispatch or call
|
||||
})
|
||||
end
|
||||
else
|
||||
debugLog(('Attach failure, unknown call or unit (C: %s) (U: %s)'):format(json.encode(call), json.encode(unit)))
|
||||
return false, 'invalid call or unit'
|
||||
end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
EVENT_DISPATCH_UNIT_DETACH = function(body)
|
||||
local call = GetCallCache()[body.data.callId]
|
||||
local idents = {}
|
||||
if body.data.idents ~= nil then
|
||||
idents = body.data.idents
|
||||
elseif body.data.ident ~= nil then
|
||||
table.insert(idents, body.data.ident)
|
||||
end
|
||||
for i = 1, #idents do
|
||||
local unit = GetUnitById(idents[i])
|
||||
if call and unit then
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitDetach', call, GetUnitCache()[unit])
|
||||
local idx = nil
|
||||
for x = 1, #call.dispatch.idents do
|
||||
if call.dispatch.idents[x] == idents[i] then
|
||||
idx = x
|
||||
end
|
||||
end
|
||||
if unit ~= nil then
|
||||
table.remove(call.dispatch.idents, idx)
|
||||
SetCallCache(body.data.callId, {
|
||||
dispatch_type = 'CALL_EDIT',
|
||||
dispatch = call.dispatch ~= nil and call.dispatch or call
|
||||
})
|
||||
end
|
||||
else
|
||||
debugLog(('Detach failure, unknown call or unit (C: %s) (U: %s)'):format(json.encode(call), json.encode(unit)))
|
||||
return false, 'invalid call or unit'
|
||||
end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
GET_LOGS = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:SendSupportLogs', body.logKey)
|
||||
return true
|
||||
end,
|
||||
EVENT_911 = function(body)
|
||||
SetEmergencyCache(body.data.call.callId, body.data.call)
|
||||
TriggerEvent('SonoranCAD::pushevents:IncomingCadCall', body.data.call, body.data.call.metaData, body.data.apiIds)
|
||||
return true
|
||||
end,
|
||||
EVENT_REMOVE_911 = function(body)
|
||||
for i = 1, #body.data.callIds do
|
||||
if body.data.callIds[i] then
|
||||
SetEmergencyCache(body.data.callIds[i], nil)
|
||||
TriggerEvent('SonoranCAD::pushevents:CadCallRemoved', body.data.callIds[i])
|
||||
end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
EVENT_UNIT_PANIC = function(body)
|
||||
local identIds = body.data.identIds or {}
|
||||
-- for legacy
|
||||
if body.data.identId then
|
||||
table.insert(identIds, body.data.identId)
|
||||
end
|
||||
|
||||
for _, identId in ipairs(identIds) do
|
||||
local unit = GetUnitById(identId)
|
||||
if unit then
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitPanic', unit, identId, body.data.isPanic)
|
||||
else
|
||||
debugLog(('Ignore panic event, unit ident %s not found'):format(tostring(identId)))
|
||||
end
|
||||
end
|
||||
end,
|
||||
EVENT_STREETSIGN_UPDATED = function(body)
|
||||
if body == nil or body.data == nil or body.data.signData == nil then
|
||||
return false, 'invalid data'
|
||||
end
|
||||
TriggerEvent('SonoranCAD::pushevents:SmartSignUpdate', body.data.signData)
|
||||
return true
|
||||
end,
|
||||
EVENT_RECORD_ADD = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:RecordAdded', body.data.record)
|
||||
return true
|
||||
end,
|
||||
EVENT_RECORD_EDIT = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:RecordEdited', body.data.record)
|
||||
return true
|
||||
end,
|
||||
EVENT_RECORD_REMOVE = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:RecordRemoved', body.data.record)
|
||||
return true
|
||||
end,
|
||||
EVENT_UNIT_GROUP_ADD = function(body)
|
||||
local idents = {}
|
||||
if body.identId ~= nil then
|
||||
table.insert(idents, body.identId)
|
||||
elseif body.identIds ~= nil then
|
||||
for _, v in pairs(body.identIds) do
|
||||
table.insert(idents, v)
|
||||
end
|
||||
else
|
||||
return false, 'invalid data'
|
||||
end
|
||||
local payload = {
|
||||
groupName = body.data.groupName,
|
||||
idents = idents
|
||||
}
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitGroupAdd', payload)
|
||||
return true
|
||||
end,
|
||||
EVENT_UNIT_GROUP_REMOVE = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:UnitGroupRemove', body.data)
|
||||
return true
|
||||
end,
|
||||
EVENT_TONE = function(body)
|
||||
TriggerEvent('SonoranCAD::pushevents:Tone', body.data)
|
||||
return true
|
||||
end
|
||||
}
|
||||
|
||||
SetHttpHandler(function(req, res)
|
||||
local path = req.path
|
||||
local method = req.method
|
||||
local base = ''
|
||||
local file = ''
|
||||
for word in path:gmatch('[^/]+') do
|
||||
if base == '' then
|
||||
base = word
|
||||
elseif file == '' then
|
||||
file = word
|
||||
end
|
||||
end
|
||||
if method == 'POST' and path == '/info' then
|
||||
req.setDataHandler(function(body)
|
||||
if not body then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
return
|
||||
end
|
||||
local data = json.decode(body)
|
||||
if not data then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
elseif Config.critError and not Config.apiKey then
|
||||
res.send(json.encode({
|
||||
['error'] = 'critical config error'
|
||||
}))
|
||||
elseif string.upper(data.password) ~= string.upper(Config.apiKey) then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
else
|
||||
local pluginsFormatted = {}
|
||||
for name, plugin in pairs(Config.plugins) do
|
||||
local pl = plugin
|
||||
for k, v in pairs(pl) do
|
||||
if type(v) == 'function' then
|
||||
debugLog('replacing a function')
|
||||
pl[k] = 'function'
|
||||
end
|
||||
end
|
||||
table.insert(pluginsFormatted, name .. ': ' .. json.encode(pl))
|
||||
end
|
||||
res.send(json.encode({
|
||||
['status'] = 'ok',
|
||||
['cadInfo'] = string.gsub(dumpInfo(), '\n', '<br />'),
|
||||
['config'] = table.concat(pluginsFormatted, '<br /><br/>'),
|
||||
['console'] = string.gsub(GetConsoleBuffer(), '\n', '<br />'),
|
||||
['debug'] = string.gsub(table.concat(getDebugBuffer(), '\n'), '\n', '<br />')
|
||||
}))
|
||||
end
|
||||
end)
|
||||
elseif method == 'POST' and path == '/console' then
|
||||
req.setDataHandler(function(body)
|
||||
if not body then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
return
|
||||
end
|
||||
local data = json.decode(body)
|
||||
if not data then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
elseif Config.critError and not Config.apiKey then
|
||||
res.send(json.encode({
|
||||
['error'] = 'critical config error'
|
||||
}))
|
||||
elseif string.upper(data.password) ~= string.upper(Config.apiKey) then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
else
|
||||
local s = string.gmatch(data.command, '%S+')()
|
||||
if s ~= 'sonoran' then
|
||||
res.send(json.encode({
|
||||
['error'] = 'not allowed'
|
||||
}))
|
||||
return
|
||||
end
|
||||
ExecuteCommand(data.command)
|
||||
res.send(json.encode({
|
||||
['status'] = 'ok',
|
||||
['output'] = string.gsub(GetConsoleBuffer(), '\n', '<br />')
|
||||
}))
|
||||
end
|
||||
end)
|
||||
elseif method == 'POST' and path == '/event' then
|
||||
req.setDataHandler(function(data)
|
||||
if not data then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
return
|
||||
end
|
||||
local body = json.decode(data)
|
||||
if not body then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
debugLog('Invalid event: ' .. tostring(body))
|
||||
return
|
||||
end
|
||||
if body.key and body.key:upper() == Config.apiKey:upper() then
|
||||
debugLog(('EVENT: %s - %s'):format(body.type, json.encode(body)))
|
||||
if Config.enablePushEventForwarding then
|
||||
PerformHttpRequest(Config.pushEventForwardUrl, function(statusCode, res, headers)
|
||||
debugLog('Forward Response: ' .. tostring(res))
|
||||
end, 'POST', data, {
|
||||
['Content-Type'] = 'application/json'
|
||||
})
|
||||
end
|
||||
if PushEventHandler[body.type:upper()] then
|
||||
CreateThread(function()
|
||||
body.res = res
|
||||
local success, result = PushEventHandler[body.type:upper()](body)
|
||||
if success then
|
||||
res.send('ok')
|
||||
else
|
||||
if not result then
|
||||
result = 'error'
|
||||
end
|
||||
res.send(result);
|
||||
end
|
||||
end)
|
||||
else
|
||||
TriggerEvent('SonoranCAD::pushevents:OtherEvent', body.type:upper(), body.data)
|
||||
res.send('ok - custom')
|
||||
end
|
||||
end
|
||||
end)
|
||||
elseif method == 'POST' and path == '/pluginevent' then
|
||||
req.setDataHandler(function(data)
|
||||
if not data then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
return
|
||||
end
|
||||
local body = json.decode(data)
|
||||
if not body then
|
||||
res.send(json.encode({
|
||||
['error'] = 'bad request'
|
||||
}))
|
||||
return
|
||||
end
|
||||
if body.key and body.key:upper() == Config.apiKey:upper() then
|
||||
if not body.type or not PluginHttpHandlers[body.type] then
|
||||
return res.send('error')
|
||||
end
|
||||
local resp = PluginHttpHandlers[body.type](body)
|
||||
return res.send(json.encode(resp))
|
||||
else
|
||||
return res.send('error')
|
||||
end
|
||||
end)
|
||||
elseif method == 'GET' and PluginFilePaths[base] ~= nil then
|
||||
local data = LoadResourceFile(GetCurrentResourceName(), ('filestore/%s/%s'):format(base, file), 'r')
|
||||
if not data then
|
||||
warnLog('NOFILE: ' .. tostring(('%s/filestore/%s/%s'):format(GetResourcePath(GetCurrentResourceName()), base, file)))
|
||||
res.writeHead(404)
|
||||
res.send('404')
|
||||
else
|
||||
res.send(data)
|
||||
end
|
||||
elseif method == 'GET' and path:find('^/bodycam') then
|
||||
-- Extract the query string from the path
|
||||
local queryString = path:match('?.*$')
|
||||
local params = {}
|
||||
-- Parse the query string
|
||||
if queryString then
|
||||
for key, value in queryString:gmatch('([^&=?]-)=([^&=?]+)') do
|
||||
-- URL decode the value
|
||||
value = value:gsub('+', ' '):gsub('%%(%x%x)', function(h)
|
||||
return string.char(tonumber(h, 16))
|
||||
end)
|
||||
params[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
-- Extract the 'ident' and 'image' parameters
|
||||
local ident = params['ident']
|
||||
local image = params['image']
|
||||
-- Check if 'ident' and 'image' parameters exist and proceed with your logic
|
||||
if ident and image then
|
||||
local imagePath = GetResourcePath(GetCurrentResourceName()) .. '/screenshots/' .. ident .. '/' .. image
|
||||
-- Your logic here, for example, fetching and sending the image
|
||||
local imageFile = io.open(imagePath, 'rb')
|
||||
if not imageFile then
|
||||
res.send(json.encode({
|
||||
error = 'Image not found'
|
||||
}))
|
||||
return
|
||||
else
|
||||
local content = imageFile:read('*all')
|
||||
res.send(content)
|
||||
end
|
||||
-- Respond to the request with the bodycam image or relevant information
|
||||
else
|
||||
-- Handle the case where the required parameters are missing
|
||||
print('Missing \'ident\' or \'image\' parameter')
|
||||
-- Respond with an error or a message indicating the missing parameters
|
||||
end
|
||||
elseif path == '/' then
|
||||
local html = LoadResourceFile(GetCurrentResourceName(), '/core/html/index.html')
|
||||
res.send(html)
|
||||
else
|
||||
res.send('If you\'re seeing this, sonorancad is loaded.')
|
||||
end
|
||||
end)
|
||||
|
||||
function AddPluginFilePath(path)
|
||||
if PluginFilePaths[path] == nil then
|
||||
PluginFilePaths[path] = true
|
||||
exports[GetCurrentResourceName()]:CreateFolderIfNotExisting(('%s/filestore/%s'):format(GetResourcePath(GetCurrentResourceName()), path))
|
||||
end
|
||||
end
|
||||
|
||||
function SaveFileInPluginPath(path, filename, filedata)
|
||||
if PluginFilePaths[path] ~= nil then
|
||||
local file = assert(io.open(('%s/filestore/%s/%s'):format(GetResourcePath(GetCurrentResourceName()), path, filename), 'wb+'))
|
||||
file:write(filedata)
|
||||
file:close()
|
||||
debugLog('Saved file: ' .. ('%s/filestore/%s/%s'):format(GetResourcePath(GetCurrentResourceName()), path, filename))
|
||||
end
|
||||
end
|
||||
|
||||
AddEventHandler('SonoranCAD::pushevents:shim', function(chunk)
|
||||
local body = json.decode(chunk)
|
||||
if not body then
|
||||
debugLog('Invalid event: ' .. tostring(chunk))
|
||||
return
|
||||
end
|
||||
if body.key and body.key:upper() == Config.apiKey:upper() then
|
||||
if PushEventHandler[body.type:upper()] then
|
||||
CreateThread(function()
|
||||
body.res = res
|
||||
local success, result = PushEventHandler[body.type:upper()](body)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- AddPluginFilePath('images', function(path)
|
||||
|
||||
-- end)
|
||||
@@ -0,0 +1,97 @@
|
||||
const path = require("path");
|
||||
exports("SaveBase64ToFile", function (base64String, filename) {
|
||||
let base64Image = base64String.split(";base64,").pop();
|
||||
fs.writeFile(filename, base64Image, { encoding: "base64" }, function (err) {
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
exports("createScreenshotDirectory", async function (apiID) {
|
||||
let screenshotFolder = `${GetResourcePath(GetCurrentResourceName())}/screenshots`;
|
||||
if (!fs.existsSync(screenshotFolder)) {
|
||||
fs.mkdirSync(screenshotFolder);
|
||||
}
|
||||
let dir = `${GetResourcePath(GetCurrentResourceName())}/screenshots/${apiID}`;
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
return dir;
|
||||
});
|
||||
|
||||
function deleteFileWithRetry(filePath, maxRetries = 50, interval = 100, attempt = 0) {
|
||||
try {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
if (attempt < maxRetries) {
|
||||
setTimeout(() => {
|
||||
deleteFileWithRetry(filePath, maxRetries, interval, attempt + 1);
|
||||
}, interval);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function deleteDirectoryWithRetry(dirPath) {
|
||||
await fs.rm(dirPath, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports("createScreenshotFilename", async function (directory) {
|
||||
try {
|
||||
// Get all jpg files with their full paths
|
||||
let files = fs
|
||||
.readdirSync(directory)
|
||||
.filter((file) => file.endsWith(".jpg"))
|
||||
.map((file) => ({
|
||||
name: file,
|
||||
time: fs.statSync(path.join(directory, file)).mtime.getTime(),
|
||||
}))
|
||||
.sort((a, b) => a.time - b.time); // Sort files by modification time, oldest first
|
||||
|
||||
let nextFileNumber;
|
||||
if (files.length >= 10) {
|
||||
// If we have 10 or more files, increment the highest number by 1 and delete the oldest file if more than 10 files exist
|
||||
const highestNumber = Math.max(...files.map((file) => parseInt(file.name.replace(".jpg", ""), 10)));
|
||||
nextFileNumber = highestNumber + 1;
|
||||
if (files.length > 10) {
|
||||
// Delete the oldest file
|
||||
const oldestFile = files[0].name;
|
||||
deleteFileWithRetry(path.join(directory, oldestFile));
|
||||
}
|
||||
} else {
|
||||
// If less than 10 files, find the first number not used
|
||||
let existingNumbers = files.map((file) => parseInt(file.name, 10));
|
||||
nextFileNumber = 1;
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
if (!existingNumbers.includes(i)) {
|
||||
nextFileNumber = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return `${nextFileNumber}.jpg`;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
exports("deleteDirectory", async function (dir) {
|
||||
try {
|
||||
if (fs.existsSync(dir)) {
|
||||
deleteDirectoryWithRetry(dir);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
exports("clearScreenshotsFolder", async function () {
|
||||
try {
|
||||
let dir = `${GetResourcePath(GetCurrentResourceName())}/screenshots`;
|
||||
if (fs.existsSync(dir)) {
|
||||
deleteDirectoryWithRetry(dir);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
light_port = 9990
|
||||
light_last_event = "restore"
|
||||
|
||||
local function runEvent(event)
|
||||
if event == nil then
|
||||
return
|
||||
end
|
||||
if event == light_last_event or (light_last_event == "panic" and event ~= "restore") then
|
||||
return
|
||||
end
|
||||
light_last_event = event
|
||||
debugLog("send light event "..json.encode({ type = "light_event", event = event, port = light_port }))
|
||||
SendNUIMessage({ type = "light_event", event = event, port = light_port })
|
||||
end
|
||||
|
||||
local function vehicleSignalState(veh)
|
||||
local lights = GetVehicleIndicatorLights(veh)
|
||||
if lights ~= nil then
|
||||
if lights == 0 then
|
||||
return "restore"
|
||||
elseif lights == 1 then
|
||||
return "left"
|
||||
elseif lights == 2 then
|
||||
return "right"
|
||||
elseif lights == 3 then
|
||||
return "hazard"
|
||||
else
|
||||
return "restore"
|
||||
end
|
||||
else
|
||||
return "restore"
|
||||
end
|
||||
end
|
||||
|
||||
local function vehicleEmergencyState(veh)
|
||||
return (IsVehicleSirenOn(veh) == 1)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
while not NetworkIsPlayerActive(PlayerId()) do
|
||||
Wait(10)
|
||||
end
|
||||
while true do
|
||||
local ped = GetPlayerPed(PlayerId())
|
||||
if IsPedInAnyVehicle(ped, false) then
|
||||
local veh = GetVehiclePedIsIn(ped, false)
|
||||
if veh then
|
||||
if vehicleEmergencyState(veh) then
|
||||
runEvent("lights")
|
||||
else
|
||||
local state = vehicleSignalState(veh)
|
||||
if state ~= light_last_event then
|
||||
runEvent(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
runEvent("restore")
|
||||
end
|
||||
Wait(1000)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand("setlightport", function(source, args, rawCommand)
|
||||
local port = args[1]
|
||||
if args[1] == nil or not tonumber(args[1]) then
|
||||
return print("Invalid argument.")
|
||||
end
|
||||
port = tonumber(port)
|
||||
if port < 1 or port > 65535 then
|
||||
return print("Invalid port")
|
||||
end
|
||||
light_port = port
|
||||
end)
|
||||
@@ -0,0 +1,146 @@
|
||||
local MessageBuffer = {}
|
||||
local DebugBuffer = {}
|
||||
local ErrorBuffer = {}
|
||||
|
||||
local ErrorCodes = {
|
||||
['STEAM_ERROR'] = "You have set SonoranCAD to Steam mode, but have not configured a Steam Web API key. Please see FXServer documentation. SonoranCAD will not function in Steam mode without this set.",
|
||||
['PORT_MISSING_ERROR'] = "Could not find valid server information for server ID %s. Ensure you have configured your server in the CAD before using the map or push events.",
|
||||
['PORT_CONFIG_ERROR'] = "CONFIGURATION PROBLEM: Your current game server port (%s) does not match your CAD configuration (%s). Please ensure they match.",
|
||||
['MAP_CONFIG_ERROR'] = "CONFIGURATION PROBLEM: Map port on the server (%s) does not match your CAD configuration (%s) for server ID (%s). Please ensure they match.",
|
||||
['PORT_OUTBOUND_ERROR'] = "CONFIGURATION PROBLEM: Detected outbound IP (%s), but (%s) is configured in the CAD. They must match!",
|
||||
['PORT_OUTBOUND_MISMATCH'] = "CONFIGURATION PROBLEM: Detected IP (%s), but (%s) is configured in the CAD. They must match!",
|
||||
['CONFIG_ERROR'] = "Failed to load core configuration. Ensure config.json is present and is the correct format.",
|
||||
['API_ERROR'] = "Failed to get version information. Is the API down? Please restart sonorancad.",
|
||||
['API_PAID_ONLY'] = "ERROR: Your community cannot use any plugins requiring the API. Please purchase a subscription of Standard or higher.",
|
||||
['ERROR_ABORT'] = "Aborted startup due to critical errors reported. Review logs for troubleshooting.",
|
||||
['PLUGIN_DEPENDENCY_ERROR'] = "Submodule %s requires %s, which is not loaded! Skipping.",
|
||||
['PLUGIN_VERSION_MISMATCH'] = "PLUGIN ERROR: Plugin %s requires %s at version %s or higher, but only %s was found. Use the command \"sonoran pluginupdate\" to check for updates.",
|
||||
['PLUGIN_CONFIG_OUTDATED'] = "Plugin Updater: %s has a new configuration version (%s ~= %s). You should look at the template configuration file (%s_config.dist.lua) and update your configuration before using this plugin.",
|
||||
['PLUGIN_CORE_OUTDATED'] = "PLUGIN ERROR: Plugin %s requires Core Version %s, but you have %s. Please update SonoranCAD to use this plugin. Force disabled.",
|
||||
['CUSTOM_POSTALS_FILE_NOT_FOUND'] = "Your custom postals file %s could not be found in the /sonorancad/submodules/postals/ directory. Please ensure it exists and is not corrupted.",
|
||||
['POSTAL_RESOURCE_MISSING'] = "The configured postals resource (%s) does not exist. Please check the name.",
|
||||
['POSTAL_RESOURCE_STOPPED'] = "The postals resource (%s) is not started. Please ensure it's started before clients connect. This is only a warning.",
|
||||
['POSTAL_RESOURCE_BAD_STATE'] = "The configured postals resource (%s) is in a bad state (%s). Please check it.",
|
||||
['POSTAL_FILE_READ_ERROR'] = "Failed to open postals file for reading",
|
||||
['POSTAL_CUSTOM_RESOURCE_FILE_ERROR'] = "Failed to locate postal file from resource %s! Please ensure that it is defined in %s's fxmanifest.lua as 'postal_file'",
|
||||
['IDCARD_RESOURCE_NOT_STARTED'] = "The sonoran_idcard resource seems to be stopped, this resource is required for the ID card UI to work. Attempting to start the resource. Please add it to your server.cfg to run by default.",
|
||||
['IDCARD_RESOURCE_MISSING'] = "The sonoran_idcard resource seems to be missing, this resource is required for the the ID card UI to work. Please ensure the resource name is exactly: 'sonoran_idcard' or install it from our GitHub: https://github.com/Sonoran-Software/id_card_ui",
|
||||
['IDCARD_RESOURCE_BAD_STATE'] = "The sonoran_idcard resource is in a bad state. Please check it.",
|
||||
['INCORRECT_WKWARS2X_VERSION'] = "It appears that you are using an incorrect version of the resource wk_wars2x. Please ensure you install the version from our GitHub: https://github.com/Sonoran-Software/wk_wars2x. The version you are using is not compatible with SonoranCAD.",
|
||||
}
|
||||
|
||||
function getErrorText(err)
|
||||
return ErrorCodes[err]
|
||||
end
|
||||
|
||||
local function LocalTime()
|
||||
local _, _, _, h, m, s = GetLocalTime()
|
||||
return '' .. h .. ':' .. m .. ':' .. s
|
||||
end
|
||||
|
||||
local function sendConsole(level, color, message)
|
||||
local debugging = true
|
||||
if Config ~= nil then
|
||||
debugging = (Config.debugMode == true and Config.debugMode ~= "false")
|
||||
end
|
||||
local time = os and os.date("%X") or LocalTime()
|
||||
local info = debug.getinfo(3, 'S')
|
||||
local source = "."
|
||||
if info.source:find("@@sonorancad") then
|
||||
source = info.source:gsub("@@sonorancad/","")..":"..info.linedefined
|
||||
end
|
||||
local msg = ("[%s][%s:%s%s^7]%s %s^0"):format(time, debugging and source or "SonoranCAD", color, level, color, message)
|
||||
if (debugging and level == "DEBUG") or (not debugging and level ~= "DEBUG") then
|
||||
print(msg)
|
||||
end
|
||||
if (level == "ERROR" or level == "WARNING") and IsDuplicityVersion() then
|
||||
table.insert(ErrorBuffer, 1, msg)
|
||||
end
|
||||
if level == "DEBUG" and IsDuplicityVersion() then
|
||||
if #DebugBuffer > 50 then
|
||||
table.remove(DebugBuffer)
|
||||
end
|
||||
table.insert(DebugBuffer, 1, msg)
|
||||
else
|
||||
if not IsDuplicityVersion() then
|
||||
if #MessageBuffer > 10 then
|
||||
table.remove(MessageBuffer)
|
||||
end
|
||||
table.insert(MessageBuffer, 1, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getDebugBuffer()
|
||||
return DebugBuffer
|
||||
end
|
||||
|
||||
function getErrorBuffer()
|
||||
return ErrorBuffer
|
||||
end
|
||||
|
||||
function debugLog(message)
|
||||
if Config == nil then
|
||||
return
|
||||
end
|
||||
sendConsole("DEBUG", "^7", message)
|
||||
end
|
||||
|
||||
function debugPrint(message)
|
||||
debugLog(message)
|
||||
end
|
||||
|
||||
function logError(err, msg)
|
||||
local o = ""
|
||||
if msg == nil then
|
||||
o = ("ERR %s: %s - See https://sonoran.software/errorcodes for more information."):format(err, ErrorCodes[err])
|
||||
else
|
||||
o = ("ERR %s: %s - See https://sonoran.software/errorcodes for more information."):format(err, msg)
|
||||
end
|
||||
sendConsole("ERROR", "^1", o)
|
||||
end
|
||||
|
||||
function errorLog(message)
|
||||
sendConsole("ERROR", "^1", message)
|
||||
end
|
||||
|
||||
function warnLog(message)
|
||||
sendConsole("WARNING", "^3", message)
|
||||
end
|
||||
|
||||
function infoLog(message)
|
||||
sendConsole("INFO", "^5", message)
|
||||
end
|
||||
|
||||
--RegisterServerEvent("SonoranCAD::core:writeLog")
|
||||
AddEventHandler("SonoranCAD::core:writeLog", function(level, message)
|
||||
if level == "debug" then
|
||||
debugLog(message)
|
||||
elseif level == "info" then
|
||||
infoLog(message)
|
||||
elseif level == "error" then
|
||||
errorLog(message)
|
||||
elseif level == "warn" then
|
||||
warnLog(message)
|
||||
else
|
||||
debugLog(message)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("SonoranCAD::core:RequestLogBuffer")
|
||||
AddEventHandler("SonoranCAD::core:RequestLogBuffer", function()
|
||||
if not IsDuplicityVersion() then
|
||||
TriggerServerEvent("SonoranCAD::core:LogBuffer", MessageBuffer)
|
||||
print("log buffer requested")
|
||||
end
|
||||
end)
|
||||
|
||||
print(("^5%s^0"):format([[
|
||||
_____ _________ ____
|
||||
/ ___/____ ____ ____ _________ _____ / ____/ | / __ \
|
||||
\__ \/ __ \/ __ \/ __ \/ ___/ __ `/ __ \/ / / /| | / / / /
|
||||
___/ / /_/ / / / / /_/ / / / /_/ / / / / /___/ ___ |/ /_/ /
|
||||
/____/\____/_/ /_/\____/_/ \__,_/_/ /_/\____/_/ |_/_____/
|
||||
|
||||
]]))
|
||||
infoLog("Starting up...")
|
||||
@@ -0,0 +1,148 @@
|
||||
--[[
|
||||
SonoranCAD FiveM Integration
|
||||
|
||||
Plugin Loader
|
||||
|
||||
Provides logic for checking loaded plugins after startup
|
||||
]]
|
||||
|
||||
local function LoadVersionFile()
|
||||
local f = LoadResourceFile(GetCurrentResourceName(), ("version.json"))
|
||||
if f then
|
||||
return f
|
||||
else
|
||||
warnLog(("Failed to load version file from /sonorancad/version.json Check to see if the file exists."))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function CheckForPluginUpdate(name)
|
||||
local plugin = Config.plugins[name]
|
||||
plugin.check_url = 'https://raw.githubusercontent.com/Sonoran-Software/SonoranCADFiveM/refs/heads/master/sonorancad/version.json'
|
||||
if plugin == nil then
|
||||
errorLog(("Submodule %s not found."):format(name))
|
||||
return
|
||||
elseif plugin.check_url == nil or plugin.check_url == "" then
|
||||
debugLog(("Submodule %s does not have check_url set. Is it configured correctly?"):format(name))
|
||||
return
|
||||
end
|
||||
PerformHttpRequestS(plugin.check_url, function(code, data, headers)
|
||||
if code == 200 then
|
||||
local remote = json.decode(data)
|
||||
if remote == nil then
|
||||
if plugin.enabled then
|
||||
warnLog(("Failed to get a valid response for %s. Skipping."):format(k))
|
||||
debugLog(("Raw output for %s: %s"):format(k, data))
|
||||
end
|
||||
elseif (remote.submoduleConfigs[name].version ~= nil and plugin.configVersion ~= nil) then
|
||||
local configCompare = compareVersions(remote.submoduleConfigs[name].version, plugin.configVersion)
|
||||
if configCompare.result and not Config.debugMode then
|
||||
if plugin.enabled then
|
||||
errorLog(("Submodule Updater: %s has a new configuration version. You should look at the template configuration file (%s_config.dist.lua) and update your configuration before using this submodule. Guide: https://sonoran.link/config-update"):format(name, name))
|
||||
Config.plugins[name].enabled = false
|
||||
Config.plugins[name].disableReason = "outdated config file"
|
||||
end
|
||||
else
|
||||
debugLog(("Submodule %s has the same configuration version."):format(name))
|
||||
local distConfig = LoadResourceFile(GetCurrentResourceName(), ("/configuration/%s_config.dist.lua"):format(name))
|
||||
local normalConfig = LoadResourceFile(GetCurrentResourceName(), ("/configuration/%s_config.lua"):format(name))
|
||||
if distConfig and normalConfig then
|
||||
local filePath = ("%s/configuration/config-backup"):format(GetResourcePath(GetCurrentResourceName()))
|
||||
exports['sonorancad']:CreateFolderIfNotExisting(filePath)
|
||||
local backupFile = io.open(("%s/configuration/config-backup/%s_config.lua"):format(GetResourcePath(GetCurrentResourceName()), name), "w")
|
||||
backupFile:write(distConfig)
|
||||
backupFile:close()
|
||||
os.remove(("%s/configuration/%s_config.dist.lua"):format(GetResourcePath(GetCurrentResourceName()), name))
|
||||
debugLog(("Submodule %s configuration file is up to date. Backup saved."):format(name))
|
||||
end
|
||||
end
|
||||
elseif name == 'locations' then
|
||||
if (remote.submoduleConfigs[name].version ~= nil and plugin.pluginVersion ~= nil) then
|
||||
local configCompare = compareVersions(remote.submoduleConfigs[name].version, plugin.pluginVersion)
|
||||
if configCompare.result and not Config.debugMode then
|
||||
if plugin.enabled then
|
||||
errorLog(("Submodule Updater: %s has a new configuration version. You should look at the template configuration file (%s_config.dist.lua) and update your configuration before using this submodule."):format(name, name))
|
||||
Config.plugins[name].enabled = false
|
||||
Config.plugins[name].disableReason = "outdated config file"
|
||||
end
|
||||
else
|
||||
debugLog(("Submodule %s has the same configuration version."):format(name))
|
||||
local distConfig = LoadResourceFile(GetCurrentResourceName(), ("/configuration/%s_config.dist.lua"):format(name))
|
||||
local normalConfig = LoadResourceFile(GetCurrentResourceName(), ("/configuration/%s_config.lua"):format(name))
|
||||
if distConfig and normalConfig then
|
||||
local filePath = ("%s/configuration/config-backup"):format(GetResourcePath(GetCurrentResourceName()))
|
||||
exports['sonorancad']:CreateFolderIfNotExisting(filePath)
|
||||
local backupFile = io.open(("%s/configuration/config-backup/%s_config.lua"):format(GetResourcePath(GetCurrentResourceName()), name), "w")
|
||||
backupFile:write(distConfig)
|
||||
backupFile:close()
|
||||
os.remove(("%s/configuration/%s_config.dist.lua"):format(GetResourcePath(GetCurrentResourceName()), name))
|
||||
debugLog(("Submodule %s configuration file is up to date. Backup saved."):format(name))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif plugin.enabled then
|
||||
warnLog(("Failed to check submodule config updates for %s: %s %s"):format(name, code, data))
|
||||
end
|
||||
end, "GET")
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
Wait(5000)
|
||||
while Config.apiVersion == -1 do Wait(10) end
|
||||
if Config.critError then logError("ERROR_ABORT") end
|
||||
for k, v in pairs(Config.plugins) do
|
||||
if Config.critError then
|
||||
Config.plugins[k].enabled = false
|
||||
Config.plugins[k].disableReason = "Startup aborted"
|
||||
goto skip
|
||||
end
|
||||
local vfile = LoadVersionFile(k)
|
||||
if vfile == nil then
|
||||
goto skip
|
||||
end
|
||||
local versionFile = json.decode(vfile)
|
||||
if Config.plugins[k].enabled then
|
||||
if versionFile.submoduleConfigs[k].requiresPlugins ~= nil then
|
||||
for _, plugin in pairs(versionFile.submoduleConfigs[k].requiresPlugins) do
|
||||
local isCritical = plugin.critical
|
||||
if Config.plugins[plugin.name] == nil or not Config.plugins[plugin.name].enabled then
|
||||
if isCritical then
|
||||
logError("PLUGIN_DEPENDENCY_ERROR", getErrorText("PLUGIN_DEPENDENCY_ERROR"):format(k, plugin.name))
|
||||
Config.plugins[k].enabled = false
|
||||
Config.plugins[k].disableReason = ("Missing dependency %s"):format(plugin.name)
|
||||
elseif plugin.name ~= "esxsupport" then
|
||||
warnLog(("[submodule loader] submodule %s requires %s, but it is not installed. Some features may not work properly."):format(k, plugin.name))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
CheckForPluginUpdate(k)
|
||||
end
|
||||
::skip::
|
||||
local pluginList = {}
|
||||
local loadedPlugins = {}
|
||||
local disabledPlugins = {}
|
||||
local disableFormatted = {}
|
||||
for name, v in pairs(Config.plugins) do
|
||||
table.insert(pluginList, name)
|
||||
if v.enabled then
|
||||
table.insert(loadedPlugins, name)
|
||||
else
|
||||
if v.disableReason == nil then
|
||||
v.disableReason = "disabled in config"
|
||||
end
|
||||
disabledPlugins[name] = v.disableReason
|
||||
end
|
||||
end
|
||||
infoLog(("Available Submodules: %s"):format(table.concat(pluginList, ", ")))
|
||||
infoLog(("Loaded Submodules: %s"):format(table.concat(loadedPlugins, ", ")))
|
||||
for name, reason in pairs(disabledPlugins) do
|
||||
table.insert(disableFormatted, ("%s (%s)"):format(name, reason))
|
||||
end
|
||||
if #disableFormatted > 0 then
|
||||
infoLog(("Disabled Submodules: %s"):format(
|
||||
table.concat(disableFormatted, ", ")))
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,33 @@
|
||||
latestFrame = {};
|
||||
|
||||
RegisterNetEvent('SonoranCAD::core:TakeScreenshot', function()
|
||||
local source = source
|
||||
local unit = GetUnitByPlayerId(source)
|
||||
if unit == nil then
|
||||
debugLog('Unit not found')
|
||||
-- TriggerClientEvent('SonoranCAD::core::ScreenshotOff', source)
|
||||
return
|
||||
end
|
||||
local screenshotDirectory = exports['sonorancad']:createScreenshotDirectory(tostring(unit.id))
|
||||
local screenshotName = exports['sonorancad']:createScreenshotFilename(screenshotDirectory)
|
||||
local frameName = screenshotName:gsub("%.jpg$", "")
|
||||
latestFrame[source] = tonumber(frameName)
|
||||
exports['screenshot-basic']:requestClientScreenshot(source, {
|
||||
fileName = screenshotDirectory .. '/' .. screenshotName,
|
||||
quality = 0.5
|
||||
}, function()
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('SonoranCAD::core::bodyCamOff', function()
|
||||
local source = source
|
||||
latestFrame[source] = nil
|
||||
local unit = GetUnitByPlayerId(source)
|
||||
if unit == nil then
|
||||
debugLog('Unit not found')
|
||||
-- TriggerClientEvent('SonoranCAD::core::ScreenshotOff', source)
|
||||
return
|
||||
end
|
||||
local screenshotDirectory = exports['sonorancad']:createScreenshotDirectory(tostring(unit.id))
|
||||
exports['sonorancad']:deleteDirectory(screenshotDirectory)
|
||||
end)
|
||||
@@ -0,0 +1,530 @@
|
||||
Plugins = {}
|
||||
|
||||
ApiUrls = {
|
||||
production = "https://api.sonorancad.com/",
|
||||
development = "https://cadapi.dev.sonoransoftware.com/"
|
||||
}
|
||||
|
||||
function getApiUrl()
|
||||
if Config.mode == nil then
|
||||
return ApiUrls.production
|
||||
else
|
||||
if ApiUrls[Config.mode] ~= nil then
|
||||
return ApiUrls[Config.mode]
|
||||
else
|
||||
Config.critError = true
|
||||
assert(false, "Invalid mode. Valid values are production, development")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
infoLog("Starting SonoranCAD from "..GetResourcePath("sonorancad"))
|
||||
Config.apiUrl = getApiUrl()
|
||||
exports['sonorancad']:clearScreenshotsFolder()
|
||||
performApiRequest({}, "GET_VERSION", function(result, ok)
|
||||
if not ok then
|
||||
logError("API_ERROR")
|
||||
Config.critError = true
|
||||
return
|
||||
end
|
||||
Config.apiVersion = tonumber(string.sub(result, 1, 1))
|
||||
if Config.apiVersion < 2 then
|
||||
logError("API_PAID_ONLY")
|
||||
Config.critError = true
|
||||
end
|
||||
debugLog(("Set version %s from response %s"):format(Config.apiVersion, result))
|
||||
infoLog(("Loaded community ID %s with API URL: %s"):format(Config.communityID, Config.apiUrl))
|
||||
end)
|
||||
if Config.primaryIdentifier == "steam" and (GetConvar("steam_webapiKey", "none") == "none" or GetConvar("steam_webapiKey", "none") == "") then
|
||||
logError("STEAM_ERROR")
|
||||
Config.critError = true
|
||||
end
|
||||
local versionfile = json.decode(LoadResourceFile(GetCurrentResourceName(), "/version.json"))
|
||||
local fxversion = versionfile.testedFxServerVersion
|
||||
local currentFxVersion = getServerVersion()
|
||||
if currentFxVersion ~= nil and fxversion ~= nil then
|
||||
if tonumber(currentFxVersion) < tonumber(fxversion) then
|
||||
warnLog(("SonoranCAD has been tested with FXServer version %s, but you're running %s. Please update ASAP."):format(fxversion, currentFxVersion))
|
||||
end
|
||||
end
|
||||
if GetResourceState("sonoran_updatehelper") == "started" then
|
||||
ExecuteCommand("stop sonoran_updatehelper")
|
||||
end
|
||||
manuallySetUnitCache() -- set unit cache on startup
|
||||
end)
|
||||
|
||||
exports("getCadVersion", function()
|
||||
return Config.apiVersion
|
||||
end)
|
||||
|
||||
-- Toggles API sender.
|
||||
RegisterServerEvent("cadToggleApi")
|
||||
AddEventHandler("cadToggleApi", function()
|
||||
Config.apiSendEnabled = not Config.apiSendEnabled
|
||||
end)
|
||||
|
||||
--[[
|
||||
Sonoran CAD API Handler - Core Wrapper
|
||||
]]
|
||||
|
||||
ApiEndpoints = {
|
||||
["UNIT_LOCATION"] = "emergency",
|
||||
["CALL_911"] = "emergency",
|
||||
["UNIT_PANIC"] = "emergency",
|
||||
["GET_VERSION"] = "general",
|
||||
["GET_SERVERS"] = "general",
|
||||
["ATTACH_UNIT"] = "emergency",
|
||||
["DETACH_UNIT"] = "emergency",
|
||||
["ADD_CALL_NOTE"] = "emergency",
|
||||
["RECORD_ADD"] = "general",
|
||||
["RECORD_UPDATE"] = "general",
|
||||
["SET_SERVERS"] = "general",
|
||||
["GET_CHARACTERS"] = "civilian",
|
||||
["EDIT_CHARACTER"] = "civilian",
|
||||
["NEW_RECORD"] = "general",
|
||||
["EDIT_RECORD"] = "general",
|
||||
["REMOVE_RECORD"] = "general",
|
||||
["GET_TEMPLATES"] = "general",
|
||||
["LOOKUP_INT"] = "general",
|
||||
}
|
||||
|
||||
EndpointsRequireId = {
|
||||
["UNIT_STATUS"] = true,
|
||||
["KICK_UNIT"] = true,
|
||||
["UNIT_PANIC"] = true,
|
||||
["UNIT_LOCATION"] = true,
|
||||
["NEW_CHARACTER"] = true,
|
||||
["REMOVE_CHARACTER"] = true,
|
||||
["EDIT_CHARACTER"] = true,
|
||||
["GET_CHARACTERS"] = true,
|
||||
["CHECK_APIID"] = true,
|
||||
["APPLY_PERMISSION_KEY"] = true,
|
||||
["BAN_USER"] = true,
|
||||
["KICK_USER"] = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
function registerApiType(type, endpoint)
|
||||
ApiEndpoints[type] = endpoint
|
||||
end
|
||||
exports("registerApiType", registerApiType)
|
||||
|
||||
local rateLimitedEndpoints = {}
|
||||
|
||||
function performApiRequest(postData, type, cb)
|
||||
-- apply required headers
|
||||
local payload = {}
|
||||
payload["id"] = Config.communityID
|
||||
payload["key"] = Config.apiKey
|
||||
payload["data"] = postData
|
||||
payload["type"] = type
|
||||
local endpoint = nil
|
||||
if ApiEndpoints[type] ~= nil then
|
||||
endpoint = ApiEndpoints[type]
|
||||
else
|
||||
return warnLog(("API request failed: endpoint %s is not registered. Use the registerApiType function to register this endpoint with the appropriate type."):format(type))
|
||||
end
|
||||
if not cb then
|
||||
cb = function() end
|
||||
end
|
||||
local url = ""
|
||||
if endpoint == "support" then
|
||||
apiUrl = "https://api.sonoransoftware.com/"
|
||||
url = apiUrl..tostring(endpoint).."/"
|
||||
else
|
||||
apiUrl = getApiUrl()
|
||||
url = apiUrl..tostring(endpoint).."/"..tostring(type:lower())
|
||||
end
|
||||
assert(type ~= nil, "No type specified, invalid request.")
|
||||
if Config.critError then
|
||||
return
|
||||
elseif not Config.apiSendEnabled then
|
||||
warnLog("API sending is disabled, ignoring request.")
|
||||
return
|
||||
end
|
||||
if rateLimitedEndpoints[type] == nil then
|
||||
PerformHttpRequestS(url, function(statusCode, res, headers)
|
||||
debugLog(("type %s called with post data %s to url %s"):format(type, json.encode(payload), url))
|
||||
if statusCode == 200 and res ~= nil then
|
||||
debugLog("result: "..tostring(res))
|
||||
if res == "Sonoran CAD: Backend Service Reached" or res == "Backend Service Reached" then
|
||||
errorLog(("API ERROR: Invalid endpoint (URL: %s). Ensure you're using a valid endpoint."):format(url))
|
||||
else
|
||||
if res == nil then
|
||||
res = {}
|
||||
debugLog("Warning: Response had no result, setting to empty table.")
|
||||
end
|
||||
cb(res, true)
|
||||
end
|
||||
elseif statusCode == 400 then
|
||||
warnLog("Bad request was sent to the API. Enable debug mode and retry your request. Response: "..tostring(res))
|
||||
-- additional safeguards
|
||||
if res == "INVALID COMMUNITY ID"
|
||||
or res == "API IS NOT ENABLED FOR THIS COMMUNITY"
|
||||
or string.find(res, "IS NOT ENABLED FOR THIS COMMUNITY")
|
||||
or res == "INVALID API KEY" then
|
||||
errorLog("Fatal: Disabling API - an error was encountered that must be resolved. Please restart the resource after resolving: "..tostring(res))
|
||||
Config.apiSendEnabled = false
|
||||
end
|
||||
cb(res, false)
|
||||
elseif statusCode == 404 then -- handle 404 requests, like from CHECK_APIID
|
||||
debugLog("404 response found")
|
||||
cb(res, false)
|
||||
elseif statusCode == 429 then -- rate limited :(
|
||||
if rateLimitedEndpoints[type] then
|
||||
-- don't warn again, it's spammy. Instead, just print a debug
|
||||
debugLog(("Endpoint %s ratelimited. Dropping request."))
|
||||
return
|
||||
end
|
||||
rateLimitedEndpoints[type] = true
|
||||
warnLog(("WARN_RATELIMIT: You are being ratelimited (last request made to %s) - Ignoring all API requests to this endpoint for 60 seconds. If this is happening frequently, please review your configuration to ensure you're not sending data too quickly."):format(type))
|
||||
SetTimeout(60000, function()
|
||||
rateLimitedEndpoints[type] = nil
|
||||
infoLog(("Endpoint %s no longer ignored."):format(type))
|
||||
end)
|
||||
elseif string.match(tostring(statusCode), "50") then
|
||||
errorLog(("API error returned (%s). Check status.sonoransoftware.com or our Discord to see if there's an outage."):format(statusCode))
|
||||
debugLog(("API_ERROR Error returned: %s %s"):format(statusCode, res))
|
||||
else
|
||||
errorLog(("CAD API ERROR (from %s): %s %s"):format(url, statusCode, res))
|
||||
end
|
||||
end, "POST", json.encode(payload), {["Content-Type"]="application/json"})
|
||||
else
|
||||
debugLog(("Endpoint %s is ratelimited. Dropped request: %s"):format(type, json.encode(payload)))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
exports("performApiRequest", performApiRequest)
|
||||
|
||||
-- Metrics
|
||||
CreateThread(function()
|
||||
registerApiType("HEARTBEAT", "general")
|
||||
while true do
|
||||
-- Wait a few seconds for server startup
|
||||
Wait(5000)
|
||||
local coreVersion = GetResourceMetadata(GetCurrentResourceName(), "version", 0)
|
||||
SetConvarServerInfo("SonoranCAD", coreVersion)
|
||||
local plugins = {}
|
||||
local playerCount = GetNumPlayerIndices()
|
||||
for k, v in pairs(Config.plugins) do
|
||||
table.insert(plugins, {["name"] = k, ["version"] = v.version, ["latest"] = v.latestVersion, ["enabled"] = v.enabled})
|
||||
end
|
||||
local payload = {
|
||||
coreVersion = coreVersion,
|
||||
commId = Config.communityID,
|
||||
playerCount = playerCount,
|
||||
serverId = Config.serverId,
|
||||
fxVersion = getServerVersion(),
|
||||
plugins = plugins,
|
||||
ingressUrl = GetConvar("web_baseUrl", "")
|
||||
}
|
||||
performApiRequest(payload, "HEARTBEAT", function() end)
|
||||
Wait(1000*60*60)
|
||||
end
|
||||
end)
|
||||
|
||||
if Config.devHiddenSwitch then
|
||||
RegisterCommand("cc", function()
|
||||
TriggerClientEvent("chat:clear", -1)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Missing identifier detection
|
||||
RegisterNetEvent("SonoranCAD::core:PlayerReady")
|
||||
AddEventHandler("SonoranCAD::core:PlayerReady", function()
|
||||
local ids = GetIdentifiers(source)
|
||||
if ids[Config.primaryIdentifier] == nil then
|
||||
warnLog(("Player %s connected, but did not have an %s ID."):format(source, Config.primaryIdentifier))
|
||||
end
|
||||
end)
|
||||
|
||||
-- Jordan - Add universal handler for 911 calls
|
||||
--[[
|
||||
SonoranCAD API Handler - 911 Calls
|
||||
@param caller string
|
||||
@param location string
|
||||
@param description string
|
||||
@param postal number
|
||||
@param plate string (optional)
|
||||
@param cb function
|
||||
@param silenceAlert boolean
|
||||
@param useCallLocation boolean
|
||||
]]
|
||||
function call911(caller, location, description, postal, plate, cb, silenceAlert, useCallLocation)
|
||||
if not silenceAlert then
|
||||
silenceAlert = false
|
||||
end
|
||||
if not useCallLocation then
|
||||
useCallLocation = false
|
||||
end
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1),
|
||||
['isEmergency'] = true,
|
||||
['caller'] = caller,
|
||||
['location'] = location,
|
||||
['description'] = description,
|
||||
['metaData'] = {
|
||||
['plate'] = plate,
|
||||
['postal'] = postal,
|
||||
['useCallLocation'] = useCallLocation,
|
||||
['silenceAlert'] = silenceAlert
|
||||
}
|
||||
}
|
||||
}, 'CALL_911', cb)
|
||||
end
|
||||
|
||||
RegisterNetEvent('SonoranScripts::Call911', function(caller, location, description, postal, plate, cb, silenceAlert, useCallLocation)
|
||||
call911(caller, location, description, postal, plate, function(response)
|
||||
json.encode(response) -- Not, CB's can only be used on the server side, so we just print this here for you to see.
|
||||
end, silenceAlert, useCallLocation)
|
||||
end)
|
||||
|
||||
-- Jordan - CAD Utils
|
||||
dispatchOnline = false
|
||||
ActiveDispatchers = {}
|
||||
|
||||
registerEndpoints = function()
|
||||
exports['sonorancad']:registerApiType('MODIFY_BLIP', 'emergency')
|
||||
exports['sonorancad']:registerApiType('ADD_BLIP', 'emergency')
|
||||
exports['sonorancad']:registerApiType('REMOVE_BLIP', 'emergency')
|
||||
exports['sonorancad']:registerApiType('GET_BLIPS', 'emergency')
|
||||
exports['sonorancad']:registerApiType('MODIFY_BLIP', 'emergency')
|
||||
exports['sonorancad']:registerApiType('CALL_911', 'emergency')
|
||||
exports['sonorancad']:registerApiType('ADD_CALL_NOTE', 'emergency')
|
||||
exports['sonorancad']:registerApiType('REMOVE_911', 'emergency')
|
||||
exports['sonorancad']:registerApiType('LOOKUP', 'general')
|
||||
exports['sonorancad']:registerApiType('SET_CALL_POSTAL', 'emergency')
|
||||
exports['sonorancad']:registerApiType('GET_ACTIVE_UNITS', 'emergency')
|
||||
end
|
||||
addBlip = function(coords, colorHex, subType, toolTip, icon, dataTable, cb)
|
||||
local data = {
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1),
|
||||
['blip'] = {
|
||||
['id'] = -1,
|
||||
['subType'] = subType,
|
||||
['coordinates'] = {
|
||||
['x'] = coords.x,
|
||||
['y'] = coords.y
|
||||
},
|
||||
['icon'] = icon,
|
||||
['color'] = colorHex,
|
||||
['tooltip'] = toolTip,
|
||||
['data'] = dataTable
|
||||
}
|
||||
}
|
||||
}
|
||||
exports['sonorancad']:performApiRequest(data, 'ADD_BLIP', function(res)
|
||||
if cb ~= nil then
|
||||
cb(res)
|
||||
end
|
||||
end)
|
||||
end
|
||||
addBlips = function(blips, cb)
|
||||
exports['sonorancad']:performApiRequest(blips, 'ADD_BLIP', function(res)
|
||||
if cb ~= nil then
|
||||
cb(res)
|
||||
end
|
||||
end)
|
||||
end
|
||||
removeBlip = function(ids, cb)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['ids'] = ids
|
||||
}
|
||||
}, 'REMOVE_BLIP', function(res)
|
||||
if cb ~= nil then
|
||||
cb(res)
|
||||
end
|
||||
end)
|
||||
end
|
||||
modifyBlipd = function(blipId, dataTable)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['id'] = blipId,
|
||||
['data'] = dataTable
|
||||
}
|
||||
}, 'MODIFY_BLIP', function(_)
|
||||
end)
|
||||
end
|
||||
getBlips = function(cb)
|
||||
local data = {
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1)
|
||||
}
|
||||
}
|
||||
exports['sonorancad']:performApiRequest(data, 'GET_BLIPS', function(res)
|
||||
if cb ~= nil then
|
||||
cb(res)
|
||||
end
|
||||
end)
|
||||
end
|
||||
removeWithSubtype = function(subType, cb)
|
||||
getBlips(function(res)
|
||||
local dres = json.decode(res)
|
||||
local ids = {}
|
||||
if type(dres) == 'table' then
|
||||
for _, v in ipairs(dres) do
|
||||
if v.subType == subType then
|
||||
table.insert(ids, #ids + 1, v.id)
|
||||
end
|
||||
end
|
||||
if #ids > 0 then
|
||||
removeBlip(ids, cb)
|
||||
end
|
||||
else
|
||||
warnLog('No blips were returned.')
|
||||
end
|
||||
end)
|
||||
end
|
||||
call911 = function(caller, location, description, postal, plate, cb)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1),
|
||||
['isEmergency'] = true,
|
||||
['caller'] = caller,
|
||||
['location'] = location,
|
||||
['description'] = description,
|
||||
['metaData'] = {
|
||||
['plate'] = plate,
|
||||
['postal'] = postal
|
||||
}
|
||||
}
|
||||
}, 'CALL_911', cb)
|
||||
end
|
||||
addTempBlipData = function(blipId, blipData, waitSeconds, returnToData)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['id'] = blipId,
|
||||
['data'] = blipData
|
||||
}
|
||||
}, 'MODIFY_BLIP', function(_)
|
||||
|
||||
end)
|
||||
|
||||
Citizen.CreateThread(function()
|
||||
Citizen.Wait(waitSeconds * 1000)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['id'] = blipId,
|
||||
['data'] = returnToData
|
||||
}
|
||||
}, 'MODIFY_BLIP', function(_)
|
||||
|
||||
end)
|
||||
end)
|
||||
end
|
||||
addTempBlipColor = function(blipId, color, waitSeconds, returnToColor)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['id'] = blipId,
|
||||
['color'] = color
|
||||
}
|
||||
}, 'MODIFY_BLIP', function(_)
|
||||
|
||||
end)
|
||||
|
||||
Citizen.CreateThread(function()
|
||||
Citizen.Wait(waitSeconds * 1000)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['id'] = blipId,
|
||||
['color'] = returnToColor
|
||||
}
|
||||
}, 'MODIFY_BLIP', function(_)
|
||||
|
||||
end)
|
||||
end)
|
||||
end
|
||||
remove911 = function(callId)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1),
|
||||
['callId'] = callId
|
||||
}
|
||||
}, 'REMOVE_911', function(_)
|
||||
end)
|
||||
end
|
||||
addCallNote = function(callId, caller)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1),
|
||||
['callId'] = callId,
|
||||
['note'] = caller
|
||||
}
|
||||
}, 'ADD_CALL_NOTE', function(_)
|
||||
end)
|
||||
end
|
||||
setCallPostal = function(callId, postal)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['serverId'] = GetConvar('sonoran_serverId', 1),
|
||||
['callId'] = callId,
|
||||
['postal'] = postal
|
||||
}
|
||||
}, 'SET_CALL_POSTAL', function(_)
|
||||
end)
|
||||
end
|
||||
performLookup = function(plate, cb)
|
||||
exports['sonorancad']:performApiRequest({
|
||||
{
|
||||
['types'] = {
|
||||
2,
|
||||
3
|
||||
},
|
||||
['plate'] = plate,
|
||||
['partial'] = false,
|
||||
['first'] = '',
|
||||
['last'] = '',
|
||||
['mi'] = ''
|
||||
}
|
||||
}, 'LOOKUP', function(res)
|
||||
if cb ~= nil then
|
||||
cb(res)
|
||||
end
|
||||
end)
|
||||
end
|
||||
checkCADSubscriptionType = function()
|
||||
while exports['sonorancad']:getCadVersion() == nil or exports['sonorancad']:getCadVersion() == -1 do
|
||||
Citizen.Wait(100)
|
||||
end
|
||||
local version = exports['sonorancad']:getCadVersion()
|
||||
if version ~= 4 and version == 3 then
|
||||
errorLog('The live map blip feature require the Pro plan for the CAD. It will be disabled for this run.'
|
||||
.. ' We recommend either upgrading your plan or disabling this feature in the config file.')
|
||||
Config.integration.SonoranCAD_integration.addLiveMapBlips = false
|
||||
Config.modified = true
|
||||
TriggerClientEvent(GetCurrentResourceName() .. '::ModifiedConfig', -1, Config)
|
||||
elseif version ~= 4 and version ~= 3 and version ~= 5 and version ~= 6 then
|
||||
errorLog('SonoranCAD integration with this script requires at least a Plus plan for the CAD. It will be'
|
||||
.. ' disabled for this run. We recommend either upgrading your plan or disabling this' .. ' feature in the config file.')
|
||||
Config.integration.SonoranCAD_integration.use = false
|
||||
Config.modified = true
|
||||
TriggerClientEvent(GetCurrentResourceName() .. '::ModifiedConfig', -1, Config)
|
||||
end
|
||||
end
|
||||
getDispatchStatus = function(_)
|
||||
return dispatchOnline
|
||||
end
|
||||
|
||||
exports('registerEndpoints', registerEndpoints)
|
||||
exports('addBlip', addBlip)
|
||||
exports('addBlips', addBlips)
|
||||
exports('removeBlip', removeBlip)
|
||||
exports('modifyBlipd', modifyBlipd)
|
||||
exports('getBlips', getBlips)
|
||||
exports('removeWithSubtype', removeWithSubtype)
|
||||
exports('call911', call911)
|
||||
exports('addTempBlipData', addTempBlipData)
|
||||
exports('addTempBlipColor', addTempBlipColor)
|
||||
exports('remove911', remove911)
|
||||
exports('addCallNote', addCallNote)
|
||||
exports('setCallPostal', setCallPostal)
|
||||
exports('performLookup', performLookup)
|
||||
exports('checkCADSubscriptionType', checkCADSubscriptionType)
|
||||
exports('getDispatchStatus', getDispatchStatus)
|
||||
-- Jordan - CAD Utils
|
||||
@@ -0,0 +1,113 @@
|
||||
function shallowcopy(orig)
|
||||
local orig_type = type(orig)
|
||||
local copy
|
||||
if orig_type == 'table' then
|
||||
copy = {}
|
||||
for orig_key, orig_value in pairs(orig) do
|
||||
copy[orig_key] = orig_value
|
||||
end
|
||||
else -- number, string, boolean, etc
|
||||
copy = orig
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function stringsplit(inputstr, sep)
|
||||
if sep == nil then
|
||||
sep = "%s"
|
||||
end
|
||||
local t={} ; i=1
|
||||
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||
t[i] = str
|
||||
i = i + 1
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Helper function to determine index of given identifier
|
||||
function findIndex(identifier)
|
||||
for i,loc in ipairs(LocationCache) do
|
||||
if loc.apiId == identifier then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function GetIdentifiers(player)
|
||||
local ids = {}
|
||||
for _, id in ipairs(GetPlayerIdentifiers(player)) do
|
||||
local split = stringsplit(id, ":")
|
||||
ids[split[1]] = split[2]
|
||||
end
|
||||
--debugLog("Returning "..json.encode(ids))
|
||||
return ids
|
||||
end
|
||||
|
||||
function isPluginLoaded(pluginName)
|
||||
for k, v in pairs(Plugins) do
|
||||
if v == pluginName then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
exports('isPluginLoaded', isPluginLoaded)
|
||||
|
||||
function PerformHttpRequestS(url, cb, method, data, headers)
|
||||
if not data then
|
||||
data = ""
|
||||
end
|
||||
if not headers then
|
||||
headers = {["X-User-Agent"] = "SonoranCAD"}
|
||||
end
|
||||
exports["sonorancad"]:HandleHttpRequest(url, cb, method, data, headers)
|
||||
end
|
||||
|
||||
function has_value(tab, val)
|
||||
if tab == nil then
|
||||
debugLog("nil passed to has_value, ignore")
|
||||
return false
|
||||
end
|
||||
for index, value in ipairs(tab) do
|
||||
if value == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function getServerVersion()
|
||||
local s = GetConvar("version", "")
|
||||
local v = s:find("v1.0.0.")
|
||||
local e = string.gsub(s:sub(v),"v1.0.0.","")
|
||||
local i = e:sub(1, string.len(e) - e:find(" "))
|
||||
return i
|
||||
end
|
||||
|
||||
function compareVersions(version1, version2)
|
||||
local v1, v2, v3 = version1:match("(%d+)%.(%d*)%.?(%d*)")
|
||||
local r1, r2, r3 = version2:match("(%d+)%.(%d*)%.?(%d*)")
|
||||
|
||||
-- Convert to numbers and default to 0 for minor and patch if missing
|
||||
v1, v2, v3 = tonumber(v1) or 0, tonumber(v2) or 0, tonumber(v3) or 0
|
||||
r1, r2, r3 = tonumber(r1) or 0, tonumber(r2) or 0, tonumber(r3) or 0
|
||||
|
||||
-- Calculate parsed versions with proper weights
|
||||
local parsedVersion1 = v1 * 10000 + v2 * 100 + v3
|
||||
local parsedVersion2 = r1 * 10000 + r2 * 100 + r3
|
||||
|
||||
-- Create debug log table
|
||||
local tbl = {
|
||||
result = (parsedVersion2 < parsedVersion1),
|
||||
parsedVersion1 = parsedVersion1,
|
||||
parsedVersion2 = parsedVersion2,
|
||||
version1 = version1,
|
||||
version2 = version2
|
||||
}
|
||||
debugLog(json.encode(tbl))
|
||||
|
||||
return tbl
|
||||
end
|
||||
@@ -0,0 +1,268 @@
|
||||
local UnitCache = {}
|
||||
local CallCache = {}
|
||||
local EmergencyCache = {}
|
||||
local PlayerUnitMapping = {}
|
||||
|
||||
local function findUnitById(identIds)
|
||||
if identIds == nil then
|
||||
return nil
|
||||
end
|
||||
for k, v in pairs(UnitCache) do
|
||||
if type(identIds) == "number" then
|
||||
if identIds == v.id then
|
||||
return k
|
||||
end
|
||||
else
|
||||
local ids = nil
|
||||
if v.data ~= nil then
|
||||
ids = v.data.apiIds
|
||||
else
|
||||
ids = v.apiIds
|
||||
end
|
||||
for _, id in pairs(ids) do
|
||||
if has_value(identIds, id) then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function GetSourceByApiId(apiIds)
|
||||
if apiIds == nil then return nil end
|
||||
for x=1, #apiIds do
|
||||
for i=0, GetNumPlayerIndices()-1 do
|
||||
local player = GetPlayerFromIndex(i)
|
||||
if player then
|
||||
local identifiers = GetIdentifiers(player)
|
||||
for type, id in pairs(identifiers) do
|
||||
if id == apiIds[x] then
|
||||
return player
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function GetUnitCache() return UnitCache end
|
||||
function GetCallCache() return CallCache end
|
||||
function GetEmergencyCache() return EmergencyCache end
|
||||
function SetUnitCache(k, v)
|
||||
local key = findUnitById(k)
|
||||
if key ~= nil and UnitCache[key] ~= nil then
|
||||
UnitCache[key] = v
|
||||
else
|
||||
table.insert(UnitCache, v)
|
||||
end
|
||||
end
|
||||
function SetCallCache(k, v)
|
||||
CallCache[k] = v
|
||||
TriggerEvent('SonoranCAD::pushevents:CallCacheUpdated')
|
||||
end
|
||||
function SetEmergencyCache(k, v)
|
||||
EmergencyCache[k] = v
|
||||
TriggerEvent('SonoranCAD::pushevents:EmergencyCacheUpdated')
|
||||
end
|
||||
|
||||
|
||||
-- Global function wrapper
|
||||
function GetUnitById(ids) return findUnitById(ids) end
|
||||
|
||||
function GetUnitObjectById(id)
|
||||
if UnitCache[id] ~= nil then
|
||||
return UnitCache[id]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function GetUnitByPlayerId(player)
|
||||
local identifiers = GetIdentifiers(player)
|
||||
local ids = {}
|
||||
for k, v in pairs(identifiers) do
|
||||
table.insert(ids, v)
|
||||
end
|
||||
local index = findUnitById(ids)
|
||||
if index then
|
||||
return UnitCache[index]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
exports('GetUnitByPlayerId', GetUnitByPlayerId)
|
||||
exports('GetUnitCache', GetUnitCache)
|
||||
exports('GetCallCache', GetCallCache)
|
||||
exports('GetEmergencyCache', GetEmergencyCache)
|
||||
exports('GetUnitById', GetUnitById)
|
||||
|
||||
|
||||
AddEventHandler("playerDropped", function()
|
||||
local id = GetUnitByPlayerId(source)
|
||||
local unit = findUnitById(id)
|
||||
if unit then
|
||||
TriggerEvent("SonoranCAD::core:RemovePlayer", source, UnitCache[unit])
|
||||
UnitCache[unit] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("SonoranCAD::pushevents:UnitLogin", function(unit)
|
||||
local playerId = GetSourceByApiId(unit.data.apiIds)
|
||||
if playerId then
|
||||
PlayerUnitMapping[playerId] = unit.id
|
||||
TriggerEvent("SonoranCAD::core:AddPlayer", playerId, unit)
|
||||
TriggerClientEvent("SonoranCAD::core:AddPlayer", playerId, unit)
|
||||
else
|
||||
debugLog(("Unknown unit %s and player %s"):format(json.encode(unit), playerId))
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("SonoranCAD::pushevents:UnitLogout", function(id)
|
||||
if Config.noUnitTimer then
|
||||
local key = findUnitById(id)
|
||||
debugLog(("unitlogout key %s"):format(key))
|
||||
if key then
|
||||
local playerId = GetSourceByApiId(UnitCache[key].data.apiIds)
|
||||
if playerId then
|
||||
debugLog(("Triggering RemovePlayer on ID %s"):format(playerId))
|
||||
TriggerEvent("SonoranCAD::core:RemovePlayer", playerId, UnitCache[key])
|
||||
TriggerClientEvent("SonoranCAD::core:RemovePlayer", playerId)
|
||||
PlayerUnitMapping[playerId] = nil
|
||||
end
|
||||
end
|
||||
SetUnitCache(id, nil)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
registerApiType("GET_ACTIVE_UNITS", "emergency")
|
||||
Citizen.CreateThread(function()
|
||||
Wait(500)
|
||||
while Config.apiVersion == -1 do
|
||||
Wait(1000)
|
||||
end
|
||||
if not Config.apiSendEnabled or (Config.noUnitTimer == "true" or Config.noUnitTimer == true) or Config.apiVersion < 3 then
|
||||
debugLog("Disabling active units routine")
|
||||
return
|
||||
end
|
||||
while true do
|
||||
local OldUnits = {}
|
||||
local NewUnits = {}
|
||||
for k, v in pairs(UnitCache) do
|
||||
OldUnits[k] = v
|
||||
end
|
||||
if GetNumPlayerIndices() > 0 then
|
||||
local payload = { serverId = Config.serverId, unitsOnly = false }
|
||||
performApiRequest({payload}, "GET_ACTIVE_UNITS", function(runits)
|
||||
local allUnits = json.decode(runits)
|
||||
if allUnits ~= nil then
|
||||
for k, v in pairs(allUnits) do
|
||||
local playerId = GetSourceByApiId(v.data.apiIds)
|
||||
if playerId then
|
||||
PlayerUnitMapping[playerId] = v.id
|
||||
table.insert(NewUnits, v)
|
||||
TriggerEvent("SonoranCAD::core:AddPlayer", playerId, v)
|
||||
else
|
||||
debugLog(("Couldn't find unit, not adding %s (%s)"):format(playerId, json.encode(v.data.apiIds)))
|
||||
end
|
||||
end
|
||||
end
|
||||
for k, v in pairs(OldUnits) do
|
||||
local exists = false
|
||||
for _, n in pairs(NewUnits) do
|
||||
if n.id == v.id then
|
||||
exists = true
|
||||
end
|
||||
end
|
||||
if not exists then
|
||||
debugLog(("Removing player %s, not on units list"):format(k))
|
||||
PlayerUnitMapping[k] = nil
|
||||
TriggerEvent("SonoranCAD::core:RemovePlayer", k, v)
|
||||
TriggerClientEvent("SonoranCAD::core:RemovePlayer", k, v)
|
||||
end
|
||||
end
|
||||
UnitCache = {}
|
||||
for k, v in pairs(NewUnits) do
|
||||
debugLog("Insert unit "..json.encode(v))
|
||||
table.insert(UnitCache, v)
|
||||
end
|
||||
end)
|
||||
end
|
||||
Citizen.Wait(60000)
|
||||
end
|
||||
end)
|
||||
|
||||
registerApiType("GET_CALLS", "emergency")
|
||||
CreateThread(function()
|
||||
Wait(1000)
|
||||
while Config.apiVersion == -1 do
|
||||
Wait(10)
|
||||
end
|
||||
if not Config.apiSendEnabled or Config.apiVersion < 3 then
|
||||
debugLog("Too low version or API disabled, skip call caching")
|
||||
return
|
||||
end
|
||||
local payload = { serverId = Config.serverId}
|
||||
while true do
|
||||
performApiRequest({payload},"GET_CALLS",function(response)
|
||||
local calls = json.decode(response)
|
||||
for k, v in pairs(calls.activeCalls) do
|
||||
CallCache[v.callId] = { dispatch = v }
|
||||
end
|
||||
for k, v in pairs(calls.emergencyCalls) do
|
||||
EmergencyCache[v.callId] = v
|
||||
end
|
||||
end)
|
||||
Citizen.Wait(60 * 1000)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
function manuallySetUnitCache()
|
||||
local OldUnits = {}
|
||||
local NewUnits = {}
|
||||
for k, v in pairs(UnitCache) do
|
||||
OldUnits[k] = v
|
||||
end
|
||||
if GetNumPlayerIndices() > 0 then
|
||||
local payload = { serverId = Config.serverId, unitsOnly = false }
|
||||
performApiRequest({payload}, "GET_ACTIVE_UNITS", function(runits)
|
||||
local allUnits = json.decode(runits)
|
||||
if allUnits ~= nil then
|
||||
for _, v in pairs(allUnits) do
|
||||
local playerId = GetSourceByApiId(v.data.apiIds)
|
||||
if playerId then
|
||||
PlayerUnitMapping[playerId] = v.id
|
||||
table.insert(NewUnits, v)
|
||||
TriggerEvent("SonoranCAD::core:AddPlayer", playerId, v)
|
||||
else
|
||||
debugLog(("Couldn't find unit, not adding %s (%s)"):format(playerId, json.encode(v.data.apiIds)))
|
||||
end
|
||||
end
|
||||
end
|
||||
for k, v in pairs(OldUnits) do
|
||||
local exists = false
|
||||
for _, n in pairs(NewUnits) do
|
||||
if n.id == v.id then
|
||||
exists = true
|
||||
end
|
||||
end
|
||||
if not exists then
|
||||
debugLog(("Removing player %s, not on units list"):format(k))
|
||||
PlayerUnitMapping[k] = nil
|
||||
TriggerEvent("SonoranCAD::core:RemovePlayer", k, v)
|
||||
TriggerClientEvent("SonoranCAD::core:RemovePlayer", k, v)
|
||||
end
|
||||
end
|
||||
UnitCache = {}
|
||||
for _, v in pairs(NewUnits) do
|
||||
debugLog("Insert unit "..json.encode(v))
|
||||
table.insert(UnitCache, v)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
exports('ManuallySetUnitCache', manuallySetUnitCache())
|
||||
@@ -0,0 +1,92 @@
|
||||
var unzipper = require("unzipper");
|
||||
var fs = require("fs");
|
||||
|
||||
exports('UnzipFile', (file, dest) => {
|
||||
try {
|
||||
fs.createReadStream(file).pipe(unzipper.Extract({ path: dest}).on('close', () => {
|
||||
emit("unzipCoreCompleted", true);
|
||||
}).on('error', (error) => {
|
||||
emit("unzipCoreCompleted", false, error);
|
||||
}));
|
||||
} catch(ex) {
|
||||
console.error("Failed to unzip a file: " + ex);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
function deleteDirR(dir) {
|
||||
fs.rmdir(dir, {recursive:true}, (err) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return false, err;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
exports('UnzipFolder', (file, name, dest) => {
|
||||
let firstDir = null;
|
||||
let hasStreamFolder = false;
|
||||
const rootPath = GetResourcePath(GetCurrentResourceName());
|
||||
const streamPath = rootPath + "/stream/" + name + "/";
|
||||
if (!fs.existsSync(file)) {
|
||||
console.error("File " + file + " doesn't exist.");
|
||||
return false;
|
||||
}
|
||||
fs.createReadStream(file).pipe(unzipper.Parse())
|
||||
.on('entry', function(entry) {
|
||||
var fileName = entry.path;
|
||||
const type = entry.type;
|
||||
if (type == "Directory") {
|
||||
if (fileName.includes("stream") && !hasStreamFolder) {
|
||||
hasStreamFolder = true;
|
||||
deleteDirR(streamPath);
|
||||
}
|
||||
if (firstDir == null) {
|
||||
firstDir = fileName;
|
||||
}
|
||||
else {
|
||||
fileName = fileName.replace(firstDir, "");
|
||||
if (!fs.existsSync(dest + fileName)) {
|
||||
fs.mkdirSync(dest + fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == "File") {
|
||||
fileName = fileName.replace(firstDir, "");
|
||||
let finalPath = dest + fileName;
|
||||
if (fileName.includes("stream")) {
|
||||
let file = fileName.replace(/^.*[\\\/]/, '');
|
||||
finalPath = `${rootPath}/stream/${name}/${file}`;
|
||||
if (!fs.existsSync(`${rootPath}/stream/${name}/`)) {
|
||||
fs.mkdirSync(`${rootPath}/stream/${name}/`);
|
||||
}
|
||||
}
|
||||
emit("SonoranCAD::core:writeLog", "debug", "write: " + finalPath);
|
||||
entry.pipe(fs.createWriteStream(finalPath));
|
||||
} else {
|
||||
entry.autodrain();
|
||||
|
||||
}
|
||||
}).on('close', () => {
|
||||
emit("unzipCompleted", true, name, file);
|
||||
}).on('error', (error) => {
|
||||
emit("unzipCompleted", false, name, file, error);
|
||||
})
|
||||
});
|
||||
|
||||
exports('CreateFolderIfNotExisting', (path) => {
|
||||
if (!fs.existsSync(path)) {
|
||||
fs.mkdirSync(path);
|
||||
}
|
||||
});
|
||||
|
||||
exports('DeleteDirectoryRecursively', (dir) => {
|
||||
fs.rmdir(dir, {recursive:true}, (err) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return false, err;
|
||||
}
|
||||
});
|
||||
return true
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
local pendingRestart = false
|
||||
|
||||
local function doUnzip(path)
|
||||
local unzipPath = GetResourcePath(GetCurrentResourceName()).."/../../"
|
||||
exports[GetCurrentResourceName()]:UnzipFile(path, unzipPath)
|
||||
end
|
||||
|
||||
AddEventHandler("unzipCoreCompleted", function(success, error)
|
||||
if success then
|
||||
if not Config.allowUpdateWithPlayers and GetNumPlayerIndices() > 0 then
|
||||
pendingRestart = true
|
||||
infoLog("Delaying auto-update until server is empty.")
|
||||
return
|
||||
end
|
||||
warnLog("Auto-restarting...")
|
||||
local f = assert(io.open(GetResourcePath("sonoran_updatehelper").."/run.lock", "w+"))
|
||||
f:write("core")
|
||||
f:close()
|
||||
Wait(5000)
|
||||
ExecuteCommand("ensure sonoran_updatehelper")
|
||||
else
|
||||
errorLog("Failed to download core update. "..tostring(error))
|
||||
end
|
||||
end)
|
||||
|
||||
local function doUpdate(latest)
|
||||
-- best way to do this...
|
||||
local releaseUrl = ("https://github.com/Sonoran-Software/SonoranCADFiveM/releases/download/v%s/sonorancad-%s.zip"):format(latest, latest)
|
||||
PerformHttpRequest(releaseUrl, function(code, data, headers)
|
||||
if code == 200 then
|
||||
local savePath = GetResourcePath(GetCurrentResourceName()).."/update.zip"
|
||||
local f = assert(io.open(savePath, 'wb'))
|
||||
f:write(data)
|
||||
f:close()
|
||||
infoLog("Saved file...")
|
||||
doUnzip(savePath)
|
||||
else
|
||||
warnLog(("Failed to download from %s: %s %s"):format(releaseUrl, code, data))
|
||||
end
|
||||
end, "GET")
|
||||
|
||||
end
|
||||
|
||||
function RunAutoUpdater(manualRun)
|
||||
if Config.updateBranch == nil then
|
||||
return
|
||||
end
|
||||
local f = LoadResourceFile(GetCurrentResourceName(), "/update.zip")
|
||||
if f ~= nil then
|
||||
-- remove the update file and stop the helper
|
||||
ExecuteCommand("stop sonoran_updatehelper")
|
||||
os.remove(GetResourcePath(GetCurrentResourceName()).."/update.zip")
|
||||
os.remove(GetResourcePath("sonoran_updatehelper").."/run.lock")
|
||||
end
|
||||
local versionFile = Config.autoUpdateUrl
|
||||
if versionFile == "https://raw.githubusercontent.com/Sonoran-Software/SonoranCADLuaIntegration/{branch}/sonorancad/version.json" then
|
||||
errorLog('It seems like you might be running a v2.X.X core configuration file. Please update from the config.CHANGEME.json file or reinstall the resource. Install guide: https://sonoran.link/v3')
|
||||
versionFile = "https://raw.githubusercontent.com/Sonoran-Software/SonoranCADFiveM/{branch}/sonorancad/version.json"
|
||||
end
|
||||
if versionFile == nil then
|
||||
versionFile = "https://raw.githubusercontent.com/Sonoran-Software/SonoranCADFiveM/{branch}/sonorancad/version.json"
|
||||
end
|
||||
versionFile = string.gsub(versionFile, "{branch}", Config.updateBranch)
|
||||
local myVersion = GetResourceMetadata(GetCurrentResourceName(), "version", 0)
|
||||
|
||||
PerformHttpRequestS(versionFile, function(code, data, headers)
|
||||
if code == 200 then
|
||||
local remote = json.decode(data)
|
||||
if remote == nil then
|
||||
warnLog(("Failed to get a valid response for %s. Skipping."):format(k))
|
||||
debugLog(("Raw output for %s: %s"):format(k, data))
|
||||
else
|
||||
Config.latestVersion = remote.resource
|
||||
local compare = compareVersions(remote.resource, myVersion)
|
||||
if compare.result then
|
||||
if not Config.allowAutoUpdate then
|
||||
print("^3|===========================================================================|")
|
||||
print("^3| ^5SonoranCAD Update Available ^3|")
|
||||
print("^3| ^8Current : " .. compare.version2 .. " ^3|")
|
||||
print("^3| ^2Latest : " .. compare.version1 .. " ^3|")
|
||||
print("^3| Download at: ^4https://github.com/Sonoran-Software/SonoranCADFiveM ^3|")
|
||||
print("^3|===========================================================================|^7")
|
||||
if Config.allowAutoUpdate == nil then
|
||||
warnLog("You have not configured the automatic updater. Please set allowAutoUpdate in config.json to allow updates.")
|
||||
end
|
||||
else
|
||||
infoLog("Running auto-update now...")
|
||||
doUpdate(remote.resource)
|
||||
end
|
||||
else
|
||||
if manualRun then
|
||||
infoLog(("No updates available. Detected version %s, latest version is %s"):format(compare.version1, compare.version2))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "GET")
|
||||
end
|
||||
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
if pendingRestart then
|
||||
if GetNumPlayerIndices() > 0 then
|
||||
warnLog("An update has been applied to SonoranCAD but requires a resource restart. Restart delayed until server is empty.")
|
||||
else
|
||||
infoLog("Server is empty, restarting resources...")
|
||||
local f = assert(io.open(GetResourcePath("sonoran_updatehelper").."/run.lock", "w+"))
|
||||
f:write("core")
|
||||
f:close()
|
||||
ExecuteCommand("ensure sonoran_updatehelper")
|
||||
end
|
||||
else
|
||||
RunAutoUpdater()
|
||||
end
|
||||
Wait(60000*60)
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,50 @@
|
||||
fx_version 'cerulean'
|
||||
games {'gta5'}
|
||||
|
||||
author 'Sonoran CAD'
|
||||
description 'Sonoran CAD FiveM Integration'
|
||||
version '3.1.4'
|
||||
|
||||
server_scripts {
|
||||
'core/http.js'
|
||||
,'core/unzipper/unzip.js'
|
||||
,'core/image.js'
|
||||
,'core/logging.lua'
|
||||
,'core/shared_functions.lua'
|
||||
,'core/configuration.lua'
|
||||
,'core/server.lua'
|
||||
,'core/commands.lua'
|
||||
,'core/httpd.lua'
|
||||
,'core/unittracking.lua'
|
||||
,'core/updater.lua'
|
||||
,'core/apicheck.lua'
|
||||
,'configuration/*_config.lua'
|
||||
,'core/plugin_loader.lua'
|
||||
,'submodules/**/sv_*.lua'
|
||||
,'submodules/**/sv_*.js'
|
||||
,'core/screenshot.lua'
|
||||
}
|
||||
client_scripts {
|
||||
'core/logging.lua'
|
||||
,'core/headshots.lua'
|
||||
,'core/shared_functions.lua'
|
||||
,'core/client.lua'
|
||||
,'core/lighting.lua'
|
||||
,'configuration/*_config.lua'
|
||||
,'submodules/**/cl_*.lua'
|
||||
,'submodules/**/cl_*.js'
|
||||
}
|
||||
|
||||
ui_page 'core/client_nui/index.html'
|
||||
|
||||
files {
|
||||
'stream/**/*.ytyp',
|
||||
'core/client_nui/index.html',
|
||||
'core/client_nui/js/*.js',
|
||||
'core/client_nui/sounds/*.mp3',
|
||||
'core/client_nui/img/logo.gif',
|
||||
'submodules/**/*.mp3',
|
||||
'submodules/postals/*.json'
|
||||
}
|
||||
|
||||
data_file 'DLC_ITYP_REQUEST' 'stream/**/*.ytyp'
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
|
||||
fi
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %*
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../rimraf/bin.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../rimraf/bin.js" "$@"
|
||||
fi
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rimraf\bin.js" %*
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../rimraf/bin.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../rimraf/bin.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../rimraf/bin.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../rimraf/bin.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
{
|
||||
"name": "sonorancad",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.49",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.49.tgz",
|
||||
"integrity": "sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/binary": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
|
||||
"integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
|
||||
"dependencies": {
|
||||
"buffers": "~0.1.1",
|
||||
"chainsaw": "~0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
|
||||
"integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-indexof-polyfill": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
|
||||
"integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/buffers": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
|
||||
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=",
|
||||
"engines": {
|
||||
"node": ">=0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chainsaw": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
|
||||
"integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
|
||||
"dependencies": {
|
||||
"traverse": ">=0.3.0 <0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/duplexer2": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
||||
"integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"node_modules/fstream": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
|
||||
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"inherits": "~2.0.0",
|
||||
"mkdirp": ">=0.5 0",
|
||||
"rimraf": "2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
|
||||
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg=="
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"node_modules/listenercount": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
|
||||
"integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.5.0.tgz",
|
||||
"integrity": "sha512-iUmRkhH9KGeszQwDW7YyyqjsMTf4z+0o48Cp4xOwlY5LjtbIAvyd3fwnsoUZW/hXmTCRA3yt7S/Jb9uVjErVlA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"asn1": "^0.2.4",
|
||||
"bcrypt-pbkdf": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"cpu-features": "0.0.2",
|
||||
"nan": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ts3-nodejs-library": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ts3-nodejs-library/-/ts3-nodejs-library-3.4.0.tgz",
|
||||
"integrity": "sha512-T9LV0zxvMmF6cl3GjRAUcVNrZnuCbVHS0HQw1qg7kYdrNOnXh1Eg4Ix5WpiBsuSQ0bVB45VEF85vgmmXO3S2AA==",
|
||||
"dependencies": {
|
||||
"buffer-crc32": "^0.2.13",
|
||||
"ssh2": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"node_modules/unzipper": {
|
||||
"version": "0.10.11",
|
||||
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz",
|
||||
"integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==",
|
||||
"dependencies": {
|
||||
"big-integer": "^1.6.17",
|
||||
"binary": "~0.3.0",
|
||||
"bluebird": "~3.4.1",
|
||||
"buffer-indexof-polyfill": "~1.0.0",
|
||||
"duplexer2": "~0.1.4",
|
||||
"fstream": "^1.0.12",
|
||||
"graceful-fs": "^4.2.2",
|
||||
"listenercount": "~1.0.1",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "~1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
@Library('jenkins-joylib@v1.0.8') _
|
||||
|
||||
pipeline {
|
||||
|
||||
agent none
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '45'))
|
||||
timestamps()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('top') {
|
||||
parallel {
|
||||
stage('v4-zone') {
|
||||
agent {
|
||||
label joyCommonLabels(image_ver: '15.4.1')
|
||||
}
|
||||
tools {
|
||||
nodejs 'sdcnode-v4-zone'
|
||||
}
|
||||
stages {
|
||||
stage('check') {
|
||||
steps{
|
||||
sh('make check')
|
||||
}
|
||||
}
|
||||
stage('test') {
|
||||
steps{
|
||||
sh('make test')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('v6-zone64') {
|
||||
agent {
|
||||
label joyCommonLabels(image_ver: '18.4.0')
|
||||
}
|
||||
tools {
|
||||
nodejs 'sdcnode-v6-zone64'
|
||||
}
|
||||
stages {
|
||||
stage('check') {
|
||||
steps{
|
||||
sh('make check')
|
||||
}
|
||||
}
|
||||
stage('test') {
|
||||
steps{
|
||||
sh('make test')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
joySlackNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011 Mark Cavage, All rights reserved.
|
||||
|
||||
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
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
node-asn1 is a library for encoding and decoding ASN.1 datatypes in pure JS.
|
||||
Currently BER encoding is supported; at some point I'll likely have to do DER.
|
||||
|
||||
## Usage
|
||||
|
||||
Mostly, if you're *actually* needing to read and write ASN.1, you probably don't
|
||||
need this readme to explain what and why. If you have no idea what ASN.1 is,
|
||||
see this: ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc
|
||||
|
||||
The source is pretty much self-explanatory, and has read/write methods for the
|
||||
common types out there.
|
||||
|
||||
### Decoding
|
||||
|
||||
The following reads an ASN.1 sequence with a boolean.
|
||||
|
||||
var Ber = require('asn1').Ber;
|
||||
|
||||
var reader = new Ber.Reader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]));
|
||||
|
||||
reader.readSequence();
|
||||
console.log('Sequence len: ' + reader.length);
|
||||
if (reader.peek() === Ber.Boolean)
|
||||
console.log(reader.readBoolean());
|
||||
|
||||
### Encoding
|
||||
|
||||
The following generates the same payload as above.
|
||||
|
||||
var Ber = require('asn1').Ber;
|
||||
|
||||
var writer = new Ber.Writer();
|
||||
|
||||
writer.startSequence();
|
||||
writer.writeBoolean(true);
|
||||
writer.endSequence();
|
||||
|
||||
console.log(writer.buffer);
|
||||
|
||||
## Installation
|
||||
|
||||
npm install asn1
|
||||
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
## Bugs
|
||||
|
||||
See <https://github.com/joyent/node-asn1/issues>.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
newInvalidAsn1Error: function (msg) {
|
||||
var e = new Error();
|
||||
e.name = 'InvalidAsn1Error';
|
||||
e.message = msg || '';
|
||||
return e;
|
||||
}
|
||||
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
var errors = require('./errors');
|
||||
var types = require('./types');
|
||||
|
||||
var Reader = require('./reader');
|
||||
var Writer = require('./writer');
|
||||
|
||||
|
||||
// --- Exports
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reader: Reader,
|
||||
|
||||
Writer: Writer
|
||||
|
||||
};
|
||||
|
||||
for (var t in types) {
|
||||
if (types.hasOwnProperty(t))
|
||||
module.exports[t] = types[t];
|
||||
}
|
||||
for (var e in errors) {
|
||||
if (errors.hasOwnProperty(e))
|
||||
module.exports[e] = errors[e];
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
var Buffer = require('safer-buffer').Buffer;
|
||||
|
||||
var ASN1 = require('./types');
|
||||
var errors = require('./errors');
|
||||
|
||||
|
||||
// --- Globals
|
||||
|
||||
var newInvalidAsn1Error = errors.newInvalidAsn1Error;
|
||||
|
||||
|
||||
|
||||
// --- API
|
||||
|
||||
function Reader(data) {
|
||||
if (!data || !Buffer.isBuffer(data))
|
||||
throw new TypeError('data must be a node Buffer');
|
||||
|
||||
this._buf = data;
|
||||
this._size = data.length;
|
||||
|
||||
// These hold the "current" state
|
||||
this._len = 0;
|
||||
this._offset = 0;
|
||||
}
|
||||
|
||||
Object.defineProperty(Reader.prototype, 'length', {
|
||||
enumerable: true,
|
||||
get: function () { return (this._len); }
|
||||
});
|
||||
|
||||
Object.defineProperty(Reader.prototype, 'offset', {
|
||||
enumerable: true,
|
||||
get: function () { return (this._offset); }
|
||||
});
|
||||
|
||||
Object.defineProperty(Reader.prototype, 'remain', {
|
||||
get: function () { return (this._size - this._offset); }
|
||||
});
|
||||
|
||||
Object.defineProperty(Reader.prototype, 'buffer', {
|
||||
get: function () { return (this._buf.slice(this._offset)); }
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Reads a single byte and advances offset; you can pass in `true` to make this
|
||||
* a "peek" operation (i.e., get the byte, but don't advance the offset).
|
||||
*
|
||||
* @param {Boolean} peek true means don't move offset.
|
||||
* @return {Number} the next byte, null if not enough data.
|
||||
*/
|
||||
Reader.prototype.readByte = function (peek) {
|
||||
if (this._size - this._offset < 1)
|
||||
return null;
|
||||
|
||||
var b = this._buf[this._offset] & 0xff;
|
||||
|
||||
if (!peek)
|
||||
this._offset += 1;
|
||||
|
||||
return b;
|
||||
};
|
||||
|
||||
|
||||
Reader.prototype.peek = function () {
|
||||
return this.readByte(true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads a (potentially) variable length off the BER buffer. This call is
|
||||
* not really meant to be called directly, as callers have to manipulate
|
||||
* the internal buffer afterwards.
|
||||
*
|
||||
* As a result of this call, you can call `Reader.length`, until the
|
||||
* next thing called that does a readLength.
|
||||
*
|
||||
* @return {Number} the amount of offset to advance the buffer.
|
||||
* @throws {InvalidAsn1Error} on bad ASN.1
|
||||
*/
|
||||
Reader.prototype.readLength = function (offset) {
|
||||
if (offset === undefined)
|
||||
offset = this._offset;
|
||||
|
||||
if (offset >= this._size)
|
||||
return null;
|
||||
|
||||
var lenB = this._buf[offset++] & 0xff;
|
||||
if (lenB === null)
|
||||
return null;
|
||||
|
||||
if ((lenB & 0x80) === 0x80) {
|
||||
lenB &= 0x7f;
|
||||
|
||||
if (lenB === 0)
|
||||
throw newInvalidAsn1Error('Indefinite length not supported');
|
||||
|
||||
if (lenB > 4)
|
||||
throw newInvalidAsn1Error('encoding too long');
|
||||
|
||||
if (this._size - offset < lenB)
|
||||
return null;
|
||||
|
||||
this._len = 0;
|
||||
for (var i = 0; i < lenB; i++)
|
||||
this._len = (this._len << 8) + (this._buf[offset++] & 0xff);
|
||||
|
||||
} else {
|
||||
// Wasn't a variable length
|
||||
this._len = lenB;
|
||||
}
|
||||
|
||||
return offset;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses the next sequence in this BER buffer.
|
||||
*
|
||||
* To get the length of the sequence, call `Reader.length`.
|
||||
*
|
||||
* @return {Number} the sequence's tag.
|
||||
*/
|
||||
Reader.prototype.readSequence = function (tag) {
|
||||
var seq = this.peek();
|
||||
if (seq === null)
|
||||
return null;
|
||||
if (tag !== undefined && tag !== seq)
|
||||
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
|
||||
': got 0x' + seq.toString(16));
|
||||
|
||||
var o = this.readLength(this._offset + 1); // stored in `length`
|
||||
if (o === null)
|
||||
return null;
|
||||
|
||||
this._offset = o;
|
||||
return seq;
|
||||
};
|
||||
|
||||
|
||||
Reader.prototype.readInt = function () {
|
||||
return this._readTag(ASN1.Integer);
|
||||
};
|
||||
|
||||
|
||||
Reader.prototype.readBoolean = function () {
|
||||
return (this._readTag(ASN1.Boolean) === 0 ? false : true);
|
||||
};
|
||||
|
||||
|
||||
Reader.prototype.readEnumeration = function () {
|
||||
return this._readTag(ASN1.Enumeration);
|
||||
};
|
||||
|
||||
|
||||
Reader.prototype.readString = function (tag, retbuf) {
|
||||
if (!tag)
|
||||
tag = ASN1.OctetString;
|
||||
|
||||
var b = this.peek();
|
||||
if (b === null)
|
||||
return null;
|
||||
|
||||
if (b !== tag)
|
||||
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
|
||||
': got 0x' + b.toString(16));
|
||||
|
||||
var o = this.readLength(this._offset + 1); // stored in `length`
|
||||
|
||||
if (o === null)
|
||||
return null;
|
||||
|
||||
if (this.length > this._size - o)
|
||||
return null;
|
||||
|
||||
this._offset = o;
|
||||
|
||||
if (this.length === 0)
|
||||
return retbuf ? Buffer.alloc(0) : '';
|
||||
|
||||
var str = this._buf.slice(this._offset, this._offset + this.length);
|
||||
this._offset += this.length;
|
||||
|
||||
return retbuf ? str : str.toString('utf8');
|
||||
};
|
||||
|
||||
Reader.prototype.readOID = function (tag) {
|
||||
if (!tag)
|
||||
tag = ASN1.OID;
|
||||
|
||||
var b = this.readString(tag, true);
|
||||
if (b === null)
|
||||
return null;
|
||||
|
||||
var values = [];
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < b.length; i++) {
|
||||
var byte = b[i] & 0xff;
|
||||
|
||||
value <<= 7;
|
||||
value += byte & 0x7f;
|
||||
if ((byte & 0x80) === 0) {
|
||||
values.push(value);
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
value = values.shift();
|
||||
values.unshift(value % 40);
|
||||
values.unshift((value / 40) >> 0);
|
||||
|
||||
return values.join('.');
|
||||
};
|
||||
|
||||
|
||||
Reader.prototype._readTag = function (tag) {
|
||||
assert.ok(tag !== undefined);
|
||||
|
||||
var b = this.peek();
|
||||
|
||||
if (b === null)
|
||||
return null;
|
||||
|
||||
if (b !== tag)
|
||||
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
|
||||
': got 0x' + b.toString(16));
|
||||
|
||||
var o = this.readLength(this._offset + 1); // stored in `length`
|
||||
if (o === null)
|
||||
return null;
|
||||
|
||||
if (this.length > 4)
|
||||
throw newInvalidAsn1Error('Integer too long: ' + this.length);
|
||||
|
||||
if (this.length > this._size - o)
|
||||
return null;
|
||||
this._offset = o;
|
||||
|
||||
var fb = this._buf[this._offset];
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
value <<= 8;
|
||||
value |= (this._buf[this._offset++] & 0xff);
|
||||
}
|
||||
|
||||
if ((fb & 0x80) === 0x80 && i !== 4)
|
||||
value -= (1 << (i * 8));
|
||||
|
||||
return value >> 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// --- Exported API
|
||||
|
||||
module.exports = Reader;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
|
||||
module.exports = {
|
||||
EOC: 0,
|
||||
Boolean: 1,
|
||||
Integer: 2,
|
||||
BitString: 3,
|
||||
OctetString: 4,
|
||||
Null: 5,
|
||||
OID: 6,
|
||||
ObjectDescriptor: 7,
|
||||
External: 8,
|
||||
Real: 9, // float
|
||||
Enumeration: 10,
|
||||
PDV: 11,
|
||||
Utf8String: 12,
|
||||
RelativeOID: 13,
|
||||
Sequence: 16,
|
||||
Set: 17,
|
||||
NumericString: 18,
|
||||
PrintableString: 19,
|
||||
T61String: 20,
|
||||
VideotexString: 21,
|
||||
IA5String: 22,
|
||||
UTCTime: 23,
|
||||
GeneralizedTime: 24,
|
||||
GraphicString: 25,
|
||||
VisibleString: 26,
|
||||
GeneralString: 28,
|
||||
UniversalString: 29,
|
||||
CharacterString: 30,
|
||||
BMPString: 31,
|
||||
Constructor: 32,
|
||||
Context: 128
|
||||
};
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
var Buffer = require('safer-buffer').Buffer;
|
||||
var ASN1 = require('./types');
|
||||
var errors = require('./errors');
|
||||
|
||||
|
||||
// --- Globals
|
||||
|
||||
var newInvalidAsn1Error = errors.newInvalidAsn1Error;
|
||||
|
||||
var DEFAULT_OPTS = {
|
||||
size: 1024,
|
||||
growthFactor: 8
|
||||
};
|
||||
|
||||
|
||||
// --- Helpers
|
||||
|
||||
function merge(from, to) {
|
||||
assert.ok(from);
|
||||
assert.equal(typeof (from), 'object');
|
||||
assert.ok(to);
|
||||
assert.equal(typeof (to), 'object');
|
||||
|
||||
var keys = Object.getOwnPropertyNames(from);
|
||||
keys.forEach(function (key) {
|
||||
if (to[key])
|
||||
return;
|
||||
|
||||
var value = Object.getOwnPropertyDescriptor(from, key);
|
||||
Object.defineProperty(to, key, value);
|
||||
});
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- API
|
||||
|
||||
function Writer(options) {
|
||||
options = merge(DEFAULT_OPTS, options || {});
|
||||
|
||||
this._buf = Buffer.alloc(options.size || 1024);
|
||||
this._size = this._buf.length;
|
||||
this._offset = 0;
|
||||
this._options = options;
|
||||
|
||||
// A list of offsets in the buffer where we need to insert
|
||||
// sequence tag/len pairs.
|
||||
this._seq = [];
|
||||
}
|
||||
|
||||
Object.defineProperty(Writer.prototype, 'buffer', {
|
||||
get: function () {
|
||||
if (this._seq.length)
|
||||
throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)');
|
||||
|
||||
return (this._buf.slice(0, this._offset));
|
||||
}
|
||||
});
|
||||
|
||||
Writer.prototype.writeByte = function (b) {
|
||||
if (typeof (b) !== 'number')
|
||||
throw new TypeError('argument must be a Number');
|
||||
|
||||
this._ensure(1);
|
||||
this._buf[this._offset++] = b;
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeInt = function (i, tag) {
|
||||
if (typeof (i) !== 'number')
|
||||
throw new TypeError('argument must be a Number');
|
||||
if (typeof (tag) !== 'number')
|
||||
tag = ASN1.Integer;
|
||||
|
||||
var sz = 4;
|
||||
|
||||
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
|
||||
(sz > 1)) {
|
||||
sz--;
|
||||
i <<= 8;
|
||||
}
|
||||
|
||||
if (sz > 4)
|
||||
throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff');
|
||||
|
||||
this._ensure(2 + sz);
|
||||
this._buf[this._offset++] = tag;
|
||||
this._buf[this._offset++] = sz;
|
||||
|
||||
while (sz-- > 0) {
|
||||
this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
|
||||
i <<= 8;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeNull = function () {
|
||||
this.writeByte(ASN1.Null);
|
||||
this.writeByte(0x00);
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeEnumeration = function (i, tag) {
|
||||
if (typeof (i) !== 'number')
|
||||
throw new TypeError('argument must be a Number');
|
||||
if (typeof (tag) !== 'number')
|
||||
tag = ASN1.Enumeration;
|
||||
|
||||
return this.writeInt(i, tag);
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeBoolean = function (b, tag) {
|
||||
if (typeof (b) !== 'boolean')
|
||||
throw new TypeError('argument must be a Boolean');
|
||||
if (typeof (tag) !== 'number')
|
||||
tag = ASN1.Boolean;
|
||||
|
||||
this._ensure(3);
|
||||
this._buf[this._offset++] = tag;
|
||||
this._buf[this._offset++] = 0x01;
|
||||
this._buf[this._offset++] = b ? 0xff : 0x00;
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeString = function (s, tag) {
|
||||
if (typeof (s) !== 'string')
|
||||
throw new TypeError('argument must be a string (was: ' + typeof (s) + ')');
|
||||
if (typeof (tag) !== 'number')
|
||||
tag = ASN1.OctetString;
|
||||
|
||||
var len = Buffer.byteLength(s);
|
||||
this.writeByte(tag);
|
||||
this.writeLength(len);
|
||||
if (len) {
|
||||
this._ensure(len);
|
||||
this._buf.write(s, this._offset);
|
||||
this._offset += len;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeBuffer = function (buf, tag) {
|
||||
if (typeof (tag) !== 'number')
|
||||
throw new TypeError('tag must be a number');
|
||||
if (!Buffer.isBuffer(buf))
|
||||
throw new TypeError('argument must be a buffer');
|
||||
|
||||
this.writeByte(tag);
|
||||
this.writeLength(buf.length);
|
||||
this._ensure(buf.length);
|
||||
buf.copy(this._buf, this._offset, 0, buf.length);
|
||||
this._offset += buf.length;
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeStringArray = function (strings) {
|
||||
if ((!strings instanceof Array))
|
||||
throw new TypeError('argument must be an Array[String]');
|
||||
|
||||
var self = this;
|
||||
strings.forEach(function (s) {
|
||||
self.writeString(s);
|
||||
});
|
||||
};
|
||||
|
||||
// This is really to solve DER cases, but whatever for now
|
||||
Writer.prototype.writeOID = function (s, tag) {
|
||||
if (typeof (s) !== 'string')
|
||||
throw new TypeError('argument must be a string');
|
||||
if (typeof (tag) !== 'number')
|
||||
tag = ASN1.OID;
|
||||
|
||||
if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
|
||||
throw new Error('argument is not a valid OID string');
|
||||
|
||||
function encodeOctet(bytes, octet) {
|
||||
if (octet < 128) {
|
||||
bytes.push(octet);
|
||||
} else if (octet < 16384) {
|
||||
bytes.push((octet >>> 7) | 0x80);
|
||||
bytes.push(octet & 0x7F);
|
||||
} else if (octet < 2097152) {
|
||||
bytes.push((octet >>> 14) | 0x80);
|
||||
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
|
||||
bytes.push(octet & 0x7F);
|
||||
} else if (octet < 268435456) {
|
||||
bytes.push((octet >>> 21) | 0x80);
|
||||
bytes.push(((octet >>> 14) | 0x80) & 0xFF);
|
||||
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
|
||||
bytes.push(octet & 0x7F);
|
||||
} else {
|
||||
bytes.push(((octet >>> 28) | 0x80) & 0xFF);
|
||||
bytes.push(((octet >>> 21) | 0x80) & 0xFF);
|
||||
bytes.push(((octet >>> 14) | 0x80) & 0xFF);
|
||||
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
|
||||
bytes.push(octet & 0x7F);
|
||||
}
|
||||
}
|
||||
|
||||
var tmp = s.split('.');
|
||||
var bytes = [];
|
||||
bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
|
||||
tmp.slice(2).forEach(function (b) {
|
||||
encodeOctet(bytes, parseInt(b, 10));
|
||||
});
|
||||
|
||||
var self = this;
|
||||
this._ensure(2 + bytes.length);
|
||||
this.writeByte(tag);
|
||||
this.writeLength(bytes.length);
|
||||
bytes.forEach(function (b) {
|
||||
self.writeByte(b);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.writeLength = function (len) {
|
||||
if (typeof (len) !== 'number')
|
||||
throw new TypeError('argument must be a Number');
|
||||
|
||||
this._ensure(4);
|
||||
|
||||
if (len <= 0x7f) {
|
||||
this._buf[this._offset++] = len;
|
||||
} else if (len <= 0xff) {
|
||||
this._buf[this._offset++] = 0x81;
|
||||
this._buf[this._offset++] = len;
|
||||
} else if (len <= 0xffff) {
|
||||
this._buf[this._offset++] = 0x82;
|
||||
this._buf[this._offset++] = len >> 8;
|
||||
this._buf[this._offset++] = len;
|
||||
} else if (len <= 0xffffff) {
|
||||
this._buf[this._offset++] = 0x83;
|
||||
this._buf[this._offset++] = len >> 16;
|
||||
this._buf[this._offset++] = len >> 8;
|
||||
this._buf[this._offset++] = len;
|
||||
} else {
|
||||
throw newInvalidAsn1Error('Length too long (> 4 bytes)');
|
||||
}
|
||||
};
|
||||
|
||||
Writer.prototype.startSequence = function (tag) {
|
||||
if (typeof (tag) !== 'number')
|
||||
tag = ASN1.Sequence | ASN1.Constructor;
|
||||
|
||||
this.writeByte(tag);
|
||||
this._seq.push(this._offset);
|
||||
this._ensure(3);
|
||||
this._offset += 3;
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype.endSequence = function () {
|
||||
var seq = this._seq.pop();
|
||||
var start = seq + 3;
|
||||
var len = this._offset - start;
|
||||
|
||||
if (len <= 0x7f) {
|
||||
this._shift(start, len, -2);
|
||||
this._buf[seq] = len;
|
||||
} else if (len <= 0xff) {
|
||||
this._shift(start, len, -1);
|
||||
this._buf[seq] = 0x81;
|
||||
this._buf[seq + 1] = len;
|
||||
} else if (len <= 0xffff) {
|
||||
this._buf[seq] = 0x82;
|
||||
this._buf[seq + 1] = len >> 8;
|
||||
this._buf[seq + 2] = len;
|
||||
} else if (len <= 0xffffff) {
|
||||
this._shift(start, len, 1);
|
||||
this._buf[seq] = 0x83;
|
||||
this._buf[seq + 1] = len >> 16;
|
||||
this._buf[seq + 2] = len >> 8;
|
||||
this._buf[seq + 3] = len;
|
||||
} else {
|
||||
throw newInvalidAsn1Error('Sequence too long');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Writer.prototype._shift = function (start, len, shift) {
|
||||
assert.ok(start !== undefined);
|
||||
assert.ok(len !== undefined);
|
||||
assert.ok(shift);
|
||||
|
||||
this._buf.copy(this._buf, start + shift, start, start + len);
|
||||
this._offset += shift;
|
||||
};
|
||||
|
||||
Writer.prototype._ensure = function (len) {
|
||||
assert.ok(len);
|
||||
|
||||
if (this._size - this._offset < len) {
|
||||
var sz = this._size * this._options.growthFactor;
|
||||
if (sz - this._offset < len)
|
||||
sz += len;
|
||||
|
||||
var buf = Buffer.alloc(sz);
|
||||
|
||||
this._buf.copy(buf, 0, 0, this._offset);
|
||||
this._buf = buf;
|
||||
this._size = sz;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// --- Exported API
|
||||
|
||||
module.exports = Writer;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
// If you have no idea what ASN.1 or BER is, see this:
|
||||
// ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc
|
||||
|
||||
var Ber = require('./ber/index');
|
||||
|
||||
|
||||
|
||||
// --- Exported API
|
||||
|
||||
module.exports = {
|
||||
|
||||
Ber: Ber,
|
||||
|
||||
BerReader: Ber.Reader,
|
||||
|
||||
BerWriter: Ber.Writer
|
||||
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"author": "Joyent (joyent.com)",
|
||||
"contributors": [
|
||||
"Mark Cavage <mcavage@gmail.com>",
|
||||
"David Gwynne <loki@animata.net>",
|
||||
"Yunong Xiao <yunong@joyent.com>",
|
||||
"Alex Wilson <alex.wilson@joyent.com>"
|
||||
],
|
||||
"name": "asn1",
|
||||
"description": "Contains parsers and serializers for ASN.1 (currently BER only)",
|
||||
"version": "0.2.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/joyent/node-asn1.git"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "^0.3.6",
|
||||
"faucet": "0.0.1",
|
||||
"tape": "^3.5.0",
|
||||
"eslint": "2.13.1",
|
||||
"eslint-plugin-joyent": "~1.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/tape ./test/ber/*.test.js"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
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.
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# balanced-match
|
||||
|
||||
Match balanced string pairs, like `{` and `}` or `<b>` and `</b>`. Supports regular expressions as well!
|
||||
|
||||
[](http://travis-ci.org/juliangruber/balanced-match)
|
||||
[](https://www.npmjs.org/package/balanced-match)
|
||||
|
||||
[](https://ci.testling.com/juliangruber/balanced-match)
|
||||
|
||||
## Example
|
||||
|
||||
Get the first matching pair of braces:
|
||||
|
||||
```js
|
||||
var balanced = require('balanced-match');
|
||||
|
||||
console.log(balanced('{', '}', 'pre{in{nested}}post'));
|
||||
console.log(balanced('{', '}', 'pre{first}between{second}post'));
|
||||
console.log(balanced(/\s+\{\s+/, /\s+\}\s+/, 'pre { in{nest} } post'));
|
||||
```
|
||||
|
||||
The matches are:
|
||||
|
||||
```bash
|
||||
$ node example.js
|
||||
{ start: 3, end: 14, pre: 'pre', body: 'in{nested}', post: 'post' }
|
||||
{ start: 3,
|
||||
end: 9,
|
||||
pre: 'pre',
|
||||
body: 'first',
|
||||
post: 'between{second}post' }
|
||||
{ start: 3, end: 17, pre: 'pre', body: 'in{nest}', post: 'post' }
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### var m = balanced(a, b, str)
|
||||
|
||||
For the first non-nested matching pair of `a` and `b` in `str`, return an
|
||||
object with those keys:
|
||||
|
||||
* **start** the index of the first match of `a`
|
||||
* **end** the index of the matching `b`
|
||||
* **pre** the preamble, `a` and `b` not included
|
||||
* **body** the match, `a` and `b` not included
|
||||
* **post** the postscript, `a` and `b` not included
|
||||
|
||||
If there's no match, `undefined` will be returned.
|
||||
|
||||
If the `str` contains more `a` than `b` / there are unmatched pairs, the first match that was closed will be used. For example, `{{a}` will match `['{', 'a', '']` and `{a}}` will match `['', 'a', '}']`.
|
||||
|
||||
### var r = balanced.range(a, b, str)
|
||||
|
||||
For the first non-nested matching pair of `a` and `b` in `str`, return an
|
||||
array with indexes: `[ <a index>, <b index> ]`.
|
||||
|
||||
If there's no match, `undefined` will be returned.
|
||||
|
||||
If the `str` contains more `a` than `b` / there are unmatched pairs, the first match that was closed will be used. For example, `{{a}` will match `[ 1, 3 ]` and `{a}}` will match `[0, 2]`.
|
||||
|
||||
## Installation
|
||||
|
||||
With [npm](https://npmjs.org) do:
|
||||
|
||||
```bash
|
||||
npm install balanced-match
|
||||
```
|
||||
|
||||
## Security contact information
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
## License
|
||||
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
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.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
module.exports = balanced;
|
||||
function balanced(a, b, str) {
|
||||
if (a instanceof RegExp) a = maybeMatch(a, str);
|
||||
if (b instanceof RegExp) b = maybeMatch(b, str);
|
||||
|
||||
var r = range(a, b, str);
|
||||
|
||||
return r && {
|
||||
start: r[0],
|
||||
end: r[1],
|
||||
pre: str.slice(0, r[0]),
|
||||
body: str.slice(r[0] + a.length, r[1]),
|
||||
post: str.slice(r[1] + b.length)
|
||||
};
|
||||
}
|
||||
|
||||
function maybeMatch(reg, str) {
|
||||
var m = str.match(reg);
|
||||
return m ? m[0] : null;
|
||||
}
|
||||
|
||||
balanced.range = range;
|
||||
function range(a, b, str) {
|
||||
var begs, beg, left, right, result;
|
||||
var ai = str.indexOf(a);
|
||||
var bi = str.indexOf(b, ai + 1);
|
||||
var i = ai;
|
||||
|
||||
if (ai >= 0 && bi > 0) {
|
||||
if(a===b) {
|
||||
return [ai, bi];
|
||||
}
|
||||
begs = [];
|
||||
left = str.length;
|
||||
|
||||
while (i >= 0 && !result) {
|
||||
if (i == ai) {
|
||||
begs.push(i);
|
||||
ai = str.indexOf(a, i + 1);
|
||||
} else if (begs.length == 1) {
|
||||
result = [ begs.pop(), bi ];
|
||||
} else {
|
||||
beg = begs.pop();
|
||||
if (beg < left) {
|
||||
left = beg;
|
||||
right = bi;
|
||||
}
|
||||
|
||||
bi = str.indexOf(b, i + 1);
|
||||
}
|
||||
|
||||
i = ai < bi && ai >= 0 ? ai : bi;
|
||||
}
|
||||
|
||||
if (begs.length) {
|
||||
result = [ left, right ];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "balanced-match",
|
||||
"description": "Match balanced character pairs, like \"{\" and \"}\"",
|
||||
"version": "1.0.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/juliangruber/balanced-match.git"
|
||||
},
|
||||
"homepage": "https://github.com/juliangruber/balanced-match",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "tape test/test.js",
|
||||
"bench": "matcha test/bench.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"matcha": "^0.7.0",
|
||||
"tape": "^4.6.0"
|
||||
},
|
||||
"keywords": [
|
||||
"match",
|
||||
"regexp",
|
||||
"test",
|
||||
"balanced",
|
||||
"parse"
|
||||
],
|
||||
"author": {
|
||||
"name": "Julian Gruber",
|
||||
"email": "mail@juliangruber.com",
|
||||
"url": "http://juliangruber.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"testling": {
|
||||
"files": "test/*.js",
|
||||
"browsers": [
|
||||
"ie/8..latest",
|
||||
"firefox/20..latest",
|
||||
"firefox/nightly",
|
||||
"chrome/25..latest",
|
||||
"chrome/canary",
|
||||
"opera/12..latest",
|
||||
"opera/next",
|
||||
"safari/5.1..latest",
|
||||
"ipad/6.0..latest",
|
||||
"iphone/6.0..latest",
|
||||
"android-browser/4.2..latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Contributing
|
||||
|
||||
This repository uses [cr.joyent.us](https://cr.joyent.us) (Gerrit) for new
|
||||
changes. Anyone can submit changes. To get started, see the [cr.joyent.us user
|
||||
guide](https://github.com/joyent/joyent-gerrit/blob/master/docs/user/README.md).
|
||||
This repo does not use GitHub pull requests.
|
||||
|
||||
See the [Joyent Engineering
|
||||
Guidelines](https://github.com/joyent/eng/blob/master/docs/index.md) for general
|
||||
best practices expected in this repository.
|
||||
|
||||
If you're changing something non-trivial or user-facing, you may want to submit
|
||||
an issue first.
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
The Blowfish portions are under the following license:
|
||||
|
||||
Blowfish block cipher for OpenBSD
|
||||
Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
|
||||
All rights reserved.
|
||||
|
||||
Implementation advice by David Mazieres <dm@lcs.mit.edu>.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
The bcrypt_pbkdf portions are under the following license:
|
||||
|
||||
Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Performance improvements (Javascript-specific):
|
||||
|
||||
Copyright 2016, Joyent Inc
|
||||
Author: Alex Wilson <alex.wilson@joyent.com>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
Port of the OpenBSD `bcrypt_pbkdf` function to pure Javascript. `npm`-ified
|
||||
version of [Devi Mandiri's port](https://github.com/devi/tmp/blob/master/js/bcrypt_pbkdf.js),
|
||||
with some minor performance improvements. The code is copied verbatim (and
|
||||
un-styled) from Devi's work.
|
||||
|
||||
This product includes software developed by Niels Provos.
|
||||
|
||||
## API
|
||||
|
||||
### `bcrypt_pbkdf.pbkdf(pass, passlen, salt, saltlen, key, keylen, rounds)`
|
||||
|
||||
Derive a cryptographic key of arbitrary length from a given password and salt,
|
||||
using the OpenBSD `bcrypt_pbkdf` function. This is a combination of Blowfish and
|
||||
SHA-512.
|
||||
|
||||
See [this article](http://www.tedunangst.com/flak/post/bcrypt-pbkdf) for
|
||||
further information.
|
||||
|
||||
Parameters:
|
||||
|
||||
* `pass`, a Uint8Array of length `passlen`
|
||||
* `passlen`, an integer Number
|
||||
* `salt`, a Uint8Array of length `saltlen`
|
||||
* `saltlen`, an integer Number
|
||||
* `key`, a Uint8Array of length `keylen`, will be filled with output
|
||||
* `keylen`, an integer Number
|
||||
* `rounds`, an integer Number, number of rounds of the PBKDF to run
|
||||
|
||||
### `bcrypt_pbkdf.hash(sha2pass, sha2salt, out)`
|
||||
|
||||
Calculate a Blowfish hash, given SHA2-512 output of a password and salt. Used as
|
||||
part of the inner round function in the PBKDF.
|
||||
|
||||
Parameters:
|
||||
|
||||
* `sha2pass`, a Uint8Array of length 64
|
||||
* `sha2salt`, a Uint8Array of length 64
|
||||
* `out`, a Uint8Array of length 32, will be filled with output
|
||||
|
||||
## License
|
||||
|
||||
This source form is a 1:1 port from the OpenBSD `blowfish.c` and `bcrypt_pbkdf.c`.
|
||||
As a result, it retains the original copyright and license. The two files are
|
||||
under slightly different (but compatible) licenses, and are here combined in
|
||||
one file. For each of the full license texts see `LICENSE`.
|
||||
+556
@@ -0,0 +1,556 @@
|
||||
'use strict';
|
||||
|
||||
var crypto_hash_sha512 = require('tweetnacl').lowlevel.crypto_hash;
|
||||
|
||||
/*
|
||||
* This file is a 1:1 port from the OpenBSD blowfish.c and bcrypt_pbkdf.c. As a
|
||||
* result, it retains the original copyright and license. The two files are
|
||||
* under slightly different (but compatible) licenses, and are here combined in
|
||||
* one file.
|
||||
*
|
||||
* Credit for the actual porting work goes to:
|
||||
* Devi Mandiri <me@devi.web.id>
|
||||
*/
|
||||
|
||||
/*
|
||||
* The Blowfish portions are under the following license:
|
||||
*
|
||||
* Blowfish block cipher for OpenBSD
|
||||
* Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Implementation advice by David Mazieres <dm@lcs.mit.edu>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The bcrypt_pbkdf portions are under the following license:
|
||||
*
|
||||
* Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Performance improvements (Javascript-specific):
|
||||
*
|
||||
* Copyright 2016, Joyent Inc
|
||||
* Author: Alex Wilson <alex.wilson@joyent.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// Ported from OpenBSD bcrypt_pbkdf.c v1.9
|
||||
|
||||
var BLF_J = 0;
|
||||
|
||||
var Blowfish = function() {
|
||||
this.S = [
|
||||
new Uint32Array([
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
|
||||
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
|
||||
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
|
||||
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
|
||||
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
|
||||
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
|
||||
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
|
||||
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
|
||||
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
|
||||
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
|
||||
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
|
||||
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
|
||||
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
|
||||
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
|
||||
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
|
||||
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
|
||||
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
|
||||
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
|
||||
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
|
||||
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
|
||||
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a]),
|
||||
new Uint32Array([
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
|
||||
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
|
||||
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
|
||||
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
|
||||
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
|
||||
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
|
||||
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
|
||||
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
|
||||
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
|
||||
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
|
||||
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
|
||||
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
|
||||
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
|
||||
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
|
||||
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
|
||||
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
|
||||
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
|
||||
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
|
||||
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
|
||||
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
|
||||
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
|
||||
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7]),
|
||||
new Uint32Array([
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
|
||||
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
|
||||
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
|
||||
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
|
||||
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
|
||||
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
|
||||
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
|
||||
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
|
||||
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
|
||||
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
|
||||
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
|
||||
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
|
||||
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
|
||||
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
|
||||
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
|
||||
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
|
||||
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
|
||||
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
|
||||
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
|
||||
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
|
||||
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
|
||||
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0]),
|
||||
new Uint32Array([
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
|
||||
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
|
||||
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
|
||||
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
|
||||
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
|
||||
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
|
||||
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
|
||||
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
|
||||
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
|
||||
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
|
||||
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
|
||||
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
|
||||
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
|
||||
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
|
||||
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
|
||||
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
|
||||
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
|
||||
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
|
||||
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
|
||||
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
|
||||
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
|
||||
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6])
|
||||
];
|
||||
this.P = new Uint32Array([
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b]);
|
||||
};
|
||||
|
||||
function F(S, x8, i) {
|
||||
return (((S[0][x8[i+3]] +
|
||||
S[1][x8[i+2]]) ^
|
||||
S[2][x8[i+1]]) +
|
||||
S[3][x8[i]]);
|
||||
};
|
||||
|
||||
Blowfish.prototype.encipher = function(x, x8) {
|
||||
if (x8 === undefined) {
|
||||
x8 = new Uint8Array(x.buffer);
|
||||
if (x.byteOffset !== 0)
|
||||
x8 = x8.subarray(x.byteOffset);
|
||||
}
|
||||
x[0] ^= this.P[0];
|
||||
for (var i = 1; i < 16; i += 2) {
|
||||
x[1] ^= F(this.S, x8, 0) ^ this.P[i];
|
||||
x[0] ^= F(this.S, x8, 4) ^ this.P[i+1];
|
||||
}
|
||||
var t = x[0];
|
||||
x[0] = x[1] ^ this.P[17];
|
||||
x[1] = t;
|
||||
};
|
||||
|
||||
Blowfish.prototype.decipher = function(x) {
|
||||
var x8 = new Uint8Array(x.buffer);
|
||||
if (x.byteOffset !== 0)
|
||||
x8 = x8.subarray(x.byteOffset);
|
||||
x[0] ^= this.P[17];
|
||||
for (var i = 16; i > 0; i -= 2) {
|
||||
x[1] ^= F(this.S, x8, 0) ^ this.P[i];
|
||||
x[0] ^= F(this.S, x8, 4) ^ this.P[i-1];
|
||||
}
|
||||
var t = x[0];
|
||||
x[0] = x[1] ^ this.P[0];
|
||||
x[1] = t;
|
||||
};
|
||||
|
||||
function stream2word(data, databytes){
|
||||
var i, temp = 0;
|
||||
for (i = 0; i < 4; i++, BLF_J++) {
|
||||
if (BLF_J >= databytes) BLF_J = 0;
|
||||
temp = (temp << 8) | data[BLF_J];
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
Blowfish.prototype.expand0state = function(key, keybytes) {
|
||||
var d = new Uint32Array(2), i, k;
|
||||
var d8 = new Uint8Array(d.buffer);
|
||||
|
||||
for (i = 0, BLF_J = 0; i < 18; i++) {
|
||||
this.P[i] ^= stream2word(key, keybytes);
|
||||
}
|
||||
BLF_J = 0;
|
||||
|
||||
for (i = 0; i < 18; i += 2) {
|
||||
this.encipher(d, d8);
|
||||
this.P[i] = d[0];
|
||||
this.P[i+1] = d[1];
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (k = 0; k < 256; k += 2) {
|
||||
this.encipher(d, d8);
|
||||
this.S[i][k] = d[0];
|
||||
this.S[i][k+1] = d[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blowfish.prototype.expandstate = function(data, databytes, key, keybytes) {
|
||||
var d = new Uint32Array(2), i, k;
|
||||
|
||||
for (i = 0, BLF_J = 0; i < 18; i++) {
|
||||
this.P[i] ^= stream2word(key, keybytes);
|
||||
}
|
||||
|
||||
for (i = 0, BLF_J = 0; i < 18; i += 2) {
|
||||
d[0] ^= stream2word(data, databytes);
|
||||
d[1] ^= stream2word(data, databytes);
|
||||
this.encipher(d);
|
||||
this.P[i] = d[0];
|
||||
this.P[i+1] = d[1];
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (k = 0; k < 256; k += 2) {
|
||||
d[0] ^= stream2word(data, databytes);
|
||||
d[1] ^= stream2word(data, databytes);
|
||||
this.encipher(d);
|
||||
this.S[i][k] = d[0];
|
||||
this.S[i][k+1] = d[1];
|
||||
}
|
||||
}
|
||||
BLF_J = 0;
|
||||
};
|
||||
|
||||
Blowfish.prototype.enc = function(data, blocks) {
|
||||
for (var i = 0; i < blocks; i++) {
|
||||
this.encipher(data.subarray(i*2));
|
||||
}
|
||||
};
|
||||
|
||||
Blowfish.prototype.dec = function(data, blocks) {
|
||||
for (var i = 0; i < blocks; i++) {
|
||||
this.decipher(data.subarray(i*2));
|
||||
}
|
||||
};
|
||||
|
||||
var BCRYPT_BLOCKS = 8,
|
||||
BCRYPT_HASHSIZE = 32;
|
||||
|
||||
function bcrypt_hash(sha2pass, sha2salt, out) {
|
||||
var state = new Blowfish(),
|
||||
cdata = new Uint32Array(BCRYPT_BLOCKS), i,
|
||||
ciphertext = new Uint8Array([79,120,121,99,104,114,111,109,97,116,105,
|
||||
99,66,108,111,119,102,105,115,104,83,119,97,116,68,121,110,97,109,
|
||||
105,116,101]); //"OxychromaticBlowfishSwatDynamite"
|
||||
|
||||
state.expandstate(sha2salt, 64, sha2pass, 64);
|
||||
for (i = 0; i < 64; i++) {
|
||||
state.expand0state(sha2salt, 64);
|
||||
state.expand0state(sha2pass, 64);
|
||||
}
|
||||
|
||||
for (i = 0; i < BCRYPT_BLOCKS; i++)
|
||||
cdata[i] = stream2word(ciphertext, ciphertext.byteLength);
|
||||
for (i = 0; i < 64; i++)
|
||||
state.enc(cdata, cdata.byteLength / 8);
|
||||
|
||||
for (i = 0; i < BCRYPT_BLOCKS; i++) {
|
||||
out[4*i+3] = cdata[i] >>> 24;
|
||||
out[4*i+2] = cdata[i] >>> 16;
|
||||
out[4*i+1] = cdata[i] >>> 8;
|
||||
out[4*i+0] = cdata[i];
|
||||
}
|
||||
};
|
||||
|
||||
function bcrypt_pbkdf(pass, passlen, salt, saltlen, key, keylen, rounds) {
|
||||
var sha2pass = new Uint8Array(64),
|
||||
sha2salt = new Uint8Array(64),
|
||||
out = new Uint8Array(BCRYPT_HASHSIZE),
|
||||
tmpout = new Uint8Array(BCRYPT_HASHSIZE),
|
||||
countsalt = new Uint8Array(saltlen+4),
|
||||
i, j, amt, stride, dest, count,
|
||||
origkeylen = keylen;
|
||||
|
||||
if (rounds < 1)
|
||||
return -1;
|
||||
if (passlen === 0 || saltlen === 0 || keylen === 0 ||
|
||||
keylen > (out.byteLength * out.byteLength) || saltlen > (1<<20))
|
||||
return -1;
|
||||
|
||||
stride = Math.floor((keylen + out.byteLength - 1) / out.byteLength);
|
||||
amt = Math.floor((keylen + stride - 1) / stride);
|
||||
|
||||
for (i = 0; i < saltlen; i++)
|
||||
countsalt[i] = salt[i];
|
||||
|
||||
crypto_hash_sha512(sha2pass, pass, passlen);
|
||||
|
||||
for (count = 1; keylen > 0; count++) {
|
||||
countsalt[saltlen+0] = count >>> 24;
|
||||
countsalt[saltlen+1] = count >>> 16;
|
||||
countsalt[saltlen+2] = count >>> 8;
|
||||
countsalt[saltlen+3] = count;
|
||||
|
||||
crypto_hash_sha512(sha2salt, countsalt, saltlen + 4);
|
||||
bcrypt_hash(sha2pass, sha2salt, tmpout);
|
||||
for (i = out.byteLength; i--;)
|
||||
out[i] = tmpout[i];
|
||||
|
||||
for (i = 1; i < rounds; i++) {
|
||||
crypto_hash_sha512(sha2salt, tmpout, tmpout.byteLength);
|
||||
bcrypt_hash(sha2pass, sha2salt, tmpout);
|
||||
for (j = 0; j < out.byteLength; j++)
|
||||
out[j] ^= tmpout[j];
|
||||
}
|
||||
|
||||
amt = Math.min(amt, keylen);
|
||||
for (i = 0; i < amt; i++) {
|
||||
dest = i * stride + (count - 1);
|
||||
if (dest >= origkeylen)
|
||||
break;
|
||||
key[dest] = out[i];
|
||||
}
|
||||
keylen -= i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
BLOCKS: BCRYPT_BLOCKS,
|
||||
HASHSIZE: BCRYPT_HASHSIZE,
|
||||
hash: bcrypt_hash,
|
||||
pbkdf: bcrypt_pbkdf
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "bcrypt-pbkdf",
|
||||
"version": "1.0.2",
|
||||
"description": "Port of the OpenBSD bcrypt_pbkdf function to pure JS",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/joyent/node-bcrypt-pbkdf.git"
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"license": "BSD-3-Clause"
|
||||
}
|
||||
+2393
File diff suppressed because it is too large
Load Diff
+1453
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+24
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
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 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.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
||||
+589
@@ -0,0 +1,589 @@
|
||||
# BigInteger.js [![Build Status][travis-img]][travis-url] [![Coverage Status][coveralls-img]][coveralls-url] [![Monthly Downloads][downloads-img]][downloads-url]
|
||||
|
||||
[travis-url]: https://travis-ci.org/peterolson/BigInteger.js
|
||||
[travis-img]: https://travis-ci.org/peterolson/BigInteger.js.svg?branch=master
|
||||
[coveralls-url]: https://coveralls.io/github/peterolson/BigInteger.js?branch=master
|
||||
[coveralls-img]: https://coveralls.io/repos/peterolson/BigInteger.js/badge.svg?branch=master&service=github
|
||||
[downloads-url]: https://www.npmjs.com/package/big-integer
|
||||
[downloads-img]: https://img.shields.io/npm/dm/big-integer.svg
|
||||
|
||||
**BigInteger.js** is an arbitrary-length integer library for Javascript, allowing arithmetic operations on integers of unlimited size, notwithstanding memory and time limitations.
|
||||
|
||||
**Update (December 2, 2018):** [`BigInt` is being added as a native feature of JavaScript](https://tc39.github.io/proposal-bigint/). This library now works as a polyfill: if the environment supports the native `BigInt`, this library acts as a thin wrapper over the native implementation.
|
||||
|
||||
## Installation
|
||||
|
||||
If you are using a browser, you can download [BigInteger.js from GitHub](http://peterolson.github.com/BigInteger.js/BigInteger.min.js) or just hotlink to it:
|
||||
|
||||
<script src="https://peterolson.github.io/BigInteger.js/BigInteger.min.js"></script>
|
||||
|
||||
If you are using node, you can install BigInteger with [npm](https://npmjs.org/).
|
||||
|
||||
npm install big-integer
|
||||
|
||||
Then you can include it in your code:
|
||||
|
||||
var bigInt = require("big-integer");
|
||||
|
||||
|
||||
## Usage
|
||||
### `bigInt(number, [base], [alphabet], [caseSensitive])`
|
||||
|
||||
You can create a bigInt by calling the `bigInt` function. You can pass in
|
||||
|
||||
- a string, which it will parse as an bigInt and throw an `"Invalid integer"` error if the parsing fails.
|
||||
- a Javascript number, which it will parse as an bigInt and throw an `"Invalid integer"` error if the parsing fails.
|
||||
- another bigInt.
|
||||
- nothing, and it will return `bigInt.zero`.
|
||||
|
||||
If you provide a second parameter, then it will parse `number` as a number in base `base`. Note that `base` can be any bigInt (even negative or zero). The letters "a-z" and "A-Z" will be interpreted as the numbers 10 to 35. Higher digits can be specified in angle brackets (`<` and `>`). The default `base` is `10`.
|
||||
|
||||
You can specify a custom alphabet for base conversion with the third parameter. The default `alphabet` is `"0123456789abcdefghijklmnopqrstuvwxyz"`.
|
||||
|
||||
The fourth parameter specifies whether or not the number string should be case-sensitive, i.e. whether `a` and `A` should be treated as different digits. By default `caseSensitive` is `false`.
|
||||
|
||||
Examples:
|
||||
|
||||
var zero = bigInt();
|
||||
var ninetyThree = bigInt(93);
|
||||
var largeNumber = bigInt("75643564363473453456342378564387956906736546456235345");
|
||||
var googol = bigInt("1e100");
|
||||
var bigNumber = bigInt(largeNumber);
|
||||
|
||||
var maximumByte = bigInt("FF", 16);
|
||||
var fiftyFiveGoogol = bigInt("<55>0", googol);
|
||||
|
||||
Note that Javascript numbers larger than `9007199254740992` and smaller than `-9007199254740992` are not precisely represented numbers and will not produce exact results. If you are dealing with numbers outside that range, it is better to pass in strings.
|
||||
|
||||
### Method Chaining
|
||||
|
||||
Note that bigInt operations return bigInts, which allows you to chain methods, for example:
|
||||
|
||||
var salary = bigInt(dollarsPerHour).times(hoursWorked).plus(randomBonuses)
|
||||
|
||||
### Constants
|
||||
|
||||
There are three named constants already stored that you do not have to construct with the `bigInt` function yourself:
|
||||
|
||||
- `bigInt.one`, equivalent to `bigInt(1)`
|
||||
- `bigInt.zero`, equivalent to `bigInt(0)`
|
||||
- `bigInt.minusOne`, equivalent to `bigInt(-1)`
|
||||
|
||||
The numbers from -999 to 999 are also already prestored and can be accessed using `bigInt[index]`, for example:
|
||||
|
||||
- `bigInt[-999]`, equivalent to `bigInt(-999)`
|
||||
- `bigInt[256]`, equivalent to `bigInt(256)`
|
||||
|
||||
### Methods
|
||||
|
||||
#### `abs()`
|
||||
|
||||
Returns the absolute value of a bigInt.
|
||||
|
||||
- `bigInt(-45).abs()` => `45`
|
||||
- `bigInt(45).abs()` => `45`
|
||||
|
||||
#### `add(number)`
|
||||
|
||||
Performs addition.
|
||||
|
||||
- `bigInt(5).add(7)` => `12`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Addition)
|
||||
|
||||
#### `and(number)`
|
||||
|
||||
Performs the bitwise AND operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement).
|
||||
|
||||
- `bigInt(6).and(3)` => `2`
|
||||
- `bigInt(6).and(-3)` => `4`
|
||||
|
||||
#### `bitLength()`
|
||||
|
||||
Returns the number of digits required to represent a bigInt in binary.
|
||||
|
||||
- `bigInt(5)` => `3` (since 5 is `101` in binary, which is three digits long)
|
||||
|
||||
#### `compare(number)`
|
||||
|
||||
Performs a comparison between two numbers. If the numbers are equal, it returns `0`. If the first number is greater, it returns `1`. If the first number is lesser, it returns `-1`.
|
||||
|
||||
- `bigInt(5).compare(5)` => `0`
|
||||
- `bigInt(5).compare(4)` => `1`
|
||||
- `bigInt(4).compare(5)` => `-1`
|
||||
|
||||
#### `compareAbs(number)`
|
||||
|
||||
Performs a comparison between the absolute value of two numbers.
|
||||
|
||||
- `bigInt(5).compareAbs(-5)` => `0`
|
||||
- `bigInt(5).compareAbs(4)` => `1`
|
||||
- `bigInt(4).compareAbs(-5)` => `-1`
|
||||
|
||||
#### `compareTo(number)`
|
||||
|
||||
Alias for the `compare` method.
|
||||
|
||||
#### `divide(number)`
|
||||
|
||||
Performs integer division, disregarding the remainder.
|
||||
|
||||
- `bigInt(59).divide(5)` => `11`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division)
|
||||
|
||||
#### `divmod(number)`
|
||||
|
||||
Performs division and returns an object with two properties: `quotient` and `remainder`. The sign of the remainder will match the sign of the dividend.
|
||||
|
||||
- `bigInt(59).divmod(5)` => `{quotient: bigInt(11), remainder: bigInt(4) }`
|
||||
- `bigInt(-5).divmod(2)` => `{quotient: bigInt(-2), remainder: bigInt(-1) }`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division)
|
||||
|
||||
#### `eq(number)`
|
||||
|
||||
Alias for the `equals` method.
|
||||
|
||||
#### `equals(number)`
|
||||
|
||||
Checks if two numbers are equal.
|
||||
|
||||
- `bigInt(5).equals(5)` => `true`
|
||||
- `bigInt(4).equals(7)` => `false`
|
||||
|
||||
#### `geq(number)`
|
||||
|
||||
Alias for the `greaterOrEquals` method.
|
||||
|
||||
|
||||
#### `greater(number)`
|
||||
|
||||
Checks if the first number is greater than the second.
|
||||
|
||||
- `bigInt(5).greater(6)` => `false`
|
||||
- `bigInt(5).greater(5)` => `false`
|
||||
- `bigInt(5).greater(4)` => `true`
|
||||
|
||||
#### `greaterOrEquals(number)`
|
||||
|
||||
Checks if the first number is greater than or equal to the second.
|
||||
|
||||
- `bigInt(5).greaterOrEquals(6)` => `false`
|
||||
- `bigInt(5).greaterOrEquals(5)` => `true`
|
||||
- `bigInt(5).greaterOrEquals(4)` => `true`
|
||||
|
||||
#### `gt(number)`
|
||||
|
||||
Alias for the `greater` method.
|
||||
|
||||
#### `isDivisibleBy(number)`
|
||||
|
||||
Returns `true` if the first number is divisible by the second number, `false` otherwise.
|
||||
|
||||
- `bigInt(999).isDivisibleBy(333)` => `true`
|
||||
- `bigInt(99).isDivisibleBy(5)` => `false`
|
||||
|
||||
#### `isEven()`
|
||||
|
||||
Returns `true` if the number is even, `false` otherwise.
|
||||
|
||||
- `bigInt(6).isEven()` => `true`
|
||||
- `bigInt(3).isEven()` => `false`
|
||||
|
||||
#### `isNegative()`
|
||||
|
||||
Returns `true` if the number is negative, `false` otherwise.
|
||||
Returns `false` for `0` and `-0`.
|
||||
|
||||
- `bigInt(-23).isNegative()` => `true`
|
||||
- `bigInt(50).isNegative()` => `false`
|
||||
|
||||
#### `isOdd()`
|
||||
|
||||
Returns `true` if the number is odd, `false` otherwise.
|
||||
|
||||
- `bigInt(13).isOdd()` => `true`
|
||||
- `bigInt(40).isOdd()` => `false`
|
||||
|
||||
#### `isPositive()`
|
||||
|
||||
Return `true` if the number is positive, `false` otherwise.
|
||||
Returns `false` for `0` and `-0`.
|
||||
|
||||
- `bigInt(54).isPositive()` => `true`
|
||||
- `bigInt(-1).isPositive()` => `false`
|
||||
|
||||
#### `isPrime(strict?)`
|
||||
|
||||
Returns `true` if the number is prime, `false` otherwise.
|
||||
Set "strict" boolean to true to force GRH-supported lower bound of 2*log(N)^2.
|
||||
|
||||
- `bigInt(5).isPrime()` => `true`
|
||||
- `bigInt(6).isPrime()` => `false`
|
||||
|
||||
#### `isProbablePrime([iterations], [rng])`
|
||||
|
||||
Returns `true` if the number is very likely to be prime, `false` otherwise.
|
||||
Supplying `iterations` is optional - it determines the number of iterations of the test (default: `5`). The more iterations, the lower chance of getting a false positive.
|
||||
This uses the [Miller Rabin test](https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test).
|
||||
|
||||
- `bigInt(5).isProbablePrime()` => `true`
|
||||
- `bigInt(49).isProbablePrime()` => `false`
|
||||
- `bigInt(1729).isProbablePrime()` => `false`
|
||||
|
||||
Note that this function is not deterministic, since it relies on random sampling of factors, so the result for some numbers is not always the same - unless you pass a predictable random number generator as `rng`. The behavior and requirements are the same as with `randBetween`.
|
||||
|
||||
- `bigInt(1729).isProbablePrime(1, () => 0.1)` => `false`
|
||||
- `bigInt(1729).isProbablePrime(1, () => 0.2)` => `true`
|
||||
|
||||
If the number is composite then the Miller–Rabin primality test declares the number probably prime with a probability at most `4` to the power `−iterations`.
|
||||
If the number is prime, this function always returns `true`.
|
||||
|
||||
#### `isUnit()`
|
||||
|
||||
Returns `true` if the number is `1` or `-1`, `false` otherwise.
|
||||
|
||||
- `bigInt.one.isUnit()` => `true`
|
||||
- `bigInt.minusOne.isUnit()` => `true`
|
||||
- `bigInt(5).isUnit()` => `false`
|
||||
|
||||
#### `isZero()`
|
||||
|
||||
Return `true` if the number is `0` or `-0`, `false` otherwise.
|
||||
|
||||
- `bigInt.zero.isZero()` => `true`
|
||||
- `bigInt("-0").isZero()` => `true`
|
||||
- `bigInt(50).isZero()` => `false`
|
||||
|
||||
#### `leq(number)`
|
||||
|
||||
Alias for the `lesserOrEquals` method.
|
||||
|
||||
#### `lesser(number)`
|
||||
|
||||
Checks if the first number is lesser than the second.
|
||||
|
||||
- `bigInt(5).lesser(6)` => `true`
|
||||
- `bigInt(5).lesser(5)` => `false`
|
||||
- `bigInt(5).lesser(4)` => `false`
|
||||
|
||||
#### `lesserOrEquals(number)`
|
||||
|
||||
Checks if the first number is less than or equal to the second.
|
||||
|
||||
- `bigInt(5).lesserOrEquals(6)` => `true`
|
||||
- `bigInt(5).lesserOrEquals(5)` => `true`
|
||||
- `bigInt(5).lesserOrEquals(4)` => `false`
|
||||
|
||||
#### `lt(number)`
|
||||
|
||||
Alias for the `lesser` method.
|
||||
|
||||
#### `minus(number)`
|
||||
|
||||
Alias for the `subtract` method.
|
||||
|
||||
- `bigInt(3).minus(5)` => `-2`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Subtraction)
|
||||
|
||||
#### `mod(number)`
|
||||
|
||||
Performs division and returns the remainder, disregarding the quotient. The sign of the remainder will match the sign of the dividend.
|
||||
|
||||
- `bigInt(59).mod(5)` => `4`
|
||||
- `bigInt(-5).mod(2)` => `-1`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division)
|
||||
|
||||
#### `modInv(mod)`
|
||||
|
||||
Finds the [multiplicative inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of the number modulo `mod`.
|
||||
|
||||
- `bigInt(3).modInv(11)` => `4`
|
||||
- `bigInt(42).modInv(2017)` => `1969`
|
||||
|
||||
#### `modPow(exp, mod)`
|
||||
|
||||
Takes the number to the power `exp` modulo `mod`.
|
||||
|
||||
- `bigInt(10).modPow(3, 30)` => `10`
|
||||
|
||||
#### `multiply(number)`
|
||||
|
||||
Performs multiplication.
|
||||
|
||||
- `bigInt(111).multiply(111)` => `12321`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Multiplication)
|
||||
|
||||
#### `neq(number)`
|
||||
|
||||
Alias for the `notEquals` method.
|
||||
|
||||
#### `next()`
|
||||
|
||||
Adds one to the number.
|
||||
|
||||
- `bigInt(6).next()` => `7`
|
||||
|
||||
#### `not()`
|
||||
|
||||
Performs the bitwise NOT operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement).
|
||||
|
||||
- `bigInt(10).not()` => `-11`
|
||||
- `bigInt(0).not()` => `-1`
|
||||
|
||||
#### `notEquals(number)`
|
||||
|
||||
Checks if two numbers are not equal.
|
||||
|
||||
- `bigInt(5).notEquals(5)` => `false`
|
||||
- `bigInt(4).notEquals(7)` => `true`
|
||||
|
||||
#### `or(number)`
|
||||
|
||||
Performs the bitwise OR operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement).
|
||||
|
||||
- `bigInt(13).or(10)` => `15`
|
||||
- `bigInt(13).or(-8)` => `-3`
|
||||
|
||||
#### `over(number)`
|
||||
|
||||
Alias for the `divide` method.
|
||||
|
||||
- `bigInt(59).over(5)` => `11`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division)
|
||||
|
||||
#### `plus(number)`
|
||||
|
||||
Alias for the `add` method.
|
||||
|
||||
- `bigInt(5).plus(7)` => `12`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Addition)
|
||||
|
||||
#### `pow(number)`
|
||||
|
||||
Performs exponentiation. If the exponent is less than `0`, `pow` returns `0`. `bigInt.zero.pow(0)` returns `1`.
|
||||
|
||||
- `bigInt(16).pow(16)` => `18446744073709551616`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Exponentiation)
|
||||
|
||||
#### `prev(number)`
|
||||
|
||||
Subtracts one from the number.
|
||||
|
||||
- `bigInt(6).prev()` => `5`
|
||||
|
||||
#### `remainder(number)`
|
||||
|
||||
Alias for the `mod` method.
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division)
|
||||
|
||||
#### `shiftLeft(n)`
|
||||
|
||||
Shifts the number left by `n` places in its binary representation. If a negative number is provided, it will shift right. Throws an error if `n` is outside of the range `[-9007199254740992, 9007199254740992]`.
|
||||
|
||||
- `bigInt(8).shiftLeft(2)` => `32`
|
||||
- `bigInt(8).shiftLeft(-2)` => `2`
|
||||
|
||||
#### `shiftRight(n)`
|
||||
|
||||
Shifts the number right by `n` places in its binary representation. If a negative number is provided, it will shift left. Throws an error if `n` is outside of the range `[-9007199254740992, 9007199254740992]`.
|
||||
|
||||
- `bigInt(8).shiftRight(2)` => `2`
|
||||
- `bigInt(8).shiftRight(-2)` => `32`
|
||||
|
||||
#### `square()`
|
||||
|
||||
Squares the number
|
||||
|
||||
- `bigInt(3).square()` => `9`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Squaring)
|
||||
|
||||
#### `subtract(number)`
|
||||
|
||||
Performs subtraction.
|
||||
|
||||
- `bigInt(3).subtract(5)` => `-2`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Subtraction)
|
||||
|
||||
#### `times(number)`
|
||||
|
||||
Alias for the `multiply` method.
|
||||
|
||||
- `bigInt(111).times(111)` => `12321`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Multiplication)
|
||||
|
||||
#### `toArray(radix)`
|
||||
|
||||
Converts a bigInt into an object with the properties "value" and "isNegative." "Value" is an array of integers modulo the given radix. "isNegative" is a boolean that represents the sign of the result.
|
||||
|
||||
- `bigInt("1e9").toArray(10)` => {
|
||||
value: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
isNegative: false
|
||||
}
|
||||
- `bigInt("1e9").toArray(16)` => {
|
||||
value: [3, 11, 9, 10, 12, 10, 0, 0],
|
||||
isNegative: false
|
||||
}
|
||||
- `bigInt(567890).toArray(100)` => {
|
||||
value: [56, 78, 90],
|
||||
isNegative: false
|
||||
}
|
||||
|
||||
Negative bases are supported.
|
||||
|
||||
- `bigInt(12345).toArray(-10)` => {
|
||||
value: [2, 8, 4, 6, 5],
|
||||
isNegative: false
|
||||
}
|
||||
|
||||
Base 1 and base -1 are also supported.
|
||||
|
||||
- `bigInt(-15).toArray(1)` => {
|
||||
value: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
isNegative: true
|
||||
}
|
||||
- `bigInt(-15).toArray(-1)` => {
|
||||
value: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
|
||||
isNegative: false
|
||||
}
|
||||
|
||||
Base 0 is only allowed for the number zero.
|
||||
|
||||
- `bigInt(0).toArray(0)` => {
|
||||
value: [0],
|
||||
isNegative: false
|
||||
}
|
||||
- `bigInt(1).toArray(0)` => `Error: Cannot convert nonzero numbers to base 0.`
|
||||
|
||||
#### `toJSNumber()`
|
||||
|
||||
Converts a bigInt into a native Javascript number. Loses precision for numbers outside the range `[-9007199254740992, 9007199254740992]`.
|
||||
|
||||
- `bigInt("18446744073709551616").toJSNumber()` => `18446744073709552000`
|
||||
|
||||
#### `xor(number)`
|
||||
|
||||
Performs the bitwise XOR operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement).
|
||||
|
||||
- `bigInt(12).xor(5)` => `9`
|
||||
- `bigInt(12).xor(-5)` => `-9`
|
||||
|
||||
### Static Methods
|
||||
|
||||
#### `fromArray(digits, base = 10, isNegative?)`
|
||||
|
||||
Constructs a bigInt from an array of digits in base `base`. The optional `isNegative` flag will make the number negative.
|
||||
|
||||
- `bigInt.fromArray([1, 2, 3, 4, 5], 10)` => `12345`
|
||||
- `bigInt.fromArray([1, 0, 0], 2, true)` => `-4`
|
||||
|
||||
#### `gcd(a, b)`
|
||||
|
||||
Finds the greatest common denominator of `a` and `b`.
|
||||
|
||||
- `bigInt.gcd(42,56)` => `14`
|
||||
|
||||
#### `isInstance(x)`
|
||||
|
||||
Returns `true` if `x` is a BigInteger, `false` otherwise.
|
||||
|
||||
- `bigInt.isInstance(bigInt(14))` => `true`
|
||||
- `bigInt.isInstance(14)` => `false`
|
||||
|
||||
#### `lcm(a,b)`
|
||||
|
||||
Finds the least common multiple of `a` and `b`.
|
||||
|
||||
- `bigInt.lcm(21, 6)` => `42`
|
||||
|
||||
#### `max(a,b)`
|
||||
|
||||
Returns the largest of `a` and `b`.
|
||||
|
||||
- `bigInt.max(77, 432)` => `432`
|
||||
|
||||
#### `min(a,b)`
|
||||
|
||||
Returns the smallest of `a` and `b`.
|
||||
|
||||
- `bigInt.min(77, 432)` => `77`
|
||||
|
||||
#### `randBetween(min, max, [rng])`
|
||||
|
||||
Returns a random number between `min` and `max`, optionally using `rng` to generate randomness.
|
||||
|
||||
- `bigInt.randBetween("-1e100", "1e100")` => (for example) `8494907165436643479673097939554427056789510374838494147955756275846226209006506706784609314471378745`
|
||||
|
||||
`rng` should take no arguments and return a `number` between 0 and 1. It defaults to `Math.random`.
|
||||
|
||||
- `bigInt.randBetween("-1e100", "1e100", () => 0.5)` => (always) `50000005000000500000050000005000000500000050000005000000500000050000005000000500000050000005000000`
|
||||
|
||||
|
||||
### Override Methods
|
||||
|
||||
#### `toString(radix = 10, [alphabet])`
|
||||
|
||||
Converts a bigInt to a string. There is an optional radix parameter (which defaults to 10) that converts the number to the given radix. Digits in the range `10-35` will use the letters `a-z`.
|
||||
|
||||
- `bigInt("1e9").toString()` => `"1000000000"`
|
||||
- `bigInt("1e9").toString(16)` => `"3b9aca00"`
|
||||
|
||||
You can use a custom base alphabet with the second parameter. The default `alphabet` is `"0123456789abcdefghijklmnopqrstuvwxyz"`.
|
||||
|
||||
- `bigInt("5").toString(2, "aA")` => `"AaA"`
|
||||
|
||||
**Note that arithmetical operators will trigger the `valueOf` function rather than the `toString` function.** When converting a bigInteger to a string, you should use the `toString` method or the `String` function instead of adding the empty string.
|
||||
|
||||
- `bigInt("999999999999999999").toString()` => `"999999999999999999"`
|
||||
- `String(bigInt("999999999999999999"))` => `"999999999999999999"`
|
||||
- `bigInt("999999999999999999") + ""` => `1000000000000000000`
|
||||
|
||||
Bases larger than 36 are supported. If a digit is greater than or equal to 36, it will be enclosed in angle brackets.
|
||||
|
||||
- `bigInt(567890).toString(100)` => `"<56><78><90>"`
|
||||
|
||||
Negative bases are also supported.
|
||||
|
||||
- `bigInt(12345).toString(-10)` => `"28465"`
|
||||
|
||||
Base 1 and base -1 are also supported.
|
||||
|
||||
- `bigInt(-15).toString(1)` => `"-111111111111111"`
|
||||
- `bigInt(-15).toString(-1)` => `"101010101010101010101010101010"`
|
||||
|
||||
Base 0 is only allowed for the number zero.
|
||||
|
||||
- `bigInt(0).toString(0)` => `0`
|
||||
- `bigInt(1).toString(0)` => `Error: Cannot convert nonzero numbers to base 0.`
|
||||
|
||||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#toString)
|
||||
|
||||
#### `valueOf()`
|
||||
|
||||
Converts a bigInt to a native Javascript number. This override allows you to use native arithmetic operators without explicit conversion:
|
||||
|
||||
- `bigInt("100") + bigInt("200") === 300; //true`
|
||||
|
||||
## Contributors
|
||||
|
||||
To contribute, just fork the project, make some changes, and submit a pull request. Please verify that the unit tests pass before submitting.
|
||||
|
||||
The unit tests are contained in the `spec/spec.js` file. You can run them locally by opening the `spec/SpecRunner.html` or file or running `npm test`. You can also [run the tests online from GitHub](http://peterolson.github.io/BigInteger.js/spec/SpecRunner.html).
|
||||
|
||||
There are performance benchmarks that can be viewed from the `benchmarks/index.html` page. You can [run them online from GitHub](http://peterolson.github.io/BigInteger.js/benchmark/).
|
||||
|
||||
## License
|
||||
|
||||
This project is public domain. For more details, read about the [Unlicense](http://unlicense.org/).
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "big-integer",
|
||||
"description": "An arbitrary length integer library for Javascript",
|
||||
"main": "./BigInteger.js",
|
||||
"authors": [
|
||||
"Peter Olson"
|
||||
],
|
||||
"license": "Unlicense",
|
||||
"keywords": [
|
||||
"math",
|
||||
"big",
|
||||
"bignum",
|
||||
"bigint",
|
||||
"biginteger",
|
||||
"integer",
|
||||
"arbitrary",
|
||||
"precision",
|
||||
"arithmetic"
|
||||
],
|
||||
"homepage": "https://github.com/peterolson/BigInteger.js",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"coverage",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "big-integer",
|
||||
"version": "1.6.49",
|
||||
"author": "Peter Olson <peter.e.c.olson+npm@gmail.com>",
|
||||
"description": "An arbitrary length integer library for Javascript",
|
||||
"contributors": [],
|
||||
"bin": {},
|
||||
"scripts": {
|
||||
"test": "tsc && karma start my.conf.js && node spec/tsDefinitions.js",
|
||||
"minify": "uglifyjs BigInteger.js -o BigInteger.min.js"
|
||||
},
|
||||
"main": "./BigInteger",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:peterolson/BigInteger.js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"math",
|
||||
"big",
|
||||
"bignum",
|
||||
"bigint",
|
||||
"biginteger",
|
||||
"integer",
|
||||
"arbitrary",
|
||||
"precision",
|
||||
"arithmetic"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.118",
|
||||
"@types/node": "^7.10.2",
|
||||
"coveralls": "^3.0.6",
|
||||
"jasmine": "3.5.0",
|
||||
"jasmine-core": "^3.5.0",
|
||||
"karma": "^4.3.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage": "^2.0.1",
|
||||
"karma-jasmine": "^2.0.1",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"typescript": "^3.6.3",
|
||||
"uglifyjs": "^2.4.10"
|
||||
},
|
||||
"license": "Unlicense",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"typings": "./BigInteger.d.ts"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": false,
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"typeRoots": [
|
||||
"./"
|
||||
],
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": [
|
||||
"BigInteger.d.ts",
|
||||
"spec/tsDefinitions.ts"
|
||||
]
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.4
|
||||
- 0.6
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user